Merge remote-tracking branch 'aosp/upstream-master' into HEAD

Change-Id: I3bfaf72376866c93610dc6f8807edaeadc44ad5f
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..d7df7b3
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,6 @@
+TestService.java.txt binary
+TestServiceLite.java.txt binary
+TestServiceNano.java.txt binary
+TestDeprecatedService.java.txt binary
+TestDeprecatedServiceLite.java.txt binary
+TestDeprecatedServiceNano.java.txt binary
diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE
new file mode 100644
index 0000000..431fcb1
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE
@@ -0,0 +1,8 @@
+Please answer these questions before submitting your issue.
+
+### What version of gRPC are you using?
+
+
+### What did you expect to see?
+
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e82cb67
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+# Gradle
+build
+gradle.properties
+.gradle
+local.properties
+out
+
+# Maven (examples)
+target
+
+# Bazel
+bazel-bin
+bazel-examples
+bazel-genfiles
+bazel-grpc-java
+bazel-out
+bazel-testlogs
+
+# IntelliJ IDEA
+.idea
+*.iml
+
+# Eclipse
+.classpath
+.project
+.settings
+.gitignore
+bin
+
+# OS X
+.DS_Store
+
+# Emacs
+*~
+\#*\#
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..e3b59ff
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,66 @@
+sudo: false
+
+language: java
+
+env:
+  global:
+    - GRADLE_OPTS=-Xmx512m
+    - LDFLAGS=-L/tmp/protobuf/lib
+    - CXXFLAGS=-I/tmp/protobuf/include
+    - LD_LIBRARY_PATH=/tmp/protobuf/lib
+
+before_install:
+  - rm ~/.m2/settings.xml || true # Avoid repository.apache.org, which has QPS limits and is useless
+  - mkdir -p $HOME/.gradle/caches &&
+    ln -s /tmp/gradle-caches-modules-2 $HOME/.gradle/caches/modules-2
+  - mkdir -p $HOME/.gradle &&
+    ln -s /tmp/gradle-wrapper $HOME/.gradle/wrapper
+  - buildscripts/make_dependencies.sh # build protoc into /tmp/protobuf
+  - mkdir -p $HOME/.gradle
+  - echo "checkstyle.ignoreFailures=false" >> $HOME/.gradle/gradle.properties
+  - echo "failOnWarnings=true" >> $HOME/.gradle/gradle.properties
+  - echo "errorProne=true" >> $HOME/.gradle/gradle.properties
+
+install:
+  - ./gradlew assemble generateTestProto install
+  - pushd examples && ./gradlew build && popd
+  - pushd examples && mvn verify && popd
+
+before_script:
+  - test -z "$(git status --porcelain)" || (git status && echo Error Working directory is not clean. Forget to commit generated files? && false)
+
+script:
+  - ./gradlew check :grpc-all:jacocoTestReport
+
+after_success:
+    # Upload to coveralls once, instead of for each job in the matrix
+  - if [[ "$TRAVIS_JOB_NUMBER" == *.1 ]]; then ./gradlew :grpc-all:coveralls; fi
+  - bash <(curl -s https://codecov.io/bash)
+
+os:
+  - linux
+
+jdk:
+  # net.ltgt.errorprone supports jdk8 and jdk9, but has problems with jdk10
+  # For jdk10, we need to switch over to using net.ltgt.errorprone-javacplugin,
+  # and likely update to the latest com.google.errorprone:error_prone_core.
+  # We have decided not to make our build.gradle support both plugins, so when
+  # we finally move off of jdk8 and jdk9 we will need use the javac annotation
+  # processor based plugin.
+  - oraclejdk8  # if both jdk 8 and 9 are removed, migrate to net.ltgt.errorprone-javacplugin (see above comment)
+  - oraclejdk9  # if both jdk 8 and 9 are removed, migrate to net.ltgt.errorprone-javacplugin (see above comment)
+  - oraclejdk10
+
+notifications:
+  email: false
+
+cache:
+  directories:
+    - /tmp/protobuf-cache
+    - /tmp/gradle-caches-modules-2
+    - /tmp/gradle-wrapper
+
+before_cache:
+  # The lock changes based on folder name; normally $HOME/.gradle/caches/modules-2/modules-2.lock
+  - rm /tmp/gradle-caches-modules-2/gradle-caches-modules-2.lock
+  - find $HOME/.gradle/wrapper -not -name "*-all.zip" -and -not -name "*-bin.zip" -delete
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..e491a9e
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Google Inc.
diff --git a/BUILD.bazel b/BUILD.bazel
new file mode 100644
index 0000000..e4175b0
--- /dev/null
+++ b/BUILD.bazel
@@ -0,0 +1,26 @@
+# Copyright 2017 The gRPC Authors
+#
+# Licensed 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.
+
+load(":java_grpc_library.bzl", "java_grpc_library")
+
+java_proto_library(
+    name = "api_proto_java",
+    deps = ["@com_google_protobuf//:api_proto"],
+)
+
+java_grpc_library(
+    name = "java_grpc_library__external_repo_test",
+    srcs = ["@com_google_protobuf//:api_proto"],
+    deps = [":api_proto_java"],
+)
diff --git a/COMPILING.md b/COMPILING.md
new file mode 100644
index 0000000..f75023c
--- /dev/null
+++ b/COMPILING.md
@@ -0,0 +1,117 @@
+Building gRPC-Java
+==================
+
+Building is only necessary if you are making changes to gRPC-Java.
+
+Building requires JDK 8, as our tests use TLS.
+
+grpc-java has a C++ code generation plugin for protoc. Since many Java
+developers don't have C compilers installed and don't need to modify the
+codegen, the build can skip it. To skip, create the file
+`<project-root>/gradle.properties` and add `skipCodegen=true`.
+
+Then, to build, run:
+```
+$ ./gradlew build
+```
+
+To install the artifacts to your Maven local repository for use in your own
+project, run:
+```
+$ ./gradlew install
+```
+
+### Notes for IntelliJ
+Building in IntelliJ works best when you import the project as a Gradle project and delegate IDE 
+build/run actions to Gradle.
+
+You can find this setting at:
+```Settings -> Build, Execution, Deployment
+      -> Build Tools -> Gradle -> Runner
+      -> Delegate IDE build/run actions to gradle.
+```
+
+How to Build Code Generation Plugin
+-----------------------------------
+This section is only necessary if you are making changes to the code
+generation. Most users only need to use `skipCodegen=true` as discussed above.
+
+### Build Protobuf
+The codegen plugin is C++ code and requires protobuf 3.0.0 or later.
+
+For Linux, Mac and MinGW:
+```
+$ git clone https://github.com/google/protobuf.git
+$ cd protobuf
+$ git checkout v3.5.1
+$ ./autogen.sh
+$ ./configure
+$ make
+$ make check
+$ sudo make install
+```
+
+If you are comfortable with C++ compilation and autotools, you can specify a
+``--prefix`` for Protobuf and use ``-I`` in ``CXXFLAGS``, ``-L`` in
+``LDFLAGS``, ``LD_LIBRARY_PATH``, and ``PATH`` to reference it. The
+environment variables will be used when building grpc-java.
+
+Protobuf installs to ``/usr/local`` by default.
+
+For Visual C++, please refer to the [Protobuf README](https://github.com/google/protobuf/blob/master/cmake/README.md)
+for how to compile Protobuf. gRPC-java assumes a Release build.
+
+#### Linux and MinGW
+If ``/usr/local/lib`` is not in your library search path, you can add it by running:
+```
+$ sudo sh -c 'echo /usr/local/lib >> /etc/ld.so.conf'
+$ sudo ldconfig
+```
+
+#### Mac
+Some versions of Mac OS X (e.g., 10.10) doesn't have ``/usr/local`` in the
+default search paths for header files and libraries. It will fail the build of
+the codegen. To work around this, you will need to set environment variables:
+```
+$ export CXXFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib"
+```
+
+### Notes for Visual C++
+
+When building on Windows and VC++, you need to specify project properties for
+Gradle to find protobuf:
+```
+.\gradlew install ^
+    -PvcProtobufInclude=C:\path\to\protobuf-3.5.1\src ^
+    -PvcProtobufLibs=C:\path\to\protobuf-3.5.1\vsprojects\Release ^
+    -PtargetArch=x86_32
+```
+
+Since specifying those properties every build is bothersome, you can instead
+create ``<project-root>\gradle.properties`` with contents like:
+```
+vcProtobufInclude=C:\\path\\to\\protobuf-3.5.1\\src
+vcProtobufLibs=C:\\path\\to\\protobuf-3.5.1\\vsprojects\\Release
+targetArch=x86_32
+```
+
+By default, the build script will build the codegen for the same architecture as
+the Java runtime installed on your system. If you are using 64-bit JVM, the
+codegen will be compiled for 64-bit. Since Protobuf is only built for 32-bit by
+default, the `targetArch=x86_32` is necessary.
+
+### Notes for MinGW on Windows
+If you have both MinGW and VC++ installed on Windows, VC++ will be used by
+default. To override this default and use MinGW, add ``-PvcDisable=true``
+to your Gradle command line or add ``vcDisable=true`` to your
+``<project-root>\gradle.properties``.
+
+### Notes for Unsupported Operating Systems
+The build script pulls pre-compiled ``protoc`` from Maven Central by default.
+We have built ``protoc`` binaries for popular systems, but they may not work
+for your system. If ``protoc`` cannot be downloaded or would not run, you can
+use the one that has been built by your own, by adding this property to
+``<project-root>/gradle.properties``:
+```
+protoc=/path/to/protoc
+```
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..9027b8f
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,83 @@
+# How to contribute
+
+We definitely welcome your patches and contributions to gRPC!
+
+If you are new to github, please start by reading [Pull Request howto](https://help.github.com/articles/about-pull-requests/)
+
+## Legal requirements
+
+In order to protect both you and ourselves, you will need to sign the
+[Contributor License Agreement](https://identity.linuxfoundation.org/projects/cncf).
+
+## Compiling
+
+See [COMPILING.md](COMPILING.md). Specifically, you'll generally want to set
+`skipCodegen=true` so you don't need to deal with the C++ compilation.
+
+## Code style
+
+We follow the [Google Java Style
+Guide](https://google-styleguide.googlecode.com/svn/trunk/javaguide.html). Our
+build automatically will provide warnings for style issues.
+[Eclipse](https://raw.githubusercontent.com/google/styleguide/gh-pages/eclipse-java-google-style.xml)
+and
+[IntelliJ](https://raw.githubusercontent.com/google/styleguide/gh-pages/intellij-java-google-style.xml)
+style configurations are commonly useful. For IntelliJ 14, copy the style to
+`~/.IdeaIC14/config/codestyles/`, start IntelliJ, go to File > Settings > Code
+Style, and set the Scheme to `GoogleStyle`.
+
+## Maintaining clean commit history
+
+We have few conventions for keeping history clean and making code reviews easier
+for reviewers:
+
+* First line of commit messages should be in format of
+
+  `package-name: summary of change`
+
+  where the summary finishes the sentence: `This commit improves gRPC to ____________.`
+
+  for example:
+
+  `core,netty,interop-testing: add capacitive duractance to turbo encabulators`
+
+* Every time you receive a feedback on your pull request, push changes that
+  address it as a separate one or multiple commits with a descriptive commit
+  message (try avoid using vauge `addressed pr feedback` type of messages).
+
+  Project maintainers are obligated to squash those commits into one when
+  merging.
+
+## Running tests
+
+### Jetty ALPN setup for IntelliJ
+
+The tests in interop-testing project require jetty-alpn agent running in the background
+otherwise they'll fail. Here are instructions on how to setup IntellJ IDEA to enable running
+those tests in IDE:
+
+* Settings -> Build Tools -> Gradle -> Runner -> select Gradle Test Runner
+* View -> Tool Windows -> Gradle -> Edit Run Configuration -> Defaults -> JUnit -> Before lauch -> + -> Run Gradle task, enter the task in the build.gradle that sets the javaagent.
+
+Step 1 must be taken, otherwise by the default JUnit Test Runner running a single test in IDE will trigger all the tests.
+
+## Guidelines for Pull Requests
+How to get your contributions merged smoothly and quickly.
+ 
+- Create **small PRs** that are narrowly focused on **addressing a single concern**. We often times receive PRs that are trying to fix several things at a time, but only one fix is considered acceptable, nothing gets merged and both author's & review's time is wasted. Create more PRs to address different concerns and everyone will be happy.
+ 
+- For speculative changes, consider opening an issue and discussing it first. If you are suggesting a behavioral or API change, consider starting with a [gRFC proposal](https://github.com/grpc/proposal). 
+ 
+- Provide a good **PR description** as a record of **what** change is being made and **why** it was made. Link to a github issue if it exists.
+ 
+- Don't fix code style and formatting unless you are already changing that line to address an issue. PRs with irrelevant changes won't be merged. If you do want to fix formatting or style, do that in a separate PR.
+ 
+- Unless your PR is trivial, you should expect there will be reviewer comments that you'll need to address before merging. We expect you to be reasonably responsive to those comments, otherwise the PR will be closed after 2-3 weeks of inactivity.
+ 
+- Maintain **clean commit history** and use **meaningful commit messages**. See [maintaining clean commit history](#maintaining-clean-commit-history) for details.
+ 
+- Keep your PR up to date with upstream/master (if there are merge conflicts, we can't really merge your change).
+
+- **All tests need to be passing** before your change can be merged. We recommend you **run tests locally** before creating your PR to catch breakages early on. Also, `./gradlew build` (`gradlew build` on Windows) **must not introduce any new warnings**.
+ 
+- Exceptions to the rules can be made if there's a compelling reason for doing so.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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/NOTICE.txt b/NOTICE.txt
new file mode 100644
index 0000000..79a48ce
--- /dev/null
+++ b/NOTICE.txt
@@ -0,0 +1,36 @@
+Copyright 2014 The gRPC Authors
+
+Licensed 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.
+
+-----------------------------------------------------------------------
+
+This product contains a modified portion of 'OkHttp', an open source
+HTTP & SPDY client for Android and Java applications, which can be obtained
+at:
+
+  * LICENSE:
+    * okhttp/third_party/okhttp/LICENSE (Apache License 2.0)
+  * HOMEPAGE:
+    * https://github.com/square/okhttp
+  * LOCATION_IN_GRPC:
+    * okhttp/third_party/okhttp
+
+This product contains a modified portion of 'Netty', an open source
+networking library, which can be obtained at:
+
+  * LICENSE:
+    * netty/third_party/netty/LICENSE.txt (Apache License 2.0)
+  * HOMEPAGE:
+    * https://netty.io
+  * LOCATION_IN_GRPC:
+    * netty/third_party/netty
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bbede99
--- /dev/null
+++ b/README.md
@@ -0,0 +1,220 @@
+gRPC-Java - An RPC library and framework
+========================================
+
+gRPC-Java works with JDK 7. gRPC-Java clients are supported on Android API
+levels 14 and up (Ice Cream Sandwich and later). Deploying gRPC servers on an
+Android device is not supported.
+
+TLS usage typically requires using Java 8, or Play Services Dynamic Security
+Provider on Android. Please see the [Security Readme](SECURITY.md).
+
+<table>
+  <tr>
+    <td><b>Homepage:</b></td>
+    <td><a href="https://grpc.io/">grpc.io</a></td>
+  </tr>
+  <tr>
+    <td><b>Mailing List:</b></td>
+    <td><a href="https://groups.google.com/forum/#!forum/grpc-io">grpc-io@googlegroups.com</a></td>
+  </tr>
+</table>
+
+[![Join the chat at https://gitter.im/grpc/grpc](https://badges.gitter.im/grpc/grpc.svg)](https://gitter.im/grpc/grpc?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![Build Status](https://travis-ci.org/grpc/grpc-java.svg?branch=master)](https://travis-ci.org/grpc/grpc-java)
+[![Coverage Status](https://coveralls.io/repos/grpc/grpc-java/badge.svg?branch=master&service=github)](https://coveralls.io/github/grpc/grpc-java?branch=master)
+
+Getting Started
+---------------
+
+For a guided tour, take a look at the [quick start
+guide](https://grpc.io/docs/quickstart/java.html) or the more explanatory [gRPC
+basics](https://grpc.io/docs/tutorials/basic/java.html).
+
+The [examples](https://github.com/grpc/grpc-java/tree/v1.15.0/examples) and the
+[Android example](https://github.com/grpc/grpc-java/tree/v1.15.0/examples/android)
+are standalone projects that showcase the usage of gRPC.
+
+Download
+--------
+
+Download [the JARs][]. Or for Maven with non-Android, add to your `pom.xml`:
+```xml
+<dependency>
+  <groupId>io.grpc</groupId>
+  <artifactId>grpc-netty-shaded</artifactId>
+  <version>1.15.0</version>
+</dependency>
+<dependency>
+  <groupId>io.grpc</groupId>
+  <artifactId>grpc-protobuf</artifactId>
+  <version>1.15.0</version>
+</dependency>
+<dependency>
+  <groupId>io.grpc</groupId>
+  <artifactId>grpc-stub</artifactId>
+  <version>1.15.0</version>
+</dependency>
+```
+
+Or for Gradle with non-Android, add to your dependencies:
+```gradle
+compile 'io.grpc:grpc-netty-shaded:1.15.0'
+compile 'io.grpc:grpc-protobuf:1.15.0'
+compile 'io.grpc:grpc-stub:1.15.0'
+```
+
+For Android client, use `grpc-okhttp` instead of `grpc-netty-shaded` and
+`grpc-protobuf-lite` instead of `grpc-protobuf`:
+```gradle
+compile 'io.grpc:grpc-okhttp:1.15.0'
+compile 'io.grpc:grpc-protobuf-lite:1.15.0'
+compile 'io.grpc:grpc-stub:1.15.0'
+```
+
+[the JARs]:
+https://search.maven.org/search?q=g:io.grpc%20AND%20v:1.15.0
+
+Development snapshots are available in [Sonatypes's snapshot
+repository](https://oss.sonatype.org/content/repositories/snapshots/).
+
+Generated Code
+--------------
+
+For protobuf-based codegen, you can put your proto files in the `src/main/proto`
+and `src/test/proto` directories along with an appropriate plugin.
+
+For protobuf-based codegen integrated with the Maven build system, you can use
+[protobuf-maven-plugin][] (Eclipse and NetBeans users should also look at
+`os-maven-plugin`'s
+[IDE documentation](https://github.com/trustin/os-maven-plugin#issues-with-eclipse-m2e-or-other-ides)):
+```xml
+<build>
+  <extensions>
+    <extension>
+      <groupId>kr.motd.maven</groupId>
+      <artifactId>os-maven-plugin</artifactId>
+      <version>1.5.0.Final</version>
+    </extension>
+  </extensions>
+  <plugins>
+    <plugin>
+      <groupId>org.xolstice.maven.plugins</groupId>
+      <artifactId>protobuf-maven-plugin</artifactId>
+      <version>0.5.1</version>
+      <configuration>
+        <protocArtifact>com.google.protobuf:protoc:3.5.1-1:exe:${os.detected.classifier}</protocArtifact>
+        <pluginId>grpc-java</pluginId>
+        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.15.0:exe:${os.detected.classifier}</pluginArtifact>
+      </configuration>
+      <executions>
+        <execution>
+          <goals>
+            <goal>compile</goal>
+            <goal>compile-custom</goal>
+          </goals>
+        </execution>
+      </executions>
+    </plugin>
+  </plugins>
+</build>
+```
+
+[protobuf-maven-plugin]: https://www.xolstice.org/protobuf-maven-plugin/
+
+For protobuf-based codegen integrated with the Gradle build system, you can use
+[protobuf-gradle-plugin][]:
+```gradle
+apply plugin: 'com.google.protobuf'
+
+buildscript {
+  repositories {
+    mavenCentral()
+  }
+  dependencies {
+    classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.5'
+  }
+}
+
+protobuf {
+  protoc {
+    artifact = "com.google.protobuf:protoc:3.5.1-1"
+  }
+  plugins {
+    grpc {
+      artifact = 'io.grpc:protoc-gen-grpc-java:1.15.0'
+    }
+  }
+  generateProtoTasks {
+    all()*.plugins {
+      grpc {}
+    }
+  }
+}
+```
+
+[protobuf-gradle-plugin]: https://github.com/google/protobuf-gradle-plugin
+
+The prebuilt protoc-gen-grpc-java binary uses glibc on Linux. If you are
+compiling on Alpine Linux, you may want to use the [Alpine grpc-java package][]
+which uses musl instead.
+
+[Alpine grpc-java package]: https://pkgs.alpinelinux.org/package/edge/testing/x86_64/grpc-java
+
+API Stability
+-------------
+
+APIs annotated with `@Internal` are for internal use by the gRPC library and
+should not be used by gRPC users. APIs annotated with `@ExperimentalApi` are
+subject to change in future releases, and library code that other projects
+may depend on should not use these APIs.
+
+We recommend using the
+[grpc-java-api-checker](https://github.com/grpc/grpc-java-api-checker)
+(an [Error Prone](https://github.com/google/error-prone) plugin)
+to check for usages of `@ExperimentalApi` and `@Internal` in any library code
+that depends on gRPC. It may also be used to check for `@Internal` usage or 
+unintended `@ExperimentalApi` consumption in non-library code.
+
+How to Build
+------------
+
+If you are making changes to gRPC-Java, see the [compiling
+instructions](COMPILING.md).
+
+High-level Components
+---------------------
+
+At a high level there are three distinct layers to the library: *Stub*,
+*Channel*, and *Transport*.
+
+### Stub
+
+The Stub layer is what is exposed to most developers and provides type-safe
+bindings to whatever datamodel/IDL/interface you are adapting. gRPC comes with
+a [plugin](https://github.com/google/grpc-java/blob/master/compiler) to the
+protocol-buffers compiler that generates Stub interfaces out of `.proto` files,
+but bindings to other datamodel/IDL are easy and encouraged.
+
+### Channel
+
+The Channel layer is an abstraction over Transport handling that is suitable for
+interception/decoration and exposes more behavior to the application than the
+Stub layer. It is intended to be easy for application frameworks to use this
+layer to address cross-cutting concerns such as logging, monitoring, auth, etc.
+
+### Transport
+
+The Transport layer does the heavy lifting of putting and taking bytes off the
+wire. The interfaces to it are abstract just enough to allow plugging in of
+different implementations. Note the transport layer API is considered internal
+to gRPC and has weaker API guarantees than the core API under package `io.grpc`.
+
+gRPC comes with three Transport implementations:
+
+1. The Netty-based transport is the main transport implementation based on
+   [Netty](http://netty.io). It is for both the client and the server.
+2. The OkHttp-based transport is a lightweight transport based on
+   [OkHttp](http://square.github.io/okhttp/). It is mainly for use on Android
+   and is for client only.
+3. The in-process transport is for when a server is in the same process as the
+   client. It is useful for testing, while also being safe for production use.
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 0000000..9fee632
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,223 @@
+How to Create a Release of GRPC Java (for Maintainers Only)
+===============================================================
+
+Build Environments
+------------------
+We deploy GRPC to Maven Central under the following systems:
+- Ubuntu 14.04 with Docker 13.03.0 that runs CentOS 6.9
+- Windows 7 64-bit with Visual Studio
+- Mac OS X 10.12.6
+
+Other systems may also work, but we haven't verified them.
+
+Prerequisites
+-------------
+
+### Set Up OSSRH Account
+
+If you haven't deployed artifacts to Maven Central before, you need to setup
+your OSSRH (OSS Repository Hosting) account.
+- Follow the instructions on [this
+  page](http://central.sonatype.org/pages/ossrh-guide.html) to set up an
+  account with OSSRH.
+  - You only need to create the account, not set up a new project
+  - Contact a gRPC maintainer to add your account after you have created it.
+
+Common Variables
+----------------
+Many of the following commands expect release-specific variables to be set. Set
+them before continuing, and set them again when resuming.
+
+```bash
+$ MAJOR=1 MINOR=7 PATCH=0 # Set appropriately for new release
+$ VERSION_FILES=(
+  build.gradle
+  android/build.gradle
+  android-interop-testing/app/build.gradle
+  core/src/main/java/io/grpc/internal/GrpcUtil.java
+  cronet/build.gradle
+  documentation/android-channel-builder.md
+  examples/build.gradle
+  examples/pom.xml
+  examples/android/clientcache/app/build.gradle
+  examples/android/helloworld/app/build.gradle
+  examples/android/routeguide/app/build.gradle
+  examples/example-kotlin/build.gradle
+  examples/example-kotlin/android/helloworld/app/build.gradle
+  )
+```
+
+
+Branching the Release
+---------------------
+The first step in the release process is to create a release branch and bump
+the SNAPSHOT version. Our release branches follow the naming
+convention of `v<major>.<minor>.x`, while the tags include the patch version
+`v<major>.<minor>.<patch>`. For example, the same branch `v1.7.x`
+would be used to create all `v1.7` tags (e.g. `v1.7.0`, `v1.7.1`).
+
+1. For `master`, change root build files to the next minor snapshot (e.g.
+   ``1.8.0-SNAPSHOT``).
+
+   ```bash
+   $ git checkout -b bump-version master
+   # Change version to next minor (and keep -SNAPSHOT)
+   $ sed -i 's/[0-9]\+\.[0-9]\+\.[0-9]\+\(.*CURRENT_GRPC_VERSION\)/'$MAJOR.$((MINOR+1)).0'\1/' \
+     "${VERSION_FILES[@]}"
+   $ sed -i s/$MAJOR.$MINOR.$PATCH/$MAJOR.$((MINOR+1)).0/ \
+     compiler/src/test{,Lite,Nano}/golden/Test{,Deprecated}Service.java.txt
+   $ ./gradlew build
+   $ git commit -a -m "Start $MAJOR.$((MINOR+1)).0 development cycle"
+   ```
+2. Go through PR review and submit.
+3. Create the release branch starting just before your commit and push it to GitHub:
+
+   ```bash
+   $ git fetch upstream
+   $ git checkout -b v$MAJOR.$MINOR.x \
+     $(git log --pretty=format:%H --grep "^Start $MAJOR.$((MINOR+1)).0 development cycle$" upstream/master)^
+   $ git push upstream v$MAJOR.$MINOR.x
+   ```
+4. Go to [Travis CI settings](https://travis-ci.org/grpc/grpc-java/settings) and
+   add a _Cron Job_:
+   * Branch: `v$MAJOR.$MINOR.x`
+   * Interval: `weekly`
+   * Options: `Do not run if there has been a build in the last 24h`
+   * Click _Add_ button
+5. Continue with Google-internal steps at go/grpc/java/releasing.
+6. Create a milestone for the next release.
+7. Move items out of the release milestone that didn't make the cut. Issues that
+   may be backported should stay in the release milestone. Treat issues with the
+   'release blocker' label with special care.
+
+Tagging the Release
+-------------------
+
+1. Verify there are no open issues in the release milestone. Open issues should
+   either be deferred or resolved and the fix backported.
+2. For vMajor.Minor.x branch, change `README.md` to refer to the next release
+   version. _Also_ update the version numbers for protoc if the protobuf library
+   version was updated since the last release.
+
+   ```bash
+   $ git checkout v$MAJOR.$MINOR.x
+   $ git pull upstream v$MAJOR.$MINOR.x
+   $ git checkout -b release
+   # Bump documented versions. Don't forget protobuf version
+   $ ${EDITOR:-nano -w} README.md
+   $ git commit -a -m "Update README to reference $MAJOR.$MINOR.$PATCH"
+   ```
+3. Change root build files to remove "-SNAPSHOT" for the next release version
+   (e.g. `0.7.0`). Commit the result and make a tag:
+
+   ```bash
+   # Change version to remove -SNAPSHOT
+   $ sed -i 's/-SNAPSHOT\(.*CURRENT_GRPC_VERSION\)/\1/' "${VERSION_FILES[@]}"
+   $ sed -i s/-SNAPSHOT// compiler/src/test{,Lite,Nano}/golden/TestService.java.txt
+   $ ./gradlew build
+   $ git commit -a -m "Bump version to $MAJOR.$MINOR.$PATCH"
+   $ git tag -a v$MAJOR.$MINOR.$PATCH -m "Version $MAJOR.$MINOR.$PATCH"
+   ```
+4. Change root build files to the next snapshot version (e.g. `0.7.1-SNAPSHOT`).
+   Commit the result:
+
+   ```bash
+   # Change version to next patch and add -SNAPSHOT
+   $ sed -i 's/[0-9]\+\.[0-9]\+\.[0-9]\+\(.*CURRENT_GRPC_VERSION\)/'$MAJOR.$MINOR.$((PATCH+1))-SNAPSHOT'\1/' \
+     "${VERSION_FILES[@]}"
+   $ sed -i s/$MAJOR.$MINOR.$PATCH/$MAJOR.$MINOR.$((PATCH+1))-SNAPSHOT/ compiler/src/test{,Lite,Nano}/golden/TestService.java.txt
+   $ ./gradlew build
+   $ git commit -a -m "Bump version to $MAJOR.$MINOR.$((PATCH+1))-SNAPSHOT"
+   ```
+5. Go through PR review and push the release tag and updated release branch to
+   GitHub:
+
+   ```bash
+   $ git checkout v$MAJOR.$MINOR.x
+   $ git merge --ff-only release
+   $ git push upstream v$MAJOR.$MINOR.x
+   $ git push upstream v$MAJOR.$MINOR.$PATCH
+   ```
+6. Close the release milestone.
+
+Build Artifacts
+---------------
+
+Trigger build as described in "Auto releasing using kokoro" at
+go/grpc/java/releasing.
+
+It runs three jobs on Kokoro, one on each platform. See their scripts:
+`linux_artifacts.sh`, `windows.bat`, and `unix.sh` (called directly for OS X;
+called within the Docker environment on Linux). The mvn-artifacts/ outputs of
+each script is combined into a single folder and then processed by
+`upload_artifacts.sh`, which signs the files and uploads to Sonatype.
+
+Releasing on Maven Central
+--------------------------
+
+Once all of the artifacts have been pushed to the staging repository, the
+repository should have been closed by `upload_artifacts.sh`. Closing triggers
+several sanity checks on the repository. If this completes successfully, the
+repository can then be `released`, which will begin the process of pushing the
+new artifacts to Maven Central (the staging repository will be destroyed in the
+process). You can see the complete process for releasing to Maven Central on the
+[OSSRH site](http://central.sonatype.org/pages/releasing-the-deployment.html).
+
+Build interop container image
+-----------------------------
+
+We have containers for each release to detect compatibility regressions with old
+releases. Generate one for the new release by following the
+[GCR image generation instructions](https://github.com/grpc/grpc/blob/master/tools/interop_matrix/README.md#step-by-step-instructions-for-adding-a-gcr-image-for-a-new-release-for-compatibility-test).
+
+Update README.md
+----------------
+After waiting ~1 day and verifying that the release appears on [Maven
+Central](http://mvnrepository.com/), cherry-pick the commit that updated the
+README into the master branch and go through review process.
+
+```
+$ git checkout -b bump-readme master
+$ git cherry-pick v$MAJOR.$MINOR.$PATCH^
+```
+
+Update version referenced by tutorials
+--------------------------------------
+
+Update the `grpc_java_release_tag` in
+[\_data/config.yml](https://github.com/grpc/grpc.github.io/blob/master/_data/config.yml)
+of the grpc.github.io repository.
+
+Notify the Community
+--------------------
+Finally, document and publicize the release.
+
+1. Add [Release Notes](https://github.com/grpc/grpc-java/releases) for the new tag.
+   The description should include any major fixes or features since the last release.
+   You may choose to add links to bugs, PRs, or commits if appropriate.
+2. Post a release announcement to [grpc-io](https://groups.google.com/forum/#!forum/grpc-io)
+   (`grpc-io@googlegroups.com`). The title should be something that clearly identifies
+   the release (e.g.`GRPC-Java <tag> Released`).
+    1. Check if JCenter has picked up the new release (https://jcenter.bintray.com/io/grpc/)
+       and include its availability in the release announcement email. JCenter should mirror
+       everything on Maven Central, but users have reported delays.
+
+Update Hosted Javadoc
+---------------------
+
+Now we need to update gh-pages with the new Javadoc:
+
+```bash
+git checkout gh-pages
+rm -r javadoc/
+wget -O grpc-all-javadoc.jar "http://search.maven.org/remotecontent?filepath=io/grpc/grpc-all/$MAJOR.$MINOR.$PATCH/grpc-all-$MAJOR.$MINOR.$PATCH-javadoc.jar"
+unzip -d javadoc grpc-all-javadoc.jar
+patch -p1 < ga.patch
+rm grpc-all-javadoc.jar
+rm -r javadoc/META-INF/
+git add -A javadoc
+git commit -m "Javadoc for $MAJOR.$MINOR.$PATCH"
+```
+
+Push gh-pages to the main repository and verify the current version is [live
+on grpc.io](https://grpc.io/grpc-java/javadoc/).
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..296aad2
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,417 @@
+# Authentication
+
+gRPC supports a number of different mechanisms for asserting identity between an client and server. This document provides code samples demonstrating how to provide SSL/TLS encryption support and identity assertions in Java, as well as passing OAuth2 tokens to services that support it.
+
+# Transport Security (TLS)
+
+HTTP/2 over TLS mandates the use of [ALPN](https://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg-05) to negotiate the use of the h2 protocol. ALPN is a fairly new standard and (where possible) gRPC also supports protocol negotiation via [NPN](https://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04) for systems that do not yet support ALPN.
+
+On Android, use the [Play Services Provider](#tls-on-android). For non-Android systems, use [OpenSSL](#tls-with-openssl).
+
+## TLS on Android
+
+On Android we recommend the use of the [Play Services Dynamic Security
+Provider](https://www.appfoundry.be/blog/2014/11/18/Google-Play-Services-Dynamic-Security-Provider/)
+to ensure your application has an up-to-date OpenSSL library with the necessary
+ciper-suites and a reliable ALPN implementation. This requires [updating the
+security provider at
+runtime](https://developer.android.com/training/articles/security-gms-provider.html).
+
+Although ALPN mostly works on newer Android releases (especially since 5.0),
+there are bugs and discovered security vulnerabilities that are only fixed by
+upgrading the security provider. Thus, we recommend using the Play Service
+Dynamic Security Provider for all Android versions.
+
+*Note: The Dynamic Security Provider must be installed **before** creating a gRPC OkHttp channel. gRPC's OkHttpProtocolNegotiator statically initializes the security protocol(s) available to gRPC, which means that changes to the security provider after the first channel is created will not be picked up by gRPC.*
+
+### Bundling Conscrypt
+
+If depending on Play Services is not an option for your app, then you may bundle
+[Conscrypt](https://conscrypt.org) with your application. Binaries are available
+on [Maven
+Central](https://search.maven.org/#search%7Cga%7C1%7Cg%3Aorg.conscrypt%20a%3Aconscrypt-android).
+
+Like the Play Services Dynamic Security Provider, you must still "install"
+Conscrypt before use.
+
+```java
+import org.conscrypt.Conscrypt;
+import java.security.Security;
+...
+
+Security.insertProviderAt(Conscrypt.newProvider(), 1);
+```
+
+## TLS with OpenSSL
+
+This is currently the recommended approach for using gRPC over TLS (on non-Android systems).
+
+The main benefits of using OpenSSL are:
+
+1. **Speed**: In local testing, we've seen performance improvements of 3x over the JDK. GCM, which is used by the only cipher suite required by the HTTP/2 spec, is 10-500x faster.
+2. **Ciphers**: OpenSSL has its own ciphers and is not dependent on the limitations of the JDK. This allows supporting GCM on Java 7.
+3. **ALPN to NPN Fallback**: if the remote endpoint doesn't support ALPN.
+4. **Version Independence**: does not require using a different library version depending on the JDK update.
+
+Support for OpenSSL is only provided for the Netty transport via [netty-tcnative](https://github.com/netty/netty-tcnative), which is a fork of
+[Apache Tomcat's tcnative](http://tomcat.apache.org/native-doc/), a JNI wrapper around OpenSSL.
+
+### OpenSSL: Dynamic vs Static (which to use?)
+
+As of version `1.1.33.Fork14`, netty-tcnative provides two options for usage: statically or dynamically linked. For simplification of initial setup,
+we recommend that users first look at `netty-tcnative-boringssl-static`, which is statically linked against BoringSSL and Apache APR. Using this artifact requires no extra installation and guarantees that ALPN and the ciphers required for
+HTTP/2 are available. In addition, starting with `1.1.33.Fork16` binaries for
+all supported platforms can be included at compile time and the correct binary
+for the platform can be selected at runtime.
+
+Production systems, however, may require an easy upgrade path for OpenSSL security patches. In this case, relying on the statically linked artifact also implies waiting for the Netty team
+to release the new artifact to Maven Central, which can take some time. A better solution in this case is to use the dynamically linked `netty-tcnative` artifact, which allows the site administrator
+to easily upgrade OpenSSL in the standard way (e.g. apt-get) without relying on any new builds from Netty.
+
+### OpenSSL: Statically Linked (netty-tcnative-boringssl-static)
+
+This is the simplest way to configure the Netty transport for OpenSSL. You just need to add the appropriate `netty-tcnative-boringssl-static` artifact to your application's classpath.
+
+Artifacts are available on [Maven Central](http://repo1.maven.org/maven2/io/netty/netty-tcnative-boringssl-static/) for the following platforms:
+
+Maven Classifier | Description
+---------------- | -----------
+windows-x86_64 | Windows distribution
+osx-x86_64 | Mac distribution
+linux-x86_64 | Linux distribution
+
+##### Getting netty-tcnative-boringssl-static from Maven
+
+In Maven, you can use the [os-maven-plugin](https://github.com/trustin/os-maven-plugin) to help simplify the dependency.
+
+```xml
+<project>
+  <dependencies>
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-tcnative-boringssl-static</artifactId>
+      <version>2.0.7.Final</version>
+    </dependency>
+  </dependencies>
+</project>
+```
+
+##### Getting netty-tcnative-boringssl-static from Gradle
+
+Gradle you can use the [osdetector-gradle-plugin](https://github.com/google/osdetector-gradle-plugin), which is a wrapper around the os-maven-plugin.
+
+```gradle
+buildscript {
+  repositories {
+    mavenCentral()
+  }
+}
+
+dependencies {
+    compile 'io.netty:netty-tcnative-boringssl-static:2.0.7.Final'
+}
+```
+
+### OpenSSL: Dynamically Linked (netty-tcnative)
+
+If for any reason you need to dynamically link against OpenSSL (e.g. you need control over the version of OpenSSL), you can instead use the `netty-tcnative` artifact.
+
+Requirements:
+
+1. [OpenSSL](https://www.openssl.org/) version >= 1.0.2 for ALPN support, or version >= 1.0.1 for NPN.
+2. [Apache APR library (libapr-1)](https://apr.apache.org/) version >= 1.5.2.
+3. [netty-tcnative](https://github.com/netty/netty-tcnative) version >= 1.1.33.Fork7 must be on classpath. Prior versions only supported NPN and only Fedora-derivatives were supported for Linux.
+
+Artifacts are available on [Maven Central](http://repo1.maven.org/maven2/io/netty/netty-tcnative/) for the following platforms:
+
+Classifier | Description
+---------------- | -----------
+windows-x86_64 | Windows distribution
+osx-x86_64 | Mac distribution
+linux-x86_64 | Used for non-Fedora derivatives of Linux
+linux-x86_64-fedora | Used for Fedora derivatives
+
+On Linux it should be noted that OpenSSL uses a different soname for Fedora derivatives than other Linux releases. To work around this limitation, netty-tcnative deploys two separate versions for linux.
+
+##### Getting netty-tcnative from Maven
+
+In Maven, you can use the [os-maven-plugin](https://github.com/trustin/os-maven-plugin) to help simplify the dependency.
+
+```xml
+<project>
+  <dependencies>
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-tcnative</artifactId>
+      <version>2.0.7.Final</version>
+      <classifier>${tcnative.classifier}</classifier>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <extensions>
+      <!-- Use os-maven-plugin to initialize the "os.detected" properties -->
+      <extension>
+        <groupId>kr.motd.maven</groupId>
+        <artifactId>os-maven-plugin</artifactId>
+        <version>1.5.0.Final</version>
+      </extension>
+    </extensions>
+    <plugins>
+      <!-- Use Ant to configure the appropriate "tcnative.classifier" property -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>initialize</phase>
+            <configuration>
+              <exportAntProperties>true</exportAntProperties>
+              <target>
+                <condition property="tcnative.classifier"
+                           value="${os.detected.classifier}-fedora"
+                           else="${os.detected.classifier}">
+                  <isset property="os.detected.release.fedora"/>
+                </condition>
+              </target>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
+```
+
+##### Getting netty-tcnative from Gradle
+
+Gradle you can use the [osdetector-gradle-plugin](https://github.com/google/osdetector-gradle-plugin), which is a wrapper around the os-maven-plugin.
+
+```gradle
+buildscript {
+  repositories {
+    mavenCentral()
+  }
+  dependencies {
+    classpath 'com.google.gradle:osdetector-gradle-plugin:1.4.0'
+  }
+}
+
+// Use the osdetector-gradle-plugin
+apply plugin: "com.google.osdetector"
+
+def tcnative_classifier = osdetector.classifier;
+// Fedora variants use a different soname for OpenSSL than other linux distributions
+// (see http://netty.io/wiki/forked-tomcat-native.html).
+if (osdetector.os == "linux" && osdetector.release.isLike("fedora")) {
+  tcnative_classifier += "-fedora";
+}
+
+dependencies {
+    compile 'io.netty:netty-tcnative:2.0.7.Final:' + tcnative_classifier
+}
+```
+
+## TLS with JDK (Jetty ALPN/NPN)
+
+**WARNING: DON'T DO THIS!!**
+
+*For non-Android systems, the recommended approach is to use [OpenSSL](#tls-with-openssl). Using the JDK for ALPN is generally much slower and may not support the necessary ciphers for HTTP2.*
+
+*Jetty ALPN brings its own baggage in that the Java bootclasspath needs to be modified, which may not be an option for some environments. In addition, a specific version of Jetty ALPN has to be used for a given version of the JRE. If the versions don't match the negotiation will fail, but you won't really know why. And since there is such a tight coupling between Jetty ALPN and the JRE, there are no guarantees that Jetty ALPN will support every JRE out in the wild.*
+
+*The moral of the story is: Don't use the JDK for ALPN!  But if you absolutely have to, here's how you do it... :)*
+
+---
+
+If not using the Netty transport (or you are unable to use OpenSSL for some reason) another alternative is to use the JDK for TLS.
+
+No standard Java release has built-in support for ALPN today ([there is a tracking issue](https://bugs.openjdk.java.net/browse/JDK-8051498) so go upvote it!) so we need to use the [Jetty-ALPN](https://github.com/jetty-project/jetty-alpn) (or [Jetty-NPN](https://github.com/jetty-project/jetty-npn) if on Java < 8) bootclasspath extension for OpenJDK. To do this, add an `Xbootclasspath` JVM option referencing the path to the Jetty `alpn-boot` jar.
+
+```sh
+java -Xbootclasspath/p:/path/to/jetty/alpn/extension.jar ...
+```
+
+Note that you must use the [release of the Jetty-ALPN jar](http://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn-versions) specific to the version of Java you are using. However, you can use the JVM agent [Jetty-ALPN-Agent](https://github.com/jetty-project/jetty-alpn-agent) to load the correct Jetty `alpn-boot` jar file for the current Java version. To do this, instead of adding an `Xbootclasspath` option, add a `javaagent` JVM option referencing the path to the Jetty `alpn-agent` jar.
+
+```sh
+java -javaagent:/path/to/jetty-alpn-agent.jar ...
+```
+
+### JDK Ciphers
+
+Java 7 does not support [the cipher suites recommended](https://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-9.2.2) by the HTTP2 specification. To address this we suggest servers use Java 8 where possible or use an alternative JCE implementation such as [Bouncy Castle](https://www.bouncycastle.org/java.html). If this is not practical it is possible to use other ciphers but you need to ensure that the services you intend to call have [allowed out-of-spec ciphers](https://github.com/grpc/grpc/issues/681) and have evaluated the security risks of doing so.
+
+Users should be aware that GCM is [_very_ slow (1 MB/s)](https://bugzilla.redhat.com/show_bug.cgi?id=1135504) before Java 8u60. With Java 8u60 GCM is 10x faster (10-20 MB/s), but that is still slow compared to OpenSSL (~200 MB/s), especially with AES-NI support (~1 GB/s). GCM cipher suites are the only suites available that comply with HTTP2's cipher requirements.
+
+### Configuring Jetty ALPN in Web Containers
+
+Some web containers, such as [Jetty](http://www.eclipse.org/jetty/documentation/current/jetty-classloading.html) restrict access to server classes for web applications. A gRPC client running within such a container must be properly configured to allow access to the ALPN classes. In Jetty, this is done by including a `WEB-INF/jetty-env.xml` file containing the following:
+
+```xml
+<?xml version="1.0"  encoding="ISO-8859-1"?>
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+    <!-- Must be done in jetty-env.xml, since jetty-web.xml is loaded too late.   -->
+    <!-- Removing ALPN from the blacklisted server classes (using "-" to remove). -->
+    <!-- Must prepend to the blacklist since order matters.                       -->
+    <Call name="prependServerClass">
+        <Arg>-org.eclipse.jetty.alpn.</Arg>
+    </Call>
+</Configure>
+```
+## Enabling TLS on a server
+
+To use TLS on the server, a certificate chain and private key need to be
+specified in PEM format. The standard TLS port is 443, but we use 8443 below to
+avoid needing extra permissions from the OS.
+
+```java
+Server server = ServerBuilder.forPort(8443)
+    // Enable TLS
+    .useTransportSecurity(certChainFile, privateKeyFile)
+    .addService(serviceImplementation)
+    .build();
+server.start();
+```
+
+If the issuing certificate authority is not known to the client then a properly
+configured SslContext or SSLSocketFactory should be provided to the
+NettyChannelBuilder or OkHttpChannelBuilder, respectively.
+
+## Mutual TLS
+
+[Mutual authentication][] (or "client-side authentication") configuration is similar to the server by providing truststores, a client certificate and private key to the client channel.  The server must also be configured to request a certificate from clients, as well as truststores for which client certificates it should allow.
+
+```java
+Server server = NettyServerBuilder.forPort(8443)
+    .sslContext(GrpcSslContexts.forServer(certChainFile, privateKeyFile)
+        .trustManager(clientCAsFile)
+        .clientAuth(ClientAuth.REQUIRE)
+        .build());
+```
+
+Negotiated client certificates are available in the SSLSession, which is found in the `TRANSPORT_ATTR_SSL_SESSION` attribute of <a href="https://github.com/grpc/grpc-java/blob/master/core/src/main/java/io/grpc/Grpc.java">Grpc</a>.  A server interceptor can provide details in the current Context.
+
+```java
+public final static Context.Key<SSLSession> SSL_SESSION_CONTEXT = Context.key("SSLSession");
+
+@Override
+public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<RespT> call, 
+    Metadata headers, ServerCallHandler<ReqT, RespT> next) {
+    SSLSession sslSession = call.attributes().get(Grpc.TRANSPORT_ATTR_SSL_SESSION);
+    if (sslSession == null) {
+        return next.startCall(call, headers)
+    }
+    return Contexts.interceptCall(
+        Context.current().withValue(SSL_SESSION_CONTEXT, clientContext), call, headers, next);
+}
+```
+
+[Mutual authentication]: http://en.wikipedia.org/wiki/Transport_Layer_Security#Client-authenticated_TLS_handshake
+
+## Troubleshooting
+
+If you received an error message "ALPN is not configured properly" or "Jetty ALPN/NPN has not been properly configured", it most likely means that:
+ - ALPN related dependencies are either not present in the classpath
+ - or that there is a classpath conflict
+ - or that a wrong version is used due to dependency management
+ - or you are on an unsupported platform (e.g., 32-bit OS, Alpine with `musl` libc). See [Transport Security](#transport-security-tls) for supported platforms.
+
+### Netty
+If you aren't using gRPC on Android devices, you are most likely using `grpc-netty` transport.
+
+If you are developing for Android and have a dependency on `grpc-netty`, you should remove it as `grpc-netty` is unsupported on Android. Use `grpc-okhttp` instead.
+
+If you are on a 32-bit operating system, or not on a [Transport Security supported platform](#transport-security-tls), you should use Jetty ALPN (and beware of potential issues), or you'll need to build your own 32-bit version of `netty-tcnative`.
+
+If you are using `musl` libc (e.g., with Alpine Linux), then
+`netty-tcnative-boringssl-static` won't work. There are several alternatives:
+ - Use [netty-tcnative-alpine](https://github.com/pires/netty-tcnative-alpine)
+ - Use a distribution with `glibc`
+
+If you are running inside of an embedded Tomcat runtime (e.g., Spring Boot),
+then some versions of `netty-tcnative-boringssl-static` will have conflicts and
+won't work. You must use gRPC 1.4.0 or later.
+
+Most dependency versioning problems can be solved by using
+`io.grpc:grpc-netty-shaded` instead of `io.grpc:grpc-netty`, although this also
+limits your usage of the Netty-specific APIs. `io.grpc:grpc-netty-shaded`
+includes the proper version of Netty and `netty-tcnative-boringssl-static` in a
+way that won't conflict with other Netty usages.
+
+Find the dependency tree (e.g., `mvn dependency:tree`), and look for versions of:
+ - `io.grpc:grpc-netty`
+ - `io.netty:netty-handler` (really, make sure all of io.netty except for
+   netty-tcnative has the same version)
+ - `io.netty:netty-tcnative-boringssl-static:jar` 
+
+If `netty-tcnative-boringssl-static` is missing, then you either need to add it as a dependency, or use alternative methods of providing ALPN capability by reading the *Transport Security (TLS)* section carefully.
+
+If you have both `netty-handler` and `netty-tcnative-boringssl-static` dependencies, then check the versions carefully. These versions could've been overridden by dependency management from another BOM. You would receive the "ALPN is not configured properly" exception if you are using incompatible versions.
+
+If you have other `netty` dependencies, such as `netty-all`, that are pulled in from other libraries, then ultimately you should make sure only one `netty` dependency is used to avoid classpath conflict. The easiest way is to exclude transitive Netty dependencies from all the immediate dependencies, e.g., in Maven use `<exclusions>`, and then add an explict Netty dependency in your project along with the corresponding `tcnative` versions. See the versions table below.
+
+If you are running in a runtime environment that also uses Netty (e.g., Hadoop, Spark, Spring Boot 2) and you have no control over the Netty version at all, then you should use a shaded gRPC Netty dependency to avoid classpath conflicts with other Netty versions in runtime the classpath:
+ - Remove `io.grpc:grpc-netty` dependency
+ - Add `io.grpc:grpc-netty-shaded` dependency
+
+Below are known to work version combinations:
+
+grpc-netty version | netty-handler version | netty-tcnative-boringssl-static version
+------------------ | --------------------- | ---------------------------------------
+1.0.0-1.0.1        | 4.1.3.Final           | 1.1.33.Fork19
+1.0.2-1.0.3        | 4.1.6.Final           | 1.1.33.Fork23
+1.1.x-1.3.x        | 4.1.8.Final           | 1.1.33.Fork26
+1.4.x              | 4.1.11.Final          | 2.0.1.Final
+1.5.x              | 4.1.12.Final          | 2.0.5.Final
+1.6.x              | 4.1.14.Final          | 2.0.5.Final
+1.7.x-1.8.x        | 4.1.16.Final          | 2.0.6.Final
+1.9.x-1.10.x       | 4.1.17.Final          | 2.0.7.Final
+1.11.x-1.12.x      | 4.1.22.Final          | 2.0.7.Final
+1.13.x             | 4.1.25.Final          | 2.0.8.Final
+1.14.x-            | 4.1.27.Final          | 2.0.12.Final
+
+_(grpc-netty-shaded avoids issues with keeping these versions in sync.)_
+
+### OkHttp
+If you are using gRPC on Android devices, you are most likely using `grpc-okhttp` transport.
+
+Find the dependency tree (e.g., `mvn dependency:tree`), and look for versions of:
+ - `io.grpc:grpc-okhttp`
+ - `com.squareup.okhttp:okhttp`
+
+If you don't have `grpc-okhttp`, you should add it as a dependency.
+
+If you have both `io.grpc:grpc-netty` and `io.grpc:grpc-okhttp`, you may also have issues. Remove `grpc-netty` if you are on Android.
+
+If you have `okhttp` version below 2.5.0, then it may not work with gRPC.
+
+It is OK to have both `okhttp` 2.x and 3.x since they have different group name and under different packages.
+
+# gRPC over plaintext
+
+An option is provided to use gRPC over plaintext without TLS. While this is convenient for testing environments, users must be aware of the security risks of doing so for real production systems.
+
+# Using OAuth2
+
+The following code snippet shows how you can call the Google Cloud PubSub API using gRPC with a service account. The credentials are loaded from a key stored in a well-known location or by detecting that the application is running in an environment that can provide one automatically, e.g. Google Compute Engine. While this example is specific to Google and it's services, similar patterns can be followed for other service providers.
+
+```java
+// Create a channel to the test service.
+ManagedChannel channel = ManagedChannelBuilder.forTarget("pubsub.googleapis.com")
+    .build();
+// Get the default credentials from the environment
+GoogleCredentials creds = GoogleCredentials.getApplicationDefault();
+// Down-scope the credential to just the scopes required by the service
+creds = creds.createScoped(Arrays.asList("https://www.googleapis.com/auth/pubsub"));
+// Create an instance of {@link io.grpc.CallCredentials}
+CallCredentials callCreds = MoreCallCredentials.from(creds);
+// Create a stub with credential
+PublisherGrpc.PublisherBlockingStub publisherStub =
+    PublisherGrpc.newBlockingStub(channel).withCallCredentials(callCreds);
+publisherStub.publish(someMessage);
+```
diff --git a/WORKSPACE b/WORKSPACE
new file mode 100644
index 0000000..65bf26a
--- /dev/null
+++ b/WORKSPACE
@@ -0,0 +1,5 @@
+workspace(name = "io_grpc_grpc_java")
+
+load("//:repositories.bzl", "grpc_java_repositories")
+
+grpc_java_repositories()
diff --git a/all/build.gradle b/all/build.gradle
new file mode 100644
index 0000000..438ec1b
--- /dev/null
+++ b/all/build.gradle
@@ -0,0 +1,81 @@
+apply plugin: 'com.github.kt3k.coveralls'
+
+description = "gRPC: All"
+
+buildscript {
+    repositories {
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    }
+    dependencies { classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.0.1' }
+}
+
+def subprojects = [
+    project(':grpc-auth'),
+    project(':grpc-core'),
+    project(':grpc-context'),
+    project(':grpc-netty'),
+    project(':grpc-okhttp'),
+    project(':grpc-protobuf'),
+    project(':grpc-protobuf-lite'),
+    project(':grpc-protobuf-nano'),
+    project(':grpc-stub'),
+    project(':grpc-testing'),
+]
+
+for (subproject in rootProject.subprojects) {
+    if (subproject == project) {
+        continue
+    }
+    evaluationDependsOn(subproject.path)
+}
+
+dependencies {
+    compile subprojects.minus(project(':grpc-protobuf-lite'))
+}
+
+javadoc {
+    classpath = files(subprojects.collect { subproject ->
+        subproject.javadoc.classpath
+    })
+    for (subproject in subprojects) {
+        if (subproject == project) {
+            continue;
+        }
+        source subproject.javadoc.source
+        options.links subproject.javadoc.options.links.toArray(new String[0])
+    }
+}
+
+task jacocoMerge(type: JacocoMerge) {
+    dependsOn(subprojects.jacocoTestReport.dependsOn)
+    mustRunAfter(subprojects.jacocoTestReport.mustRunAfter)
+    destinationFile = file("${buildDir}/jacoco/test.exec")
+    executionData = files(subprojects.jacocoTestReport.executionData)
+            .plus(project(':grpc-interop-testing').jacocoTestReport.executionData)
+            .filter { f -> f.exists() }
+}
+
+jacocoTestReport {
+    dependsOn(jacocoMerge)
+    reports {
+        xml.enabled = true
+        html.enabled = true
+    }
+
+    additionalSourceDirs = files(subprojects.sourceSets.main.allSource.srcDirs)
+    sourceDirectories = files(subprojects.sourceSets.main.allSource.srcDirs)
+    classDirectories = files(subprojects.sourceSets.main.output)
+    classDirectories = files(classDirectories.files.collect {
+        fileTree(dir: it,
+        exclude: [
+            '**/io/grpc/okhttp/internal/**'
+        ])
+    })
+}
+
+coveralls {
+    sourceDirs = subprojects.sourceSets.main.allSource.srcDirs.flatten()
+}
+
+tasks.coveralls { dependsOn(jacocoTestReport) }
diff --git a/alts/BUILD.bazel b/alts/BUILD.bazel
new file mode 100644
index 0000000..4e3d266
--- /dev/null
+++ b/alts/BUILD.bazel
@@ -0,0 +1,83 @@
+load("//:java_grpc_library.bzl", "java_grpc_library")
+
+java_library(
+    name = "alts_internal",
+    srcs = glob([
+        "src/main/java/io/grpc/alts/internal/*.java",
+    ]),
+    deps = [
+        ":handshaker_java_grpc",
+        ":handshaker_java_proto",
+        "//core",
+        "//core:internal",
+        "//netty",
+        "//stub",
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_guava_guava//jar",
+        "@com_google_protobuf//:protobuf_java",
+        "@com_google_protobuf//:protobuf_java_util",
+        "@io_netty_netty_buffer//jar",
+        "@io_netty_netty_codec//jar",
+        "@io_netty_netty_common//jar",
+        "@io_netty_netty_handler//jar",
+        "@io_netty_netty_transport//jar",
+    ],
+)
+
+java_library(
+    name = "alts",
+    srcs = glob([
+        "src/main/java/io/grpc/alts/*.java",
+    ]),
+    visibility = ["//visibility:public"],
+    runtime_deps = ["//grpclb"],
+    deps = [
+        ":alts_internal",
+        ":handshaker_java_grpc",
+        "//core",
+        "//core:internal",
+        "//auth",
+        "//netty",
+        "@com_google_auth_google_auth_library_oauth2_http//jar",
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_guava_guava//jar",
+        "@io_netty_netty_common//jar",
+        "@io_netty_netty_handler//jar",
+        "@io_netty_netty_transport//jar",
+        "@org_apache_commons_commons_lang3//jar",
+    ],
+)
+
+# bazel only accepts proto import with absolute path.
+genrule(
+    name = "protobuf_imports",
+    srcs = glob(["src/main/proto/grpc/gcp/*.proto"]),
+    outs = [
+        "protobuf_out/grpc/gcp/altscontext.proto",
+        "protobuf_out/grpc/gcp/handshaker.proto",
+        "protobuf_out/grpc/gcp/transport_security_common.proto",
+    ],
+    cmd = "for fname in $(SRCS); do " +
+          "sed 's,import \",import \"alts/protobuf_out/,g' $$fname > " +
+          "$(@D)/protobuf_out/grpc/gcp/$$(basename $$fname); done",
+)
+
+proto_library(
+    name = "handshaker_proto",
+    srcs = [
+        "protobuf_out/grpc/gcp/altscontext.proto",
+        "protobuf_out/grpc/gcp/handshaker.proto",
+        "protobuf_out/grpc/gcp/transport_security_common.proto",
+    ],
+)
+
+java_proto_library(
+    name = "handshaker_java_proto",
+    deps = [":handshaker_proto"],
+)
+
+java_grpc_library(
+    name = "handshaker_java_grpc",
+    srcs = [":handshaker_proto"],
+    deps = [":handshaker_java_proto"],
+)
diff --git a/alts/build.gradle b/alts/build.gradle
new file mode 100644
index 0000000..5a8c9e3
--- /dev/null
+++ b/alts/build.gradle
@@ -0,0 +1,90 @@
+buildscript {
+    repositories { jcenter() }
+    dependencies { classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4' }
+}
+
+apply plugin: 'com.github.johnrengelman.shadow'
+
+description = "gRPC: ALTS"
+
+sourceCompatibility = 1.7
+targetCompatibility = 1.7
+
+buildscript {
+    repositories {
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    }
+    dependencies { classpath libraries.protobuf_plugin }
+}
+
+dependencies {
+    compile project(':grpc-auth'),
+            project(':grpc-core'),
+            project(':grpc-netty'),
+            project(':grpc-protobuf'),
+            project(':grpc-stub'),
+            libraries.lang,
+            libraries.protobuf
+    compile (libraries.google_auth_oauth2_http) {
+        // prefer 3.0.0 from libraries instead of 1.3.9
+        exclude group: 'com.google.code.findbugs', module: 'jsr305'
+        // prefer 20.0 from libraries instead of 19.0
+        exclude group: 'com.google.guava', module: 'guava'
+    }
+    runtime project(':grpc-grpclb')
+    testCompile libraries.guava,
+            libraries.guava_testlib,
+            libraries.junit,
+            libraries.mockito,
+            libraries.truth
+    testRuntime libraries.netty_tcnative,
+            libraries.conscrypt
+    signature 'org.codehaus.mojo.signature:java17:1.0@signature'
+}
+
+configureProtoCompilation()
+
+[compileJava, compileTestJava].each() {
+    // ALTS retuns a lot of futures that we mostly don't care about.
+    // protobuf calls valueof. Will be fixed in next release (google/protobuf#4046)
+    it.options.compilerArgs += [
+        "-Xlint:-deprecation",
+        "-Xep:FutureReturnValueIgnored:OFF"
+    ]
+}
+
+javadoc { exclude 'io/grpc/alts/internal/**' }
+
+artifacts {
+    archives shadowJar
+}
+
+jar {
+    // Must use a different classifier to avoid conflicting with shadowJar
+    classifier = 'original'
+}
+configurations.archives.artifacts.removeAll { it.classifier == "original" }
+
+// We want to use grpc-netty-shaded instead of grpc-netty. But we also want our
+// source to work with Bazel, so we rewrite the code as part of the build.
+shadowJar {
+    classifier = null
+    dependencies {
+        exclude(dependency {true})
+    }
+    relocate 'io.grpc.netty', 'io.grpc.netty.shaded.io.grpc.netty'
+    relocate 'io.netty', 'io.grpc.netty.shaded.io.netty'
+}
+
+[
+    install.repositories.mavenInstaller,
+    uploadArchives.repositories.mavenDeployer,
+]*.pom*.whenConfigured { pom ->
+    def netty = pom.dependencies.find {dep -> dep.artifactId == 'grpc-netty'}
+    // Swap our dependency to grpc-netty-shaded. Projects depending on this via
+    // project(':grpc-alts') will still be using the non-shaded form.
+    netty.artifactId = "grpc-netty-shaded"
+    // Depend on specific version of grpc-netty-shaded because it is unstable API
+    netty.version = "[" + netty.version + "]"
+}
diff --git a/alts/src/generated/main/grpc/io/grpc/alts/internal/HandshakerServiceGrpc.java b/alts/src/generated/main/grpc/io/grpc/alts/internal/HandshakerServiceGrpc.java
new file mode 100644
index 0000000..012748d
--- /dev/null
+++ b/alts/src/generated/main/grpc/io/grpc/alts/internal/HandshakerServiceGrpc.java
@@ -0,0 +1,280 @@
+package io.grpc.alts.internal;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: grpc/gcp/handshaker.proto")
+public final class HandshakerServiceGrpc {
+
+  private HandshakerServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.gcp.HandshakerService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.alts.internal.Handshaker.HandshakerReq,
+      io.grpc.alts.internal.Handshaker.HandshakerResp> getDoHandshakeMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "DoHandshake",
+      requestType = io.grpc.alts.internal.Handshaker.HandshakerReq.class,
+      responseType = io.grpc.alts.internal.Handshaker.HandshakerResp.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.alts.internal.Handshaker.HandshakerReq,
+      io.grpc.alts.internal.Handshaker.HandshakerResp> getDoHandshakeMethod() {
+    io.grpc.MethodDescriptor<io.grpc.alts.internal.Handshaker.HandshakerReq, io.grpc.alts.internal.Handshaker.HandshakerResp> getDoHandshakeMethod;
+    if ((getDoHandshakeMethod = HandshakerServiceGrpc.getDoHandshakeMethod) == null) {
+      synchronized (HandshakerServiceGrpc.class) {
+        if ((getDoHandshakeMethod = HandshakerServiceGrpc.getDoHandshakeMethod) == null) {
+          HandshakerServiceGrpc.getDoHandshakeMethod = getDoHandshakeMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.alts.internal.Handshaker.HandshakerReq, io.grpc.alts.internal.Handshaker.HandshakerResp>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.gcp.HandshakerService", "DoHandshake"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.alts.internal.Handshaker.HandshakerReq.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.alts.internal.Handshaker.HandshakerResp.getDefaultInstance()))
+                  .setSchemaDescriptor(new HandshakerServiceMethodDescriptorSupplier("DoHandshake"))
+                  .build();
+          }
+        }
+     }
+     return getDoHandshakeMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static HandshakerServiceStub newStub(io.grpc.Channel channel) {
+    return new HandshakerServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static HandshakerServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new HandshakerServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static HandshakerServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new HandshakerServiceFutureStub(channel);
+  }
+
+  /**
+   */
+  public static abstract class HandshakerServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * Handshaker service accepts a stream of handshaker request, returning a
+     * stream of handshaker response. Client is expected to send exactly one
+     * message with either client_start or server_start followed by one or more
+     * messages with next. Each time client sends a request, the handshaker
+     * service expects to respond. Client does not have to wait for service's
+     * response before sending next request.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.alts.internal.Handshaker.HandshakerReq> doHandshake(
+        io.grpc.stub.StreamObserver<io.grpc.alts.internal.Handshaker.HandshakerResp> responseObserver) {
+      return asyncUnimplementedStreamingCall(getDoHandshakeMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getDoHandshakeMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.alts.internal.Handshaker.HandshakerReq,
+                io.grpc.alts.internal.Handshaker.HandshakerResp>(
+                  this, METHODID_DO_HANDSHAKE)))
+          .build();
+    }
+  }
+
+  /**
+   */
+  public static final class HandshakerServiceStub extends io.grpc.stub.AbstractStub<HandshakerServiceStub> {
+    private HandshakerServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private HandshakerServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected HandshakerServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new HandshakerServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Handshaker service accepts a stream of handshaker request, returning a
+     * stream of handshaker response. Client is expected to send exactly one
+     * message with either client_start or server_start followed by one or more
+     * messages with next. Each time client sends a request, the handshaker
+     * service expects to respond. Client does not have to wait for service's
+     * response before sending next request.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.alts.internal.Handshaker.HandshakerReq> doHandshake(
+        io.grpc.stub.StreamObserver<io.grpc.alts.internal.Handshaker.HandshakerResp> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getDoHandshakeMethod(), getCallOptions()), responseObserver);
+    }
+  }
+
+  /**
+   */
+  public static final class HandshakerServiceBlockingStub extends io.grpc.stub.AbstractStub<HandshakerServiceBlockingStub> {
+    private HandshakerServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private HandshakerServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected HandshakerServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new HandshakerServiceBlockingStub(channel, callOptions);
+    }
+  }
+
+  /**
+   */
+  public static final class HandshakerServiceFutureStub extends io.grpc.stub.AbstractStub<HandshakerServiceFutureStub> {
+    private HandshakerServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private HandshakerServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected HandshakerServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new HandshakerServiceFutureStub(channel, callOptions);
+    }
+  }
+
+  private static final int METHODID_DO_HANDSHAKE = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final HandshakerServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(HandshakerServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_DO_HANDSHAKE:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.doHandshake(
+              (io.grpc.stub.StreamObserver<io.grpc.alts.internal.Handshaker.HandshakerResp>) responseObserver);
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class HandshakerServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    HandshakerServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.alts.internal.Handshaker.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("HandshakerService");
+    }
+  }
+
+  private static final class HandshakerServiceFileDescriptorSupplier
+      extends HandshakerServiceBaseDescriptorSupplier {
+    HandshakerServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class HandshakerServiceMethodDescriptorSupplier
+      extends HandshakerServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    HandshakerServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (HandshakerServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new HandshakerServiceFileDescriptorSupplier())
+              .addMethod(getDoHandshakeMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/alts/src/generated/main/java/io/grpc/alts/internal/Altscontext.java b/alts/src/generated/main/java/io/grpc/alts/internal/Altscontext.java
new file mode 100644
index 0000000..d036c2e
--- /dev/null
+++ b/alts/src/generated/main/java/io/grpc/alts/internal/Altscontext.java
@@ -0,0 +1,1526 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/gcp/altscontext.proto
+
+package io.grpc.alts.internal;
+
+public final class Altscontext {
+  private Altscontext() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  public interface AltsContextOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.gcp.AltsContext)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * The application protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string application_protocol = 1;</code>
+     */
+    java.lang.String getApplicationProtocol();
+    /**
+     * <pre>
+     * The application protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string application_protocol = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getApplicationProtocolBytes();
+
+    /**
+     * <pre>
+     * The record protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string record_protocol = 2;</code>
+     */
+    java.lang.String getRecordProtocol();
+    /**
+     * <pre>
+     * The record protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string record_protocol = 2;</code>
+     */
+    com.google.protobuf.ByteString
+        getRecordProtocolBytes();
+
+    /**
+     * <pre>
+     * The security level of the created secure channel.
+     * </pre>
+     *
+     * <code>.grpc.gcp.SecurityLevel security_level = 3;</code>
+     */
+    int getSecurityLevelValue();
+    /**
+     * <pre>
+     * The security level of the created secure channel.
+     * </pre>
+     *
+     * <code>.grpc.gcp.SecurityLevel security_level = 3;</code>
+     */
+    io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel getSecurityLevel();
+
+    /**
+     * <pre>
+     * The peer service account.
+     * </pre>
+     *
+     * <code>string peer_service_account = 4;</code>
+     */
+    java.lang.String getPeerServiceAccount();
+    /**
+     * <pre>
+     * The peer service account.
+     * </pre>
+     *
+     * <code>string peer_service_account = 4;</code>
+     */
+    com.google.protobuf.ByteString
+        getPeerServiceAccountBytes();
+
+    /**
+     * <pre>
+     * The local service account.
+     * </pre>
+     *
+     * <code>string local_service_account = 5;</code>
+     */
+    java.lang.String getLocalServiceAccount();
+    /**
+     * <pre>
+     * The local service account.
+     * </pre>
+     *
+     * <code>string local_service_account = 5;</code>
+     */
+    com.google.protobuf.ByteString
+        getLocalServiceAccountBytes();
+
+    /**
+     * <pre>
+     * The RPC protocol versions supported by the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+     */
+    boolean hasPeerRpcVersions();
+    /**
+     * <pre>
+     * The RPC protocol versions supported by the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+     */
+    io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getPeerRpcVersions();
+    /**
+     * <pre>
+     * The RPC protocol versions supported by the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+     */
+    io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder getPeerRpcVersionsOrBuilder();
+  }
+  /**
+   * Protobuf type {@code grpc.gcp.AltsContext}
+   */
+  public  static final class AltsContext extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.gcp.AltsContext)
+      AltsContextOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use AltsContext.newBuilder() to construct.
+    private AltsContext(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private AltsContext() {
+      applicationProtocol_ = "";
+      recordProtocol_ = "";
+      securityLevel_ = 0;
+      peerServiceAccount_ = "";
+      localServiceAccount_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private AltsContext(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              applicationProtocol_ = s;
+              break;
+            }
+            case 18: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              recordProtocol_ = s;
+              break;
+            }
+            case 24: {
+              int rawValue = input.readEnum();
+
+              securityLevel_ = rawValue;
+              break;
+            }
+            case 34: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              peerServiceAccount_ = s;
+              break;
+            }
+            case 42: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              localServiceAccount_ = s;
+              break;
+            }
+            case 50: {
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder subBuilder = null;
+              if (peerRpcVersions_ != null) {
+                subBuilder = peerRpcVersions_.toBuilder();
+              }
+              peerRpcVersions_ = input.readMessage(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(peerRpcVersions_);
+                peerRpcVersions_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.Altscontext.internal_static_grpc_gcp_AltsContext_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.alts.internal.Altscontext.internal_static_grpc_gcp_AltsContext_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.alts.internal.Altscontext.AltsContext.class, io.grpc.alts.internal.Altscontext.AltsContext.Builder.class);
+    }
+
+    public static final int APPLICATION_PROTOCOL_FIELD_NUMBER = 1;
+    private volatile java.lang.Object applicationProtocol_;
+    /**
+     * <pre>
+     * The application protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string application_protocol = 1;</code>
+     */
+    public java.lang.String getApplicationProtocol() {
+      java.lang.Object ref = applicationProtocol_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        applicationProtocol_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * The application protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string application_protocol = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getApplicationProtocolBytes() {
+      java.lang.Object ref = applicationProtocol_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        applicationProtocol_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int RECORD_PROTOCOL_FIELD_NUMBER = 2;
+    private volatile java.lang.Object recordProtocol_;
+    /**
+     * <pre>
+     * The record protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string record_protocol = 2;</code>
+     */
+    public java.lang.String getRecordProtocol() {
+      java.lang.Object ref = recordProtocol_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        recordProtocol_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * The record protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string record_protocol = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getRecordProtocolBytes() {
+      java.lang.Object ref = recordProtocol_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        recordProtocol_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int SECURITY_LEVEL_FIELD_NUMBER = 3;
+    private int securityLevel_;
+    /**
+     * <pre>
+     * The security level of the created secure channel.
+     * </pre>
+     *
+     * <code>.grpc.gcp.SecurityLevel security_level = 3;</code>
+     */
+    public int getSecurityLevelValue() {
+      return securityLevel_;
+    }
+    /**
+     * <pre>
+     * The security level of the created secure channel.
+     * </pre>
+     *
+     * <code>.grpc.gcp.SecurityLevel security_level = 3;</code>
+     */
+    public io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel getSecurityLevel() {
+      io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel result = io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel.valueOf(securityLevel_);
+      return result == null ? io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel.UNRECOGNIZED : result;
+    }
+
+    public static final int PEER_SERVICE_ACCOUNT_FIELD_NUMBER = 4;
+    private volatile java.lang.Object peerServiceAccount_;
+    /**
+     * <pre>
+     * The peer service account.
+     * </pre>
+     *
+     * <code>string peer_service_account = 4;</code>
+     */
+    public java.lang.String getPeerServiceAccount() {
+      java.lang.Object ref = peerServiceAccount_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        peerServiceAccount_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * The peer service account.
+     * </pre>
+     *
+     * <code>string peer_service_account = 4;</code>
+     */
+    public com.google.protobuf.ByteString
+        getPeerServiceAccountBytes() {
+      java.lang.Object ref = peerServiceAccount_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        peerServiceAccount_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int LOCAL_SERVICE_ACCOUNT_FIELD_NUMBER = 5;
+    private volatile java.lang.Object localServiceAccount_;
+    /**
+     * <pre>
+     * The local service account.
+     * </pre>
+     *
+     * <code>string local_service_account = 5;</code>
+     */
+    public java.lang.String getLocalServiceAccount() {
+      java.lang.Object ref = localServiceAccount_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        localServiceAccount_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * The local service account.
+     * </pre>
+     *
+     * <code>string local_service_account = 5;</code>
+     */
+    public com.google.protobuf.ByteString
+        getLocalServiceAccountBytes() {
+      java.lang.Object ref = localServiceAccount_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        localServiceAccount_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int PEER_RPC_VERSIONS_FIELD_NUMBER = 6;
+    private io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions peerRpcVersions_;
+    /**
+     * <pre>
+     * The RPC protocol versions supported by the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+     */
+    public boolean hasPeerRpcVersions() {
+      return peerRpcVersions_ != null;
+    }
+    /**
+     * <pre>
+     * The RPC protocol versions supported by the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+     */
+    public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getPeerRpcVersions() {
+      return peerRpcVersions_ == null ? io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance() : peerRpcVersions_;
+    }
+    /**
+     * <pre>
+     * The RPC protocol versions supported by the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+     */
+    public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder getPeerRpcVersionsOrBuilder() {
+      return getPeerRpcVersions();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!getApplicationProtocolBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, applicationProtocol_);
+      }
+      if (!getRecordProtocolBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, recordProtocol_);
+      }
+      if (securityLevel_ != io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel.SECURITY_NONE.getNumber()) {
+        output.writeEnum(3, securityLevel_);
+      }
+      if (!getPeerServiceAccountBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 4, peerServiceAccount_);
+      }
+      if (!getLocalServiceAccountBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 5, localServiceAccount_);
+      }
+      if (peerRpcVersions_ != null) {
+        output.writeMessage(6, getPeerRpcVersions());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!getApplicationProtocolBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, applicationProtocol_);
+      }
+      if (!getRecordProtocolBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, recordProtocol_);
+      }
+      if (securityLevel_ != io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel.SECURITY_NONE.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(3, securityLevel_);
+      }
+      if (!getPeerServiceAccountBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(4, peerServiceAccount_);
+      }
+      if (!getLocalServiceAccountBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(5, localServiceAccount_);
+      }
+      if (peerRpcVersions_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(6, getPeerRpcVersions());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.alts.internal.Altscontext.AltsContext)) {
+        return super.equals(obj);
+      }
+      io.grpc.alts.internal.Altscontext.AltsContext other = (io.grpc.alts.internal.Altscontext.AltsContext) obj;
+
+      boolean result = true;
+      result = result && getApplicationProtocol()
+          .equals(other.getApplicationProtocol());
+      result = result && getRecordProtocol()
+          .equals(other.getRecordProtocol());
+      result = result && securityLevel_ == other.securityLevel_;
+      result = result && getPeerServiceAccount()
+          .equals(other.getPeerServiceAccount());
+      result = result && getLocalServiceAccount()
+          .equals(other.getLocalServiceAccount());
+      result = result && (hasPeerRpcVersions() == other.hasPeerRpcVersions());
+      if (hasPeerRpcVersions()) {
+        result = result && getPeerRpcVersions()
+            .equals(other.getPeerRpcVersions());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + APPLICATION_PROTOCOL_FIELD_NUMBER;
+      hash = (53 * hash) + getApplicationProtocol().hashCode();
+      hash = (37 * hash) + RECORD_PROTOCOL_FIELD_NUMBER;
+      hash = (53 * hash) + getRecordProtocol().hashCode();
+      hash = (37 * hash) + SECURITY_LEVEL_FIELD_NUMBER;
+      hash = (53 * hash) + securityLevel_;
+      hash = (37 * hash) + PEER_SERVICE_ACCOUNT_FIELD_NUMBER;
+      hash = (53 * hash) + getPeerServiceAccount().hashCode();
+      hash = (37 * hash) + LOCAL_SERVICE_ACCOUNT_FIELD_NUMBER;
+      hash = (53 * hash) + getLocalServiceAccount().hashCode();
+      if (hasPeerRpcVersions()) {
+        hash = (37 * hash) + PEER_RPC_VERSIONS_FIELD_NUMBER;
+        hash = (53 * hash) + getPeerRpcVersions().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.alts.internal.Altscontext.AltsContext parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Altscontext.AltsContext parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Altscontext.AltsContext parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Altscontext.AltsContext parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Altscontext.AltsContext parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Altscontext.AltsContext parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Altscontext.AltsContext parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Altscontext.AltsContext parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Altscontext.AltsContext parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Altscontext.AltsContext parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Altscontext.AltsContext parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Altscontext.AltsContext parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.alts.internal.Altscontext.AltsContext prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.gcp.AltsContext}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.gcp.AltsContext)
+        io.grpc.alts.internal.Altscontext.AltsContextOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.alts.internal.Altscontext.internal_static_grpc_gcp_AltsContext_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.alts.internal.Altscontext.internal_static_grpc_gcp_AltsContext_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.alts.internal.Altscontext.AltsContext.class, io.grpc.alts.internal.Altscontext.AltsContext.Builder.class);
+      }
+
+      // Construct using io.grpc.alts.internal.Altscontext.AltsContext.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        applicationProtocol_ = "";
+
+        recordProtocol_ = "";
+
+        securityLevel_ = 0;
+
+        peerServiceAccount_ = "";
+
+        localServiceAccount_ = "";
+
+        if (peerRpcVersionsBuilder_ == null) {
+          peerRpcVersions_ = null;
+        } else {
+          peerRpcVersions_ = null;
+          peerRpcVersionsBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.alts.internal.Altscontext.internal_static_grpc_gcp_AltsContext_descriptor;
+      }
+
+      public io.grpc.alts.internal.Altscontext.AltsContext getDefaultInstanceForType() {
+        return io.grpc.alts.internal.Altscontext.AltsContext.getDefaultInstance();
+      }
+
+      public io.grpc.alts.internal.Altscontext.AltsContext build() {
+        io.grpc.alts.internal.Altscontext.AltsContext result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.alts.internal.Altscontext.AltsContext buildPartial() {
+        io.grpc.alts.internal.Altscontext.AltsContext result = new io.grpc.alts.internal.Altscontext.AltsContext(this);
+        result.applicationProtocol_ = applicationProtocol_;
+        result.recordProtocol_ = recordProtocol_;
+        result.securityLevel_ = securityLevel_;
+        result.peerServiceAccount_ = peerServiceAccount_;
+        result.localServiceAccount_ = localServiceAccount_;
+        if (peerRpcVersionsBuilder_ == null) {
+          result.peerRpcVersions_ = peerRpcVersions_;
+        } else {
+          result.peerRpcVersions_ = peerRpcVersionsBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.alts.internal.Altscontext.AltsContext) {
+          return mergeFrom((io.grpc.alts.internal.Altscontext.AltsContext)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.alts.internal.Altscontext.AltsContext other) {
+        if (other == io.grpc.alts.internal.Altscontext.AltsContext.getDefaultInstance()) return this;
+        if (!other.getApplicationProtocol().isEmpty()) {
+          applicationProtocol_ = other.applicationProtocol_;
+          onChanged();
+        }
+        if (!other.getRecordProtocol().isEmpty()) {
+          recordProtocol_ = other.recordProtocol_;
+          onChanged();
+        }
+        if (other.securityLevel_ != 0) {
+          setSecurityLevelValue(other.getSecurityLevelValue());
+        }
+        if (!other.getPeerServiceAccount().isEmpty()) {
+          peerServiceAccount_ = other.peerServiceAccount_;
+          onChanged();
+        }
+        if (!other.getLocalServiceAccount().isEmpty()) {
+          localServiceAccount_ = other.localServiceAccount_;
+          onChanged();
+        }
+        if (other.hasPeerRpcVersions()) {
+          mergePeerRpcVersions(other.getPeerRpcVersions());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.alts.internal.Altscontext.AltsContext parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.alts.internal.Altscontext.AltsContext) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private java.lang.Object applicationProtocol_ = "";
+      /**
+       * <pre>
+       * The application protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string application_protocol = 1;</code>
+       */
+      public java.lang.String getApplicationProtocol() {
+        java.lang.Object ref = applicationProtocol_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          applicationProtocol_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The application protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string application_protocol = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getApplicationProtocolBytes() {
+        java.lang.Object ref = applicationProtocol_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          applicationProtocol_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The application protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string application_protocol = 1;</code>
+       */
+      public Builder setApplicationProtocol(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        applicationProtocol_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The application protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string application_protocol = 1;</code>
+       */
+      public Builder clearApplicationProtocol() {
+        
+        applicationProtocol_ = getDefaultInstance().getApplicationProtocol();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The application protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string application_protocol = 1;</code>
+       */
+      public Builder setApplicationProtocolBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        applicationProtocol_ = value;
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object recordProtocol_ = "";
+      /**
+       * <pre>
+       * The record protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string record_protocol = 2;</code>
+       */
+      public java.lang.String getRecordProtocol() {
+        java.lang.Object ref = recordProtocol_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          recordProtocol_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The record protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string record_protocol = 2;</code>
+       */
+      public com.google.protobuf.ByteString
+          getRecordProtocolBytes() {
+        java.lang.Object ref = recordProtocol_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          recordProtocol_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The record protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string record_protocol = 2;</code>
+       */
+      public Builder setRecordProtocol(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        recordProtocol_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The record protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string record_protocol = 2;</code>
+       */
+      public Builder clearRecordProtocol() {
+        
+        recordProtocol_ = getDefaultInstance().getRecordProtocol();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The record protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string record_protocol = 2;</code>
+       */
+      public Builder setRecordProtocolBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        recordProtocol_ = value;
+        onChanged();
+        return this;
+      }
+
+      private int securityLevel_ = 0;
+      /**
+       * <pre>
+       * The security level of the created secure channel.
+       * </pre>
+       *
+       * <code>.grpc.gcp.SecurityLevel security_level = 3;</code>
+       */
+      public int getSecurityLevelValue() {
+        return securityLevel_;
+      }
+      /**
+       * <pre>
+       * The security level of the created secure channel.
+       * </pre>
+       *
+       * <code>.grpc.gcp.SecurityLevel security_level = 3;</code>
+       */
+      public Builder setSecurityLevelValue(int value) {
+        securityLevel_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The security level of the created secure channel.
+       * </pre>
+       *
+       * <code>.grpc.gcp.SecurityLevel security_level = 3;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel getSecurityLevel() {
+        io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel result = io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel.valueOf(securityLevel_);
+        return result == null ? io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel.UNRECOGNIZED : result;
+      }
+      /**
+       * <pre>
+       * The security level of the created secure channel.
+       * </pre>
+       *
+       * <code>.grpc.gcp.SecurityLevel security_level = 3;</code>
+       */
+      public Builder setSecurityLevel(io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        securityLevel_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The security level of the created secure channel.
+       * </pre>
+       *
+       * <code>.grpc.gcp.SecurityLevel security_level = 3;</code>
+       */
+      public Builder clearSecurityLevel() {
+        
+        securityLevel_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object peerServiceAccount_ = "";
+      /**
+       * <pre>
+       * The peer service account.
+       * </pre>
+       *
+       * <code>string peer_service_account = 4;</code>
+       */
+      public java.lang.String getPeerServiceAccount() {
+        java.lang.Object ref = peerServiceAccount_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          peerServiceAccount_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The peer service account.
+       * </pre>
+       *
+       * <code>string peer_service_account = 4;</code>
+       */
+      public com.google.protobuf.ByteString
+          getPeerServiceAccountBytes() {
+        java.lang.Object ref = peerServiceAccount_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          peerServiceAccount_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The peer service account.
+       * </pre>
+       *
+       * <code>string peer_service_account = 4;</code>
+       */
+      public Builder setPeerServiceAccount(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        peerServiceAccount_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The peer service account.
+       * </pre>
+       *
+       * <code>string peer_service_account = 4;</code>
+       */
+      public Builder clearPeerServiceAccount() {
+        
+        peerServiceAccount_ = getDefaultInstance().getPeerServiceAccount();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The peer service account.
+       * </pre>
+       *
+       * <code>string peer_service_account = 4;</code>
+       */
+      public Builder setPeerServiceAccountBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        peerServiceAccount_ = value;
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object localServiceAccount_ = "";
+      /**
+       * <pre>
+       * The local service account.
+       * </pre>
+       *
+       * <code>string local_service_account = 5;</code>
+       */
+      public java.lang.String getLocalServiceAccount() {
+        java.lang.Object ref = localServiceAccount_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          localServiceAccount_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The local service account.
+       * </pre>
+       *
+       * <code>string local_service_account = 5;</code>
+       */
+      public com.google.protobuf.ByteString
+          getLocalServiceAccountBytes() {
+        java.lang.Object ref = localServiceAccount_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          localServiceAccount_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The local service account.
+       * </pre>
+       *
+       * <code>string local_service_account = 5;</code>
+       */
+      public Builder setLocalServiceAccount(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        localServiceAccount_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The local service account.
+       * </pre>
+       *
+       * <code>string local_service_account = 5;</code>
+       */
+      public Builder clearLocalServiceAccount() {
+        
+        localServiceAccount_ = getDefaultInstance().getLocalServiceAccount();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The local service account.
+       * </pre>
+       *
+       * <code>string local_service_account = 5;</code>
+       */
+      public Builder setLocalServiceAccountBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        localServiceAccount_ = value;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions peerRpcVersions_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder> peerRpcVersionsBuilder_;
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+       */
+      public boolean hasPeerRpcVersions() {
+        return peerRpcVersionsBuilder_ != null || peerRpcVersions_ != null;
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getPeerRpcVersions() {
+        if (peerRpcVersionsBuilder_ == null) {
+          return peerRpcVersions_ == null ? io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance() : peerRpcVersions_;
+        } else {
+          return peerRpcVersionsBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+       */
+      public Builder setPeerRpcVersions(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions value) {
+        if (peerRpcVersionsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          peerRpcVersions_ = value;
+          onChanged();
+        } else {
+          peerRpcVersionsBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+       */
+      public Builder setPeerRpcVersions(
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder builderForValue) {
+        if (peerRpcVersionsBuilder_ == null) {
+          peerRpcVersions_ = builderForValue.build();
+          onChanged();
+        } else {
+          peerRpcVersionsBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+       */
+      public Builder mergePeerRpcVersions(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions value) {
+        if (peerRpcVersionsBuilder_ == null) {
+          if (peerRpcVersions_ != null) {
+            peerRpcVersions_ =
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.newBuilder(peerRpcVersions_).mergeFrom(value).buildPartial();
+          } else {
+            peerRpcVersions_ = value;
+          }
+          onChanged();
+        } else {
+          peerRpcVersionsBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+       */
+      public Builder clearPeerRpcVersions() {
+        if (peerRpcVersionsBuilder_ == null) {
+          peerRpcVersions_ = null;
+          onChanged();
+        } else {
+          peerRpcVersions_ = null;
+          peerRpcVersionsBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder getPeerRpcVersionsBuilder() {
+        
+        onChanged();
+        return getPeerRpcVersionsFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder getPeerRpcVersionsOrBuilder() {
+        if (peerRpcVersionsBuilder_ != null) {
+          return peerRpcVersionsBuilder_.getMessageOrBuilder();
+        } else {
+          return peerRpcVersions_ == null ?
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance() : peerRpcVersions_;
+        }
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 6;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder> 
+          getPeerRpcVersionsFieldBuilder() {
+        if (peerRpcVersionsBuilder_ == null) {
+          peerRpcVersionsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder>(
+                  getPeerRpcVersions(),
+                  getParentForChildren(),
+                  isClean());
+          peerRpcVersions_ = null;
+        }
+        return peerRpcVersionsBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.gcp.AltsContext)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.gcp.AltsContext)
+    private static final io.grpc.alts.internal.Altscontext.AltsContext DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.alts.internal.Altscontext.AltsContext();
+    }
+
+    public static io.grpc.alts.internal.Altscontext.AltsContext getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<AltsContext>
+        PARSER = new com.google.protobuf.AbstractParser<AltsContext>() {
+      public AltsContext parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new AltsContext(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<AltsContext> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<AltsContext> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.alts.internal.Altscontext.AltsContext getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_AltsContext_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_AltsContext_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\032grpc/gcp/altscontext.proto\022\010grpc.gcp\032(" +
+      "grpc/gcp/transport_security_common.proto" +
+      "\"\354\001\n\013AltsContext\022\034\n\024application_protocol" +
+      "\030\001 \001(\t\022\027\n\017record_protocol\030\002 \001(\t\022/\n\016secur" +
+      "ity_level\030\003 \001(\0162\027.grpc.gcp.SecurityLevel" +
+      "\022\034\n\024peer_service_account\030\004 \001(\t\022\035\n\025local_" +
+      "service_account\030\005 \001(\t\0228\n\021peer_rpc_versio" +
+      "ns\030\006 \001(\0132\035.grpc.gcp.RpcProtocolVersionsB" +
+      "\027\n\025io.grpc.alts.internalb\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          io.grpc.alts.internal.TransportSecurityCommon.getDescriptor(),
+        }, assigner);
+    internal_static_grpc_gcp_AltsContext_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_gcp_AltsContext_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_AltsContext_descriptor,
+        new java.lang.String[] { "ApplicationProtocol", "RecordProtocol", "SecurityLevel", "PeerServiceAccount", "LocalServiceAccount", "PeerRpcVersions", });
+    io.grpc.alts.internal.TransportSecurityCommon.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/alts/src/generated/main/java/io/grpc/alts/internal/Handshaker.java b/alts/src/generated/main/java/io/grpc/alts/internal/Handshaker.java
new file mode 100644
index 0000000..51dc505
--- /dev/null
+++ b/alts/src/generated/main/java/io/grpc/alts/internal/Handshaker.java
@@ -0,0 +1,13320 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/gcp/handshaker.proto
+
+package io.grpc.alts.internal;
+
+public final class Handshaker {
+  private Handshaker() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  /**
+   * Protobuf enum {@code grpc.gcp.HandshakeProtocol}
+   */
+  public enum HandshakeProtocol
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <pre>
+     * Default value.
+     * </pre>
+     *
+     * <code>HANDSHAKE_PROTOCOL_UNSPECIFIED = 0;</code>
+     */
+    HANDSHAKE_PROTOCOL_UNSPECIFIED(0),
+    /**
+     * <pre>
+     * TLS handshake protocol.
+     * </pre>
+     *
+     * <code>TLS = 1;</code>
+     */
+    TLS(1),
+    /**
+     * <pre>
+     * Application Layer Transport Security handshake protocol.
+     * </pre>
+     *
+     * <code>ALTS = 2;</code>
+     */
+    ALTS(2),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <pre>
+     * Default value.
+     * </pre>
+     *
+     * <code>HANDSHAKE_PROTOCOL_UNSPECIFIED = 0;</code>
+     */
+    public static final int HANDSHAKE_PROTOCOL_UNSPECIFIED_VALUE = 0;
+    /**
+     * <pre>
+     * TLS handshake protocol.
+     * </pre>
+     *
+     * <code>TLS = 1;</code>
+     */
+    public static final int TLS_VALUE = 1;
+    /**
+     * <pre>
+     * Application Layer Transport Security handshake protocol.
+     * </pre>
+     *
+     * <code>ALTS = 2;</code>
+     */
+    public static final int ALTS_VALUE = 2;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static HandshakeProtocol valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static HandshakeProtocol forNumber(int value) {
+      switch (value) {
+        case 0: return HANDSHAKE_PROTOCOL_UNSPECIFIED;
+        case 1: return TLS;
+        case 2: return ALTS;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<HandshakeProtocol>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        HandshakeProtocol> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<HandshakeProtocol>() {
+            public HandshakeProtocol findValueByNumber(int number) {
+              return HandshakeProtocol.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.Handshaker.getDescriptor().getEnumTypes().get(0);
+    }
+
+    private static final HandshakeProtocol[] VALUES = values();
+
+    public static HandshakeProtocol valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private HandshakeProtocol(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.gcp.HandshakeProtocol)
+  }
+
+  /**
+   * Protobuf enum {@code grpc.gcp.NetworkProtocol}
+   */
+  public enum NetworkProtocol
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>NETWORK_PROTOCOL_UNSPECIFIED = 0;</code>
+     */
+    NETWORK_PROTOCOL_UNSPECIFIED(0),
+    /**
+     * <code>TCP = 1;</code>
+     */
+    TCP(1),
+    /**
+     * <code>UDP = 2;</code>
+     */
+    UDP(2),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <code>NETWORK_PROTOCOL_UNSPECIFIED = 0;</code>
+     */
+    public static final int NETWORK_PROTOCOL_UNSPECIFIED_VALUE = 0;
+    /**
+     * <code>TCP = 1;</code>
+     */
+    public static final int TCP_VALUE = 1;
+    /**
+     * <code>UDP = 2;</code>
+     */
+    public static final int UDP_VALUE = 2;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static NetworkProtocol valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static NetworkProtocol forNumber(int value) {
+      switch (value) {
+        case 0: return NETWORK_PROTOCOL_UNSPECIFIED;
+        case 1: return TCP;
+        case 2: return UDP;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<NetworkProtocol>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        NetworkProtocol> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<NetworkProtocol>() {
+            public NetworkProtocol findValueByNumber(int number) {
+              return NetworkProtocol.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.Handshaker.getDescriptor().getEnumTypes().get(1);
+    }
+
+    private static final NetworkProtocol[] VALUES = values();
+
+    public static NetworkProtocol valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private NetworkProtocol(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.gcp.NetworkProtocol)
+  }
+
+  public interface EndpointOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.gcp.Endpoint)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * IP address. It should contain an IPv4 or IPv6 string literal, e.g.
+     * "192.168.0.1" or "2001:db8::1".
+     * </pre>
+     *
+     * <code>string ip_address = 1;</code>
+     */
+    java.lang.String getIpAddress();
+    /**
+     * <pre>
+     * IP address. It should contain an IPv4 or IPv6 string literal, e.g.
+     * "192.168.0.1" or "2001:db8::1".
+     * </pre>
+     *
+     * <code>string ip_address = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getIpAddressBytes();
+
+    /**
+     * <pre>
+     * Port number.
+     * </pre>
+     *
+     * <code>int32 port = 2;</code>
+     */
+    int getPort();
+
+    /**
+     * <pre>
+     * Network protocol (e.g., TCP, UDP) associated with this endpoint.
+     * </pre>
+     *
+     * <code>.grpc.gcp.NetworkProtocol protocol = 3;</code>
+     */
+    int getProtocolValue();
+    /**
+     * <pre>
+     * Network protocol (e.g., TCP, UDP) associated with this endpoint.
+     * </pre>
+     *
+     * <code>.grpc.gcp.NetworkProtocol protocol = 3;</code>
+     */
+    io.grpc.alts.internal.Handshaker.NetworkProtocol getProtocol();
+  }
+  /**
+   * Protobuf type {@code grpc.gcp.Endpoint}
+   */
+  public  static final class Endpoint extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.gcp.Endpoint)
+      EndpointOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use Endpoint.newBuilder() to construct.
+    private Endpoint(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private Endpoint() {
+      ipAddress_ = "";
+      port_ = 0;
+      protocol_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private Endpoint(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              ipAddress_ = s;
+              break;
+            }
+            case 16: {
+
+              port_ = input.readInt32();
+              break;
+            }
+            case 24: {
+              int rawValue = input.readEnum();
+
+              protocol_ = rawValue;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_Endpoint_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_Endpoint_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.alts.internal.Handshaker.Endpoint.class, io.grpc.alts.internal.Handshaker.Endpoint.Builder.class);
+    }
+
+    public static final int IP_ADDRESS_FIELD_NUMBER = 1;
+    private volatile java.lang.Object ipAddress_;
+    /**
+     * <pre>
+     * IP address. It should contain an IPv4 or IPv6 string literal, e.g.
+     * "192.168.0.1" or "2001:db8::1".
+     * </pre>
+     *
+     * <code>string ip_address = 1;</code>
+     */
+    public java.lang.String getIpAddress() {
+      java.lang.Object ref = ipAddress_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        ipAddress_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * IP address. It should contain an IPv4 or IPv6 string literal, e.g.
+     * "192.168.0.1" or "2001:db8::1".
+     * </pre>
+     *
+     * <code>string ip_address = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getIpAddressBytes() {
+      java.lang.Object ref = ipAddress_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        ipAddress_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int PORT_FIELD_NUMBER = 2;
+    private int port_;
+    /**
+     * <pre>
+     * Port number.
+     * </pre>
+     *
+     * <code>int32 port = 2;</code>
+     */
+    public int getPort() {
+      return port_;
+    }
+
+    public static final int PROTOCOL_FIELD_NUMBER = 3;
+    private int protocol_;
+    /**
+     * <pre>
+     * Network protocol (e.g., TCP, UDP) associated with this endpoint.
+     * </pre>
+     *
+     * <code>.grpc.gcp.NetworkProtocol protocol = 3;</code>
+     */
+    public int getProtocolValue() {
+      return protocol_;
+    }
+    /**
+     * <pre>
+     * Network protocol (e.g., TCP, UDP) associated with this endpoint.
+     * </pre>
+     *
+     * <code>.grpc.gcp.NetworkProtocol protocol = 3;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.NetworkProtocol getProtocol() {
+      io.grpc.alts.internal.Handshaker.NetworkProtocol result = io.grpc.alts.internal.Handshaker.NetworkProtocol.valueOf(protocol_);
+      return result == null ? io.grpc.alts.internal.Handshaker.NetworkProtocol.UNRECOGNIZED : result;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!getIpAddressBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, ipAddress_);
+      }
+      if (port_ != 0) {
+        output.writeInt32(2, port_);
+      }
+      if (protocol_ != io.grpc.alts.internal.Handshaker.NetworkProtocol.NETWORK_PROTOCOL_UNSPECIFIED.getNumber()) {
+        output.writeEnum(3, protocol_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!getIpAddressBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, ipAddress_);
+      }
+      if (port_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(2, port_);
+      }
+      if (protocol_ != io.grpc.alts.internal.Handshaker.NetworkProtocol.NETWORK_PROTOCOL_UNSPECIFIED.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(3, protocol_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.alts.internal.Handshaker.Endpoint)) {
+        return super.equals(obj);
+      }
+      io.grpc.alts.internal.Handshaker.Endpoint other = (io.grpc.alts.internal.Handshaker.Endpoint) obj;
+
+      boolean result = true;
+      result = result && getIpAddress()
+          .equals(other.getIpAddress());
+      result = result && (getPort()
+          == other.getPort());
+      result = result && protocol_ == other.protocol_;
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + IP_ADDRESS_FIELD_NUMBER;
+      hash = (53 * hash) + getIpAddress().hashCode();
+      hash = (37 * hash) + PORT_FIELD_NUMBER;
+      hash = (53 * hash) + getPort();
+      hash = (37 * hash) + PROTOCOL_FIELD_NUMBER;
+      hash = (53 * hash) + protocol_;
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.alts.internal.Handshaker.Endpoint parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.Endpoint parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.Endpoint parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.Endpoint parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.Endpoint parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.Endpoint parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.Endpoint parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.Endpoint parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.Endpoint parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.Endpoint parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.Endpoint parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.Endpoint parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.alts.internal.Handshaker.Endpoint prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.gcp.Endpoint}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.gcp.Endpoint)
+        io.grpc.alts.internal.Handshaker.EndpointOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_Endpoint_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_Endpoint_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.alts.internal.Handshaker.Endpoint.class, io.grpc.alts.internal.Handshaker.Endpoint.Builder.class);
+      }
+
+      // Construct using io.grpc.alts.internal.Handshaker.Endpoint.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        ipAddress_ = "";
+
+        port_ = 0;
+
+        protocol_ = 0;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_Endpoint_descriptor;
+      }
+
+      public io.grpc.alts.internal.Handshaker.Endpoint getDefaultInstanceForType() {
+        return io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance();
+      }
+
+      public io.grpc.alts.internal.Handshaker.Endpoint build() {
+        io.grpc.alts.internal.Handshaker.Endpoint result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.alts.internal.Handshaker.Endpoint buildPartial() {
+        io.grpc.alts.internal.Handshaker.Endpoint result = new io.grpc.alts.internal.Handshaker.Endpoint(this);
+        result.ipAddress_ = ipAddress_;
+        result.port_ = port_;
+        result.protocol_ = protocol_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.alts.internal.Handshaker.Endpoint) {
+          return mergeFrom((io.grpc.alts.internal.Handshaker.Endpoint)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.alts.internal.Handshaker.Endpoint other) {
+        if (other == io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance()) return this;
+        if (!other.getIpAddress().isEmpty()) {
+          ipAddress_ = other.ipAddress_;
+          onChanged();
+        }
+        if (other.getPort() != 0) {
+          setPort(other.getPort());
+        }
+        if (other.protocol_ != 0) {
+          setProtocolValue(other.getProtocolValue());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.alts.internal.Handshaker.Endpoint parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.alts.internal.Handshaker.Endpoint) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private java.lang.Object ipAddress_ = "";
+      /**
+       * <pre>
+       * IP address. It should contain an IPv4 or IPv6 string literal, e.g.
+       * "192.168.0.1" or "2001:db8::1".
+       * </pre>
+       *
+       * <code>string ip_address = 1;</code>
+       */
+      public java.lang.String getIpAddress() {
+        java.lang.Object ref = ipAddress_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          ipAddress_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * IP address. It should contain an IPv4 or IPv6 string literal, e.g.
+       * "192.168.0.1" or "2001:db8::1".
+       * </pre>
+       *
+       * <code>string ip_address = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getIpAddressBytes() {
+        java.lang.Object ref = ipAddress_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          ipAddress_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * IP address. It should contain an IPv4 or IPv6 string literal, e.g.
+       * "192.168.0.1" or "2001:db8::1".
+       * </pre>
+       *
+       * <code>string ip_address = 1;</code>
+       */
+      public Builder setIpAddress(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        ipAddress_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * IP address. It should contain an IPv4 or IPv6 string literal, e.g.
+       * "192.168.0.1" or "2001:db8::1".
+       * </pre>
+       *
+       * <code>string ip_address = 1;</code>
+       */
+      public Builder clearIpAddress() {
+        
+        ipAddress_ = getDefaultInstance().getIpAddress();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * IP address. It should contain an IPv4 or IPv6 string literal, e.g.
+       * "192.168.0.1" or "2001:db8::1".
+       * </pre>
+       *
+       * <code>string ip_address = 1;</code>
+       */
+      public Builder setIpAddressBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        ipAddress_ = value;
+        onChanged();
+        return this;
+      }
+
+      private int port_ ;
+      /**
+       * <pre>
+       * Port number.
+       * </pre>
+       *
+       * <code>int32 port = 2;</code>
+       */
+      public int getPort() {
+        return port_;
+      }
+      /**
+       * <pre>
+       * Port number.
+       * </pre>
+       *
+       * <code>int32 port = 2;</code>
+       */
+      public Builder setPort(int value) {
+        
+        port_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Port number.
+       * </pre>
+       *
+       * <code>int32 port = 2;</code>
+       */
+      public Builder clearPort() {
+        
+        port_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int protocol_ = 0;
+      /**
+       * <pre>
+       * Network protocol (e.g., TCP, UDP) associated with this endpoint.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NetworkProtocol protocol = 3;</code>
+       */
+      public int getProtocolValue() {
+        return protocol_;
+      }
+      /**
+       * <pre>
+       * Network protocol (e.g., TCP, UDP) associated with this endpoint.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NetworkProtocol protocol = 3;</code>
+       */
+      public Builder setProtocolValue(int value) {
+        protocol_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Network protocol (e.g., TCP, UDP) associated with this endpoint.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NetworkProtocol protocol = 3;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.NetworkProtocol getProtocol() {
+        io.grpc.alts.internal.Handshaker.NetworkProtocol result = io.grpc.alts.internal.Handshaker.NetworkProtocol.valueOf(protocol_);
+        return result == null ? io.grpc.alts.internal.Handshaker.NetworkProtocol.UNRECOGNIZED : result;
+      }
+      /**
+       * <pre>
+       * Network protocol (e.g., TCP, UDP) associated with this endpoint.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NetworkProtocol protocol = 3;</code>
+       */
+      public Builder setProtocol(io.grpc.alts.internal.Handshaker.NetworkProtocol value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        protocol_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Network protocol (e.g., TCP, UDP) associated with this endpoint.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NetworkProtocol protocol = 3;</code>
+       */
+      public Builder clearProtocol() {
+        
+        protocol_ = 0;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.gcp.Endpoint)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.gcp.Endpoint)
+    private static final io.grpc.alts.internal.Handshaker.Endpoint DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.alts.internal.Handshaker.Endpoint();
+    }
+
+    public static io.grpc.alts.internal.Handshaker.Endpoint getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<Endpoint>
+        PARSER = new com.google.protobuf.AbstractParser<Endpoint>() {
+      public Endpoint parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new Endpoint(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<Endpoint> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<Endpoint> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.alts.internal.Handshaker.Endpoint getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface IdentityOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.gcp.Identity)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Service account of a connection endpoint.
+     * </pre>
+     *
+     * <code>string service_account = 1;</code>
+     */
+    java.lang.String getServiceAccount();
+    /**
+     * <pre>
+     * Service account of a connection endpoint.
+     * </pre>
+     *
+     * <code>string service_account = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getServiceAccountBytes();
+
+    /**
+     * <pre>
+     * Hostname of a connection endpoint.
+     * </pre>
+     *
+     * <code>string hostname = 2;</code>
+     */
+    java.lang.String getHostname();
+    /**
+     * <pre>
+     * Hostname of a connection endpoint.
+     * </pre>
+     *
+     * <code>string hostname = 2;</code>
+     */
+    com.google.protobuf.ByteString
+        getHostnameBytes();
+
+    public io.grpc.alts.internal.Handshaker.Identity.IdentityOneofCase getIdentityOneofCase();
+  }
+  /**
+   * Protobuf type {@code grpc.gcp.Identity}
+   */
+  public  static final class Identity extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.gcp.Identity)
+      IdentityOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use Identity.newBuilder() to construct.
+    private Identity(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private Identity() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private Identity(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+              identityOneofCase_ = 1;
+              identityOneof_ = s;
+              break;
+            }
+            case 18: {
+              java.lang.String s = input.readStringRequireUtf8();
+              identityOneofCase_ = 2;
+              identityOneof_ = s;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_Identity_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_Identity_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.alts.internal.Handshaker.Identity.class, io.grpc.alts.internal.Handshaker.Identity.Builder.class);
+    }
+
+    private int identityOneofCase_ = 0;
+    private java.lang.Object identityOneof_;
+    public enum IdentityOneofCase
+        implements com.google.protobuf.Internal.EnumLite {
+      SERVICE_ACCOUNT(1),
+      HOSTNAME(2),
+      IDENTITYONEOF_NOT_SET(0);
+      private final int value;
+      private IdentityOneofCase(int value) {
+        this.value = value;
+      }
+      /**
+       * @deprecated Use {@link #forNumber(int)} instead.
+       */
+      @java.lang.Deprecated
+      public static IdentityOneofCase valueOf(int value) {
+        return forNumber(value);
+      }
+
+      public static IdentityOneofCase forNumber(int value) {
+        switch (value) {
+          case 1: return SERVICE_ACCOUNT;
+          case 2: return HOSTNAME;
+          case 0: return IDENTITYONEOF_NOT_SET;
+          default: return null;
+        }
+      }
+      public int getNumber() {
+        return this.value;
+      }
+    };
+
+    public IdentityOneofCase
+    getIdentityOneofCase() {
+      return IdentityOneofCase.forNumber(
+          identityOneofCase_);
+    }
+
+    public static final int SERVICE_ACCOUNT_FIELD_NUMBER = 1;
+    /**
+     * <pre>
+     * Service account of a connection endpoint.
+     * </pre>
+     *
+     * <code>string service_account = 1;</code>
+     */
+    public java.lang.String getServiceAccount() {
+      java.lang.Object ref = "";
+      if (identityOneofCase_ == 1) {
+        ref = identityOneof_;
+      }
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (identityOneofCase_ == 1) {
+          identityOneof_ = s;
+        }
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * Service account of a connection endpoint.
+     * </pre>
+     *
+     * <code>string service_account = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getServiceAccountBytes() {
+      java.lang.Object ref = "";
+      if (identityOneofCase_ == 1) {
+        ref = identityOneof_;
+      }
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        if (identityOneofCase_ == 1) {
+          identityOneof_ = b;
+        }
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int HOSTNAME_FIELD_NUMBER = 2;
+    /**
+     * <pre>
+     * Hostname of a connection endpoint.
+     * </pre>
+     *
+     * <code>string hostname = 2;</code>
+     */
+    public java.lang.String getHostname() {
+      java.lang.Object ref = "";
+      if (identityOneofCase_ == 2) {
+        ref = identityOneof_;
+      }
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (identityOneofCase_ == 2) {
+          identityOneof_ = s;
+        }
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * Hostname of a connection endpoint.
+     * </pre>
+     *
+     * <code>string hostname = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getHostnameBytes() {
+      java.lang.Object ref = "";
+      if (identityOneofCase_ == 2) {
+        ref = identityOneof_;
+      }
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        if (identityOneofCase_ == 2) {
+          identityOneof_ = b;
+        }
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (identityOneofCase_ == 1) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, identityOneof_);
+      }
+      if (identityOneofCase_ == 2) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, identityOneof_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (identityOneofCase_ == 1) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, identityOneof_);
+      }
+      if (identityOneofCase_ == 2) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, identityOneof_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.alts.internal.Handshaker.Identity)) {
+        return super.equals(obj);
+      }
+      io.grpc.alts.internal.Handshaker.Identity other = (io.grpc.alts.internal.Handshaker.Identity) obj;
+
+      boolean result = true;
+      result = result && getIdentityOneofCase().equals(
+          other.getIdentityOneofCase());
+      if (!result) return false;
+      switch (identityOneofCase_) {
+        case 1:
+          result = result && getServiceAccount()
+              .equals(other.getServiceAccount());
+          break;
+        case 2:
+          result = result && getHostname()
+              .equals(other.getHostname());
+          break;
+        case 0:
+        default:
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      switch (identityOneofCase_) {
+        case 1:
+          hash = (37 * hash) + SERVICE_ACCOUNT_FIELD_NUMBER;
+          hash = (53 * hash) + getServiceAccount().hashCode();
+          break;
+        case 2:
+          hash = (37 * hash) + HOSTNAME_FIELD_NUMBER;
+          hash = (53 * hash) + getHostname().hashCode();
+          break;
+        case 0:
+        default:
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.alts.internal.Handshaker.Identity parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.Identity parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.Identity parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.Identity parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.Identity parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.Identity parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.Identity parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.Identity parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.Identity parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.Identity parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.Identity parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.Identity parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.alts.internal.Handshaker.Identity prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.gcp.Identity}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.gcp.Identity)
+        io.grpc.alts.internal.Handshaker.IdentityOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_Identity_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_Identity_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.alts.internal.Handshaker.Identity.class, io.grpc.alts.internal.Handshaker.Identity.Builder.class);
+      }
+
+      // Construct using io.grpc.alts.internal.Handshaker.Identity.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        identityOneofCase_ = 0;
+        identityOneof_ = null;
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_Identity_descriptor;
+      }
+
+      public io.grpc.alts.internal.Handshaker.Identity getDefaultInstanceForType() {
+        return io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance();
+      }
+
+      public io.grpc.alts.internal.Handshaker.Identity build() {
+        io.grpc.alts.internal.Handshaker.Identity result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.alts.internal.Handshaker.Identity buildPartial() {
+        io.grpc.alts.internal.Handshaker.Identity result = new io.grpc.alts.internal.Handshaker.Identity(this);
+        if (identityOneofCase_ == 1) {
+          result.identityOneof_ = identityOneof_;
+        }
+        if (identityOneofCase_ == 2) {
+          result.identityOneof_ = identityOneof_;
+        }
+        result.identityOneofCase_ = identityOneofCase_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.alts.internal.Handshaker.Identity) {
+          return mergeFrom((io.grpc.alts.internal.Handshaker.Identity)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.alts.internal.Handshaker.Identity other) {
+        if (other == io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance()) return this;
+        switch (other.getIdentityOneofCase()) {
+          case SERVICE_ACCOUNT: {
+            identityOneofCase_ = 1;
+            identityOneof_ = other.identityOneof_;
+            onChanged();
+            break;
+          }
+          case HOSTNAME: {
+            identityOneofCase_ = 2;
+            identityOneof_ = other.identityOneof_;
+            onChanged();
+            break;
+          }
+          case IDENTITYONEOF_NOT_SET: {
+            break;
+          }
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.alts.internal.Handshaker.Identity parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.alts.internal.Handshaker.Identity) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int identityOneofCase_ = 0;
+      private java.lang.Object identityOneof_;
+      public IdentityOneofCase
+          getIdentityOneofCase() {
+        return IdentityOneofCase.forNumber(
+            identityOneofCase_);
+      }
+
+      public Builder clearIdentityOneof() {
+        identityOneofCase_ = 0;
+        identityOneof_ = null;
+        onChanged();
+        return this;
+      }
+
+
+      /**
+       * <pre>
+       * Service account of a connection endpoint.
+       * </pre>
+       *
+       * <code>string service_account = 1;</code>
+       */
+      public java.lang.String getServiceAccount() {
+        java.lang.Object ref = "";
+        if (identityOneofCase_ == 1) {
+          ref = identityOneof_;
+        }
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          if (identityOneofCase_ == 1) {
+            identityOneof_ = s;
+          }
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * Service account of a connection endpoint.
+       * </pre>
+       *
+       * <code>string service_account = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getServiceAccountBytes() {
+        java.lang.Object ref = "";
+        if (identityOneofCase_ == 1) {
+          ref = identityOneof_;
+        }
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          if (identityOneofCase_ == 1) {
+            identityOneof_ = b;
+          }
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * Service account of a connection endpoint.
+       * </pre>
+       *
+       * <code>string service_account = 1;</code>
+       */
+      public Builder setServiceAccount(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  identityOneofCase_ = 1;
+        identityOneof_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Service account of a connection endpoint.
+       * </pre>
+       *
+       * <code>string service_account = 1;</code>
+       */
+      public Builder clearServiceAccount() {
+        if (identityOneofCase_ == 1) {
+          identityOneofCase_ = 0;
+          identityOneof_ = null;
+          onChanged();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Service account of a connection endpoint.
+       * </pre>
+       *
+       * <code>string service_account = 1;</code>
+       */
+      public Builder setServiceAccountBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        identityOneofCase_ = 1;
+        identityOneof_ = value;
+        onChanged();
+        return this;
+      }
+
+      /**
+       * <pre>
+       * Hostname of a connection endpoint.
+       * </pre>
+       *
+       * <code>string hostname = 2;</code>
+       */
+      public java.lang.String getHostname() {
+        java.lang.Object ref = "";
+        if (identityOneofCase_ == 2) {
+          ref = identityOneof_;
+        }
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          if (identityOneofCase_ == 2) {
+            identityOneof_ = s;
+          }
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * Hostname of a connection endpoint.
+       * </pre>
+       *
+       * <code>string hostname = 2;</code>
+       */
+      public com.google.protobuf.ByteString
+          getHostnameBytes() {
+        java.lang.Object ref = "";
+        if (identityOneofCase_ == 2) {
+          ref = identityOneof_;
+        }
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          if (identityOneofCase_ == 2) {
+            identityOneof_ = b;
+          }
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * Hostname of a connection endpoint.
+       * </pre>
+       *
+       * <code>string hostname = 2;</code>
+       */
+      public Builder setHostname(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  identityOneofCase_ = 2;
+        identityOneof_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Hostname of a connection endpoint.
+       * </pre>
+       *
+       * <code>string hostname = 2;</code>
+       */
+      public Builder clearHostname() {
+        if (identityOneofCase_ == 2) {
+          identityOneofCase_ = 0;
+          identityOneof_ = null;
+          onChanged();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Hostname of a connection endpoint.
+       * </pre>
+       *
+       * <code>string hostname = 2;</code>
+       */
+      public Builder setHostnameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        identityOneofCase_ = 2;
+        identityOneof_ = value;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.gcp.Identity)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.gcp.Identity)
+    private static final io.grpc.alts.internal.Handshaker.Identity DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.alts.internal.Handshaker.Identity();
+    }
+
+    public static io.grpc.alts.internal.Handshaker.Identity getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<Identity>
+        PARSER = new com.google.protobuf.AbstractParser<Identity>() {
+      public Identity parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new Identity(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<Identity> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<Identity> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.alts.internal.Handshaker.Identity getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface StartClientHandshakeReqOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.gcp.StartClientHandshakeReq)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Handshake security protocol requested by the client.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakeProtocol handshake_security_protocol = 1;</code>
+     */
+    int getHandshakeSecurityProtocolValue();
+    /**
+     * <pre>
+     * Handshake security protocol requested by the client.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakeProtocol handshake_security_protocol = 1;</code>
+     */
+    io.grpc.alts.internal.Handshaker.HandshakeProtocol getHandshakeSecurityProtocol();
+
+    /**
+     * <pre>
+     * The application protocols supported by the client, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 2;</code>
+     */
+    java.util.List<java.lang.String>
+        getApplicationProtocolsList();
+    /**
+     * <pre>
+     * The application protocols supported by the client, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 2;</code>
+     */
+    int getApplicationProtocolsCount();
+    /**
+     * <pre>
+     * The application protocols supported by the client, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 2;</code>
+     */
+    java.lang.String getApplicationProtocols(int index);
+    /**
+     * <pre>
+     * The application protocols supported by the client, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 2;</code>
+     */
+    com.google.protobuf.ByteString
+        getApplicationProtocolsBytes(int index);
+
+    /**
+     * <pre>
+     * The record protocols supported by the client, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 3;</code>
+     */
+    java.util.List<java.lang.String>
+        getRecordProtocolsList();
+    /**
+     * <pre>
+     * The record protocols supported by the client, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 3;</code>
+     */
+    int getRecordProtocolsCount();
+    /**
+     * <pre>
+     * The record protocols supported by the client, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 3;</code>
+     */
+    java.lang.String getRecordProtocols(int index);
+    /**
+     * <pre>
+     * The record protocols supported by the client, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 3;</code>
+     */
+    com.google.protobuf.ByteString
+        getRecordProtocolsBytes(int index);
+
+    /**
+     * <pre>
+     * (Optional) Describes which server identities are acceptable by the client.
+     * If target identities are provided and none of them matches the peer
+     * identity of the server, handshake will fail.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+     */
+    java.util.List<io.grpc.alts.internal.Handshaker.Identity> 
+        getTargetIdentitiesList();
+    /**
+     * <pre>
+     * (Optional) Describes which server identities are acceptable by the client.
+     * If target identities are provided and none of them matches the peer
+     * identity of the server, handshake will fail.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+     */
+    io.grpc.alts.internal.Handshaker.Identity getTargetIdentities(int index);
+    /**
+     * <pre>
+     * (Optional) Describes which server identities are acceptable by the client.
+     * If target identities are provided and none of them matches the peer
+     * identity of the server, handshake will fail.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+     */
+    int getTargetIdentitiesCount();
+    /**
+     * <pre>
+     * (Optional) Describes which server identities are acceptable by the client.
+     * If target identities are provided and none of them matches the peer
+     * identity of the server, handshake will fail.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+     */
+    java.util.List<? extends io.grpc.alts.internal.Handshaker.IdentityOrBuilder> 
+        getTargetIdentitiesOrBuilderList();
+    /**
+     * <pre>
+     * (Optional) Describes which server identities are acceptable by the client.
+     * If target identities are provided and none of them matches the peer
+     * identity of the server, handshake will fail.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+     */
+    io.grpc.alts.internal.Handshaker.IdentityOrBuilder getTargetIdentitiesOrBuilder(
+        int index);
+
+    /**
+     * <pre>
+     * (Optional) Application may specify a local identity. Otherwise, the
+     * handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity local_identity = 5;</code>
+     */
+    boolean hasLocalIdentity();
+    /**
+     * <pre>
+     * (Optional) Application may specify a local identity. Otherwise, the
+     * handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity local_identity = 5;</code>
+     */
+    io.grpc.alts.internal.Handshaker.Identity getLocalIdentity();
+    /**
+     * <pre>
+     * (Optional) Application may specify a local identity. Otherwise, the
+     * handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity local_identity = 5;</code>
+     */
+    io.grpc.alts.internal.Handshaker.IdentityOrBuilder getLocalIdentityOrBuilder();
+
+    /**
+     * <pre>
+     * (Optional) Local endpoint information of the connection to the server,
+     * such as local IP address, port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+     */
+    boolean hasLocalEndpoint();
+    /**
+     * <pre>
+     * (Optional) Local endpoint information of the connection to the server,
+     * such as local IP address, port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+     */
+    io.grpc.alts.internal.Handshaker.Endpoint getLocalEndpoint();
+    /**
+     * <pre>
+     * (Optional) Local endpoint information of the connection to the server,
+     * such as local IP address, port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+     */
+    io.grpc.alts.internal.Handshaker.EndpointOrBuilder getLocalEndpointOrBuilder();
+
+    /**
+     * <pre>
+     * (Optional) Endpoint information of the remote server, such as IP address,
+     * port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+     */
+    boolean hasRemoteEndpoint();
+    /**
+     * <pre>
+     * (Optional) Endpoint information of the remote server, such as IP address,
+     * port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+     */
+    io.grpc.alts.internal.Handshaker.Endpoint getRemoteEndpoint();
+    /**
+     * <pre>
+     * (Optional) Endpoint information of the remote server, such as IP address,
+     * port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+     */
+    io.grpc.alts.internal.Handshaker.EndpointOrBuilder getRemoteEndpointOrBuilder();
+
+    /**
+     * <pre>
+     * (Optional) If target name is provided, a secure naming check is performed
+     * to verify that the peer authenticated identity is indeed authorized to run
+     * the target name.
+     * </pre>
+     *
+     * <code>string target_name = 8;</code>
+     */
+    java.lang.String getTargetName();
+    /**
+     * <pre>
+     * (Optional) If target name is provided, a secure naming check is performed
+     * to verify that the peer authenticated identity is indeed authorized to run
+     * the target name.
+     * </pre>
+     *
+     * <code>string target_name = 8;</code>
+     */
+    com.google.protobuf.ByteString
+        getTargetNameBytes();
+
+    /**
+     * <pre>
+     * (Optional) RPC protocol versions supported by the client.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+     */
+    boolean hasRpcVersions();
+    /**
+     * <pre>
+     * (Optional) RPC protocol versions supported by the client.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+     */
+    io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getRpcVersions();
+    /**
+     * <pre>
+     * (Optional) RPC protocol versions supported by the client.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+     */
+    io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder getRpcVersionsOrBuilder();
+  }
+  /**
+   * Protobuf type {@code grpc.gcp.StartClientHandshakeReq}
+   */
+  public  static final class StartClientHandshakeReq extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.gcp.StartClientHandshakeReq)
+      StartClientHandshakeReqOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use StartClientHandshakeReq.newBuilder() to construct.
+    private StartClientHandshakeReq(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private StartClientHandshakeReq() {
+      handshakeSecurityProtocol_ = 0;
+      applicationProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+      recordProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+      targetIdentities_ = java.util.Collections.emptyList();
+      targetName_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private StartClientHandshakeReq(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+              int rawValue = input.readEnum();
+
+              handshakeSecurityProtocol_ = rawValue;
+              break;
+            }
+            case 18: {
+              java.lang.String s = input.readStringRequireUtf8();
+              if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+                applicationProtocols_ = new com.google.protobuf.LazyStringArrayList();
+                mutable_bitField0_ |= 0x00000002;
+              }
+              applicationProtocols_.add(s);
+              break;
+            }
+            case 26: {
+              java.lang.String s = input.readStringRequireUtf8();
+              if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) {
+                recordProtocols_ = new com.google.protobuf.LazyStringArrayList();
+                mutable_bitField0_ |= 0x00000004;
+              }
+              recordProtocols_.add(s);
+              break;
+            }
+            case 34: {
+              if (!((mutable_bitField0_ & 0x00000008) == 0x00000008)) {
+                targetIdentities_ = new java.util.ArrayList<io.grpc.alts.internal.Handshaker.Identity>();
+                mutable_bitField0_ |= 0x00000008;
+              }
+              targetIdentities_.add(
+                  input.readMessage(io.grpc.alts.internal.Handshaker.Identity.parser(), extensionRegistry));
+              break;
+            }
+            case 42: {
+              io.grpc.alts.internal.Handshaker.Identity.Builder subBuilder = null;
+              if (localIdentity_ != null) {
+                subBuilder = localIdentity_.toBuilder();
+              }
+              localIdentity_ = input.readMessage(io.grpc.alts.internal.Handshaker.Identity.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(localIdentity_);
+                localIdentity_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 50: {
+              io.grpc.alts.internal.Handshaker.Endpoint.Builder subBuilder = null;
+              if (localEndpoint_ != null) {
+                subBuilder = localEndpoint_.toBuilder();
+              }
+              localEndpoint_ = input.readMessage(io.grpc.alts.internal.Handshaker.Endpoint.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(localEndpoint_);
+                localEndpoint_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 58: {
+              io.grpc.alts.internal.Handshaker.Endpoint.Builder subBuilder = null;
+              if (remoteEndpoint_ != null) {
+                subBuilder = remoteEndpoint_.toBuilder();
+              }
+              remoteEndpoint_ = input.readMessage(io.grpc.alts.internal.Handshaker.Endpoint.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(remoteEndpoint_);
+                remoteEndpoint_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 66: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              targetName_ = s;
+              break;
+            }
+            case 74: {
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder subBuilder = null;
+              if (rpcVersions_ != null) {
+                subBuilder = rpcVersions_.toBuilder();
+              }
+              rpcVersions_ = input.readMessage(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(rpcVersions_);
+                rpcVersions_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+          applicationProtocols_ = applicationProtocols_.getUnmodifiableView();
+        }
+        if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) {
+          recordProtocols_ = recordProtocols_.getUnmodifiableView();
+        }
+        if (((mutable_bitField0_ & 0x00000008) == 0x00000008)) {
+          targetIdentities_ = java.util.Collections.unmodifiableList(targetIdentities_);
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_StartClientHandshakeReq_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_StartClientHandshakeReq_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.class, io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.Builder.class);
+    }
+
+    private int bitField0_;
+    public static final int HANDSHAKE_SECURITY_PROTOCOL_FIELD_NUMBER = 1;
+    private int handshakeSecurityProtocol_;
+    /**
+     * <pre>
+     * Handshake security protocol requested by the client.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakeProtocol handshake_security_protocol = 1;</code>
+     */
+    public int getHandshakeSecurityProtocolValue() {
+      return handshakeSecurityProtocol_;
+    }
+    /**
+     * <pre>
+     * Handshake security protocol requested by the client.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakeProtocol handshake_security_protocol = 1;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.HandshakeProtocol getHandshakeSecurityProtocol() {
+      io.grpc.alts.internal.Handshaker.HandshakeProtocol result = io.grpc.alts.internal.Handshaker.HandshakeProtocol.valueOf(handshakeSecurityProtocol_);
+      return result == null ? io.grpc.alts.internal.Handshaker.HandshakeProtocol.UNRECOGNIZED : result;
+    }
+
+    public static final int APPLICATION_PROTOCOLS_FIELD_NUMBER = 2;
+    private com.google.protobuf.LazyStringList applicationProtocols_;
+    /**
+     * <pre>
+     * The application protocols supported by the client, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 2;</code>
+     */
+    public com.google.protobuf.ProtocolStringList
+        getApplicationProtocolsList() {
+      return applicationProtocols_;
+    }
+    /**
+     * <pre>
+     * The application protocols supported by the client, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 2;</code>
+     */
+    public int getApplicationProtocolsCount() {
+      return applicationProtocols_.size();
+    }
+    /**
+     * <pre>
+     * The application protocols supported by the client, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 2;</code>
+     */
+    public java.lang.String getApplicationProtocols(int index) {
+      return applicationProtocols_.get(index);
+    }
+    /**
+     * <pre>
+     * The application protocols supported by the client, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getApplicationProtocolsBytes(int index) {
+      return applicationProtocols_.getByteString(index);
+    }
+
+    public static final int RECORD_PROTOCOLS_FIELD_NUMBER = 3;
+    private com.google.protobuf.LazyStringList recordProtocols_;
+    /**
+     * <pre>
+     * The record protocols supported by the client, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 3;</code>
+     */
+    public com.google.protobuf.ProtocolStringList
+        getRecordProtocolsList() {
+      return recordProtocols_;
+    }
+    /**
+     * <pre>
+     * The record protocols supported by the client, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 3;</code>
+     */
+    public int getRecordProtocolsCount() {
+      return recordProtocols_.size();
+    }
+    /**
+     * <pre>
+     * The record protocols supported by the client, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 3;</code>
+     */
+    public java.lang.String getRecordProtocols(int index) {
+      return recordProtocols_.get(index);
+    }
+    /**
+     * <pre>
+     * The record protocols supported by the client, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 3;</code>
+     */
+    public com.google.protobuf.ByteString
+        getRecordProtocolsBytes(int index) {
+      return recordProtocols_.getByteString(index);
+    }
+
+    public static final int TARGET_IDENTITIES_FIELD_NUMBER = 4;
+    private java.util.List<io.grpc.alts.internal.Handshaker.Identity> targetIdentities_;
+    /**
+     * <pre>
+     * (Optional) Describes which server identities are acceptable by the client.
+     * If target identities are provided and none of them matches the peer
+     * identity of the server, handshake will fail.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+     */
+    public java.util.List<io.grpc.alts.internal.Handshaker.Identity> getTargetIdentitiesList() {
+      return targetIdentities_;
+    }
+    /**
+     * <pre>
+     * (Optional) Describes which server identities are acceptable by the client.
+     * If target identities are provided and none of them matches the peer
+     * identity of the server, handshake will fail.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+     */
+    public java.util.List<? extends io.grpc.alts.internal.Handshaker.IdentityOrBuilder> 
+        getTargetIdentitiesOrBuilderList() {
+      return targetIdentities_;
+    }
+    /**
+     * <pre>
+     * (Optional) Describes which server identities are acceptable by the client.
+     * If target identities are provided and none of them matches the peer
+     * identity of the server, handshake will fail.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+     */
+    public int getTargetIdentitiesCount() {
+      return targetIdentities_.size();
+    }
+    /**
+     * <pre>
+     * (Optional) Describes which server identities are acceptable by the client.
+     * If target identities are provided and none of them matches the peer
+     * identity of the server, handshake will fail.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.Identity getTargetIdentities(int index) {
+      return targetIdentities_.get(index);
+    }
+    /**
+     * <pre>
+     * (Optional) Describes which server identities are acceptable by the client.
+     * If target identities are provided and none of them matches the peer
+     * identity of the server, handshake will fail.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.IdentityOrBuilder getTargetIdentitiesOrBuilder(
+        int index) {
+      return targetIdentities_.get(index);
+    }
+
+    public static final int LOCAL_IDENTITY_FIELD_NUMBER = 5;
+    private io.grpc.alts.internal.Handshaker.Identity localIdentity_;
+    /**
+     * <pre>
+     * (Optional) Application may specify a local identity. Otherwise, the
+     * handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity local_identity = 5;</code>
+     */
+    public boolean hasLocalIdentity() {
+      return localIdentity_ != null;
+    }
+    /**
+     * <pre>
+     * (Optional) Application may specify a local identity. Otherwise, the
+     * handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity local_identity = 5;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.Identity getLocalIdentity() {
+      return localIdentity_ == null ? io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance() : localIdentity_;
+    }
+    /**
+     * <pre>
+     * (Optional) Application may specify a local identity. Otherwise, the
+     * handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity local_identity = 5;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.IdentityOrBuilder getLocalIdentityOrBuilder() {
+      return getLocalIdentity();
+    }
+
+    public static final int LOCAL_ENDPOINT_FIELD_NUMBER = 6;
+    private io.grpc.alts.internal.Handshaker.Endpoint localEndpoint_;
+    /**
+     * <pre>
+     * (Optional) Local endpoint information of the connection to the server,
+     * such as local IP address, port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+     */
+    public boolean hasLocalEndpoint() {
+      return localEndpoint_ != null;
+    }
+    /**
+     * <pre>
+     * (Optional) Local endpoint information of the connection to the server,
+     * such as local IP address, port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.Endpoint getLocalEndpoint() {
+      return localEndpoint_ == null ? io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance() : localEndpoint_;
+    }
+    /**
+     * <pre>
+     * (Optional) Local endpoint information of the connection to the server,
+     * such as local IP address, port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.EndpointOrBuilder getLocalEndpointOrBuilder() {
+      return getLocalEndpoint();
+    }
+
+    public static final int REMOTE_ENDPOINT_FIELD_NUMBER = 7;
+    private io.grpc.alts.internal.Handshaker.Endpoint remoteEndpoint_;
+    /**
+     * <pre>
+     * (Optional) Endpoint information of the remote server, such as IP address,
+     * port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+     */
+    public boolean hasRemoteEndpoint() {
+      return remoteEndpoint_ != null;
+    }
+    /**
+     * <pre>
+     * (Optional) Endpoint information of the remote server, such as IP address,
+     * port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.Endpoint getRemoteEndpoint() {
+      return remoteEndpoint_ == null ? io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance() : remoteEndpoint_;
+    }
+    /**
+     * <pre>
+     * (Optional) Endpoint information of the remote server, such as IP address,
+     * port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.EndpointOrBuilder getRemoteEndpointOrBuilder() {
+      return getRemoteEndpoint();
+    }
+
+    public static final int TARGET_NAME_FIELD_NUMBER = 8;
+    private volatile java.lang.Object targetName_;
+    /**
+     * <pre>
+     * (Optional) If target name is provided, a secure naming check is performed
+     * to verify that the peer authenticated identity is indeed authorized to run
+     * the target name.
+     * </pre>
+     *
+     * <code>string target_name = 8;</code>
+     */
+    public java.lang.String getTargetName() {
+      java.lang.Object ref = targetName_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        targetName_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * (Optional) If target name is provided, a secure naming check is performed
+     * to verify that the peer authenticated identity is indeed authorized to run
+     * the target name.
+     * </pre>
+     *
+     * <code>string target_name = 8;</code>
+     */
+    public com.google.protobuf.ByteString
+        getTargetNameBytes() {
+      java.lang.Object ref = targetName_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        targetName_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int RPC_VERSIONS_FIELD_NUMBER = 9;
+    private io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions rpcVersions_;
+    /**
+     * <pre>
+     * (Optional) RPC protocol versions supported by the client.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+     */
+    public boolean hasRpcVersions() {
+      return rpcVersions_ != null;
+    }
+    /**
+     * <pre>
+     * (Optional) RPC protocol versions supported by the client.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+     */
+    public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getRpcVersions() {
+      return rpcVersions_ == null ? io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance() : rpcVersions_;
+    }
+    /**
+     * <pre>
+     * (Optional) RPC protocol versions supported by the client.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+     */
+    public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder getRpcVersionsOrBuilder() {
+      return getRpcVersions();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (handshakeSecurityProtocol_ != io.grpc.alts.internal.Handshaker.HandshakeProtocol.HANDSHAKE_PROTOCOL_UNSPECIFIED.getNumber()) {
+        output.writeEnum(1, handshakeSecurityProtocol_);
+      }
+      for (int i = 0; i < applicationProtocols_.size(); i++) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, applicationProtocols_.getRaw(i));
+      }
+      for (int i = 0; i < recordProtocols_.size(); i++) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 3, recordProtocols_.getRaw(i));
+      }
+      for (int i = 0; i < targetIdentities_.size(); i++) {
+        output.writeMessage(4, targetIdentities_.get(i));
+      }
+      if (localIdentity_ != null) {
+        output.writeMessage(5, getLocalIdentity());
+      }
+      if (localEndpoint_ != null) {
+        output.writeMessage(6, getLocalEndpoint());
+      }
+      if (remoteEndpoint_ != null) {
+        output.writeMessage(7, getRemoteEndpoint());
+      }
+      if (!getTargetNameBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 8, targetName_);
+      }
+      if (rpcVersions_ != null) {
+        output.writeMessage(9, getRpcVersions());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (handshakeSecurityProtocol_ != io.grpc.alts.internal.Handshaker.HandshakeProtocol.HANDSHAKE_PROTOCOL_UNSPECIFIED.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(1, handshakeSecurityProtocol_);
+      }
+      {
+        int dataSize = 0;
+        for (int i = 0; i < applicationProtocols_.size(); i++) {
+          dataSize += computeStringSizeNoTag(applicationProtocols_.getRaw(i));
+        }
+        size += dataSize;
+        size += 1 * getApplicationProtocolsList().size();
+      }
+      {
+        int dataSize = 0;
+        for (int i = 0; i < recordProtocols_.size(); i++) {
+          dataSize += computeStringSizeNoTag(recordProtocols_.getRaw(i));
+        }
+        size += dataSize;
+        size += 1 * getRecordProtocolsList().size();
+      }
+      for (int i = 0; i < targetIdentities_.size(); i++) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(4, targetIdentities_.get(i));
+      }
+      if (localIdentity_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(5, getLocalIdentity());
+      }
+      if (localEndpoint_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(6, getLocalEndpoint());
+      }
+      if (remoteEndpoint_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(7, getRemoteEndpoint());
+      }
+      if (!getTargetNameBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(8, targetName_);
+      }
+      if (rpcVersions_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(9, getRpcVersions());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.alts.internal.Handshaker.StartClientHandshakeReq)) {
+        return super.equals(obj);
+      }
+      io.grpc.alts.internal.Handshaker.StartClientHandshakeReq other = (io.grpc.alts.internal.Handshaker.StartClientHandshakeReq) obj;
+
+      boolean result = true;
+      result = result && handshakeSecurityProtocol_ == other.handshakeSecurityProtocol_;
+      result = result && getApplicationProtocolsList()
+          .equals(other.getApplicationProtocolsList());
+      result = result && getRecordProtocolsList()
+          .equals(other.getRecordProtocolsList());
+      result = result && getTargetIdentitiesList()
+          .equals(other.getTargetIdentitiesList());
+      result = result && (hasLocalIdentity() == other.hasLocalIdentity());
+      if (hasLocalIdentity()) {
+        result = result && getLocalIdentity()
+            .equals(other.getLocalIdentity());
+      }
+      result = result && (hasLocalEndpoint() == other.hasLocalEndpoint());
+      if (hasLocalEndpoint()) {
+        result = result && getLocalEndpoint()
+            .equals(other.getLocalEndpoint());
+      }
+      result = result && (hasRemoteEndpoint() == other.hasRemoteEndpoint());
+      if (hasRemoteEndpoint()) {
+        result = result && getRemoteEndpoint()
+            .equals(other.getRemoteEndpoint());
+      }
+      result = result && getTargetName()
+          .equals(other.getTargetName());
+      result = result && (hasRpcVersions() == other.hasRpcVersions());
+      if (hasRpcVersions()) {
+        result = result && getRpcVersions()
+            .equals(other.getRpcVersions());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + HANDSHAKE_SECURITY_PROTOCOL_FIELD_NUMBER;
+      hash = (53 * hash) + handshakeSecurityProtocol_;
+      if (getApplicationProtocolsCount() > 0) {
+        hash = (37 * hash) + APPLICATION_PROTOCOLS_FIELD_NUMBER;
+        hash = (53 * hash) + getApplicationProtocolsList().hashCode();
+      }
+      if (getRecordProtocolsCount() > 0) {
+        hash = (37 * hash) + RECORD_PROTOCOLS_FIELD_NUMBER;
+        hash = (53 * hash) + getRecordProtocolsList().hashCode();
+      }
+      if (getTargetIdentitiesCount() > 0) {
+        hash = (37 * hash) + TARGET_IDENTITIES_FIELD_NUMBER;
+        hash = (53 * hash) + getTargetIdentitiesList().hashCode();
+      }
+      if (hasLocalIdentity()) {
+        hash = (37 * hash) + LOCAL_IDENTITY_FIELD_NUMBER;
+        hash = (53 * hash) + getLocalIdentity().hashCode();
+      }
+      if (hasLocalEndpoint()) {
+        hash = (37 * hash) + LOCAL_ENDPOINT_FIELD_NUMBER;
+        hash = (53 * hash) + getLocalEndpoint().hashCode();
+      }
+      if (hasRemoteEndpoint()) {
+        hash = (37 * hash) + REMOTE_ENDPOINT_FIELD_NUMBER;
+        hash = (53 * hash) + getRemoteEndpoint().hashCode();
+      }
+      hash = (37 * hash) + TARGET_NAME_FIELD_NUMBER;
+      hash = (53 * hash) + getTargetName().hashCode();
+      if (hasRpcVersions()) {
+        hash = (37 * hash) + RPC_VERSIONS_FIELD_NUMBER;
+        hash = (53 * hash) + getRpcVersions().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.alts.internal.Handshaker.StartClientHandshakeReq parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartClientHandshakeReq parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartClientHandshakeReq parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartClientHandshakeReq parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartClientHandshakeReq parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartClientHandshakeReq parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartClientHandshakeReq parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartClientHandshakeReq parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartClientHandshakeReq parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartClientHandshakeReq parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartClientHandshakeReq parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartClientHandshakeReq parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.alts.internal.Handshaker.StartClientHandshakeReq prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.gcp.StartClientHandshakeReq}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.gcp.StartClientHandshakeReq)
+        io.grpc.alts.internal.Handshaker.StartClientHandshakeReqOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_StartClientHandshakeReq_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_StartClientHandshakeReq_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.class, io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.Builder.class);
+      }
+
+      // Construct using io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+          getTargetIdentitiesFieldBuilder();
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        handshakeSecurityProtocol_ = 0;
+
+        applicationProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+        bitField0_ = (bitField0_ & ~0x00000002);
+        recordProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+        bitField0_ = (bitField0_ & ~0x00000004);
+        if (targetIdentitiesBuilder_ == null) {
+          targetIdentities_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000008);
+        } else {
+          targetIdentitiesBuilder_.clear();
+        }
+        if (localIdentityBuilder_ == null) {
+          localIdentity_ = null;
+        } else {
+          localIdentity_ = null;
+          localIdentityBuilder_ = null;
+        }
+        if (localEndpointBuilder_ == null) {
+          localEndpoint_ = null;
+        } else {
+          localEndpoint_ = null;
+          localEndpointBuilder_ = null;
+        }
+        if (remoteEndpointBuilder_ == null) {
+          remoteEndpoint_ = null;
+        } else {
+          remoteEndpoint_ = null;
+          remoteEndpointBuilder_ = null;
+        }
+        targetName_ = "";
+
+        if (rpcVersionsBuilder_ == null) {
+          rpcVersions_ = null;
+        } else {
+          rpcVersions_ = null;
+          rpcVersionsBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_StartClientHandshakeReq_descriptor;
+      }
+
+      public io.grpc.alts.internal.Handshaker.StartClientHandshakeReq getDefaultInstanceForType() {
+        return io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.getDefaultInstance();
+      }
+
+      public io.grpc.alts.internal.Handshaker.StartClientHandshakeReq build() {
+        io.grpc.alts.internal.Handshaker.StartClientHandshakeReq result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.alts.internal.Handshaker.StartClientHandshakeReq buildPartial() {
+        io.grpc.alts.internal.Handshaker.StartClientHandshakeReq result = new io.grpc.alts.internal.Handshaker.StartClientHandshakeReq(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        result.handshakeSecurityProtocol_ = handshakeSecurityProtocol_;
+        if (((bitField0_ & 0x00000002) == 0x00000002)) {
+          applicationProtocols_ = applicationProtocols_.getUnmodifiableView();
+          bitField0_ = (bitField0_ & ~0x00000002);
+        }
+        result.applicationProtocols_ = applicationProtocols_;
+        if (((bitField0_ & 0x00000004) == 0x00000004)) {
+          recordProtocols_ = recordProtocols_.getUnmodifiableView();
+          bitField0_ = (bitField0_ & ~0x00000004);
+        }
+        result.recordProtocols_ = recordProtocols_;
+        if (targetIdentitiesBuilder_ == null) {
+          if (((bitField0_ & 0x00000008) == 0x00000008)) {
+            targetIdentities_ = java.util.Collections.unmodifiableList(targetIdentities_);
+            bitField0_ = (bitField0_ & ~0x00000008);
+          }
+          result.targetIdentities_ = targetIdentities_;
+        } else {
+          result.targetIdentities_ = targetIdentitiesBuilder_.build();
+        }
+        if (localIdentityBuilder_ == null) {
+          result.localIdentity_ = localIdentity_;
+        } else {
+          result.localIdentity_ = localIdentityBuilder_.build();
+        }
+        if (localEndpointBuilder_ == null) {
+          result.localEndpoint_ = localEndpoint_;
+        } else {
+          result.localEndpoint_ = localEndpointBuilder_.build();
+        }
+        if (remoteEndpointBuilder_ == null) {
+          result.remoteEndpoint_ = remoteEndpoint_;
+        } else {
+          result.remoteEndpoint_ = remoteEndpointBuilder_.build();
+        }
+        result.targetName_ = targetName_;
+        if (rpcVersionsBuilder_ == null) {
+          result.rpcVersions_ = rpcVersions_;
+        } else {
+          result.rpcVersions_ = rpcVersionsBuilder_.build();
+        }
+        result.bitField0_ = to_bitField0_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.alts.internal.Handshaker.StartClientHandshakeReq) {
+          return mergeFrom((io.grpc.alts.internal.Handshaker.StartClientHandshakeReq)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.alts.internal.Handshaker.StartClientHandshakeReq other) {
+        if (other == io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.getDefaultInstance()) return this;
+        if (other.handshakeSecurityProtocol_ != 0) {
+          setHandshakeSecurityProtocolValue(other.getHandshakeSecurityProtocolValue());
+        }
+        if (!other.applicationProtocols_.isEmpty()) {
+          if (applicationProtocols_.isEmpty()) {
+            applicationProtocols_ = other.applicationProtocols_;
+            bitField0_ = (bitField0_ & ~0x00000002);
+          } else {
+            ensureApplicationProtocolsIsMutable();
+            applicationProtocols_.addAll(other.applicationProtocols_);
+          }
+          onChanged();
+        }
+        if (!other.recordProtocols_.isEmpty()) {
+          if (recordProtocols_.isEmpty()) {
+            recordProtocols_ = other.recordProtocols_;
+            bitField0_ = (bitField0_ & ~0x00000004);
+          } else {
+            ensureRecordProtocolsIsMutable();
+            recordProtocols_.addAll(other.recordProtocols_);
+          }
+          onChanged();
+        }
+        if (targetIdentitiesBuilder_ == null) {
+          if (!other.targetIdentities_.isEmpty()) {
+            if (targetIdentities_.isEmpty()) {
+              targetIdentities_ = other.targetIdentities_;
+              bitField0_ = (bitField0_ & ~0x00000008);
+            } else {
+              ensureTargetIdentitiesIsMutable();
+              targetIdentities_.addAll(other.targetIdentities_);
+            }
+            onChanged();
+          }
+        } else {
+          if (!other.targetIdentities_.isEmpty()) {
+            if (targetIdentitiesBuilder_.isEmpty()) {
+              targetIdentitiesBuilder_.dispose();
+              targetIdentitiesBuilder_ = null;
+              targetIdentities_ = other.targetIdentities_;
+              bitField0_ = (bitField0_ & ~0x00000008);
+              targetIdentitiesBuilder_ = 
+                com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                   getTargetIdentitiesFieldBuilder() : null;
+            } else {
+              targetIdentitiesBuilder_.addAllMessages(other.targetIdentities_);
+            }
+          }
+        }
+        if (other.hasLocalIdentity()) {
+          mergeLocalIdentity(other.getLocalIdentity());
+        }
+        if (other.hasLocalEndpoint()) {
+          mergeLocalEndpoint(other.getLocalEndpoint());
+        }
+        if (other.hasRemoteEndpoint()) {
+          mergeRemoteEndpoint(other.getRemoteEndpoint());
+        }
+        if (!other.getTargetName().isEmpty()) {
+          targetName_ = other.targetName_;
+          onChanged();
+        }
+        if (other.hasRpcVersions()) {
+          mergeRpcVersions(other.getRpcVersions());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.alts.internal.Handshaker.StartClientHandshakeReq parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.alts.internal.Handshaker.StartClientHandshakeReq) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private int handshakeSecurityProtocol_ = 0;
+      /**
+       * <pre>
+       * Handshake security protocol requested by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakeProtocol handshake_security_protocol = 1;</code>
+       */
+      public int getHandshakeSecurityProtocolValue() {
+        return handshakeSecurityProtocol_;
+      }
+      /**
+       * <pre>
+       * Handshake security protocol requested by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakeProtocol handshake_security_protocol = 1;</code>
+       */
+      public Builder setHandshakeSecurityProtocolValue(int value) {
+        handshakeSecurityProtocol_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Handshake security protocol requested by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakeProtocol handshake_security_protocol = 1;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.HandshakeProtocol getHandshakeSecurityProtocol() {
+        io.grpc.alts.internal.Handshaker.HandshakeProtocol result = io.grpc.alts.internal.Handshaker.HandshakeProtocol.valueOf(handshakeSecurityProtocol_);
+        return result == null ? io.grpc.alts.internal.Handshaker.HandshakeProtocol.UNRECOGNIZED : result;
+      }
+      /**
+       * <pre>
+       * Handshake security protocol requested by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakeProtocol handshake_security_protocol = 1;</code>
+       */
+      public Builder setHandshakeSecurityProtocol(io.grpc.alts.internal.Handshaker.HandshakeProtocol value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        handshakeSecurityProtocol_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Handshake security protocol requested by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakeProtocol handshake_security_protocol = 1;</code>
+       */
+      public Builder clearHandshakeSecurityProtocol() {
+        
+        handshakeSecurityProtocol_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private com.google.protobuf.LazyStringList applicationProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+      private void ensureApplicationProtocolsIsMutable() {
+        if (!((bitField0_ & 0x00000002) == 0x00000002)) {
+          applicationProtocols_ = new com.google.protobuf.LazyStringArrayList(applicationProtocols_);
+          bitField0_ |= 0x00000002;
+         }
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the client, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 2;</code>
+       */
+      public com.google.protobuf.ProtocolStringList
+          getApplicationProtocolsList() {
+        return applicationProtocols_.getUnmodifiableView();
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the client, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 2;</code>
+       */
+      public int getApplicationProtocolsCount() {
+        return applicationProtocols_.size();
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the client, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 2;</code>
+       */
+      public java.lang.String getApplicationProtocols(int index) {
+        return applicationProtocols_.get(index);
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the client, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 2;</code>
+       */
+      public com.google.protobuf.ByteString
+          getApplicationProtocolsBytes(int index) {
+        return applicationProtocols_.getByteString(index);
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the client, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 2;</code>
+       */
+      public Builder setApplicationProtocols(
+          int index, java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureApplicationProtocolsIsMutable();
+        applicationProtocols_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the client, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 2;</code>
+       */
+      public Builder addApplicationProtocols(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureApplicationProtocolsIsMutable();
+        applicationProtocols_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the client, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 2;</code>
+       */
+      public Builder addAllApplicationProtocols(
+          java.lang.Iterable<java.lang.String> values) {
+        ensureApplicationProtocolsIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, applicationProtocols_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the client, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 2;</code>
+       */
+      public Builder clearApplicationProtocols() {
+        applicationProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+        bitField0_ = (bitField0_ & ~0x00000002);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the client, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 2;</code>
+       */
+      public Builder addApplicationProtocolsBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        ensureApplicationProtocolsIsMutable();
+        applicationProtocols_.add(value);
+        onChanged();
+        return this;
+      }
+
+      private com.google.protobuf.LazyStringList recordProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+      private void ensureRecordProtocolsIsMutable() {
+        if (!((bitField0_ & 0x00000004) == 0x00000004)) {
+          recordProtocols_ = new com.google.protobuf.LazyStringArrayList(recordProtocols_);
+          bitField0_ |= 0x00000004;
+         }
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the client, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 3;</code>
+       */
+      public com.google.protobuf.ProtocolStringList
+          getRecordProtocolsList() {
+        return recordProtocols_.getUnmodifiableView();
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the client, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 3;</code>
+       */
+      public int getRecordProtocolsCount() {
+        return recordProtocols_.size();
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the client, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 3;</code>
+       */
+      public java.lang.String getRecordProtocols(int index) {
+        return recordProtocols_.get(index);
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the client, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 3;</code>
+       */
+      public com.google.protobuf.ByteString
+          getRecordProtocolsBytes(int index) {
+        return recordProtocols_.getByteString(index);
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the client, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 3;</code>
+       */
+      public Builder setRecordProtocols(
+          int index, java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureRecordProtocolsIsMutable();
+        recordProtocols_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the client, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 3;</code>
+       */
+      public Builder addRecordProtocols(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureRecordProtocolsIsMutable();
+        recordProtocols_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the client, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 3;</code>
+       */
+      public Builder addAllRecordProtocols(
+          java.lang.Iterable<java.lang.String> values) {
+        ensureRecordProtocolsIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, recordProtocols_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the client, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 3;</code>
+       */
+      public Builder clearRecordProtocols() {
+        recordProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+        bitField0_ = (bitField0_ & ~0x00000004);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the client, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 3;</code>
+       */
+      public Builder addRecordProtocolsBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        ensureRecordProtocolsIsMutable();
+        recordProtocols_.add(value);
+        onChanged();
+        return this;
+      }
+
+      private java.util.List<io.grpc.alts.internal.Handshaker.Identity> targetIdentities_ =
+        java.util.Collections.emptyList();
+      private void ensureTargetIdentitiesIsMutable() {
+        if (!((bitField0_ & 0x00000008) == 0x00000008)) {
+          targetIdentities_ = new java.util.ArrayList<io.grpc.alts.internal.Handshaker.Identity>(targetIdentities_);
+          bitField0_ |= 0x00000008;
+         }
+      }
+
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder> targetIdentitiesBuilder_;
+
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public java.util.List<io.grpc.alts.internal.Handshaker.Identity> getTargetIdentitiesList() {
+        if (targetIdentitiesBuilder_ == null) {
+          return java.util.Collections.unmodifiableList(targetIdentities_);
+        } else {
+          return targetIdentitiesBuilder_.getMessageList();
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public int getTargetIdentitiesCount() {
+        if (targetIdentitiesBuilder_ == null) {
+          return targetIdentities_.size();
+        } else {
+          return targetIdentitiesBuilder_.getCount();
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity getTargetIdentities(int index) {
+        if (targetIdentitiesBuilder_ == null) {
+          return targetIdentities_.get(index);
+        } else {
+          return targetIdentitiesBuilder_.getMessage(index);
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public Builder setTargetIdentities(
+          int index, io.grpc.alts.internal.Handshaker.Identity value) {
+        if (targetIdentitiesBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureTargetIdentitiesIsMutable();
+          targetIdentities_.set(index, value);
+          onChanged();
+        } else {
+          targetIdentitiesBuilder_.setMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public Builder setTargetIdentities(
+          int index, io.grpc.alts.internal.Handshaker.Identity.Builder builderForValue) {
+        if (targetIdentitiesBuilder_ == null) {
+          ensureTargetIdentitiesIsMutable();
+          targetIdentities_.set(index, builderForValue.build());
+          onChanged();
+        } else {
+          targetIdentitiesBuilder_.setMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public Builder addTargetIdentities(io.grpc.alts.internal.Handshaker.Identity value) {
+        if (targetIdentitiesBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureTargetIdentitiesIsMutable();
+          targetIdentities_.add(value);
+          onChanged();
+        } else {
+          targetIdentitiesBuilder_.addMessage(value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public Builder addTargetIdentities(
+          int index, io.grpc.alts.internal.Handshaker.Identity value) {
+        if (targetIdentitiesBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureTargetIdentitiesIsMutable();
+          targetIdentities_.add(index, value);
+          onChanged();
+        } else {
+          targetIdentitiesBuilder_.addMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public Builder addTargetIdentities(
+          io.grpc.alts.internal.Handshaker.Identity.Builder builderForValue) {
+        if (targetIdentitiesBuilder_ == null) {
+          ensureTargetIdentitiesIsMutable();
+          targetIdentities_.add(builderForValue.build());
+          onChanged();
+        } else {
+          targetIdentitiesBuilder_.addMessage(builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public Builder addTargetIdentities(
+          int index, io.grpc.alts.internal.Handshaker.Identity.Builder builderForValue) {
+        if (targetIdentitiesBuilder_ == null) {
+          ensureTargetIdentitiesIsMutable();
+          targetIdentities_.add(index, builderForValue.build());
+          onChanged();
+        } else {
+          targetIdentitiesBuilder_.addMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public Builder addAllTargetIdentities(
+          java.lang.Iterable<? extends io.grpc.alts.internal.Handshaker.Identity> values) {
+        if (targetIdentitiesBuilder_ == null) {
+          ensureTargetIdentitiesIsMutable();
+          com.google.protobuf.AbstractMessageLite.Builder.addAll(
+              values, targetIdentities_);
+          onChanged();
+        } else {
+          targetIdentitiesBuilder_.addAllMessages(values);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public Builder clearTargetIdentities() {
+        if (targetIdentitiesBuilder_ == null) {
+          targetIdentities_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000008);
+          onChanged();
+        } else {
+          targetIdentitiesBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public Builder removeTargetIdentities(int index) {
+        if (targetIdentitiesBuilder_ == null) {
+          ensureTargetIdentitiesIsMutable();
+          targetIdentities_.remove(index);
+          onChanged();
+        } else {
+          targetIdentitiesBuilder_.remove(index);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity.Builder getTargetIdentitiesBuilder(
+          int index) {
+        return getTargetIdentitiesFieldBuilder().getBuilder(index);
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.IdentityOrBuilder getTargetIdentitiesOrBuilder(
+          int index) {
+        if (targetIdentitiesBuilder_ == null) {
+          return targetIdentities_.get(index);  } else {
+          return targetIdentitiesBuilder_.getMessageOrBuilder(index);
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public java.util.List<? extends io.grpc.alts.internal.Handshaker.IdentityOrBuilder> 
+           getTargetIdentitiesOrBuilderList() {
+        if (targetIdentitiesBuilder_ != null) {
+          return targetIdentitiesBuilder_.getMessageOrBuilderList();
+        } else {
+          return java.util.Collections.unmodifiableList(targetIdentities_);
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity.Builder addTargetIdentitiesBuilder() {
+        return getTargetIdentitiesFieldBuilder().addBuilder(
+            io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity.Builder addTargetIdentitiesBuilder(
+          int index) {
+        return getTargetIdentitiesFieldBuilder().addBuilder(
+            index, io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * (Optional) Describes which server identities are acceptable by the client.
+       * If target identities are provided and none of them matches the peer
+       * identity of the server, handshake will fail.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity target_identities = 4;</code>
+       */
+      public java.util.List<io.grpc.alts.internal.Handshaker.Identity.Builder> 
+           getTargetIdentitiesBuilderList() {
+        return getTargetIdentitiesFieldBuilder().getBuilderList();
+      }
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder> 
+          getTargetIdentitiesFieldBuilder() {
+        if (targetIdentitiesBuilder_ == null) {
+          targetIdentitiesBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder>(
+                  targetIdentities_,
+                  ((bitField0_ & 0x00000008) == 0x00000008),
+                  getParentForChildren(),
+                  isClean());
+          targetIdentities_ = null;
+        }
+        return targetIdentitiesBuilder_;
+      }
+
+      private io.grpc.alts.internal.Handshaker.Identity localIdentity_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder> localIdentityBuilder_;
+      /**
+       * <pre>
+       * (Optional) Application may specify a local identity. Otherwise, the
+       * handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public boolean hasLocalIdentity() {
+        return localIdentityBuilder_ != null || localIdentity_ != null;
+      }
+      /**
+       * <pre>
+       * (Optional) Application may specify a local identity. Otherwise, the
+       * handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity getLocalIdentity() {
+        if (localIdentityBuilder_ == null) {
+          return localIdentity_ == null ? io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance() : localIdentity_;
+        } else {
+          return localIdentityBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Application may specify a local identity. Otherwise, the
+       * handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public Builder setLocalIdentity(io.grpc.alts.internal.Handshaker.Identity value) {
+        if (localIdentityBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          localIdentity_ = value;
+          onChanged();
+        } else {
+          localIdentityBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Application may specify a local identity. Otherwise, the
+       * handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public Builder setLocalIdentity(
+          io.grpc.alts.internal.Handshaker.Identity.Builder builderForValue) {
+        if (localIdentityBuilder_ == null) {
+          localIdentity_ = builderForValue.build();
+          onChanged();
+        } else {
+          localIdentityBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Application may specify a local identity. Otherwise, the
+       * handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public Builder mergeLocalIdentity(io.grpc.alts.internal.Handshaker.Identity value) {
+        if (localIdentityBuilder_ == null) {
+          if (localIdentity_ != null) {
+            localIdentity_ =
+              io.grpc.alts.internal.Handshaker.Identity.newBuilder(localIdentity_).mergeFrom(value).buildPartial();
+          } else {
+            localIdentity_ = value;
+          }
+          onChanged();
+        } else {
+          localIdentityBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Application may specify a local identity. Otherwise, the
+       * handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public Builder clearLocalIdentity() {
+        if (localIdentityBuilder_ == null) {
+          localIdentity_ = null;
+          onChanged();
+        } else {
+          localIdentity_ = null;
+          localIdentityBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Application may specify a local identity. Otherwise, the
+       * handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity.Builder getLocalIdentityBuilder() {
+        
+        onChanged();
+        return getLocalIdentityFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * (Optional) Application may specify a local identity. Otherwise, the
+       * handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.IdentityOrBuilder getLocalIdentityOrBuilder() {
+        if (localIdentityBuilder_ != null) {
+          return localIdentityBuilder_.getMessageOrBuilder();
+        } else {
+          return localIdentity_ == null ?
+              io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance() : localIdentity_;
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Application may specify a local identity. Otherwise, the
+       * handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder> 
+          getLocalIdentityFieldBuilder() {
+        if (localIdentityBuilder_ == null) {
+          localIdentityBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder>(
+                  getLocalIdentity(),
+                  getParentForChildren(),
+                  isClean());
+          localIdentity_ = null;
+        }
+        return localIdentityBuilder_;
+      }
+
+      private io.grpc.alts.internal.Handshaker.Endpoint localEndpoint_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Endpoint, io.grpc.alts.internal.Handshaker.Endpoint.Builder, io.grpc.alts.internal.Handshaker.EndpointOrBuilder> localEndpointBuilder_;
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the server,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+       */
+      public boolean hasLocalEndpoint() {
+        return localEndpointBuilder_ != null || localEndpoint_ != null;
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the server,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Endpoint getLocalEndpoint() {
+        if (localEndpointBuilder_ == null) {
+          return localEndpoint_ == null ? io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance() : localEndpoint_;
+        } else {
+          return localEndpointBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the server,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+       */
+      public Builder setLocalEndpoint(io.grpc.alts.internal.Handshaker.Endpoint value) {
+        if (localEndpointBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          localEndpoint_ = value;
+          onChanged();
+        } else {
+          localEndpointBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the server,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+       */
+      public Builder setLocalEndpoint(
+          io.grpc.alts.internal.Handshaker.Endpoint.Builder builderForValue) {
+        if (localEndpointBuilder_ == null) {
+          localEndpoint_ = builderForValue.build();
+          onChanged();
+        } else {
+          localEndpointBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the server,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+       */
+      public Builder mergeLocalEndpoint(io.grpc.alts.internal.Handshaker.Endpoint value) {
+        if (localEndpointBuilder_ == null) {
+          if (localEndpoint_ != null) {
+            localEndpoint_ =
+              io.grpc.alts.internal.Handshaker.Endpoint.newBuilder(localEndpoint_).mergeFrom(value).buildPartial();
+          } else {
+            localEndpoint_ = value;
+          }
+          onChanged();
+        } else {
+          localEndpointBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the server,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+       */
+      public Builder clearLocalEndpoint() {
+        if (localEndpointBuilder_ == null) {
+          localEndpoint_ = null;
+          onChanged();
+        } else {
+          localEndpoint_ = null;
+          localEndpointBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the server,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Endpoint.Builder getLocalEndpointBuilder() {
+        
+        onChanged();
+        return getLocalEndpointFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the server,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.EndpointOrBuilder getLocalEndpointOrBuilder() {
+        if (localEndpointBuilder_ != null) {
+          return localEndpointBuilder_.getMessageOrBuilder();
+        } else {
+          return localEndpoint_ == null ?
+              io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance() : localEndpoint_;
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the server,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 6;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Endpoint, io.grpc.alts.internal.Handshaker.Endpoint.Builder, io.grpc.alts.internal.Handshaker.EndpointOrBuilder> 
+          getLocalEndpointFieldBuilder() {
+        if (localEndpointBuilder_ == null) {
+          localEndpointBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.Endpoint, io.grpc.alts.internal.Handshaker.Endpoint.Builder, io.grpc.alts.internal.Handshaker.EndpointOrBuilder>(
+                  getLocalEndpoint(),
+                  getParentForChildren(),
+                  isClean());
+          localEndpoint_ = null;
+        }
+        return localEndpointBuilder_;
+      }
+
+      private io.grpc.alts.internal.Handshaker.Endpoint remoteEndpoint_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Endpoint, io.grpc.alts.internal.Handshaker.Endpoint.Builder, io.grpc.alts.internal.Handshaker.EndpointOrBuilder> remoteEndpointBuilder_;
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote server, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+       */
+      public boolean hasRemoteEndpoint() {
+        return remoteEndpointBuilder_ != null || remoteEndpoint_ != null;
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote server, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Endpoint getRemoteEndpoint() {
+        if (remoteEndpointBuilder_ == null) {
+          return remoteEndpoint_ == null ? io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance() : remoteEndpoint_;
+        } else {
+          return remoteEndpointBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote server, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+       */
+      public Builder setRemoteEndpoint(io.grpc.alts.internal.Handshaker.Endpoint value) {
+        if (remoteEndpointBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          remoteEndpoint_ = value;
+          onChanged();
+        } else {
+          remoteEndpointBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote server, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+       */
+      public Builder setRemoteEndpoint(
+          io.grpc.alts.internal.Handshaker.Endpoint.Builder builderForValue) {
+        if (remoteEndpointBuilder_ == null) {
+          remoteEndpoint_ = builderForValue.build();
+          onChanged();
+        } else {
+          remoteEndpointBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote server, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+       */
+      public Builder mergeRemoteEndpoint(io.grpc.alts.internal.Handshaker.Endpoint value) {
+        if (remoteEndpointBuilder_ == null) {
+          if (remoteEndpoint_ != null) {
+            remoteEndpoint_ =
+              io.grpc.alts.internal.Handshaker.Endpoint.newBuilder(remoteEndpoint_).mergeFrom(value).buildPartial();
+          } else {
+            remoteEndpoint_ = value;
+          }
+          onChanged();
+        } else {
+          remoteEndpointBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote server, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+       */
+      public Builder clearRemoteEndpoint() {
+        if (remoteEndpointBuilder_ == null) {
+          remoteEndpoint_ = null;
+          onChanged();
+        } else {
+          remoteEndpoint_ = null;
+          remoteEndpointBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote server, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Endpoint.Builder getRemoteEndpointBuilder() {
+        
+        onChanged();
+        return getRemoteEndpointFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote server, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.EndpointOrBuilder getRemoteEndpointOrBuilder() {
+        if (remoteEndpointBuilder_ != null) {
+          return remoteEndpointBuilder_.getMessageOrBuilder();
+        } else {
+          return remoteEndpoint_ == null ?
+              io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance() : remoteEndpoint_;
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote server, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 7;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Endpoint, io.grpc.alts.internal.Handshaker.Endpoint.Builder, io.grpc.alts.internal.Handshaker.EndpointOrBuilder> 
+          getRemoteEndpointFieldBuilder() {
+        if (remoteEndpointBuilder_ == null) {
+          remoteEndpointBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.Endpoint, io.grpc.alts.internal.Handshaker.Endpoint.Builder, io.grpc.alts.internal.Handshaker.EndpointOrBuilder>(
+                  getRemoteEndpoint(),
+                  getParentForChildren(),
+                  isClean());
+          remoteEndpoint_ = null;
+        }
+        return remoteEndpointBuilder_;
+      }
+
+      private java.lang.Object targetName_ = "";
+      /**
+       * <pre>
+       * (Optional) If target name is provided, a secure naming check is performed
+       * to verify that the peer authenticated identity is indeed authorized to run
+       * the target name.
+       * </pre>
+       *
+       * <code>string target_name = 8;</code>
+       */
+      public java.lang.String getTargetName() {
+        java.lang.Object ref = targetName_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          targetName_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) If target name is provided, a secure naming check is performed
+       * to verify that the peer authenticated identity is indeed authorized to run
+       * the target name.
+       * </pre>
+       *
+       * <code>string target_name = 8;</code>
+       */
+      public com.google.protobuf.ByteString
+          getTargetNameBytes() {
+        java.lang.Object ref = targetName_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          targetName_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) If target name is provided, a secure naming check is performed
+       * to verify that the peer authenticated identity is indeed authorized to run
+       * the target name.
+       * </pre>
+       *
+       * <code>string target_name = 8;</code>
+       */
+      public Builder setTargetName(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        targetName_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) If target name is provided, a secure naming check is performed
+       * to verify that the peer authenticated identity is indeed authorized to run
+       * the target name.
+       * </pre>
+       *
+       * <code>string target_name = 8;</code>
+       */
+      public Builder clearTargetName() {
+        
+        targetName_ = getDefaultInstance().getTargetName();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) If target name is provided, a secure naming check is performed
+       * to verify that the peer authenticated identity is indeed authorized to run
+       * the target name.
+       * </pre>
+       *
+       * <code>string target_name = 8;</code>
+       */
+      public Builder setTargetNameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        targetName_ = value;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions rpcVersions_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder> rpcVersionsBuilder_;
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+       */
+      public boolean hasRpcVersions() {
+        return rpcVersionsBuilder_ != null || rpcVersions_ != null;
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getRpcVersions() {
+        if (rpcVersionsBuilder_ == null) {
+          return rpcVersions_ == null ? io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance() : rpcVersions_;
+        } else {
+          return rpcVersionsBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+       */
+      public Builder setRpcVersions(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions value) {
+        if (rpcVersionsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          rpcVersions_ = value;
+          onChanged();
+        } else {
+          rpcVersionsBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+       */
+      public Builder setRpcVersions(
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder builderForValue) {
+        if (rpcVersionsBuilder_ == null) {
+          rpcVersions_ = builderForValue.build();
+          onChanged();
+        } else {
+          rpcVersionsBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+       */
+      public Builder mergeRpcVersions(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions value) {
+        if (rpcVersionsBuilder_ == null) {
+          if (rpcVersions_ != null) {
+            rpcVersions_ =
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.newBuilder(rpcVersions_).mergeFrom(value).buildPartial();
+          } else {
+            rpcVersions_ = value;
+          }
+          onChanged();
+        } else {
+          rpcVersionsBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+       */
+      public Builder clearRpcVersions() {
+        if (rpcVersionsBuilder_ == null) {
+          rpcVersions_ = null;
+          onChanged();
+        } else {
+          rpcVersions_ = null;
+          rpcVersionsBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder getRpcVersionsBuilder() {
+        
+        onChanged();
+        return getRpcVersionsFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder getRpcVersionsOrBuilder() {
+        if (rpcVersionsBuilder_ != null) {
+          return rpcVersionsBuilder_.getMessageOrBuilder();
+        } else {
+          return rpcVersions_ == null ?
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance() : rpcVersions_;
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the client.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 9;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder> 
+          getRpcVersionsFieldBuilder() {
+        if (rpcVersionsBuilder_ == null) {
+          rpcVersionsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder>(
+                  getRpcVersions(),
+                  getParentForChildren(),
+                  isClean());
+          rpcVersions_ = null;
+        }
+        return rpcVersionsBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.gcp.StartClientHandshakeReq)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.gcp.StartClientHandshakeReq)
+    private static final io.grpc.alts.internal.Handshaker.StartClientHandshakeReq DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.alts.internal.Handshaker.StartClientHandshakeReq();
+    }
+
+    public static io.grpc.alts.internal.Handshaker.StartClientHandshakeReq getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<StartClientHandshakeReq>
+        PARSER = new com.google.protobuf.AbstractParser<StartClientHandshakeReq>() {
+      public StartClientHandshakeReq parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new StartClientHandshakeReq(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<StartClientHandshakeReq> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<StartClientHandshakeReq> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.alts.internal.Handshaker.StartClientHandshakeReq getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ServerHandshakeParametersOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.gcp.ServerHandshakeParameters)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * The record protocols supported by the server, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 1;</code>
+     */
+    java.util.List<java.lang.String>
+        getRecordProtocolsList();
+    /**
+     * <pre>
+     * The record protocols supported by the server, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 1;</code>
+     */
+    int getRecordProtocolsCount();
+    /**
+     * <pre>
+     * The record protocols supported by the server, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 1;</code>
+     */
+    java.lang.String getRecordProtocols(int index);
+    /**
+     * <pre>
+     * The record protocols supported by the server, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getRecordProtocolsBytes(int index);
+
+    /**
+     * <pre>
+     * (Optional) A list of local identities supported by the server, if
+     * specified. Otherwise, the handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+     */
+    java.util.List<io.grpc.alts.internal.Handshaker.Identity> 
+        getLocalIdentitiesList();
+    /**
+     * <pre>
+     * (Optional) A list of local identities supported by the server, if
+     * specified. Otherwise, the handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+     */
+    io.grpc.alts.internal.Handshaker.Identity getLocalIdentities(int index);
+    /**
+     * <pre>
+     * (Optional) A list of local identities supported by the server, if
+     * specified. Otherwise, the handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+     */
+    int getLocalIdentitiesCount();
+    /**
+     * <pre>
+     * (Optional) A list of local identities supported by the server, if
+     * specified. Otherwise, the handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+     */
+    java.util.List<? extends io.grpc.alts.internal.Handshaker.IdentityOrBuilder> 
+        getLocalIdentitiesOrBuilderList();
+    /**
+     * <pre>
+     * (Optional) A list of local identities supported by the server, if
+     * specified. Otherwise, the handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+     */
+    io.grpc.alts.internal.Handshaker.IdentityOrBuilder getLocalIdentitiesOrBuilder(
+        int index);
+  }
+  /**
+   * Protobuf type {@code grpc.gcp.ServerHandshakeParameters}
+   */
+  public  static final class ServerHandshakeParameters extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.gcp.ServerHandshakeParameters)
+      ServerHandshakeParametersOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ServerHandshakeParameters.newBuilder() to construct.
+    private ServerHandshakeParameters(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ServerHandshakeParameters() {
+      recordProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+      localIdentities_ = java.util.Collections.emptyList();
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ServerHandshakeParameters(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+              if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+                recordProtocols_ = new com.google.protobuf.LazyStringArrayList();
+                mutable_bitField0_ |= 0x00000001;
+              }
+              recordProtocols_.add(s);
+              break;
+            }
+            case 18: {
+              if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+                localIdentities_ = new java.util.ArrayList<io.grpc.alts.internal.Handshaker.Identity>();
+                mutable_bitField0_ |= 0x00000002;
+              }
+              localIdentities_.add(
+                  input.readMessage(io.grpc.alts.internal.Handshaker.Identity.parser(), extensionRegistry));
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+          recordProtocols_ = recordProtocols_.getUnmodifiableView();
+        }
+        if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+          localIdentities_ = java.util.Collections.unmodifiableList(localIdentities_);
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_ServerHandshakeParameters_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_ServerHandshakeParameters_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.alts.internal.Handshaker.ServerHandshakeParameters.class, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters.Builder.class);
+    }
+
+    public static final int RECORD_PROTOCOLS_FIELD_NUMBER = 1;
+    private com.google.protobuf.LazyStringList recordProtocols_;
+    /**
+     * <pre>
+     * The record protocols supported by the server, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 1;</code>
+     */
+    public com.google.protobuf.ProtocolStringList
+        getRecordProtocolsList() {
+      return recordProtocols_;
+    }
+    /**
+     * <pre>
+     * The record protocols supported by the server, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 1;</code>
+     */
+    public int getRecordProtocolsCount() {
+      return recordProtocols_.size();
+    }
+    /**
+     * <pre>
+     * The record protocols supported by the server, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 1;</code>
+     */
+    public java.lang.String getRecordProtocols(int index) {
+      return recordProtocols_.get(index);
+    }
+    /**
+     * <pre>
+     * The record protocols supported by the server, e.g.,
+     * "ALTSRP_GCM_AES128".
+     * </pre>
+     *
+     * <code>repeated string record_protocols = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getRecordProtocolsBytes(int index) {
+      return recordProtocols_.getByteString(index);
+    }
+
+    public static final int LOCAL_IDENTITIES_FIELD_NUMBER = 2;
+    private java.util.List<io.grpc.alts.internal.Handshaker.Identity> localIdentities_;
+    /**
+     * <pre>
+     * (Optional) A list of local identities supported by the server, if
+     * specified. Otherwise, the handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+     */
+    public java.util.List<io.grpc.alts.internal.Handshaker.Identity> getLocalIdentitiesList() {
+      return localIdentities_;
+    }
+    /**
+     * <pre>
+     * (Optional) A list of local identities supported by the server, if
+     * specified. Otherwise, the handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+     */
+    public java.util.List<? extends io.grpc.alts.internal.Handshaker.IdentityOrBuilder> 
+        getLocalIdentitiesOrBuilderList() {
+      return localIdentities_;
+    }
+    /**
+     * <pre>
+     * (Optional) A list of local identities supported by the server, if
+     * specified. Otherwise, the handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+     */
+    public int getLocalIdentitiesCount() {
+      return localIdentities_.size();
+    }
+    /**
+     * <pre>
+     * (Optional) A list of local identities supported by the server, if
+     * specified. Otherwise, the handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.Identity getLocalIdentities(int index) {
+      return localIdentities_.get(index);
+    }
+    /**
+     * <pre>
+     * (Optional) A list of local identities supported by the server, if
+     * specified. Otherwise, the handshaker chooses a default local identity.
+     * </pre>
+     *
+     * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.IdentityOrBuilder getLocalIdentitiesOrBuilder(
+        int index) {
+      return localIdentities_.get(index);
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      for (int i = 0; i < recordProtocols_.size(); i++) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, recordProtocols_.getRaw(i));
+      }
+      for (int i = 0; i < localIdentities_.size(); i++) {
+        output.writeMessage(2, localIdentities_.get(i));
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      {
+        int dataSize = 0;
+        for (int i = 0; i < recordProtocols_.size(); i++) {
+          dataSize += computeStringSizeNoTag(recordProtocols_.getRaw(i));
+        }
+        size += dataSize;
+        size += 1 * getRecordProtocolsList().size();
+      }
+      for (int i = 0; i < localIdentities_.size(); i++) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, localIdentities_.get(i));
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.alts.internal.Handshaker.ServerHandshakeParameters)) {
+        return super.equals(obj);
+      }
+      io.grpc.alts.internal.Handshaker.ServerHandshakeParameters other = (io.grpc.alts.internal.Handshaker.ServerHandshakeParameters) obj;
+
+      boolean result = true;
+      result = result && getRecordProtocolsList()
+          .equals(other.getRecordProtocolsList());
+      result = result && getLocalIdentitiesList()
+          .equals(other.getLocalIdentitiesList());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (getRecordProtocolsCount() > 0) {
+        hash = (37 * hash) + RECORD_PROTOCOLS_FIELD_NUMBER;
+        hash = (53 * hash) + getRecordProtocolsList().hashCode();
+      }
+      if (getLocalIdentitiesCount() > 0) {
+        hash = (37 * hash) + LOCAL_IDENTITIES_FIELD_NUMBER;
+        hash = (53 * hash) + getLocalIdentitiesList().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.alts.internal.Handshaker.ServerHandshakeParameters parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.ServerHandshakeParameters parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.ServerHandshakeParameters parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.ServerHandshakeParameters parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.ServerHandshakeParameters parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.ServerHandshakeParameters parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.ServerHandshakeParameters parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.ServerHandshakeParameters parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.ServerHandshakeParameters parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.ServerHandshakeParameters parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.ServerHandshakeParameters parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.ServerHandshakeParameters parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.alts.internal.Handshaker.ServerHandshakeParameters prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.gcp.ServerHandshakeParameters}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.gcp.ServerHandshakeParameters)
+        io.grpc.alts.internal.Handshaker.ServerHandshakeParametersOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_ServerHandshakeParameters_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_ServerHandshakeParameters_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.alts.internal.Handshaker.ServerHandshakeParameters.class, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters.Builder.class);
+      }
+
+      // Construct using io.grpc.alts.internal.Handshaker.ServerHandshakeParameters.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+          getLocalIdentitiesFieldBuilder();
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        recordProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+        bitField0_ = (bitField0_ & ~0x00000001);
+        if (localIdentitiesBuilder_ == null) {
+          localIdentities_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000002);
+        } else {
+          localIdentitiesBuilder_.clear();
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_ServerHandshakeParameters_descriptor;
+      }
+
+      public io.grpc.alts.internal.Handshaker.ServerHandshakeParameters getDefaultInstanceForType() {
+        return io.grpc.alts.internal.Handshaker.ServerHandshakeParameters.getDefaultInstance();
+      }
+
+      public io.grpc.alts.internal.Handshaker.ServerHandshakeParameters build() {
+        io.grpc.alts.internal.Handshaker.ServerHandshakeParameters result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.alts.internal.Handshaker.ServerHandshakeParameters buildPartial() {
+        io.grpc.alts.internal.Handshaker.ServerHandshakeParameters result = new io.grpc.alts.internal.Handshaker.ServerHandshakeParameters(this);
+        int from_bitField0_ = bitField0_;
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          recordProtocols_ = recordProtocols_.getUnmodifiableView();
+          bitField0_ = (bitField0_ & ~0x00000001);
+        }
+        result.recordProtocols_ = recordProtocols_;
+        if (localIdentitiesBuilder_ == null) {
+          if (((bitField0_ & 0x00000002) == 0x00000002)) {
+            localIdentities_ = java.util.Collections.unmodifiableList(localIdentities_);
+            bitField0_ = (bitField0_ & ~0x00000002);
+          }
+          result.localIdentities_ = localIdentities_;
+        } else {
+          result.localIdentities_ = localIdentitiesBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.alts.internal.Handshaker.ServerHandshakeParameters) {
+          return mergeFrom((io.grpc.alts.internal.Handshaker.ServerHandshakeParameters)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.alts.internal.Handshaker.ServerHandshakeParameters other) {
+        if (other == io.grpc.alts.internal.Handshaker.ServerHandshakeParameters.getDefaultInstance()) return this;
+        if (!other.recordProtocols_.isEmpty()) {
+          if (recordProtocols_.isEmpty()) {
+            recordProtocols_ = other.recordProtocols_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+          } else {
+            ensureRecordProtocolsIsMutable();
+            recordProtocols_.addAll(other.recordProtocols_);
+          }
+          onChanged();
+        }
+        if (localIdentitiesBuilder_ == null) {
+          if (!other.localIdentities_.isEmpty()) {
+            if (localIdentities_.isEmpty()) {
+              localIdentities_ = other.localIdentities_;
+              bitField0_ = (bitField0_ & ~0x00000002);
+            } else {
+              ensureLocalIdentitiesIsMutable();
+              localIdentities_.addAll(other.localIdentities_);
+            }
+            onChanged();
+          }
+        } else {
+          if (!other.localIdentities_.isEmpty()) {
+            if (localIdentitiesBuilder_.isEmpty()) {
+              localIdentitiesBuilder_.dispose();
+              localIdentitiesBuilder_ = null;
+              localIdentities_ = other.localIdentities_;
+              bitField0_ = (bitField0_ & ~0x00000002);
+              localIdentitiesBuilder_ = 
+                com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                   getLocalIdentitiesFieldBuilder() : null;
+            } else {
+              localIdentitiesBuilder_.addAllMessages(other.localIdentities_);
+            }
+          }
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.alts.internal.Handshaker.ServerHandshakeParameters parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.alts.internal.Handshaker.ServerHandshakeParameters) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private com.google.protobuf.LazyStringList recordProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+      private void ensureRecordProtocolsIsMutable() {
+        if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+          recordProtocols_ = new com.google.protobuf.LazyStringArrayList(recordProtocols_);
+          bitField0_ |= 0x00000001;
+         }
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the server, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 1;</code>
+       */
+      public com.google.protobuf.ProtocolStringList
+          getRecordProtocolsList() {
+        return recordProtocols_.getUnmodifiableView();
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the server, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 1;</code>
+       */
+      public int getRecordProtocolsCount() {
+        return recordProtocols_.size();
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the server, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 1;</code>
+       */
+      public java.lang.String getRecordProtocols(int index) {
+        return recordProtocols_.get(index);
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the server, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getRecordProtocolsBytes(int index) {
+        return recordProtocols_.getByteString(index);
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the server, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 1;</code>
+       */
+      public Builder setRecordProtocols(
+          int index, java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureRecordProtocolsIsMutable();
+        recordProtocols_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the server, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 1;</code>
+       */
+      public Builder addRecordProtocols(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureRecordProtocolsIsMutable();
+        recordProtocols_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the server, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 1;</code>
+       */
+      public Builder addAllRecordProtocols(
+          java.lang.Iterable<java.lang.String> values) {
+        ensureRecordProtocolsIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, recordProtocols_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the server, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 1;</code>
+       */
+      public Builder clearRecordProtocols() {
+        recordProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+        bitField0_ = (bitField0_ & ~0x00000001);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The record protocols supported by the server, e.g.,
+       * "ALTSRP_GCM_AES128".
+       * </pre>
+       *
+       * <code>repeated string record_protocols = 1;</code>
+       */
+      public Builder addRecordProtocolsBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        ensureRecordProtocolsIsMutable();
+        recordProtocols_.add(value);
+        onChanged();
+        return this;
+      }
+
+      private java.util.List<io.grpc.alts.internal.Handshaker.Identity> localIdentities_ =
+        java.util.Collections.emptyList();
+      private void ensureLocalIdentitiesIsMutable() {
+        if (!((bitField0_ & 0x00000002) == 0x00000002)) {
+          localIdentities_ = new java.util.ArrayList<io.grpc.alts.internal.Handshaker.Identity>(localIdentities_);
+          bitField0_ |= 0x00000002;
+         }
+      }
+
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder> localIdentitiesBuilder_;
+
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public java.util.List<io.grpc.alts.internal.Handshaker.Identity> getLocalIdentitiesList() {
+        if (localIdentitiesBuilder_ == null) {
+          return java.util.Collections.unmodifiableList(localIdentities_);
+        } else {
+          return localIdentitiesBuilder_.getMessageList();
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public int getLocalIdentitiesCount() {
+        if (localIdentitiesBuilder_ == null) {
+          return localIdentities_.size();
+        } else {
+          return localIdentitiesBuilder_.getCount();
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity getLocalIdentities(int index) {
+        if (localIdentitiesBuilder_ == null) {
+          return localIdentities_.get(index);
+        } else {
+          return localIdentitiesBuilder_.getMessage(index);
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public Builder setLocalIdentities(
+          int index, io.grpc.alts.internal.Handshaker.Identity value) {
+        if (localIdentitiesBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureLocalIdentitiesIsMutable();
+          localIdentities_.set(index, value);
+          onChanged();
+        } else {
+          localIdentitiesBuilder_.setMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public Builder setLocalIdentities(
+          int index, io.grpc.alts.internal.Handshaker.Identity.Builder builderForValue) {
+        if (localIdentitiesBuilder_ == null) {
+          ensureLocalIdentitiesIsMutable();
+          localIdentities_.set(index, builderForValue.build());
+          onChanged();
+        } else {
+          localIdentitiesBuilder_.setMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public Builder addLocalIdentities(io.grpc.alts.internal.Handshaker.Identity value) {
+        if (localIdentitiesBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureLocalIdentitiesIsMutable();
+          localIdentities_.add(value);
+          onChanged();
+        } else {
+          localIdentitiesBuilder_.addMessage(value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public Builder addLocalIdentities(
+          int index, io.grpc.alts.internal.Handshaker.Identity value) {
+        if (localIdentitiesBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureLocalIdentitiesIsMutable();
+          localIdentities_.add(index, value);
+          onChanged();
+        } else {
+          localIdentitiesBuilder_.addMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public Builder addLocalIdentities(
+          io.grpc.alts.internal.Handshaker.Identity.Builder builderForValue) {
+        if (localIdentitiesBuilder_ == null) {
+          ensureLocalIdentitiesIsMutable();
+          localIdentities_.add(builderForValue.build());
+          onChanged();
+        } else {
+          localIdentitiesBuilder_.addMessage(builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public Builder addLocalIdentities(
+          int index, io.grpc.alts.internal.Handshaker.Identity.Builder builderForValue) {
+        if (localIdentitiesBuilder_ == null) {
+          ensureLocalIdentitiesIsMutable();
+          localIdentities_.add(index, builderForValue.build());
+          onChanged();
+        } else {
+          localIdentitiesBuilder_.addMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public Builder addAllLocalIdentities(
+          java.lang.Iterable<? extends io.grpc.alts.internal.Handshaker.Identity> values) {
+        if (localIdentitiesBuilder_ == null) {
+          ensureLocalIdentitiesIsMutable();
+          com.google.protobuf.AbstractMessageLite.Builder.addAll(
+              values, localIdentities_);
+          onChanged();
+        } else {
+          localIdentitiesBuilder_.addAllMessages(values);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public Builder clearLocalIdentities() {
+        if (localIdentitiesBuilder_ == null) {
+          localIdentities_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000002);
+          onChanged();
+        } else {
+          localIdentitiesBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public Builder removeLocalIdentities(int index) {
+        if (localIdentitiesBuilder_ == null) {
+          ensureLocalIdentitiesIsMutable();
+          localIdentities_.remove(index);
+          onChanged();
+        } else {
+          localIdentitiesBuilder_.remove(index);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity.Builder getLocalIdentitiesBuilder(
+          int index) {
+        return getLocalIdentitiesFieldBuilder().getBuilder(index);
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.IdentityOrBuilder getLocalIdentitiesOrBuilder(
+          int index) {
+        if (localIdentitiesBuilder_ == null) {
+          return localIdentities_.get(index);  } else {
+          return localIdentitiesBuilder_.getMessageOrBuilder(index);
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public java.util.List<? extends io.grpc.alts.internal.Handshaker.IdentityOrBuilder> 
+           getLocalIdentitiesOrBuilderList() {
+        if (localIdentitiesBuilder_ != null) {
+          return localIdentitiesBuilder_.getMessageOrBuilderList();
+        } else {
+          return java.util.Collections.unmodifiableList(localIdentities_);
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity.Builder addLocalIdentitiesBuilder() {
+        return getLocalIdentitiesFieldBuilder().addBuilder(
+            io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity.Builder addLocalIdentitiesBuilder(
+          int index) {
+        return getLocalIdentitiesFieldBuilder().addBuilder(
+            index, io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * (Optional) A list of local identities supported by the server, if
+       * specified. Otherwise, the handshaker chooses a default local identity.
+       * </pre>
+       *
+       * <code>repeated .grpc.gcp.Identity local_identities = 2;</code>
+       */
+      public java.util.List<io.grpc.alts.internal.Handshaker.Identity.Builder> 
+           getLocalIdentitiesBuilderList() {
+        return getLocalIdentitiesFieldBuilder().getBuilderList();
+      }
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder> 
+          getLocalIdentitiesFieldBuilder() {
+        if (localIdentitiesBuilder_ == null) {
+          localIdentitiesBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder>(
+                  localIdentities_,
+                  ((bitField0_ & 0x00000002) == 0x00000002),
+                  getParentForChildren(),
+                  isClean());
+          localIdentities_ = null;
+        }
+        return localIdentitiesBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.gcp.ServerHandshakeParameters)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.gcp.ServerHandshakeParameters)
+    private static final io.grpc.alts.internal.Handshaker.ServerHandshakeParameters DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.alts.internal.Handshaker.ServerHandshakeParameters();
+    }
+
+    public static io.grpc.alts.internal.Handshaker.ServerHandshakeParameters getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ServerHandshakeParameters>
+        PARSER = new com.google.protobuf.AbstractParser<ServerHandshakeParameters>() {
+      public ServerHandshakeParameters parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ServerHandshakeParameters(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ServerHandshakeParameters> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ServerHandshakeParameters> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.alts.internal.Handshaker.ServerHandshakeParameters getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface StartServerHandshakeReqOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.gcp.StartServerHandshakeReq)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * The application protocols supported by the server, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 1;</code>
+     */
+    java.util.List<java.lang.String>
+        getApplicationProtocolsList();
+    /**
+     * <pre>
+     * The application protocols supported by the server, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 1;</code>
+     */
+    int getApplicationProtocolsCount();
+    /**
+     * <pre>
+     * The application protocols supported by the server, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 1;</code>
+     */
+    java.lang.String getApplicationProtocols(int index);
+    /**
+     * <pre>
+     * The application protocols supported by the server, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getApplicationProtocolsBytes(int index);
+
+    /**
+     * <pre>
+     * Handshake parameters (record protocols and local identities supported by
+     * the server) mapped by the handshake protocol. Each handshake security
+     * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+     * identities. Since protobuf does not support enum as key to the map, the key
+     * to handshake_parameters is the integer value of HandshakeProtocol enum.
+     * </pre>
+     *
+     * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+     */
+    int getHandshakeParametersCount();
+    /**
+     * <pre>
+     * Handshake parameters (record protocols and local identities supported by
+     * the server) mapped by the handshake protocol. Each handshake security
+     * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+     * identities. Since protobuf does not support enum as key to the map, the key
+     * to handshake_parameters is the integer value of HandshakeProtocol enum.
+     * </pre>
+     *
+     * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+     */
+    boolean containsHandshakeParameters(
+        int key);
+    /**
+     * Use {@link #getHandshakeParametersMap()} instead.
+     */
+    @java.lang.Deprecated
+    java.util.Map<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters>
+    getHandshakeParameters();
+    /**
+     * <pre>
+     * Handshake parameters (record protocols and local identities supported by
+     * the server) mapped by the handshake protocol. Each handshake security
+     * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+     * identities. Since protobuf does not support enum as key to the map, the key
+     * to handshake_parameters is the integer value of HandshakeProtocol enum.
+     * </pre>
+     *
+     * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+     */
+    java.util.Map<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters>
+    getHandshakeParametersMap();
+    /**
+     * <pre>
+     * Handshake parameters (record protocols and local identities supported by
+     * the server) mapped by the handshake protocol. Each handshake security
+     * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+     * identities. Since protobuf does not support enum as key to the map, the key
+     * to handshake_parameters is the integer value of HandshakeProtocol enum.
+     * </pre>
+     *
+     * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+     */
+
+    io.grpc.alts.internal.Handshaker.ServerHandshakeParameters getHandshakeParametersOrDefault(
+        int key,
+        io.grpc.alts.internal.Handshaker.ServerHandshakeParameters defaultValue);
+    /**
+     * <pre>
+     * Handshake parameters (record protocols and local identities supported by
+     * the server) mapped by the handshake protocol. Each handshake security
+     * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+     * identities. Since protobuf does not support enum as key to the map, the key
+     * to handshake_parameters is the integer value of HandshakeProtocol enum.
+     * </pre>
+     *
+     * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+     */
+
+    io.grpc.alts.internal.Handshaker.ServerHandshakeParameters getHandshakeParametersOrThrow(
+        int key);
+
+    /**
+     * <pre>
+     * Bytes in out_frames returned from the peer's HandshakerResp. It is possible
+     * that the peer's out_frames are split into multiple HandshakReq messages.
+     * </pre>
+     *
+     * <code>bytes in_bytes = 3;</code>
+     */
+    com.google.protobuf.ByteString getInBytes();
+
+    /**
+     * <pre>
+     * (Optional) Local endpoint information of the connection to the client,
+     * such as local IP address, port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+     */
+    boolean hasLocalEndpoint();
+    /**
+     * <pre>
+     * (Optional) Local endpoint information of the connection to the client,
+     * such as local IP address, port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+     */
+    io.grpc.alts.internal.Handshaker.Endpoint getLocalEndpoint();
+    /**
+     * <pre>
+     * (Optional) Local endpoint information of the connection to the client,
+     * such as local IP address, port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+     */
+    io.grpc.alts.internal.Handshaker.EndpointOrBuilder getLocalEndpointOrBuilder();
+
+    /**
+     * <pre>
+     * (Optional) Endpoint information of the remote client, such as IP address,
+     * port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+     */
+    boolean hasRemoteEndpoint();
+    /**
+     * <pre>
+     * (Optional) Endpoint information of the remote client, such as IP address,
+     * port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+     */
+    io.grpc.alts.internal.Handshaker.Endpoint getRemoteEndpoint();
+    /**
+     * <pre>
+     * (Optional) Endpoint information of the remote client, such as IP address,
+     * port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+     */
+    io.grpc.alts.internal.Handshaker.EndpointOrBuilder getRemoteEndpointOrBuilder();
+
+    /**
+     * <pre>
+     * (Optional) RPC protocol versions supported by the server.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+     */
+    boolean hasRpcVersions();
+    /**
+     * <pre>
+     * (Optional) RPC protocol versions supported by the server.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+     */
+    io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getRpcVersions();
+    /**
+     * <pre>
+     * (Optional) RPC protocol versions supported by the server.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+     */
+    io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder getRpcVersionsOrBuilder();
+  }
+  /**
+   * Protobuf type {@code grpc.gcp.StartServerHandshakeReq}
+   */
+  public  static final class StartServerHandshakeReq extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.gcp.StartServerHandshakeReq)
+      StartServerHandshakeReqOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use StartServerHandshakeReq.newBuilder() to construct.
+    private StartServerHandshakeReq(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private StartServerHandshakeReq() {
+      applicationProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+      inBytes_ = com.google.protobuf.ByteString.EMPTY;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private StartServerHandshakeReq(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+              if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+                applicationProtocols_ = new com.google.protobuf.LazyStringArrayList();
+                mutable_bitField0_ |= 0x00000001;
+              }
+              applicationProtocols_.add(s);
+              break;
+            }
+            case 18: {
+              if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+                handshakeParameters_ = com.google.protobuf.MapField.newMapField(
+                    HandshakeParametersDefaultEntryHolder.defaultEntry);
+                mutable_bitField0_ |= 0x00000002;
+              }
+              com.google.protobuf.MapEntry<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters>
+              handshakeParameters__ = input.readMessage(
+                  HandshakeParametersDefaultEntryHolder.defaultEntry.getParserForType(), extensionRegistry);
+              handshakeParameters_.getMutableMap().put(
+                  handshakeParameters__.getKey(), handshakeParameters__.getValue());
+              break;
+            }
+            case 26: {
+
+              inBytes_ = input.readBytes();
+              break;
+            }
+            case 34: {
+              io.grpc.alts.internal.Handshaker.Endpoint.Builder subBuilder = null;
+              if (localEndpoint_ != null) {
+                subBuilder = localEndpoint_.toBuilder();
+              }
+              localEndpoint_ = input.readMessage(io.grpc.alts.internal.Handshaker.Endpoint.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(localEndpoint_);
+                localEndpoint_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 42: {
+              io.grpc.alts.internal.Handshaker.Endpoint.Builder subBuilder = null;
+              if (remoteEndpoint_ != null) {
+                subBuilder = remoteEndpoint_.toBuilder();
+              }
+              remoteEndpoint_ = input.readMessage(io.grpc.alts.internal.Handshaker.Endpoint.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(remoteEndpoint_);
+                remoteEndpoint_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 50: {
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder subBuilder = null;
+              if (rpcVersions_ != null) {
+                subBuilder = rpcVersions_.toBuilder();
+              }
+              rpcVersions_ = input.readMessage(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(rpcVersions_);
+                rpcVersions_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+          applicationProtocols_ = applicationProtocols_.getUnmodifiableView();
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_StartServerHandshakeReq_descriptor;
+    }
+
+    @SuppressWarnings({"rawtypes"})
+    protected com.google.protobuf.MapField internalGetMapField(
+        int number) {
+      switch (number) {
+        case 2:
+          return internalGetHandshakeParameters();
+        default:
+          throw new RuntimeException(
+              "Invalid map field number: " + number);
+      }
+    }
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_StartServerHandshakeReq_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.class, io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.Builder.class);
+    }
+
+    private int bitField0_;
+    public static final int APPLICATION_PROTOCOLS_FIELD_NUMBER = 1;
+    private com.google.protobuf.LazyStringList applicationProtocols_;
+    /**
+     * <pre>
+     * The application protocols supported by the server, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 1;</code>
+     */
+    public com.google.protobuf.ProtocolStringList
+        getApplicationProtocolsList() {
+      return applicationProtocols_;
+    }
+    /**
+     * <pre>
+     * The application protocols supported by the server, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 1;</code>
+     */
+    public int getApplicationProtocolsCount() {
+      return applicationProtocols_.size();
+    }
+    /**
+     * <pre>
+     * The application protocols supported by the server, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 1;</code>
+     */
+    public java.lang.String getApplicationProtocols(int index) {
+      return applicationProtocols_.get(index);
+    }
+    /**
+     * <pre>
+     * The application protocols supported by the server, e.g., "h2" (for http2),
+     * "grpc".
+     * </pre>
+     *
+     * <code>repeated string application_protocols = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getApplicationProtocolsBytes(int index) {
+      return applicationProtocols_.getByteString(index);
+    }
+
+    public static final int HANDSHAKE_PARAMETERS_FIELD_NUMBER = 2;
+    private static final class HandshakeParametersDefaultEntryHolder {
+      static final com.google.protobuf.MapEntry<
+          java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters> defaultEntry =
+              com.google.protobuf.MapEntry
+              .<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters>newDefaultInstance(
+                  io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_StartServerHandshakeReq_HandshakeParametersEntry_descriptor, 
+                  com.google.protobuf.WireFormat.FieldType.INT32,
+                  0,
+                  com.google.protobuf.WireFormat.FieldType.MESSAGE,
+                  io.grpc.alts.internal.Handshaker.ServerHandshakeParameters.getDefaultInstance());
+    }
+    private com.google.protobuf.MapField<
+        java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters> handshakeParameters_;
+    private com.google.protobuf.MapField<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters>
+    internalGetHandshakeParameters() {
+      if (handshakeParameters_ == null) {
+        return com.google.protobuf.MapField.emptyMapField(
+            HandshakeParametersDefaultEntryHolder.defaultEntry);
+      }
+      return handshakeParameters_;
+    }
+
+    public int getHandshakeParametersCount() {
+      return internalGetHandshakeParameters().getMap().size();
+    }
+    /**
+     * <pre>
+     * Handshake parameters (record protocols and local identities supported by
+     * the server) mapped by the handshake protocol. Each handshake security
+     * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+     * identities. Since protobuf does not support enum as key to the map, the key
+     * to handshake_parameters is the integer value of HandshakeProtocol enum.
+     * </pre>
+     *
+     * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+     */
+
+    public boolean containsHandshakeParameters(
+        int key) {
+      
+      return internalGetHandshakeParameters().getMap().containsKey(key);
+    }
+    /**
+     * Use {@link #getHandshakeParametersMap()} instead.
+     */
+    @java.lang.Deprecated
+    public java.util.Map<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters> getHandshakeParameters() {
+      return getHandshakeParametersMap();
+    }
+    /**
+     * <pre>
+     * Handshake parameters (record protocols and local identities supported by
+     * the server) mapped by the handshake protocol. Each handshake security
+     * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+     * identities. Since protobuf does not support enum as key to the map, the key
+     * to handshake_parameters is the integer value of HandshakeProtocol enum.
+     * </pre>
+     *
+     * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+     */
+
+    public java.util.Map<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters> getHandshakeParametersMap() {
+      return internalGetHandshakeParameters().getMap();
+    }
+    /**
+     * <pre>
+     * Handshake parameters (record protocols and local identities supported by
+     * the server) mapped by the handshake protocol. Each handshake security
+     * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+     * identities. Since protobuf does not support enum as key to the map, the key
+     * to handshake_parameters is the integer value of HandshakeProtocol enum.
+     * </pre>
+     *
+     * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+     */
+
+    public io.grpc.alts.internal.Handshaker.ServerHandshakeParameters getHandshakeParametersOrDefault(
+        int key,
+        io.grpc.alts.internal.Handshaker.ServerHandshakeParameters defaultValue) {
+      
+      java.util.Map<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters> map =
+          internalGetHandshakeParameters().getMap();
+      return map.containsKey(key) ? map.get(key) : defaultValue;
+    }
+    /**
+     * <pre>
+     * Handshake parameters (record protocols and local identities supported by
+     * the server) mapped by the handshake protocol. Each handshake security
+     * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+     * identities. Since protobuf does not support enum as key to the map, the key
+     * to handshake_parameters is the integer value of HandshakeProtocol enum.
+     * </pre>
+     *
+     * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+     */
+
+    public io.grpc.alts.internal.Handshaker.ServerHandshakeParameters getHandshakeParametersOrThrow(
+        int key) {
+      
+      java.util.Map<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters> map =
+          internalGetHandshakeParameters().getMap();
+      if (!map.containsKey(key)) {
+        throw new java.lang.IllegalArgumentException();
+      }
+      return map.get(key);
+    }
+
+    public static final int IN_BYTES_FIELD_NUMBER = 3;
+    private com.google.protobuf.ByteString inBytes_;
+    /**
+     * <pre>
+     * Bytes in out_frames returned from the peer's HandshakerResp. It is possible
+     * that the peer's out_frames are split into multiple HandshakReq messages.
+     * </pre>
+     *
+     * <code>bytes in_bytes = 3;</code>
+     */
+    public com.google.protobuf.ByteString getInBytes() {
+      return inBytes_;
+    }
+
+    public static final int LOCAL_ENDPOINT_FIELD_NUMBER = 4;
+    private io.grpc.alts.internal.Handshaker.Endpoint localEndpoint_;
+    /**
+     * <pre>
+     * (Optional) Local endpoint information of the connection to the client,
+     * such as local IP address, port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+     */
+    public boolean hasLocalEndpoint() {
+      return localEndpoint_ != null;
+    }
+    /**
+     * <pre>
+     * (Optional) Local endpoint information of the connection to the client,
+     * such as local IP address, port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.Endpoint getLocalEndpoint() {
+      return localEndpoint_ == null ? io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance() : localEndpoint_;
+    }
+    /**
+     * <pre>
+     * (Optional) Local endpoint information of the connection to the client,
+     * such as local IP address, port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.EndpointOrBuilder getLocalEndpointOrBuilder() {
+      return getLocalEndpoint();
+    }
+
+    public static final int REMOTE_ENDPOINT_FIELD_NUMBER = 5;
+    private io.grpc.alts.internal.Handshaker.Endpoint remoteEndpoint_;
+    /**
+     * <pre>
+     * (Optional) Endpoint information of the remote client, such as IP address,
+     * port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+     */
+    public boolean hasRemoteEndpoint() {
+      return remoteEndpoint_ != null;
+    }
+    /**
+     * <pre>
+     * (Optional) Endpoint information of the remote client, such as IP address,
+     * port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.Endpoint getRemoteEndpoint() {
+      return remoteEndpoint_ == null ? io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance() : remoteEndpoint_;
+    }
+    /**
+     * <pre>
+     * (Optional) Endpoint information of the remote client, such as IP address,
+     * port number, and network protocol.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.EndpointOrBuilder getRemoteEndpointOrBuilder() {
+      return getRemoteEndpoint();
+    }
+
+    public static final int RPC_VERSIONS_FIELD_NUMBER = 6;
+    private io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions rpcVersions_;
+    /**
+     * <pre>
+     * (Optional) RPC protocol versions supported by the server.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+     */
+    public boolean hasRpcVersions() {
+      return rpcVersions_ != null;
+    }
+    /**
+     * <pre>
+     * (Optional) RPC protocol versions supported by the server.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+     */
+    public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getRpcVersions() {
+      return rpcVersions_ == null ? io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance() : rpcVersions_;
+    }
+    /**
+     * <pre>
+     * (Optional) RPC protocol versions supported by the server.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+     */
+    public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder getRpcVersionsOrBuilder() {
+      return getRpcVersions();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      for (int i = 0; i < applicationProtocols_.size(); i++) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, applicationProtocols_.getRaw(i));
+      }
+      com.google.protobuf.GeneratedMessageV3
+        .serializeIntegerMapTo(
+          output,
+          internalGetHandshakeParameters(),
+          HandshakeParametersDefaultEntryHolder.defaultEntry,
+          2);
+      if (!inBytes_.isEmpty()) {
+        output.writeBytes(3, inBytes_);
+      }
+      if (localEndpoint_ != null) {
+        output.writeMessage(4, getLocalEndpoint());
+      }
+      if (remoteEndpoint_ != null) {
+        output.writeMessage(5, getRemoteEndpoint());
+      }
+      if (rpcVersions_ != null) {
+        output.writeMessage(6, getRpcVersions());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      {
+        int dataSize = 0;
+        for (int i = 0; i < applicationProtocols_.size(); i++) {
+          dataSize += computeStringSizeNoTag(applicationProtocols_.getRaw(i));
+        }
+        size += dataSize;
+        size += 1 * getApplicationProtocolsList().size();
+      }
+      for (java.util.Map.Entry<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters> entry
+           : internalGetHandshakeParameters().getMap().entrySet()) {
+        com.google.protobuf.MapEntry<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters>
+        handshakeParameters__ = HandshakeParametersDefaultEntryHolder.defaultEntry.newBuilderForType()
+            .setKey(entry.getKey())
+            .setValue(entry.getValue())
+            .build();
+        size += com.google.protobuf.CodedOutputStream
+            .computeMessageSize(2, handshakeParameters__);
+      }
+      if (!inBytes_.isEmpty()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBytesSize(3, inBytes_);
+      }
+      if (localEndpoint_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(4, getLocalEndpoint());
+      }
+      if (remoteEndpoint_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(5, getRemoteEndpoint());
+      }
+      if (rpcVersions_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(6, getRpcVersions());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.alts.internal.Handshaker.StartServerHandshakeReq)) {
+        return super.equals(obj);
+      }
+      io.grpc.alts.internal.Handshaker.StartServerHandshakeReq other = (io.grpc.alts.internal.Handshaker.StartServerHandshakeReq) obj;
+
+      boolean result = true;
+      result = result && getApplicationProtocolsList()
+          .equals(other.getApplicationProtocolsList());
+      result = result && internalGetHandshakeParameters().equals(
+          other.internalGetHandshakeParameters());
+      result = result && getInBytes()
+          .equals(other.getInBytes());
+      result = result && (hasLocalEndpoint() == other.hasLocalEndpoint());
+      if (hasLocalEndpoint()) {
+        result = result && getLocalEndpoint()
+            .equals(other.getLocalEndpoint());
+      }
+      result = result && (hasRemoteEndpoint() == other.hasRemoteEndpoint());
+      if (hasRemoteEndpoint()) {
+        result = result && getRemoteEndpoint()
+            .equals(other.getRemoteEndpoint());
+      }
+      result = result && (hasRpcVersions() == other.hasRpcVersions());
+      if (hasRpcVersions()) {
+        result = result && getRpcVersions()
+            .equals(other.getRpcVersions());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (getApplicationProtocolsCount() > 0) {
+        hash = (37 * hash) + APPLICATION_PROTOCOLS_FIELD_NUMBER;
+        hash = (53 * hash) + getApplicationProtocolsList().hashCode();
+      }
+      if (!internalGetHandshakeParameters().getMap().isEmpty()) {
+        hash = (37 * hash) + HANDSHAKE_PARAMETERS_FIELD_NUMBER;
+        hash = (53 * hash) + internalGetHandshakeParameters().hashCode();
+      }
+      hash = (37 * hash) + IN_BYTES_FIELD_NUMBER;
+      hash = (53 * hash) + getInBytes().hashCode();
+      if (hasLocalEndpoint()) {
+        hash = (37 * hash) + LOCAL_ENDPOINT_FIELD_NUMBER;
+        hash = (53 * hash) + getLocalEndpoint().hashCode();
+      }
+      if (hasRemoteEndpoint()) {
+        hash = (37 * hash) + REMOTE_ENDPOINT_FIELD_NUMBER;
+        hash = (53 * hash) + getRemoteEndpoint().hashCode();
+      }
+      if (hasRpcVersions()) {
+        hash = (37 * hash) + RPC_VERSIONS_FIELD_NUMBER;
+        hash = (53 * hash) + getRpcVersions().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.alts.internal.Handshaker.StartServerHandshakeReq parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartServerHandshakeReq parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartServerHandshakeReq parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartServerHandshakeReq parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartServerHandshakeReq parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartServerHandshakeReq parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartServerHandshakeReq parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartServerHandshakeReq parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartServerHandshakeReq parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartServerHandshakeReq parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartServerHandshakeReq parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.StartServerHandshakeReq parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.alts.internal.Handshaker.StartServerHandshakeReq prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.gcp.StartServerHandshakeReq}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.gcp.StartServerHandshakeReq)
+        io.grpc.alts.internal.Handshaker.StartServerHandshakeReqOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_StartServerHandshakeReq_descriptor;
+      }
+
+      @SuppressWarnings({"rawtypes"})
+      protected com.google.protobuf.MapField internalGetMapField(
+          int number) {
+        switch (number) {
+          case 2:
+            return internalGetHandshakeParameters();
+          default:
+            throw new RuntimeException(
+                "Invalid map field number: " + number);
+        }
+      }
+      @SuppressWarnings({"rawtypes"})
+      protected com.google.protobuf.MapField internalGetMutableMapField(
+          int number) {
+        switch (number) {
+          case 2:
+            return internalGetMutableHandshakeParameters();
+          default:
+            throw new RuntimeException(
+                "Invalid map field number: " + number);
+        }
+      }
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_StartServerHandshakeReq_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.class, io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.Builder.class);
+      }
+
+      // Construct using io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        applicationProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+        bitField0_ = (bitField0_ & ~0x00000001);
+        internalGetMutableHandshakeParameters().clear();
+        inBytes_ = com.google.protobuf.ByteString.EMPTY;
+
+        if (localEndpointBuilder_ == null) {
+          localEndpoint_ = null;
+        } else {
+          localEndpoint_ = null;
+          localEndpointBuilder_ = null;
+        }
+        if (remoteEndpointBuilder_ == null) {
+          remoteEndpoint_ = null;
+        } else {
+          remoteEndpoint_ = null;
+          remoteEndpointBuilder_ = null;
+        }
+        if (rpcVersionsBuilder_ == null) {
+          rpcVersions_ = null;
+        } else {
+          rpcVersions_ = null;
+          rpcVersionsBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_StartServerHandshakeReq_descriptor;
+      }
+
+      public io.grpc.alts.internal.Handshaker.StartServerHandshakeReq getDefaultInstanceForType() {
+        return io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.getDefaultInstance();
+      }
+
+      public io.grpc.alts.internal.Handshaker.StartServerHandshakeReq build() {
+        io.grpc.alts.internal.Handshaker.StartServerHandshakeReq result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.alts.internal.Handshaker.StartServerHandshakeReq buildPartial() {
+        io.grpc.alts.internal.Handshaker.StartServerHandshakeReq result = new io.grpc.alts.internal.Handshaker.StartServerHandshakeReq(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          applicationProtocols_ = applicationProtocols_.getUnmodifiableView();
+          bitField0_ = (bitField0_ & ~0x00000001);
+        }
+        result.applicationProtocols_ = applicationProtocols_;
+        result.handshakeParameters_ = internalGetHandshakeParameters();
+        result.handshakeParameters_.makeImmutable();
+        result.inBytes_ = inBytes_;
+        if (localEndpointBuilder_ == null) {
+          result.localEndpoint_ = localEndpoint_;
+        } else {
+          result.localEndpoint_ = localEndpointBuilder_.build();
+        }
+        if (remoteEndpointBuilder_ == null) {
+          result.remoteEndpoint_ = remoteEndpoint_;
+        } else {
+          result.remoteEndpoint_ = remoteEndpointBuilder_.build();
+        }
+        if (rpcVersionsBuilder_ == null) {
+          result.rpcVersions_ = rpcVersions_;
+        } else {
+          result.rpcVersions_ = rpcVersionsBuilder_.build();
+        }
+        result.bitField0_ = to_bitField0_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.alts.internal.Handshaker.StartServerHandshakeReq) {
+          return mergeFrom((io.grpc.alts.internal.Handshaker.StartServerHandshakeReq)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.alts.internal.Handshaker.StartServerHandshakeReq other) {
+        if (other == io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.getDefaultInstance()) return this;
+        if (!other.applicationProtocols_.isEmpty()) {
+          if (applicationProtocols_.isEmpty()) {
+            applicationProtocols_ = other.applicationProtocols_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+          } else {
+            ensureApplicationProtocolsIsMutable();
+            applicationProtocols_.addAll(other.applicationProtocols_);
+          }
+          onChanged();
+        }
+        internalGetMutableHandshakeParameters().mergeFrom(
+            other.internalGetHandshakeParameters());
+        if (other.getInBytes() != com.google.protobuf.ByteString.EMPTY) {
+          setInBytes(other.getInBytes());
+        }
+        if (other.hasLocalEndpoint()) {
+          mergeLocalEndpoint(other.getLocalEndpoint());
+        }
+        if (other.hasRemoteEndpoint()) {
+          mergeRemoteEndpoint(other.getRemoteEndpoint());
+        }
+        if (other.hasRpcVersions()) {
+          mergeRpcVersions(other.getRpcVersions());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.alts.internal.Handshaker.StartServerHandshakeReq parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.alts.internal.Handshaker.StartServerHandshakeReq) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private com.google.protobuf.LazyStringList applicationProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+      private void ensureApplicationProtocolsIsMutable() {
+        if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+          applicationProtocols_ = new com.google.protobuf.LazyStringArrayList(applicationProtocols_);
+          bitField0_ |= 0x00000001;
+         }
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the server, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 1;</code>
+       */
+      public com.google.protobuf.ProtocolStringList
+          getApplicationProtocolsList() {
+        return applicationProtocols_.getUnmodifiableView();
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the server, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 1;</code>
+       */
+      public int getApplicationProtocolsCount() {
+        return applicationProtocols_.size();
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the server, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 1;</code>
+       */
+      public java.lang.String getApplicationProtocols(int index) {
+        return applicationProtocols_.get(index);
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the server, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getApplicationProtocolsBytes(int index) {
+        return applicationProtocols_.getByteString(index);
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the server, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 1;</code>
+       */
+      public Builder setApplicationProtocols(
+          int index, java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureApplicationProtocolsIsMutable();
+        applicationProtocols_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the server, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 1;</code>
+       */
+      public Builder addApplicationProtocols(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureApplicationProtocolsIsMutable();
+        applicationProtocols_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the server, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 1;</code>
+       */
+      public Builder addAllApplicationProtocols(
+          java.lang.Iterable<java.lang.String> values) {
+        ensureApplicationProtocolsIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, applicationProtocols_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the server, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 1;</code>
+       */
+      public Builder clearApplicationProtocols() {
+        applicationProtocols_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+        bitField0_ = (bitField0_ & ~0x00000001);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The application protocols supported by the server, e.g., "h2" (for http2),
+       * "grpc".
+       * </pre>
+       *
+       * <code>repeated string application_protocols = 1;</code>
+       */
+      public Builder addApplicationProtocolsBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        ensureApplicationProtocolsIsMutable();
+        applicationProtocols_.add(value);
+        onChanged();
+        return this;
+      }
+
+      private com.google.protobuf.MapField<
+          java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters> handshakeParameters_;
+      private com.google.protobuf.MapField<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters>
+      internalGetHandshakeParameters() {
+        if (handshakeParameters_ == null) {
+          return com.google.protobuf.MapField.emptyMapField(
+              HandshakeParametersDefaultEntryHolder.defaultEntry);
+        }
+        return handshakeParameters_;
+      }
+      private com.google.protobuf.MapField<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters>
+      internalGetMutableHandshakeParameters() {
+        onChanged();;
+        if (handshakeParameters_ == null) {
+          handshakeParameters_ = com.google.protobuf.MapField.newMapField(
+              HandshakeParametersDefaultEntryHolder.defaultEntry);
+        }
+        if (!handshakeParameters_.isMutable()) {
+          handshakeParameters_ = handshakeParameters_.copy();
+        }
+        return handshakeParameters_;
+      }
+
+      public int getHandshakeParametersCount() {
+        return internalGetHandshakeParameters().getMap().size();
+      }
+      /**
+       * <pre>
+       * Handshake parameters (record protocols and local identities supported by
+       * the server) mapped by the handshake protocol. Each handshake security
+       * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+       * identities. Since protobuf does not support enum as key to the map, the key
+       * to handshake_parameters is the integer value of HandshakeProtocol enum.
+       * </pre>
+       *
+       * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+       */
+
+      public boolean containsHandshakeParameters(
+          int key) {
+        
+        return internalGetHandshakeParameters().getMap().containsKey(key);
+      }
+      /**
+       * Use {@link #getHandshakeParametersMap()} instead.
+       */
+      @java.lang.Deprecated
+      public java.util.Map<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters> getHandshakeParameters() {
+        return getHandshakeParametersMap();
+      }
+      /**
+       * <pre>
+       * Handshake parameters (record protocols and local identities supported by
+       * the server) mapped by the handshake protocol. Each handshake security
+       * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+       * identities. Since protobuf does not support enum as key to the map, the key
+       * to handshake_parameters is the integer value of HandshakeProtocol enum.
+       * </pre>
+       *
+       * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+       */
+
+      public java.util.Map<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters> getHandshakeParametersMap() {
+        return internalGetHandshakeParameters().getMap();
+      }
+      /**
+       * <pre>
+       * Handshake parameters (record protocols and local identities supported by
+       * the server) mapped by the handshake protocol. Each handshake security
+       * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+       * identities. Since protobuf does not support enum as key to the map, the key
+       * to handshake_parameters is the integer value of HandshakeProtocol enum.
+       * </pre>
+       *
+       * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+       */
+
+      public io.grpc.alts.internal.Handshaker.ServerHandshakeParameters getHandshakeParametersOrDefault(
+          int key,
+          io.grpc.alts.internal.Handshaker.ServerHandshakeParameters defaultValue) {
+        
+        java.util.Map<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters> map =
+            internalGetHandshakeParameters().getMap();
+        return map.containsKey(key) ? map.get(key) : defaultValue;
+      }
+      /**
+       * <pre>
+       * Handshake parameters (record protocols and local identities supported by
+       * the server) mapped by the handshake protocol. Each handshake security
+       * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+       * identities. Since protobuf does not support enum as key to the map, the key
+       * to handshake_parameters is the integer value of HandshakeProtocol enum.
+       * </pre>
+       *
+       * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+       */
+
+      public io.grpc.alts.internal.Handshaker.ServerHandshakeParameters getHandshakeParametersOrThrow(
+          int key) {
+        
+        java.util.Map<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters> map =
+            internalGetHandshakeParameters().getMap();
+        if (!map.containsKey(key)) {
+          throw new java.lang.IllegalArgumentException();
+        }
+        return map.get(key);
+      }
+
+      public Builder clearHandshakeParameters() {
+        internalGetMutableHandshakeParameters().getMutableMap()
+            .clear();
+        return this;
+      }
+      /**
+       * <pre>
+       * Handshake parameters (record protocols and local identities supported by
+       * the server) mapped by the handshake protocol. Each handshake security
+       * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+       * identities. Since protobuf does not support enum as key to the map, the key
+       * to handshake_parameters is the integer value of HandshakeProtocol enum.
+       * </pre>
+       *
+       * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+       */
+
+      public Builder removeHandshakeParameters(
+          int key) {
+        
+        internalGetMutableHandshakeParameters().getMutableMap()
+            .remove(key);
+        return this;
+      }
+      /**
+       * Use alternate mutation accessors instead.
+       */
+      @java.lang.Deprecated
+      public java.util.Map<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters>
+      getMutableHandshakeParameters() {
+        return internalGetMutableHandshakeParameters().getMutableMap();
+      }
+      /**
+       * <pre>
+       * Handshake parameters (record protocols and local identities supported by
+       * the server) mapped by the handshake protocol. Each handshake security
+       * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+       * identities. Since protobuf does not support enum as key to the map, the key
+       * to handshake_parameters is the integer value of HandshakeProtocol enum.
+       * </pre>
+       *
+       * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+       */
+      public Builder putHandshakeParameters(
+          int key,
+          io.grpc.alts.internal.Handshaker.ServerHandshakeParameters value) {
+        
+        if (value == null) { throw new java.lang.NullPointerException(); }
+        internalGetMutableHandshakeParameters().getMutableMap()
+            .put(key, value);
+        return this;
+      }
+      /**
+       * <pre>
+       * Handshake parameters (record protocols and local identities supported by
+       * the server) mapped by the handshake protocol. Each handshake security
+       * protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+       * identities. Since protobuf does not support enum as key to the map, the key
+       * to handshake_parameters is the integer value of HandshakeProtocol enum.
+       * </pre>
+       *
+       * <code>map&lt;int32, .grpc.gcp.ServerHandshakeParameters&gt; handshake_parameters = 2;</code>
+       */
+
+      public Builder putAllHandshakeParameters(
+          java.util.Map<java.lang.Integer, io.grpc.alts.internal.Handshaker.ServerHandshakeParameters> values) {
+        internalGetMutableHandshakeParameters().getMutableMap()
+            .putAll(values);
+        return this;
+      }
+
+      private com.google.protobuf.ByteString inBytes_ = com.google.protobuf.ByteString.EMPTY;
+      /**
+       * <pre>
+       * Bytes in out_frames returned from the peer's HandshakerResp. It is possible
+       * that the peer's out_frames are split into multiple HandshakReq messages.
+       * </pre>
+       *
+       * <code>bytes in_bytes = 3;</code>
+       */
+      public com.google.protobuf.ByteString getInBytes() {
+        return inBytes_;
+      }
+      /**
+       * <pre>
+       * Bytes in out_frames returned from the peer's HandshakerResp. It is possible
+       * that the peer's out_frames are split into multiple HandshakReq messages.
+       * </pre>
+       *
+       * <code>bytes in_bytes = 3;</code>
+       */
+      public Builder setInBytes(com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        inBytes_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Bytes in out_frames returned from the peer's HandshakerResp. It is possible
+       * that the peer's out_frames are split into multiple HandshakReq messages.
+       * </pre>
+       *
+       * <code>bytes in_bytes = 3;</code>
+       */
+      public Builder clearInBytes() {
+        
+        inBytes_ = getDefaultInstance().getInBytes();
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.alts.internal.Handshaker.Endpoint localEndpoint_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Endpoint, io.grpc.alts.internal.Handshaker.Endpoint.Builder, io.grpc.alts.internal.Handshaker.EndpointOrBuilder> localEndpointBuilder_;
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the client,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+       */
+      public boolean hasLocalEndpoint() {
+        return localEndpointBuilder_ != null || localEndpoint_ != null;
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the client,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Endpoint getLocalEndpoint() {
+        if (localEndpointBuilder_ == null) {
+          return localEndpoint_ == null ? io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance() : localEndpoint_;
+        } else {
+          return localEndpointBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the client,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+       */
+      public Builder setLocalEndpoint(io.grpc.alts.internal.Handshaker.Endpoint value) {
+        if (localEndpointBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          localEndpoint_ = value;
+          onChanged();
+        } else {
+          localEndpointBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the client,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+       */
+      public Builder setLocalEndpoint(
+          io.grpc.alts.internal.Handshaker.Endpoint.Builder builderForValue) {
+        if (localEndpointBuilder_ == null) {
+          localEndpoint_ = builderForValue.build();
+          onChanged();
+        } else {
+          localEndpointBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the client,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+       */
+      public Builder mergeLocalEndpoint(io.grpc.alts.internal.Handshaker.Endpoint value) {
+        if (localEndpointBuilder_ == null) {
+          if (localEndpoint_ != null) {
+            localEndpoint_ =
+              io.grpc.alts.internal.Handshaker.Endpoint.newBuilder(localEndpoint_).mergeFrom(value).buildPartial();
+          } else {
+            localEndpoint_ = value;
+          }
+          onChanged();
+        } else {
+          localEndpointBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the client,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+       */
+      public Builder clearLocalEndpoint() {
+        if (localEndpointBuilder_ == null) {
+          localEndpoint_ = null;
+          onChanged();
+        } else {
+          localEndpoint_ = null;
+          localEndpointBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the client,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Endpoint.Builder getLocalEndpointBuilder() {
+        
+        onChanged();
+        return getLocalEndpointFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the client,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.EndpointOrBuilder getLocalEndpointOrBuilder() {
+        if (localEndpointBuilder_ != null) {
+          return localEndpointBuilder_.getMessageOrBuilder();
+        } else {
+          return localEndpoint_ == null ?
+              io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance() : localEndpoint_;
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Local endpoint information of the connection to the client,
+       * such as local IP address, port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint local_endpoint = 4;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Endpoint, io.grpc.alts.internal.Handshaker.Endpoint.Builder, io.grpc.alts.internal.Handshaker.EndpointOrBuilder> 
+          getLocalEndpointFieldBuilder() {
+        if (localEndpointBuilder_ == null) {
+          localEndpointBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.Endpoint, io.grpc.alts.internal.Handshaker.Endpoint.Builder, io.grpc.alts.internal.Handshaker.EndpointOrBuilder>(
+                  getLocalEndpoint(),
+                  getParentForChildren(),
+                  isClean());
+          localEndpoint_ = null;
+        }
+        return localEndpointBuilder_;
+      }
+
+      private io.grpc.alts.internal.Handshaker.Endpoint remoteEndpoint_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Endpoint, io.grpc.alts.internal.Handshaker.Endpoint.Builder, io.grpc.alts.internal.Handshaker.EndpointOrBuilder> remoteEndpointBuilder_;
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote client, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+       */
+      public boolean hasRemoteEndpoint() {
+        return remoteEndpointBuilder_ != null || remoteEndpoint_ != null;
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote client, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Endpoint getRemoteEndpoint() {
+        if (remoteEndpointBuilder_ == null) {
+          return remoteEndpoint_ == null ? io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance() : remoteEndpoint_;
+        } else {
+          return remoteEndpointBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote client, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+       */
+      public Builder setRemoteEndpoint(io.grpc.alts.internal.Handshaker.Endpoint value) {
+        if (remoteEndpointBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          remoteEndpoint_ = value;
+          onChanged();
+        } else {
+          remoteEndpointBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote client, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+       */
+      public Builder setRemoteEndpoint(
+          io.grpc.alts.internal.Handshaker.Endpoint.Builder builderForValue) {
+        if (remoteEndpointBuilder_ == null) {
+          remoteEndpoint_ = builderForValue.build();
+          onChanged();
+        } else {
+          remoteEndpointBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote client, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+       */
+      public Builder mergeRemoteEndpoint(io.grpc.alts.internal.Handshaker.Endpoint value) {
+        if (remoteEndpointBuilder_ == null) {
+          if (remoteEndpoint_ != null) {
+            remoteEndpoint_ =
+              io.grpc.alts.internal.Handshaker.Endpoint.newBuilder(remoteEndpoint_).mergeFrom(value).buildPartial();
+          } else {
+            remoteEndpoint_ = value;
+          }
+          onChanged();
+        } else {
+          remoteEndpointBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote client, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+       */
+      public Builder clearRemoteEndpoint() {
+        if (remoteEndpointBuilder_ == null) {
+          remoteEndpoint_ = null;
+          onChanged();
+        } else {
+          remoteEndpoint_ = null;
+          remoteEndpointBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote client, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Endpoint.Builder getRemoteEndpointBuilder() {
+        
+        onChanged();
+        return getRemoteEndpointFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote client, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.EndpointOrBuilder getRemoteEndpointOrBuilder() {
+        if (remoteEndpointBuilder_ != null) {
+          return remoteEndpointBuilder_.getMessageOrBuilder();
+        } else {
+          return remoteEndpoint_ == null ?
+              io.grpc.alts.internal.Handshaker.Endpoint.getDefaultInstance() : remoteEndpoint_;
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) Endpoint information of the remote client, such as IP address,
+       * port number, and network protocol.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Endpoint remote_endpoint = 5;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Endpoint, io.grpc.alts.internal.Handshaker.Endpoint.Builder, io.grpc.alts.internal.Handshaker.EndpointOrBuilder> 
+          getRemoteEndpointFieldBuilder() {
+        if (remoteEndpointBuilder_ == null) {
+          remoteEndpointBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.Endpoint, io.grpc.alts.internal.Handshaker.Endpoint.Builder, io.grpc.alts.internal.Handshaker.EndpointOrBuilder>(
+                  getRemoteEndpoint(),
+                  getParentForChildren(),
+                  isClean());
+          remoteEndpoint_ = null;
+        }
+        return remoteEndpointBuilder_;
+      }
+
+      private io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions rpcVersions_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder> rpcVersionsBuilder_;
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the server.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+       */
+      public boolean hasRpcVersions() {
+        return rpcVersionsBuilder_ != null || rpcVersions_ != null;
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the server.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getRpcVersions() {
+        if (rpcVersionsBuilder_ == null) {
+          return rpcVersions_ == null ? io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance() : rpcVersions_;
+        } else {
+          return rpcVersionsBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the server.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+       */
+      public Builder setRpcVersions(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions value) {
+        if (rpcVersionsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          rpcVersions_ = value;
+          onChanged();
+        } else {
+          rpcVersionsBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the server.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+       */
+      public Builder setRpcVersions(
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder builderForValue) {
+        if (rpcVersionsBuilder_ == null) {
+          rpcVersions_ = builderForValue.build();
+          onChanged();
+        } else {
+          rpcVersionsBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the server.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+       */
+      public Builder mergeRpcVersions(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions value) {
+        if (rpcVersionsBuilder_ == null) {
+          if (rpcVersions_ != null) {
+            rpcVersions_ =
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.newBuilder(rpcVersions_).mergeFrom(value).buildPartial();
+          } else {
+            rpcVersions_ = value;
+          }
+          onChanged();
+        } else {
+          rpcVersionsBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the server.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+       */
+      public Builder clearRpcVersions() {
+        if (rpcVersionsBuilder_ == null) {
+          rpcVersions_ = null;
+          onChanged();
+        } else {
+          rpcVersions_ = null;
+          rpcVersionsBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the server.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder getRpcVersionsBuilder() {
+        
+        onChanged();
+        return getRpcVersionsFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the server.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder getRpcVersionsOrBuilder() {
+        if (rpcVersionsBuilder_ != null) {
+          return rpcVersionsBuilder_.getMessageOrBuilder();
+        } else {
+          return rpcVersions_ == null ?
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance() : rpcVersions_;
+        }
+      }
+      /**
+       * <pre>
+       * (Optional) RPC protocol versions supported by the server.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions rpc_versions = 6;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder> 
+          getRpcVersionsFieldBuilder() {
+        if (rpcVersionsBuilder_ == null) {
+          rpcVersionsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder>(
+                  getRpcVersions(),
+                  getParentForChildren(),
+                  isClean());
+          rpcVersions_ = null;
+        }
+        return rpcVersionsBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.gcp.StartServerHandshakeReq)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.gcp.StartServerHandshakeReq)
+    private static final io.grpc.alts.internal.Handshaker.StartServerHandshakeReq DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.alts.internal.Handshaker.StartServerHandshakeReq();
+    }
+
+    public static io.grpc.alts.internal.Handshaker.StartServerHandshakeReq getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<StartServerHandshakeReq>
+        PARSER = new com.google.protobuf.AbstractParser<StartServerHandshakeReq>() {
+      public StartServerHandshakeReq parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new StartServerHandshakeReq(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<StartServerHandshakeReq> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<StartServerHandshakeReq> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.alts.internal.Handshaker.StartServerHandshakeReq getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface NextHandshakeMessageReqOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.gcp.NextHandshakeMessageReq)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Bytes in out_frames returned from the peer's HandshakerResp. It is possible
+     * that the peer's out_frames are split into multiple NextHandshakerMessageReq
+     * messages.
+     * </pre>
+     *
+     * <code>bytes in_bytes = 1;</code>
+     */
+    com.google.protobuf.ByteString getInBytes();
+  }
+  /**
+   * Protobuf type {@code grpc.gcp.NextHandshakeMessageReq}
+   */
+  public  static final class NextHandshakeMessageReq extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.gcp.NextHandshakeMessageReq)
+      NextHandshakeMessageReqOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use NextHandshakeMessageReq.newBuilder() to construct.
+    private NextHandshakeMessageReq(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private NextHandshakeMessageReq() {
+      inBytes_ = com.google.protobuf.ByteString.EMPTY;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private NextHandshakeMessageReq(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+
+              inBytes_ = input.readBytes();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_NextHandshakeMessageReq_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_NextHandshakeMessageReq_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.class, io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.Builder.class);
+    }
+
+    public static final int IN_BYTES_FIELD_NUMBER = 1;
+    private com.google.protobuf.ByteString inBytes_;
+    /**
+     * <pre>
+     * Bytes in out_frames returned from the peer's HandshakerResp. It is possible
+     * that the peer's out_frames are split into multiple NextHandshakerMessageReq
+     * messages.
+     * </pre>
+     *
+     * <code>bytes in_bytes = 1;</code>
+     */
+    public com.google.protobuf.ByteString getInBytes() {
+      return inBytes_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!inBytes_.isEmpty()) {
+        output.writeBytes(1, inBytes_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!inBytes_.isEmpty()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBytesSize(1, inBytes_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq)) {
+        return super.equals(obj);
+      }
+      io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq other = (io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq) obj;
+
+      boolean result = true;
+      result = result && getInBytes()
+          .equals(other.getInBytes());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + IN_BYTES_FIELD_NUMBER;
+      hash = (53 * hash) + getInBytes().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.gcp.NextHandshakeMessageReq}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.gcp.NextHandshakeMessageReq)
+        io.grpc.alts.internal.Handshaker.NextHandshakeMessageReqOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_NextHandshakeMessageReq_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_NextHandshakeMessageReq_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.class, io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.Builder.class);
+      }
+
+      // Construct using io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        inBytes_ = com.google.protobuf.ByteString.EMPTY;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_NextHandshakeMessageReq_descriptor;
+      }
+
+      public io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq getDefaultInstanceForType() {
+        return io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.getDefaultInstance();
+      }
+
+      public io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq build() {
+        io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq buildPartial() {
+        io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq result = new io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq(this);
+        result.inBytes_ = inBytes_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq) {
+          return mergeFrom((io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq other) {
+        if (other == io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.getDefaultInstance()) return this;
+        if (other.getInBytes() != com.google.protobuf.ByteString.EMPTY) {
+          setInBytes(other.getInBytes());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private com.google.protobuf.ByteString inBytes_ = com.google.protobuf.ByteString.EMPTY;
+      /**
+       * <pre>
+       * Bytes in out_frames returned from the peer's HandshakerResp. It is possible
+       * that the peer's out_frames are split into multiple NextHandshakerMessageReq
+       * messages.
+       * </pre>
+       *
+       * <code>bytes in_bytes = 1;</code>
+       */
+      public com.google.protobuf.ByteString getInBytes() {
+        return inBytes_;
+      }
+      /**
+       * <pre>
+       * Bytes in out_frames returned from the peer's HandshakerResp. It is possible
+       * that the peer's out_frames are split into multiple NextHandshakerMessageReq
+       * messages.
+       * </pre>
+       *
+       * <code>bytes in_bytes = 1;</code>
+       */
+      public Builder setInBytes(com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        inBytes_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Bytes in out_frames returned from the peer's HandshakerResp. It is possible
+       * that the peer's out_frames are split into multiple NextHandshakerMessageReq
+       * messages.
+       * </pre>
+       *
+       * <code>bytes in_bytes = 1;</code>
+       */
+      public Builder clearInBytes() {
+        
+        inBytes_ = getDefaultInstance().getInBytes();
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.gcp.NextHandshakeMessageReq)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.gcp.NextHandshakeMessageReq)
+    private static final io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq();
+    }
+
+    public static io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<NextHandshakeMessageReq>
+        PARSER = new com.google.protobuf.AbstractParser<NextHandshakeMessageReq>() {
+      public NextHandshakeMessageReq parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new NextHandshakeMessageReq(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<NextHandshakeMessageReq> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<NextHandshakeMessageReq> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface HandshakerReqOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.gcp.HandshakerReq)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * The start client handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+     */
+    boolean hasClientStart();
+    /**
+     * <pre>
+     * The start client handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+     */
+    io.grpc.alts.internal.Handshaker.StartClientHandshakeReq getClientStart();
+    /**
+     * <pre>
+     * The start client handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+     */
+    io.grpc.alts.internal.Handshaker.StartClientHandshakeReqOrBuilder getClientStartOrBuilder();
+
+    /**
+     * <pre>
+     * The start server handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+     */
+    boolean hasServerStart();
+    /**
+     * <pre>
+     * The start server handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+     */
+    io.grpc.alts.internal.Handshaker.StartServerHandshakeReq getServerStart();
+    /**
+     * <pre>
+     * The start server handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+     */
+    io.grpc.alts.internal.Handshaker.StartServerHandshakeReqOrBuilder getServerStartOrBuilder();
+
+    /**
+     * <pre>
+     * The next handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+     */
+    boolean hasNext();
+    /**
+     * <pre>
+     * The next handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+     */
+    io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq getNext();
+    /**
+     * <pre>
+     * The next handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+     */
+    io.grpc.alts.internal.Handshaker.NextHandshakeMessageReqOrBuilder getNextOrBuilder();
+
+    public io.grpc.alts.internal.Handshaker.HandshakerReq.ReqOneofCase getReqOneofCase();
+  }
+  /**
+   * Protobuf type {@code grpc.gcp.HandshakerReq}
+   */
+  public  static final class HandshakerReq extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.gcp.HandshakerReq)
+      HandshakerReqOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use HandshakerReq.newBuilder() to construct.
+    private HandshakerReq(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private HandshakerReq() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private HandshakerReq(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.Builder subBuilder = null;
+              if (reqOneofCase_ == 1) {
+                subBuilder = ((io.grpc.alts.internal.Handshaker.StartClientHandshakeReq) reqOneof_).toBuilder();
+              }
+              reqOneof_ =
+                  input.readMessage(io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom((io.grpc.alts.internal.Handshaker.StartClientHandshakeReq) reqOneof_);
+                reqOneof_ = subBuilder.buildPartial();
+              }
+              reqOneofCase_ = 1;
+              break;
+            }
+            case 18: {
+              io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.Builder subBuilder = null;
+              if (reqOneofCase_ == 2) {
+                subBuilder = ((io.grpc.alts.internal.Handshaker.StartServerHandshakeReq) reqOneof_).toBuilder();
+              }
+              reqOneof_ =
+                  input.readMessage(io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom((io.grpc.alts.internal.Handshaker.StartServerHandshakeReq) reqOneof_);
+                reqOneof_ = subBuilder.buildPartial();
+              }
+              reqOneofCase_ = 2;
+              break;
+            }
+            case 26: {
+              io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.Builder subBuilder = null;
+              if (reqOneofCase_ == 3) {
+                subBuilder = ((io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq) reqOneof_).toBuilder();
+              }
+              reqOneof_ =
+                  input.readMessage(io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom((io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq) reqOneof_);
+                reqOneof_ = subBuilder.buildPartial();
+              }
+              reqOneofCase_ = 3;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerReq_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerReq_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.alts.internal.Handshaker.HandshakerReq.class, io.grpc.alts.internal.Handshaker.HandshakerReq.Builder.class);
+    }
+
+    private int reqOneofCase_ = 0;
+    private java.lang.Object reqOneof_;
+    public enum ReqOneofCase
+        implements com.google.protobuf.Internal.EnumLite {
+      CLIENT_START(1),
+      SERVER_START(2),
+      NEXT(3),
+      REQONEOF_NOT_SET(0);
+      private final int value;
+      private ReqOneofCase(int value) {
+        this.value = value;
+      }
+      /**
+       * @deprecated Use {@link #forNumber(int)} instead.
+       */
+      @java.lang.Deprecated
+      public static ReqOneofCase valueOf(int value) {
+        return forNumber(value);
+      }
+
+      public static ReqOneofCase forNumber(int value) {
+        switch (value) {
+          case 1: return CLIENT_START;
+          case 2: return SERVER_START;
+          case 3: return NEXT;
+          case 0: return REQONEOF_NOT_SET;
+          default: return null;
+        }
+      }
+      public int getNumber() {
+        return this.value;
+      }
+    };
+
+    public ReqOneofCase
+    getReqOneofCase() {
+      return ReqOneofCase.forNumber(
+          reqOneofCase_);
+    }
+
+    public static final int CLIENT_START_FIELD_NUMBER = 1;
+    /**
+     * <pre>
+     * The start client handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+     */
+    public boolean hasClientStart() {
+      return reqOneofCase_ == 1;
+    }
+    /**
+     * <pre>
+     * The start client handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.StartClientHandshakeReq getClientStart() {
+      if (reqOneofCase_ == 1) {
+         return (io.grpc.alts.internal.Handshaker.StartClientHandshakeReq) reqOneof_;
+      }
+      return io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.getDefaultInstance();
+    }
+    /**
+     * <pre>
+     * The start client handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.StartClientHandshakeReqOrBuilder getClientStartOrBuilder() {
+      if (reqOneofCase_ == 1) {
+         return (io.grpc.alts.internal.Handshaker.StartClientHandshakeReq) reqOneof_;
+      }
+      return io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.getDefaultInstance();
+    }
+
+    public static final int SERVER_START_FIELD_NUMBER = 2;
+    /**
+     * <pre>
+     * The start server handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+     */
+    public boolean hasServerStart() {
+      return reqOneofCase_ == 2;
+    }
+    /**
+     * <pre>
+     * The start server handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.StartServerHandshakeReq getServerStart() {
+      if (reqOneofCase_ == 2) {
+         return (io.grpc.alts.internal.Handshaker.StartServerHandshakeReq) reqOneof_;
+      }
+      return io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.getDefaultInstance();
+    }
+    /**
+     * <pre>
+     * The start server handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.StartServerHandshakeReqOrBuilder getServerStartOrBuilder() {
+      if (reqOneofCase_ == 2) {
+         return (io.grpc.alts.internal.Handshaker.StartServerHandshakeReq) reqOneof_;
+      }
+      return io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.getDefaultInstance();
+    }
+
+    public static final int NEXT_FIELD_NUMBER = 3;
+    /**
+     * <pre>
+     * The next handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+     */
+    public boolean hasNext() {
+      return reqOneofCase_ == 3;
+    }
+    /**
+     * <pre>
+     * The next handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq getNext() {
+      if (reqOneofCase_ == 3) {
+         return (io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq) reqOneof_;
+      }
+      return io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.getDefaultInstance();
+    }
+    /**
+     * <pre>
+     * The next handshake request message.
+     * </pre>
+     *
+     * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.NextHandshakeMessageReqOrBuilder getNextOrBuilder() {
+      if (reqOneofCase_ == 3) {
+         return (io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq) reqOneof_;
+      }
+      return io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.getDefaultInstance();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (reqOneofCase_ == 1) {
+        output.writeMessage(1, (io.grpc.alts.internal.Handshaker.StartClientHandshakeReq) reqOneof_);
+      }
+      if (reqOneofCase_ == 2) {
+        output.writeMessage(2, (io.grpc.alts.internal.Handshaker.StartServerHandshakeReq) reqOneof_);
+      }
+      if (reqOneofCase_ == 3) {
+        output.writeMessage(3, (io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq) reqOneof_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (reqOneofCase_ == 1) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, (io.grpc.alts.internal.Handshaker.StartClientHandshakeReq) reqOneof_);
+      }
+      if (reqOneofCase_ == 2) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, (io.grpc.alts.internal.Handshaker.StartServerHandshakeReq) reqOneof_);
+      }
+      if (reqOneofCase_ == 3) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(3, (io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq) reqOneof_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.alts.internal.Handshaker.HandshakerReq)) {
+        return super.equals(obj);
+      }
+      io.grpc.alts.internal.Handshaker.HandshakerReq other = (io.grpc.alts.internal.Handshaker.HandshakerReq) obj;
+
+      boolean result = true;
+      result = result && getReqOneofCase().equals(
+          other.getReqOneofCase());
+      if (!result) return false;
+      switch (reqOneofCase_) {
+        case 1:
+          result = result && getClientStart()
+              .equals(other.getClientStart());
+          break;
+        case 2:
+          result = result && getServerStart()
+              .equals(other.getServerStart());
+          break;
+        case 3:
+          result = result && getNext()
+              .equals(other.getNext());
+          break;
+        case 0:
+        default:
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      switch (reqOneofCase_) {
+        case 1:
+          hash = (37 * hash) + CLIENT_START_FIELD_NUMBER;
+          hash = (53 * hash) + getClientStart().hashCode();
+          break;
+        case 2:
+          hash = (37 * hash) + SERVER_START_FIELD_NUMBER;
+          hash = (53 * hash) + getServerStart().hashCode();
+          break;
+        case 3:
+          hash = (37 * hash) + NEXT_FIELD_NUMBER;
+          hash = (53 * hash) + getNext().hashCode();
+          break;
+        case 0:
+        default:
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.alts.internal.Handshaker.HandshakerReq parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerReq parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerReq parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerReq parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerReq parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerReq parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerReq parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerReq parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerReq parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerReq parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerReq parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerReq parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.alts.internal.Handshaker.HandshakerReq prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.gcp.HandshakerReq}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.gcp.HandshakerReq)
+        io.grpc.alts.internal.Handshaker.HandshakerReqOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerReq_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerReq_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.alts.internal.Handshaker.HandshakerReq.class, io.grpc.alts.internal.Handshaker.HandshakerReq.Builder.class);
+      }
+
+      // Construct using io.grpc.alts.internal.Handshaker.HandshakerReq.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        reqOneofCase_ = 0;
+        reqOneof_ = null;
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerReq_descriptor;
+      }
+
+      public io.grpc.alts.internal.Handshaker.HandshakerReq getDefaultInstanceForType() {
+        return io.grpc.alts.internal.Handshaker.HandshakerReq.getDefaultInstance();
+      }
+
+      public io.grpc.alts.internal.Handshaker.HandshakerReq build() {
+        io.grpc.alts.internal.Handshaker.HandshakerReq result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.alts.internal.Handshaker.HandshakerReq buildPartial() {
+        io.grpc.alts.internal.Handshaker.HandshakerReq result = new io.grpc.alts.internal.Handshaker.HandshakerReq(this);
+        if (reqOneofCase_ == 1) {
+          if (clientStartBuilder_ == null) {
+            result.reqOneof_ = reqOneof_;
+          } else {
+            result.reqOneof_ = clientStartBuilder_.build();
+          }
+        }
+        if (reqOneofCase_ == 2) {
+          if (serverStartBuilder_ == null) {
+            result.reqOneof_ = reqOneof_;
+          } else {
+            result.reqOneof_ = serverStartBuilder_.build();
+          }
+        }
+        if (reqOneofCase_ == 3) {
+          if (nextBuilder_ == null) {
+            result.reqOneof_ = reqOneof_;
+          } else {
+            result.reqOneof_ = nextBuilder_.build();
+          }
+        }
+        result.reqOneofCase_ = reqOneofCase_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.alts.internal.Handshaker.HandshakerReq) {
+          return mergeFrom((io.grpc.alts.internal.Handshaker.HandshakerReq)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.alts.internal.Handshaker.HandshakerReq other) {
+        if (other == io.grpc.alts.internal.Handshaker.HandshakerReq.getDefaultInstance()) return this;
+        switch (other.getReqOneofCase()) {
+          case CLIENT_START: {
+            mergeClientStart(other.getClientStart());
+            break;
+          }
+          case SERVER_START: {
+            mergeServerStart(other.getServerStart());
+            break;
+          }
+          case NEXT: {
+            mergeNext(other.getNext());
+            break;
+          }
+          case REQONEOF_NOT_SET: {
+            break;
+          }
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.alts.internal.Handshaker.HandshakerReq parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.alts.internal.Handshaker.HandshakerReq) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int reqOneofCase_ = 0;
+      private java.lang.Object reqOneof_;
+      public ReqOneofCase
+          getReqOneofCase() {
+        return ReqOneofCase.forNumber(
+            reqOneofCase_);
+      }
+
+      public Builder clearReqOneof() {
+        reqOneofCase_ = 0;
+        reqOneof_ = null;
+        onChanged();
+        return this;
+      }
+
+
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.StartClientHandshakeReq, io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.Builder, io.grpc.alts.internal.Handshaker.StartClientHandshakeReqOrBuilder> clientStartBuilder_;
+      /**
+       * <pre>
+       * The start client handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+       */
+      public boolean hasClientStart() {
+        return reqOneofCase_ == 1;
+      }
+      /**
+       * <pre>
+       * The start client handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.StartClientHandshakeReq getClientStart() {
+        if (clientStartBuilder_ == null) {
+          if (reqOneofCase_ == 1) {
+            return (io.grpc.alts.internal.Handshaker.StartClientHandshakeReq) reqOneof_;
+          }
+          return io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.getDefaultInstance();
+        } else {
+          if (reqOneofCase_ == 1) {
+            return clientStartBuilder_.getMessage();
+          }
+          return io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.getDefaultInstance();
+        }
+      }
+      /**
+       * <pre>
+       * The start client handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+       */
+      public Builder setClientStart(io.grpc.alts.internal.Handshaker.StartClientHandshakeReq value) {
+        if (clientStartBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          reqOneof_ = value;
+          onChanged();
+        } else {
+          clientStartBuilder_.setMessage(value);
+        }
+        reqOneofCase_ = 1;
+        return this;
+      }
+      /**
+       * <pre>
+       * The start client handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+       */
+      public Builder setClientStart(
+          io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.Builder builderForValue) {
+        if (clientStartBuilder_ == null) {
+          reqOneof_ = builderForValue.build();
+          onChanged();
+        } else {
+          clientStartBuilder_.setMessage(builderForValue.build());
+        }
+        reqOneofCase_ = 1;
+        return this;
+      }
+      /**
+       * <pre>
+       * The start client handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+       */
+      public Builder mergeClientStart(io.grpc.alts.internal.Handshaker.StartClientHandshakeReq value) {
+        if (clientStartBuilder_ == null) {
+          if (reqOneofCase_ == 1 &&
+              reqOneof_ != io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.getDefaultInstance()) {
+            reqOneof_ = io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.newBuilder((io.grpc.alts.internal.Handshaker.StartClientHandshakeReq) reqOneof_)
+                .mergeFrom(value).buildPartial();
+          } else {
+            reqOneof_ = value;
+          }
+          onChanged();
+        } else {
+          if (reqOneofCase_ == 1) {
+            clientStartBuilder_.mergeFrom(value);
+          }
+          clientStartBuilder_.setMessage(value);
+        }
+        reqOneofCase_ = 1;
+        return this;
+      }
+      /**
+       * <pre>
+       * The start client handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+       */
+      public Builder clearClientStart() {
+        if (clientStartBuilder_ == null) {
+          if (reqOneofCase_ == 1) {
+            reqOneofCase_ = 0;
+            reqOneof_ = null;
+            onChanged();
+          }
+        } else {
+          if (reqOneofCase_ == 1) {
+            reqOneofCase_ = 0;
+            reqOneof_ = null;
+          }
+          clientStartBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * The start client handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.Builder getClientStartBuilder() {
+        return getClientStartFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * The start client handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.StartClientHandshakeReqOrBuilder getClientStartOrBuilder() {
+        if ((reqOneofCase_ == 1) && (clientStartBuilder_ != null)) {
+          return clientStartBuilder_.getMessageOrBuilder();
+        } else {
+          if (reqOneofCase_ == 1) {
+            return (io.grpc.alts.internal.Handshaker.StartClientHandshakeReq) reqOneof_;
+          }
+          return io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.getDefaultInstance();
+        }
+      }
+      /**
+       * <pre>
+       * The start client handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartClientHandshakeReq client_start = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.StartClientHandshakeReq, io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.Builder, io.grpc.alts.internal.Handshaker.StartClientHandshakeReqOrBuilder> 
+          getClientStartFieldBuilder() {
+        if (clientStartBuilder_ == null) {
+          if (!(reqOneofCase_ == 1)) {
+            reqOneof_ = io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.getDefaultInstance();
+          }
+          clientStartBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.StartClientHandshakeReq, io.grpc.alts.internal.Handshaker.StartClientHandshakeReq.Builder, io.grpc.alts.internal.Handshaker.StartClientHandshakeReqOrBuilder>(
+                  (io.grpc.alts.internal.Handshaker.StartClientHandshakeReq) reqOneof_,
+                  getParentForChildren(),
+                  isClean());
+          reqOneof_ = null;
+        }
+        reqOneofCase_ = 1;
+        onChanged();;
+        return clientStartBuilder_;
+      }
+
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.StartServerHandshakeReq, io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.Builder, io.grpc.alts.internal.Handshaker.StartServerHandshakeReqOrBuilder> serverStartBuilder_;
+      /**
+       * <pre>
+       * The start server handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+       */
+      public boolean hasServerStart() {
+        return reqOneofCase_ == 2;
+      }
+      /**
+       * <pre>
+       * The start server handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.StartServerHandshakeReq getServerStart() {
+        if (serverStartBuilder_ == null) {
+          if (reqOneofCase_ == 2) {
+            return (io.grpc.alts.internal.Handshaker.StartServerHandshakeReq) reqOneof_;
+          }
+          return io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.getDefaultInstance();
+        } else {
+          if (reqOneofCase_ == 2) {
+            return serverStartBuilder_.getMessage();
+          }
+          return io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.getDefaultInstance();
+        }
+      }
+      /**
+       * <pre>
+       * The start server handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+       */
+      public Builder setServerStart(io.grpc.alts.internal.Handshaker.StartServerHandshakeReq value) {
+        if (serverStartBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          reqOneof_ = value;
+          onChanged();
+        } else {
+          serverStartBuilder_.setMessage(value);
+        }
+        reqOneofCase_ = 2;
+        return this;
+      }
+      /**
+       * <pre>
+       * The start server handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+       */
+      public Builder setServerStart(
+          io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.Builder builderForValue) {
+        if (serverStartBuilder_ == null) {
+          reqOneof_ = builderForValue.build();
+          onChanged();
+        } else {
+          serverStartBuilder_.setMessage(builderForValue.build());
+        }
+        reqOneofCase_ = 2;
+        return this;
+      }
+      /**
+       * <pre>
+       * The start server handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+       */
+      public Builder mergeServerStart(io.grpc.alts.internal.Handshaker.StartServerHandshakeReq value) {
+        if (serverStartBuilder_ == null) {
+          if (reqOneofCase_ == 2 &&
+              reqOneof_ != io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.getDefaultInstance()) {
+            reqOneof_ = io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.newBuilder((io.grpc.alts.internal.Handshaker.StartServerHandshakeReq) reqOneof_)
+                .mergeFrom(value).buildPartial();
+          } else {
+            reqOneof_ = value;
+          }
+          onChanged();
+        } else {
+          if (reqOneofCase_ == 2) {
+            serverStartBuilder_.mergeFrom(value);
+          }
+          serverStartBuilder_.setMessage(value);
+        }
+        reqOneofCase_ = 2;
+        return this;
+      }
+      /**
+       * <pre>
+       * The start server handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+       */
+      public Builder clearServerStart() {
+        if (serverStartBuilder_ == null) {
+          if (reqOneofCase_ == 2) {
+            reqOneofCase_ = 0;
+            reqOneof_ = null;
+            onChanged();
+          }
+        } else {
+          if (reqOneofCase_ == 2) {
+            reqOneofCase_ = 0;
+            reqOneof_ = null;
+          }
+          serverStartBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * The start server handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.Builder getServerStartBuilder() {
+        return getServerStartFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * The start server handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.StartServerHandshakeReqOrBuilder getServerStartOrBuilder() {
+        if ((reqOneofCase_ == 2) && (serverStartBuilder_ != null)) {
+          return serverStartBuilder_.getMessageOrBuilder();
+        } else {
+          if (reqOneofCase_ == 2) {
+            return (io.grpc.alts.internal.Handshaker.StartServerHandshakeReq) reqOneof_;
+          }
+          return io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.getDefaultInstance();
+        }
+      }
+      /**
+       * <pre>
+       * The start server handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.StartServerHandshakeReq server_start = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.StartServerHandshakeReq, io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.Builder, io.grpc.alts.internal.Handshaker.StartServerHandshakeReqOrBuilder> 
+          getServerStartFieldBuilder() {
+        if (serverStartBuilder_ == null) {
+          if (!(reqOneofCase_ == 2)) {
+            reqOneof_ = io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.getDefaultInstance();
+          }
+          serverStartBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.StartServerHandshakeReq, io.grpc.alts.internal.Handshaker.StartServerHandshakeReq.Builder, io.grpc.alts.internal.Handshaker.StartServerHandshakeReqOrBuilder>(
+                  (io.grpc.alts.internal.Handshaker.StartServerHandshakeReq) reqOneof_,
+                  getParentForChildren(),
+                  isClean());
+          reqOneof_ = null;
+        }
+        reqOneofCase_ = 2;
+        onChanged();;
+        return serverStartBuilder_;
+      }
+
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq, io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.Builder, io.grpc.alts.internal.Handshaker.NextHandshakeMessageReqOrBuilder> nextBuilder_;
+      /**
+       * <pre>
+       * The next handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+       */
+      public boolean hasNext() {
+        return reqOneofCase_ == 3;
+      }
+      /**
+       * <pre>
+       * The next handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq getNext() {
+        if (nextBuilder_ == null) {
+          if (reqOneofCase_ == 3) {
+            return (io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq) reqOneof_;
+          }
+          return io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.getDefaultInstance();
+        } else {
+          if (reqOneofCase_ == 3) {
+            return nextBuilder_.getMessage();
+          }
+          return io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.getDefaultInstance();
+        }
+      }
+      /**
+       * <pre>
+       * The next handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+       */
+      public Builder setNext(io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq value) {
+        if (nextBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          reqOneof_ = value;
+          onChanged();
+        } else {
+          nextBuilder_.setMessage(value);
+        }
+        reqOneofCase_ = 3;
+        return this;
+      }
+      /**
+       * <pre>
+       * The next handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+       */
+      public Builder setNext(
+          io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.Builder builderForValue) {
+        if (nextBuilder_ == null) {
+          reqOneof_ = builderForValue.build();
+          onChanged();
+        } else {
+          nextBuilder_.setMessage(builderForValue.build());
+        }
+        reqOneofCase_ = 3;
+        return this;
+      }
+      /**
+       * <pre>
+       * The next handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+       */
+      public Builder mergeNext(io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq value) {
+        if (nextBuilder_ == null) {
+          if (reqOneofCase_ == 3 &&
+              reqOneof_ != io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.getDefaultInstance()) {
+            reqOneof_ = io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.newBuilder((io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq) reqOneof_)
+                .mergeFrom(value).buildPartial();
+          } else {
+            reqOneof_ = value;
+          }
+          onChanged();
+        } else {
+          if (reqOneofCase_ == 3) {
+            nextBuilder_.mergeFrom(value);
+          }
+          nextBuilder_.setMessage(value);
+        }
+        reqOneofCase_ = 3;
+        return this;
+      }
+      /**
+       * <pre>
+       * The next handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+       */
+      public Builder clearNext() {
+        if (nextBuilder_ == null) {
+          if (reqOneofCase_ == 3) {
+            reqOneofCase_ = 0;
+            reqOneof_ = null;
+            onChanged();
+          }
+        } else {
+          if (reqOneofCase_ == 3) {
+            reqOneofCase_ = 0;
+            reqOneof_ = null;
+          }
+          nextBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * The next handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.Builder getNextBuilder() {
+        return getNextFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * The next handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.NextHandshakeMessageReqOrBuilder getNextOrBuilder() {
+        if ((reqOneofCase_ == 3) && (nextBuilder_ != null)) {
+          return nextBuilder_.getMessageOrBuilder();
+        } else {
+          if (reqOneofCase_ == 3) {
+            return (io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq) reqOneof_;
+          }
+          return io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.getDefaultInstance();
+        }
+      }
+      /**
+       * <pre>
+       * The next handshake request message.
+       * </pre>
+       *
+       * <code>.grpc.gcp.NextHandshakeMessageReq next = 3;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq, io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.Builder, io.grpc.alts.internal.Handshaker.NextHandshakeMessageReqOrBuilder> 
+          getNextFieldBuilder() {
+        if (nextBuilder_ == null) {
+          if (!(reqOneofCase_ == 3)) {
+            reqOneof_ = io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.getDefaultInstance();
+          }
+          nextBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq, io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq.Builder, io.grpc.alts.internal.Handshaker.NextHandshakeMessageReqOrBuilder>(
+                  (io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq) reqOneof_,
+                  getParentForChildren(),
+                  isClean());
+          reqOneof_ = null;
+        }
+        reqOneofCase_ = 3;
+        onChanged();;
+        return nextBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.gcp.HandshakerReq)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.gcp.HandshakerReq)
+    private static final io.grpc.alts.internal.Handshaker.HandshakerReq DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.alts.internal.Handshaker.HandshakerReq();
+    }
+
+    public static io.grpc.alts.internal.Handshaker.HandshakerReq getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<HandshakerReq>
+        PARSER = new com.google.protobuf.AbstractParser<HandshakerReq>() {
+      public HandshakerReq parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new HandshakerReq(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<HandshakerReq> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<HandshakerReq> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.alts.internal.Handshaker.HandshakerReq getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface HandshakerResultOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.gcp.HandshakerResult)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * The application protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string application_protocol = 1;</code>
+     */
+    java.lang.String getApplicationProtocol();
+    /**
+     * <pre>
+     * The application protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string application_protocol = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getApplicationProtocolBytes();
+
+    /**
+     * <pre>
+     * The record protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string record_protocol = 2;</code>
+     */
+    java.lang.String getRecordProtocol();
+    /**
+     * <pre>
+     * The record protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string record_protocol = 2;</code>
+     */
+    com.google.protobuf.ByteString
+        getRecordProtocolBytes();
+
+    /**
+     * <pre>
+     * Cryptographic key data. The key data may be more than the key length
+     * required for the record protocol, thus the client of the handshaker
+     * service needs to truncate the key data into the right key length.
+     * </pre>
+     *
+     * <code>bytes key_data = 3;</code>
+     */
+    com.google.protobuf.ByteString getKeyData();
+
+    /**
+     * <pre>
+     * The authenticated identity of the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+     */
+    boolean hasPeerIdentity();
+    /**
+     * <pre>
+     * The authenticated identity of the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+     */
+    io.grpc.alts.internal.Handshaker.Identity getPeerIdentity();
+    /**
+     * <pre>
+     * The authenticated identity of the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+     */
+    io.grpc.alts.internal.Handshaker.IdentityOrBuilder getPeerIdentityOrBuilder();
+
+    /**
+     * <pre>
+     * The local identity used in the handshake.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity local_identity = 5;</code>
+     */
+    boolean hasLocalIdentity();
+    /**
+     * <pre>
+     * The local identity used in the handshake.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity local_identity = 5;</code>
+     */
+    io.grpc.alts.internal.Handshaker.Identity getLocalIdentity();
+    /**
+     * <pre>
+     * The local identity used in the handshake.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity local_identity = 5;</code>
+     */
+    io.grpc.alts.internal.Handshaker.IdentityOrBuilder getLocalIdentityOrBuilder();
+
+    /**
+     * <pre>
+     * Indicate whether the handshaker service client should keep the channel
+     * between the handshaker service open, e.g., in order to handle
+     * post-handshake messages in the future.
+     * </pre>
+     *
+     * <code>bool keep_channel_open = 6;</code>
+     */
+    boolean getKeepChannelOpen();
+
+    /**
+     * <pre>
+     * The RPC protocol versions supported by the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+     */
+    boolean hasPeerRpcVersions();
+    /**
+     * <pre>
+     * The RPC protocol versions supported by the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+     */
+    io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getPeerRpcVersions();
+    /**
+     * <pre>
+     * The RPC protocol versions supported by the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+     */
+    io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder getPeerRpcVersionsOrBuilder();
+  }
+  /**
+   * Protobuf type {@code grpc.gcp.HandshakerResult}
+   */
+  public  static final class HandshakerResult extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.gcp.HandshakerResult)
+      HandshakerResultOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use HandshakerResult.newBuilder() to construct.
+    private HandshakerResult(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private HandshakerResult() {
+      applicationProtocol_ = "";
+      recordProtocol_ = "";
+      keyData_ = com.google.protobuf.ByteString.EMPTY;
+      keepChannelOpen_ = false;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private HandshakerResult(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              applicationProtocol_ = s;
+              break;
+            }
+            case 18: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              recordProtocol_ = s;
+              break;
+            }
+            case 26: {
+
+              keyData_ = input.readBytes();
+              break;
+            }
+            case 34: {
+              io.grpc.alts.internal.Handshaker.Identity.Builder subBuilder = null;
+              if (peerIdentity_ != null) {
+                subBuilder = peerIdentity_.toBuilder();
+              }
+              peerIdentity_ = input.readMessage(io.grpc.alts.internal.Handshaker.Identity.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(peerIdentity_);
+                peerIdentity_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 42: {
+              io.grpc.alts.internal.Handshaker.Identity.Builder subBuilder = null;
+              if (localIdentity_ != null) {
+                subBuilder = localIdentity_.toBuilder();
+              }
+              localIdentity_ = input.readMessage(io.grpc.alts.internal.Handshaker.Identity.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(localIdentity_);
+                localIdentity_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 48: {
+
+              keepChannelOpen_ = input.readBool();
+              break;
+            }
+            case 58: {
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder subBuilder = null;
+              if (peerRpcVersions_ != null) {
+                subBuilder = peerRpcVersions_.toBuilder();
+              }
+              peerRpcVersions_ = input.readMessage(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(peerRpcVersions_);
+                peerRpcVersions_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerResult_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerResult_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.alts.internal.Handshaker.HandshakerResult.class, io.grpc.alts.internal.Handshaker.HandshakerResult.Builder.class);
+    }
+
+    public static final int APPLICATION_PROTOCOL_FIELD_NUMBER = 1;
+    private volatile java.lang.Object applicationProtocol_;
+    /**
+     * <pre>
+     * The application protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string application_protocol = 1;</code>
+     */
+    public java.lang.String getApplicationProtocol() {
+      java.lang.Object ref = applicationProtocol_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        applicationProtocol_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * The application protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string application_protocol = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getApplicationProtocolBytes() {
+      java.lang.Object ref = applicationProtocol_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        applicationProtocol_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int RECORD_PROTOCOL_FIELD_NUMBER = 2;
+    private volatile java.lang.Object recordProtocol_;
+    /**
+     * <pre>
+     * The record protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string record_protocol = 2;</code>
+     */
+    public java.lang.String getRecordProtocol() {
+      java.lang.Object ref = recordProtocol_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        recordProtocol_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * The record protocol negotiated for this connection.
+     * </pre>
+     *
+     * <code>string record_protocol = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getRecordProtocolBytes() {
+      java.lang.Object ref = recordProtocol_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        recordProtocol_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int KEY_DATA_FIELD_NUMBER = 3;
+    private com.google.protobuf.ByteString keyData_;
+    /**
+     * <pre>
+     * Cryptographic key data. The key data may be more than the key length
+     * required for the record protocol, thus the client of the handshaker
+     * service needs to truncate the key data into the right key length.
+     * </pre>
+     *
+     * <code>bytes key_data = 3;</code>
+     */
+    public com.google.protobuf.ByteString getKeyData() {
+      return keyData_;
+    }
+
+    public static final int PEER_IDENTITY_FIELD_NUMBER = 4;
+    private io.grpc.alts.internal.Handshaker.Identity peerIdentity_;
+    /**
+     * <pre>
+     * The authenticated identity of the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+     */
+    public boolean hasPeerIdentity() {
+      return peerIdentity_ != null;
+    }
+    /**
+     * <pre>
+     * The authenticated identity of the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.Identity getPeerIdentity() {
+      return peerIdentity_ == null ? io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance() : peerIdentity_;
+    }
+    /**
+     * <pre>
+     * The authenticated identity of the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.IdentityOrBuilder getPeerIdentityOrBuilder() {
+      return getPeerIdentity();
+    }
+
+    public static final int LOCAL_IDENTITY_FIELD_NUMBER = 5;
+    private io.grpc.alts.internal.Handshaker.Identity localIdentity_;
+    /**
+     * <pre>
+     * The local identity used in the handshake.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity local_identity = 5;</code>
+     */
+    public boolean hasLocalIdentity() {
+      return localIdentity_ != null;
+    }
+    /**
+     * <pre>
+     * The local identity used in the handshake.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity local_identity = 5;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.Identity getLocalIdentity() {
+      return localIdentity_ == null ? io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance() : localIdentity_;
+    }
+    /**
+     * <pre>
+     * The local identity used in the handshake.
+     * </pre>
+     *
+     * <code>.grpc.gcp.Identity local_identity = 5;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.IdentityOrBuilder getLocalIdentityOrBuilder() {
+      return getLocalIdentity();
+    }
+
+    public static final int KEEP_CHANNEL_OPEN_FIELD_NUMBER = 6;
+    private boolean keepChannelOpen_;
+    /**
+     * <pre>
+     * Indicate whether the handshaker service client should keep the channel
+     * between the handshaker service open, e.g., in order to handle
+     * post-handshake messages in the future.
+     * </pre>
+     *
+     * <code>bool keep_channel_open = 6;</code>
+     */
+    public boolean getKeepChannelOpen() {
+      return keepChannelOpen_;
+    }
+
+    public static final int PEER_RPC_VERSIONS_FIELD_NUMBER = 7;
+    private io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions peerRpcVersions_;
+    /**
+     * <pre>
+     * The RPC protocol versions supported by the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+     */
+    public boolean hasPeerRpcVersions() {
+      return peerRpcVersions_ != null;
+    }
+    /**
+     * <pre>
+     * The RPC protocol versions supported by the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+     */
+    public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getPeerRpcVersions() {
+      return peerRpcVersions_ == null ? io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance() : peerRpcVersions_;
+    }
+    /**
+     * <pre>
+     * The RPC protocol versions supported by the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+     */
+    public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder getPeerRpcVersionsOrBuilder() {
+      return getPeerRpcVersions();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!getApplicationProtocolBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, applicationProtocol_);
+      }
+      if (!getRecordProtocolBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, recordProtocol_);
+      }
+      if (!keyData_.isEmpty()) {
+        output.writeBytes(3, keyData_);
+      }
+      if (peerIdentity_ != null) {
+        output.writeMessage(4, getPeerIdentity());
+      }
+      if (localIdentity_ != null) {
+        output.writeMessage(5, getLocalIdentity());
+      }
+      if (keepChannelOpen_ != false) {
+        output.writeBool(6, keepChannelOpen_);
+      }
+      if (peerRpcVersions_ != null) {
+        output.writeMessage(7, getPeerRpcVersions());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!getApplicationProtocolBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, applicationProtocol_);
+      }
+      if (!getRecordProtocolBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, recordProtocol_);
+      }
+      if (!keyData_.isEmpty()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBytesSize(3, keyData_);
+      }
+      if (peerIdentity_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(4, getPeerIdentity());
+      }
+      if (localIdentity_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(5, getLocalIdentity());
+      }
+      if (keepChannelOpen_ != false) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBoolSize(6, keepChannelOpen_);
+      }
+      if (peerRpcVersions_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(7, getPeerRpcVersions());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.alts.internal.Handshaker.HandshakerResult)) {
+        return super.equals(obj);
+      }
+      io.grpc.alts.internal.Handshaker.HandshakerResult other = (io.grpc.alts.internal.Handshaker.HandshakerResult) obj;
+
+      boolean result = true;
+      result = result && getApplicationProtocol()
+          .equals(other.getApplicationProtocol());
+      result = result && getRecordProtocol()
+          .equals(other.getRecordProtocol());
+      result = result && getKeyData()
+          .equals(other.getKeyData());
+      result = result && (hasPeerIdentity() == other.hasPeerIdentity());
+      if (hasPeerIdentity()) {
+        result = result && getPeerIdentity()
+            .equals(other.getPeerIdentity());
+      }
+      result = result && (hasLocalIdentity() == other.hasLocalIdentity());
+      if (hasLocalIdentity()) {
+        result = result && getLocalIdentity()
+            .equals(other.getLocalIdentity());
+      }
+      result = result && (getKeepChannelOpen()
+          == other.getKeepChannelOpen());
+      result = result && (hasPeerRpcVersions() == other.hasPeerRpcVersions());
+      if (hasPeerRpcVersions()) {
+        result = result && getPeerRpcVersions()
+            .equals(other.getPeerRpcVersions());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + APPLICATION_PROTOCOL_FIELD_NUMBER;
+      hash = (53 * hash) + getApplicationProtocol().hashCode();
+      hash = (37 * hash) + RECORD_PROTOCOL_FIELD_NUMBER;
+      hash = (53 * hash) + getRecordProtocol().hashCode();
+      hash = (37 * hash) + KEY_DATA_FIELD_NUMBER;
+      hash = (53 * hash) + getKeyData().hashCode();
+      if (hasPeerIdentity()) {
+        hash = (37 * hash) + PEER_IDENTITY_FIELD_NUMBER;
+        hash = (53 * hash) + getPeerIdentity().hashCode();
+      }
+      if (hasLocalIdentity()) {
+        hash = (37 * hash) + LOCAL_IDENTITY_FIELD_NUMBER;
+        hash = (53 * hash) + getLocalIdentity().hashCode();
+      }
+      hash = (37 * hash) + KEEP_CHANNEL_OPEN_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+          getKeepChannelOpen());
+      if (hasPeerRpcVersions()) {
+        hash = (37 * hash) + PEER_RPC_VERSIONS_FIELD_NUMBER;
+        hash = (53 * hash) + getPeerRpcVersions().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.alts.internal.Handshaker.HandshakerResult parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResult parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResult parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResult parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResult parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResult parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResult parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResult parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResult parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResult parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResult parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResult parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.alts.internal.Handshaker.HandshakerResult prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.gcp.HandshakerResult}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.gcp.HandshakerResult)
+        io.grpc.alts.internal.Handshaker.HandshakerResultOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerResult_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerResult_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.alts.internal.Handshaker.HandshakerResult.class, io.grpc.alts.internal.Handshaker.HandshakerResult.Builder.class);
+      }
+
+      // Construct using io.grpc.alts.internal.Handshaker.HandshakerResult.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        applicationProtocol_ = "";
+
+        recordProtocol_ = "";
+
+        keyData_ = com.google.protobuf.ByteString.EMPTY;
+
+        if (peerIdentityBuilder_ == null) {
+          peerIdentity_ = null;
+        } else {
+          peerIdentity_ = null;
+          peerIdentityBuilder_ = null;
+        }
+        if (localIdentityBuilder_ == null) {
+          localIdentity_ = null;
+        } else {
+          localIdentity_ = null;
+          localIdentityBuilder_ = null;
+        }
+        keepChannelOpen_ = false;
+
+        if (peerRpcVersionsBuilder_ == null) {
+          peerRpcVersions_ = null;
+        } else {
+          peerRpcVersions_ = null;
+          peerRpcVersionsBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerResult_descriptor;
+      }
+
+      public io.grpc.alts.internal.Handshaker.HandshakerResult getDefaultInstanceForType() {
+        return io.grpc.alts.internal.Handshaker.HandshakerResult.getDefaultInstance();
+      }
+
+      public io.grpc.alts.internal.Handshaker.HandshakerResult build() {
+        io.grpc.alts.internal.Handshaker.HandshakerResult result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.alts.internal.Handshaker.HandshakerResult buildPartial() {
+        io.grpc.alts.internal.Handshaker.HandshakerResult result = new io.grpc.alts.internal.Handshaker.HandshakerResult(this);
+        result.applicationProtocol_ = applicationProtocol_;
+        result.recordProtocol_ = recordProtocol_;
+        result.keyData_ = keyData_;
+        if (peerIdentityBuilder_ == null) {
+          result.peerIdentity_ = peerIdentity_;
+        } else {
+          result.peerIdentity_ = peerIdentityBuilder_.build();
+        }
+        if (localIdentityBuilder_ == null) {
+          result.localIdentity_ = localIdentity_;
+        } else {
+          result.localIdentity_ = localIdentityBuilder_.build();
+        }
+        result.keepChannelOpen_ = keepChannelOpen_;
+        if (peerRpcVersionsBuilder_ == null) {
+          result.peerRpcVersions_ = peerRpcVersions_;
+        } else {
+          result.peerRpcVersions_ = peerRpcVersionsBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.alts.internal.Handshaker.HandshakerResult) {
+          return mergeFrom((io.grpc.alts.internal.Handshaker.HandshakerResult)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.alts.internal.Handshaker.HandshakerResult other) {
+        if (other == io.grpc.alts.internal.Handshaker.HandshakerResult.getDefaultInstance()) return this;
+        if (!other.getApplicationProtocol().isEmpty()) {
+          applicationProtocol_ = other.applicationProtocol_;
+          onChanged();
+        }
+        if (!other.getRecordProtocol().isEmpty()) {
+          recordProtocol_ = other.recordProtocol_;
+          onChanged();
+        }
+        if (other.getKeyData() != com.google.protobuf.ByteString.EMPTY) {
+          setKeyData(other.getKeyData());
+        }
+        if (other.hasPeerIdentity()) {
+          mergePeerIdentity(other.getPeerIdentity());
+        }
+        if (other.hasLocalIdentity()) {
+          mergeLocalIdentity(other.getLocalIdentity());
+        }
+        if (other.getKeepChannelOpen() != false) {
+          setKeepChannelOpen(other.getKeepChannelOpen());
+        }
+        if (other.hasPeerRpcVersions()) {
+          mergePeerRpcVersions(other.getPeerRpcVersions());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.alts.internal.Handshaker.HandshakerResult parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.alts.internal.Handshaker.HandshakerResult) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private java.lang.Object applicationProtocol_ = "";
+      /**
+       * <pre>
+       * The application protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string application_protocol = 1;</code>
+       */
+      public java.lang.String getApplicationProtocol() {
+        java.lang.Object ref = applicationProtocol_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          applicationProtocol_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The application protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string application_protocol = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getApplicationProtocolBytes() {
+        java.lang.Object ref = applicationProtocol_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          applicationProtocol_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The application protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string application_protocol = 1;</code>
+       */
+      public Builder setApplicationProtocol(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        applicationProtocol_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The application protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string application_protocol = 1;</code>
+       */
+      public Builder clearApplicationProtocol() {
+        
+        applicationProtocol_ = getDefaultInstance().getApplicationProtocol();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The application protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string application_protocol = 1;</code>
+       */
+      public Builder setApplicationProtocolBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        applicationProtocol_ = value;
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object recordProtocol_ = "";
+      /**
+       * <pre>
+       * The record protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string record_protocol = 2;</code>
+       */
+      public java.lang.String getRecordProtocol() {
+        java.lang.Object ref = recordProtocol_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          recordProtocol_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The record protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string record_protocol = 2;</code>
+       */
+      public com.google.protobuf.ByteString
+          getRecordProtocolBytes() {
+        java.lang.Object ref = recordProtocol_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          recordProtocol_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The record protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string record_protocol = 2;</code>
+       */
+      public Builder setRecordProtocol(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        recordProtocol_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The record protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string record_protocol = 2;</code>
+       */
+      public Builder clearRecordProtocol() {
+        
+        recordProtocol_ = getDefaultInstance().getRecordProtocol();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The record protocol negotiated for this connection.
+       * </pre>
+       *
+       * <code>string record_protocol = 2;</code>
+       */
+      public Builder setRecordProtocolBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        recordProtocol_ = value;
+        onChanged();
+        return this;
+      }
+
+      private com.google.protobuf.ByteString keyData_ = com.google.protobuf.ByteString.EMPTY;
+      /**
+       * <pre>
+       * Cryptographic key data. The key data may be more than the key length
+       * required for the record protocol, thus the client of the handshaker
+       * service needs to truncate the key data into the right key length.
+       * </pre>
+       *
+       * <code>bytes key_data = 3;</code>
+       */
+      public com.google.protobuf.ByteString getKeyData() {
+        return keyData_;
+      }
+      /**
+       * <pre>
+       * Cryptographic key data. The key data may be more than the key length
+       * required for the record protocol, thus the client of the handshaker
+       * service needs to truncate the key data into the right key length.
+       * </pre>
+       *
+       * <code>bytes key_data = 3;</code>
+       */
+      public Builder setKeyData(com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        keyData_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Cryptographic key data. The key data may be more than the key length
+       * required for the record protocol, thus the client of the handshaker
+       * service needs to truncate the key data into the right key length.
+       * </pre>
+       *
+       * <code>bytes key_data = 3;</code>
+       */
+      public Builder clearKeyData() {
+        
+        keyData_ = getDefaultInstance().getKeyData();
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.alts.internal.Handshaker.Identity peerIdentity_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder> peerIdentityBuilder_;
+      /**
+       * <pre>
+       * The authenticated identity of the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+       */
+      public boolean hasPeerIdentity() {
+        return peerIdentityBuilder_ != null || peerIdentity_ != null;
+      }
+      /**
+       * <pre>
+       * The authenticated identity of the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity getPeerIdentity() {
+        if (peerIdentityBuilder_ == null) {
+          return peerIdentity_ == null ? io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance() : peerIdentity_;
+        } else {
+          return peerIdentityBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * The authenticated identity of the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+       */
+      public Builder setPeerIdentity(io.grpc.alts.internal.Handshaker.Identity value) {
+        if (peerIdentityBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          peerIdentity_ = value;
+          onChanged();
+        } else {
+          peerIdentityBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The authenticated identity of the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+       */
+      public Builder setPeerIdentity(
+          io.grpc.alts.internal.Handshaker.Identity.Builder builderForValue) {
+        if (peerIdentityBuilder_ == null) {
+          peerIdentity_ = builderForValue.build();
+          onChanged();
+        } else {
+          peerIdentityBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The authenticated identity of the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+       */
+      public Builder mergePeerIdentity(io.grpc.alts.internal.Handshaker.Identity value) {
+        if (peerIdentityBuilder_ == null) {
+          if (peerIdentity_ != null) {
+            peerIdentity_ =
+              io.grpc.alts.internal.Handshaker.Identity.newBuilder(peerIdentity_).mergeFrom(value).buildPartial();
+          } else {
+            peerIdentity_ = value;
+          }
+          onChanged();
+        } else {
+          peerIdentityBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The authenticated identity of the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+       */
+      public Builder clearPeerIdentity() {
+        if (peerIdentityBuilder_ == null) {
+          peerIdentity_ = null;
+          onChanged();
+        } else {
+          peerIdentity_ = null;
+          peerIdentityBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The authenticated identity of the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity.Builder getPeerIdentityBuilder() {
+        
+        onChanged();
+        return getPeerIdentityFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * The authenticated identity of the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.IdentityOrBuilder getPeerIdentityOrBuilder() {
+        if (peerIdentityBuilder_ != null) {
+          return peerIdentityBuilder_.getMessageOrBuilder();
+        } else {
+          return peerIdentity_ == null ?
+              io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance() : peerIdentity_;
+        }
+      }
+      /**
+       * <pre>
+       * The authenticated identity of the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity peer_identity = 4;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder> 
+          getPeerIdentityFieldBuilder() {
+        if (peerIdentityBuilder_ == null) {
+          peerIdentityBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder>(
+                  getPeerIdentity(),
+                  getParentForChildren(),
+                  isClean());
+          peerIdentity_ = null;
+        }
+        return peerIdentityBuilder_;
+      }
+
+      private io.grpc.alts.internal.Handshaker.Identity localIdentity_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder> localIdentityBuilder_;
+      /**
+       * <pre>
+       * The local identity used in the handshake.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public boolean hasLocalIdentity() {
+        return localIdentityBuilder_ != null || localIdentity_ != null;
+      }
+      /**
+       * <pre>
+       * The local identity used in the handshake.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity getLocalIdentity() {
+        if (localIdentityBuilder_ == null) {
+          return localIdentity_ == null ? io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance() : localIdentity_;
+        } else {
+          return localIdentityBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * The local identity used in the handshake.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public Builder setLocalIdentity(io.grpc.alts.internal.Handshaker.Identity value) {
+        if (localIdentityBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          localIdentity_ = value;
+          onChanged();
+        } else {
+          localIdentityBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The local identity used in the handshake.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public Builder setLocalIdentity(
+          io.grpc.alts.internal.Handshaker.Identity.Builder builderForValue) {
+        if (localIdentityBuilder_ == null) {
+          localIdentity_ = builderForValue.build();
+          onChanged();
+        } else {
+          localIdentityBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The local identity used in the handshake.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public Builder mergeLocalIdentity(io.grpc.alts.internal.Handshaker.Identity value) {
+        if (localIdentityBuilder_ == null) {
+          if (localIdentity_ != null) {
+            localIdentity_ =
+              io.grpc.alts.internal.Handshaker.Identity.newBuilder(localIdentity_).mergeFrom(value).buildPartial();
+          } else {
+            localIdentity_ = value;
+          }
+          onChanged();
+        } else {
+          localIdentityBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The local identity used in the handshake.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public Builder clearLocalIdentity() {
+        if (localIdentityBuilder_ == null) {
+          localIdentity_ = null;
+          onChanged();
+        } else {
+          localIdentity_ = null;
+          localIdentityBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The local identity used in the handshake.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.Identity.Builder getLocalIdentityBuilder() {
+        
+        onChanged();
+        return getLocalIdentityFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * The local identity used in the handshake.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.IdentityOrBuilder getLocalIdentityOrBuilder() {
+        if (localIdentityBuilder_ != null) {
+          return localIdentityBuilder_.getMessageOrBuilder();
+        } else {
+          return localIdentity_ == null ?
+              io.grpc.alts.internal.Handshaker.Identity.getDefaultInstance() : localIdentity_;
+        }
+      }
+      /**
+       * <pre>
+       * The local identity used in the handshake.
+       * </pre>
+       *
+       * <code>.grpc.gcp.Identity local_identity = 5;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder> 
+          getLocalIdentityFieldBuilder() {
+        if (localIdentityBuilder_ == null) {
+          localIdentityBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.Identity, io.grpc.alts.internal.Handshaker.Identity.Builder, io.grpc.alts.internal.Handshaker.IdentityOrBuilder>(
+                  getLocalIdentity(),
+                  getParentForChildren(),
+                  isClean());
+          localIdentity_ = null;
+        }
+        return localIdentityBuilder_;
+      }
+
+      private boolean keepChannelOpen_ ;
+      /**
+       * <pre>
+       * Indicate whether the handshaker service client should keep the channel
+       * between the handshaker service open, e.g., in order to handle
+       * post-handshake messages in the future.
+       * </pre>
+       *
+       * <code>bool keep_channel_open = 6;</code>
+       */
+      public boolean getKeepChannelOpen() {
+        return keepChannelOpen_;
+      }
+      /**
+       * <pre>
+       * Indicate whether the handshaker service client should keep the channel
+       * between the handshaker service open, e.g., in order to handle
+       * post-handshake messages in the future.
+       * </pre>
+       *
+       * <code>bool keep_channel_open = 6;</code>
+       */
+      public Builder setKeepChannelOpen(boolean value) {
+        
+        keepChannelOpen_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Indicate whether the handshaker service client should keep the channel
+       * between the handshaker service open, e.g., in order to handle
+       * post-handshake messages in the future.
+       * </pre>
+       *
+       * <code>bool keep_channel_open = 6;</code>
+       */
+      public Builder clearKeepChannelOpen() {
+        
+        keepChannelOpen_ = false;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions peerRpcVersions_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder> peerRpcVersionsBuilder_;
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+       */
+      public boolean hasPeerRpcVersions() {
+        return peerRpcVersionsBuilder_ != null || peerRpcVersions_ != null;
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getPeerRpcVersions() {
+        if (peerRpcVersionsBuilder_ == null) {
+          return peerRpcVersions_ == null ? io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance() : peerRpcVersions_;
+        } else {
+          return peerRpcVersionsBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+       */
+      public Builder setPeerRpcVersions(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions value) {
+        if (peerRpcVersionsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          peerRpcVersions_ = value;
+          onChanged();
+        } else {
+          peerRpcVersionsBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+       */
+      public Builder setPeerRpcVersions(
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder builderForValue) {
+        if (peerRpcVersionsBuilder_ == null) {
+          peerRpcVersions_ = builderForValue.build();
+          onChanged();
+        } else {
+          peerRpcVersionsBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+       */
+      public Builder mergePeerRpcVersions(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions value) {
+        if (peerRpcVersionsBuilder_ == null) {
+          if (peerRpcVersions_ != null) {
+            peerRpcVersions_ =
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.newBuilder(peerRpcVersions_).mergeFrom(value).buildPartial();
+          } else {
+            peerRpcVersions_ = value;
+          }
+          onChanged();
+        } else {
+          peerRpcVersionsBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+       */
+      public Builder clearPeerRpcVersions() {
+        if (peerRpcVersionsBuilder_ == null) {
+          peerRpcVersions_ = null;
+          onChanged();
+        } else {
+          peerRpcVersions_ = null;
+          peerRpcVersionsBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder getPeerRpcVersionsBuilder() {
+        
+        onChanged();
+        return getPeerRpcVersionsFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder getPeerRpcVersionsOrBuilder() {
+        if (peerRpcVersionsBuilder_ != null) {
+          return peerRpcVersionsBuilder_.getMessageOrBuilder();
+        } else {
+          return peerRpcVersions_ == null ?
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance() : peerRpcVersions_;
+        }
+      }
+      /**
+       * <pre>
+       * The RPC protocol versions supported by the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions peer_rpc_versions = 7;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder> 
+          getPeerRpcVersionsFieldBuilder() {
+        if (peerRpcVersionsBuilder_ == null) {
+          peerRpcVersionsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder>(
+                  getPeerRpcVersions(),
+                  getParentForChildren(),
+                  isClean());
+          peerRpcVersions_ = null;
+        }
+        return peerRpcVersionsBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.gcp.HandshakerResult)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.gcp.HandshakerResult)
+    private static final io.grpc.alts.internal.Handshaker.HandshakerResult DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.alts.internal.Handshaker.HandshakerResult();
+    }
+
+    public static io.grpc.alts.internal.Handshaker.HandshakerResult getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<HandshakerResult>
+        PARSER = new com.google.protobuf.AbstractParser<HandshakerResult>() {
+      public HandshakerResult parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new HandshakerResult(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<HandshakerResult> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<HandshakerResult> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.alts.internal.Handshaker.HandshakerResult getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface HandshakerStatusOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.gcp.HandshakerStatus)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * The status code. This could be the gRPC status code.
+     * </pre>
+     *
+     * <code>uint32 code = 1;</code>
+     */
+    int getCode();
+
+    /**
+     * <pre>
+     * The status details.
+     * </pre>
+     *
+     * <code>string details = 2;</code>
+     */
+    java.lang.String getDetails();
+    /**
+     * <pre>
+     * The status details.
+     * </pre>
+     *
+     * <code>string details = 2;</code>
+     */
+    com.google.protobuf.ByteString
+        getDetailsBytes();
+  }
+  /**
+   * Protobuf type {@code grpc.gcp.HandshakerStatus}
+   */
+  public  static final class HandshakerStatus extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.gcp.HandshakerStatus)
+      HandshakerStatusOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use HandshakerStatus.newBuilder() to construct.
+    private HandshakerStatus(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private HandshakerStatus() {
+      code_ = 0;
+      details_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private HandshakerStatus(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              code_ = input.readUInt32();
+              break;
+            }
+            case 18: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              details_ = s;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerStatus_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerStatus_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.alts.internal.Handshaker.HandshakerStatus.class, io.grpc.alts.internal.Handshaker.HandshakerStatus.Builder.class);
+    }
+
+    public static final int CODE_FIELD_NUMBER = 1;
+    private int code_;
+    /**
+     * <pre>
+     * The status code. This could be the gRPC status code.
+     * </pre>
+     *
+     * <code>uint32 code = 1;</code>
+     */
+    public int getCode() {
+      return code_;
+    }
+
+    public static final int DETAILS_FIELD_NUMBER = 2;
+    private volatile java.lang.Object details_;
+    /**
+     * <pre>
+     * The status details.
+     * </pre>
+     *
+     * <code>string details = 2;</code>
+     */
+    public java.lang.String getDetails() {
+      java.lang.Object ref = details_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        details_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * The status details.
+     * </pre>
+     *
+     * <code>string details = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getDetailsBytes() {
+      java.lang.Object ref = details_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        details_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (code_ != 0) {
+        output.writeUInt32(1, code_);
+      }
+      if (!getDetailsBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, details_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (code_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeUInt32Size(1, code_);
+      }
+      if (!getDetailsBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, details_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.alts.internal.Handshaker.HandshakerStatus)) {
+        return super.equals(obj);
+      }
+      io.grpc.alts.internal.Handshaker.HandshakerStatus other = (io.grpc.alts.internal.Handshaker.HandshakerStatus) obj;
+
+      boolean result = true;
+      result = result && (getCode()
+          == other.getCode());
+      result = result && getDetails()
+          .equals(other.getDetails());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + CODE_FIELD_NUMBER;
+      hash = (53 * hash) + getCode();
+      hash = (37 * hash) + DETAILS_FIELD_NUMBER;
+      hash = (53 * hash) + getDetails().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.alts.internal.Handshaker.HandshakerStatus parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerStatus parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerStatus parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerStatus parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerStatus parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerStatus parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerStatus parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerStatus parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerStatus parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerStatus parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerStatus parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerStatus parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.alts.internal.Handshaker.HandshakerStatus prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.gcp.HandshakerStatus}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.gcp.HandshakerStatus)
+        io.grpc.alts.internal.Handshaker.HandshakerStatusOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerStatus_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerStatus_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.alts.internal.Handshaker.HandshakerStatus.class, io.grpc.alts.internal.Handshaker.HandshakerStatus.Builder.class);
+      }
+
+      // Construct using io.grpc.alts.internal.Handshaker.HandshakerStatus.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        code_ = 0;
+
+        details_ = "";
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerStatus_descriptor;
+      }
+
+      public io.grpc.alts.internal.Handshaker.HandshakerStatus getDefaultInstanceForType() {
+        return io.grpc.alts.internal.Handshaker.HandshakerStatus.getDefaultInstance();
+      }
+
+      public io.grpc.alts.internal.Handshaker.HandshakerStatus build() {
+        io.grpc.alts.internal.Handshaker.HandshakerStatus result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.alts.internal.Handshaker.HandshakerStatus buildPartial() {
+        io.grpc.alts.internal.Handshaker.HandshakerStatus result = new io.grpc.alts.internal.Handshaker.HandshakerStatus(this);
+        result.code_ = code_;
+        result.details_ = details_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.alts.internal.Handshaker.HandshakerStatus) {
+          return mergeFrom((io.grpc.alts.internal.Handshaker.HandshakerStatus)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.alts.internal.Handshaker.HandshakerStatus other) {
+        if (other == io.grpc.alts.internal.Handshaker.HandshakerStatus.getDefaultInstance()) return this;
+        if (other.getCode() != 0) {
+          setCode(other.getCode());
+        }
+        if (!other.getDetails().isEmpty()) {
+          details_ = other.details_;
+          onChanged();
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.alts.internal.Handshaker.HandshakerStatus parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.alts.internal.Handshaker.HandshakerStatus) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int code_ ;
+      /**
+       * <pre>
+       * The status code. This could be the gRPC status code.
+       * </pre>
+       *
+       * <code>uint32 code = 1;</code>
+       */
+      public int getCode() {
+        return code_;
+      }
+      /**
+       * <pre>
+       * The status code. This could be the gRPC status code.
+       * </pre>
+       *
+       * <code>uint32 code = 1;</code>
+       */
+      public Builder setCode(int value) {
+        
+        code_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The status code. This could be the gRPC status code.
+       * </pre>
+       *
+       * <code>uint32 code = 1;</code>
+       */
+      public Builder clearCode() {
+        
+        code_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object details_ = "";
+      /**
+       * <pre>
+       * The status details.
+       * </pre>
+       *
+       * <code>string details = 2;</code>
+       */
+      public java.lang.String getDetails() {
+        java.lang.Object ref = details_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          details_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The status details.
+       * </pre>
+       *
+       * <code>string details = 2;</code>
+       */
+      public com.google.protobuf.ByteString
+          getDetailsBytes() {
+        java.lang.Object ref = details_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          details_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The status details.
+       * </pre>
+       *
+       * <code>string details = 2;</code>
+       */
+      public Builder setDetails(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        details_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The status details.
+       * </pre>
+       *
+       * <code>string details = 2;</code>
+       */
+      public Builder clearDetails() {
+        
+        details_ = getDefaultInstance().getDetails();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The status details.
+       * </pre>
+       *
+       * <code>string details = 2;</code>
+       */
+      public Builder setDetailsBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        details_ = value;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.gcp.HandshakerStatus)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.gcp.HandshakerStatus)
+    private static final io.grpc.alts.internal.Handshaker.HandshakerStatus DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.alts.internal.Handshaker.HandshakerStatus();
+    }
+
+    public static io.grpc.alts.internal.Handshaker.HandshakerStatus getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<HandshakerStatus>
+        PARSER = new com.google.protobuf.AbstractParser<HandshakerStatus>() {
+      public HandshakerStatus parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new HandshakerStatus(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<HandshakerStatus> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<HandshakerStatus> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.alts.internal.Handshaker.HandshakerStatus getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface HandshakerRespOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.gcp.HandshakerResp)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Frames to be given to the peer for the NextHandshakeMessageReq. May be
+     * empty if no out_frames have to be sent to the peer or if in_bytes in the
+     * HandshakerReq are incomplete. All the non-empty out frames must be sent to
+     * the peer even if the handshaker status is not OK as these frames may
+     * contain the alert frames.
+     * </pre>
+     *
+     * <code>bytes out_frames = 1;</code>
+     */
+    com.google.protobuf.ByteString getOutFrames();
+
+    /**
+     * <pre>
+     * Number of bytes in the in_bytes consumed by the handshaker. It is possible
+     * that part of in_bytes in HandshakerReq was unrelated to the handshake
+     * process.
+     * </pre>
+     *
+     * <code>uint32 bytes_consumed = 2;</code>
+     */
+    int getBytesConsumed();
+
+    /**
+     * <pre>
+     * This is set iff the handshake was successful. out_frames may still be set
+     * to frames that needs to be forwarded to the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+     */
+    boolean hasResult();
+    /**
+     * <pre>
+     * This is set iff the handshake was successful. out_frames may still be set
+     * to frames that needs to be forwarded to the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+     */
+    io.grpc.alts.internal.Handshaker.HandshakerResult getResult();
+    /**
+     * <pre>
+     * This is set iff the handshake was successful. out_frames may still be set
+     * to frames that needs to be forwarded to the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+     */
+    io.grpc.alts.internal.Handshaker.HandshakerResultOrBuilder getResultOrBuilder();
+
+    /**
+     * <pre>
+     * Status of the handshaker.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+     */
+    boolean hasStatus();
+    /**
+     * <pre>
+     * Status of the handshaker.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+     */
+    io.grpc.alts.internal.Handshaker.HandshakerStatus getStatus();
+    /**
+     * <pre>
+     * Status of the handshaker.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+     */
+    io.grpc.alts.internal.Handshaker.HandshakerStatusOrBuilder getStatusOrBuilder();
+  }
+  /**
+   * Protobuf type {@code grpc.gcp.HandshakerResp}
+   */
+  public  static final class HandshakerResp extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.gcp.HandshakerResp)
+      HandshakerRespOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use HandshakerResp.newBuilder() to construct.
+    private HandshakerResp(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private HandshakerResp() {
+      outFrames_ = com.google.protobuf.ByteString.EMPTY;
+      bytesConsumed_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private HandshakerResp(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+
+              outFrames_ = input.readBytes();
+              break;
+            }
+            case 16: {
+
+              bytesConsumed_ = input.readUInt32();
+              break;
+            }
+            case 26: {
+              io.grpc.alts.internal.Handshaker.HandshakerResult.Builder subBuilder = null;
+              if (result_ != null) {
+                subBuilder = result_.toBuilder();
+              }
+              result_ = input.readMessage(io.grpc.alts.internal.Handshaker.HandshakerResult.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(result_);
+                result_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 34: {
+              io.grpc.alts.internal.Handshaker.HandshakerStatus.Builder subBuilder = null;
+              if (status_ != null) {
+                subBuilder = status_.toBuilder();
+              }
+              status_ = input.readMessage(io.grpc.alts.internal.Handshaker.HandshakerStatus.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(status_);
+                status_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerResp_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerResp_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.alts.internal.Handshaker.HandshakerResp.class, io.grpc.alts.internal.Handshaker.HandshakerResp.Builder.class);
+    }
+
+    public static final int OUT_FRAMES_FIELD_NUMBER = 1;
+    private com.google.protobuf.ByteString outFrames_;
+    /**
+     * <pre>
+     * Frames to be given to the peer for the NextHandshakeMessageReq. May be
+     * empty if no out_frames have to be sent to the peer or if in_bytes in the
+     * HandshakerReq are incomplete. All the non-empty out frames must be sent to
+     * the peer even if the handshaker status is not OK as these frames may
+     * contain the alert frames.
+     * </pre>
+     *
+     * <code>bytes out_frames = 1;</code>
+     */
+    public com.google.protobuf.ByteString getOutFrames() {
+      return outFrames_;
+    }
+
+    public static final int BYTES_CONSUMED_FIELD_NUMBER = 2;
+    private int bytesConsumed_;
+    /**
+     * <pre>
+     * Number of bytes in the in_bytes consumed by the handshaker. It is possible
+     * that part of in_bytes in HandshakerReq was unrelated to the handshake
+     * process.
+     * </pre>
+     *
+     * <code>uint32 bytes_consumed = 2;</code>
+     */
+    public int getBytesConsumed() {
+      return bytesConsumed_;
+    }
+
+    public static final int RESULT_FIELD_NUMBER = 3;
+    private io.grpc.alts.internal.Handshaker.HandshakerResult result_;
+    /**
+     * <pre>
+     * This is set iff the handshake was successful. out_frames may still be set
+     * to frames that needs to be forwarded to the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+     */
+    public boolean hasResult() {
+      return result_ != null;
+    }
+    /**
+     * <pre>
+     * This is set iff the handshake was successful. out_frames may still be set
+     * to frames that needs to be forwarded to the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.HandshakerResult getResult() {
+      return result_ == null ? io.grpc.alts.internal.Handshaker.HandshakerResult.getDefaultInstance() : result_;
+    }
+    /**
+     * <pre>
+     * This is set iff the handshake was successful. out_frames may still be set
+     * to frames that needs to be forwarded to the peer.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.HandshakerResultOrBuilder getResultOrBuilder() {
+      return getResult();
+    }
+
+    public static final int STATUS_FIELD_NUMBER = 4;
+    private io.grpc.alts.internal.Handshaker.HandshakerStatus status_;
+    /**
+     * <pre>
+     * Status of the handshaker.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+     */
+    public boolean hasStatus() {
+      return status_ != null;
+    }
+    /**
+     * <pre>
+     * Status of the handshaker.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.HandshakerStatus getStatus() {
+      return status_ == null ? io.grpc.alts.internal.Handshaker.HandshakerStatus.getDefaultInstance() : status_;
+    }
+    /**
+     * <pre>
+     * Status of the handshaker.
+     * </pre>
+     *
+     * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+     */
+    public io.grpc.alts.internal.Handshaker.HandshakerStatusOrBuilder getStatusOrBuilder() {
+      return getStatus();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!outFrames_.isEmpty()) {
+        output.writeBytes(1, outFrames_);
+      }
+      if (bytesConsumed_ != 0) {
+        output.writeUInt32(2, bytesConsumed_);
+      }
+      if (result_ != null) {
+        output.writeMessage(3, getResult());
+      }
+      if (status_ != null) {
+        output.writeMessage(4, getStatus());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!outFrames_.isEmpty()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBytesSize(1, outFrames_);
+      }
+      if (bytesConsumed_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeUInt32Size(2, bytesConsumed_);
+      }
+      if (result_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(3, getResult());
+      }
+      if (status_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(4, getStatus());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.alts.internal.Handshaker.HandshakerResp)) {
+        return super.equals(obj);
+      }
+      io.grpc.alts.internal.Handshaker.HandshakerResp other = (io.grpc.alts.internal.Handshaker.HandshakerResp) obj;
+
+      boolean result = true;
+      result = result && getOutFrames()
+          .equals(other.getOutFrames());
+      result = result && (getBytesConsumed()
+          == other.getBytesConsumed());
+      result = result && (hasResult() == other.hasResult());
+      if (hasResult()) {
+        result = result && getResult()
+            .equals(other.getResult());
+      }
+      result = result && (hasStatus() == other.hasStatus());
+      if (hasStatus()) {
+        result = result && getStatus()
+            .equals(other.getStatus());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + OUT_FRAMES_FIELD_NUMBER;
+      hash = (53 * hash) + getOutFrames().hashCode();
+      hash = (37 * hash) + BYTES_CONSUMED_FIELD_NUMBER;
+      hash = (53 * hash) + getBytesConsumed();
+      if (hasResult()) {
+        hash = (37 * hash) + RESULT_FIELD_NUMBER;
+        hash = (53 * hash) + getResult().hashCode();
+      }
+      if (hasStatus()) {
+        hash = (37 * hash) + STATUS_FIELD_NUMBER;
+        hash = (53 * hash) + getStatus().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.alts.internal.Handshaker.HandshakerResp parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResp parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResp parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResp parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResp parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResp parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResp parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResp parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResp parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResp parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResp parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.Handshaker.HandshakerResp parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.alts.internal.Handshaker.HandshakerResp prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.gcp.HandshakerResp}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.gcp.HandshakerResp)
+        io.grpc.alts.internal.Handshaker.HandshakerRespOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerResp_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerResp_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.alts.internal.Handshaker.HandshakerResp.class, io.grpc.alts.internal.Handshaker.HandshakerResp.Builder.class);
+      }
+
+      // Construct using io.grpc.alts.internal.Handshaker.HandshakerResp.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        outFrames_ = com.google.protobuf.ByteString.EMPTY;
+
+        bytesConsumed_ = 0;
+
+        if (resultBuilder_ == null) {
+          result_ = null;
+        } else {
+          result_ = null;
+          resultBuilder_ = null;
+        }
+        if (statusBuilder_ == null) {
+          status_ = null;
+        } else {
+          status_ = null;
+          statusBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.alts.internal.Handshaker.internal_static_grpc_gcp_HandshakerResp_descriptor;
+      }
+
+      public io.grpc.alts.internal.Handshaker.HandshakerResp getDefaultInstanceForType() {
+        return io.grpc.alts.internal.Handshaker.HandshakerResp.getDefaultInstance();
+      }
+
+      public io.grpc.alts.internal.Handshaker.HandshakerResp build() {
+        io.grpc.alts.internal.Handshaker.HandshakerResp result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.alts.internal.Handshaker.HandshakerResp buildPartial() {
+        io.grpc.alts.internal.Handshaker.HandshakerResp result = new io.grpc.alts.internal.Handshaker.HandshakerResp(this);
+        result.outFrames_ = outFrames_;
+        result.bytesConsumed_ = bytesConsumed_;
+        if (resultBuilder_ == null) {
+          result.result_ = result_;
+        } else {
+          result.result_ = resultBuilder_.build();
+        }
+        if (statusBuilder_ == null) {
+          result.status_ = status_;
+        } else {
+          result.status_ = statusBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.alts.internal.Handshaker.HandshakerResp) {
+          return mergeFrom((io.grpc.alts.internal.Handshaker.HandshakerResp)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.alts.internal.Handshaker.HandshakerResp other) {
+        if (other == io.grpc.alts.internal.Handshaker.HandshakerResp.getDefaultInstance()) return this;
+        if (other.getOutFrames() != com.google.protobuf.ByteString.EMPTY) {
+          setOutFrames(other.getOutFrames());
+        }
+        if (other.getBytesConsumed() != 0) {
+          setBytesConsumed(other.getBytesConsumed());
+        }
+        if (other.hasResult()) {
+          mergeResult(other.getResult());
+        }
+        if (other.hasStatus()) {
+          mergeStatus(other.getStatus());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.alts.internal.Handshaker.HandshakerResp parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.alts.internal.Handshaker.HandshakerResp) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private com.google.protobuf.ByteString outFrames_ = com.google.protobuf.ByteString.EMPTY;
+      /**
+       * <pre>
+       * Frames to be given to the peer for the NextHandshakeMessageReq. May be
+       * empty if no out_frames have to be sent to the peer or if in_bytes in the
+       * HandshakerReq are incomplete. All the non-empty out frames must be sent to
+       * the peer even if the handshaker status is not OK as these frames may
+       * contain the alert frames.
+       * </pre>
+       *
+       * <code>bytes out_frames = 1;</code>
+       */
+      public com.google.protobuf.ByteString getOutFrames() {
+        return outFrames_;
+      }
+      /**
+       * <pre>
+       * Frames to be given to the peer for the NextHandshakeMessageReq. May be
+       * empty if no out_frames have to be sent to the peer or if in_bytes in the
+       * HandshakerReq are incomplete. All the non-empty out frames must be sent to
+       * the peer even if the handshaker status is not OK as these frames may
+       * contain the alert frames.
+       * </pre>
+       *
+       * <code>bytes out_frames = 1;</code>
+       */
+      public Builder setOutFrames(com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        outFrames_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Frames to be given to the peer for the NextHandshakeMessageReq. May be
+       * empty if no out_frames have to be sent to the peer or if in_bytes in the
+       * HandshakerReq are incomplete. All the non-empty out frames must be sent to
+       * the peer even if the handshaker status is not OK as these frames may
+       * contain the alert frames.
+       * </pre>
+       *
+       * <code>bytes out_frames = 1;</code>
+       */
+      public Builder clearOutFrames() {
+        
+        outFrames_ = getDefaultInstance().getOutFrames();
+        onChanged();
+        return this;
+      }
+
+      private int bytesConsumed_ ;
+      /**
+       * <pre>
+       * Number of bytes in the in_bytes consumed by the handshaker. It is possible
+       * that part of in_bytes in HandshakerReq was unrelated to the handshake
+       * process.
+       * </pre>
+       *
+       * <code>uint32 bytes_consumed = 2;</code>
+       */
+      public int getBytesConsumed() {
+        return bytesConsumed_;
+      }
+      /**
+       * <pre>
+       * Number of bytes in the in_bytes consumed by the handshaker. It is possible
+       * that part of in_bytes in HandshakerReq was unrelated to the handshake
+       * process.
+       * </pre>
+       *
+       * <code>uint32 bytes_consumed = 2;</code>
+       */
+      public Builder setBytesConsumed(int value) {
+        
+        bytesConsumed_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of bytes in the in_bytes consumed by the handshaker. It is possible
+       * that part of in_bytes in HandshakerReq was unrelated to the handshake
+       * process.
+       * </pre>
+       *
+       * <code>uint32 bytes_consumed = 2;</code>
+       */
+      public Builder clearBytesConsumed() {
+        
+        bytesConsumed_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.alts.internal.Handshaker.HandshakerResult result_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.HandshakerResult, io.grpc.alts.internal.Handshaker.HandshakerResult.Builder, io.grpc.alts.internal.Handshaker.HandshakerResultOrBuilder> resultBuilder_;
+      /**
+       * <pre>
+       * This is set iff the handshake was successful. out_frames may still be set
+       * to frames that needs to be forwarded to the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+       */
+      public boolean hasResult() {
+        return resultBuilder_ != null || result_ != null;
+      }
+      /**
+       * <pre>
+       * This is set iff the handshake was successful. out_frames may still be set
+       * to frames that needs to be forwarded to the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.HandshakerResult getResult() {
+        if (resultBuilder_ == null) {
+          return result_ == null ? io.grpc.alts.internal.Handshaker.HandshakerResult.getDefaultInstance() : result_;
+        } else {
+          return resultBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * This is set iff the handshake was successful. out_frames may still be set
+       * to frames that needs to be forwarded to the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+       */
+      public Builder setResult(io.grpc.alts.internal.Handshaker.HandshakerResult value) {
+        if (resultBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          result_ = value;
+          onChanged();
+        } else {
+          resultBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * This is set iff the handshake was successful. out_frames may still be set
+       * to frames that needs to be forwarded to the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+       */
+      public Builder setResult(
+          io.grpc.alts.internal.Handshaker.HandshakerResult.Builder builderForValue) {
+        if (resultBuilder_ == null) {
+          result_ = builderForValue.build();
+          onChanged();
+        } else {
+          resultBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * This is set iff the handshake was successful. out_frames may still be set
+       * to frames that needs to be forwarded to the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+       */
+      public Builder mergeResult(io.grpc.alts.internal.Handshaker.HandshakerResult value) {
+        if (resultBuilder_ == null) {
+          if (result_ != null) {
+            result_ =
+              io.grpc.alts.internal.Handshaker.HandshakerResult.newBuilder(result_).mergeFrom(value).buildPartial();
+          } else {
+            result_ = value;
+          }
+          onChanged();
+        } else {
+          resultBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * This is set iff the handshake was successful. out_frames may still be set
+       * to frames that needs to be forwarded to the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+       */
+      public Builder clearResult() {
+        if (resultBuilder_ == null) {
+          result_ = null;
+          onChanged();
+        } else {
+          result_ = null;
+          resultBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * This is set iff the handshake was successful. out_frames may still be set
+       * to frames that needs to be forwarded to the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.HandshakerResult.Builder getResultBuilder() {
+        
+        onChanged();
+        return getResultFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * This is set iff the handshake was successful. out_frames may still be set
+       * to frames that needs to be forwarded to the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.HandshakerResultOrBuilder getResultOrBuilder() {
+        if (resultBuilder_ != null) {
+          return resultBuilder_.getMessageOrBuilder();
+        } else {
+          return result_ == null ?
+              io.grpc.alts.internal.Handshaker.HandshakerResult.getDefaultInstance() : result_;
+        }
+      }
+      /**
+       * <pre>
+       * This is set iff the handshake was successful. out_frames may still be set
+       * to frames that needs to be forwarded to the peer.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerResult result = 3;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.HandshakerResult, io.grpc.alts.internal.Handshaker.HandshakerResult.Builder, io.grpc.alts.internal.Handshaker.HandshakerResultOrBuilder> 
+          getResultFieldBuilder() {
+        if (resultBuilder_ == null) {
+          resultBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.HandshakerResult, io.grpc.alts.internal.Handshaker.HandshakerResult.Builder, io.grpc.alts.internal.Handshaker.HandshakerResultOrBuilder>(
+                  getResult(),
+                  getParentForChildren(),
+                  isClean());
+          result_ = null;
+        }
+        return resultBuilder_;
+      }
+
+      private io.grpc.alts.internal.Handshaker.HandshakerStatus status_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.HandshakerStatus, io.grpc.alts.internal.Handshaker.HandshakerStatus.Builder, io.grpc.alts.internal.Handshaker.HandshakerStatusOrBuilder> statusBuilder_;
+      /**
+       * <pre>
+       * Status of the handshaker.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+       */
+      public boolean hasStatus() {
+        return statusBuilder_ != null || status_ != null;
+      }
+      /**
+       * <pre>
+       * Status of the handshaker.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.HandshakerStatus getStatus() {
+        if (statusBuilder_ == null) {
+          return status_ == null ? io.grpc.alts.internal.Handshaker.HandshakerStatus.getDefaultInstance() : status_;
+        } else {
+          return statusBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Status of the handshaker.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+       */
+      public Builder setStatus(io.grpc.alts.internal.Handshaker.HandshakerStatus value) {
+        if (statusBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          status_ = value;
+          onChanged();
+        } else {
+          statusBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Status of the handshaker.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+       */
+      public Builder setStatus(
+          io.grpc.alts.internal.Handshaker.HandshakerStatus.Builder builderForValue) {
+        if (statusBuilder_ == null) {
+          status_ = builderForValue.build();
+          onChanged();
+        } else {
+          statusBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Status of the handshaker.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+       */
+      public Builder mergeStatus(io.grpc.alts.internal.Handshaker.HandshakerStatus value) {
+        if (statusBuilder_ == null) {
+          if (status_ != null) {
+            status_ =
+              io.grpc.alts.internal.Handshaker.HandshakerStatus.newBuilder(status_).mergeFrom(value).buildPartial();
+          } else {
+            status_ = value;
+          }
+          onChanged();
+        } else {
+          statusBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Status of the handshaker.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+       */
+      public Builder clearStatus() {
+        if (statusBuilder_ == null) {
+          status_ = null;
+          onChanged();
+        } else {
+          status_ = null;
+          statusBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Status of the handshaker.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.HandshakerStatus.Builder getStatusBuilder() {
+        
+        onChanged();
+        return getStatusFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Status of the handshaker.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+       */
+      public io.grpc.alts.internal.Handshaker.HandshakerStatusOrBuilder getStatusOrBuilder() {
+        if (statusBuilder_ != null) {
+          return statusBuilder_.getMessageOrBuilder();
+        } else {
+          return status_ == null ?
+              io.grpc.alts.internal.Handshaker.HandshakerStatus.getDefaultInstance() : status_;
+        }
+      }
+      /**
+       * <pre>
+       * Status of the handshaker.
+       * </pre>
+       *
+       * <code>.grpc.gcp.HandshakerStatus status = 4;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.Handshaker.HandshakerStatus, io.grpc.alts.internal.Handshaker.HandshakerStatus.Builder, io.grpc.alts.internal.Handshaker.HandshakerStatusOrBuilder> 
+          getStatusFieldBuilder() {
+        if (statusBuilder_ == null) {
+          statusBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.Handshaker.HandshakerStatus, io.grpc.alts.internal.Handshaker.HandshakerStatus.Builder, io.grpc.alts.internal.Handshaker.HandshakerStatusOrBuilder>(
+                  getStatus(),
+                  getParentForChildren(),
+                  isClean());
+          status_ = null;
+        }
+        return statusBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.gcp.HandshakerResp)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.gcp.HandshakerResp)
+    private static final io.grpc.alts.internal.Handshaker.HandshakerResp DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.alts.internal.Handshaker.HandshakerResp();
+    }
+
+    public static io.grpc.alts.internal.Handshaker.HandshakerResp getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<HandshakerResp>
+        PARSER = new com.google.protobuf.AbstractParser<HandshakerResp>() {
+      public HandshakerResp parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new HandshakerResp(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<HandshakerResp> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<HandshakerResp> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.alts.internal.Handshaker.HandshakerResp getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_Endpoint_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_Endpoint_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_Identity_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_Identity_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_StartClientHandshakeReq_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_StartClientHandshakeReq_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_ServerHandshakeParameters_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_ServerHandshakeParameters_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_StartServerHandshakeReq_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_StartServerHandshakeReq_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_StartServerHandshakeReq_HandshakeParametersEntry_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_StartServerHandshakeReq_HandshakeParametersEntry_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_NextHandshakeMessageReq_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_NextHandshakeMessageReq_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_HandshakerReq_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_HandshakerReq_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_HandshakerResult_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_HandshakerResult_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_HandshakerStatus_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_HandshakerStatus_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_HandshakerResp_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_HandshakerResp_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\031grpc/gcp/handshaker.proto\022\010grpc.gcp\032(g" +
+      "rpc/gcp/transport_security_common.proto\"" +
+      "Y\n\010Endpoint\022\022\n\nip_address\030\001 \001(\t\022\014\n\004port\030" +
+      "\002 \001(\005\022+\n\010protocol\030\003 \001(\0162\031.grpc.gcp.Netwo" +
+      "rkProtocol\"K\n\010Identity\022\031\n\017service_accoun" +
+      "t\030\001 \001(\tH\000\022\022\n\010hostname\030\002 \001(\tH\000B\020\n\016identit" +
+      "y_oneof\"\222\003\n\027StartClientHandshakeReq\022@\n\033h" +
+      "andshake_security_protocol\030\001 \001(\0162\033.grpc." +
+      "gcp.HandshakeProtocol\022\035\n\025application_pro" +
+      "tocols\030\002 \003(\t\022\030\n\020record_protocols\030\003 \003(\t\022-" +
+      "\n\021target_identities\030\004 \003(\0132\022.grpc.gcp.Ide" +
+      "ntity\022*\n\016local_identity\030\005 \001(\0132\022.grpc.gcp" +
+      ".Identity\022*\n\016local_endpoint\030\006 \001(\0132\022.grpc" +
+      ".gcp.Endpoint\022+\n\017remote_endpoint\030\007 \001(\0132\022" +
+      ".grpc.gcp.Endpoint\022\023\n\013target_name\030\010 \001(\t\022" +
+      "3\n\014rpc_versions\030\t \001(\0132\035.grpc.gcp.RpcProt" +
+      "ocolVersions\"c\n\031ServerHandshakeParameter" +
+      "s\022\030\n\020record_protocols\030\001 \003(\t\022,\n\020local_ide" +
+      "ntities\030\002 \003(\0132\022.grpc.gcp.Identity\"\223\003\n\027St" +
+      "artServerHandshakeReq\022\035\n\025application_pro" +
+      "tocols\030\001 \003(\t\022X\n\024handshake_parameters\030\002 \003" +
+      "(\0132:.grpc.gcp.StartServerHandshakeReq.Ha" +
+      "ndshakeParametersEntry\022\020\n\010in_bytes\030\003 \001(\014" +
+      "\022*\n\016local_endpoint\030\004 \001(\0132\022.grpc.gcp.Endp" +
+      "oint\022+\n\017remote_endpoint\030\005 \001(\0132\022.grpc.gcp" +
+      ".Endpoint\0223\n\014rpc_versions\030\006 \001(\0132\035.grpc.g" +
+      "cp.RpcProtocolVersions\032_\n\030HandshakeParam" +
+      "etersEntry\022\013\n\003key\030\001 \001(\005\0222\n\005value\030\002 \001(\0132#" +
+      ".grpc.gcp.ServerHandshakeParameters:\0028\001\"" +
+      "+\n\027NextHandshakeMessageReq\022\020\n\010in_bytes\030\001" +
+      " \001(\014\"\305\001\n\rHandshakerReq\0229\n\014client_start\030\001" +
+      " \001(\0132!.grpc.gcp.StartClientHandshakeReqH" +
+      "\000\0229\n\014server_start\030\002 \001(\0132!.grpc.gcp.Start" +
+      "ServerHandshakeReqH\000\0221\n\004next\030\003 \001(\0132!.grp" +
+      "c.gcp.NextHandshakeMessageReqH\000B\013\n\treq_o" +
+      "neof\"\207\002\n\020HandshakerResult\022\034\n\024application" +
+      "_protocol\030\001 \001(\t\022\027\n\017record_protocol\030\002 \001(\t" +
+      "\022\020\n\010key_data\030\003 \001(\014\022)\n\rpeer_identity\030\004 \001(" +
+      "\0132\022.grpc.gcp.Identity\022*\n\016local_identity\030" +
+      "\005 \001(\0132\022.grpc.gcp.Identity\022\031\n\021keep_channe" +
+      "l_open\030\006 \001(\010\0228\n\021peer_rpc_versions\030\007 \001(\0132" +
+      "\035.grpc.gcp.RpcProtocolVersions\"1\n\020Handsh" +
+      "akerStatus\022\014\n\004code\030\001 \001(\r\022\017\n\007details\030\002 \001(" +
+      "\t\"\224\001\n\016HandshakerResp\022\022\n\nout_frames\030\001 \001(\014" +
+      "\022\026\n\016bytes_consumed\030\002 \001(\r\022*\n\006result\030\003 \001(\013" +
+      "2\032.grpc.gcp.HandshakerResult\022*\n\006status\030\004" +
+      " \001(\0132\032.grpc.gcp.HandshakerStatus*J\n\021Hand" +
+      "shakeProtocol\022\"\n\036HANDSHAKE_PROTOCOL_UNSP" +
+      "ECIFIED\020\000\022\007\n\003TLS\020\001\022\010\n\004ALTS\020\002*E\n\017NetworkP" +
+      "rotocol\022 \n\034NETWORK_PROTOCOL_UNSPECIFIED\020" +
+      "\000\022\007\n\003TCP\020\001\022\007\n\003UDP\020\0022[\n\021HandshakerService" +
+      "\022F\n\013DoHandshake\022\027.grpc.gcp.HandshakerReq" +
+      "\032\030.grpc.gcp.HandshakerResp\"\000(\0010\001B\027\n\025io.g" +
+      "rpc.alts.internalb\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          io.grpc.alts.internal.TransportSecurityCommon.getDescriptor(),
+        }, assigner);
+    internal_static_grpc_gcp_Endpoint_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_gcp_Endpoint_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_Endpoint_descriptor,
+        new java.lang.String[] { "IpAddress", "Port", "Protocol", });
+    internal_static_grpc_gcp_Identity_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_gcp_Identity_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_Identity_descriptor,
+        new java.lang.String[] { "ServiceAccount", "Hostname", "IdentityOneof", });
+    internal_static_grpc_gcp_StartClientHandshakeReq_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_gcp_StartClientHandshakeReq_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_StartClientHandshakeReq_descriptor,
+        new java.lang.String[] { "HandshakeSecurityProtocol", "ApplicationProtocols", "RecordProtocols", "TargetIdentities", "LocalIdentity", "LocalEndpoint", "RemoteEndpoint", "TargetName", "RpcVersions", });
+    internal_static_grpc_gcp_ServerHandshakeParameters_descriptor =
+      getDescriptor().getMessageTypes().get(3);
+    internal_static_grpc_gcp_ServerHandshakeParameters_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_ServerHandshakeParameters_descriptor,
+        new java.lang.String[] { "RecordProtocols", "LocalIdentities", });
+    internal_static_grpc_gcp_StartServerHandshakeReq_descriptor =
+      getDescriptor().getMessageTypes().get(4);
+    internal_static_grpc_gcp_StartServerHandshakeReq_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_StartServerHandshakeReq_descriptor,
+        new java.lang.String[] { "ApplicationProtocols", "HandshakeParameters", "InBytes", "LocalEndpoint", "RemoteEndpoint", "RpcVersions", });
+    internal_static_grpc_gcp_StartServerHandshakeReq_HandshakeParametersEntry_descriptor =
+      internal_static_grpc_gcp_StartServerHandshakeReq_descriptor.getNestedTypes().get(0);
+    internal_static_grpc_gcp_StartServerHandshakeReq_HandshakeParametersEntry_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_StartServerHandshakeReq_HandshakeParametersEntry_descriptor,
+        new java.lang.String[] { "Key", "Value", });
+    internal_static_grpc_gcp_NextHandshakeMessageReq_descriptor =
+      getDescriptor().getMessageTypes().get(5);
+    internal_static_grpc_gcp_NextHandshakeMessageReq_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_NextHandshakeMessageReq_descriptor,
+        new java.lang.String[] { "InBytes", });
+    internal_static_grpc_gcp_HandshakerReq_descriptor =
+      getDescriptor().getMessageTypes().get(6);
+    internal_static_grpc_gcp_HandshakerReq_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_HandshakerReq_descriptor,
+        new java.lang.String[] { "ClientStart", "ServerStart", "Next", "ReqOneof", });
+    internal_static_grpc_gcp_HandshakerResult_descriptor =
+      getDescriptor().getMessageTypes().get(7);
+    internal_static_grpc_gcp_HandshakerResult_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_HandshakerResult_descriptor,
+        new java.lang.String[] { "ApplicationProtocol", "RecordProtocol", "KeyData", "PeerIdentity", "LocalIdentity", "KeepChannelOpen", "PeerRpcVersions", });
+    internal_static_grpc_gcp_HandshakerStatus_descriptor =
+      getDescriptor().getMessageTypes().get(8);
+    internal_static_grpc_gcp_HandshakerStatus_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_HandshakerStatus_descriptor,
+        new java.lang.String[] { "Code", "Details", });
+    internal_static_grpc_gcp_HandshakerResp_descriptor =
+      getDescriptor().getMessageTypes().get(9);
+    internal_static_grpc_gcp_HandshakerResp_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_HandshakerResp_descriptor,
+        new java.lang.String[] { "OutFrames", "BytesConsumed", "Result", "Status", });
+    io.grpc.alts.internal.TransportSecurityCommon.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/alts/src/generated/main/java/io/grpc/alts/internal/TransportSecurityCommon.java b/alts/src/generated/main/java/io/grpc/alts/internal/TransportSecurityCommon.java
new file mode 100644
index 0000000..beb3839
--- /dev/null
+++ b/alts/src/generated/main/java/io/grpc/alts/internal/TransportSecurityCommon.java
@@ -0,0 +1,1612 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/gcp/transport_security_common.proto
+
+package io.grpc.alts.internal;
+
+public final class TransportSecurityCommon {
+  private TransportSecurityCommon() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  /**
+   * <pre>
+   * The security level of the created channel. The list is sorted in increasing
+   * level of security. This order must always be maintained.
+   * </pre>
+   *
+   * Protobuf enum {@code grpc.gcp.SecurityLevel}
+   */
+  public enum SecurityLevel
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>SECURITY_NONE = 0;</code>
+     */
+    SECURITY_NONE(0),
+    /**
+     * <code>INTEGRITY_ONLY = 1;</code>
+     */
+    INTEGRITY_ONLY(1),
+    /**
+     * <code>INTEGRITY_AND_PRIVACY = 2;</code>
+     */
+    INTEGRITY_AND_PRIVACY(2),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <code>SECURITY_NONE = 0;</code>
+     */
+    public static final int SECURITY_NONE_VALUE = 0;
+    /**
+     * <code>INTEGRITY_ONLY = 1;</code>
+     */
+    public static final int INTEGRITY_ONLY_VALUE = 1;
+    /**
+     * <code>INTEGRITY_AND_PRIVACY = 2;</code>
+     */
+    public static final int INTEGRITY_AND_PRIVACY_VALUE = 2;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static SecurityLevel valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static SecurityLevel forNumber(int value) {
+      switch (value) {
+        case 0: return SECURITY_NONE;
+        case 1: return INTEGRITY_ONLY;
+        case 2: return INTEGRITY_AND_PRIVACY;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<SecurityLevel>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        SecurityLevel> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<SecurityLevel>() {
+            public SecurityLevel findValueByNumber(int number) {
+              return SecurityLevel.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.TransportSecurityCommon.getDescriptor().getEnumTypes().get(0);
+    }
+
+    private static final SecurityLevel[] VALUES = values();
+
+    public static SecurityLevel valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private SecurityLevel(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.gcp.SecurityLevel)
+  }
+
+  public interface RpcProtocolVersionsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.gcp.RpcProtocolVersions)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Maximum supported RPC version.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+     */
+    boolean hasMaxRpcVersion();
+    /**
+     * <pre>
+     * Maximum supported RPC version.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+     */
+    io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version getMaxRpcVersion();
+    /**
+     * <pre>
+     * Maximum supported RPC version.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+     */
+    io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.VersionOrBuilder getMaxRpcVersionOrBuilder();
+
+    /**
+     * <pre>
+     * Minimum supported RPC version.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+     */
+    boolean hasMinRpcVersion();
+    /**
+     * <pre>
+     * Minimum supported RPC version.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+     */
+    io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version getMinRpcVersion();
+    /**
+     * <pre>
+     * Minimum supported RPC version.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+     */
+    io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.VersionOrBuilder getMinRpcVersionOrBuilder();
+  }
+  /**
+   * <pre>
+   * Max and min supported RPC protocol versions.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.gcp.RpcProtocolVersions}
+   */
+  public  static final class RpcProtocolVersions extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.gcp.RpcProtocolVersions)
+      RpcProtocolVersionsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use RpcProtocolVersions.newBuilder() to construct.
+    private RpcProtocolVersions(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private RpcProtocolVersions() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private RpcProtocolVersions(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder subBuilder = null;
+              if (maxRpcVersion_ != null) {
+                subBuilder = maxRpcVersion_.toBuilder();
+              }
+              maxRpcVersion_ = input.readMessage(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(maxRpcVersion_);
+                maxRpcVersion_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 18: {
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder subBuilder = null;
+              if (minRpcVersion_ != null) {
+                subBuilder = minRpcVersion_.toBuilder();
+              }
+              minRpcVersion_ = input.readMessage(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(minRpcVersion_);
+                minRpcVersion_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.alts.internal.TransportSecurityCommon.internal_static_grpc_gcp_RpcProtocolVersions_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.alts.internal.TransportSecurityCommon.internal_static_grpc_gcp_RpcProtocolVersions_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.class, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder.class);
+    }
+
+    public interface VersionOrBuilder extends
+        // @@protoc_insertion_point(interface_extends:grpc.gcp.RpcProtocolVersions.Version)
+        com.google.protobuf.MessageOrBuilder {
+
+      /**
+       * <code>uint32 major = 1;</code>
+       */
+      int getMajor();
+
+      /**
+       * <code>uint32 minor = 2;</code>
+       */
+      int getMinor();
+    }
+    /**
+     * <pre>
+     * RPC version contains a major version and a minor version.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.gcp.RpcProtocolVersions.Version}
+     */
+    public  static final class Version extends
+        com.google.protobuf.GeneratedMessageV3 implements
+        // @@protoc_insertion_point(message_implements:grpc.gcp.RpcProtocolVersions.Version)
+        VersionOrBuilder {
+    private static final long serialVersionUID = 0L;
+      // Use Version.newBuilder() to construct.
+      private Version(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+        super(builder);
+      }
+      private Version() {
+        major_ = 0;
+        minor_ = 0;
+      }
+
+      @java.lang.Override
+      public final com.google.protobuf.UnknownFieldSet
+      getUnknownFields() {
+        return this.unknownFields;
+      }
+      private Version(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        this();
+        if (extensionRegistry == null) {
+          throw new java.lang.NullPointerException();
+        }
+        int mutable_bitField0_ = 0;
+        com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+            com.google.protobuf.UnknownFieldSet.newBuilder();
+        try {
+          boolean done = false;
+          while (!done) {
+            int tag = input.readTag();
+            switch (tag) {
+              case 0:
+                done = true;
+                break;
+              default: {
+                if (!parseUnknownFieldProto3(
+                    input, unknownFields, extensionRegistry, tag)) {
+                  done = true;
+                }
+                break;
+              }
+              case 8: {
+
+                major_ = input.readUInt32();
+                break;
+              }
+              case 16: {
+
+                minor_ = input.readUInt32();
+                break;
+              }
+            }
+          }
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          throw e.setUnfinishedMessage(this);
+        } catch (java.io.IOException e) {
+          throw new com.google.protobuf.InvalidProtocolBufferException(
+              e).setUnfinishedMessage(this);
+        } finally {
+          this.unknownFields = unknownFields.build();
+          makeExtensionsImmutable();
+        }
+      }
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.alts.internal.TransportSecurityCommon.internal_static_grpc_gcp_RpcProtocolVersions_Version_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.alts.internal.TransportSecurityCommon.internal_static_grpc_gcp_RpcProtocolVersions_Version_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.class, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder.class);
+      }
+
+      public static final int MAJOR_FIELD_NUMBER = 1;
+      private int major_;
+      /**
+       * <code>uint32 major = 1;</code>
+       */
+      public int getMajor() {
+        return major_;
+      }
+
+      public static final int MINOR_FIELD_NUMBER = 2;
+      private int minor_;
+      /**
+       * <code>uint32 minor = 2;</code>
+       */
+      public int getMinor() {
+        return minor_;
+      }
+
+      private byte memoizedIsInitialized = -1;
+      public final boolean isInitialized() {
+        byte isInitialized = memoizedIsInitialized;
+        if (isInitialized == 1) return true;
+        if (isInitialized == 0) return false;
+
+        memoizedIsInitialized = 1;
+        return true;
+      }
+
+      public void writeTo(com.google.protobuf.CodedOutputStream output)
+                          throws java.io.IOException {
+        if (major_ != 0) {
+          output.writeUInt32(1, major_);
+        }
+        if (minor_ != 0) {
+          output.writeUInt32(2, minor_);
+        }
+        unknownFields.writeTo(output);
+      }
+
+      public int getSerializedSize() {
+        int size = memoizedSize;
+        if (size != -1) return size;
+
+        size = 0;
+        if (major_ != 0) {
+          size += com.google.protobuf.CodedOutputStream
+            .computeUInt32Size(1, major_);
+        }
+        if (minor_ != 0) {
+          size += com.google.protobuf.CodedOutputStream
+            .computeUInt32Size(2, minor_);
+        }
+        size += unknownFields.getSerializedSize();
+        memoizedSize = size;
+        return size;
+      }
+
+      @java.lang.Override
+      public boolean equals(final java.lang.Object obj) {
+        if (obj == this) {
+         return true;
+        }
+        if (!(obj instanceof io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version)) {
+          return super.equals(obj);
+        }
+        io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version other = (io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version) obj;
+
+        boolean result = true;
+        result = result && (getMajor()
+            == other.getMajor());
+        result = result && (getMinor()
+            == other.getMinor());
+        result = result && unknownFields.equals(other.unknownFields);
+        return result;
+      }
+
+      @java.lang.Override
+      public int hashCode() {
+        if (memoizedHashCode != 0) {
+          return memoizedHashCode;
+        }
+        int hash = 41;
+        hash = (19 * hash) + getDescriptor().hashCode();
+        hash = (37 * hash) + MAJOR_FIELD_NUMBER;
+        hash = (53 * hash) + getMajor();
+        hash = (37 * hash) + MINOR_FIELD_NUMBER;
+        hash = (53 * hash) + getMinor();
+        hash = (29 * hash) + unknownFields.hashCode();
+        memoizedHashCode = hash;
+        return hash;
+      }
+
+      public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version parseFrom(
+          java.nio.ByteBuffer data)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data);
+      }
+      public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version parseFrom(
+          java.nio.ByteBuffer data,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data, extensionRegistry);
+      }
+      public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version parseFrom(
+          com.google.protobuf.ByteString data)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data);
+      }
+      public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version parseFrom(
+          com.google.protobuf.ByteString data,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data, extensionRegistry);
+      }
+      public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version parseFrom(byte[] data)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data);
+      }
+      public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version parseFrom(
+          byte[] data,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data, extensionRegistry);
+      }
+      public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version parseFrom(java.io.InputStream input)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseWithIOException(PARSER, input);
+      }
+      public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version parseFrom(
+          java.io.InputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseWithIOException(PARSER, input, extensionRegistry);
+      }
+      public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version parseDelimitedFrom(java.io.InputStream input)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseDelimitedWithIOException(PARSER, input);
+      }
+      public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version parseDelimitedFrom(
+          java.io.InputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+      }
+      public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version parseFrom(
+          com.google.protobuf.CodedInputStream input)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseWithIOException(PARSER, input);
+      }
+      public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version parseFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseWithIOException(PARSER, input, extensionRegistry);
+      }
+
+      public Builder newBuilderForType() { return newBuilder(); }
+      public static Builder newBuilder() {
+        return DEFAULT_INSTANCE.toBuilder();
+      }
+      public static Builder newBuilder(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version prototype) {
+        return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+      }
+      public Builder toBuilder() {
+        return this == DEFAULT_INSTANCE
+            ? new Builder() : new Builder().mergeFrom(this);
+      }
+
+      @java.lang.Override
+      protected Builder newBuilderForType(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        Builder builder = new Builder(parent);
+        return builder;
+      }
+      /**
+       * <pre>
+       * RPC version contains a major version and a minor version.
+       * </pre>
+       *
+       * Protobuf type {@code grpc.gcp.RpcProtocolVersions.Version}
+       */
+      public static final class Builder extends
+          com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+          // @@protoc_insertion_point(builder_implements:grpc.gcp.RpcProtocolVersions.Version)
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.VersionOrBuilder {
+        public static final com.google.protobuf.Descriptors.Descriptor
+            getDescriptor() {
+          return io.grpc.alts.internal.TransportSecurityCommon.internal_static_grpc_gcp_RpcProtocolVersions_Version_descriptor;
+        }
+
+        protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+            internalGetFieldAccessorTable() {
+          return io.grpc.alts.internal.TransportSecurityCommon.internal_static_grpc_gcp_RpcProtocolVersions_Version_fieldAccessorTable
+              .ensureFieldAccessorsInitialized(
+                  io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.class, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder.class);
+        }
+
+        // Construct using io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.newBuilder()
+        private Builder() {
+          maybeForceBuilderInitialization();
+        }
+
+        private Builder(
+            com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+          super(parent);
+          maybeForceBuilderInitialization();
+        }
+        private void maybeForceBuilderInitialization() {
+          if (com.google.protobuf.GeneratedMessageV3
+                  .alwaysUseFieldBuilders) {
+          }
+        }
+        public Builder clear() {
+          super.clear();
+          major_ = 0;
+
+          minor_ = 0;
+
+          return this;
+        }
+
+        public com.google.protobuf.Descriptors.Descriptor
+            getDescriptorForType() {
+          return io.grpc.alts.internal.TransportSecurityCommon.internal_static_grpc_gcp_RpcProtocolVersions_Version_descriptor;
+        }
+
+        public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version getDefaultInstanceForType() {
+          return io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.getDefaultInstance();
+        }
+
+        public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version build() {
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version result = buildPartial();
+          if (!result.isInitialized()) {
+            throw newUninitializedMessageException(result);
+          }
+          return result;
+        }
+
+        public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version buildPartial() {
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version result = new io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version(this);
+          result.major_ = major_;
+          result.minor_ = minor_;
+          onBuilt();
+          return result;
+        }
+
+        public Builder clone() {
+          return (Builder) super.clone();
+        }
+        public Builder setField(
+            com.google.protobuf.Descriptors.FieldDescriptor field,
+            java.lang.Object value) {
+          return (Builder) super.setField(field, value);
+        }
+        public Builder clearField(
+            com.google.protobuf.Descriptors.FieldDescriptor field) {
+          return (Builder) super.clearField(field);
+        }
+        public Builder clearOneof(
+            com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+          return (Builder) super.clearOneof(oneof);
+        }
+        public Builder setRepeatedField(
+            com.google.protobuf.Descriptors.FieldDescriptor field,
+            int index, java.lang.Object value) {
+          return (Builder) super.setRepeatedField(field, index, value);
+        }
+        public Builder addRepeatedField(
+            com.google.protobuf.Descriptors.FieldDescriptor field,
+            java.lang.Object value) {
+          return (Builder) super.addRepeatedField(field, value);
+        }
+        public Builder mergeFrom(com.google.protobuf.Message other) {
+          if (other instanceof io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version) {
+            return mergeFrom((io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version)other);
+          } else {
+            super.mergeFrom(other);
+            return this;
+          }
+        }
+
+        public Builder mergeFrom(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version other) {
+          if (other == io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.getDefaultInstance()) return this;
+          if (other.getMajor() != 0) {
+            setMajor(other.getMajor());
+          }
+          if (other.getMinor() != 0) {
+            setMinor(other.getMinor());
+          }
+          this.mergeUnknownFields(other.unknownFields);
+          onChanged();
+          return this;
+        }
+
+        public final boolean isInitialized() {
+          return true;
+        }
+
+        public Builder mergeFrom(
+            com.google.protobuf.CodedInputStream input,
+            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+            throws java.io.IOException {
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version parsedMessage = null;
+          try {
+            parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+          } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            parsedMessage = (io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version) e.getUnfinishedMessage();
+            throw e.unwrapIOException();
+          } finally {
+            if (parsedMessage != null) {
+              mergeFrom(parsedMessage);
+            }
+          }
+          return this;
+        }
+
+        private int major_ ;
+        /**
+         * <code>uint32 major = 1;</code>
+         */
+        public int getMajor() {
+          return major_;
+        }
+        /**
+         * <code>uint32 major = 1;</code>
+         */
+        public Builder setMajor(int value) {
+          
+          major_ = value;
+          onChanged();
+          return this;
+        }
+        /**
+         * <code>uint32 major = 1;</code>
+         */
+        public Builder clearMajor() {
+          
+          major_ = 0;
+          onChanged();
+          return this;
+        }
+
+        private int minor_ ;
+        /**
+         * <code>uint32 minor = 2;</code>
+         */
+        public int getMinor() {
+          return minor_;
+        }
+        /**
+         * <code>uint32 minor = 2;</code>
+         */
+        public Builder setMinor(int value) {
+          
+          minor_ = value;
+          onChanged();
+          return this;
+        }
+        /**
+         * <code>uint32 minor = 2;</code>
+         */
+        public Builder clearMinor() {
+          
+          minor_ = 0;
+          onChanged();
+          return this;
+        }
+        public final Builder setUnknownFields(
+            final com.google.protobuf.UnknownFieldSet unknownFields) {
+          return super.setUnknownFieldsProto3(unknownFields);
+        }
+
+        public final Builder mergeUnknownFields(
+            final com.google.protobuf.UnknownFieldSet unknownFields) {
+          return super.mergeUnknownFields(unknownFields);
+        }
+
+
+        // @@protoc_insertion_point(builder_scope:grpc.gcp.RpcProtocolVersions.Version)
+      }
+
+      // @@protoc_insertion_point(class_scope:grpc.gcp.RpcProtocolVersions.Version)
+      private static final io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version DEFAULT_INSTANCE;
+      static {
+        DEFAULT_INSTANCE = new io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version();
+      }
+
+      public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version getDefaultInstance() {
+        return DEFAULT_INSTANCE;
+      }
+
+      private static final com.google.protobuf.Parser<Version>
+          PARSER = new com.google.protobuf.AbstractParser<Version>() {
+        public Version parsePartialFrom(
+            com.google.protobuf.CodedInputStream input,
+            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+            throws com.google.protobuf.InvalidProtocolBufferException {
+          return new Version(input, extensionRegistry);
+        }
+      };
+
+      public static com.google.protobuf.Parser<Version> parser() {
+        return PARSER;
+      }
+
+      @java.lang.Override
+      public com.google.protobuf.Parser<Version> getParserForType() {
+        return PARSER;
+      }
+
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version getDefaultInstanceForType() {
+        return DEFAULT_INSTANCE;
+      }
+
+    }
+
+    public static final int MAX_RPC_VERSION_FIELD_NUMBER = 1;
+    private io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version maxRpcVersion_;
+    /**
+     * <pre>
+     * Maximum supported RPC version.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+     */
+    public boolean hasMaxRpcVersion() {
+      return maxRpcVersion_ != null;
+    }
+    /**
+     * <pre>
+     * Maximum supported RPC version.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+     */
+    public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version getMaxRpcVersion() {
+      return maxRpcVersion_ == null ? io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.getDefaultInstance() : maxRpcVersion_;
+    }
+    /**
+     * <pre>
+     * Maximum supported RPC version.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+     */
+    public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.VersionOrBuilder getMaxRpcVersionOrBuilder() {
+      return getMaxRpcVersion();
+    }
+
+    public static final int MIN_RPC_VERSION_FIELD_NUMBER = 2;
+    private io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version minRpcVersion_;
+    /**
+     * <pre>
+     * Minimum supported RPC version.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+     */
+    public boolean hasMinRpcVersion() {
+      return minRpcVersion_ != null;
+    }
+    /**
+     * <pre>
+     * Minimum supported RPC version.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+     */
+    public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version getMinRpcVersion() {
+      return minRpcVersion_ == null ? io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.getDefaultInstance() : minRpcVersion_;
+    }
+    /**
+     * <pre>
+     * Minimum supported RPC version.
+     * </pre>
+     *
+     * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+     */
+    public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.VersionOrBuilder getMinRpcVersionOrBuilder() {
+      return getMinRpcVersion();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (maxRpcVersion_ != null) {
+        output.writeMessage(1, getMaxRpcVersion());
+      }
+      if (minRpcVersion_ != null) {
+        output.writeMessage(2, getMinRpcVersion());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (maxRpcVersion_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, getMaxRpcVersion());
+      }
+      if (minRpcVersion_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, getMinRpcVersion());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions)) {
+        return super.equals(obj);
+      }
+      io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions other = (io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions) obj;
+
+      boolean result = true;
+      result = result && (hasMaxRpcVersion() == other.hasMaxRpcVersion());
+      if (hasMaxRpcVersion()) {
+        result = result && getMaxRpcVersion()
+            .equals(other.getMaxRpcVersion());
+      }
+      result = result && (hasMinRpcVersion() == other.hasMinRpcVersion());
+      if (hasMinRpcVersion()) {
+        result = result && getMinRpcVersion()
+            .equals(other.getMinRpcVersion());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (hasMaxRpcVersion()) {
+        hash = (37 * hash) + MAX_RPC_VERSION_FIELD_NUMBER;
+        hash = (53 * hash) + getMaxRpcVersion().hashCode();
+      }
+      if (hasMinRpcVersion()) {
+        hash = (37 * hash) + MIN_RPC_VERSION_FIELD_NUMBER;
+        hash = (53 * hash) + getMinRpcVersion().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Max and min supported RPC protocol versions.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.gcp.RpcProtocolVersions}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.gcp.RpcProtocolVersions)
+        io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersionsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.alts.internal.TransportSecurityCommon.internal_static_grpc_gcp_RpcProtocolVersions_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.alts.internal.TransportSecurityCommon.internal_static_grpc_gcp_RpcProtocolVersions_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.class, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Builder.class);
+      }
+
+      // Construct using io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        if (maxRpcVersionBuilder_ == null) {
+          maxRpcVersion_ = null;
+        } else {
+          maxRpcVersion_ = null;
+          maxRpcVersionBuilder_ = null;
+        }
+        if (minRpcVersionBuilder_ == null) {
+          minRpcVersion_ = null;
+        } else {
+          minRpcVersion_ = null;
+          minRpcVersionBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.alts.internal.TransportSecurityCommon.internal_static_grpc_gcp_RpcProtocolVersions_descriptor;
+      }
+
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getDefaultInstanceForType() {
+        return io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance();
+      }
+
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions build() {
+        io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions buildPartial() {
+        io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions result = new io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions(this);
+        if (maxRpcVersionBuilder_ == null) {
+          result.maxRpcVersion_ = maxRpcVersion_;
+        } else {
+          result.maxRpcVersion_ = maxRpcVersionBuilder_.build();
+        }
+        if (minRpcVersionBuilder_ == null) {
+          result.minRpcVersion_ = minRpcVersion_;
+        } else {
+          result.minRpcVersion_ = minRpcVersionBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions) {
+          return mergeFrom((io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions other) {
+        if (other == io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.getDefaultInstance()) return this;
+        if (other.hasMaxRpcVersion()) {
+          mergeMaxRpcVersion(other.getMaxRpcVersion());
+        }
+        if (other.hasMinRpcVersion()) {
+          mergeMinRpcVersion(other.getMinRpcVersion());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version maxRpcVersion_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.VersionOrBuilder> maxRpcVersionBuilder_;
+      /**
+       * <pre>
+       * Maximum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+       */
+      public boolean hasMaxRpcVersion() {
+        return maxRpcVersionBuilder_ != null || maxRpcVersion_ != null;
+      }
+      /**
+       * <pre>
+       * Maximum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version getMaxRpcVersion() {
+        if (maxRpcVersionBuilder_ == null) {
+          return maxRpcVersion_ == null ? io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.getDefaultInstance() : maxRpcVersion_;
+        } else {
+          return maxRpcVersionBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Maximum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+       */
+      public Builder setMaxRpcVersion(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version value) {
+        if (maxRpcVersionBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          maxRpcVersion_ = value;
+          onChanged();
+        } else {
+          maxRpcVersionBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Maximum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+       */
+      public Builder setMaxRpcVersion(
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder builderForValue) {
+        if (maxRpcVersionBuilder_ == null) {
+          maxRpcVersion_ = builderForValue.build();
+          onChanged();
+        } else {
+          maxRpcVersionBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Maximum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+       */
+      public Builder mergeMaxRpcVersion(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version value) {
+        if (maxRpcVersionBuilder_ == null) {
+          if (maxRpcVersion_ != null) {
+            maxRpcVersion_ =
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.newBuilder(maxRpcVersion_).mergeFrom(value).buildPartial();
+          } else {
+            maxRpcVersion_ = value;
+          }
+          onChanged();
+        } else {
+          maxRpcVersionBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Maximum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+       */
+      public Builder clearMaxRpcVersion() {
+        if (maxRpcVersionBuilder_ == null) {
+          maxRpcVersion_ = null;
+          onChanged();
+        } else {
+          maxRpcVersion_ = null;
+          maxRpcVersionBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Maximum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder getMaxRpcVersionBuilder() {
+        
+        onChanged();
+        return getMaxRpcVersionFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Maximum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.VersionOrBuilder getMaxRpcVersionOrBuilder() {
+        if (maxRpcVersionBuilder_ != null) {
+          return maxRpcVersionBuilder_.getMessageOrBuilder();
+        } else {
+          return maxRpcVersion_ == null ?
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.getDefaultInstance() : maxRpcVersion_;
+        }
+      }
+      /**
+       * <pre>
+       * Maximum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version max_rpc_version = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.VersionOrBuilder> 
+          getMaxRpcVersionFieldBuilder() {
+        if (maxRpcVersionBuilder_ == null) {
+          maxRpcVersionBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.VersionOrBuilder>(
+                  getMaxRpcVersion(),
+                  getParentForChildren(),
+                  isClean());
+          maxRpcVersion_ = null;
+        }
+        return maxRpcVersionBuilder_;
+      }
+
+      private io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version minRpcVersion_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.VersionOrBuilder> minRpcVersionBuilder_;
+      /**
+       * <pre>
+       * Minimum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+       */
+      public boolean hasMinRpcVersion() {
+        return minRpcVersionBuilder_ != null || minRpcVersion_ != null;
+      }
+      /**
+       * <pre>
+       * Minimum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version getMinRpcVersion() {
+        if (minRpcVersionBuilder_ == null) {
+          return minRpcVersion_ == null ? io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.getDefaultInstance() : minRpcVersion_;
+        } else {
+          return minRpcVersionBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Minimum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+       */
+      public Builder setMinRpcVersion(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version value) {
+        if (minRpcVersionBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          minRpcVersion_ = value;
+          onChanged();
+        } else {
+          minRpcVersionBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Minimum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+       */
+      public Builder setMinRpcVersion(
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder builderForValue) {
+        if (minRpcVersionBuilder_ == null) {
+          minRpcVersion_ = builderForValue.build();
+          onChanged();
+        } else {
+          minRpcVersionBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Minimum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+       */
+      public Builder mergeMinRpcVersion(io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version value) {
+        if (minRpcVersionBuilder_ == null) {
+          if (minRpcVersion_ != null) {
+            minRpcVersion_ =
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.newBuilder(minRpcVersion_).mergeFrom(value).buildPartial();
+          } else {
+            minRpcVersion_ = value;
+          }
+          onChanged();
+        } else {
+          minRpcVersionBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Minimum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+       */
+      public Builder clearMinRpcVersion() {
+        if (minRpcVersionBuilder_ == null) {
+          minRpcVersion_ = null;
+          onChanged();
+        } else {
+          minRpcVersion_ = null;
+          minRpcVersionBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Minimum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder getMinRpcVersionBuilder() {
+        
+        onChanged();
+        return getMinRpcVersionFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Minimum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+       */
+      public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.VersionOrBuilder getMinRpcVersionOrBuilder() {
+        if (minRpcVersionBuilder_ != null) {
+          return minRpcVersionBuilder_.getMessageOrBuilder();
+        } else {
+          return minRpcVersion_ == null ?
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.getDefaultInstance() : minRpcVersion_;
+        }
+      }
+      /**
+       * <pre>
+       * Minimum supported RPC version.
+       * </pre>
+       *
+       * <code>.grpc.gcp.RpcProtocolVersions.Version min_rpc_version = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.VersionOrBuilder> 
+          getMinRpcVersionFieldBuilder() {
+        if (minRpcVersionBuilder_ == null) {
+          minRpcVersionBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version.Builder, io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.VersionOrBuilder>(
+                  getMinRpcVersion(),
+                  getParentForChildren(),
+                  isClean());
+          minRpcVersion_ = null;
+        }
+        return minRpcVersionBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.gcp.RpcProtocolVersions)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.gcp.RpcProtocolVersions)
+    private static final io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions();
+    }
+
+    public static io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<RpcProtocolVersions>
+        PARSER = new com.google.protobuf.AbstractParser<RpcProtocolVersions>() {
+      public RpcProtocolVersions parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new RpcProtocolVersions(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<RpcProtocolVersions> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<RpcProtocolVersions> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_RpcProtocolVersions_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_RpcProtocolVersions_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_gcp_RpcProtocolVersions_Version_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_gcp_RpcProtocolVersions_Version_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n(grpc/gcp/transport_security_common.pro" +
+      "to\022\010grpc.gcp\"\276\001\n\023RpcProtocolVersions\022>\n\017" +
+      "max_rpc_version\030\001 \001(\0132%.grpc.gcp.RpcProt" +
+      "ocolVersions.Version\022>\n\017min_rpc_version\030" +
+      "\002 \001(\0132%.grpc.gcp.RpcProtocolVersions.Ver" +
+      "sion\032\'\n\007Version\022\r\n\005major\030\001 \001(\r\022\r\n\005minor\030" +
+      "\002 \001(\r*Q\n\rSecurityLevel\022\021\n\rSECURITY_NONE\020" +
+      "\000\022\022\n\016INTEGRITY_ONLY\020\001\022\031\n\025INTEGRITY_AND_P" +
+      "RIVACY\020\002B\027\n\025io.grpc.alts.internalb\006proto" +
+      "3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        }, assigner);
+    internal_static_grpc_gcp_RpcProtocolVersions_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_gcp_RpcProtocolVersions_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_RpcProtocolVersions_descriptor,
+        new java.lang.String[] { "MaxRpcVersion", "MinRpcVersion", });
+    internal_static_grpc_gcp_RpcProtocolVersions_Version_descriptor =
+      internal_static_grpc_gcp_RpcProtocolVersions_descriptor.getNestedTypes().get(0);
+    internal_static_grpc_gcp_RpcProtocolVersions_Version_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_gcp_RpcProtocolVersions_Version_descriptor,
+        new java.lang.String[] { "Major", "Minor", });
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/alts/src/main/java/io/grpc/alts/AltsChannelBuilder.java b/alts/src/main/java/io/grpc/alts/AltsChannelBuilder.java
new file mode 100644
index 0000000..ce24861
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/AltsChannelBuilder.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ExperimentalApi;
+import io.grpc.ForwardingChannelBuilder;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.alts.internal.AltsClientOptions;
+import io.grpc.alts.internal.AltsProtocolNegotiator;
+import io.grpc.alts.internal.AltsTsiHandshaker;
+import io.grpc.alts.internal.HandshakerServiceGrpc;
+import io.grpc.alts.internal.RpcProtocolVersionsUtil;
+import io.grpc.alts.internal.TsiHandshaker;
+import io.grpc.alts.internal.TsiHandshakerFactory;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.ObjectPool;
+import io.grpc.internal.ProxyParameters;
+import io.grpc.internal.SharedResourcePool;
+import io.grpc.netty.InternalNettyChannelBuilder;
+import io.grpc.netty.InternalNettyChannelBuilder.TransportCreationParamsFilter;
+import io.grpc.netty.InternalNettyChannelBuilder.TransportCreationParamsFilterFactory;
+import io.grpc.netty.NettyChannelBuilder;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * ALTS version of {@code ManagedChannelBuilder}. This class sets up a secure and authenticated
+ * commmunication between two cloud VMs using ALTS.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4151")
+public final class AltsChannelBuilder extends ForwardingChannelBuilder<AltsChannelBuilder> {
+
+  private static final Logger logger = Logger.getLogger(AltsChannelBuilder.class.getName());
+  private final NettyChannelBuilder delegate;
+  private final AltsClientOptions.Builder handshakerOptionsBuilder =
+      new AltsClientOptions.Builder();
+  private ObjectPool<ManagedChannel> handshakerChannelPool =
+      SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL);
+  private TcpfFactory tcpfFactoryForTest;
+  private boolean enableUntrustedAlts;
+
+  /** "Overrides" the static method in {@link ManagedChannelBuilder}. */
+  public static final AltsChannelBuilder forTarget(String target) {
+    return new AltsChannelBuilder(target);
+  }
+
+  /** "Overrides" the static method in {@link ManagedChannelBuilder}. */
+  public static AltsChannelBuilder forAddress(String name, int port) {
+    return forTarget(GrpcUtil.authorityFromHostAndPort(name, port));
+  }
+
+  private AltsChannelBuilder(String target) {
+    delegate =
+        NettyChannelBuilder.forTarget(target)
+            .keepAliveTime(20, TimeUnit.SECONDS)
+            .keepAliveTimeout(10, TimeUnit.SECONDS)
+            .keepAliveWithoutCalls(true);
+    handshakerOptionsBuilder.setRpcProtocolVersions(
+        RpcProtocolVersionsUtil.getRpcProtocolVersions());
+  }
+
+  /** The server service account name for secure name checking. */
+  public AltsChannelBuilder withSecureNamingTarget(String targetName) {
+    handshakerOptionsBuilder.setTargetName(targetName);
+    return this;
+  }
+
+  /**
+   * Adds an expected target service accounts. One of the added service accounts should match peer
+   * service account in the handshaker result. Otherwise, the handshake fails.
+   */
+  public AltsChannelBuilder addTargetServiceAccount(String targetServiceAccount) {
+    handshakerOptionsBuilder.addTargetServiceAccount(targetServiceAccount);
+    return this;
+  }
+
+  /**
+   * Enables untrusted ALTS for testing. If this function is called, we will not check whether ALTS
+   * is running on Google Cloud Platform.
+   */
+  public AltsChannelBuilder enableUntrustedAltsForTesting() {
+    enableUntrustedAlts = true;
+    return this;
+  }
+
+  /** Sets a new handshaker service address for testing. */
+  public AltsChannelBuilder setHandshakerAddressForTesting(String handshakerAddress) {
+    // Instead of using the default shared channel to the handshaker service, create a fix object
+    // pool of handshaker service channel for testing.
+    handshakerChannelPool =
+        HandshakerServiceChannel.getHandshakerChannelPoolForTesting(handshakerAddress);
+    return this;
+  }
+
+  @Override
+  protected NettyChannelBuilder delegate() {
+    return delegate;
+  }
+
+  @Override
+  public ManagedChannel build() {
+    if (!CheckGcpEnvironment.isOnGcp()) {
+      if (enableUntrustedAlts) {
+        logger.log(
+            Level.WARNING,
+            "Untrusted ALTS mode is enabled and we cannot guarantee the trustworthiness of the "
+                + "ALTS handshaker service");
+      } else {
+        Status status =
+            Status.INTERNAL.withDescription("ALTS is only allowed to run on Google Cloud Platform");
+        delegate().intercept(new FailingClientInterceptor(status));
+      }
+    }
+
+    final AltsClientOptions handshakerOptions = handshakerOptionsBuilder.build();
+    TsiHandshakerFactory altsHandshakerFactory =
+        new TsiHandshakerFactory() {
+          @Override
+          public TsiHandshaker newHandshaker() {
+            // Used the shared grpc channel to connecting to the ALTS handshaker service.
+            // TODO: Release the channel if it is not used.
+            // https://github.com/grpc/grpc-java/issues/4755.
+            return AltsTsiHandshaker.newClient(
+                HandshakerServiceGrpc.newStub(handshakerChannelPool.getObject()),
+                handshakerOptions);
+          }
+        };
+    AltsProtocolNegotiator negotiator = AltsProtocolNegotiator.create(altsHandshakerFactory);
+
+    TcpfFactory tcpfFactory = new TcpfFactory(handshakerOptions, negotiator);
+    InternalNettyChannelBuilder.setDynamicTransportParamsFactory(delegate(), tcpfFactory);
+    tcpfFactoryForTest = tcpfFactory;
+
+    return delegate().build();
+  }
+
+  @VisibleForTesting
+  @Nullable
+  TransportCreationParamsFilterFactory getTcpfFactoryForTest() {
+    return tcpfFactoryForTest;
+  }
+
+  @VisibleForTesting
+  @Nullable
+  AltsClientOptions getAltsClientOptionsForTest() {
+    if (tcpfFactoryForTest == null) {
+      return null;
+    }
+    return tcpfFactoryForTest.handshakerOptions;
+  }
+
+  private static final class TcpfFactory implements TransportCreationParamsFilterFactory {
+
+    final AltsClientOptions handshakerOptions;
+    private final AltsProtocolNegotiator negotiator;
+
+    public TcpfFactory(AltsClientOptions handshakerOptions, AltsProtocolNegotiator negotiator) {
+      this.handshakerOptions = handshakerOptions;
+      this.negotiator = negotiator;
+    }
+
+    @Override
+    public TransportCreationParamsFilter create(
+        final SocketAddress serverAddress,
+        final String authority,
+        final String userAgent,
+        final ProxyParameters proxy) {
+      checkArgument(
+          serverAddress instanceof InetSocketAddress,
+          "%s must be a InetSocketAddress",
+          serverAddress);
+      return new TransportCreationParamsFilter() {
+        @Override
+        public SocketAddress getTargetServerAddress() {
+          return serverAddress;
+        }
+
+        @Override
+        public String getAuthority() {
+          return authority;
+        }
+
+        @Override
+        public String getUserAgent() {
+          return userAgent;
+        }
+
+        @Override
+        public AltsProtocolNegotiator getProtocolNegotiator() {
+          return negotiator;
+        }
+      };
+    }
+  }
+
+  /** An implementation of {@link ClientInterceptor} that fails each call. */
+  static final class FailingClientInterceptor implements ClientInterceptor {
+
+    private final Status status;
+
+    public FailingClientInterceptor(Status status) {
+      this.status = status;
+    }
+
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+        MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+      return new FailingClientCall<>(status);
+    }
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/AltsServerBuilder.java b/alts/src/main/java/io/grpc/alts/AltsServerBuilder.java
new file mode 100644
index 0000000..a264431
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/AltsServerBuilder.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts;
+
+import io.grpc.BindableService;
+import io.grpc.CompressorRegistry;
+import io.grpc.DecompressorRegistry;
+import io.grpc.ExperimentalApi;
+import io.grpc.HandlerRegistry;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.ServerCall;
+import io.grpc.ServerCall.Listener;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.ServerStreamTracer.Factory;
+import io.grpc.ServerTransportFilter;
+import io.grpc.Status;
+import io.grpc.alts.internal.AltsHandshakerOptions;
+import io.grpc.alts.internal.AltsProtocolNegotiator;
+import io.grpc.alts.internal.AltsTsiHandshaker;
+import io.grpc.alts.internal.HandshakerServiceGrpc;
+import io.grpc.alts.internal.RpcProtocolVersionsUtil;
+import io.grpc.alts.internal.TsiHandshaker;
+import io.grpc.alts.internal.TsiHandshakerFactory;
+import io.grpc.internal.ObjectPool;
+import io.grpc.internal.SharedResourcePool;
+import io.grpc.netty.NettyServerBuilder;
+import java.io.File;
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * gRPC secure server builder used for ALTS. This class adds on the necessary ALTS support to create
+ * a production server on Google Cloud Platform.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4151")
+public final class AltsServerBuilder extends ServerBuilder<AltsServerBuilder> {
+
+  private static final Logger logger = Logger.getLogger(AltsServerBuilder.class.getName());
+  private final NettyServerBuilder delegate;
+  private ObjectPool<ManagedChannel> handshakerChannelPool =
+      SharedResourcePool.forResource(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL);
+  private boolean enableUntrustedAlts;
+
+  private AltsServerBuilder(NettyServerBuilder nettyDelegate) {
+    this.delegate = nettyDelegate;
+  }
+
+  /** Creates a gRPC server builder for the given port. */
+  public static AltsServerBuilder forPort(int port) {
+    NettyServerBuilder nettyDelegate =
+        NettyServerBuilder.forAddress(new InetSocketAddress(port))
+            .maxConnectionIdle(1, TimeUnit.HOURS)
+            .keepAliveTime(270, TimeUnit.SECONDS)
+            .keepAliveTimeout(20, TimeUnit.SECONDS)
+            .permitKeepAliveTime(10, TimeUnit.SECONDS)
+            .permitKeepAliveWithoutCalls(true);
+    return new AltsServerBuilder(nettyDelegate);
+  }
+
+  /**
+   * Enables untrusted ALTS for testing. If this function is called, we will not check whether ALTS
+   * is running on Google Cloud Platform.
+   */
+  public AltsServerBuilder enableUntrustedAltsForTesting() {
+    enableUntrustedAlts = true;
+    return this;
+  }
+
+  /** Sets a new handshaker service address for testing. */
+  public AltsServerBuilder setHandshakerAddressForTesting(String handshakerAddress) {
+    // Instead of using the default shared channel to the handshaker service, create a fix object
+    // pool of handshaker service channel for testing.
+    handshakerChannelPool =
+        HandshakerServiceChannel.getHandshakerChannelPoolForTesting(handshakerAddress);
+    return this;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public AltsServerBuilder handshakeTimeout(long timeout, TimeUnit unit) {
+    delegate.handshakeTimeout(timeout, unit);
+    return this;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public AltsServerBuilder directExecutor() {
+    delegate.directExecutor();
+    return this;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public AltsServerBuilder addStreamTracerFactory(Factory factory) {
+    delegate.addStreamTracerFactory(factory);
+    return this;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public AltsServerBuilder addTransportFilter(ServerTransportFilter filter) {
+    delegate.addTransportFilter(filter);
+    return this;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public AltsServerBuilder executor(Executor executor) {
+    delegate.executor(executor);
+    return this;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public AltsServerBuilder addService(ServerServiceDefinition service) {
+    delegate.addService(service);
+    return this;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public AltsServerBuilder addService(BindableService bindableService) {
+    delegate.addService(bindableService);
+    return this;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public AltsServerBuilder fallbackHandlerRegistry(HandlerRegistry fallbackRegistry) {
+    delegate.fallbackHandlerRegistry(fallbackRegistry);
+    return this;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public AltsServerBuilder useTransportSecurity(File certChain, File privateKey) {
+    throw new UnsupportedOperationException("Can't set TLS settings for ALTS");
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public AltsServerBuilder decompressorRegistry(DecompressorRegistry registry) {
+    delegate.decompressorRegistry(registry);
+    return this;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public AltsServerBuilder compressorRegistry(CompressorRegistry registry) {
+    delegate.compressorRegistry(registry);
+    return this;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public AltsServerBuilder intercept(ServerInterceptor interceptor) {
+    delegate.intercept(interceptor);
+    return this;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Server build() {
+    if (!CheckGcpEnvironment.isOnGcp()) {
+      if (enableUntrustedAlts) {
+        logger.log(
+            Level.WARNING,
+            "Untrusted ALTS mode is enabled and we cannot guarantee the trustworthiness of the "
+                + "ALTS handshaker service");
+      } else {
+        Status status =
+            Status.INTERNAL.withDescription("ALTS is only allowed to run on Google Cloud Platform");
+        delegate.intercept(new FailingServerInterceptor(status));
+      }
+    }
+
+    delegate.protocolNegotiator(
+        AltsProtocolNegotiator.create(
+            new TsiHandshakerFactory() {
+              @Override
+              public TsiHandshaker newHandshaker() {
+                // Used the shared grpc channel to connecting to the ALTS handshaker service.
+                // TODO: Release the channel if it is not used.
+                // https://github.com/grpc/grpc-java/issues/4755.
+                return AltsTsiHandshaker.newServer(
+                    HandshakerServiceGrpc.newStub(handshakerChannelPool.getObject()),
+                    new AltsHandshakerOptions(RpcProtocolVersionsUtil.getRpcProtocolVersions()));
+              }
+            }));
+    return delegate.build();
+  }
+
+  /** An implementation of {@link ServerInterceptor} that fails each call. */
+  static final class FailingServerInterceptor implements ServerInterceptor {
+
+    private final Status status;
+
+    public FailingServerInterceptor(Status status) {
+      this.status = status;
+    }
+
+    @Override
+    public <ReqT, RespT> Listener<ReqT> interceptCall(
+        ServerCall<ReqT, RespT> serverCall,
+        Metadata metadata,
+        ServerCallHandler<ReqT, RespT> nextHandler) {
+      serverCall.close(status, new Metadata());
+      return new Listener<ReqT>() {};
+    }
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/CheckGcpEnvironment.java b/alts/src/main/java/io/grpc/alts/CheckGcpEnvironment.java
new file mode 100644
index 0000000..1533415
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/CheckGcpEnvironment.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.apache.commons.lang3.SystemUtils;
+
+/** Class for checking if the system is running on Google Cloud Platform (GCP). */
+final class CheckGcpEnvironment {
+
+  private static final Logger logger = Logger.getLogger(CheckGcpEnvironment.class.getName());
+  private static final String DMI_PRODUCT_NAME = "/sys/class/dmi/id/product_name";
+  private static final String WINDOWS_COMMAND = "powershell.exe";
+  private static Boolean cachedResult = null;
+
+  // Construct me not!
+  private CheckGcpEnvironment() {}
+
+  static synchronized boolean isOnGcp() {
+    if (cachedResult == null) {
+      cachedResult = isRunningOnGcp();
+    }
+    return cachedResult;
+  }
+
+  @VisibleForTesting
+  static boolean checkProductNameOnLinux(BufferedReader reader) throws IOException {
+    String name = reader.readLine().trim();
+    return name.equals("Google") || name.equals("Google Compute Engine");
+  }
+
+  @VisibleForTesting
+  static boolean checkBiosDataOnWindows(BufferedReader reader) throws IOException {
+    String line;
+    while ((line = reader.readLine()) != null) {
+      if (line.startsWith("Manufacturer")) {
+        String name = line.substring(line.indexOf(':') + 1).trim();
+        return name.equals("Google");
+      }
+    }
+    return false;
+  }
+
+  private static boolean isRunningOnGcp() {
+    try {
+      if (SystemUtils.IS_OS_LINUX) {
+        // Checks GCE residency on Linux platform.
+        return checkProductNameOnLinux(Files.newBufferedReader(Paths.get(DMI_PRODUCT_NAME), UTF_8));
+      } else if (SystemUtils.IS_OS_WINDOWS) {
+        // Checks GCE residency on Windows platform.
+        Process p =
+            new ProcessBuilder()
+                .command(WINDOWS_COMMAND, "Get-WmiObject", "-Class", "Win32_BIOS")
+                .start();
+        return checkBiosDataOnWindows(
+            new BufferedReader(new InputStreamReader(p.getInputStream(), UTF_8)));
+      }
+    } catch (IOException e) {
+      logger.log(Level.WARNING, "Fail to read platform information: ", e);
+      return false;
+    }
+    // Platforms other than Linux and Windows are not supported.
+    return false;
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/FailingClientCall.java b/alts/src/main/java/io/grpc/alts/FailingClientCall.java
new file mode 100644
index 0000000..ab67436
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/FailingClientCall.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts;
+
+import io.grpc.ClientCall;
+import io.grpc.Metadata;
+import io.grpc.Status;
+
+/** An implementation of {@link ClientCall} that fails when started. */
+final class FailingClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> {
+
+  private final Status error;
+
+  public FailingClientCall(Status error) {
+    this.error = error;
+  }
+
+  @Override
+  public void start(ClientCall.Listener<RespT> listener, Metadata headers) {
+    listener.onClose(error, new Metadata());
+  }
+
+  @Override
+  public void request(int numMessages) {}
+
+  @Override
+  public void cancel(String message, Throwable cause) {}
+
+  @Override
+  public void halfClose() {}
+
+  @Override
+  public void sendMessage(ReqT message) {}
+}
diff --git a/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java
new file mode 100644
index 0000000..619ce04
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/GoogleDefaultChannelBuilder.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.CallCredentials;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ForwardingChannelBuilder;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.alts.internal.AltsClientOptions;
+import io.grpc.alts.internal.AltsTsiHandshaker;
+import io.grpc.alts.internal.GoogleDefaultProtocolNegotiator;
+import io.grpc.alts.internal.HandshakerServiceGrpc;
+import io.grpc.alts.internal.RpcProtocolVersionsUtil;
+import io.grpc.alts.internal.TsiHandshaker;
+import io.grpc.alts.internal.TsiHandshakerFactory;
+import io.grpc.auth.MoreCallCredentials;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.ProxyParameters;
+import io.grpc.internal.SharedResourceHolder;
+import io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.InternalNettyChannelBuilder;
+import io.grpc.netty.InternalNettyChannelBuilder.TransportCreationParamsFilter;
+import io.grpc.netty.InternalNettyChannelBuilder.TransportCreationParamsFilterFactory;
+import io.grpc.netty.NettyChannelBuilder;
+import io.netty.handler.ssl.SslContext;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import javax.annotation.Nullable;
+import javax.net.ssl.SSLException;
+
+/**
+ * Google default version of {@code ManagedChannelBuilder}. This class sets up a secure channel
+ * using ALTS if applicable and using TLS as fallback.
+ */
+public final class GoogleDefaultChannelBuilder
+    extends ForwardingChannelBuilder<GoogleDefaultChannelBuilder> {
+
+  private final NettyChannelBuilder delegate;
+  private final TcpfFactory tcpfFactory;
+
+  private GoogleDefaultChannelBuilder(String target) {
+    delegate = NettyChannelBuilder.forTarget(target);
+
+    final AltsClientOptions handshakerOptions =
+        new AltsClientOptions.Builder()
+            .setRpcProtocolVersions(RpcProtocolVersionsUtil.getRpcProtocolVersions())
+            .build();
+    TsiHandshakerFactory altsHandshakerFactory =
+        new TsiHandshakerFactory() {
+          @Override
+          public TsiHandshaker newHandshaker() {
+            // Used the shared grpc channel to connecting to the ALTS handshaker service.
+            // TODO: Release the channel if it is not used.
+            // https://github.com/grpc/grpc-java/issues/4755.
+            ManagedChannel channel =
+                SharedResourceHolder.get(HandshakerServiceChannel.SHARED_HANDSHAKER_CHANNEL);
+            return AltsTsiHandshaker.newClient(
+                HandshakerServiceGrpc.newStub(channel), handshakerOptions);
+          }
+        };
+    SslContext sslContext;
+    try {
+      sslContext = GrpcSslContexts.forClient().build();
+    } catch (SSLException ex) {
+      throw new RuntimeException(ex);
+    }
+    tcpfFactory = new TcpfFactory(
+        new GoogleDefaultProtocolNegotiator(altsHandshakerFactory, sslContext));
+    InternalNettyChannelBuilder.setDynamicTransportParamsFactory(delegate(), tcpfFactory);
+  }
+
+  /** "Overrides" the static method in {@link ManagedChannelBuilder}. */
+  public static final GoogleDefaultChannelBuilder forTarget(String target) {
+    return new GoogleDefaultChannelBuilder(target);
+  }
+
+  /** "Overrides" the static method in {@link ManagedChannelBuilder}. */
+  public static GoogleDefaultChannelBuilder forAddress(String name, int port) {
+    return forTarget(GrpcUtil.authorityFromHostAndPort(name, port));
+  }
+
+  @Override
+  protected NettyChannelBuilder delegate() {
+    return delegate;
+  }
+
+  @Override
+  public ManagedChannel build() {
+    @Nullable CallCredentials credentials = null;
+    Status status = Status.OK;
+    try {
+      credentials = MoreCallCredentials.from(GoogleCredentials.getApplicationDefault());
+    } catch (IOException e) {
+      status =
+          Status.UNAUTHENTICATED
+              .withDescription("Failed to get Google default credentials")
+              .withCause(e);
+    }
+    return delegate().intercept(new GoogleDefaultInterceptor(credentials, status)).build();
+  }
+
+  @VisibleForTesting
+  TransportCreationParamsFilterFactory getTcpfFactoryForTest() {
+    return tcpfFactory;
+  }
+
+  private static final class TcpfFactory implements TransportCreationParamsFilterFactory {
+    private final GoogleDefaultProtocolNegotiator negotiator;
+
+    private TcpfFactory(GoogleDefaultProtocolNegotiator negotiator) {
+      this.negotiator = negotiator;
+    }
+
+    @Override
+    public TransportCreationParamsFilter create(
+        final SocketAddress serverAddress,
+        final String authority,
+        final String userAgent,
+        final ProxyParameters proxy) {
+      checkArgument(
+          serverAddress instanceof InetSocketAddress,
+          "%s must be a InetSocketAddress",
+          serverAddress);
+      return new TransportCreationParamsFilter() {
+        @Override
+        public SocketAddress getTargetServerAddress() {
+          return serverAddress;
+        }
+
+        @Override
+        public String getAuthority() {
+          return authority;
+        }
+
+        @Override
+        public String getUserAgent() {
+          return userAgent;
+        }
+
+        @Override
+        public GoogleDefaultProtocolNegotiator getProtocolNegotiator() {
+          return negotiator;
+        }
+      };
+    }
+  }
+
+  /**
+   * An implementation of {@link ClientInterceptor} that adds Google call credentials on each call.
+   */
+  static final class GoogleDefaultInterceptor implements ClientInterceptor {
+
+    @Nullable private final CallCredentials credentials;
+    private final Status status;
+
+    public GoogleDefaultInterceptor(@Nullable CallCredentials credentials, Status status) {
+      this.credentials = credentials;
+      this.status = status;
+    }
+
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+        MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+      if (!status.isOk()) {
+        return new FailingClientCall<>(status);
+      }
+      return next.newCall(method, callOptions.withCallCredentials(credentials));
+    }
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/HandshakerServiceChannel.java b/alts/src/main/java/io/grpc/alts/HandshakerServiceChannel.java
new file mode 100644
index 0000000..7472112
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/HandshakerServiceChannel.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts;
+
+import io.grpc.ManagedChannel;
+import io.grpc.internal.FixedObjectPool;
+import io.grpc.internal.SharedResourceHolder.Resource;
+import io.grpc.netty.NettyChannelBuilder;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.util.concurrent.DefaultThreadFactory;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * Class for creating a single shared gRPC channel to the ALTS Handshaker Service using
+ * SharedResourceHolder. The channel to the handshaker service is local and is over plaintext. Each
+ * application will have at most one connection to the handshaker service.
+ */
+final class HandshakerServiceChannel {
+
+  static final Resource<ManagedChannel> SHARED_HANDSHAKER_CHANNEL =
+      new Resource<ManagedChannel>() {
+
+        private EventLoopGroup eventGroup = null;
+
+        @Override
+        public ManagedChannel create() {
+          /* Use its own event loop thread pool to avoid blocking. */
+          if (eventGroup == null) {
+            eventGroup =
+                new NioEventLoopGroup(1, new DefaultThreadFactory("handshaker pool", true));
+          }
+          return NettyChannelBuilder.forTarget("metadata.google.internal:8080")
+              .directExecutor()
+              .eventLoopGroup(eventGroup)
+              .usePlaintext()
+              .build();
+        }
+
+        @Override
+        @SuppressWarnings("FutureReturnValueIgnored") // netty ChannelFuture
+        public void close(ManagedChannel instance) {
+          instance.shutdownNow();
+          if (eventGroup != null) {
+            eventGroup.shutdownGracefully();
+          }
+        }
+
+        @Override
+        public String toString() {
+          return "grpc-alts-handshaker-service-channel";
+        }
+      };
+
+  /** Returns a fixed object pool of handshaker service channel for testing only. */
+  static FixedObjectPool<ManagedChannel> getHandshakerChannelPoolForTesting(
+      String handshakerAddress) {
+    ThreadFactory clientThreadFactory = new DefaultThreadFactory("handshaker pool", true);
+    ManagedChannel channel =
+        NettyChannelBuilder.forTarget(handshakerAddress)
+            .directExecutor()
+            .eventLoopGroup(new NioEventLoopGroup(1, clientThreadFactory))
+            .usePlaintext()
+            .build();
+    return new FixedObjectPool<ManagedChannel>(channel);
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/AeadCrypter.java b/alts/src/main/java/io/grpc/alts/internal/AeadCrypter.java
new file mode 100644
index 0000000..4d99187
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/AeadCrypter.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+
+/**
+ * {@code AeadCrypter} performs authenticated encryption and decryption for a fixed key given unique
+ * nonces. Authenticated additional data is supported.
+ */
+interface AeadCrypter {
+  /**
+   * Encrypt plaintext into ciphertext buffer using the given nonce.
+   *
+   * @param ciphertext the encrypted plaintext and the tag will be written into this buffer.
+   * @param plaintext the input that should be encrypted.
+   * @param nonce the unique nonce used for the encryption.
+   * @throws GeneralSecurityException if ciphertext buffer is short or the nonce does not have the
+   *     expected size.
+   */
+  void encrypt(ByteBuffer ciphertext, ByteBuffer plaintext, byte[] nonce)
+      throws GeneralSecurityException;
+
+  /**
+   * Encrypt plaintext into ciphertext buffer using the given nonce with authenticated data.
+   *
+   * @param ciphertext the encrypted plaintext and the tag will be written into this buffer.
+   * @param plaintext the input that should be encrypted.
+   * @param aad additional data that should be authenticated, but not encrypted.
+   * @param nonce the unique nonce used for the encryption.
+   * @throws GeneralSecurityException if ciphertext buffer is short or the nonce does not have the
+   *     expected size.
+   */
+  void encrypt(ByteBuffer ciphertext, ByteBuffer plaintext, ByteBuffer aad, byte[] nonce)
+      throws GeneralSecurityException;
+
+  /**
+   * Decrypt ciphertext into plaintext buffer using the given nonce.
+   *
+   * @param plaintext the decrypted plaintext will be written into this buffer.
+   * @param ciphertext the ciphertext and tag that should be decrypted.
+   * @param nonce the nonce that was used for the encryption.
+   * @throws GeneralSecurityException if the tag is invalid or any of the inputs do not have the
+   *     expected size.
+   */
+  void decrypt(ByteBuffer plaintext, ByteBuffer ciphertext, byte[] nonce)
+      throws GeneralSecurityException;
+
+  /**
+   * Decrypt ciphertext into plaintext buffer using the given nonce.
+   *
+   * @param plaintext the decrypted plaintext will be written into this buffer.
+   * @param ciphertext the ciphertext and tag that should be decrypted.
+   * @param aad additional data that is checked for authenticity.
+   * @param nonce the nonce that was used for the encryption.
+   * @throws GeneralSecurityException if the tag is invalid or any of the inputs do not have the
+   *     expected size.
+   */
+  void decrypt(ByteBuffer plaintext, ByteBuffer ciphertext, ByteBuffer aad, byte[] nonce)
+      throws GeneralSecurityException;
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/AesGcmAeadCrypter.java b/alts/src/main/java/io/grpc/alts/internal/AesGcmAeadCrypter.java
new file mode 100644
index 0000000..f3b968f
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/AesGcmAeadCrypter.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import javax.annotation.Nullable;
+import javax.crypto.Cipher;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/** AES128-GCM implementation of {@link AeadCrypter} that uses default JCE provider. */
+final class AesGcmAeadCrypter implements AeadCrypter {
+  private static final int KEY_LENGTH = 16;
+  private static final int TAG_LENGTH = 16;
+  static final int NONCE_LENGTH = 12;
+
+  private static final String AES = "AES";
+  private static final String AES_GCM = AES + "/GCM/NoPadding";
+
+  private final byte[] key;
+  private final Cipher cipher;
+
+  AesGcmAeadCrypter(byte[] key) throws GeneralSecurityException {
+    checkArgument(key.length == KEY_LENGTH);
+    this.key = key;
+    cipher = Cipher.getInstance(AES_GCM);
+  }
+
+  private int encryptAad(
+      ByteBuffer ciphertext, ByteBuffer plaintext, @Nullable ByteBuffer aad, byte[] nonce)
+      throws GeneralSecurityException {
+    checkArgument(nonce.length == NONCE_LENGTH);
+    cipher.init(
+        Cipher.ENCRYPT_MODE,
+        new SecretKeySpec(this.key, AES),
+        new GCMParameterSpec(TAG_LENGTH * 8, nonce));
+    if (aad != null) {
+      cipher.updateAAD(aad);
+    }
+    return cipher.doFinal(plaintext, ciphertext);
+  }
+
+  private void decryptAad(
+      ByteBuffer plaintext, ByteBuffer ciphertext, @Nullable ByteBuffer aad, byte[] nonce)
+      throws GeneralSecurityException {
+    checkArgument(nonce.length == NONCE_LENGTH);
+    cipher.init(
+        Cipher.DECRYPT_MODE,
+        new SecretKeySpec(this.key, AES),
+        new GCMParameterSpec(TAG_LENGTH * 8, nonce));
+    if (aad != null) {
+      cipher.updateAAD(aad);
+    }
+    cipher.doFinal(ciphertext, plaintext);
+  }
+
+  @Override
+  public void encrypt(ByteBuffer ciphertext, ByteBuffer plaintext, byte[] nonce)
+      throws GeneralSecurityException {
+    encryptAad(ciphertext, plaintext, null, nonce);
+  }
+
+  @Override
+  public void encrypt(ByteBuffer ciphertext, ByteBuffer plaintext, ByteBuffer aad, byte[] nonce)
+      throws GeneralSecurityException {
+    encryptAad(ciphertext, plaintext, aad, nonce);
+  }
+
+  @Override
+  public void decrypt(ByteBuffer plaintext, ByteBuffer ciphertext, byte[] nonce)
+      throws GeneralSecurityException {
+    decryptAad(plaintext, ciphertext, null, nonce);
+  }
+
+  @Override
+  public void decrypt(ByteBuffer plaintext, ByteBuffer ciphertext, ByteBuffer aad, byte[] nonce)
+      throws GeneralSecurityException {
+    decryptAad(plaintext, ciphertext, aad, nonce);
+  }
+
+  static int getKeyLength() {
+    return KEY_LENGTH;
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/AesGcmHkdfAeadCrypter.java b/alts/src/main/java/io/grpc/alts/internal/AesGcmHkdfAeadCrypter.java
new file mode 100644
index 0000000..b0f7535
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/AesGcmHkdfAeadCrypter.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * {@link AeadCrypter} implementation based on {@link AesGcmAeadCrypter} with nonce-based rekeying
+ * using HKDF-expand and random nonce-mask that is XORed with the given nonce/counter. The AES-GCM
+ * key is computed as HKDF-expand(kdfKey, nonce[2..7]), i.e., the first 2 bytes are ignored to
+ * require rekeying only after 2^16 operations and the last 4 bytes (including the direction bit)
+ * are ignored to allow for optimizations (use same AEAD context for both directions, store counter
+ * as unsigned long and boolean for direction).
+ */
+final class AesGcmHkdfAeadCrypter implements AeadCrypter {
+  private static final int KDF_KEY_LENGTH = 32;
+  // Rekey after 2^(2*8) = 2^16 operations by ignoring the first 2 nonce bytes for key derivation.
+  private static final int KDF_COUNTER_OFFSET = 2;
+  // Use remaining bytes of 64-bit counter included in nonce for key derivation.
+  private static final int KDF_COUNTER_LENGTH = 6;
+  private static final int NONCE_LENGTH = AesGcmAeadCrypter.NONCE_LENGTH;
+  private static final int KEY_LENGTH = KDF_KEY_LENGTH + NONCE_LENGTH;
+
+  private final byte[] kdfKey;
+  private final byte[] kdfCounter = new byte[KDF_COUNTER_LENGTH];
+  private final byte[] nonceMask;
+  private final byte[] nonceBuffer = new byte[NONCE_LENGTH];
+
+  private AeadCrypter aeadCrypter;
+
+  AesGcmHkdfAeadCrypter(byte[] key) {
+    checkArgument(key.length == KEY_LENGTH);
+    this.kdfKey = Arrays.copyOf(key, KDF_KEY_LENGTH);
+    this.nonceMask = Arrays.copyOfRange(key, KDF_KEY_LENGTH, KDF_KEY_LENGTH + NONCE_LENGTH);
+  }
+
+  @Override
+  public void encrypt(ByteBuffer ciphertext, ByteBuffer plaintext, byte[] nonce)
+      throws GeneralSecurityException {
+    maybeRekey(nonce);
+    maskNonce(nonceBuffer, nonceMask, nonce);
+    aeadCrypter.encrypt(ciphertext, plaintext, nonceBuffer);
+  }
+
+  @Override
+  public void encrypt(ByteBuffer ciphertext, ByteBuffer plaintext, ByteBuffer aad, byte[] nonce)
+      throws GeneralSecurityException {
+    maybeRekey(nonce);
+    maskNonce(nonceBuffer, nonceMask, nonce);
+    aeadCrypter.encrypt(ciphertext, plaintext, aad, nonceBuffer);
+  }
+
+  @Override
+  public void decrypt(ByteBuffer plaintext, ByteBuffer ciphertext, byte[] nonce)
+      throws GeneralSecurityException {
+    maybeRekey(nonce);
+    maskNonce(nonceBuffer, nonceMask, nonce);
+    aeadCrypter.decrypt(plaintext, ciphertext, nonceBuffer);
+  }
+
+  @Override
+  public void decrypt(ByteBuffer plaintext, ByteBuffer ciphertext, ByteBuffer aad, byte[] nonce)
+      throws GeneralSecurityException {
+    maybeRekey(nonce);
+    maskNonce(nonceBuffer, nonceMask, nonce);
+    aeadCrypter.decrypt(plaintext, ciphertext, aad, nonceBuffer);
+  }
+
+  private void maybeRekey(byte[] nonce) throws GeneralSecurityException {
+    if (aeadCrypter != null
+        && arrayEqualOn(nonce, KDF_COUNTER_OFFSET, kdfCounter, 0, KDF_COUNTER_LENGTH)) {
+      return;
+    }
+    System.arraycopy(nonce, KDF_COUNTER_OFFSET, kdfCounter, 0, KDF_COUNTER_LENGTH);
+    int aeKeyLen = AesGcmAeadCrypter.getKeyLength();
+    byte[] aeKey = Arrays.copyOf(hkdfExpandSha256(kdfKey, kdfCounter), aeKeyLen);
+    aeadCrypter = new AesGcmAeadCrypter(aeKey);
+  }
+
+  private static void maskNonce(byte[] nonceBuffer, byte[] nonceMask, byte[] nonce) {
+    checkArgument(nonce.length == NONCE_LENGTH);
+    for (int i = 0; i < NONCE_LENGTH; i++) {
+      nonceBuffer[i] = (byte) (nonceMask[i] ^ nonce[i]);
+    }
+  }
+
+  private static byte[] hkdfExpandSha256(byte[] key, byte[] info) throws GeneralSecurityException {
+    Mac mac = Mac.getInstance("HMACSHA256");
+    mac.init(new SecretKeySpec(key, mac.getAlgorithm()));
+    mac.update(info);
+    mac.update((byte) 0x01);
+    return mac.doFinal();
+  }
+
+  private static boolean arrayEqualOn(byte[] a, int aPos, byte[] b, int bPos, int length) {
+    for (int i = 0; i < length; i++) {
+      if (a[aPos + i] != b[bPos + i]) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  static int getKeyLength() {
+    return KEY_LENGTH;
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsAuthContext.java b/alts/src/main/java/io/grpc/alts/internal/AltsAuthContext.java
new file mode 100644
index 0000000..cc335fe
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/AltsAuthContext.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.alts.internal.Altscontext.AltsContext;
+import io.grpc.alts.internal.Handshaker.HandshakerResult;
+import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions;
+import io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel;
+
+/** AltsAuthContext contains security-related context information about an ALTs connection. */
+public final class AltsAuthContext {
+  final AltsContext context;
+
+  /** Create a new AltsAuthContext. */
+  public AltsAuthContext(HandshakerResult result) {
+    context =
+        AltsContext.newBuilder()
+            .setApplicationProtocol(result.getApplicationProtocol())
+            .setRecordProtocol(result.getRecordProtocol())
+            // TODO: Set security level based on the handshaker result.
+            .setSecurityLevel(SecurityLevel.INTEGRITY_AND_PRIVACY)
+            .setPeerServiceAccount(result.getPeerIdentity().getServiceAccount())
+            .setLocalServiceAccount(result.getLocalIdentity().getServiceAccount())
+            .setPeerRpcVersions(result.getPeerRpcVersions())
+            .build();
+  }
+
+  @VisibleForTesting
+  public static AltsAuthContext getDefaultInstance() {
+    return new AltsAuthContext(HandshakerResult.newBuilder().build());
+  }
+
+  /**
+   * Get application protocol.
+   *
+   * @return the context's application protocol.
+   */
+  public String getApplicationProtocol() {
+    return context.getApplicationProtocol();
+  }
+
+  /**
+   * Get negotiated record protocol.
+   *
+   * @return the context's negotiated record protocol.
+   */
+  public String getRecordProtocol() {
+    return context.getRecordProtocol();
+  }
+
+  /**
+   * Get security level.
+   *
+   * @return the context's security level.
+   */
+  public SecurityLevel getSecurityLevel() {
+    return context.getSecurityLevel();
+  }
+
+  /**
+   * Get peer service account.
+   *
+   * @return the context's peer service account.
+   */
+  public String getPeerServiceAccount() {
+    return context.getPeerServiceAccount();
+  }
+
+  /**
+   * Get local service account.
+   *
+   * @return the context's local service account.
+   */
+  public String getLocalServiceAccount() {
+    return context.getLocalServiceAccount();
+  }
+
+  /**
+   * Get peer RPC versions.
+   *
+   * @return the context's peer RPC versions.
+   */
+  public RpcProtocolVersions getPeerRpcVersions() {
+    return context.getPeerRpcVersions();
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsChannelCrypter.java b/alts/src/main/java/io/grpc/alts/internal/AltsChannelCrypter.java
new file mode 100644
index 0000000..3e999fa
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/AltsChannelCrypter.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Verify.verify;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.netty.buffer.ByteBuf;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.List;
+
+/** Performs encryption and decryption with AES-GCM using JCE. All methods are thread-compatible. */
+final class AltsChannelCrypter implements ChannelCrypterNetty {
+  private static final int KEY_LENGTH = AesGcmHkdfAeadCrypter.getKeyLength();
+  private static final int COUNTER_LENGTH = 12;
+  // The counter will overflow after 2^64 operations and encryption/decryption will stop working.
+  private static final int COUNTER_OVERFLOW_LENGTH = 8;
+  private static final int TAG_LENGTH = 16;
+
+  private final AeadCrypter aeadCrypter;
+
+  private final byte[] outCounter = new byte[COUNTER_LENGTH];
+  private final byte[] inCounter = new byte[COUNTER_LENGTH];
+  private final byte[] oldCounter = new byte[COUNTER_LENGTH];
+
+  AltsChannelCrypter(byte[] key, boolean isClient) {
+    checkArgument(key.length == KEY_LENGTH);
+    byte[] counter = isClient ? inCounter : outCounter;
+    counter[counter.length - 1] = (byte) 0x80;
+    this.aeadCrypter = new AesGcmHkdfAeadCrypter(key);
+  }
+
+  static int getKeyLength() {
+    return KEY_LENGTH;
+  }
+
+  static int getCounterLength() {
+    return COUNTER_LENGTH;
+  }
+
+  @SuppressWarnings("BetaApi") // verify is stable in Guava
+  @Override
+  public void encrypt(ByteBuf outBuf, List<ByteBuf> plainBufs) throws GeneralSecurityException {
+    checkArgument(outBuf.nioBufferCount() == 1);
+    // Copy plaintext buffers into outBuf for in-place encryption on single direct buffer.
+    ByteBuf plainBuf = outBuf.slice(outBuf.writerIndex(), outBuf.writableBytes());
+    plainBuf.writerIndex(0);
+    for (ByteBuf inBuf : plainBufs) {
+      plainBuf.writeBytes(inBuf);
+    }
+
+    verify(outBuf.writableBytes() == plainBuf.readableBytes() + TAG_LENGTH);
+    ByteBuffer out = outBuf.internalNioBuffer(outBuf.writerIndex(), outBuf.writableBytes());
+    ByteBuffer plain = out.duplicate();
+    plain.limit(out.limit() - TAG_LENGTH);
+
+    byte[] counter = incrementOutCounter();
+    int outPosition = out.position();
+    aeadCrypter.encrypt(out, plain, counter);
+    int bytesWritten = out.position() - outPosition;
+    outBuf.writerIndex(outBuf.writerIndex() + bytesWritten);
+    verify(!outBuf.isWritable());
+  }
+
+  @Override
+  public void decrypt(ByteBuf out, ByteBuf tag, List<ByteBuf> ciphertextBufs)
+      throws GeneralSecurityException {
+
+    ByteBuf cipherTextAndTag = out.slice(out.writerIndex(), out.writableBytes());
+    cipherTextAndTag.writerIndex(0);
+
+    for (ByteBuf inBuf : ciphertextBufs) {
+      cipherTextAndTag.writeBytes(inBuf);
+    }
+    cipherTextAndTag.writeBytes(tag);
+
+    decrypt(out, cipherTextAndTag);
+  }
+
+  @SuppressWarnings("BetaApi") // verify is stable in Guava
+  @Override
+  public void decrypt(ByteBuf out, ByteBuf ciphertextAndTag) throws GeneralSecurityException {
+    int bytesRead = ciphertextAndTag.readableBytes();
+    checkArgument(bytesRead == out.writableBytes());
+
+    checkArgument(out.nioBufferCount() == 1);
+    ByteBuffer outBuffer = out.internalNioBuffer(out.writerIndex(), out.writableBytes());
+
+    checkArgument(ciphertextAndTag.nioBufferCount() == 1);
+    ByteBuffer ciphertextAndTagBuffer =
+        ciphertextAndTag.nioBuffer(ciphertextAndTag.readerIndex(), bytesRead);
+
+    byte[] counter = incrementInCounter();
+    int outPosition = outBuffer.position();
+    aeadCrypter.decrypt(outBuffer, ciphertextAndTagBuffer, counter);
+    int bytesWritten = outBuffer.position() - outPosition;
+    out.writerIndex(out.writerIndex() + bytesWritten);
+    ciphertextAndTag.readerIndex(out.readerIndex() + bytesRead);
+    verify(out.writableBytes() == TAG_LENGTH);
+  }
+
+  @Override
+  public int getSuffixLength() {
+    return TAG_LENGTH;
+  }
+
+  @Override
+  public void destroy() {
+    // no destroy required
+  }
+
+  /** Increments {@code counter}, store the unincremented value in {@code oldCounter}. */
+  static void incrementCounter(byte[] counter, byte[] oldCounter) throws GeneralSecurityException {
+    System.arraycopy(counter, 0, oldCounter, 0, counter.length);
+    int i = 0;
+    for (; i < COUNTER_OVERFLOW_LENGTH; i++) {
+      counter[i]++;
+      if (counter[i] != (byte) 0x00) {
+        break;
+      }
+    }
+
+    if (i == COUNTER_OVERFLOW_LENGTH) {
+      // Restore old counter value to ensure that encrypt and decrypt keep failing.
+      System.arraycopy(oldCounter, 0, counter, 0, counter.length);
+      throw new GeneralSecurityException("Counter has overflowed.");
+    }
+  }
+
+  /** Increments the input counter, returning the previous (unincremented) value. */
+  private byte[] incrementInCounter() throws GeneralSecurityException {
+    incrementCounter(inCounter, oldCounter);
+    return oldCounter;
+  }
+
+  /** Increments the output counter, returning the previous (unincremented) value. */
+  private byte[] incrementOutCounter() throws GeneralSecurityException {
+    incrementCounter(outCounter, oldCounter);
+    return oldCounter;
+  }
+
+  @VisibleForTesting
+  void incrementInCounterForTesting(int n) throws GeneralSecurityException {
+    for (int i = 0; i < n; i++) {
+      incrementInCounter();
+    }
+  }
+
+  @VisibleForTesting
+  void incrementOutCounterForTesting(int n) throws GeneralSecurityException {
+    for (int i = 0; i < n; i++) {
+      incrementOutCounter();
+    }
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsClientOptions.java b/alts/src/main/java/io/grpc/alts/internal/AltsClientOptions.java
new file mode 100644
index 0000000..e0e506a
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/AltsClientOptions.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/** Handshaker options for creating ALTS client channel. */
+public final class AltsClientOptions extends AltsHandshakerOptions {
+  // targetName is the server service account name for secure name checking. This field is not yet
+  // supported.
+  @Nullable private final String targetName;
+  // targetServiceAccounts contains a list of expected target service accounts. One of these service
+  // accounts should match peer service account in the handshaker result. Otherwise, the handshake
+  // fails.
+  private final List<String> targetServiceAccounts;
+
+  private AltsClientOptions(Builder builder) {
+    super(builder.rpcProtocolVersions);
+    targetName = builder.targetName;
+    targetServiceAccounts =
+        Collections.unmodifiableList(new ArrayList<>(builder.targetServiceAccounts));
+  }
+
+  public String getTargetName() {
+    return targetName;
+  }
+
+  public List<String> getTargetServiceAccounts() {
+    return targetServiceAccounts;
+  }
+
+  /** Builder for AltsClientOptions. */
+  public static final class Builder {
+    @Nullable private String targetName;
+    @Nullable private RpcProtocolVersions rpcProtocolVersions;
+    private ArrayList<String> targetServiceAccounts = new ArrayList<>();
+
+    public Builder setTargetName(String targetName) {
+      this.targetName = targetName;
+      return this;
+    }
+
+    public Builder setRpcProtocolVersions(RpcProtocolVersions rpcProtocolVersions) {
+      this.rpcProtocolVersions = rpcProtocolVersions;
+      return this;
+    }
+
+    public Builder addTargetServiceAccount(String targetServiceAccount) {
+      targetServiceAccounts.add(targetServiceAccount);
+      return this;
+    }
+
+    public AltsClientOptions build() {
+      return new AltsClientOptions(this);
+    }
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsFraming.java b/alts/src/main/java/io/grpc/alts/internal/AltsFraming.java
new file mode 100644
index 0000000..d243d95
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/AltsFraming.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import com.google.common.base.Preconditions;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.GeneralSecurityException;
+
+/** Framing and deframing methods and classes used by handshaker. */
+public final class AltsFraming {
+  // The size of the frame field. Must correspond to the size of int, 4 bytes.
+  // Left package-private for testing.
+  private static final int FRAME_LENGTH_HEADER_SIZE = 4;
+  private static final int FRAME_MESSAGE_TYPE_HEADER_SIZE = 4;
+  private static final int MAX_DATA_LENGTH = 1024 * 1024;
+  private static final int INITIAL_BUFFER_CAPACITY = 1024 * 64;
+
+  // TODO: Make this the responsibility of the caller.
+  private static final int MESSAGE_TYPE = 6;
+
+  private AltsFraming() {}
+
+  static int getFrameLengthHeaderSize() {
+    return FRAME_LENGTH_HEADER_SIZE;
+  }
+
+  static int getFrameMessageTypeHeaderSize() {
+    return FRAME_MESSAGE_TYPE_HEADER_SIZE;
+  }
+
+  static int getMaxDataLength() {
+    return MAX_DATA_LENGTH;
+  }
+
+  static int getFramingOverhead() {
+    return FRAME_LENGTH_HEADER_SIZE + FRAME_MESSAGE_TYPE_HEADER_SIZE;
+  }
+
+  /**
+   * Creates a frame of length dataSize + FRAME_HEADER_SIZE using the input bytes, if dataSize <=
+   * input.remaining(). Otherwise, a frame of length input.remaining() + FRAME_HEADER_SIZE is
+   * created.
+   */
+  static ByteBuffer toFrame(ByteBuffer input, int dataSize) throws GeneralSecurityException {
+    Preconditions.checkNotNull(input);
+    if (dataSize > input.remaining()) {
+      dataSize = input.remaining();
+    }
+    Producer producer = new Producer();
+    ByteBuffer inputAlias = input.duplicate();
+    inputAlias.limit(input.position() + dataSize);
+    producer.readBytes(inputAlias);
+    producer.flush();
+    input.position(inputAlias.position());
+    ByteBuffer output = producer.getRawFrame();
+    return output;
+  }
+
+  /**
+   * A helper class to write a frame.
+   *
+   * <p>This class guarantees that one of the following is true:
+   *
+   * <ul>
+   *   <li>readBytes will read from the input
+   *   <li>writeBytes will write to the output
+   * </ul>
+   *
+   * <p>Sample usage:
+   *
+   * <pre>{@code
+   * Producer producer = new Producer();
+   * ByteBuffer inputBuffer = readBytesFromMyStream();
+   * ByteBuffer outputBuffer = writeBytesToMyStream();
+   * while (inputBuffer.hasRemaining() || outputBuffer.hasRemaining()) {
+   *   producer.readBytes(inputBuffer);
+   *   producer.writeBytes(outputBuffer);
+   * }
+   * }</pre>
+   *
+   * <p>Alternatively, this class guarantees that one of the following is true:
+   *
+   * <ul>
+   *   <li>readBytes will read from the input
+   *   <li>{@code isComplete()} returns true and {@code getByteBuffer()} returns the contents of a
+   *       processed frame.
+   * </ul>
+   *
+   * <p>Sample usage:
+   *
+   * <pre>{@code
+   * Producer producer = new Producer();
+   * while (!producer.isComplete()) {
+   *   ByteBuffer inputBuffer = readBytesFromMyStream();
+   *   producer.readBytes(inputBuffer);
+   * }
+   * producer.flush();
+   * ByteBuffer outputBuffer = producer.getRawFrame();
+   * }</pre>
+   */
+  static final class Producer {
+    private ByteBuffer buffer;
+    private boolean isComplete;
+
+    Producer(int maxFrameSize) {
+      buffer = ByteBuffer.allocate(maxFrameSize);
+      reset();
+      Preconditions.checkArgument(maxFrameSize > getFramePrefixLength() + getFrameSuffixLength());
+    }
+
+    Producer() {
+      this(INITIAL_BUFFER_CAPACITY);
+    }
+
+    /** The length of the frame prefix data, including the message length/type fields. */
+    int getFramePrefixLength() {
+      int result = FRAME_LENGTH_HEADER_SIZE + FRAME_MESSAGE_TYPE_HEADER_SIZE;
+      return result;
+    }
+
+    int getFrameSuffixLength() {
+      return 0;
+    }
+
+    /**
+     * Reads bytes from input, parsing them into a frame. Returns false if and only if more data is
+     * needed. To obtain a full frame this method must be called repeatedly until it returns true.
+     */
+    boolean readBytes(ByteBuffer input) throws GeneralSecurityException {
+      Preconditions.checkNotNull(input);
+      if (isComplete) {
+        return true;
+      }
+      copy(buffer, input);
+      if (!buffer.hasRemaining()) {
+        flush();
+      }
+      return isComplete;
+    }
+
+    /**
+     * Completes the current frame, signaling that no further data is available to be passed to
+     * readBytes and that the client requires writeBytes to start returning data. isComplete() is
+     * guaranteed to return true after this call.
+     */
+    void flush() throws GeneralSecurityException {
+      if (isComplete) {
+        return;
+      }
+      // Get the length of the complete frame.
+      int frameLength = buffer.position() + getFrameSuffixLength();
+
+      // Set the limit and move to the start.
+      buffer.flip();
+
+      // Advance the limit to allow a crypto suffix.
+      buffer.limit(buffer.limit() + getFrameSuffixLength());
+
+      // Write the data length and the message type.
+      int dataLength = frameLength - FRAME_LENGTH_HEADER_SIZE;
+      buffer.order(ByteOrder.LITTLE_ENDIAN);
+      buffer.putInt(dataLength);
+      buffer.putInt(MESSAGE_TYPE);
+
+      // Move the position back to 0, the frame is ready.
+      buffer.position(0);
+      isComplete = true;
+    }
+
+    /** Resets the state, preparing to construct a new frame. Must be called between frames. */
+    private void reset() {
+      buffer.clear();
+
+      // Save some space for framing, we'll fill that in later.
+      buffer.position(getFramePrefixLength());
+      buffer.limit(buffer.limit() - getFrameSuffixLength());
+
+      isComplete = false;
+    }
+
+    /**
+     * Returns a ByteBuffer containing a complete raw frame, if it's available. Should only be
+     * called when isComplete() returns true, otherwise null is returned. The returned object
+     * aliases the internal buffer, that is, it shares memory with the internal buffer. No further
+     * operations are permitted on this object until the caller has processed the data it needs from
+     * the returned byte buffer.
+     */
+    ByteBuffer getRawFrame() {
+      if (!isComplete) {
+        return null;
+      }
+      ByteBuffer result = buffer.duplicate();
+      reset();
+      return result;
+    }
+  }
+
+  /**
+   * A helper class to read a frame.
+   *
+   * <p>This class guarantees that one of the following is true:
+   *
+   * <ul>
+   *   <li>readBytes will read from the input
+   *   <li>writeBytes will write to the output
+   * </ul>
+   *
+   * <p>Sample usage:
+   *
+   * <pre>{@code
+   * Parser parser = new Parser();
+   * ByteBuffer inputBuffer = readBytesFromMyStream();
+   * ByteBuffer outputBuffer = writeBytesToMyStream();
+   * while (inputBuffer.hasRemaining() || outputBuffer.hasRemaining()) {
+   *   parser.readBytes(inputBuffer);
+   *   parser.writeBytes(outputBuffer); }
+   * }</pre>
+   *
+   * <p>Alternatively, this class guarantees that one of the following is true:
+   *
+   * <ul>
+   *   <li>readBytes will read from the input
+   *   <li>{@code isComplete()} returns true and {@code getByteBuffer()} returns the contents of a
+   *       processed frame.
+   * </ul>
+   *
+   * <p>Sample usage:
+   *
+   * <pre>{@code
+   * Parser parser = new Parser();
+   * while (!parser.isComplete()) {
+   *   ByteBuffer inputBuffer = readBytesFromMyStream();
+   *   parser.readBytes(inputBuffer);
+   * }
+   * ByteBuffer outputBuffer = parser.getRawFrame();
+   * }</pre>
+   */
+  public static final class Parser {
+    private ByteBuffer buffer = ByteBuffer.allocate(INITIAL_BUFFER_CAPACITY);
+    private boolean isComplete = false;
+
+    public Parser() {
+      Preconditions.checkArgument(
+          INITIAL_BUFFER_CAPACITY > getFramePrefixLength() + getFrameSuffixLength());
+    }
+
+    /**
+     * Reads bytes from input, parsing them into a frame. Returns false if and only if more data is
+     * needed. To obtain a full frame this method must be called repeatedly until it returns true.
+     */
+    public boolean readBytes(ByteBuffer input) throws GeneralSecurityException {
+      Preconditions.checkNotNull(input);
+
+      if (isComplete) {
+        return true;
+      }
+
+      // Read enough bytes to determine the length
+      while (buffer.position() < FRAME_LENGTH_HEADER_SIZE && input.hasRemaining()) {
+        buffer.put(input.get());
+      }
+
+      // If we have enough bytes to determine the length, read the length and ensure that our
+      // internal buffer is large enough.
+      if (buffer.position() == FRAME_LENGTH_HEADER_SIZE && input.hasRemaining()) {
+        ByteBuffer bufferAlias = buffer.duplicate();
+        bufferAlias.flip();
+        bufferAlias.order(ByteOrder.LITTLE_ENDIAN);
+        int dataLength = bufferAlias.getInt();
+        if (dataLength < FRAME_MESSAGE_TYPE_HEADER_SIZE || dataLength > MAX_DATA_LENGTH) {
+          throw new IllegalArgumentException("Invalid frame length " + dataLength);
+        }
+        // Maybe resize the buffer
+        int frameLength = dataLength + FRAME_LENGTH_HEADER_SIZE;
+        if (buffer.capacity() < frameLength) {
+          buffer = ByteBuffer.allocate(frameLength);
+          buffer.order(ByteOrder.LITTLE_ENDIAN);
+          buffer.putInt(dataLength);
+        }
+        buffer.limit(frameLength);
+      }
+
+      // TODO: Similarly extract and check message type.
+
+      // Read the remaining data into the internal buffer.
+      copy(buffer, input);
+      if (!buffer.hasRemaining()) {
+        buffer.flip();
+        isComplete = true;
+      }
+      return isComplete;
+    }
+
+    /** The length of the frame prefix data, including the message length/type fields. */
+    int getFramePrefixLength() {
+      int result = FRAME_LENGTH_HEADER_SIZE + FRAME_MESSAGE_TYPE_HEADER_SIZE;
+      return result;
+    }
+
+    int getFrameSuffixLength() {
+      return 0;
+    }
+
+    /** Returns true if we've parsed a complete frame. */
+    public boolean isComplete() {
+      return isComplete;
+    }
+
+    /** Resets the state, preparing to parse a new frame. Must be called between frames. */
+    private void reset() {
+      buffer.clear();
+      isComplete = false;
+    }
+
+    /**
+     * Returns a ByteBuffer containing a complete raw frame, if it's available. Should only be
+     * called when isComplete() returns true, otherwise null is returned. The returned object
+     * aliases the internal buffer, that is, it shares memory with the internal buffer. No further
+     * operations are permitted on this object until the caller has processed the data it needs from
+     * the returned byte buffer.
+     */
+    public ByteBuffer getRawFrame() {
+      if (!isComplete) {
+        return null;
+      }
+      ByteBuffer result = buffer.duplicate();
+      reset();
+      return result;
+    }
+  }
+
+  /**
+   * Copy as much as possible to dst from src. Unlike {@link ByteBuffer#put(ByteBuffer)}, this stops
+   * early if there is no room left in dst.
+   */
+  private static void copy(ByteBuffer dst, ByteBuffer src) {
+    if (dst.hasRemaining() && src.hasRemaining()) {
+      // Avoid an allocation if possible.
+      if (dst.remaining() >= src.remaining()) {
+        dst.put(src);
+      } else {
+        int count = Math.min(dst.remaining(), src.remaining());
+        ByteBuffer slice = src.slice();
+        slice.limit(count);
+        dst.put(slice);
+        src.position(src.position() + count);
+      }
+    }
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerClient.java b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerClient.java
new file mode 100644
index 0000000..8f1d79b
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerClient.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.protobuf.ByteString;
+import io.grpc.Status;
+import io.grpc.alts.internal.Handshaker.HandshakeProtocol;
+import io.grpc.alts.internal.Handshaker.HandshakerReq;
+import io.grpc.alts.internal.Handshaker.HandshakerResp;
+import io.grpc.alts.internal.Handshaker.HandshakerResult;
+import io.grpc.alts.internal.Handshaker.HandshakerStatus;
+import io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq;
+import io.grpc.alts.internal.Handshaker.ServerHandshakeParameters;
+import io.grpc.alts.internal.Handshaker.StartClientHandshakeReq;
+import io.grpc.alts.internal.Handshaker.StartServerHandshakeReq;
+import io.grpc.alts.internal.HandshakerServiceGrpc.HandshakerServiceStub;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/** An API for conducting handshakes via ALTS handshaker service. */
+class AltsHandshakerClient {
+  private static final Logger logger = Logger.getLogger(AltsHandshakerClient.class.getName());
+
+  private static final String APPLICATION_PROTOCOL = "grpc";
+  private static final String RECORD_PROTOCOL = "ALTSRP_GCM_AES128_REKEY";
+  private static final int KEY_LENGTH = AltsChannelCrypter.getKeyLength();
+
+  private final AltsHandshakerStub handshakerStub;
+  private final AltsHandshakerOptions handshakerOptions;
+  private HandshakerResult result;
+  private HandshakerStatus status;
+
+  /** Starts a new handshake interacting with the handshaker service. */
+  AltsHandshakerClient(HandshakerServiceStub stub, AltsHandshakerOptions options) {
+    handshakerStub = new AltsHandshakerStub(stub);
+    handshakerOptions = options;
+  }
+
+  @VisibleForTesting
+  AltsHandshakerClient(AltsHandshakerStub handshakerStub, AltsHandshakerOptions options) {
+    this.handshakerStub = handshakerStub;
+    handshakerOptions = options;
+  }
+
+  static String getApplicationProtocol() {
+    return APPLICATION_PROTOCOL;
+  }
+
+  static String getRecordProtocol() {
+    return RECORD_PROTOCOL;
+  }
+
+  /** Sets the start client fields for the passed handshake request. */
+  private void setStartClientFields(HandshakerReq.Builder req) {
+    // Sets the default values.
+    StartClientHandshakeReq.Builder startClientReq =
+        StartClientHandshakeReq.newBuilder()
+            .setHandshakeSecurityProtocol(HandshakeProtocol.ALTS)
+            .addApplicationProtocols(APPLICATION_PROTOCOL)
+            .addRecordProtocols(RECORD_PROTOCOL);
+    // Sets handshaker options.
+    if (handshakerOptions.getRpcProtocolVersions() != null) {
+      startClientReq.setRpcVersions(handshakerOptions.getRpcProtocolVersions());
+    }
+    if (handshakerOptions instanceof AltsClientOptions) {
+      AltsClientOptions clientOptions = (AltsClientOptions) handshakerOptions;
+      if (!Strings.isNullOrEmpty(clientOptions.getTargetName())) {
+        startClientReq.setTargetName(clientOptions.getTargetName());
+      }
+      for (String serviceAccount : clientOptions.getTargetServiceAccounts()) {
+        startClientReq.addTargetIdentitiesBuilder().setServiceAccount(serviceAccount);
+      }
+    }
+    req.setClientStart(startClientReq);
+  }
+
+  /** Sets the start server fields for the passed handshake request. */
+  private void setStartServerFields(HandshakerReq.Builder req, ByteBuffer inBytes) {
+    ServerHandshakeParameters serverParameters =
+        ServerHandshakeParameters.newBuilder().addRecordProtocols(RECORD_PROTOCOL).build();
+    StartServerHandshakeReq.Builder startServerReq =
+        StartServerHandshakeReq.newBuilder()
+            .addApplicationProtocols(APPLICATION_PROTOCOL)
+            .putHandshakeParameters(HandshakeProtocol.ALTS.getNumber(), serverParameters)
+            .setInBytes(ByteString.copyFrom(inBytes.duplicate()));
+    if (handshakerOptions.getRpcProtocolVersions() != null) {
+      startServerReq.setRpcVersions(handshakerOptions.getRpcProtocolVersions());
+    }
+    req.setServerStart(startServerReq);
+  }
+
+  /** Returns true if the handshake is complete. */
+  public boolean isFinished() {
+    // If we have a HandshakeResult, we are done.
+    if (result != null) {
+      return true;
+    }
+    // If we have an error status, we are done.
+    if (status != null && status.getCode() != Status.Code.OK.value()) {
+      return true;
+    }
+    return false;
+  }
+
+  /** Returns the handshake status. */
+  public HandshakerStatus getStatus() {
+    return status;
+  }
+
+  /** Returns the result data of the handshake, if the handshake is completed. */
+  public HandshakerResult getResult() {
+    return result;
+  }
+
+  /**
+   * Returns the resulting key of the handshake, if the handshake is completed. Note that the key
+   * data returned from the handshake may be more than the key length required for the record
+   * protocol, thus we need to truncate to the right size.
+   */
+  public byte[] getKey() {
+    if (result == null) {
+      return null;
+    }
+    if (result.getKeyData().size() < KEY_LENGTH) {
+      throw new IllegalStateException("Could not get enough key data from the handshake.");
+    }
+    byte[] key = new byte[KEY_LENGTH];
+    result.getKeyData().copyTo(key, 0, 0, KEY_LENGTH);
+    return key;
+  }
+
+  /**
+   * Parses a handshake response, setting the status, result, and closing the handshaker, as needed.
+   */
+  private void handleResponse(HandshakerResp resp) throws GeneralSecurityException {
+    status = resp.getStatus();
+    if (resp.hasResult()) {
+      result = resp.getResult();
+      close();
+    }
+    if (status.getCode() != Status.Code.OK.value()) {
+      String error = "Handshaker service error: " + status.getDetails();
+      logger.log(Level.INFO, error);
+      close();
+      throw new GeneralSecurityException(error);
+    }
+  }
+
+  /**
+   * Starts a client handshake. A GeneralSecurityException is thrown if the handshaker service is
+   * interrupted or fails. Note that isFinished() must be false before this function is called.
+   *
+   * @return the frame to give to the peer.
+   * @throws GeneralSecurityException or IllegalStateException
+   */
+  public ByteBuffer startClientHandshake() throws GeneralSecurityException {
+    Preconditions.checkState(!isFinished(), "Handshake has already finished.");
+    HandshakerReq.Builder req = HandshakerReq.newBuilder();
+    setStartClientFields(req);
+    HandshakerResp resp;
+    try {
+      resp = handshakerStub.send(req.build());
+    } catch (IOException | InterruptedException e) {
+      throw new GeneralSecurityException(e);
+    }
+    handleResponse(resp);
+    return resp.getOutFrames().asReadOnlyByteBuffer();
+  }
+
+  /**
+   * Starts a server handshake. A GeneralSecurityException is thrown if the handshaker service is
+   * interrupted or fails. Note that isFinished() must be false before this function is called.
+   *
+   * @param inBytes the bytes received from the peer.
+   * @return the frame to give to the peer.
+   * @throws GeneralSecurityException or IllegalStateException
+   */
+  public ByteBuffer startServerHandshake(ByteBuffer inBytes) throws GeneralSecurityException {
+    Preconditions.checkState(!isFinished(), "Handshake has already finished.");
+    HandshakerReq.Builder req = HandshakerReq.newBuilder();
+    setStartServerFields(req, inBytes);
+    HandshakerResp resp;
+    try {
+      resp = handshakerStub.send(req.build());
+    } catch (IOException | InterruptedException e) {
+      throw new GeneralSecurityException(e);
+    }
+    handleResponse(resp);
+    inBytes.position(inBytes.position() + resp.getBytesConsumed());
+    return resp.getOutFrames().asReadOnlyByteBuffer();
+  }
+
+  /**
+   * Processes the next bytes in a handshake. A GeneralSecurityException is thrown if the handshaker
+   * service is interrupted or fails. Note that isFinished() must be false before this function is
+   * called.
+   *
+   * @param inBytes the bytes received from the peer.
+   * @return the frame to give to the peer.
+   * @throws GeneralSecurityException or IllegalStateException
+   */
+  public ByteBuffer next(ByteBuffer inBytes) throws GeneralSecurityException {
+    Preconditions.checkState(!isFinished(), "Handshake has already finished.");
+    HandshakerReq.Builder req =
+        HandshakerReq.newBuilder()
+            .setNext(
+                NextHandshakeMessageReq.newBuilder()
+                    .setInBytes(ByteString.copyFrom(inBytes.duplicate()))
+                    .build());
+    HandshakerResp resp;
+    try {
+      resp = handshakerStub.send(req.build());
+    } catch (IOException | InterruptedException e) {
+      throw new GeneralSecurityException(e);
+    }
+    handleResponse(resp);
+    inBytes.position(inBytes.position() + resp.getBytesConsumed());
+    return resp.getOutFrames().asReadOnlyByteBuffer();
+  }
+
+  /** Closes the connection. */
+  public void close() {
+    handshakerStub.close();
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerOptions.java b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerOptions.java
new file mode 100644
index 0000000..5e0dbdb
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerOptions.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions;
+import javax.annotation.Nullable;
+
+/** Handshaker options for creating ALTS channel. */
+public class AltsHandshakerOptions {
+  @Nullable private final RpcProtocolVersions rpcProtocolVersions;
+
+  public AltsHandshakerOptions(RpcProtocolVersions rpcProtocolVersions) {
+    this.rpcProtocolVersions = rpcProtocolVersions;
+  }
+
+  public RpcProtocolVersions getRpcProtocolVersions() {
+    return rpcProtocolVersions;
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java
new file mode 100644
index 0000000..ee979aa
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/AltsHandshakerStub.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
+import io.grpc.alts.internal.Handshaker.HandshakerReq;
+import io.grpc.alts.internal.Handshaker.HandshakerResp;
+import io.grpc.alts.internal.HandshakerServiceGrpc.HandshakerServiceStub;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.atomic.AtomicReference;
+
+/** An interface to the ALTS handshaker service. */
+class AltsHandshakerStub {
+  private final StreamObserver<HandshakerResp> reader = new Reader();
+  private final StreamObserver<HandshakerReq> writer;
+  private final ArrayBlockingQueue<Optional<HandshakerResp>> responseQueue =
+      new ArrayBlockingQueue<Optional<HandshakerResp>>(1);
+  private final AtomicReference<String> exceptionMessage = new AtomicReference<>();
+
+  AltsHandshakerStub(HandshakerServiceStub serviceStub) {
+    this.writer = serviceStub.doHandshake(this.reader);
+  }
+
+  @VisibleForTesting
+  AltsHandshakerStub() {
+    writer = null;
+  }
+
+  @VisibleForTesting
+  AltsHandshakerStub(StreamObserver<HandshakerReq> writer) {
+    this.writer = writer;
+  }
+
+  @VisibleForTesting
+  StreamObserver<HandshakerResp> getReaderForTest() {
+    return reader;
+  }
+
+  /** Send a handshaker request and return the handshaker response. */
+  public HandshakerResp send(HandshakerReq req) throws InterruptedException, IOException {
+    maybeThrowIoException();
+    if (!responseQueue.isEmpty()) {
+      throw new IOException("Received an unexpected response.");
+    }
+    writer.onNext(req);
+    Optional<HandshakerResp> result = responseQueue.take();
+    if (!result.isPresent()) {
+      maybeThrowIoException();
+    }
+    return result.get();
+  }
+
+  /** Throw exception if there is an outstanding exception. */
+  private void maybeThrowIoException() throws IOException {
+    if (exceptionMessage.get() != null) {
+      throw new IOException(exceptionMessage.get());
+    }
+  }
+
+  /** Close the connection. */
+  public void close() {
+    writer.onCompleted();
+  }
+
+  private class Reader implements StreamObserver<HandshakerResp> {
+    /** Receive a handshaker response from the server. */
+    @Override
+    public void onNext(HandshakerResp resp) {
+      try {
+        AltsHandshakerStub.this.responseQueue.add(Optional.of(resp));
+      } catch (IllegalStateException e) {
+        AltsHandshakerStub.this.exceptionMessage.compareAndSet(
+            null, "Received an unexpected response.");
+        AltsHandshakerStub.this.close();
+      }
+    }
+
+    /** Receive an error from the server. */
+    @Override
+    public void onError(Throwable t) {
+      AltsHandshakerStub.this.exceptionMessage.compareAndSet(
+          null, "Received a terminating error: " + t.toString());
+      // Trigger the release of any blocked send.
+      Optional<HandshakerResp> result = Optional.absent();
+      AltsHandshakerStub.this.responseQueue.offer(result);
+    }
+
+    /** Receive the closing message from the server. */
+    @Override
+    public void onCompleted() {
+      AltsHandshakerStub.this.exceptionMessage.compareAndSet(null, "Response stream closed.");
+      // Trigger the release of any blocked send.
+      Optional<HandshakerResp> result = Optional.absent();
+      AltsHandshakerStub.this.responseQueue.offer(result);
+    }
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsProtocolNegotiator.java b/alts/src/main/java/io/grpc/alts/internal/AltsProtocolNegotiator.java
new file mode 100644
index 0000000..f146dbc
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/AltsProtocolNegotiator.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.protobuf.Any;
+import io.grpc.Attributes;
+import io.grpc.CallCredentials;
+import io.grpc.Grpc;
+import io.grpc.InternalChannelz.OtherSecurity;
+import io.grpc.InternalChannelz.Security;
+import io.grpc.SecurityLevel;
+import io.grpc.Status;
+import io.grpc.alts.internal.RpcProtocolVersionsUtil.RpcVersionsCheckResult;
+import io.grpc.alts.internal.TsiHandshakeHandler.TsiHandshakeCompletionEvent;
+import io.grpc.netty.GrpcHttp2ConnectionHandler;
+import io.grpc.netty.ProtocolNegotiator;
+import io.grpc.netty.ProtocolNegotiators.AbstractBufferingHandler;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.util.AsciiString;
+
+/**
+ * A client-side GRPC {@link ProtocolNegotiator} for ALTS. This class creates a Netty handler that
+ * provides ALTS security on the wire, similar to Netty's {@code SslHandler}.
+ */
+public abstract class AltsProtocolNegotiator implements ProtocolNegotiator {
+
+  private static final Attributes.Key<TsiPeer> TSI_PEER_KEY = Attributes.Key.create("TSI_PEER");
+  private static final Attributes.Key<AltsAuthContext> ALTS_CONTEXT_KEY =
+      Attributes.Key.create("ALTS_CONTEXT_KEY");
+  private static final AsciiString scheme = AsciiString.of("https");
+
+  public static Attributes.Key<TsiPeer> getTsiPeerAttributeKey() {
+    return TSI_PEER_KEY;
+  }
+
+  public static Attributes.Key<AltsAuthContext> getAltsAuthContextAttributeKey() {
+    return ALTS_CONTEXT_KEY;
+  }
+
+  /** Creates a negotiator used for ALTS. */
+  public static AltsProtocolNegotiator create(final TsiHandshakerFactory handshakerFactory) {
+    return new AltsProtocolNegotiator() {
+      @Override
+      public Handler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
+        return new BufferUntilAltsNegotiatedHandler(
+            grpcHandler,
+            new TsiHandshakeHandler(new NettyTsiHandshaker(handshakerFactory.newHandshaker())),
+            new TsiFrameHandler());
+      }
+    };
+  }
+
+  /** Buffers all writes until the ALTS handshake is complete. */
+  @VisibleForTesting
+  static class BufferUntilAltsNegotiatedHandler extends AbstractBufferingHandler
+      implements ProtocolNegotiator.Handler {
+
+    private final GrpcHttp2ConnectionHandler grpcHandler;
+
+    BufferUntilAltsNegotiatedHandler(
+        GrpcHttp2ConnectionHandler grpcHandler, ChannelHandler... negotiationhandlers) {
+      super(negotiationhandlers);
+      // Save the gRPC handler. The ALTS handler doesn't support buffering before the handshake
+      // completes, so we wait until the handshake was successful before adding the grpc handler.
+      this.grpcHandler = grpcHandler;
+    }
+
+    // TODO: Remove this once https://github.com/grpc/grpc-java/pull/3715 is in.
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+      fail(ctx, cause);
+      ctx.fireExceptionCaught(cause);
+    }
+
+    @Override
+    public AsciiString scheme() {
+      return scheme;
+    }
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+      if (evt instanceof TsiHandshakeCompletionEvent) {
+        TsiHandshakeCompletionEvent altsEvt = (TsiHandshakeCompletionEvent) evt;
+        if (altsEvt.isSuccess()) {
+          // Add the gRPC handler just before this handler. We only allow the grpcHandler to be
+          // null to support testing. In production, a grpc handler will always be provided.
+          if (grpcHandler != null) {
+            ctx.pipeline().addBefore(ctx.name(), null, grpcHandler);
+            AltsAuthContext altsContext = (AltsAuthContext) altsEvt.context();
+            Preconditions.checkNotNull(altsContext);
+            // Checks peer Rpc Protocol Versions in the ALTS auth context. Fails the connection if
+            // Rpc Protocol Versions mismatch.
+            RpcVersionsCheckResult checkResult =
+                RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+                    RpcProtocolVersionsUtil.getRpcProtocolVersions(),
+                    altsContext.getPeerRpcVersions());
+            if (!checkResult.getResult()) {
+              String errorMessage =
+                  "Local Rpc Protocol Versions "
+                      + RpcProtocolVersionsUtil.getRpcProtocolVersions().toString()
+                      + "are not compatible with peer Rpc Protocol Versions "
+                      + altsContext.getPeerRpcVersions().toString();
+              fail(ctx, Status.UNAVAILABLE.withDescription(errorMessage).asRuntimeException());
+            }
+            grpcHandler.handleProtocolNegotiationCompleted(
+                Attributes.newBuilder()
+                    .set(TSI_PEER_KEY, altsEvt.peer())
+                    .set(ALTS_CONTEXT_KEY, altsContext)
+                    .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress())
+                    .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
+                    .build(),
+                new Security(new OtherSecurity("alts", Any.pack(altsContext.context))));
+          }
+
+          // Now write any buffered data and remove this handler.
+          writeBufferedAndRemove(ctx);
+        } else {
+          fail(ctx, unavailableException("ALTS handshake failed", altsEvt.cause()));
+        }
+      }
+      super.userEventTriggered(ctx, evt);
+    }
+
+    private static RuntimeException unavailableException(String msg, Throwable cause) {
+      return Status.UNAVAILABLE.withCause(cause).withDescription(msg).asRuntimeException();
+    }
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsTsiFrameProtector.java b/alts/src/main/java/io/grpc/alts/internal/AltsTsiFrameProtector.java
new file mode 100644
index 0000000..c112c31
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/AltsTsiFrameProtector.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Verify.verify;
+
+import com.google.common.primitives.Ints;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Frame protector that uses the ALTS framing. */
+public final class AltsTsiFrameProtector implements TsiFrameProtector {
+  private static final int HEADER_LEN_FIELD_BYTES = 4;
+  private static final int HEADER_TYPE_FIELD_BYTES = 4;
+  private static final int HEADER_BYTES = HEADER_LEN_FIELD_BYTES + HEADER_TYPE_FIELD_BYTES;
+  private static final int HEADER_TYPE_DEFAULT = 6;
+  // Total frame size including full header and tag.
+  private static final int MAX_ALLOWED_FRAME_BYTES = 16 * 1024;
+  private static final int LIMIT_MAX_ALLOWED_FRAME_BYTES = 1024 * 1024;
+
+  private final Protector protector;
+  private final Unprotector unprotector;
+
+  /** Create a new AltsTsiFrameProtector. */
+  public AltsTsiFrameProtector(
+      int maxProtectedFrameBytes, ChannelCrypterNetty crypter, ByteBufAllocator alloc) {
+    checkArgument(maxProtectedFrameBytes > HEADER_BYTES + crypter.getSuffixLength());
+    maxProtectedFrameBytes = Math.min(LIMIT_MAX_ALLOWED_FRAME_BYTES, maxProtectedFrameBytes);
+    protector = new Protector(maxProtectedFrameBytes, crypter);
+    unprotector = new Unprotector(crypter, alloc);
+  }
+
+  static int getHeaderLenFieldBytes() {
+    return HEADER_LEN_FIELD_BYTES;
+  }
+
+  static int getHeaderTypeFieldBytes() {
+    return HEADER_TYPE_FIELD_BYTES;
+  }
+
+  public static int getHeaderBytes() {
+    return HEADER_BYTES;
+  }
+
+  static int getHeaderTypeDefault() {
+    return HEADER_TYPE_DEFAULT;
+  }
+
+  public static int getMaxAllowedFrameBytes() {
+    return MAX_ALLOWED_FRAME_BYTES;
+  }
+
+  static int getLimitMaxAllowedFrameBytes() {
+    return LIMIT_MAX_ALLOWED_FRAME_BYTES;
+  }
+
+  @Override
+  public void protectFlush(
+      List<ByteBuf> unprotectedBufs, Consumer<ByteBuf> ctxWrite, ByteBufAllocator alloc)
+      throws GeneralSecurityException {
+    protector.protectFlush(unprotectedBufs, ctxWrite, alloc);
+  }
+
+  @Override
+  public void unprotect(ByteBuf in, List<Object> out, ByteBufAllocator alloc)
+      throws GeneralSecurityException {
+    unprotector.unprotect(in, out, alloc);
+  }
+
+  @Override
+  public void destroy() {
+    try {
+      unprotector.destroy();
+    } finally {
+      protector.destroy();
+    }
+  }
+
+  static final class Protector {
+    private final int maxUnprotectedBytesPerFrame;
+    private final int suffixBytes;
+    private ChannelCrypterNetty crypter;
+
+    Protector(int maxProtectedFrameBytes, ChannelCrypterNetty crypter) {
+      this.suffixBytes = crypter.getSuffixLength();
+      this.maxUnprotectedBytesPerFrame = maxProtectedFrameBytes - HEADER_BYTES - suffixBytes;
+      this.crypter = crypter;
+    }
+
+    void destroy() {
+      // Shared with Unprotector and destroyed there.
+      crypter = null;
+    }
+
+    void protectFlush(
+        List<ByteBuf> unprotectedBufs, Consumer<ByteBuf> ctxWrite, ByteBufAllocator alloc)
+        throws GeneralSecurityException {
+      checkState(crypter != null, "Cannot protectFlush after destroy.");
+      ByteBuf protectedBuf;
+      try {
+        protectedBuf = handleUnprotected(unprotectedBufs, alloc);
+      } finally {
+        for (ByteBuf buf : unprotectedBufs) {
+          buf.release();
+        }
+      }
+      if (protectedBuf != null) {
+        ctxWrite.accept(protectedBuf);
+      }
+    }
+
+    @SuppressWarnings("BetaApi") // verify is stable in Guava
+    private ByteBuf handleUnprotected(List<ByteBuf> unprotectedBufs, ByteBufAllocator alloc)
+        throws GeneralSecurityException {
+      long unprotectedBytes = 0;
+      for (ByteBuf buf : unprotectedBufs) {
+        unprotectedBytes += buf.readableBytes();
+      }
+      // Empty plaintext not allowed since this should be handled as no-op in layer above.
+      checkArgument(unprotectedBytes > 0);
+
+      // Compute number of frames and allocate a single buffer for all frames.
+      long frameNum = unprotectedBytes / maxUnprotectedBytesPerFrame + 1;
+      int lastFrameUnprotectedBytes = (int) (unprotectedBytes % maxUnprotectedBytesPerFrame);
+      if (lastFrameUnprotectedBytes == 0) {
+        frameNum--;
+        lastFrameUnprotectedBytes = maxUnprotectedBytesPerFrame;
+      }
+      long protectedBytes = frameNum * (HEADER_BYTES + suffixBytes) + unprotectedBytes;
+
+      ByteBuf protectedBuf = alloc.directBuffer(Ints.checkedCast(protectedBytes));
+      try {
+        int bufferIdx = 0;
+        for (int frameIdx = 0; frameIdx < frameNum; ++frameIdx) {
+          int unprotectedBytesLeft =
+              (frameIdx == frameNum - 1) ? lastFrameUnprotectedBytes : maxUnprotectedBytesPerFrame;
+          // Write header (at most LIMIT_MAX_ALLOWED_FRAME_BYTES).
+          protectedBuf.writeIntLE(unprotectedBytesLeft + HEADER_TYPE_FIELD_BYTES + suffixBytes);
+          protectedBuf.writeIntLE(HEADER_TYPE_DEFAULT);
+
+          // Ownership of the backing buffer remains with protectedBuf.
+          ByteBuf frameOut = writeSlice(protectedBuf, unprotectedBytesLeft + suffixBytes);
+          List<ByteBuf> framePlain = new ArrayList<>();
+          while (unprotectedBytesLeft > 0) {
+            // Ownership of the buffer backing in remains with unprotectedBufs.
+            ByteBuf in = unprotectedBufs.get(bufferIdx);
+            if (in.readableBytes() <= unprotectedBytesLeft) {
+              // The complete buffer belongs to this frame.
+              framePlain.add(in);
+              unprotectedBytesLeft -= in.readableBytes();
+              bufferIdx++;
+            } else {
+              // The remainder of in will be part of the next frame.
+              framePlain.add(in.readSlice(unprotectedBytesLeft));
+              unprotectedBytesLeft = 0;
+            }
+          }
+          crypter.encrypt(frameOut, framePlain);
+          verify(!frameOut.isWritable());
+        }
+        protectedBuf.readerIndex(0);
+        protectedBuf.writerIndex(protectedBuf.capacity());
+        return protectedBuf.retain();
+      } finally {
+        protectedBuf.release();
+      }
+    }
+  }
+
+  static final class Unprotector {
+    private final int suffixBytes;
+    private final ChannelCrypterNetty crypter;
+
+    private DeframerState state = DeframerState.READ_HEADER;
+    private int requiredProtectedBytes;
+    private ByteBuf header;
+    private ByteBuf firstFrameTag;
+    private int unhandledIdx = 0;
+    private long unhandledBytes = 0;
+    private List<ByteBuf> unhandledBufs = new ArrayList<>(16);
+
+    Unprotector(ChannelCrypterNetty crypter, ByteBufAllocator alloc) {
+      this.crypter = crypter;
+      this.suffixBytes = crypter.getSuffixLength();
+      this.header = alloc.directBuffer(HEADER_BYTES);
+      this.firstFrameTag = alloc.directBuffer(suffixBytes);
+    }
+
+    private void addUnhandled(ByteBuf in) {
+      if (in.isReadable()) {
+        ByteBuf buf = in.readRetainedSlice(in.readableBytes());
+        unhandledBufs.add(buf);
+        unhandledBytes += buf.readableBytes();
+      }
+    }
+
+    void unprotect(ByteBuf in, List<Object> out, ByteBufAllocator alloc)
+        throws GeneralSecurityException {
+      checkState(header != null, "Cannot unprotect after destroy.");
+      addUnhandled(in);
+      decodeFrame(alloc, out);
+    }
+
+    @SuppressWarnings("fallthrough")
+    private void decodeFrame(ByteBufAllocator alloc, List<Object> out)
+        throws GeneralSecurityException {
+      switch (state) {
+        case READ_HEADER:
+          if (unhandledBytes < HEADER_BYTES) {
+            return;
+          }
+          handleHeader();
+          // fall through
+        case READ_PROTECTED_PAYLOAD:
+          if (unhandledBytes < requiredProtectedBytes) {
+            return;
+          }
+          ByteBuf unprotectedBuf;
+          try {
+            unprotectedBuf = handlePayload(alloc);
+          } finally {
+            clearState();
+          }
+          if (unprotectedBuf != null) {
+            out.add(unprotectedBuf);
+          }
+          break;
+        default:
+          throw new AssertionError("impossible enum value");
+      }
+    }
+
+    private void handleHeader() {
+      while (header.isWritable()) {
+        ByteBuf in = unhandledBufs.get(unhandledIdx);
+        int headerBytesToRead = Math.min(in.readableBytes(), header.writableBytes());
+        header.writeBytes(in, headerBytesToRead);
+        unhandledBytes -= headerBytesToRead;
+        if (!in.isReadable()) {
+          unhandledIdx++;
+        }
+      }
+      requiredProtectedBytes = header.readIntLE() - HEADER_TYPE_FIELD_BYTES;
+      checkArgument(
+          requiredProtectedBytes >= suffixBytes, "Invalid header field: frame size too small");
+      checkArgument(
+          requiredProtectedBytes <= LIMIT_MAX_ALLOWED_FRAME_BYTES - HEADER_BYTES,
+          "Invalid header field: frame size too large");
+      int frameType = header.readIntLE();
+      checkArgument(frameType == HEADER_TYPE_DEFAULT, "Invalid header field: frame type");
+      state = DeframerState.READ_PROTECTED_PAYLOAD;
+    }
+
+    @SuppressWarnings("BetaApi") // verify is stable in Guava
+    private ByteBuf handlePayload(ByteBufAllocator alloc) throws GeneralSecurityException {
+      int requiredCiphertextBytes = requiredProtectedBytes - suffixBytes;
+      int firstFrameUnprotectedLen = requiredCiphertextBytes;
+
+      // We get the ciphertexts of the first frame and copy over the tag into a single buffer.
+      List<ByteBuf> firstFrameCiphertext = new ArrayList<>();
+      while (requiredCiphertextBytes > 0) {
+        ByteBuf buf = unhandledBufs.get(unhandledIdx);
+        if (buf.readableBytes() <= requiredCiphertextBytes) {
+          // We use the whole buffer.
+          firstFrameCiphertext.add(buf);
+          requiredCiphertextBytes -= buf.readableBytes();
+          unhandledIdx++;
+        } else {
+          firstFrameCiphertext.add(buf.readSlice(requiredCiphertextBytes));
+          requiredCiphertextBytes = 0;
+        }
+      }
+      int requiredSuffixBytes = suffixBytes;
+      while (true) {
+        ByteBuf buf = unhandledBufs.get(unhandledIdx);
+        if (buf.readableBytes() <= requiredSuffixBytes) {
+          // We use the whole buffer.
+          requiredSuffixBytes -= buf.readableBytes();
+          firstFrameTag.writeBytes(buf);
+          if (requiredSuffixBytes == 0) {
+            break;
+          }
+          unhandledIdx++;
+        } else {
+          firstFrameTag.writeBytes(buf, requiredSuffixBytes);
+          break;
+        }
+      }
+      verify(unhandledIdx == unhandledBufs.size() - 1);
+      ByteBuf lastBuf = unhandledBufs.get(unhandledIdx);
+
+      // We get the remaining ciphertexts and tags contained in the last buffer.
+      List<ByteBuf> ciphertextsAndTags = new ArrayList<>();
+      List<Integer> unprotectedLens = new ArrayList<>();
+      long requiredUnprotectedBytesCompleteFrames = firstFrameUnprotectedLen;
+      while (lastBuf.readableBytes() >= HEADER_BYTES + suffixBytes) {
+        // Read frame size.
+        int frameSize = lastBuf.readIntLE();
+        int payloadSize = frameSize - HEADER_TYPE_FIELD_BYTES - suffixBytes;
+        // Break and undo read if we don't have the complete frame yet.
+        if (lastBuf.readableBytes() < frameSize) {
+          lastBuf.readerIndex(lastBuf.readerIndex() - HEADER_LEN_FIELD_BYTES);
+          break;
+        }
+        // Check the type header.
+        checkArgument(lastBuf.readIntLE() == 6);
+        // Create a new frame (except for out buffer).
+        ciphertextsAndTags.add(lastBuf.readSlice(payloadSize + suffixBytes));
+        // Update sizes for frame.
+        requiredUnprotectedBytesCompleteFrames += payloadSize;
+        unprotectedLens.add(payloadSize);
+      }
+
+      // We leave space for suffixBytes to allow for in-place encryption. This allows for calling
+      // doFinal in the JCE implementation which can be optimized better than update and doFinal.
+      ByteBuf unprotectedBuf =
+          alloc.directBuffer(
+              Ints.checkedCast(requiredUnprotectedBytesCompleteFrames + suffixBytes));
+      try {
+
+        ByteBuf out = writeSlice(unprotectedBuf, firstFrameUnprotectedLen + suffixBytes);
+        crypter.decrypt(out, firstFrameTag, firstFrameCiphertext);
+        verify(out.writableBytes() == suffixBytes);
+        unprotectedBuf.writerIndex(unprotectedBuf.writerIndex() - suffixBytes);
+
+        for (int frameIdx = 0; frameIdx < ciphertextsAndTags.size(); ++frameIdx) {
+          out = writeSlice(unprotectedBuf, unprotectedLens.get(frameIdx) + suffixBytes);
+          crypter.decrypt(out, ciphertextsAndTags.get(frameIdx));
+          verify(out.writableBytes() == suffixBytes);
+          unprotectedBuf.writerIndex(unprotectedBuf.writerIndex() - suffixBytes);
+        }
+        return unprotectedBuf.retain();
+      } finally {
+        unprotectedBuf.release();
+      }
+    }
+
+    private void clearState() {
+      int bufsSize = unhandledBufs.size();
+      ByteBuf lastBuf = unhandledBufs.get(bufsSize - 1);
+      boolean keepLast = lastBuf.isReadable();
+      for (int bufIdx = 0; bufIdx < (keepLast ? bufsSize - 1 : bufsSize); ++bufIdx) {
+        unhandledBufs.get(bufIdx).release();
+      }
+      unhandledBufs.clear();
+      unhandledBytes = 0;
+      unhandledIdx = 0;
+      if (keepLast) {
+        unhandledBufs.add(lastBuf);
+        unhandledBytes = lastBuf.readableBytes();
+      }
+      state = DeframerState.READ_HEADER;
+      requiredProtectedBytes = 0;
+      header.clear();
+      firstFrameTag.clear();
+    }
+
+    void destroy() {
+      for (ByteBuf unhandledBuf : unhandledBufs) {
+        unhandledBuf.release();
+      }
+      unhandledBufs.clear();
+      if (header != null) {
+        header.release();
+        header = null;
+      }
+      if (firstFrameTag != null) {
+        firstFrameTag.release();
+        firstFrameTag = null;
+      }
+      crypter.destroy();
+    }
+  }
+
+  private enum DeframerState {
+    READ_HEADER,
+    READ_PROTECTED_PAYLOAD
+  }
+
+  private static ByteBuf writeSlice(ByteBuf in, int len) {
+    checkArgument(len <= in.writableBytes());
+    ByteBuf out = in.slice(in.writerIndex(), len);
+    in.writerIndex(in.writerIndex() + len);
+    return out.writerIndex(0);
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/AltsTsiHandshaker.java b/alts/src/main/java/io/grpc/alts/internal/AltsTsiHandshaker.java
new file mode 100644
index 0000000..3699abb
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/AltsTsiHandshaker.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import io.grpc.alts.internal.HandshakerServiceGrpc.HandshakerServiceStub;
+import io.netty.buffer.ByteBufAllocator;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Negotiates a grpc channel key to be used by the TsiFrameProtector, using ALTs handshaker service.
+ */
+public final class AltsTsiHandshaker implements TsiHandshaker {
+  public static final String TSI_SERVICE_ACCOUNT_PEER_PROPERTY = "service_account";
+
+  private final boolean isClient;
+  private final AltsHandshakerClient handshaker;
+
+  private ByteBuffer outputFrame;
+
+  /** Starts a new TSI handshaker with client options. */
+  private AltsTsiHandshaker(
+      boolean isClient, HandshakerServiceStub stub, AltsHandshakerOptions options) {
+    this.isClient = isClient;
+    handshaker = new AltsHandshakerClient(stub, options);
+  }
+
+  @VisibleForTesting
+  AltsTsiHandshaker(boolean isClient, AltsHandshakerClient handshaker) {
+    this.isClient = isClient;
+    this.handshaker = handshaker;
+  }
+
+  /**
+   * Process the bytes received from the peer.
+   *
+   * @param bytes The buffer containing the handshake bytes from the peer.
+   * @return true, if the handshake has all the data it needs to process and false, if the method
+   *     must be called again to complete processing.
+   */
+  @Override
+  public boolean processBytesFromPeer(ByteBuffer bytes) throws GeneralSecurityException {
+    // If we're the client and we haven't given an output frame, we shouldn't be processing any
+    // bytes.
+    if (outputFrame == null && isClient) {
+      return true;
+    }
+    // If we already have bytes to write, just return.
+    if (outputFrame != null && outputFrame.hasRemaining()) {
+      return true;
+    }
+    int remaining = bytes.remaining();
+    // Call handshaker service to proceess the bytes.
+    if (outputFrame == null) {
+      checkState(!isClient, "Client handshaker should not process any frame at the beginning.");
+      outputFrame = handshaker.startServerHandshake(bytes);
+    } else {
+      outputFrame = handshaker.next(bytes);
+    }
+    // If handshake has finished or we already have bytes to write, just return true.
+    if (handshaker.isFinished() || outputFrame.hasRemaining()) {
+      return true;
+    }
+    // We have done processing input bytes, but no bytes to write. Thus we need more data.
+    if (!bytes.hasRemaining()) {
+      return false;
+    }
+    // There are still remaining bytes. Thus we need to continue processing the bytes.
+    // Prevent infinite loop by checking some bytes are consumed by handshaker.
+    checkState(bytes.remaining() < remaining, "Handshaker did not consume any bytes.");
+    return processBytesFromPeer(bytes);
+  }
+
+  /**
+   * Returns the peer extracted from a completed handshake.
+   *
+   * @return the extracted peer.
+   */
+  @Override
+  public TsiPeer extractPeer() throws GeneralSecurityException {
+    Preconditions.checkState(!isInProgress(), "Handshake is not complete.");
+    List<TsiPeer.Property<?>> peerProperties = new ArrayList<TsiPeer.Property<?>>();
+    peerProperties.add(
+        new TsiPeer.StringProperty(
+            TSI_SERVICE_ACCOUNT_PEER_PROPERTY,
+            handshaker.getResult().getPeerIdentity().getServiceAccount()));
+    return new TsiPeer(peerProperties);
+  }
+
+  /**
+   * Returns the peer extracted from a completed handshake.
+   *
+   * @return the extracted peer.
+   */
+  @Override
+  public Object extractPeerObject() throws GeneralSecurityException {
+    Preconditions.checkState(!isInProgress(), "Handshake is not complete.");
+    return new AltsAuthContext(handshaker.getResult());
+  }
+
+  /** Creates a new TsiHandshaker for use by the client. */
+  public static TsiHandshaker newClient(HandshakerServiceStub stub, AltsHandshakerOptions options) {
+    return new AltsTsiHandshaker(true, stub, options);
+  }
+
+  /** Creates a new TsiHandshaker for use by the server. */
+  public static TsiHandshaker newServer(HandshakerServiceStub stub, AltsHandshakerOptions options) {
+    return new AltsTsiHandshaker(false, stub, options);
+  }
+
+  /**
+   * Gets bytes that need to be sent to the peer.
+   *
+   * @param bytes The buffer to put handshake bytes.
+   */
+  @Override
+  public void getBytesToSendToPeer(ByteBuffer bytes) throws GeneralSecurityException {
+    if (outputFrame == null) { // A null outputFrame indicates we haven't started the handshake.
+      if (isClient) {
+        outputFrame = handshaker.startClientHandshake();
+      } else {
+        // The server needs bytes to process before it can start the handshake.
+        return;
+      }
+    }
+    // Write as many bytes as we are able.
+    ByteBuffer outputFrameAlias = outputFrame;
+    if (outputFrame.remaining() > bytes.remaining()) {
+      outputFrameAlias = outputFrame.duplicate();
+      outputFrameAlias.limit(outputFrameAlias.position() + bytes.remaining());
+    }
+    bytes.put(outputFrameAlias);
+    outputFrame.position(outputFrameAlias.position());
+  }
+
+  /**
+   * Returns true if and only if the handshake is still in progress
+   *
+   * @return true, if the handshake is still in progress, false otherwise.
+   */
+  @Override
+  public boolean isInProgress() {
+    return !handshaker.isFinished() || outputFrame.hasRemaining();
+  }
+
+  /**
+   * Creates a frame protector from a completed handshake. No other methods may be called after the
+   * frame protector is created.
+   *
+   * @param maxFrameSize the requested max frame size, the callee is free to ignore.
+   * @param alloc used for allocating ByteBufs.
+   * @return a new TsiFrameProtector.
+   */
+  @Override
+  public TsiFrameProtector createFrameProtector(int maxFrameSize, ByteBufAllocator alloc) {
+    Preconditions.checkState(!isInProgress(), "Handshake is not complete.");
+
+    byte[] key = handshaker.getKey();
+    Preconditions.checkState(key.length == AltsChannelCrypter.getKeyLength(), "Bad key length.");
+
+    return new AltsTsiFrameProtector(maxFrameSize, new AltsChannelCrypter(key, isClient), alloc);
+  }
+
+  /**
+   * Creates a frame protector from a completed handshake. No other methods may be called after the
+   * frame protector is created.
+   *
+   * @param alloc used for allocating ByteBufs.
+   * @return a new TsiFrameProtector.
+   */
+  @Override
+  public TsiFrameProtector createFrameProtector(ByteBufAllocator alloc) {
+    return createFrameProtector(AltsTsiFrameProtector.getMaxAllowedFrameBytes(), alloc);
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/BufUnwrapper.java b/alts/src/main/java/io/grpc/alts/internal/BufUnwrapper.java
new file mode 100644
index 0000000..9934dd2
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/BufUnwrapper.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.CompositeByteBuf;
+import java.nio.ByteBuffer;
+
+/** Unwraps {@link ByteBuf}s into {@link ByteBuffer}s. */
+final class BufUnwrapper implements AutoCloseable {
+
+  private final ByteBuffer[] singleReadBuffer = new ByteBuffer[1];
+  private final ByteBuffer[] singleWriteBuffer = new ByteBuffer[1];
+
+  /**
+   * Called to get access to the underlying NIO buffers for a {@link ByteBuf} that will be used for
+   * writing.
+   */
+  ByteBuffer[] writableNioBuffers(ByteBuf buf) {
+    // Set the writer index to the capacity to guarantee that the returned NIO buffers will have
+    // the capacity available.
+    int readerIndex = buf.readerIndex();
+    int writerIndex = buf.writerIndex();
+    buf.readerIndex(writerIndex);
+    buf.writerIndex(buf.capacity());
+
+    try {
+      return nioBuffers(buf, singleWriteBuffer);
+    } finally {
+      // Restore the writer index before returning.
+      buf.readerIndex(readerIndex);
+      buf.writerIndex(writerIndex);
+    }
+  }
+
+  /**
+   * Called to get access to the underlying NIO buffers for a {@link ByteBuf} that will be used for
+   * reading.
+   */
+  ByteBuffer[] readableNioBuffers(ByteBuf buf) {
+    return nioBuffers(buf, singleReadBuffer);
+  }
+
+  @Override
+  public void close() {
+    singleReadBuffer[0] = null;
+    singleWriteBuffer[0] = null;
+  }
+
+  /**
+   * Optimized accessor for obtaining the underlying NIO buffers for a Netty {@link ByteBuf}. Based
+   * on code from Netty's {@code SslHandler}. This method returns NIO buffers that span the readable
+   * region of the {@link ByteBuf}.
+   */
+  private static ByteBuffer[] nioBuffers(ByteBuf buf, ByteBuffer[] singleBuffer) {
+    // As CompositeByteBuf.nioBufferCount() can be expensive (as it needs to check all composed
+    // ByteBuf to calculate the count) we will just assume a CompositeByteBuf contains more than 1
+    // ByteBuf. The worst that can happen is that we allocate an extra ByteBuffer[] in
+    // CompositeByteBuf.nioBuffers() which is better than walking the composed ByteBuf in most
+    // cases.
+    if (!(buf instanceof CompositeByteBuf) && buf.nioBufferCount() == 1) {
+      // We know its only backed by 1 ByteBuffer so use internalNioBuffer to keep object
+      // allocation to a minimum.
+      singleBuffer[0] = buf.internalNioBuffer(buf.readerIndex(), buf.readableBytes());
+      return singleBuffer;
+    }
+
+    return buf.nioBuffers();
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/ChannelCrypterNetty.java b/alts/src/main/java/io/grpc/alts/internal/ChannelCrypterNetty.java
new file mode 100644
index 0000000..4164560
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/ChannelCrypterNetty.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import io.netty.buffer.ByteBuf;
+import java.security.GeneralSecurityException;
+import java.util.List;
+
+/**
+ * A @{code ChannelCrypterNetty} performs stateful encryption and decryption of independent input
+ * and output streams. Both decrypt and encrypt gather their input from a list of Netty @{link
+ * ByteBuf} instances.
+ *
+ * <p>Note that we provide implementations of this interface that provide integrity only and
+ * implementations that provide privacy and integrity. All methods should be thread-compatible.
+ */
+public interface ChannelCrypterNetty {
+
+  /**
+   * Encrypt plaintext into output buffer.
+   *
+   * @param out the protected input will be written into this buffer. The buffer must be direct and
+   *     have enough space to hold all input buffers and the tag. Encrypt does not take ownership of
+   *     this buffer.
+   * @param plain the input buffers that should be protected. Encrypt does not modify or take
+   *     ownership of these buffers.
+   */
+  void encrypt(ByteBuf out, List<ByteBuf> plain) throws GeneralSecurityException;
+
+  /**
+   * Decrypt ciphertext into the given output buffer and check tag.
+   *
+   * @param out the unprotected input will be written into this buffer. The buffer must be direct
+   *     and have enough space to hold all ciphertext buffers and the tag, i.e., it must have
+   *     additional space for the tag, even though this space will be unused in the final result.
+   *     Decrypt does not take ownership of this buffer.
+   * @param tag the tag appended to the ciphertext. Decrypt does not modify or take ownership of
+   *     this buffer.
+   * @param ciphertext the buffers that should be unprotected (excluding the tag). Decrypt does not
+   *     modify or take ownership of these buffers.
+   */
+  void decrypt(ByteBuf out, ByteBuf tag, List<ByteBuf> ciphertext) throws GeneralSecurityException;
+
+  /**
+   * Decrypt ciphertext into the given output buffer and check tag.
+   *
+   * @param out the unprotected input will be written into this buffer. The buffer must be direct
+   *     and have enough space to hold all ciphertext buffers and the tag, i.e., it must have
+   *     additional space for the tag, even though this space will be unused in the final result.
+   *     Decrypt does not take ownership of this buffer.
+   * @param ciphertextAndTag single buffer containing ciphertext and tag that should be unprotected.
+   *     The buffer must be direct and either completely overlap with {@code out} or not overlap at
+   *     all.
+   */
+  void decrypt(ByteBuf out, ByteBuf ciphertextAndTag) throws GeneralSecurityException;
+
+  /** Returns the length of the tag in bytes. */
+  int getSuffixLength();
+
+  /** Must be called to release all associated resources (instance cannot be used afterwards). */
+  void destroy();
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiator.java b/alts/src/main/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiator.java
new file mode 100644
index 0000000..8428894
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.internal.GrpcAttributes;
+import io.grpc.netty.GrpcHttp2ConnectionHandler;
+import io.grpc.netty.ProtocolNegotiator;
+import io.grpc.netty.ProtocolNegotiators;
+import io.netty.handler.ssl.SslContext;
+
+/** A client-side GPRC {@link ProtocolNegotiator} for Google Default Channel. */
+public final class GoogleDefaultProtocolNegotiator implements ProtocolNegotiator {
+  private final ProtocolNegotiator altsProtocolNegotiator;
+  private final ProtocolNegotiator tlsProtocolNegotiator;
+
+  public GoogleDefaultProtocolNegotiator(TsiHandshakerFactory altsFactory, SslContext sslContext) {
+    altsProtocolNegotiator = AltsProtocolNegotiator.create(altsFactory);
+    tlsProtocolNegotiator = ProtocolNegotiators.tls(sslContext);
+  }
+
+  @VisibleForTesting
+  GoogleDefaultProtocolNegotiator(
+      ProtocolNegotiator altsProtocolNegotiator, ProtocolNegotiator tlsProtocolNegotiator) {
+    this.altsProtocolNegotiator = altsProtocolNegotiator;
+    this.tlsProtocolNegotiator = tlsProtocolNegotiator;
+  }
+
+  @Override
+  public Handler newHandler(GrpcHttp2ConnectionHandler grpcHandler) {
+    if (grpcHandler.getEagAttributes().get(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY) != null
+        || grpcHandler.getEagAttributes().get(GrpcAttributes.ATTR_LB_PROVIDED_BACKEND) != null) {
+      return altsProtocolNegotiator.newHandler(grpcHandler);
+    } else {
+      return tlsProtocolNegotiator.newHandler(grpcHandler);
+    }
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/NettyTsiHandshaker.java b/alts/src/main/java/io/grpc/alts/internal/NettyTsiHandshaker.java
new file mode 100644
index 0000000..8d4bbd1
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/NettyTsiHandshaker.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+
+/**
+ * A wrapper for a {@link io.grpc.alts.internal.TsiHandshaker} that accepts netty {@link ByteBuf}s.
+ */
+public final class NettyTsiHandshaker {
+
+  private BufUnwrapper unwrapper = new BufUnwrapper();
+  private final TsiHandshaker internalHandshaker;
+
+  public NettyTsiHandshaker(TsiHandshaker handshaker) {
+    internalHandshaker = checkNotNull(handshaker);
+  }
+
+  /**
+   * Gets data that is ready to be sent to the to the remote peer. This should be called in a loop
+   * until no bytes are written to the output buffer.
+   *
+   * @param out the buffer to receive the bytes.
+   */
+  void getBytesToSendToPeer(ByteBuf out) throws GeneralSecurityException {
+    checkState(unwrapper != null, "protector already created");
+    try (BufUnwrapper unwrapper = this.unwrapper) {
+      // Write as many bytes as possible into the buffer.
+      int bytesWritten = 0;
+      for (ByteBuffer nioBuffer : unwrapper.writableNioBuffers(out)) {
+        if (!nioBuffer.hasRemaining()) {
+          // This buffer doesn't have any more space to write, go to the next buffer.
+          continue;
+        }
+
+        int prevPos = nioBuffer.position();
+        internalHandshaker.getBytesToSendToPeer(nioBuffer);
+        bytesWritten += nioBuffer.position() - prevPos;
+
+        // If the buffer position was not changed, the frame has been completely read into the
+        // buffers.
+        if (nioBuffer.position() == prevPos) {
+          break;
+        }
+      }
+
+      out.writerIndex(out.writerIndex() + bytesWritten);
+    }
+  }
+
+  /**
+   * Process handshake data received from the remote peer.
+   *
+   * @return {@code true}, if the handshake has all the data it needs to process and {@code false},
+   *     if the method must be called again to complete processing.
+   */
+  boolean processBytesFromPeer(ByteBuf data) throws GeneralSecurityException {
+    checkState(unwrapper != null, "protector already created");
+    try (BufUnwrapper unwrapper = this.unwrapper) {
+      int bytesRead = 0;
+      boolean done = false;
+      for (ByteBuffer nioBuffer : unwrapper.readableNioBuffers(data)) {
+        if (!nioBuffer.hasRemaining()) {
+          // This buffer has been fully read, continue to the next buffer.
+          continue;
+        }
+
+        int prevPos = nioBuffer.position();
+        done = internalHandshaker.processBytesFromPeer(nioBuffer);
+        bytesRead += nioBuffer.position() - prevPos;
+        if (done) {
+          break;
+        }
+      }
+
+      data.readerIndex(data.readerIndex() + bytesRead);
+      return done;
+    }
+  }
+
+  /**
+   * Returns true if and only if the handshake is still in progress
+   *
+   * @return true, if the handshake is still in progress, false otherwise.
+   */
+  boolean isInProgress() {
+    return internalHandshaker.isInProgress();
+  }
+
+  /**
+   * Returns the peer extracted from a completed handshake.
+   *
+   * @return the extracted peer.
+   */
+  TsiPeer extractPeer() throws GeneralSecurityException {
+    checkState(!internalHandshaker.isInProgress());
+    return internalHandshaker.extractPeer();
+  }
+
+  /**
+   * Returns the peer extracted from a completed handshake.
+   *
+   * @return the extracted peer.
+   */
+  Object extractPeerObject() throws GeneralSecurityException {
+    checkState(!internalHandshaker.isInProgress());
+    return internalHandshaker.extractPeerObject();
+  }
+
+  /**
+   * Creates a frame protector from a completed handshake. No other methods may be called after the
+   * frame protector is created.
+   *
+   * @param maxFrameSize the requested max frame size, the callee is free to ignore.
+   * @return a new {@link io.grpc.alts.internal.TsiFrameProtector}.
+   */
+  TsiFrameProtector createFrameProtector(int maxFrameSize, ByteBufAllocator alloc) {
+    unwrapper = null;
+    return internalHandshaker.createFrameProtector(maxFrameSize, alloc);
+  }
+
+  /**
+   * Creates a frame protector from a completed handshake. No other methods may be called after the
+   * frame protector is created.
+   *
+   * @return a new {@link io.grpc.alts.internal.TsiFrameProtector}.
+   */
+  TsiFrameProtector createFrameProtector(ByteBufAllocator alloc) {
+    unwrapper = null;
+    return internalHandshaker.createFrameProtector(alloc);
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/ProtectedPromise.java b/alts/src/main/java/io/grpc/alts/internal/ProtectedPromise.java
new file mode 100644
index 0000000..a19a816
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/ProtectedPromise.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.DefaultChannelPromise;
+import io.netty.util.concurrent.EventExecutor;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Promise used when flushing the {@code pendingUnprotectedWrites} queue. It manages the many-to
+ * many relationship between pending unprotected messages and the individual writes. Each protected
+ * frame will be written using the same instance of this promise and it will accumulate the results.
+ * Once all frames have been successfully written (or any failed), all of the promises for the
+ * pending unprotected writes are notified.
+ *
+ * <p>NOTE: this code is based on code in Netty's {@code Http2CodecUtil}.
+ */
+final class ProtectedPromise extends DefaultChannelPromise {
+  private final List<ChannelPromise> unprotectedPromises;
+  private int expectedCount;
+  private int successfulCount;
+  private int failureCount;
+  private boolean doneAllocating;
+
+  ProtectedPromise(Channel channel, EventExecutor executor, int numUnprotectedPromises) {
+    super(channel, executor);
+    unprotectedPromises = new ArrayList<>(numUnprotectedPromises);
+  }
+
+  /**
+   * Adds a promise for a pending unprotected write. This will be notified after all of the writes
+   * complete.
+   */
+  void addUnprotectedPromise(ChannelPromise promise) {
+    unprotectedPromises.add(promise);
+  }
+
+  /**
+   * Allocate a new promise for the write of a protected frame. This will be used to aggregate the
+   * overall success of the unprotected promises.
+   *
+   * @return {@code this} promise.
+   */
+  ChannelPromise newPromise() {
+    checkState(!doneAllocating, "Done allocating. No more promises can be allocated.");
+    expectedCount++;
+    return this;
+  }
+
+  /**
+   * Signify that no more {@link #newPromise()} allocations will be made. The aggregation can not be
+   * successful until this method is called.
+   *
+   * @return {@code this} promise.
+   */
+  ChannelPromise doneAllocatingPromises() {
+    if (!doneAllocating) {
+      doneAllocating = true;
+      if (successfulCount == expectedCount) {
+        trySuccessInternal(null);
+        return super.setSuccess(null);
+      }
+    }
+    return this;
+  }
+
+  @Override
+  public boolean tryFailure(Throwable cause) {
+    if (awaitingPromises()) {
+      ++failureCount;
+      if (failureCount == 1) {
+        tryFailureInternal(cause);
+        return super.tryFailure(cause);
+      }
+      // TODO: We break the interface a bit here.
+      // Multiple failure events can be processed without issue because this is an aggregation.
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Fail this object if it has not already been failed.
+   *
+   * <p>This method will NOT throw an {@link IllegalStateException} if called multiple times because
+   * that may be expected.
+   */
+  @Override
+  public ChannelPromise setFailure(Throwable cause) {
+    tryFailure(cause);
+    return this;
+  }
+
+  private boolean awaitingPromises() {
+    return successfulCount + failureCount < expectedCount;
+  }
+
+  @Override
+  public ChannelPromise setSuccess(Void result) {
+    trySuccess(result);
+    return this;
+  }
+
+  @Override
+  public boolean trySuccess(Void result) {
+    if (awaitingPromises()) {
+      ++successfulCount;
+      if (successfulCount == expectedCount && doneAllocating) {
+        trySuccessInternal(result);
+        return super.trySuccess(result);
+      }
+      // TODO: We break the interface a bit here.
+      // Multiple success events can be processed without issue because this is an aggregation.
+      return true;
+    }
+    return false;
+  }
+
+  private void trySuccessInternal(Void result) {
+    for (int i = 0; i < unprotectedPromises.size(); ++i) {
+      unprotectedPromises.get(i).trySuccess(result);
+    }
+  }
+
+  private void tryFailureInternal(Throwable cause) {
+    for (int i = 0; i < unprotectedPromises.size(); ++i) {
+      unprotectedPromises.get(i).tryFailure(cause);
+    }
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/RpcProtocolVersionsUtil.java b/alts/src/main/java/io/grpc/alts/internal/RpcProtocolVersionsUtil.java
new file mode 100644
index 0000000..b685b6b
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/RpcProtocolVersionsUtil.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions;
+import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version;
+import javax.annotation.Nullable;
+
+/** Utility class for Rpc Protocol Versions. */
+public final class RpcProtocolVersionsUtil {
+
+  private static final int MAX_RPC_VERSION_MAJOR = 2;
+  private static final int MAX_RPC_VERSION_MINOR = 1;
+  private static final int MIN_RPC_VERSION_MAJOR = 2;
+  private static final int MIN_RPC_VERSION_MINOR = 1;
+  private static final RpcProtocolVersions RPC_PROTOCOL_VERSIONS =
+      RpcProtocolVersions.newBuilder()
+          .setMaxRpcVersion(
+              RpcProtocolVersions.Version.newBuilder()
+                  .setMajor(MAX_RPC_VERSION_MAJOR)
+                  .setMinor(MAX_RPC_VERSION_MINOR)
+                  .build())
+          .setMinRpcVersion(
+              RpcProtocolVersions.Version.newBuilder()
+                  .setMajor(MIN_RPC_VERSION_MAJOR)
+                  .setMinor(MIN_RPC_VERSION_MINOR)
+                  .build())
+          .build();
+
+  /** Returns default Rpc Protocol Versions. */
+  public static RpcProtocolVersions getRpcProtocolVersions() {
+    return RPC_PROTOCOL_VERSIONS;
+  }
+
+  /**
+   * Returns true if first Rpc Protocol Version is greater than or equal to the second one. Returns
+   * false otherwise.
+   */
+  @VisibleForTesting
+  static boolean isGreaterThanOrEqualTo(Version first, Version second) {
+    if ((first.getMajor() > second.getMajor())
+        || (first.getMajor() == second.getMajor() && first.getMinor() >= second.getMinor())) {
+      return true;
+    }
+    return false;
+  }
+
+  /**
+   * Performs check between local and peer Rpc Protocol Versions. This function returns true and the
+   * highest common version if there exists a common Rpc Protocol Version to use, and returns false
+   * and null otherwise.
+   */
+  static RpcVersionsCheckResult checkRpcProtocolVersions(
+      RpcProtocolVersions localVersions, RpcProtocolVersions peerVersions) {
+    Version maxCommonVersion;
+    Version minCommonVersion;
+    // maxCommonVersion is MIN(local.max, peer.max)
+    if (isGreaterThanOrEqualTo(localVersions.getMaxRpcVersion(), peerVersions.getMaxRpcVersion())) {
+      maxCommonVersion = peerVersions.getMaxRpcVersion();
+    } else {
+      maxCommonVersion = localVersions.getMaxRpcVersion();
+    }
+    // minCommonVersion is MAX(local.min, peer.min)
+    if (isGreaterThanOrEqualTo(localVersions.getMinRpcVersion(), peerVersions.getMinRpcVersion())) {
+      minCommonVersion = localVersions.getMinRpcVersion();
+    } else {
+      minCommonVersion = peerVersions.getMinRpcVersion();
+    }
+    if (isGreaterThanOrEqualTo(maxCommonVersion, minCommonVersion)) {
+      return new RpcVersionsCheckResult.Builder()
+          .setResult(true)
+          .setHighestCommonVersion(maxCommonVersion)
+          .build();
+    }
+    return new RpcVersionsCheckResult.Builder().setResult(false).build();
+  }
+
+  /** Wrapper class that stores results of Rpc Protocol Versions check. */
+  static final class RpcVersionsCheckResult {
+    private final boolean result;
+    @Nullable private final Version highestCommonVersion;
+
+    private RpcVersionsCheckResult(Builder builder) {
+      result = builder.result;
+      highestCommonVersion = builder.highestCommonVersion;
+    }
+
+    boolean getResult() {
+      return result;
+    }
+
+    Version getHighestCommonVersion() {
+      return highestCommonVersion;
+    }
+
+    static final class Builder {
+      private boolean result;
+      @Nullable private Version highestCommonVersion = null;
+
+      public Builder setResult(boolean result) {
+        this.result = result;
+        return this;
+      }
+
+      public Builder setHighestCommonVersion(Version highestCommonVersion) {
+        this.highestCommonVersion = highestCommonVersion;
+        return this;
+      }
+
+      public RpcVersionsCheckResult build() {
+        return new RpcVersionsCheckResult(this);
+      }
+    }
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/TsiFrameHandler.java b/alts/src/main/java/io/grpc/alts/internal/TsiFrameHandler.java
new file mode 100644
index 0000000..60ce859
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/TsiFrameHandler.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.alts.internal.TsiFrameProtector.Consumer;
+import io.grpc.alts.internal.TsiHandshakeHandler.TsiHandshakeCompletionEvent;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelException;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelOutboundHandler;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.PendingWriteQueue;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import java.net.SocketAddress;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Future;
+
+/**
+ * Encrypts and decrypts TSI Frames. Writes are buffered here until {@link #flush} is called. Writes
+ * must not be made before the TSI handshake is complete.
+ */
+public final class TsiFrameHandler extends ByteToMessageDecoder implements ChannelOutboundHandler {
+
+  private TsiFrameProtector protector;
+  private PendingWriteQueue pendingUnprotectedWrites;
+
+  public TsiFrameHandler() {}
+
+  @Override
+  public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+    super.handlerAdded(ctx);
+    assert pendingUnprotectedWrites == null;
+    pendingUnprotectedWrites = new PendingWriteQueue(checkNotNull(ctx));
+  }
+
+  @Override
+  public void userEventTriggered(ChannelHandlerContext ctx, Object event) throws Exception {
+    if (event instanceof TsiHandshakeCompletionEvent) {
+      TsiHandshakeCompletionEvent tsiEvent = (TsiHandshakeCompletionEvent) event;
+      if (tsiEvent.isSuccess()) {
+        setProtector(tsiEvent.protector());
+      }
+      // Ignore errors.  Another handler in the pipeline must handle TSI Errors.
+    }
+    // Keep propagating the message, as others may want to read it.
+    super.userEventTriggered(ctx, event);
+  }
+
+  @VisibleForTesting
+  void setProtector(TsiFrameProtector protector) {
+    checkState(this.protector == null);
+    this.protector = checkNotNull(protector);
+  }
+
+  @Override
+  protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+    checkState(protector != null, "Cannot read frames while the TSI handshake is in progress");
+    protector.unprotect(in, out, ctx.alloc());
+  }
+
+  @Override
+  public void write(ChannelHandlerContext ctx, Object message, ChannelPromise promise)
+      throws Exception {
+    checkState(protector != null, "Cannot write frames while the TSI handshake is in progress");
+    ByteBuf msg = (ByteBuf) message;
+    if (!msg.isReadable()) {
+      // Nothing to encode.
+      @SuppressWarnings("unused") // go/futurereturn-lsc
+      Future<?> possiblyIgnoredError = promise.setSuccess();
+      return;
+    }
+
+    // Just add the message to the pending queue. We'll write it on the next flush.
+    pendingUnprotectedWrites.add(msg, promise);
+  }
+
+  @Override
+  public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
+    if (!pendingUnprotectedWrites.isEmpty()) {
+      pendingUnprotectedWrites.removeAndFailAll(
+          new ChannelException("Pending write on removal of TSI handler"));
+    }
+  }
+
+  @Override
+  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+    pendingUnprotectedWrites.removeAndFailAll(cause);
+    super.exceptionCaught(ctx, cause);
+  }
+
+  @Override
+  public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
+    ctx.bind(localAddress, promise);
+  }
+
+  @Override
+  public void connect(
+      ChannelHandlerContext ctx,
+      SocketAddress remoteAddress,
+      SocketAddress localAddress,
+      ChannelPromise promise) {
+    ctx.connect(remoteAddress, localAddress, promise);
+  }
+
+  @Override
+  public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) {
+    ctx.disconnect(promise);
+  }
+
+  @Override
+  public void close(ChannelHandlerContext ctx, ChannelPromise promise) {
+    ctx.close(promise);
+  }
+
+  @Override
+  public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) {
+    ctx.deregister(promise);
+  }
+
+  @Override
+  public void read(ChannelHandlerContext ctx) {
+    ctx.read();
+  }
+
+  @Override
+  public void flush(final ChannelHandlerContext ctx) throws GeneralSecurityException {
+    checkState(protector != null, "Cannot write frames while the TSI handshake is in progress");
+    final ProtectedPromise aggregatePromise =
+        new ProtectedPromise(ctx.channel(), ctx.executor(), pendingUnprotectedWrites.size());
+
+    List<ByteBuf> bufs = new ArrayList<>(pendingUnprotectedWrites.size());
+
+    if (pendingUnprotectedWrites.isEmpty()) {
+      // Return early if there's nothing to write. Otherwise protector.protectFlush() below may
+      // not check for "no-data" and go on writing the 0-byte "data" to the socket with the
+      // protection framing.
+      return;
+    }
+    // Drain the unprotected writes.
+    while (!pendingUnprotectedWrites.isEmpty()) {
+      ByteBuf in = (ByteBuf) pendingUnprotectedWrites.current();
+      bufs.add(in.retain());
+      // Remove and release the buffer and add its promise to the aggregate.
+      aggregatePromise.addUnprotectedPromise(pendingUnprotectedWrites.remove());
+    }
+
+    protector.protectFlush(
+        bufs,
+        new Consumer<ByteBuf>() {
+          @Override
+          public void accept(ByteBuf b) {
+            ctx.writeAndFlush(b, aggregatePromise.newPromise());
+          }
+        },
+        ctx.alloc());
+
+    // We're done writing, start the flow of promise events.
+    @SuppressWarnings("unused") // go/futurereturn-lsc
+    Future<?> possiblyIgnoredError = aggregatePromise.doneAllocatingPromises();
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/TsiFrameProtector.java b/alts/src/main/java/io/grpc/alts/internal/TsiFrameProtector.java
new file mode 100644
index 0000000..b422798
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/TsiFrameProtector.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import java.security.GeneralSecurityException;
+import java.util.List;
+
+/**
+ * This object protects and unprotects netty buffers once the handshake is done.
+ *
+ * <p>Implementations of this object must be thread compatible.
+ */
+public interface TsiFrameProtector {
+
+  /**
+   * Protects the buffers by performing framing and encrypting/appending MACs.
+   *
+   * @param unprotectedBufs contain the payload that will be protected
+   * @param ctxWrite is called with buffers containing protected frames and must release the given
+   *     buffers
+   * @param alloc is used to allocate new buffers for the protected frames
+   */
+  void protectFlush(
+      List<ByteBuf> unprotectedBufs, Consumer<ByteBuf> ctxWrite, ByteBufAllocator alloc)
+      throws GeneralSecurityException;
+
+  /**
+   * Unprotects the buffers by removing the framing and decrypting/checking MACs.
+   *
+   * @param in contains (partial) protected frames
+   * @param out is only used to append unprotected payload buffers
+   * @param alloc is used to allocate new buffers for the unprotected frames
+   */
+  void unprotect(ByteBuf in, List<Object> out, ByteBufAllocator alloc)
+      throws GeneralSecurityException;
+
+  /** Must be called to release all associated resources (instance cannot be used afterwards). */
+  void destroy();
+
+  /** A mirror of java.util.function.Consumer without the Java 8 dependency. */
+  interface Consumer<T> {
+    void accept(T t);
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/TsiHandshakeHandler.java b/alts/src/main/java/io/grpc/alts/internal/TsiHandshakeHandler.java
new file mode 100644
index 0000000..2cc66b0
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/TsiHandshakeHandler.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import io.netty.util.ReferenceCountUtil;
+import java.security.GeneralSecurityException;
+import java.util.List;
+import java.util.concurrent.Future;
+import javax.annotation.Nullable;
+
+/**
+ * Performs The TSI Handshake. When the handshake is complete, it fires a user event with a {@link
+ * TsiHandshakeCompletionEvent} indicating the result of the handshake.
+ */
+public final class TsiHandshakeHandler extends ByteToMessageDecoder {
+  private static final int HANDSHAKE_FRAME_SIZE = 1024;
+
+  private final NettyTsiHandshaker handshaker;
+  private boolean started;
+
+  /**
+   * This buffer doesn't store any state. We just hold onto it in case we end up allocating a buffer
+   * that ends up being unused.
+   */
+  private ByteBuf buffer;
+
+  public TsiHandshakeHandler(NettyTsiHandshaker handshaker) {
+    this.handshaker = checkNotNull(handshaker);
+  }
+
+  /**
+   * Event that is fired once the TSI handshake is complete, which may be because it was successful
+   * or there was an error.
+   */
+  public static final class TsiHandshakeCompletionEvent {
+
+    private final Throwable cause;
+    private final TsiPeer peer;
+    private final Object context;
+    private final TsiFrameProtector protector;
+
+    /** Creates a new event that indicates a successful handshake. */
+    @VisibleForTesting
+    TsiHandshakeCompletionEvent(
+        TsiFrameProtector protector, TsiPeer peer, @Nullable Object peerObject) {
+      this.cause = null;
+      this.peer = checkNotNull(peer);
+      this.protector = checkNotNull(protector);
+      this.context = peerObject;
+    }
+
+    /** Creates a new event that indicates an unsuccessful handshake/. */
+    TsiHandshakeCompletionEvent(Throwable cause) {
+      this.cause = checkNotNull(cause);
+      this.peer = null;
+      this.protector = null;
+      this.context = null;
+    }
+
+    /** Return {@code true} if the handshake was successful. */
+    public boolean isSuccess() {
+      return cause == null;
+    }
+
+    /**
+     * Return the {@link Throwable} if {@link #isSuccess()} returns {@code false} and so the
+     * handshake failed.
+     */
+    @Nullable
+    public Throwable cause() {
+      return cause;
+    }
+
+    @Nullable
+    public TsiPeer peer() {
+      return peer;
+    }
+
+    @Nullable
+    public Object context() {
+      return context;
+    }
+
+    @Nullable
+    TsiFrameProtector protector() {
+      return protector;
+    }
+  }
+
+  @Override
+  public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+    maybeStart(ctx);
+    super.handlerAdded(ctx);
+  }
+
+  @Override
+  public void channelActive(ChannelHandlerContext ctx) throws Exception {
+    maybeStart(ctx);
+    super.channelActive(ctx);
+  }
+
+  @Override
+  public void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
+    close();
+    super.handlerRemoved0(ctx);
+  }
+
+  @Override
+  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+    ctx.fireUserEventTriggered(new TsiHandshakeCompletionEvent(cause));
+    super.exceptionCaught(ctx, cause);
+  }
+
+  @Override
+  protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
+      throws Exception {
+    // TODO: Not sure why override is needed. Investigate if it can be removed.
+    decode(ctx, in, out);
+  }
+
+  @Override
+  protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
+    // Process the data. If we need to send more data, do so now.
+    if (handshaker.processBytesFromPeer(in) && handshaker.isInProgress()) {
+      sendHandshake(ctx);
+    }
+
+    // If the handshake is complete, transition to the framing state.
+    if (!handshaker.isInProgress()) {
+      try {
+        ctx.pipeline().remove(this);
+        ctx.fireUserEventTriggered(
+            new TsiHandshakeCompletionEvent(
+                handshaker.createFrameProtector(ctx.alloc()),
+                handshaker.extractPeer(),
+                handshaker.extractPeerObject()));
+        // No need to do anything with the in buffer, it will be re added to the pipeline when this
+        // handler is removed.
+      } finally {
+        close();
+      }
+    }
+  }
+
+  private void maybeStart(ChannelHandlerContext ctx) {
+    if (!started && ctx.channel().isActive()) {
+      started = true;
+      sendHandshake(ctx);
+    }
+  }
+
+  /** Sends as many bytes as are available from the handshaker to the remote peer. */
+  private void sendHandshake(ChannelHandlerContext ctx) {
+    boolean needToFlush = false;
+
+    // Iterate until there is nothing left to write.
+    while (true) {
+      buffer = getOrCreateBuffer(ctx.alloc());
+      try {
+        handshaker.getBytesToSendToPeer(buffer);
+      } catch (GeneralSecurityException e) {
+        throw new RuntimeException(e);
+      }
+      if (!buffer.isReadable()) {
+        break;
+      }
+
+      needToFlush = true;
+      @SuppressWarnings("unused") // go/futurereturn-lsc
+      Future<?> possiblyIgnoredError = ctx.write(buffer);
+      buffer = null;
+    }
+
+    // If something was written, flush.
+    if (needToFlush) {
+      ctx.flush();
+    }
+  }
+
+  private ByteBuf getOrCreateBuffer(ByteBufAllocator alloc) {
+    if (buffer == null) {
+      buffer = alloc.buffer(HANDSHAKE_FRAME_SIZE);
+    }
+    return buffer;
+  }
+
+  private void close() {
+    ReferenceCountUtil.safeRelease(buffer);
+    buffer = null;
+  }
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/TsiHandshaker.java b/alts/src/main/java/io/grpc/alts/internal/TsiHandshaker.java
new file mode 100644
index 0000000..967582a
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/TsiHandshaker.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import io.netty.buffer.ByteBufAllocator;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+
+/**
+ * This object protects and unprotects buffers once the handshake is done.
+ *
+ * <p>A typical usage of this object would be:
+ *
+ * <pre>{@code
+ * ByteBuffer buffer = allocateDirect(ALLOCATE_SIZE);
+ * while (true) {
+ *   while (true) {
+ *     tsiHandshaker.getBytesToSendToPeer(buffer.clear());
+ *     if (!buffer.hasRemaining()) break;
+ *     yourTransportSendMethod(buffer.flip());
+ *     assert(!buffer.hasRemaining());  // Guaranteed by yourTransportReceiveMethod(...)
+ *   }
+ *   if (!tsiHandshaker.isInProgress()) break;
+ *   while (true) {
+ *     assert(!buffer.hasRemaining());
+ *     yourTransportReceiveMethod(buffer.clear());
+ *     if (tsiHandshaker.processBytesFromPeer(buffer.flip())) break;
+ *   }
+ *   if (!tsiHandshaker.isInProgress()) break;
+ *   assert(!buffer.hasRemaining());
+ * }
+ * yourCheckPeerMethod(tsiHandshaker.extractPeer());
+ * TsiFrameProtector tsiFrameProtector = tsiHandshaker.createFrameProtector(MAX_FRAME_SIZE);
+ * if (buffer.hasRemaining()) tsiFrameProtector.unprotect(buffer, messageBuffer);
+ * }</pre>
+ *
+ * <p>Implementations of this object must be thread compatible.
+ */
+public interface TsiHandshaker {
+  /**
+   * Gets bytes that need to be sent to the peer.
+   *
+   * @param bytes The buffer to put handshake bytes.
+   */
+  void getBytesToSendToPeer(ByteBuffer bytes) throws GeneralSecurityException;
+
+  /**
+   * Process the bytes received from the peer.
+   *
+   * @param bytes The buffer containing the handshake bytes from the peer.
+   * @return true, if the handshake has all the data it needs to process and false, if the method
+   *     must be called again to complete processing.
+   */
+  boolean processBytesFromPeer(ByteBuffer bytes) throws GeneralSecurityException;
+
+  /**
+   * Returns true if and only if the handshake is still in progress
+   *
+   * @return true, if the handshake is still in progress, false otherwise.
+   */
+  boolean isInProgress();
+
+  /**
+   * Returns the peer extracted from a completed handshake.
+   *
+   * @return the extracted peer.
+   */
+  TsiPeer extractPeer() throws GeneralSecurityException;
+
+  /**
+   * Returns the peer extracted from a completed handshake.
+   *
+   * @return the extracted peer.
+   */
+  public Object extractPeerObject() throws GeneralSecurityException;
+
+  /**
+   * Creates a frame protector from a completed handshake. No other methods may be called after the
+   * frame protector is created.
+   *
+   * @param maxFrameSize the requested max frame size, the callee is free to ignore.
+   * @param alloc used for allocating ByteBufs.
+   * @return a new TsiFrameProtector.
+   */
+  TsiFrameProtector createFrameProtector(int maxFrameSize, ByteBufAllocator alloc);
+
+  /**
+   * Creates a frame protector from a completed handshake. No other methods may be called after the
+   * frame protector is created.
+   *
+   * @param alloc used for allocating ByteBufs.
+   * @return a new TsiFrameProtector.
+   */
+  TsiFrameProtector createFrameProtector(ByteBufAllocator alloc);
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/TsiHandshakerFactory.java b/alts/src/main/java/io/grpc/alts/internal/TsiHandshakerFactory.java
new file mode 100644
index 0000000..58e3726
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/TsiHandshakerFactory.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+/** Factory that manufactures instances of {@link TsiHandshaker}. */
+public interface TsiHandshakerFactory {
+
+  /** Creates a new handshaker. */
+  TsiHandshaker newHandshaker();
+}
diff --git a/alts/src/main/java/io/grpc/alts/internal/TsiPeer.java b/alts/src/main/java/io/grpc/alts/internal/TsiPeer.java
new file mode 100644
index 0000000..990cf0b
--- /dev/null
+++ b/alts/src/main/java/io/grpc/alts/internal/TsiPeer.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nonnull;
+
+/** A set of peer properties. */
+public final class TsiPeer {
+  private final List<Property<?>> properties;
+
+  public TsiPeer(List<Property<?>> properties) {
+    this.properties = Collections.unmodifiableList(properties);
+  }
+
+  public List<Property<?>> getProperties() {
+    return properties;
+  }
+
+  /** Get peer property. */
+  public Property<?> getProperty(String name) {
+    for (Property<?> property : properties) {
+      if (property.getName().equals(name)) {
+        return property;
+      }
+    }
+    return null;
+  }
+
+  @Override
+  public String toString() {
+    return new ArrayList<>(properties).toString();
+  }
+
+  /** A peer property. */
+  public abstract static class Property<T> {
+    private final String name;
+    private final T value;
+
+    public Property(@Nonnull String name, @Nonnull T value) {
+      this.name = name;
+      this.value = value;
+    }
+
+    public final T getValue() {
+      return value;
+    }
+
+    public final String getName() {
+      return name;
+    }
+
+    @Override
+    public String toString() {
+      return String.format("%s=%s", name, value);
+    }
+  }
+
+  /** A peer property corresponding to a signed 64-bit integer. */
+  public static final class SignedInt64Property extends Property<Long> {
+    public SignedInt64Property(@Nonnull String name, @Nonnull Long value) {
+      super(name, value);
+    }
+  }
+
+  /** A peer property corresponding to an unsigned 64-bit integer. */
+  public static final class UnsignedInt64Property extends Property<BigInteger> {
+    public UnsignedInt64Property(@Nonnull String name, @Nonnull BigInteger value) {
+      super(name, value);
+    }
+  }
+
+  /** A peer property corresponding to a double. */
+  public static final class DoubleProperty extends Property<Double> {
+    public DoubleProperty(@Nonnull String name, @Nonnull Double value) {
+      super(name, value);
+    }
+  }
+
+  /** A peer property corresponding to a string. */
+  public static final class StringProperty extends Property<String> {
+    public StringProperty(@Nonnull String name, @Nonnull String value) {
+      super(name, value);
+    }
+  }
+
+  /** A peer property corresponding to a list of peer properties. */
+  public static final class PropertyList extends Property<List<Property<?>>> {
+    public PropertyList(@Nonnull String name, @Nonnull List<Property<?>> value) {
+      super(name, value);
+    }
+  }
+}
diff --git a/alts/src/main/proto/grpc/gcp/altscontext.proto b/alts/src/main/proto/grpc/gcp/altscontext.proto
new file mode 100644
index 0000000..9568c07
--- /dev/null
+++ b/alts/src/main/proto/grpc/gcp/altscontext.proto
@@ -0,0 +1,41 @@
+// Copyright 2018 The gRPC Authors
+//
+// Licensed 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.
+
+syntax = "proto3";
+
+package grpc.gcp;
+
+import "grpc/gcp/transport_security_common.proto";
+
+option java_package = "io.grpc.alts.internal";
+
+message AltsContext {
+  // The application protocol negotiated for this connection.
+  string application_protocol = 1;
+
+  // The record protocol negotiated for this connection.
+  string record_protocol = 2;
+
+  // The security level of the created secure channel.
+  SecurityLevel security_level = 3;
+
+  // The peer service account.
+  string peer_service_account = 4;
+
+  // The local service account.
+  string local_service_account = 5;
+
+  // The RPC protocol versions supported by the peer.
+  RpcProtocolVersions peer_rpc_versions = 6;
+}
diff --git a/alts/src/main/proto/grpc/gcp/handshaker.proto b/alts/src/main/proto/grpc/gcp/handshaker.proto
new file mode 100644
index 0000000..3a1fd71
--- /dev/null
+++ b/alts/src/main/proto/grpc/gcp/handshaker.proto
@@ -0,0 +1,224 @@
+// Copyright 2018 The gRPC Authors
+//
+// Licensed 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.
+
+syntax = "proto3";
+
+package grpc.gcp;
+
+import "grpc/gcp/transport_security_common.proto";
+
+option java_package = "io.grpc.alts.internal";
+
+enum HandshakeProtocol {
+  // Default value.
+  HANDSHAKE_PROTOCOL_UNSPECIFIED = 0;
+
+  // TLS handshake protocol.
+  TLS = 1;
+
+  // Application Layer Transport Security handshake protocol.
+  ALTS = 2;
+}
+
+enum NetworkProtocol {
+  NETWORK_PROTOCOL_UNSPECIFIED = 0;
+  TCP = 1;
+  UDP = 2;
+}
+
+message Endpoint {
+  // IP address. It should contain an IPv4 or IPv6 string literal, e.g.
+  // "192.168.0.1" or "2001:db8::1".
+  string ip_address = 1;
+
+  // Port number.
+  int32 port = 2;
+
+  // Network protocol (e.g., TCP, UDP) associated with this endpoint.
+  NetworkProtocol protocol = 3;
+}
+
+message Identity {
+  oneof identity_oneof {
+    // Service account of a connection endpoint.
+    string service_account = 1;
+
+    // Hostname of a connection endpoint.
+    string hostname = 2;
+  }
+}
+
+message StartClientHandshakeReq {
+  // Handshake security protocol requested by the client.
+  HandshakeProtocol handshake_security_protocol = 1;
+
+  // The application protocols supported by the client, e.g., "h2" (for http2),
+  // "grpc".
+  repeated string application_protocols = 2;
+
+  // The record protocols supported by the client, e.g.,
+  // "ALTSRP_GCM_AES128".
+  repeated string record_protocols = 3;
+
+  // (Optional) Describes which server identities are acceptable by the client.
+  // If target identities are provided and none of them matches the peer
+  // identity of the server, handshake will fail.
+  repeated Identity target_identities = 4;
+
+  // (Optional) Application may specify a local identity. Otherwise, the
+  // handshaker chooses a default local identity.
+  Identity local_identity = 5;
+
+  // (Optional) Local endpoint information of the connection to the server,
+  // such as local IP address, port number, and network protocol.
+  Endpoint local_endpoint = 6;
+
+  // (Optional) Endpoint information of the remote server, such as IP address,
+  // port number, and network protocol.
+  Endpoint remote_endpoint = 7;
+
+  // (Optional) If target name is provided, a secure naming check is performed
+  // to verify that the peer authenticated identity is indeed authorized to run
+  // the target name.
+  string target_name = 8;
+
+  // (Optional) RPC protocol versions supported by the client.
+  RpcProtocolVersions rpc_versions = 9;
+}
+
+message ServerHandshakeParameters {
+  // The record protocols supported by the server, e.g.,
+  // "ALTSRP_GCM_AES128".
+  repeated string record_protocols = 1;
+
+  // (Optional) A list of local identities supported by the server, if
+  // specified. Otherwise, the handshaker chooses a default local identity.
+  repeated Identity local_identities = 2;
+}
+
+message StartServerHandshakeReq {
+  // The application protocols supported by the server, e.g., "h2" (for http2),
+  // "grpc".
+  repeated string application_protocols = 1;
+
+  // Handshake parameters (record protocols and local identities supported by
+  // the server) mapped by the handshake protocol. Each handshake security
+  // protocol (e.g., TLS or ALTS) has its own set of record protocols and local
+  // identities. Since protobuf does not support enum as key to the map, the key
+  // to handshake_parameters is the integer value of HandshakeProtocol enum.
+  map<int32, ServerHandshakeParameters> handshake_parameters = 2;
+
+  // Bytes in out_frames returned from the peer's HandshakerResp. It is possible
+  // that the peer's out_frames are split into multiple HandshakReq messages.
+  bytes in_bytes = 3;
+
+  // (Optional) Local endpoint information of the connection to the client,
+  // such as local IP address, port number, and network protocol.
+  Endpoint local_endpoint = 4;
+
+  // (Optional) Endpoint information of the remote client, such as IP address,
+  // port number, and network protocol.
+  Endpoint remote_endpoint = 5;
+
+  // (Optional) RPC protocol versions supported by the server.
+  RpcProtocolVersions rpc_versions = 6;
+}
+
+message NextHandshakeMessageReq {
+  // Bytes in out_frames returned from the peer's HandshakerResp. It is possible
+  // that the peer's out_frames are split into multiple NextHandshakerMessageReq
+  // messages.
+  bytes in_bytes = 1;
+}
+
+message HandshakerReq {
+  oneof req_oneof {
+    // The start client handshake request message.
+    StartClientHandshakeReq client_start = 1;
+
+    // The start server handshake request message.
+    StartServerHandshakeReq server_start = 2;
+
+    // The next handshake request message.
+    NextHandshakeMessageReq next = 3;
+  }
+}
+
+message HandshakerResult {
+  // The application protocol negotiated for this connection.
+  string application_protocol = 1;
+
+  // The record protocol negotiated for this connection.
+  string record_protocol = 2;
+
+  // Cryptographic key data. The key data may be more than the key length
+  // required for the record protocol, thus the client of the handshaker
+  // service needs to truncate the key data into the right key length.
+  bytes key_data = 3;
+
+  // The authenticated identity of the peer.
+  Identity peer_identity = 4;
+
+  // The local identity used in the handshake.
+  Identity local_identity = 5;
+
+  // Indicate whether the handshaker service client should keep the channel
+  // between the handshaker service open, e.g., in order to handle
+  // post-handshake messages in the future.
+  bool keep_channel_open = 6;
+
+  // The RPC protocol versions supported by the peer.
+  RpcProtocolVersions peer_rpc_versions = 7;
+}
+
+message HandshakerStatus {
+  // The status code. This could be the gRPC status code.
+  uint32 code = 1;
+
+  // The status details.
+  string details = 2;
+}
+
+message HandshakerResp {
+  // Frames to be given to the peer for the NextHandshakeMessageReq. May be
+  // empty if no out_frames have to be sent to the peer or if in_bytes in the
+  // HandshakerReq are incomplete. All the non-empty out frames must be sent to
+  // the peer even if the handshaker status is not OK as these frames may
+  // contain the alert frames.
+  bytes out_frames = 1;
+
+  // Number of bytes in the in_bytes consumed by the handshaker. It is possible
+  // that part of in_bytes in HandshakerReq was unrelated to the handshake
+  // process.
+  uint32 bytes_consumed = 2;
+
+  // This is set iff the handshake was successful. out_frames may still be set
+  // to frames that needs to be forwarded to the peer.
+  HandshakerResult result = 3;
+
+  // Status of the handshaker.
+  HandshakerStatus status = 4;
+}
+
+service HandshakerService {
+  // Handshaker service accepts a stream of handshaker request, returning a
+  // stream of handshaker response. Client is expected to send exactly one
+  // message with either client_start or server_start followed by one or more
+  // messages with next. Each time client sends a request, the handshaker
+  // service expects to respond. Client does not have to wait for service's
+  // response before sending next request.
+  rpc DoHandshake(stream HandshakerReq)
+      returns (stream HandshakerResp) {
+  }
+}
diff --git a/alts/src/main/proto/grpc/gcp/transport_security_common.proto b/alts/src/main/proto/grpc/gcp/transport_security_common.proto
new file mode 100644
index 0000000..e101971
--- /dev/null
+++ b/alts/src/main/proto/grpc/gcp/transport_security_common.proto
@@ -0,0 +1,40 @@
+// Copyright 2018 The gRPC Authors
+//
+// Licensed 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.
+
+syntax = "proto3";
+
+package grpc.gcp;
+
+option java_package = "io.grpc.alts.internal";
+
+// The security level of the created channel. The list is sorted in increasing
+// level of security. This order must always be maintained.
+enum SecurityLevel {
+  SECURITY_NONE = 0;
+  INTEGRITY_ONLY = 1;
+  INTEGRITY_AND_PRIVACY = 2;
+}
+
+// Max and min supported RPC protocol versions.
+message RpcProtocolVersions {
+  // RPC version contains a major version and a minor version.
+  message Version {
+    uint32 major = 1;
+    uint32 minor = 2;
+  }
+  // Maximum supported RPC version.
+  Version max_rpc_version = 1;
+  // Minimum supported RPC version.
+  Version min_rpc_version = 2;
+}
diff --git a/alts/src/test/java/io/grpc/alts/AltsChannelBuilderTest.java b/alts/src/test/java/io/grpc/alts/AltsChannelBuilderTest.java
new file mode 100644
index 0000000..747992a
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/AltsChannelBuilderTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.grpc.alts.internal.AltsClientOptions;
+import io.grpc.alts.internal.AltsProtocolNegotiator;
+import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions;
+import io.grpc.netty.InternalNettyChannelBuilder.TransportCreationParamsFilterFactory;
+import io.grpc.netty.ProtocolNegotiator;
+import java.net.InetSocketAddress;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class AltsChannelBuilderTest {
+
+  @Test
+  public void buildsNettyChannel() throws Exception {
+    AltsChannelBuilder builder =
+        AltsChannelBuilder.forTarget("localhost:8080").enableUntrustedAltsForTesting();
+
+    TransportCreationParamsFilterFactory tcpfFactory = builder.getTcpfFactoryForTest();
+    AltsClientOptions altsClientOptions = builder.getAltsClientOptionsForTest();
+
+    assertThat(tcpfFactory).isNull();
+    assertThat(altsClientOptions).isNull();
+
+    builder.build();
+
+    tcpfFactory = builder.getTcpfFactoryForTest();
+    altsClientOptions = builder.getAltsClientOptionsForTest();
+
+    assertThat(tcpfFactory).isNotNull();
+    ProtocolNegotiator protocolNegotiator =
+        tcpfFactory
+            .create(new InetSocketAddress(8080), "fakeAuthority", "fakeUserAgent", null)
+            .getProtocolNegotiator();
+    assertThat(protocolNegotiator).isInstanceOf(AltsProtocolNegotiator.class);
+
+    assertThat(altsClientOptions).isNotNull();
+    RpcProtocolVersions expectedVersions =
+        RpcProtocolVersions.newBuilder()
+            .setMaxRpcVersion(
+                RpcProtocolVersions.Version.newBuilder().setMajor(2).setMinor(1).build())
+            .setMinRpcVersion(
+                RpcProtocolVersions.Version.newBuilder().setMajor(2).setMinor(1).build())
+            .build();
+    assertThat(altsClientOptions.getRpcProtocolVersions()).isEqualTo(expectedVersions);
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/AltsServerBuilderTest.java b/alts/src/test/java/io/grpc/alts/AltsServerBuilderTest.java
new file mode 100644
index 0000000..f729a8b
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/AltsServerBuilderTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class AltsServerBuilderTest {
+
+  @Test
+  public void buildsNettyServer() throws Exception {
+    AltsServerBuilder.forPort(1234).enableUntrustedAltsForTesting().build();
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/CheckGcpEnvironmentTest.java b/alts/src/test/java/io/grpc/alts/CheckGcpEnvironmentTest.java
new file mode 100644
index 0000000..bef16f7
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/CheckGcpEnvironmentTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.StringReader;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class CheckGcpEnvironmentTest {
+
+  @Test
+  public void checkGcpLinuxPlatformData() throws Exception {
+    BufferedReader reader;
+    reader = new BufferedReader(new StringReader("HP Z440 Workstation"));
+    assertFalse(CheckGcpEnvironment.checkProductNameOnLinux(reader));
+    reader = new BufferedReader(new StringReader("Google"));
+    assertTrue(CheckGcpEnvironment.checkProductNameOnLinux(reader));
+    reader = new BufferedReader(new StringReader("Google Compute Engine"));
+    assertTrue(CheckGcpEnvironment.checkProductNameOnLinux(reader));
+    reader = new BufferedReader(new StringReader("Google Compute Engine    "));
+    assertTrue(CheckGcpEnvironment.checkProductNameOnLinux(reader));
+  }
+
+  @Test
+  public void checkGcpWindowsPlatformData() throws Exception {
+    BufferedReader reader;
+    reader = new BufferedReader(new StringReader("Product : Google"));
+    assertFalse(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
+    reader = new BufferedReader(new StringReader("Manufacturer : LENOVO"));
+    assertFalse(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
+    reader = new BufferedReader(new StringReader("Manufacturer : Google Compute Engine"));
+    assertFalse(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
+    reader = new BufferedReader(new StringReader("Manufacturer : Google"));
+    assertTrue(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
+    reader = new BufferedReader(new StringReader("Manufacturer:Google"));
+    assertTrue(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
+    reader = new BufferedReader(new StringReader("Manufacturer :   Google    "));
+    assertTrue(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
+    reader = new BufferedReader(new StringReader("BIOSVersion : 1.0\nManufacturer : Google\n"));
+    assertTrue(CheckGcpEnvironment.checkBiosDataOnWindows(reader));
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/GoogleDefaultChannelBuilderTest.java b/alts/src/test/java/io/grpc/alts/GoogleDefaultChannelBuilderTest.java
new file mode 100644
index 0000000..b681c73
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/GoogleDefaultChannelBuilderTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.grpc.alts.internal.GoogleDefaultProtocolNegotiator;
+import io.grpc.netty.InternalNettyChannelBuilder.TransportCreationParamsFilterFactory;
+import io.grpc.netty.ProtocolNegotiator;
+import java.net.InetSocketAddress;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class GoogleDefaultChannelBuilderTest {
+
+  @Test
+  public void buildsNettyChannel() throws Exception {
+    GoogleDefaultChannelBuilder builder = GoogleDefaultChannelBuilder.forTarget("localhost:8080");
+
+    TransportCreationParamsFilterFactory tcpfFactory = builder.getTcpfFactoryForTest();
+    assertThat(tcpfFactory).isNotNull();
+    ProtocolNegotiator protocolNegotiator =
+        tcpfFactory
+            .create(new InetSocketAddress(8080), "fakeAuthority", "fakeUserAgent", null)
+            .getProtocolNegotiator();
+    assertThat(protocolNegotiator).isInstanceOf(GoogleDefaultProtocolNegotiator.class);
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/AesGcmHkdfAeadCrypterTest.java b/alts/src/test/java/io/grpc/alts/internal/AesGcmHkdfAeadCrypterTest.java
new file mode 100644
index 0000000..d655a6e
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/AesGcmHkdfAeadCrypterTest.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.google.common.io.BaseEncoding;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AesGcmHkdfAeadCrypter}. */
+@RunWith(JUnit4.class)
+public final class AesGcmHkdfAeadCrypterTest {
+
+  private static class TestVector {
+    final String comment;
+    final byte[] key;
+    final byte[] nonce;
+    final byte[] aad;
+    final byte[] plaintext;
+    final byte[] ciphertext;
+
+    TestVector(TestVectorBuilder builder) {
+      comment = builder.comment;
+      key = builder.key;
+      nonce = builder.nonce;
+      aad = builder.aad;
+      plaintext = builder.plaintext;
+      ciphertext = builder.ciphertext;
+    }
+
+    static TestVectorBuilder builder() {
+      return new TestVectorBuilder();
+    }
+  }
+
+  private static class TestVectorBuilder {
+    String comment;
+    byte[] key;
+    byte[] nonce;
+    byte[] aad;
+    byte[] plaintext;
+    byte[] ciphertext;
+
+    TestVector build() {
+      if (comment == null
+          && key == null
+          && nonce == null
+          && aad == null
+          && plaintext == null
+          && ciphertext == null) {
+        throw new IllegalStateException("All fields must be set before calling build().");
+      }
+      return new TestVector(this);
+    }
+
+    TestVectorBuilder withComment(String comment) {
+      this.comment = comment;
+      return this;
+    }
+
+    TestVectorBuilder withKey(String key) {
+      this.key = BaseEncoding.base16().lowerCase().decode(key);
+      return this;
+    }
+
+    TestVectorBuilder withNonce(String nonce) {
+      this.nonce = BaseEncoding.base16().lowerCase().decode(nonce);
+      return this;
+    }
+
+    TestVectorBuilder withAad(String aad) {
+      this.aad = BaseEncoding.base16().lowerCase().decode(aad);
+      return this;
+    }
+
+    TestVectorBuilder withPlaintext(String plaintext) {
+      this.plaintext = BaseEncoding.base16().lowerCase().decode(plaintext);
+      return this;
+    }
+
+    TestVectorBuilder withCiphertext(String ciphertext) {
+      this.ciphertext = BaseEncoding.base16().lowerCase().decode(ciphertext);
+      return this;
+    }
+  }
+
+  @Test
+  public void testVectorEncrypt() throws GeneralSecurityException {
+    int i = 0;
+    for (TestVector testVector : testVectors) {
+      int bufferSize = testVector.ciphertext.length;
+      byte[] ciphertext = new byte[bufferSize];
+      ByteBuffer ciphertextBuffer = ByteBuffer.wrap(ciphertext);
+
+      AesGcmHkdfAeadCrypter aeadCrypter = new AesGcmHkdfAeadCrypter(testVector.key);
+      aeadCrypter.encrypt(
+          ciphertextBuffer,
+          ByteBuffer.wrap(testVector.plaintext),
+          ByteBuffer.wrap(testVector.aad),
+          testVector.nonce);
+      String msg = "Failure for test vector " + i;
+      assertWithMessage(msg)
+          .that(ciphertextBuffer.remaining())
+          .isEqualTo(bufferSize - testVector.ciphertext.length);
+      byte[] exactCiphertext = Arrays.copyOf(ciphertext, testVector.ciphertext.length);
+      assertWithMessage(msg).that(exactCiphertext).isEqualTo(testVector.ciphertext);
+      i++;
+    }
+  }
+
+  @Test
+  public void testVectorDecrypt() throws GeneralSecurityException {
+    int i = 0;
+    for (TestVector testVector : testVectors) {
+      // The plaintext buffer might require space for the tag to decrypt (e.g., for conscrypt).
+      int bufferSize = testVector.ciphertext.length;
+      byte[] plaintext = new byte[bufferSize];
+      ByteBuffer plaintextBuffer = ByteBuffer.wrap(plaintext);
+
+      AesGcmHkdfAeadCrypter aeadCrypter = new AesGcmHkdfAeadCrypter(testVector.key);
+      aeadCrypter.decrypt(
+          plaintextBuffer,
+          ByteBuffer.wrap(testVector.ciphertext),
+          ByteBuffer.wrap(testVector.aad),
+          testVector.nonce);
+      String msg = "Failure for test vector " + i;
+      assertWithMessage(msg)
+          .that(plaintextBuffer.remaining())
+          .isEqualTo(bufferSize - testVector.plaintext.length);
+      byte[] exactPlaintext = Arrays.copyOf(plaintext, testVector.plaintext.length);
+      assertWithMessage(msg).that(exactPlaintext).isEqualTo(testVector.plaintext);
+      i++;
+    }
+  }
+
+  /*
+   * NIST vectors from:
+   *  http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf
+   *
+   *  IEEE vectors from:
+   *  http://www.ieee802.org/1/files/public/docs2011/bn-randall-test-vectors-0511-v1.pdf
+   * Key expanded by setting
+   * expandedKey = (key||(key ^ {0x01, .., 0x01})||key ^ {0x02,..,0x02}))[0:44].
+   */
+  private static final TestVector[] testVectors =
+      new TestVector[] {
+        TestVector.builder()
+            .withComment("Derived from NIST test vector 1")
+            .withKey(
+                "0000000000000000000000000000000001010101010101010101010101010101020202020202020202"
+                    + "020202")
+            .withNonce("000000000000000000000000")
+            .withAad("")
+            .withPlaintext("")
+            .withCiphertext("85e873e002f6ebdc4060954eb8675508")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from NIST test vector 2")
+            .withKey(
+                "0000000000000000000000000000000001010101010101010101010101010101020202020202020202"
+                    + "020202")
+            .withNonce("000000000000000000000000")
+            .withAad("")
+            .withPlaintext("00000000000000000000000000000000")
+            .withCiphertext("51e9a8cb23ca2512c8256afff8e72d681aca19a1148ac115e83df4888cc00d11")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from NIST test vector 3")
+            .withKey(
+                "feffe9928665731c6d6a8f9467308308fffee8938764721d6c6b8e9566318209fcfdeb908467711e6f"
+                    + "688d96")
+            .withNonce("cafebabefacedbaddecaf888")
+            .withAad("")
+            .withPlaintext(
+                "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532f"
+                    + "cf0e2449a6b525b16aedf5aa0de657ba637b391aafd255")
+            .withCiphertext(
+                "1018ed5a1402a86516d6576d70b2ffccca261b94df88b58f53b64dfba435d18b2f6e3b7869f9353d4a"
+                    + "c8cf09afb1663daa7b4017e6fc2c177c0c087c0df1162129952213cee1bc6e9c8495dd705e1f"
+                    + "3d")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from NIST test vector 4")
+            .withKey(
+                "feffe9928665731c6d6a8f9467308308fffee8938764721d6c6b8e9566318209fcfdeb908467711e6f"
+                    + "688d96")
+            .withNonce("cafebabefacedbaddecaf888")
+            .withAad("feedfacedeadbeeffeedfacedeadbeefabaddad2")
+            .withPlaintext(
+                "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532f"
+                    + "cf0e2449a6b525b16aedf5aa0de657ba637b39")
+            .withCiphertext(
+                "1018ed5a1402a86516d6576d70b2ffccca261b94df88b58f53b64dfba435d18b2f6e3b7869f9353d4a"
+                    + "c8cf09afb1663daa7b4017e6fc2c177c0c087c4764565d077e9124001ddb27fc0848c5")
+            .build(),
+        TestVector.builder()
+            .withComment(
+                "Derived from adapted NIST test vector 4"
+                    + " for KDF counter boundary (flip nonce bit 15)")
+            .withKey(
+                "feffe9928665731c6d6a8f9467308308fffee8938764721d6c6b8e9566318209fcfdeb908467711e6f"
+                    + "688d96")
+            .withNonce("ca7ebabefacedbaddecaf888")
+            .withAad("feedfacedeadbeeffeedfacedeadbeefabaddad2")
+            .withPlaintext(
+                "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532f"
+                    + "cf0e2449a6b525b16aedf5aa0de657ba637b39")
+            .withCiphertext(
+                "e650d3c0fb879327f2d03287fa93cd07342b136215adbca00c3bd5099ec41832b1d18e0423ed26bb12"
+                    + "c6cd09debb29230a94c0cee15903656f85edb6fc509b1b28216382172ecbcc31e1e9b1")
+            .build(),
+        TestVector.builder()
+            .withComment(
+                "Derived from adapted NIST test vector 4"
+                    + " for KDF counter boundary (flip nonce bit 16)")
+            .withKey(
+                "feffe9928665731c6d6a8f9467308308fffee8938764721d6c6b8e9566318209fcfdeb908467711e6f"
+                    + "688d96")
+            .withNonce("cafebbbefacedbaddecaf888")
+            .withAad("feedfacedeadbeeffeedfacedeadbeefabaddad2")
+            .withPlaintext(
+                "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532f"
+                    + "cf0e2449a6b525b16aedf5aa0de657ba637b39")
+            .withCiphertext(
+                "c0121e6c954d0767f96630c33450999791b2da2ad05c4190169ccad9ac86ff1c721e3d82f2ad22ab46"
+                    + "3bab4a0754b7dd68ca4de7ea2531b625eda01f89312b2ab957d5c7f8568dd95fcdcd1f")
+            .build(),
+        TestVector.builder()
+            .withComment(
+                "Derived from adapted NIST test vector 4"
+                    + " for KDF counter boundary (flip nonce bit 63)")
+            .withKey(
+                "feffe9928665731c6d6a8f9467308308fffee8938764721d6c6b8e9566318209fcfdeb908467711e6f"
+                    + "688d96")
+            .withNonce("cafebabefacedb2ddecaf888")
+            .withAad("feedfacedeadbeeffeedfacedeadbeefabaddad2")
+            .withPlaintext(
+                "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532f"
+                    + "cf0e2449a6b525b16aedf5aa0de657ba637b39")
+            .withCiphertext(
+                "8af37ea5684a4d81d4fd817261fd9743099e7e6a025eaacf8e54b124fb5743149e05cb89f4a49467fe"
+                    + "2e5e5965f29a19f99416b0016b54585d12553783ba59e9f782e82e097c336bf7989f08")
+            .build(),
+        TestVector.builder()
+            .withComment(
+                "Derived from adapted NIST test vector 4"
+                    + " for KDF counter boundary (flip nonce bit 64)")
+            .withKey(
+                "feffe9928665731c6d6a8f9467308308fffee8938764721d6c6b8e9566318209fcfdeb908467711e6f"
+                    + "688d96")
+            .withNonce("cafebabefacedbaddfcaf888")
+            .withAad("feedfacedeadbeeffeedfacedeadbeefabaddad2")
+            .withPlaintext(
+                "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532f"
+                    + "cf0e2449a6b525b16aedf5aa0de657ba637b39")
+            .withCiphertext(
+                "fbd528448d0346bfa878634864d407a35a039de9db2f1feb8e965b3ae9356ce6289441d77f8f0df294"
+                    + "891f37ea438b223e3bf2bdc53d4c5a74fb680bb312a8dec6f7252cbcd7f5799750ad78")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.1.1 54-byte auth")
+            .withKey(
+                "ad7a2bd03eac835a6f620fdcb506b345ac7b2ad13fad825b6e630eddb407b244af7829d23cae81586d"
+                    + "600dde")
+            .withNonce("12153524c0895e81b2c28465")
+            .withAad(
+                "d609b1f056637a0d46df998d88e5222ab2c2846512153524c0895e8108000f10111213141516171819"
+                    + "1a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233340001")
+            .withPlaintext("")
+            .withCiphertext("3ea0b584f3c85e93f9320ea591699efb")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.1.2 54-byte auth")
+            .withKey(
+                "e3c08a8f06c6e3ad95a70557b23f75483ce33021a9c72b7025666204c69c0b72e1c2888d04c4e1af97"
+                    + "a50755")
+            .withNonce("12153524c0895e81b2c28465")
+            .withAad(
+                "d609b1f056637a0d46df998d88e5222ab2c2846512153524c0895e8108000f10111213141516171819"
+                    + "1a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233340001")
+            .withPlaintext("")
+            .withCiphertext("294e028bf1fe6f14c4e8f7305c933eb5")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.2.1 60-byte crypt")
+            .withKey(
+                "ad7a2bd03eac835a6f620fdcb506b345ac7b2ad13fad825b6e630eddb407b244af7829d23cae81586d"
+                    + "600dde")
+            .withNonce("12153524c0895e81b2c28465")
+            .withAad("d609b1f056637a0d46df998d88e52e00b2c2846512153524c0895e81")
+            .withPlaintext(
+                "08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435"
+                    + "363738393a0002")
+            .withCiphertext(
+                "db3d25719c6b0a3ca6145c159d5c6ed9aff9c6e0b79f17019ea923b8665ddf52137ad611f0d1bf417a"
+                    + "7ca85e45afe106ff9c7569d335d086ae6c03f00987ccd6")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.2.2 60-byte crypt")
+            .withKey(
+                "e3c08a8f06c6e3ad95a70557b23f75483ce33021a9c72b7025666204c69c0b72e1c2888d04c4e1af97"
+                    + "a50755")
+            .withNonce("12153524c0895e81b2c28465")
+            .withAad("d609b1f056637a0d46df998d88e52e00b2c2846512153524c0895e81")
+            .withPlaintext(
+                "08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435"
+                    + "363738393a0002")
+            .withCiphertext(
+                "1641f28ec13afcc8f7903389787201051644914933e9202bb9d06aa020c2a67ef51dfe7bc00a856c55"
+                    + "b8f8133e77f659132502bad63f5713d57d0c11e0f871ed")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.3.1 60-byte auth")
+            .withKey(
+                "071b113b0ca743fecccf3d051f737382061a103a0da642ffcdce3c041e727283051913390ea541fcce"
+                    + "cd3f07")
+            .withNonce("f0761e8dcd3d000176d457ed")
+            .withAad(
+                "e20106d7cd0df0761e8dcd3d88e5400076d457ed08000f101112131415161718191a1b1c1d1e1f2021"
+                    + "22232425262728292a2b2c2d2e2f303132333435363738393a0003")
+            .withPlaintext("")
+            .withCiphertext("58837a10562b0f1f8edbe58ca55811d3")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.3.2 60-byte auth")
+            .withKey(
+                "691d3ee909d7f54167fd1ca0b5d769081f2bde1aee655fdbab80bd5295ae6be76b1f3ceb0bd5f74365"
+                    + "ff1ea2")
+            .withNonce("f0761e8dcd3d000176d457ed")
+            .withAad(
+                "e20106d7cd0df0761e8dcd3d88e5400076d457ed08000f101112131415161718191a1b1c1d1e1f2021"
+                    + "22232425262728292a2b2c2d2e2f303132333435363738393a0003")
+            .withPlaintext("")
+            .withCiphertext("c2722ff6ca29a257718a529d1f0c6a3b")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.4.1 54-byte crypt")
+            .withKey(
+                "071b113b0ca743fecccf3d051f737382061a103a0da642ffcdce3c041e727283051913390ea541fcce"
+                    + "cd3f07")
+            .withNonce("f0761e8dcd3d000176d457ed")
+            .withAad("e20106d7cd0df0761e8dcd3d88e54c2a76d457ed")
+            .withPlaintext(
+                "08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333400"
+                    + "04")
+            .withCiphertext(
+                "fd96b715b93a13346af51e8acdf792cdc7b2686f8574c70e6b0cbf16291ded427ad73fec48cd298e05"
+                    + "28a1f4c644a949fc31dc9279706ddba33f")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.4.2 54-byte crypt")
+            .withKey(
+                "691d3ee909d7f54167fd1ca0b5d769081f2bde1aee655fdbab80bd5295ae6be76b1f3ceb0bd5f74365"
+                    + "ff1ea2")
+            .withNonce("f0761e8dcd3d000176d457ed")
+            .withAad("e20106d7cd0df0761e8dcd3d88e54c2a76d457ed")
+            .withPlaintext(
+                "08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333400"
+                    + "04")
+            .withCiphertext(
+                "b68f6300c2e9ae833bdc070e24021a3477118e78ccf84e11a485d861476c300f175353d5cdf92008a4"
+                    + "f878e6cc3577768085c50a0e98fda6cbb8")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.5.1 65-byte auth")
+            .withKey(
+                "013fe00b5f11be7f866d0cbbc55a7a90003ee10a5e10bf7e876c0dbac45b7b91033de2095d13bc7d84"
+                    + "6f0eb9")
+            .withNonce("7cfde9f9e33724c68932d612")
+            .withAad(
+                "84c5d513d2aaf6e5bbd2727788e523008932d6127cfde9f9e33724c608000f10111213141516171819"
+                    + "1a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
+                    + "0005")
+            .withPlaintext("")
+            .withCiphertext("cca20eecda6283f09bb3543dd99edb9b")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.5.2 65-byte auth")
+            .withKey(
+                "83c093b58de7ffe1c0da926ac43fb3609ac1c80fee1b624497ef942e2f79a82381c291b78fe5fde3c2"
+                    + "d89068")
+            .withNonce("7cfde9f9e33724c68932d612")
+            .withAad(
+                "84c5d513d2aaf6e5bbd2727788e523008932d6127cfde9f9e33724c608000f10111213141516171819"
+                    + "1a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
+                    + "0005")
+            .withPlaintext("")
+            .withCiphertext("b232cc1da5117bf15003734fa599d271")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE  2.6.1 61-byte crypt")
+            .withKey(
+                "013fe00b5f11be7f866d0cbbc55a7a90003ee10a5e10bf7e876c0dbac45b7b91033de2095d13bc7d84"
+                    + "6f0eb9")
+            .withNonce("7cfde9f9e33724c68932d612")
+            .withAad("84c5d513d2aaf6e5bbd2727788e52f008932d6127cfde9f9e33724c6")
+            .withPlaintext(
+                "08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435"
+                    + "363738393a3b0006")
+            .withCiphertext(
+                "ff1910d35ad7e5657890c7c560146fd038707f204b66edbc3d161f8ace244b985921023c436e3a1c35"
+                    + "32ecd5d09a056d70be583f0d10829d9387d07d33d872e490")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.6.2 61-byte crypt")
+            .withKey(
+                "83c093b58de7ffe1c0da926ac43fb3609ac1c80fee1b624497ef942e2f79a82381c291b78fe5fde3c2"
+                    + "d89068")
+            .withNonce("7cfde9f9e33724c68932d612")
+            .withAad("84c5d513d2aaf6e5bbd2727788e52f008932d6127cfde9f9e33724c6")
+            .withPlaintext(
+                "08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435"
+                    + "363738393a3b0006")
+            .withCiphertext(
+                "0db4cf956b5f97eca4eab82a6955307f9ae02a32dd7d93f83d66ad04e1cfdc5182ad12abdea5bbb619"
+                    + "a1bd5fb9a573590fba908e9c7a46c1f7ba0905d1b55ffda4")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.7.1 79-byte crypt")
+            .withKey(
+                "88ee087fd95da9fbf6725aa9d757b0cd89ef097ed85ca8faf7735ba8d656b1cc8aec0a7ddb5fabf9f4"
+                    + "7058ab")
+            .withNonce("7ae8e2ca4ec500012e58495c")
+            .withAad(
+                "68f2e77696ce7ae8e2ca4ec588e541002e58495c08000f101112131415161718191a1b1c1d1e1f2021"
+                    + "22232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041424344454647"
+                    + "48494a4b4c4d0007")
+            .withPlaintext("")
+            .withCiphertext("813f0e630f96fb2d030f58d83f5cdfd0")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.7.2 79-byte crypt")
+            .withKey(
+                "4c973dbc7364621674f8b5b89e5c15511fced9216490fb1c1a2caa0ffe0407e54e953fbe7166601476"
+                    + "fab7ba")
+            .withNonce("7ae8e2ca4ec500012e58495c")
+            .withAad(
+                "68f2e77696ce7ae8e2ca4ec588e541002e58495c08000f101112131415161718191a1b1c1d1e1f2021"
+                    + "22232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4041424344454647"
+                    + "48494a4b4c4d0007")
+            .withPlaintext("")
+            .withCiphertext("77e5a44c21eb07188aacbd74d1980e97")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.8.1 61-byte crypt")
+            .withKey(
+                "88ee087fd95da9fbf6725aa9d757b0cd89ef097ed85ca8faf7735ba8d656b1cc8aec0a7ddb5fabf9f4"
+                    + "7058ab")
+            .withNonce("7ae8e2ca4ec500012e58495c")
+            .withAad("68f2e77696ce7ae8e2ca4ec588e54d002e58495c")
+            .withPlaintext(
+                "08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435"
+                    + "363738393a3b3c3d3e3f404142434445464748490008")
+            .withCiphertext(
+                "958ec3f6d60afeda99efd888f175e5fcd4c87b9bcc5c2f5426253a8b506296c8c43309ab2adb593946"
+                    + "2541d95e80811e04e706b1498f2c407c7fb234f8cc01a647550ee6b557b35a7e3945381821"
+                    + "f4")
+            .build(),
+        TestVector.builder()
+            .withComment("Derived from IEEE 2.8.2 61-byte crypt")
+            .withKey(
+                "4c973dbc7364621674f8b5b89e5c15511fced9216490fb1c1a2caa0ffe0407e54e953fbe7166601476"
+                    + "fab7ba")
+            .withNonce("7ae8e2ca4ec500012e58495c")
+            .withAad("68f2e77696ce7ae8e2ca4ec588e54d002e58495c")
+            .withPlaintext(
+                "08000f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435"
+                    + "363738393a3b3c3d3e3f404142434445464748490008")
+            .withCiphertext(
+                "b44d072011cd36d272a9b7a98db9aa90cbc5c67b93ddce67c854503214e2e896ec7e9db649ed4bcf6f"
+                    + "850aac0223d0cf92c83db80795c3a17ecc1248bb00591712b1ae71e268164196252162810b"
+                    + "00")
+            .build()
+      };
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsAuthContextTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsAuthContextTest.java
new file mode 100644
index 0000000..eb1fbf9
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsAuthContextTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static org.junit.Assert.assertEquals;
+
+import io.grpc.alts.internal.Handshaker.HandshakerResult;
+import io.grpc.alts.internal.Handshaker.Identity;
+import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions;
+import io.grpc.alts.internal.TransportSecurityCommon.SecurityLevel;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AltsAuthContext}. */
+@RunWith(JUnit4.class)
+public final class AltsAuthContextTest {
+  private static final int TEST_MAX_RPC_VERSION_MAJOR = 3;
+  private static final int TEST_MAX_RPC_VERSION_MINOR = 5;
+  private static final int TEST_MIN_RPC_VERSION_MAJOR = 2;
+  private static final int TEST_MIN_RPC_VERSION_MINOR = 1;
+  private static final SecurityLevel TEST_SECURITY_LEVEL = SecurityLevel.INTEGRITY_AND_PRIVACY;
+  private static final String TEST_APPLICATION_PROTOCOL = "grpc";
+  private static final String TEST_LOCAL_SERVICE_ACCOUNT = "local@gserviceaccount.com";
+  private static final String TEST_PEER_SERVICE_ACCOUNT = "peer@gserviceaccount.com";
+  private static final String TEST_RECORD_PROTOCOL = "ALTSRP_GCM_AES128";
+
+  private HandshakerResult handshakerResult;
+  private RpcProtocolVersions rpcVersions;
+
+  @Before
+  public void setUp() {
+    rpcVersions =
+        RpcProtocolVersions.newBuilder()
+            .setMaxRpcVersion(
+                RpcProtocolVersions.Version.newBuilder()
+                    .setMajor(TEST_MAX_RPC_VERSION_MAJOR)
+                    .setMinor(TEST_MAX_RPC_VERSION_MINOR)
+                    .build())
+            .setMinRpcVersion(
+                RpcProtocolVersions.Version.newBuilder()
+                    .setMajor(TEST_MIN_RPC_VERSION_MAJOR)
+                    .setMinor(TEST_MIN_RPC_VERSION_MINOR)
+                    .build())
+            .build();
+    handshakerResult =
+        HandshakerResult.newBuilder()
+            .setApplicationProtocol(TEST_APPLICATION_PROTOCOL)
+            .setRecordProtocol(TEST_RECORD_PROTOCOL)
+            .setPeerIdentity(Identity.newBuilder().setServiceAccount(TEST_PEER_SERVICE_ACCOUNT))
+            .setLocalIdentity(Identity.newBuilder().setServiceAccount(TEST_LOCAL_SERVICE_ACCOUNT))
+            .setPeerRpcVersions(rpcVersions)
+            .build();
+  }
+
+  @Test
+  public void testAltsAuthContext() {
+    AltsAuthContext authContext = new AltsAuthContext(handshakerResult);
+    assertEquals(TEST_APPLICATION_PROTOCOL, authContext.getApplicationProtocol());
+    assertEquals(TEST_RECORD_PROTOCOL, authContext.getRecordProtocol());
+    assertEquals(TEST_SECURITY_LEVEL, authContext.getSecurityLevel());
+    assertEquals(TEST_PEER_SERVICE_ACCOUNT, authContext.getPeerServiceAccount());
+    assertEquals(TEST_LOCAL_SERVICE_ACCOUNT, authContext.getLocalServiceAccount());
+    assertEquals(rpcVersions, authContext.getPeerRpcVersions());
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsChannelCrypterTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsChannelCrypterTest.java
new file mode 100644
index 0000000..0b40a8b
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsChannelCrypterTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.alts.internal.AltsChannelCrypter.incrementCounter;
+import static org.junit.Assert.fail;
+
+import com.google.common.testing.GcFinalization;
+import io.netty.util.ReferenceCounted;
+import io.netty.util.ResourceLeakDetector;
+import io.netty.util.ResourceLeakDetector.Level;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AltsChannelCrypter}. */
+@RunWith(JUnit4.class)
+public final class AltsChannelCrypterTest extends ChannelCrypterNettyTestBase {
+
+  @Before
+  public void setUp() throws GeneralSecurityException {
+    ResourceLeakDetector.setLevel(Level.PARANOID);
+    client = new AltsChannelCrypter(new byte[AltsChannelCrypter.getKeyLength()], true);
+    server = new AltsChannelCrypter(new byte[AltsChannelCrypter.getKeyLength()], false);
+  }
+
+  @After
+  public void tearDown() throws GeneralSecurityException {
+    for (ReferenceCounted reference : references) {
+      reference.release();
+    }
+    references.clear();
+    client.destroy();
+    server.destroy();
+    // Increase our chances to detect ByteBuf leaks.
+    GcFinalization.awaitFullGc();
+  }
+
+  @Test
+  public void encryptDecryptKdfCounterIncr() throws GeneralSecurityException {
+    AltsChannelCrypter client =
+        new AltsChannelCrypter(new byte[AltsChannelCrypter.getKeyLength()], true);
+    AltsChannelCrypter server =
+        new AltsChannelCrypter(new byte[AltsChannelCrypter.getKeyLength()], false);
+
+    String message = "Hello world";
+    FrameEncrypt frameEncrypt1 = createFrameEncrypt(message);
+
+    client.encrypt(frameEncrypt1.out, frameEncrypt1.plain);
+    FrameDecrypt frameDecrypt1 = frameDecryptOfEncrypt(frameEncrypt1);
+
+    server.decrypt(frameDecrypt1.out, frameDecrypt1.tag, frameDecrypt1.ciphertext);
+    assertThat(frameEncrypt1.plain.get(0).slice(0, frameDecrypt1.out.readableBytes()))
+        .isEqualTo(frameDecrypt1.out);
+
+    // Increase counters to get a new KDF counter value (first two bytes are skipped).
+    client.incrementOutCounterForTesting(1 << 17);
+    server.incrementInCounterForTesting(1 << 17);
+
+    FrameEncrypt frameEncrypt2 = createFrameEncrypt(message);
+
+    client.encrypt(frameEncrypt2.out, frameEncrypt2.plain);
+    FrameDecrypt frameDecrypt2 = frameDecryptOfEncrypt(frameEncrypt2);
+
+    server.decrypt(frameDecrypt2.out, frameDecrypt2.tag, frameDecrypt2.ciphertext);
+    assertThat(frameEncrypt2.plain.get(0).slice(0, frameDecrypt2.out.readableBytes()))
+        .isEqualTo(frameDecrypt2.out);
+  }
+
+  @Test
+  public void overflowsClient() throws GeneralSecurityException {
+    byte[] maxFirst =
+        new byte[] {
+          (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+          (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+          (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+        };
+
+    byte[] maxFirstPred = Arrays.copyOf(maxFirst, maxFirst.length);
+    maxFirstPred[0]--;
+
+    byte[] oldCounter = new byte[AltsChannelCrypter.getCounterLength()];
+    byte[] counter = Arrays.copyOf(maxFirstPred, maxFirstPred.length);
+
+    incrementCounter(counter, oldCounter);
+
+    assertThat(oldCounter).isEqualTo(maxFirstPred);
+    assertThat(counter).isEqualTo(maxFirst);
+
+    try {
+      incrementCounter(counter, oldCounter);
+      fail("Exception expected");
+    } catch (GeneralSecurityException ex) {
+      assertThat(ex).hasMessageThat().contains("Counter has overflowed");
+    }
+
+    assertThat(oldCounter).isEqualTo(maxFirst);
+    assertThat(counter).isEqualTo(maxFirst);
+  }
+
+  @Test
+  public void overflowsServer() throws GeneralSecurityException {
+    byte[] maxSecond =
+        new byte[] {
+          (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+          (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+          (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80
+        };
+
+    byte[] maxSecondPred = Arrays.copyOf(maxSecond, maxSecond.length);
+    maxSecondPred[0]--;
+
+    byte[] oldCounter = new byte[AltsChannelCrypter.getCounterLength()];
+    byte[] counter = Arrays.copyOf(maxSecondPred, maxSecondPred.length);
+
+    incrementCounter(counter, oldCounter);
+
+    assertThat(oldCounter).isEqualTo(maxSecondPred);
+    assertThat(counter).isEqualTo(maxSecond);
+
+    try {
+      incrementCounter(counter, oldCounter);
+      fail("Exception expected");
+    } catch (GeneralSecurityException ex) {
+      assertThat(ex).hasMessageThat().contains("Counter has overflowed");
+    }
+
+    assertThat(oldCounter).isEqualTo(maxSecond);
+    assertThat(counter).isEqualTo(maxSecond);
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsClientOptionsTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsClientOptionsTest.java
new file mode 100644
index 0000000..cdb44c9
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsClientOptionsTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AltsClientOptions}. */
+@RunWith(JUnit4.class)
+public final class AltsClientOptionsTest {
+
+  @Test
+  public void setAndGet() throws Exception {
+    String targetName = "foo";
+    String serviceAccount1 = "bar1";
+    String serviceAccount2 = "bar2";
+    RpcProtocolVersions rpcVersions =
+        RpcProtocolVersions.newBuilder()
+            .setMaxRpcVersion(
+                RpcProtocolVersions.Version.newBuilder().setMajor(2).setMinor(1).build())
+            .setMinRpcVersion(
+                RpcProtocolVersions.Version.newBuilder().setMajor(2).setMinor(1).build())
+            .build();
+
+    AltsClientOptions options =
+        new AltsClientOptions.Builder()
+            .setTargetName(targetName)
+            .addTargetServiceAccount(serviceAccount1)
+            .addTargetServiceAccount(serviceAccount2)
+            .setRpcProtocolVersions(rpcVersions)
+            .build();
+
+    assertThat(options.getTargetName()).isEqualTo(targetName);
+    assertThat(options.getTargetServiceAccounts()).containsAllOf(serviceAccount1, serviceAccount2);
+    assertThat(options.getRpcProtocolVersions()).isEqualTo(rpcVersions);
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsFramingTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsFramingTest.java
new file mode 100644
index 0000000..5ad250e
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsFramingTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.GeneralSecurityException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AltsFraming}. */
+@RunWith(JUnit4.class)
+public class AltsFramingTest {
+
+  @Test
+  public void parserFrameLengthNegativeFails() throws GeneralSecurityException {
+    AltsFraming.Parser parser = new AltsFraming.Parser();
+    // frame length + one remaining byte (required)
+    ByteBuffer buffer = ByteBuffer.allocate(AltsFraming.getFrameLengthHeaderSize() + 1);
+    buffer.order(ByteOrder.LITTLE_ENDIAN);
+    buffer.putInt(-1); // write invalid length
+    buffer.put((byte) 0); // write some byte
+    buffer.flip();
+
+    try {
+      parser.readBytes(buffer);
+      fail("Exception expected");
+    } catch (IllegalArgumentException ex) {
+      assertThat(ex).hasMessageThat().contains("Invalid frame length");
+    }
+  }
+
+  @Test
+  public void parserFrameLengthSmallerMessageTypeFails() throws GeneralSecurityException {
+    AltsFraming.Parser parser = new AltsFraming.Parser();
+    // frame length + one remaining byte (required)
+    ByteBuffer buffer = ByteBuffer.allocate(AltsFraming.getFrameLengthHeaderSize() + 1);
+    buffer.order(ByteOrder.LITTLE_ENDIAN);
+    buffer.putInt(AltsFraming.getFrameMessageTypeHeaderSize() - 1); // write invalid length
+    buffer.put((byte) 0); // write some byte
+    buffer.flip();
+
+    try {
+      parser.readBytes(buffer);
+      fail("Exception expected");
+    } catch (IllegalArgumentException ex) {
+      assertThat(ex).hasMessageThat().contains("Invalid frame length");
+    }
+  }
+
+  @Test
+  public void parserFrameLengthTooLargeFails() throws GeneralSecurityException {
+    AltsFraming.Parser parser = new AltsFraming.Parser();
+    // frame length + one remaining byte (required)
+    ByteBuffer buffer = ByteBuffer.allocate(AltsFraming.getFrameLengthHeaderSize() + 1);
+    buffer.order(ByteOrder.LITTLE_ENDIAN);
+    buffer.putInt(AltsFraming.getMaxDataLength() + 1); // write invalid length
+    buffer.put((byte) 0); // write some byte
+    buffer.flip();
+
+    try {
+      parser.readBytes(buffer);
+      fail("Exception expected");
+    } catch (IllegalArgumentException ex) {
+      assertThat(ex).hasMessageThat().contains("Invalid frame length");
+    }
+  }
+
+  @Test
+  public void parserFrameLengthMaxOk() throws GeneralSecurityException {
+    AltsFraming.Parser parser = new AltsFraming.Parser();
+    // length of type header + data
+    int dataLength = AltsFraming.getMaxDataLength();
+    // complete frame + 1 byte
+    ByteBuffer buffer =
+        ByteBuffer.allocate(AltsFraming.getFrameLengthHeaderSize() + dataLength + 1);
+    buffer.order(ByteOrder.LITTLE_ENDIAN);
+    buffer.putInt(dataLength); // write invalid length
+    buffer.putInt(6); // default message type
+    buffer.put(new byte[dataLength - AltsFraming.getFrameMessageTypeHeaderSize()]); // write data
+    buffer.put((byte) 0);
+    buffer.flip();
+
+    parser.readBytes(buffer);
+
+    assertThat(parser.isComplete()).isTrue();
+    assertThat(buffer.remaining()).isEqualTo(1);
+  }
+
+  @Test
+  public void parserFrameLengthZeroOk() throws GeneralSecurityException {
+    AltsFraming.Parser parser = new AltsFraming.Parser();
+    int dataLength = AltsFraming.getFrameMessageTypeHeaderSize();
+    // complete frame + 1 byte
+    ByteBuffer buffer =
+        ByteBuffer.allocate(AltsFraming.getFrameLengthHeaderSize() + dataLength + 1);
+    buffer.order(ByteOrder.LITTLE_ENDIAN);
+    buffer.putInt(dataLength); // write invalid length
+    buffer.putInt(6); // default message type
+    buffer.put((byte) 0);
+    buffer.flip();
+
+    parser.readBytes(buffer);
+
+    assertThat(parser.isComplete()).isTrue();
+    assertThat(buffer.remaining()).isEqualTo(1);
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java
new file mode 100644
index 0000000..d5cbecf
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerClientTest.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.protobuf.ByteString;
+import io.grpc.alts.internal.Handshaker.HandshakeProtocol;
+import io.grpc.alts.internal.Handshaker.HandshakerReq;
+import io.grpc.alts.internal.Handshaker.Identity;
+import io.grpc.alts.internal.Handshaker.StartClientHandshakeReq;
+import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+
+/** Unit tests for {@link AltsHandshakerClient}. */
+@RunWith(JUnit4.class)
+public class AltsHandshakerClientTest {
+  private static final int IN_BYTES_SIZE = 100;
+  private static final int BYTES_CONSUMED = 30;
+  private static final int PREFIX_POSITION = 20;
+  private static final String TEST_TARGET_NAME = "target name";
+  private static final String TEST_TARGET_SERVICE_ACCOUNT = "peer service account";
+
+  private AltsHandshakerStub mockStub;
+  private AltsHandshakerClient handshaker;
+  private AltsClientOptions clientOptions;
+
+  @Before
+  public void setUp() {
+    mockStub = mock(AltsHandshakerStub.class);
+    clientOptions =
+        new AltsClientOptions.Builder()
+            .setTargetName(TEST_TARGET_NAME)
+            .addTargetServiceAccount(TEST_TARGET_SERVICE_ACCOUNT)
+            .build();
+    handshaker = new AltsHandshakerClient(mockStub, clientOptions);
+  }
+
+  @Test
+  public void startClientHandshakeFailure() throws Exception {
+    when(mockStub.send(Matchers.<HandshakerReq>any()))
+        .thenReturn(MockAltsHandshakerResp.getErrorResponse());
+
+    try {
+      handshaker.startClientHandshake();
+      fail("Exception expected");
+    } catch (GeneralSecurityException ex) {
+      assertThat(ex).hasMessageThat().contains(MockAltsHandshakerResp.getTestErrorDetails());
+    }
+  }
+
+  @Test
+  public void startClientHandshakeSuccess() throws Exception {
+    when(mockStub.send(Matchers.<HandshakerReq>any()))
+        .thenReturn(MockAltsHandshakerResp.getOkResponse(0));
+
+    ByteBuffer outFrame = handshaker.startClientHandshake();
+
+    assertEquals(ByteString.copyFrom(outFrame), MockAltsHandshakerResp.getOutFrame());
+    assertFalse(handshaker.isFinished());
+    assertNull(handshaker.getResult());
+    assertNull(handshaker.getKey());
+  }
+
+  @Test
+  public void startClientHandshakeWithOptions() throws Exception {
+    when(mockStub.send(Matchers.<HandshakerReq>any()))
+        .thenReturn(MockAltsHandshakerResp.getOkResponse(0));
+
+    ByteBuffer outFrame = handshaker.startClientHandshake();
+    assertEquals(ByteString.copyFrom(outFrame), MockAltsHandshakerResp.getOutFrame());
+
+    HandshakerReq req =
+        HandshakerReq.newBuilder()
+            .setClientStart(
+                StartClientHandshakeReq.newBuilder()
+                    .setHandshakeSecurityProtocol(HandshakeProtocol.ALTS)
+                    .addApplicationProtocols(AltsHandshakerClient.getApplicationProtocol())
+                    .addRecordProtocols(AltsHandshakerClient.getRecordProtocol())
+                    .setTargetName(TEST_TARGET_NAME)
+                    .addTargetIdentities(
+                        Identity.newBuilder().setServiceAccount(TEST_TARGET_SERVICE_ACCOUNT))
+                    .build())
+            .build();
+    verify(mockStub).send(req);
+  }
+
+  @Test
+  public void startServerHandshakeFailure() throws Exception {
+    when(mockStub.send(Matchers.<HandshakerReq>any()))
+        .thenReturn(MockAltsHandshakerResp.getErrorResponse());
+
+    try {
+      ByteBuffer inBytes = ByteBuffer.allocate(IN_BYTES_SIZE);
+      handshaker.startServerHandshake(inBytes);
+      fail("Exception expected");
+    } catch (GeneralSecurityException ex) {
+      assertThat(ex).hasMessageThat().contains(MockAltsHandshakerResp.getTestErrorDetails());
+    }
+  }
+
+  @Test
+  public void startServerHandshakeSuccess() throws Exception {
+    when(mockStub.send(Matchers.<HandshakerReq>any()))
+        .thenReturn(MockAltsHandshakerResp.getOkResponse(BYTES_CONSUMED));
+
+    ByteBuffer inBytes = ByteBuffer.allocate(IN_BYTES_SIZE);
+    ByteBuffer outFrame = handshaker.startServerHandshake(inBytes);
+
+    assertEquals(ByteString.copyFrom(outFrame), MockAltsHandshakerResp.getOutFrame());
+    assertFalse(handshaker.isFinished());
+    assertNull(handshaker.getResult());
+    assertNull(handshaker.getKey());
+    assertEquals(IN_BYTES_SIZE - BYTES_CONSUMED, inBytes.remaining());
+  }
+
+  @Test
+  public void startServerHandshakeEmptyOutFrame() throws Exception {
+    when(mockStub.send(Matchers.<HandshakerReq>any()))
+        .thenReturn(MockAltsHandshakerResp.getEmptyOutFrameResponse(BYTES_CONSUMED));
+
+    ByteBuffer inBytes = ByteBuffer.allocate(IN_BYTES_SIZE);
+    ByteBuffer outFrame = handshaker.startServerHandshake(inBytes);
+
+    assertEquals(0, outFrame.remaining());
+    assertFalse(handshaker.isFinished());
+    assertNull(handshaker.getResult());
+    assertNull(handshaker.getKey());
+    assertEquals(IN_BYTES_SIZE - BYTES_CONSUMED, inBytes.remaining());
+  }
+
+  @Test
+  public void startServerHandshakeWithPrefixBuffer() throws Exception {
+    when(mockStub.send(Matchers.<HandshakerReq>any()))
+        .thenReturn(MockAltsHandshakerResp.getOkResponse(BYTES_CONSUMED));
+
+    ByteBuffer inBytes = ByteBuffer.allocate(IN_BYTES_SIZE);
+    inBytes.position(PREFIX_POSITION);
+    ByteBuffer outFrame = handshaker.startServerHandshake(inBytes);
+
+    assertEquals(ByteString.copyFrom(outFrame), MockAltsHandshakerResp.getOutFrame());
+    assertFalse(handshaker.isFinished());
+    assertNull(handshaker.getResult());
+    assertNull(handshaker.getKey());
+    assertEquals(PREFIX_POSITION + BYTES_CONSUMED, inBytes.position());
+    assertEquals(IN_BYTES_SIZE - BYTES_CONSUMED - PREFIX_POSITION, inBytes.remaining());
+  }
+
+  @Test
+  public void nextFailure() throws Exception {
+    when(mockStub.send(Matchers.<HandshakerReq>any()))
+        .thenReturn(MockAltsHandshakerResp.getErrorResponse());
+
+    try {
+      ByteBuffer inBytes = ByteBuffer.allocate(IN_BYTES_SIZE);
+      handshaker.next(inBytes);
+      fail("Exception expected");
+    } catch (GeneralSecurityException ex) {
+      assertThat(ex).hasMessageThat().contains(MockAltsHandshakerResp.getTestErrorDetails());
+    }
+  }
+
+  @Test
+  public void nextSuccess() throws Exception {
+    when(mockStub.send(Matchers.<HandshakerReq>any()))
+        .thenReturn(MockAltsHandshakerResp.getOkResponse(BYTES_CONSUMED));
+
+    ByteBuffer inBytes = ByteBuffer.allocate(IN_BYTES_SIZE);
+    ByteBuffer outFrame = handshaker.next(inBytes);
+
+    assertEquals(ByteString.copyFrom(outFrame), MockAltsHandshakerResp.getOutFrame());
+    assertFalse(handshaker.isFinished());
+    assertNull(handshaker.getResult());
+    assertNull(handshaker.getKey());
+    assertEquals(IN_BYTES_SIZE - BYTES_CONSUMED, inBytes.remaining());
+  }
+
+  @Test
+  public void nextEmptyOutFrame() throws Exception {
+    when(mockStub.send(Matchers.<HandshakerReq>any()))
+        .thenReturn(MockAltsHandshakerResp.getEmptyOutFrameResponse(BYTES_CONSUMED));
+
+    ByteBuffer inBytes = ByteBuffer.allocate(IN_BYTES_SIZE);
+    ByteBuffer outFrame = handshaker.next(inBytes);
+
+    assertEquals(0, outFrame.remaining());
+    assertFalse(handshaker.isFinished());
+    assertNull(handshaker.getResult());
+    assertNull(handshaker.getKey());
+    assertEquals(IN_BYTES_SIZE - BYTES_CONSUMED, inBytes.remaining());
+  }
+
+  @Test
+  public void nextFinished() throws Exception {
+    when(mockStub.send(Matchers.<HandshakerReq>any()))
+        .thenReturn(MockAltsHandshakerResp.getFinishedResponse(BYTES_CONSUMED));
+
+    ByteBuffer inBytes = ByteBuffer.allocate(IN_BYTES_SIZE);
+    ByteBuffer outFrame = handshaker.next(inBytes);
+
+    assertEquals(ByteString.copyFrom(outFrame), MockAltsHandshakerResp.getOutFrame());
+    assertTrue(handshaker.isFinished());
+    assertArrayEquals(handshaker.getKey(), MockAltsHandshakerResp.getTestKeyData());
+    assertEquals(IN_BYTES_SIZE - BYTES_CONSUMED, inBytes.remaining());
+  }
+
+  @Test
+  public void setRpcVersions() throws Exception {
+    when(mockStub.send(Matchers.<HandshakerReq>any()))
+        .thenReturn(MockAltsHandshakerResp.getOkResponse(0));
+
+    RpcProtocolVersions rpcVersions =
+        RpcProtocolVersions.newBuilder()
+            .setMinRpcVersion(
+                RpcProtocolVersions.Version.newBuilder().setMajor(3).setMinor(4).build())
+            .setMaxRpcVersion(
+                RpcProtocolVersions.Version.newBuilder().setMajor(5).setMinor(6).build())
+            .build();
+    clientOptions =
+        new AltsClientOptions.Builder()
+            .setTargetName(TEST_TARGET_NAME)
+            .addTargetServiceAccount(TEST_TARGET_SERVICE_ACCOUNT)
+            .setRpcProtocolVersions(rpcVersions)
+            .build();
+    handshaker = new AltsHandshakerClient(mockStub, clientOptions);
+
+    handshaker.startClientHandshake();
+
+    ArgumentCaptor<HandshakerReq> reqCaptor = ArgumentCaptor.forClass(HandshakerReq.class);
+    verify(mockStub).send(reqCaptor.capture());
+    assertEquals(rpcVersions, reqCaptor.getValue().getClientStart().getRpcVersions());
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerOptionsTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerOptionsTest.java
new file mode 100644
index 0000000..6a7814b
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerOptionsTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AltsHandshakerOptions}. */
+@RunWith(JUnit4.class)
+public final class AltsHandshakerOptionsTest {
+
+  @Test
+  public void setAndGet() throws Exception {
+    RpcProtocolVersions rpcVersions =
+        RpcProtocolVersions.newBuilder()
+            .setMaxRpcVersion(
+                RpcProtocolVersions.Version.newBuilder().setMajor(2).setMinor(1).build())
+            .setMinRpcVersion(
+                RpcProtocolVersions.Version.newBuilder().setMajor(2).setMinor(1).build())
+            .build();
+
+    AltsHandshakerOptions options = new AltsHandshakerOptions(rpcVersions);
+    assertThat(options.getRpcProtocolVersions()).isEqualTo(rpcVersions);
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerStubTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerStubTest.java
new file mode 100644
index 0000000..b896b5b
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsHandshakerStubTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.protobuf.ByteString;
+import io.grpc.alts.internal.Handshaker.HandshakerReq;
+import io.grpc.alts.internal.Handshaker.HandshakerResp;
+import io.grpc.alts.internal.Handshaker.NextHandshakeMessageReq;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AltsHandshakerStub}. */
+@RunWith(JUnit4.class)
+public class AltsHandshakerStubTest {
+  /** Mock status of handshaker service. */
+  private static enum Status {
+    OK,
+    ERROR,
+    COMPLETE
+  }
+
+  private AltsHandshakerStub stub;
+  private MockWriter writer;
+
+  @Before
+  public void setUp() {
+    writer = new MockWriter();
+    stub = new AltsHandshakerStub(writer);
+    writer.setReader(stub.getReaderForTest());
+  }
+
+  /** Send a message as in_bytes and expect same message as out_frames echo back. */
+  private void sendSuccessfulMessage() throws Exception {
+    String message = "hello world";
+    HandshakerReq.Builder req =
+        HandshakerReq.newBuilder()
+            .setNext(
+                NextHandshakeMessageReq.newBuilder()
+                    .setInBytes(ByteString.copyFromUtf8(message))
+                    .build());
+    HandshakerResp resp = stub.send(req.build());
+    assertEquals(resp.getOutFrames().toStringUtf8(), message);
+  }
+
+  /** Send a message and expect an IOException on error. */
+  private void sendAndExpectError() throws InterruptedException {
+    try {
+      stub.send(HandshakerReq.newBuilder().build());
+      fail("Exception expected");
+    } catch (IOException ex) {
+      assertThat(ex).hasMessageThat().contains("Received a terminating error");
+    }
+  }
+
+  /** Send a message and expect an IOException on closing. */
+  private void sendAndExpectComplete() throws InterruptedException {
+    try {
+      stub.send(HandshakerReq.newBuilder().build());
+      fail("Exception expected");
+    } catch (IOException ex) {
+      assertThat(ex).hasMessageThat().contains("Response stream closed");
+    }
+  }
+
+  /** Send a message and expect an IOException on unexpected message. */
+  private void sendAndExpectUnexpectedMessage() throws InterruptedException {
+    try {
+      stub.send(HandshakerReq.newBuilder().build());
+      fail("Exception expected");
+    } catch (IOException ex) {
+      assertThat(ex).hasMessageThat().contains("Received an unexpected response");
+    }
+  }
+
+  @Test
+  public void sendSuccessfulMessageTest() throws Exception {
+    writer.setServiceStatus(Status.OK);
+    sendSuccessfulMessage();
+    stub.close();
+  }
+
+  @Test
+  public void getServiceErrorTest() throws InterruptedException {
+    writer.setServiceStatus(Status.ERROR);
+    sendAndExpectError();
+    stub.close();
+  }
+
+  @Test
+  public void getServiceCompleteTest() throws Exception {
+    writer.setServiceStatus(Status.COMPLETE);
+    sendAndExpectComplete();
+    stub.close();
+  }
+
+  @Test
+  public void getUnexpectedMessageTest() throws Exception {
+    writer.setServiceStatus(Status.OK);
+    writer.sendUnexpectedResponse();
+    sendAndExpectUnexpectedMessage();
+    stub.close();
+  }
+
+  @Test
+  public void closeEarlyTest() throws InterruptedException {
+    stub.close();
+    sendAndExpectComplete();
+  }
+
+  private static class MockWriter implements StreamObserver<HandshakerReq> {
+    private StreamObserver<HandshakerResp> reader;
+    private Status status = Status.OK;
+
+    private void setReader(StreamObserver<HandshakerResp> reader) {
+      this.reader = reader;
+    }
+
+    private void setServiceStatus(Status status) {
+      this.status = status;
+    }
+
+    /** Send a handshaker response to reader. */
+    private void sendUnexpectedResponse() {
+      reader.onNext(HandshakerResp.newBuilder().build());
+    }
+
+    /** Mock writer onNext. Will respond based on the server status. */
+    @Override
+    public void onNext(final HandshakerReq req) {
+      switch (status) {
+        case OK:
+          HandshakerResp.Builder resp = HandshakerResp.newBuilder();
+          reader.onNext(resp.setOutFrames(req.getNext().getInBytes()).build());
+          break;
+        case ERROR:
+          reader.onError(new RuntimeException());
+          break;
+        case COMPLETE:
+          reader.onCompleted();
+          break;
+        default:
+          return;
+      }
+    }
+
+    @Override
+    public void onError(Throwable t) {}
+
+    /** Mock writer onComplete. */
+    @Override
+    public void onCompleted() {
+      reader.onCompleted();
+    }
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsProtocolNegotiatorTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsProtocolNegotiatorTest.java
new file mode 100644
index 0000000..936c298
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsProtocolNegotiatorTest.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.Attributes;
+import io.grpc.CallCredentials;
+import io.grpc.Grpc;
+import io.grpc.InternalChannelz;
+import io.grpc.SecurityLevel;
+import io.grpc.alts.internal.Handshaker.HandshakerResult;
+import io.grpc.alts.internal.TsiFrameProtector.Consumer;
+import io.grpc.alts.internal.TsiPeer.Property;
+import io.grpc.netty.GrpcHttp2ConnectionHandler;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.CompositeByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelDuplexHandler;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.codec.http2.DefaultHttp2Connection;
+import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder;
+import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder;
+import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
+import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
+import io.netty.handler.codec.http2.Http2Connection;
+import io.netty.handler.codec.http2.Http2ConnectionDecoder;
+import io.netty.handler.codec.http2.Http2ConnectionEncoder;
+import io.netty.handler.codec.http2.Http2FrameReader;
+import io.netty.handler.codec.http2.Http2FrameWriter;
+import io.netty.handler.codec.http2.Http2Settings;
+import io.netty.util.ReferenceCountUtil;
+import io.netty.util.ReferenceCounted;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link AltsProtocolNegotiator}. */
+@RunWith(JUnit4.class)
+public class AltsProtocolNegotiatorTest {
+  private final CapturingGrpcHttp2ConnectionHandler grpcHandler = capturingGrpcHandler();
+
+  private final List<ReferenceCounted> references = new ArrayList<>();
+  private final LinkedBlockingQueue<InterceptingProtector> protectors = new LinkedBlockingQueue<>();
+
+  private EmbeddedChannel channel;
+  private Throwable caughtException;
+
+  private volatile TsiHandshakeHandler.TsiHandshakeCompletionEvent tsiEvent;
+  private ChannelHandler handler;
+
+  private TsiPeer mockedTsiPeer = new TsiPeer(Collections.<Property<?>>emptyList());
+  private AltsAuthContext mockedAltsContext =
+      new AltsAuthContext(
+          HandshakerResult.newBuilder()
+              .setPeerRpcVersions(RpcProtocolVersionsUtil.getRpcProtocolVersions())
+              .build());
+  private final TsiHandshaker mockHandshaker =
+      new DelegatingTsiHandshaker(FakeTsiHandshaker.newFakeHandshakerServer()) {
+        @Override
+        public TsiPeer extractPeer() throws GeneralSecurityException {
+          return mockedTsiPeer;
+        }
+
+        @Override
+        public Object extractPeerObject() throws GeneralSecurityException {
+          return mockedAltsContext;
+        }
+      };
+  private final NettyTsiHandshaker serverHandshaker = new NettyTsiHandshaker(mockHandshaker);
+
+  @Before
+  public void setup() throws Exception {
+    ChannelHandler userEventHandler =
+        new ChannelDuplexHandler() {
+          @Override
+          public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+            if (evt instanceof TsiHandshakeHandler.TsiHandshakeCompletionEvent) {
+              tsiEvent = (TsiHandshakeHandler.TsiHandshakeCompletionEvent) evt;
+            } else {
+              super.userEventTriggered(ctx, evt);
+            }
+          }
+        };
+
+    ChannelHandler uncaughtExceptionHandler =
+        new ChannelDuplexHandler() {
+          @Override
+          public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+            caughtException = cause;
+            super.exceptionCaught(ctx, cause);
+          }
+        };
+
+    TsiHandshakerFactory handshakerFactory =
+        new DelegatingTsiHandshakerFactory(FakeTsiHandshaker.clientHandshakerFactory()) {
+          @Override
+          public TsiHandshaker newHandshaker() {
+            return new DelegatingTsiHandshaker(super.newHandshaker()) {
+              @Override
+              public TsiPeer extractPeer() throws GeneralSecurityException {
+                return mockedTsiPeer;
+              }
+
+              @Override
+              public Object extractPeerObject() throws GeneralSecurityException {
+                return mockedAltsContext;
+              }
+            };
+          }
+        };
+    handler = AltsProtocolNegotiator.create(handshakerFactory).newHandler(grpcHandler);
+    channel = new EmbeddedChannel(uncaughtExceptionHandler, handler, userEventHandler);
+  }
+
+  @After
+  public void teardown() throws Exception {
+    if (channel != null) {
+      @SuppressWarnings("unused") // go/futurereturn-lsc
+      Future<?> possiblyIgnoredError = channel.close();
+    }
+
+    for (ReferenceCounted reference : references) {
+      ReferenceCountUtil.safeRelease(reference);
+    }
+  }
+
+  @Test
+  public void handshakeShouldBeSuccessful() throws Exception {
+    doHandshake();
+  }
+
+  @Test
+  @SuppressWarnings("unchecked") // List cast
+  public void protectShouldRoundtrip() throws Exception {
+    // Write the message 1 character at a time. The message should be buffered
+    // and not interfere with the handshake.
+    final AtomicInteger writeCount = new AtomicInteger();
+    String message = "hello";
+    for (int ix = 0; ix < message.length(); ++ix) {
+      ByteBuf in = Unpooled.copiedBuffer(message, ix, 1, UTF_8);
+      @SuppressWarnings("unused") // go/futurereturn-lsc
+      Future<?> possiblyIgnoredError =
+          channel
+              .write(in)
+              .addListener(
+                  new ChannelFutureListener() {
+                    @Override
+                    public void operationComplete(ChannelFuture future) throws Exception {
+                      if (future.isSuccess()) {
+                        writeCount.incrementAndGet();
+                      }
+                    }
+                  });
+    }
+    channel.flush();
+
+    // Now do the handshake. The buffered message will automatically be protected
+    // and sent.
+    doHandshake();
+
+    // Capture the protected data written to the wire.
+    assertEquals(1, channel.outboundMessages().size());
+    ByteBuf protectedData = channel.<ByteBuf>readOutbound();
+    assertEquals(message.length(), writeCount.get());
+
+    // Read the protected message at the server and verify it matches the original message.
+    TsiFrameProtector serverProtector = serverHandshaker.createFrameProtector(channel.alloc());
+    List<ByteBuf> unprotected = new ArrayList<>();
+    serverProtector.unprotect(protectedData, (List<Object>) (List<?>) unprotected, channel.alloc());
+    // We try our best to remove the HTTP2 handler as soon as possible, but just by constructing it
+    // a settings frame is written (and an HTTP2 preface).  This is hard coded into Netty, so we
+    // have to remove it here.  See {@code Http2ConnectionHandler.PrefaceDecode.sendPreface}.
+    int settingsFrameLength = 9;
+
+    CompositeByteBuf unprotectedAll =
+        new CompositeByteBuf(channel.alloc(), false, unprotected.size() + 1, unprotected);
+    ByteBuf unprotectedData = unprotectedAll.slice(settingsFrameLength, message.length());
+    assertEquals(message, unprotectedData.toString(UTF_8));
+
+    // Protect the same message at the server.
+    final AtomicReference<ByteBuf> newlyProtectedData = new AtomicReference<>();
+    serverProtector.protectFlush(
+        Collections.singletonList(unprotectedData),
+        new Consumer<ByteBuf>() {
+          @Override
+          public void accept(ByteBuf buf) {
+            newlyProtectedData.set(buf);
+          }
+        },
+        channel.alloc());
+
+    // Read the protected message at the client and verify that it matches the original message.
+    channel.writeInbound(newlyProtectedData.get());
+    assertEquals(1, channel.inboundMessages().size());
+    assertEquals(message, channel.<ByteBuf>readInbound().toString(UTF_8));
+  }
+
+  @Test
+  public void unprotectLargeIncomingFrame() throws Exception {
+
+    // We use a server frameprotector with twice the standard frame size.
+    int serverFrameSize = 4096 * 2;
+    // This should fit into one frame.
+    byte[] unprotectedBytes = new byte[serverFrameSize - 500];
+    Arrays.fill(unprotectedBytes, (byte) 7);
+    ByteBuf unprotectedData = Unpooled.wrappedBuffer(unprotectedBytes);
+    unprotectedData.writerIndex(unprotectedBytes.length);
+
+    // Perform handshake.
+    doHandshake();
+
+    // Protect the message on the server.
+    TsiFrameProtector serverProtector =
+        serverHandshaker.createFrameProtector(serverFrameSize, channel.alloc());
+    serverProtector.protectFlush(
+        Collections.singletonList(unprotectedData),
+        new Consumer<ByteBuf>() {
+          @Override
+          public void accept(ByteBuf buf) {
+            channel.writeInbound(buf);
+          }
+        },
+        channel.alloc());
+    channel.flushInbound();
+
+    // Read the protected message at the client and verify that it matches the original message.
+    assertEquals(1, channel.inboundMessages().size());
+
+    ByteBuf receivedData1 = channel.<ByteBuf>readInbound();
+    int receivedLen1 = receivedData1.readableBytes();
+    byte[] receivedBytes = new byte[receivedLen1];
+    receivedData1.readBytes(receivedBytes, 0, receivedLen1);
+
+    assertThat(unprotectedBytes.length).isEqualTo(receivedBytes.length);
+    assertThat(unprotectedBytes).isEqualTo(receivedBytes);
+  }
+
+  @Test
+  public void flushShouldFailAllPromises() throws Exception {
+    doHandshake();
+
+    channel
+        .pipeline()
+        .addFirst(
+            new ChannelDuplexHandler() {
+              @Override
+              public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
+                  throws Exception {
+                throw new Exception("Fake exception");
+              }
+            });
+
+    // Write the message 1 character at a time.
+    String message = "hello";
+    final AtomicInteger failures = new AtomicInteger();
+    for (int ix = 0; ix < message.length(); ++ix) {
+      ByteBuf in = Unpooled.copiedBuffer(message, ix, 1, UTF_8);
+      @SuppressWarnings("unused") // go/futurereturn-lsc
+      Future<?> possiblyIgnoredError =
+          channel
+              .write(in)
+              .addListener(
+                  new ChannelFutureListener() {
+                    @Override
+                    public void operationComplete(ChannelFuture future) throws Exception {
+                      if (!future.isSuccess()) {
+                        failures.incrementAndGet();
+                      }
+                    }
+                  });
+    }
+    channel.flush();
+
+    // Verify that the promises fail.
+    assertEquals(message.length(), failures.get());
+  }
+
+  @Test
+  public void doNotFlushEmptyBuffer() throws Exception {
+    doHandshake();
+    assertEquals(1, protectors.size());
+    InterceptingProtector protector = protectors.poll();
+
+    String message = "hello";
+    ByteBuf in = Unpooled.copiedBuffer(message, UTF_8);
+
+    assertEquals(0, protector.flushes.get());
+    Future<?> done = channel.write(in);
+    channel.flush();
+    done.get(5, TimeUnit.SECONDS);
+    assertEquals(1, protector.flushes.get());
+
+    done = channel.write(Unpooled.EMPTY_BUFFER);
+    channel.flush();
+    done.get(5, TimeUnit.SECONDS);
+    assertEquals(1, protector.flushes.get());
+  }
+
+  @Test
+  public void peerPropagated() throws Exception {
+    doHandshake();
+
+    assertThat(grpcHandler.attrs.get(AltsProtocolNegotiator.getTsiPeerAttributeKey()))
+        .isEqualTo(mockedTsiPeer);
+    assertThat(grpcHandler.attrs.get(AltsProtocolNegotiator.getAltsAuthContextAttributeKey()))
+        .isEqualTo(mockedAltsContext);
+    assertThat(grpcHandler.attrs.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR).toString())
+        .isEqualTo("embedded");
+    assertThat(grpcHandler.attrs.get(CallCredentials.ATTR_SECURITY_LEVEL))
+        .isEqualTo(SecurityLevel.PRIVACY_AND_INTEGRITY);
+  }
+
+  private void doHandshake() throws Exception {
+    // Capture the client frame and add to the server.
+    assertEquals(1, channel.outboundMessages().size());
+    ByteBuf clientFrame = channel.<ByteBuf>readOutbound();
+    assertTrue(serverHandshaker.processBytesFromPeer(clientFrame));
+
+    // Get the server response handshake frames.
+    ByteBuf serverFrame = channel.alloc().buffer();
+    serverHandshaker.getBytesToSendToPeer(serverFrame);
+    channel.writeInbound(serverFrame);
+
+    // Capture the next client frame and add to the server.
+    assertEquals(1, channel.outboundMessages().size());
+    clientFrame = channel.<ByteBuf>readOutbound();
+    assertTrue(serverHandshaker.processBytesFromPeer(clientFrame));
+
+    // Get the server response handshake frames.
+    serverFrame = channel.alloc().buffer();
+    serverHandshaker.getBytesToSendToPeer(serverFrame);
+    channel.writeInbound(serverFrame);
+
+    // Ensure that both sides have confirmed that the handshake has completed.
+    assertFalse(serverHandshaker.isInProgress());
+
+    if (caughtException != null) {
+      throw new RuntimeException(caughtException);
+    }
+    assertNotNull(tsiEvent);
+  }
+
+  private CapturingGrpcHttp2ConnectionHandler capturingGrpcHandler() {
+    // Netty Boilerplate.  We don't really need any of this, but there is a tight coupling
+    // between a Http2ConnectionHandler and its dependencies.
+    Http2Connection connection = new DefaultHttp2Connection(true);
+    Http2FrameWriter frameWriter = new DefaultHttp2FrameWriter();
+    Http2FrameReader frameReader = new DefaultHttp2FrameReader(false);
+    DefaultHttp2ConnectionEncoder encoder =
+        new DefaultHttp2ConnectionEncoder(connection, frameWriter);
+    DefaultHttp2ConnectionDecoder decoder =
+        new DefaultHttp2ConnectionDecoder(connection, encoder, frameReader);
+
+    return new CapturingGrpcHttp2ConnectionHandler(decoder, encoder, new Http2Settings());
+  }
+
+  private final class CapturingGrpcHttp2ConnectionHandler extends GrpcHttp2ConnectionHandler {
+    private Attributes attrs;
+
+    private CapturingGrpcHttp2ConnectionHandler(
+        Http2ConnectionDecoder decoder,
+        Http2ConnectionEncoder encoder,
+        Http2Settings initialSettings) {
+      super(null, decoder, encoder, initialSettings);
+    }
+
+    @Override
+    public void handleProtocolNegotiationCompleted(
+        Attributes attrs, InternalChannelz.Security securityInfo) {
+      // If we are added to the pipeline, we need to remove ourselves.  The HTTP2 handler
+      channel.pipeline().remove(this);
+      this.attrs = attrs;
+    }
+  }
+
+  private static class DelegatingTsiHandshakerFactory implements TsiHandshakerFactory {
+
+    private TsiHandshakerFactory delegate;
+
+    DelegatingTsiHandshakerFactory(TsiHandshakerFactory delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public TsiHandshaker newHandshaker() {
+      return delegate.newHandshaker();
+    }
+  }
+
+  private class DelegatingTsiHandshaker implements TsiHandshaker {
+
+    private final TsiHandshaker delegate;
+
+    DelegatingTsiHandshaker(TsiHandshaker delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public void getBytesToSendToPeer(ByteBuffer bytes) throws GeneralSecurityException {
+      delegate.getBytesToSendToPeer(bytes);
+    }
+
+    @Override
+    public boolean processBytesFromPeer(ByteBuffer bytes) throws GeneralSecurityException {
+      return delegate.processBytesFromPeer(bytes);
+    }
+
+    @Override
+    public boolean isInProgress() {
+      return delegate.isInProgress();
+    }
+
+    @Override
+    public TsiPeer extractPeer() throws GeneralSecurityException {
+      return delegate.extractPeer();
+    }
+
+    @Override
+    public Object extractPeerObject() throws GeneralSecurityException {
+      return delegate.extractPeerObject();
+    }
+
+    @Override
+    public TsiFrameProtector createFrameProtector(ByteBufAllocator alloc) {
+      InterceptingProtector protector =
+          new InterceptingProtector(delegate.createFrameProtector(alloc));
+      protectors.add(protector);
+      return protector;
+    }
+
+    @Override
+    public TsiFrameProtector createFrameProtector(int maxFrameSize, ByteBufAllocator alloc) {
+      InterceptingProtector protector =
+          new InterceptingProtector(delegate.createFrameProtector(maxFrameSize, alloc));
+      protectors.add(protector);
+      return protector;
+    }
+  }
+
+  private static class InterceptingProtector implements TsiFrameProtector {
+    private final TsiFrameProtector delegate;
+    final AtomicInteger flushes = new AtomicInteger();
+
+    InterceptingProtector(TsiFrameProtector delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public void protectFlush(
+        List<ByteBuf> unprotectedBufs, Consumer<ByteBuf> ctxWrite, ByteBufAllocator alloc)
+        throws GeneralSecurityException {
+      flushes.incrementAndGet();
+      delegate.protectFlush(unprotectedBufs, ctxWrite, alloc);
+    }
+
+    @Override
+    public void unprotect(ByteBuf in, List<Object> out, ByteBufAllocator alloc)
+        throws GeneralSecurityException {
+      delegate.unprotect(in, out, alloc);
+    }
+
+    @Override
+    public void destroy() {
+      delegate.destroy();
+    }
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsTsiFrameProtectorTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsTsiFrameProtectorTest.java
new file mode 100644
index 0000000..4405a99
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsTsiFrameProtectorTest.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.alts.internal.ByteBufTestUtils.getDirectBuffer;
+import static io.grpc.alts.internal.ByteBufTestUtils.getRandom;
+import static io.grpc.alts.internal.ByteBufTestUtils.writeSlice;
+import static org.junit.Assert.fail;
+
+import com.google.common.testing.GcFinalization;
+import io.grpc.alts.internal.ByteBufTestUtils.RegisterRef;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.util.ReferenceCounted;
+import io.netty.util.ResourceLeakDetector;
+import io.netty.util.ResourceLeakDetector.Level;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AltsTsiFrameProtector}. */
+@RunWith(JUnit4.class)
+public class AltsTsiFrameProtectorTest {
+  private static final int FRAME_MIN_SIZE =
+      AltsTsiFrameProtector.getHeaderTypeFieldBytes() + FakeChannelCrypter.getTagBytes();
+
+  private final List<ReferenceCounted> references = new ArrayList<>();
+  private final RegisterRef ref =
+      new RegisterRef() {
+        @Override
+        public ByteBuf register(ByteBuf buf) {
+          if (buf != null) {
+            references.add(buf);
+          }
+          return buf;
+        }
+      };
+
+  @Before
+  public void setUp() {
+    ResourceLeakDetector.setLevel(Level.PARANOID);
+  }
+
+  @After
+  public void teardown() {
+    for (ReferenceCounted reference : references) {
+      reference.release();
+    }
+    references.clear();
+    // Increase our chances to detect ByteBuf leaks.
+    GcFinalization.awaitFullGc();
+  }
+
+  @Test
+  public void parserHeader_frameLengthNegativeFails() throws GeneralSecurityException {
+    ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;
+    List<Object> out = new ArrayList<>();
+    FakeChannelCrypter crypter = new FakeChannelCrypter();
+    AltsTsiFrameProtector.Unprotector unprotector =
+        new AltsTsiFrameProtector.Unprotector(crypter, alloc);
+    ByteBuf in = getDirectBuffer(AltsTsiFrameProtector.getHeaderBytes(), ref);
+    in.writeIntLE(-1);
+    in.writeIntLE(6);
+    try {
+      unprotector.unprotect(in, out, alloc);
+      fail("Exception expected");
+    } catch (IllegalArgumentException ex) {
+      assertThat(ex).hasMessageThat().contains("Invalid header field: frame size too small");
+    }
+
+    unprotector.destroy();
+  }
+
+  @Test
+  public void parserHeader_frameTooSmall() throws GeneralSecurityException {
+    ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;
+    List<Object> out = new ArrayList<>();
+    FakeChannelCrypter crypter = new FakeChannelCrypter();
+    AltsTsiFrameProtector.Unprotector unprotector =
+        new AltsTsiFrameProtector.Unprotector(crypter, alloc);
+    ByteBuf in =
+        getDirectBuffer(
+            AltsTsiFrameProtector.getHeaderBytes() + FakeChannelCrypter.getTagBytes(), ref);
+    in.writeIntLE(FRAME_MIN_SIZE - 1);
+    in.writeIntLE(6);
+    try {
+      unprotector.unprotect(in, out, alloc);
+      fail("Exception expected");
+    } catch (IllegalArgumentException ex) {
+      assertThat(ex).hasMessageThat().contains("Invalid header field: frame size too small");
+    }
+
+    unprotector.destroy();
+  }
+
+  @Test
+  public void parserHeader_frameTooLarge() throws GeneralSecurityException {
+    ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;
+    List<Object> out = new ArrayList<>();
+    FakeChannelCrypter crypter = new FakeChannelCrypter();
+    AltsTsiFrameProtector.Unprotector unprotector =
+        new AltsTsiFrameProtector.Unprotector(crypter, alloc);
+    ByteBuf in =
+        getDirectBuffer(
+            AltsTsiFrameProtector.getHeaderBytes() + FakeChannelCrypter.getTagBytes(), ref);
+    in.writeIntLE(
+        AltsTsiFrameProtector.getLimitMaxAllowedFrameBytes()
+            - AltsTsiFrameProtector.getHeaderLenFieldBytes()
+            + 1);
+    in.writeIntLE(6);
+    try {
+      unprotector.unprotect(in, out, alloc);
+      fail("Exception expected");
+    } catch (IllegalArgumentException ex) {
+      assertThat(ex).hasMessageThat().contains("Invalid header field: frame size too large");
+    }
+
+    unprotector.destroy();
+  }
+
+  @Test
+  public void parserHeader_frameTypeInvalid() throws GeneralSecurityException {
+    ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;
+    List<Object> out = new ArrayList<>();
+    FakeChannelCrypter crypter = new FakeChannelCrypter();
+    AltsTsiFrameProtector.Unprotector unprotector =
+        new AltsTsiFrameProtector.Unprotector(crypter, alloc);
+    ByteBuf in =
+        getDirectBuffer(
+            AltsTsiFrameProtector.getHeaderBytes() + FakeChannelCrypter.getTagBytes(), ref);
+    in.writeIntLE(FRAME_MIN_SIZE);
+    in.writeIntLE(5);
+    try {
+      unprotector.unprotect(in, out, alloc);
+      fail("Exception expected");
+    } catch (IllegalArgumentException ex) {
+      assertThat(ex).hasMessageThat().contains("Invalid header field: frame type");
+    }
+
+    unprotector.destroy();
+  }
+
+  @Test
+  public void parserHeader_frameZeroOk() throws GeneralSecurityException {
+    ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;
+    List<Object> out = new ArrayList<>();
+    FakeChannelCrypter crypter = new FakeChannelCrypter();
+    AltsTsiFrameProtector.Unprotector unprotector =
+        new AltsTsiFrameProtector.Unprotector(crypter, alloc);
+    ByteBuf in =
+        getDirectBuffer(
+            AltsTsiFrameProtector.getHeaderBytes() + FakeChannelCrypter.getTagBytes(), ref);
+    in.writeIntLE(FRAME_MIN_SIZE);
+    in.writeIntLE(6);
+
+    unprotector.unprotect(in, out, alloc);
+    assertThat(in.readableBytes()).isEqualTo(0);
+
+    unprotector.destroy();
+  }
+
+  @Test
+  public void parserHeader_EmptyUnprotectNoRetain() throws GeneralSecurityException {
+    ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;
+    List<Object> out = new ArrayList<>();
+    FakeChannelCrypter crypter = new FakeChannelCrypter();
+    AltsTsiFrameProtector.Unprotector unprotector =
+        new AltsTsiFrameProtector.Unprotector(crypter, alloc);
+    ByteBuf emptyBuf = getDirectBuffer(0, ref);
+    unprotector.unprotect(emptyBuf, out, alloc);
+
+    assertThat(emptyBuf.refCnt()).isEqualTo(1);
+
+    unprotector.destroy();
+  }
+
+  @Test
+  public void parserHeader_frameMaxOk() throws GeneralSecurityException {
+    ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;
+    List<Object> out = new ArrayList<>();
+    FakeChannelCrypter crypter = new FakeChannelCrypter();
+    AltsTsiFrameProtector.Unprotector unprotector =
+        new AltsTsiFrameProtector.Unprotector(crypter, alloc);
+    ByteBuf in =
+        getDirectBuffer(
+            AltsTsiFrameProtector.getHeaderBytes() + FakeChannelCrypter.getTagBytes(), ref);
+    in.writeIntLE(
+        AltsTsiFrameProtector.getLimitMaxAllowedFrameBytes()
+            - AltsTsiFrameProtector.getHeaderLenFieldBytes());
+    in.writeIntLE(6);
+
+    unprotector.unprotect(in, out, alloc);
+    assertThat(in.readableBytes()).isEqualTo(0);
+
+    unprotector.destroy();
+  }
+
+  @Test
+  public void parserHeader_frameOkFragment() throws GeneralSecurityException {
+    ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;
+    List<Object> out = new ArrayList<>();
+    FakeChannelCrypter crypter = new FakeChannelCrypter();
+    AltsTsiFrameProtector.Unprotector unprotector =
+        new AltsTsiFrameProtector.Unprotector(crypter, alloc);
+    ByteBuf in =
+        getDirectBuffer(
+            AltsTsiFrameProtector.getHeaderBytes() + FakeChannelCrypter.getTagBytes(), ref);
+    in.writeIntLE(FRAME_MIN_SIZE);
+    in.writeIntLE(6);
+    ByteBuf in1 = in.readSlice(AltsTsiFrameProtector.getHeaderBytes() - 1);
+    ByteBuf in2 = in.readSlice(1);
+
+    unprotector.unprotect(in1, out, alloc);
+    assertThat(in1.readableBytes()).isEqualTo(0);
+
+    unprotector.unprotect(in2, out, alloc);
+    assertThat(in2.readableBytes()).isEqualTo(0);
+
+    unprotector.destroy();
+  }
+
+  @Test
+  public void parseHeader_frameFailFragment() throws GeneralSecurityException {
+    ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;
+    List<Object> out = new ArrayList<>();
+    FakeChannelCrypter crypter = new FakeChannelCrypter();
+    AltsTsiFrameProtector.Unprotector unprotector =
+        new AltsTsiFrameProtector.Unprotector(crypter, alloc);
+    ByteBuf in =
+        getDirectBuffer(
+            AltsTsiFrameProtector.getHeaderBytes() + FakeChannelCrypter.getTagBytes(), ref);
+    in.writeIntLE(FRAME_MIN_SIZE - 1);
+    in.writeIntLE(6);
+    ByteBuf in1 = in.readSlice(AltsTsiFrameProtector.getHeaderBytes() - 1);
+    ByteBuf in2 = in.readSlice(1);
+
+    unprotector.unprotect(in1, out, alloc);
+    assertThat(in1.readableBytes()).isEqualTo(0);
+
+    try {
+      unprotector.unprotect(in2, out, alloc);
+      fail("Exception expected");
+    } catch (IllegalArgumentException ex) {
+      assertThat(ex).hasMessageThat().contains("Invalid header field: frame size too small");
+    }
+
+    assertThat(in2.readableBytes()).isEqualTo(0);
+
+    unprotector.destroy();
+  }
+
+  @Test
+  public void parseFrame_oneFrameNoFragment() throws GeneralSecurityException {
+    int payloadBytes = 1024;
+    ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;
+    List<Object> out = new ArrayList<>();
+    FakeChannelCrypter crypter = new FakeChannelCrypter();
+    AltsTsiFrameProtector.Unprotector unprotector =
+        new AltsTsiFrameProtector.Unprotector(crypter, alloc);
+    ByteBuf plain = getRandom(payloadBytes, ref);
+    ByteBuf outFrame =
+        getDirectBuffer(
+            AltsTsiFrameProtector.getHeaderBytes()
+                + payloadBytes
+                + FakeChannelCrypter.getTagBytes(),
+            ref);
+
+    outFrame.writeIntLE(
+        AltsTsiFrameProtector.getHeaderTypeFieldBytes()
+            + payloadBytes
+            + FakeChannelCrypter.getTagBytes());
+    outFrame.writeIntLE(6);
+    List<ByteBuf> framePlain = Collections.singletonList(plain);
+    ByteBuf frameOut = writeSlice(outFrame, payloadBytes + FakeChannelCrypter.getTagBytes());
+    crypter.encrypt(frameOut, framePlain);
+    plain.readerIndex(0);
+
+    unprotector.unprotect(outFrame, out, alloc);
+    assertThat(outFrame.readableBytes()).isEqualTo(0);
+    assertThat(out.size()).isEqualTo(1);
+    ByteBuf out1 = ref((ByteBuf) out.get(0));
+    assertThat(out1).isEqualTo(plain);
+
+    unprotector.destroy();
+  }
+
+  @Test
+  public void parseFrame_twoFramesNoFragment() throws GeneralSecurityException {
+    int payloadBytes = 1536;
+    int payloadBytes1 = 1024;
+    int payloadBytes2 = payloadBytes - payloadBytes1;
+    ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;
+    List<Object> out = new ArrayList<>();
+    FakeChannelCrypter crypter = new FakeChannelCrypter();
+    AltsTsiFrameProtector.Unprotector unprotector =
+        new AltsTsiFrameProtector.Unprotector(crypter, alloc);
+
+    ByteBuf plain = getRandom(payloadBytes, ref);
+    ByteBuf outFrame =
+        getDirectBuffer(
+            2 * (AltsTsiFrameProtector.getHeaderBytes() + FakeChannelCrypter.getTagBytes())
+                + payloadBytes,
+            ref);
+
+    outFrame.writeIntLE(
+        AltsTsiFrameProtector.getHeaderTypeFieldBytes()
+            + payloadBytes1
+            + FakeChannelCrypter.getTagBytes());
+    outFrame.writeIntLE(6);
+    List<ByteBuf> framePlain1 = Collections.singletonList(plain.readSlice(payloadBytes1));
+    ByteBuf frameOut1 = writeSlice(outFrame, payloadBytes1 + FakeChannelCrypter.getTagBytes());
+
+    outFrame.writeIntLE(
+        AltsTsiFrameProtector.getHeaderTypeFieldBytes()
+            + payloadBytes2
+            + FakeChannelCrypter.getTagBytes());
+    outFrame.writeIntLE(6);
+    List<ByteBuf> framePlain2 = Collections.singletonList(plain);
+    ByteBuf frameOut2 = writeSlice(outFrame, payloadBytes2 + FakeChannelCrypter.getTagBytes());
+
+    crypter.encrypt(frameOut1, framePlain1);
+    crypter.encrypt(frameOut2, framePlain2);
+    plain.readerIndex(0);
+
+    unprotector.unprotect(outFrame, out, alloc);
+    assertThat(out.size()).isEqualTo(1);
+    ByteBuf out1 = ref((ByteBuf) out.get(0));
+    assertThat(out1).isEqualTo(plain);
+    assertThat(outFrame.refCnt()).isEqualTo(1);
+    assertThat(outFrame.readableBytes()).isEqualTo(0);
+
+    unprotector.destroy();
+  }
+
+  @Test
+  public void parseFrame_twoFramesNoFragment_Leftover() throws GeneralSecurityException {
+    int payloadBytes = 1536;
+    int payloadBytes1 = 1024;
+    int payloadBytes2 = payloadBytes - payloadBytes1;
+    ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;
+    List<Object> out = new ArrayList<>();
+    FakeChannelCrypter crypter = new FakeChannelCrypter();
+    AltsTsiFrameProtector.Unprotector unprotector =
+        new AltsTsiFrameProtector.Unprotector(crypter, alloc);
+
+    ByteBuf plain = getRandom(payloadBytes, ref);
+    ByteBuf protectedBuf =
+        getDirectBuffer(
+            2 * (AltsTsiFrameProtector.getHeaderBytes() + FakeChannelCrypter.getTagBytes())
+                + payloadBytes
+                + AltsTsiFrameProtector.getHeaderBytes(),
+            ref);
+
+    protectedBuf.writeIntLE(
+        AltsTsiFrameProtector.getHeaderTypeFieldBytes()
+            + payloadBytes1
+            + FakeChannelCrypter.getTagBytes());
+    protectedBuf.writeIntLE(6);
+    List<ByteBuf> framePlain1 = Collections.singletonList(plain.readSlice(payloadBytes1));
+    ByteBuf frameOut1 = writeSlice(protectedBuf, payloadBytes1 + FakeChannelCrypter.getTagBytes());
+
+    protectedBuf.writeIntLE(
+        AltsTsiFrameProtector.getHeaderTypeFieldBytes()
+            + payloadBytes2
+            + FakeChannelCrypter.getTagBytes());
+    protectedBuf.writeIntLE(6);
+    List<ByteBuf> framePlain2 = Collections.singletonList(plain);
+    ByteBuf frameOut2 = writeSlice(protectedBuf, payloadBytes2 + FakeChannelCrypter.getTagBytes());
+    // This is an invalid header length field, make sure it triggers an error
+    // when the remainder of the header is given.
+    protectedBuf.writeIntLE((byte) -1);
+
+    crypter.encrypt(frameOut1, framePlain1);
+    crypter.encrypt(frameOut2, framePlain2);
+    plain.readerIndex(0);
+
+    unprotector.unprotect(protectedBuf, out, alloc);
+    assertThat(out.size()).isEqualTo(1);
+    ByteBuf out1 = ref((ByteBuf) out.get(0));
+    assertThat(out1).isEqualTo(plain);
+
+    // The protectedBuf is buffered inside the unprotector.
+    assertThat(protectedBuf.readableBytes()).isEqualTo(0);
+    assertThat(protectedBuf.refCnt()).isEqualTo(2);
+
+    protectedBuf.writeIntLE(6);
+    try {
+      unprotector.unprotect(protectedBuf, out, alloc);
+      fail("Exception expected");
+    } catch (IllegalArgumentException ex) {
+      assertThat(ex).hasMessageThat().contains("Invalid header field: frame size too small");
+    }
+
+    unprotector.destroy();
+
+    // Make sure that unprotector does not hold onto buffered ByteBuf instance after destroy.
+    assertThat(protectedBuf.refCnt()).isEqualTo(1);
+
+    // Make sure that destroying twice does not throw.
+    unprotector.destroy();
+  }
+
+  @Test
+  public void parseFrame_twoFramesFragmentSecond() throws GeneralSecurityException {
+    int payloadBytes = 1536;
+    int payloadBytes1 = 1024;
+    int payloadBytes2 = payloadBytes - payloadBytes1;
+    ByteBufAllocator alloc = ByteBufAllocator.DEFAULT;
+    List<Object> out = new ArrayList<>();
+    FakeChannelCrypter crypter = new FakeChannelCrypter();
+    AltsTsiFrameProtector.Unprotector unprotector =
+        new AltsTsiFrameProtector.Unprotector(crypter, alloc);
+
+    ByteBuf plain = getRandom(payloadBytes, ref);
+    ByteBuf protectedBuf =
+        getDirectBuffer(
+            2 * (AltsTsiFrameProtector.getHeaderBytes() + FakeChannelCrypter.getTagBytes())
+                + payloadBytes
+                + AltsTsiFrameProtector.getHeaderBytes(),
+            ref);
+
+    protectedBuf.writeIntLE(
+        AltsTsiFrameProtector.getHeaderTypeFieldBytes()
+            + payloadBytes1
+            + FakeChannelCrypter.getTagBytes());
+    protectedBuf.writeIntLE(6);
+    List<ByteBuf> framePlain1 = Collections.singletonList(plain.readSlice(payloadBytes1));
+    ByteBuf frameOut1 = writeSlice(protectedBuf, payloadBytes1 + FakeChannelCrypter.getTagBytes());
+
+    protectedBuf.writeIntLE(
+        AltsTsiFrameProtector.getHeaderTypeFieldBytes()
+            + payloadBytes2
+            + FakeChannelCrypter.getTagBytes());
+    protectedBuf.writeIntLE(6);
+    List<ByteBuf> framePlain2 = Collections.singletonList(plain);
+    ByteBuf frameOut2 = writeSlice(protectedBuf, payloadBytes2 + FakeChannelCrypter.getTagBytes());
+
+    crypter.encrypt(frameOut1, framePlain1);
+    crypter.encrypt(frameOut2, framePlain2);
+    plain.readerIndex(0);
+
+    unprotector.unprotect(
+        protectedBuf.readSlice(
+            payloadBytes
+                + AltsTsiFrameProtector.getHeaderBytes()
+                + FakeChannelCrypter.getTagBytes()
+                + AltsTsiFrameProtector.getHeaderBytes()),
+        out,
+        alloc);
+    assertThat(out.size()).isEqualTo(1);
+    ByteBuf out1 = ref((ByteBuf) out.get(0));
+    assertThat(out1).isEqualTo(plain.readSlice(payloadBytes1));
+    assertThat(protectedBuf.refCnt()).isEqualTo(2);
+
+    unprotector.unprotect(protectedBuf, out, alloc);
+    assertThat(out.size()).isEqualTo(2);
+    ByteBuf out2 = ref((ByteBuf) out.get(1));
+    assertThat(out2).isEqualTo(plain);
+    assertThat(protectedBuf.refCnt()).isEqualTo(1);
+
+    unprotector.destroy();
+  }
+
+  private ByteBuf ref(ByteBuf buf) {
+    if (buf != null) {
+      references.add(buf);
+    }
+    return buf;
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsTsiHandshakerTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsTsiHandshakerTest.java
new file mode 100644
index 0000000..ac3dfb8
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsTsiHandshakerTest.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.protobuf.ByteString;
+import io.grpc.alts.internal.Handshaker.HandshakerResult;
+import io.grpc.alts.internal.Handshaker.Identity;
+import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions;
+import java.nio.ByteBuffer;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Matchers;
+
+/** Unit tests for {@link AltsTsiHandshaker}. */
+@RunWith(JUnit4.class)
+public class AltsTsiHandshakerTest {
+  private static final String TEST_KEY_DATA = "super secret 123";
+  private static final String TEST_APPLICATION_PROTOCOL = "grpc";
+  private static final String TEST_RECORD_PROTOCOL = "ALTSRP_GCM_AES128";
+  private static final String TEST_CLIENT_SERVICE_ACCOUNT = "client@developer.gserviceaccount.com";
+  private static final String TEST_SERVER_SERVICE_ACCOUNT = "server@developer.gserviceaccount.com";
+  private static final int OUT_FRAME_SIZE = 100;
+  private static final int TRANSPORT_BUFFER_SIZE = 200;
+  private static final int TEST_MAX_RPC_VERSION_MAJOR = 3;
+  private static final int TEST_MAX_RPC_VERSION_MINOR = 2;
+  private static final int TEST_MIN_RPC_VERSION_MAJOR = 2;
+  private static final int TEST_MIN_RPC_VERSION_MINOR = 1;
+  private static final RpcProtocolVersions TEST_RPC_PROTOCOL_VERSIONS =
+      RpcProtocolVersions.newBuilder()
+          .setMaxRpcVersion(
+              RpcProtocolVersions.Version.newBuilder()
+                  .setMajor(TEST_MAX_RPC_VERSION_MAJOR)
+                  .setMinor(TEST_MAX_RPC_VERSION_MINOR)
+                  .build())
+          .setMinRpcVersion(
+              RpcProtocolVersions.Version.newBuilder()
+                  .setMajor(TEST_MIN_RPC_VERSION_MAJOR)
+                  .setMinor(TEST_MIN_RPC_VERSION_MINOR)
+                  .build())
+          .build();
+
+  private AltsHandshakerClient mockClient;
+  private AltsHandshakerClient mockServer;
+  private AltsTsiHandshaker handshakerClient;
+  private AltsTsiHandshaker handshakerServer;
+
+  @Before
+  public void setUp() throws Exception {
+    mockClient = mock(AltsHandshakerClient.class);
+    mockServer = mock(AltsHandshakerClient.class);
+    handshakerClient = new AltsTsiHandshaker(true, mockClient);
+    handshakerServer = new AltsTsiHandshaker(false, mockServer);
+  }
+
+  private HandshakerResult getHandshakerResult(boolean isClient) {
+    HandshakerResult.Builder builder =
+        HandshakerResult.newBuilder()
+            .setApplicationProtocol(TEST_APPLICATION_PROTOCOL)
+            .setRecordProtocol(TEST_RECORD_PROTOCOL)
+            .setKeyData(ByteString.copyFromUtf8(TEST_KEY_DATA))
+            .setPeerRpcVersions(TEST_RPC_PROTOCOL_VERSIONS);
+    if (isClient) {
+      builder.setPeerIdentity(
+          Identity.newBuilder().setServiceAccount(TEST_SERVER_SERVICE_ACCOUNT).build());
+      builder.setLocalIdentity(
+          Identity.newBuilder().setServiceAccount(TEST_CLIENT_SERVICE_ACCOUNT).build());
+    } else {
+      builder.setPeerIdentity(
+          Identity.newBuilder().setServiceAccount(TEST_CLIENT_SERVICE_ACCOUNT).build());
+      builder.setLocalIdentity(
+          Identity.newBuilder().setServiceAccount(TEST_SERVER_SERVICE_ACCOUNT).build());
+    }
+    return builder.build();
+  }
+
+  @Test
+  public void processBytesFromPeerFalseStart() throws Exception {
+    verify(mockClient, never()).startClientHandshake();
+    verify(mockClient, never()).startServerHandshake(Matchers.<ByteBuffer>any());
+    verify(mockClient, never()).next(Matchers.<ByteBuffer>any());
+
+    ByteBuffer transportBuffer = ByteBuffer.allocate(TRANSPORT_BUFFER_SIZE);
+    assertTrue(handshakerClient.processBytesFromPeer(transportBuffer));
+  }
+
+  @Test
+  public void processBytesFromPeerStartServer() throws Exception {
+    ByteBuffer transportBuffer = ByteBuffer.allocate(TRANSPORT_BUFFER_SIZE);
+    ByteBuffer outputFrame = ByteBuffer.allocate(OUT_FRAME_SIZE);
+    verify(mockServer, never()).startClientHandshake();
+    verify(mockServer, never()).next(Matchers.<ByteBuffer>any());
+    // Mock transport buffer all consumed by processBytesFromPeer and there is an output frame.
+    transportBuffer.position(transportBuffer.limit());
+    when(mockServer.startServerHandshake(transportBuffer)).thenReturn(outputFrame);
+    when(mockServer.isFinished()).thenReturn(false);
+
+    assertTrue(handshakerServer.processBytesFromPeer(transportBuffer));
+  }
+
+  @Test
+  public void processBytesFromPeerStartServerEmptyOutput() throws Exception {
+    ByteBuffer transportBuffer = ByteBuffer.allocate(TRANSPORT_BUFFER_SIZE);
+    ByteBuffer emptyOutputFrame = ByteBuffer.allocate(0);
+    verify(mockServer, never()).startClientHandshake();
+    verify(mockServer, never()).next(Matchers.<ByteBuffer>any());
+    // Mock transport buffer all consumed by processBytesFromPeer and output frame is empty.
+    // Expect processBytesFromPeer return False, because more data are needed from the peer.
+    transportBuffer.position(transportBuffer.limit());
+    when(mockServer.startServerHandshake(transportBuffer)).thenReturn(emptyOutputFrame);
+    when(mockServer.isFinished()).thenReturn(false);
+
+    assertFalse(handshakerServer.processBytesFromPeer(transportBuffer));
+  }
+
+  @Test
+  public void processBytesFromPeerStartServerFinished() throws Exception {
+    ByteBuffer transportBuffer = ByteBuffer.allocate(TRANSPORT_BUFFER_SIZE);
+    ByteBuffer outputFrame = ByteBuffer.allocate(OUT_FRAME_SIZE);
+    verify(mockServer, never()).startClientHandshake();
+    verify(mockServer, never()).next(Matchers.<ByteBuffer>any());
+    // Mock handshake complete after processBytesFromPeer.
+    when(mockServer.startServerHandshake(transportBuffer)).thenReturn(outputFrame);
+    when(mockServer.isFinished()).thenReturn(true);
+
+    assertTrue(handshakerServer.processBytesFromPeer(transportBuffer));
+  }
+
+  @Test
+  public void processBytesFromPeerNoBytesConsumed() throws Exception {
+    ByteBuffer transportBuffer = ByteBuffer.allocate(TRANSPORT_BUFFER_SIZE);
+    ByteBuffer emptyOutputFrame = ByteBuffer.allocate(0);
+    verify(mockServer, never()).startClientHandshake();
+    verify(mockServer, never()).next(Matchers.<ByteBuffer>any());
+    when(mockServer.startServerHandshake(transportBuffer)).thenReturn(emptyOutputFrame);
+    when(mockServer.isFinished()).thenReturn(false);
+
+    try {
+      assertTrue(handshakerServer.processBytesFromPeer(transportBuffer));
+      fail("Expected IllegalStateException");
+    } catch (IllegalStateException expected) {
+      assertEquals("Handshaker did not consume any bytes.", expected.getMessage());
+    }
+  }
+
+  @Test
+  public void processBytesFromPeerClientNext() throws Exception {
+    ByteBuffer transportBuffer = ByteBuffer.allocate(TRANSPORT_BUFFER_SIZE);
+    ByteBuffer outputFrame = ByteBuffer.allocate(OUT_FRAME_SIZE);
+    verify(mockClient, never()).startServerHandshake(Matchers.<ByteBuffer>any());
+    when(mockClient.startClientHandshake()).thenReturn(outputFrame);
+    when(mockClient.next(transportBuffer)).thenReturn(outputFrame);
+    when(mockClient.isFinished()).thenReturn(false);
+
+    handshakerClient.getBytesToSendToPeer(transportBuffer);
+    transportBuffer.position(transportBuffer.limit());
+    assertFalse(handshakerClient.processBytesFromPeer(transportBuffer));
+  }
+
+  @Test
+  public void processBytesFromPeerClientNextFinished() throws Exception {
+    ByteBuffer transportBuffer = ByteBuffer.allocate(TRANSPORT_BUFFER_SIZE);
+    ByteBuffer outputFrame = ByteBuffer.allocate(OUT_FRAME_SIZE);
+    verify(mockClient, never()).startServerHandshake(Matchers.<ByteBuffer>any());
+    when(mockClient.startClientHandshake()).thenReturn(outputFrame);
+    when(mockClient.next(transportBuffer)).thenReturn(outputFrame);
+    when(mockClient.isFinished()).thenReturn(true);
+
+    handshakerClient.getBytesToSendToPeer(transportBuffer);
+    assertTrue(handshakerClient.processBytesFromPeer(transportBuffer));
+  }
+
+  @Test
+  public void extractPeerFailure() throws Exception {
+    when(mockClient.isFinished()).thenReturn(false);
+
+    try {
+      handshakerClient.extractPeer();
+      fail("Expected IllegalStateException");
+    } catch (IllegalStateException expected) {
+      assertEquals("Handshake is not complete.", expected.getMessage());
+    }
+  }
+
+  @Test
+  public void extractPeerObjectFailure() throws Exception {
+    when(mockClient.isFinished()).thenReturn(false);
+
+    try {
+      handshakerClient.extractPeerObject();
+      fail("Expected IllegalStateException");
+    } catch (IllegalStateException expected) {
+      assertEquals("Handshake is not complete.", expected.getMessage());
+    }
+  }
+
+  @Test
+  public void extractClientPeerSuccess() throws Exception {
+    ByteBuffer outputFrame = ByteBuffer.allocate(OUT_FRAME_SIZE);
+    ByteBuffer transportBuffer = ByteBuffer.allocate(TRANSPORT_BUFFER_SIZE);
+    when(mockClient.startClientHandshake()).thenReturn(outputFrame);
+    when(mockClient.isFinished()).thenReturn(true);
+    when(mockClient.getResult()).thenReturn(getHandshakerResult(/* isClient = */ true));
+
+    handshakerClient.getBytesToSendToPeer(transportBuffer);
+    TsiPeer clientPeer = handshakerClient.extractPeer();
+
+    assertEquals(1, clientPeer.getProperties().size());
+    assertEquals(
+        TEST_SERVER_SERVICE_ACCOUNT,
+        clientPeer.getProperty(AltsTsiHandshaker.TSI_SERVICE_ACCOUNT_PEER_PROPERTY).getValue());
+
+    AltsAuthContext clientContext = (AltsAuthContext) handshakerClient.extractPeerObject();
+    assertEquals(TEST_APPLICATION_PROTOCOL, clientContext.getApplicationProtocol());
+    assertEquals(TEST_RECORD_PROTOCOL, clientContext.getRecordProtocol());
+    assertEquals(TEST_SERVER_SERVICE_ACCOUNT, clientContext.getPeerServiceAccount());
+    assertEquals(TEST_CLIENT_SERVICE_ACCOUNT, clientContext.getLocalServiceAccount());
+    assertEquals(TEST_RPC_PROTOCOL_VERSIONS, clientContext.getPeerRpcVersions());
+  }
+
+  @Test
+  public void extractServerPeerSuccess() throws Exception {
+    ByteBuffer outputFrame = ByteBuffer.allocate(OUT_FRAME_SIZE);
+    ByteBuffer transportBuffer = ByteBuffer.allocate(TRANSPORT_BUFFER_SIZE);
+    when(mockServer.startServerHandshake(Matchers.<ByteBuffer>any())).thenReturn(outputFrame);
+    when(mockServer.isFinished()).thenReturn(true);
+    when(mockServer.getResult()).thenReturn(getHandshakerResult(/* isClient = */ false));
+
+    handshakerServer.processBytesFromPeer(transportBuffer);
+    handshakerServer.getBytesToSendToPeer(transportBuffer);
+    TsiPeer serverPeer = handshakerServer.extractPeer();
+
+    assertEquals(1, serverPeer.getProperties().size());
+    assertEquals(
+        TEST_CLIENT_SERVICE_ACCOUNT,
+        serverPeer.getProperty(AltsTsiHandshaker.TSI_SERVICE_ACCOUNT_PEER_PROPERTY).getValue());
+
+    AltsAuthContext serverContext = (AltsAuthContext) handshakerServer.extractPeerObject();
+    assertEquals(TEST_APPLICATION_PROTOCOL, serverContext.getApplicationProtocol());
+    assertEquals(TEST_RECORD_PROTOCOL, serverContext.getRecordProtocol());
+    assertEquals(TEST_CLIENT_SERVICE_ACCOUNT, serverContext.getPeerServiceAccount());
+    assertEquals(TEST_SERVER_SERVICE_ACCOUNT, serverContext.getLocalServiceAccount());
+    assertEquals(TEST_RPC_PROTOCOL_VERSIONS, serverContext.getPeerRpcVersions());
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/AltsTsiTest.java b/alts/src/test/java/io/grpc/alts/internal/AltsTsiTest.java
new file mode 100644
index 0000000..8d990bd
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/AltsTsiTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.testing.GcFinalization;
+import io.grpc.alts.internal.ByteBufTestUtils.RegisterRef;
+import io.grpc.alts.internal.Handshaker.HandshakeProtocol;
+import io.grpc.alts.internal.Handshaker.HandshakerReq;
+import io.grpc.alts.internal.Handshaker.HandshakerResp;
+import io.grpc.alts.internal.TsiTest.Handshakers;
+import io.netty.buffer.ByteBuf;
+import io.netty.util.ReferenceCounted;
+import io.netty.util.ResourceLeakDetector;
+import io.netty.util.ResourceLeakDetector.Level;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AltsTsiHandshaker}. */
+@RunWith(JUnit4.class)
+public class AltsTsiTest {
+  private static final int OVERHEAD =
+      FakeChannelCrypter.getTagBytes() + AltsTsiFrameProtector.getHeaderBytes();
+
+  private final List<ReferenceCounted> references = new ArrayList<>();
+  private AltsHandshakerClient client;
+  private AltsHandshakerClient server;
+  private final RegisterRef ref =
+      new RegisterRef() {
+        @Override
+        public ByteBuf register(ByteBuf buf) {
+          if (buf != null) {
+            references.add(buf);
+          }
+          return buf;
+        }
+      };
+
+  @Before
+  public void setUp() throws Exception {
+    ResourceLeakDetector.setLevel(Level.PARANOID);
+    // Use MockAltsHandshakerStub for all the tests.
+    AltsHandshakerOptions handshakerOptions = new AltsHandshakerOptions(null);
+    MockAltsHandshakerStub clientStub = new MockAltsHandshakerStub();
+    MockAltsHandshakerStub serverStub = new MockAltsHandshakerStub();
+    client = new AltsHandshakerClient(clientStub, handshakerOptions);
+    server = new AltsHandshakerClient(serverStub, handshakerOptions);
+  }
+
+  @After
+  public void tearDown() {
+    for (ReferenceCounted reference : references) {
+      reference.release();
+    }
+    references.clear();
+    // Increase our chances to detect ByteBuf leaks.
+    GcFinalization.awaitFullGc();
+  }
+
+  private Handshakers newHandshakers() {
+    TsiHandshaker clientHandshaker = new AltsTsiHandshaker(true, client);
+    TsiHandshaker serverHandshaker = new AltsTsiHandshaker(false, server);
+    return new Handshakers(clientHandshaker, serverHandshaker);
+  }
+
+  @Test
+  public void verifyHandshakePeer() throws Exception {
+    Handshakers handshakers = newHandshakers();
+    TsiTest.performHandshake(TsiTest.getDefaultTransportBufferSize(), handshakers);
+    TsiPeer clientPeer = handshakers.getClient().extractPeer();
+    assertEquals(1, clientPeer.getProperties().size());
+    assertEquals(
+        MockAltsHandshakerResp.getTestPeerAccount(),
+        clientPeer.getProperty("service_account").getValue());
+    TsiPeer serverPeer = handshakers.getServer().extractPeer();
+    assertEquals(1, serverPeer.getProperties().size());
+    assertEquals(
+        MockAltsHandshakerResp.getTestPeerAccount(),
+        serverPeer.getProperty("service_account").getValue());
+  }
+
+  @Test
+  public void handshake() throws GeneralSecurityException {
+    TsiTest.handshakeTest(newHandshakers());
+  }
+
+  @Test
+  public void handshakeSmallBuffer() throws GeneralSecurityException {
+    TsiTest.handshakeSmallBufferTest(newHandshakers());
+  }
+
+  @Test
+  public void pingPong() throws GeneralSecurityException {
+    TsiTest.pingPongTest(newHandshakers(), ref);
+  }
+
+  @Test
+  public void pingPongExactFrameSize() throws GeneralSecurityException {
+    TsiTest.pingPongExactFrameSizeTest(newHandshakers(), ref);
+  }
+
+  @Test
+  public void pingPongSmallBuffer() throws GeneralSecurityException {
+    TsiTest.pingPongSmallBufferTest(newHandshakers(), ref);
+  }
+
+  @Test
+  public void pingPongSmallFrame() throws GeneralSecurityException {
+    TsiTest.pingPongSmallFrameTest(OVERHEAD, newHandshakers(), ref);
+  }
+
+  @Test
+  public void pingPongSmallFrameSmallBuffer() throws GeneralSecurityException {
+    TsiTest.pingPongSmallFrameSmallBufferTest(OVERHEAD, newHandshakers(), ref);
+  }
+
+  @Test
+  public void corruptedCounter() throws GeneralSecurityException {
+    TsiTest.corruptedCounterTest(newHandshakers(), ref);
+  }
+
+  @Test
+  public void corruptedCiphertext() throws GeneralSecurityException {
+    TsiTest.corruptedCiphertextTest(newHandshakers(), ref);
+  }
+
+  @Test
+  public void corruptedTag() throws GeneralSecurityException {
+    TsiTest.corruptedTagTest(newHandshakers(), ref);
+  }
+
+  @Test
+  public void reflectedCiphertext() throws GeneralSecurityException {
+    TsiTest.reflectedCiphertextTest(newHandshakers(), ref);
+  }
+
+  private static class MockAltsHandshakerStub extends AltsHandshakerStub {
+    private boolean started = false;
+
+    @Override
+    public HandshakerResp send(HandshakerReq req) {
+      if (started) {
+        // Expect handshake next message.
+        if (req.getReqOneofCase().getNumber() != 3) {
+          return MockAltsHandshakerResp.getErrorResponse();
+        }
+        return MockAltsHandshakerResp.getFinishedResponse(req.getNext().getInBytes().size());
+      } else {
+        List<String> recordProtocols;
+        int bytesConsumed = 0;
+        switch (req.getReqOneofCase().getNumber()) {
+          case 1:
+            recordProtocols = req.getClientStart().getRecordProtocolsList();
+            break;
+          case 2:
+            recordProtocols =
+                req.getServerStart()
+                    .getHandshakeParametersMap()
+                    .get(HandshakeProtocol.ALTS.getNumber())
+                    .getRecordProtocolsList();
+            bytesConsumed = req.getServerStart().getInBytes().size();
+            break;
+          default:
+            return MockAltsHandshakerResp.getErrorResponse();
+        }
+        if (recordProtocols.isEmpty()) {
+          return MockAltsHandshakerResp.getErrorResponse();
+        }
+        started = true;
+        return MockAltsHandshakerResp.getOkResponse(bytesConsumed);
+      }
+    }
+
+    @Override
+    public void close() {}
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/BufUnwrapperTest.java b/alts/src/test/java/io/grpc/alts/internal/BufUnwrapperTest.java
new file mode 100644
index 0000000..67f318e
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/BufUnwrapperTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.truth.Truth;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.CompositeByteBuf;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import java.nio.ByteBuffer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class BufUnwrapperTest {
+
+  private final ByteBufAllocator alloc = UnpooledByteBufAllocator.DEFAULT;
+
+  @Test
+  public void closeEmptiesBuffers() {
+    BufUnwrapper unwrapper = new BufUnwrapper();
+    ByteBuf buf = alloc.buffer();
+    try {
+      ByteBuffer[] readableBufs = unwrapper.readableNioBuffers(buf);
+      ByteBuffer[] writableBufs = unwrapper.writableNioBuffers(buf);
+      Truth.assertThat(readableBufs).hasLength(1);
+      Truth.assertThat(readableBufs[0]).isNotNull();
+      Truth.assertThat(writableBufs).hasLength(1);
+      Truth.assertThat(writableBufs[0]).isNotNull();
+
+      unwrapper.close();
+
+      Truth.assertThat(readableBufs[0]).isNull();
+      Truth.assertThat(writableBufs[0]).isNull();
+    } finally {
+      buf.release();
+    }
+  }
+
+  @Test
+  public void readableNioBuffers_worksWithNormal() {
+    ByteBuf buf = alloc.buffer(1).writeByte('a');
+    try (BufUnwrapper unwrapper = new BufUnwrapper()) {
+      ByteBuffer[] internalBufs = unwrapper.readableNioBuffers(buf);
+      Truth.assertThat(internalBufs).hasLength(1);
+
+      assertEquals('a', internalBufs[0].get(0));
+    } finally {
+      buf.release();
+    }
+  }
+
+  @Test
+  public void readableNioBuffers_worksWithComposite() {
+    CompositeByteBuf buf = alloc.compositeBuffer();
+    buf.addComponent(true, alloc.buffer(1).writeByte('a'));
+    try (BufUnwrapper unwrapper = new BufUnwrapper()) {
+      ByteBuffer[] internalBufs = unwrapper.readableNioBuffers(buf);
+      Truth.assertThat(internalBufs).hasLength(1);
+
+      assertEquals('a', internalBufs[0].get(0));
+    } finally {
+      buf.release();
+    }
+  }
+
+  @Test
+  public void writableNioBuffers_indexesPreserved() {
+    ByteBuf buf = alloc.buffer(1);
+    int ridx = buf.readerIndex();
+    int widx = buf.writerIndex();
+    int cap = buf.capacity();
+    try (BufUnwrapper unwrapper = new BufUnwrapper()) {
+      ByteBuffer[] internalBufs = unwrapper.writableNioBuffers(buf);
+      Truth.assertThat(internalBufs).hasLength(1);
+
+      internalBufs[0].put((byte) 'a');
+
+      assertEquals(ridx, buf.readerIndex());
+      assertEquals(widx, buf.writerIndex());
+      assertEquals(cap, buf.capacity());
+    } finally {
+      buf.release();
+    }
+  }
+
+  @Test
+  public void writableNioBuffers_worksWithNormal() {
+    ByteBuf buf = alloc.buffer(1);
+    try (BufUnwrapper unwrapper = new BufUnwrapper()) {
+      ByteBuffer[] internalBufs = unwrapper.writableNioBuffers(buf);
+      Truth.assertThat(internalBufs).hasLength(1);
+
+      internalBufs[0].put((byte) 'a');
+
+      buf.writerIndex(1);
+      assertEquals('a', buf.readByte());
+    } finally {
+      buf.release();
+    }
+  }
+
+  @Test
+  public void writableNioBuffers_worksWithComposite() {
+    CompositeByteBuf buf = alloc.compositeBuffer();
+    buf.addComponent(alloc.buffer(1));
+    buf.capacity(1);
+    try (BufUnwrapper unwrapper = new BufUnwrapper()) {
+      ByteBuffer[] internalBufs = unwrapper.writableNioBuffers(buf);
+      Truth.assertThat(internalBufs).hasLength(1);
+
+      internalBufs[0].put((byte) 'a');
+
+      buf.writerIndex(1);
+      assertEquals('a', buf.readByte());
+    } finally {
+      buf.release();
+    }
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/ByteBufTestUtils.java b/alts/src/test/java/io/grpc/alts/internal/ByteBufTestUtils.java
new file mode 100644
index 0000000..0d0f13c
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/ByteBufTestUtils.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import com.google.common.base.Preconditions;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public final class ByteBufTestUtils {
+  public interface RegisterRef {
+    ByteBuf register(ByteBuf buf);
+  }
+
+  private static final Random random = new SecureRandom();
+
+  // The {@code ref} argument can be used to register the buffer for {@code release}.
+  // TODO: allow the allocator to be passed in.
+  public static ByteBuf getDirectBuffer(int len, RegisterRef ref) {
+    return ref.register(Unpooled.directBuffer(len));
+  }
+
+  /** Get random bytes. */
+  public static ByteBuf getRandom(int len, RegisterRef ref) {
+    ByteBuf buf = getDirectBuffer(len, ref);
+    byte[] bytes = new byte[len];
+    random.nextBytes(bytes);
+    buf.writeBytes(bytes);
+    return buf;
+  }
+
+  /** Fragment byte buffer into multiple pieces. */
+  public static List<ByteBuf> fragmentByteBuf(ByteBuf in, int num, RegisterRef ref) {
+    ByteBuf buf = in.slice();
+    Preconditions.checkArgument(num > 0);
+    List<ByteBuf> fragmentedBufs = new ArrayList<>(num);
+    int fragmentSize = buf.readableBytes() / num;
+    while (buf.isReadable()) {
+      int readBytes = num == 0 ? buf.readableBytes() : fragmentSize;
+      ByteBuf tmpBuf = getDirectBuffer(readBytes, ref);
+      tmpBuf.writeBytes(buf, readBytes);
+      fragmentedBufs.add(tmpBuf);
+      num--;
+    }
+    return fragmentedBufs;
+  }
+
+  static ByteBuf writeSlice(ByteBuf in, int len) {
+    Preconditions.checkArgument(len <= in.writableBytes());
+    ByteBuf out = in.slice(in.writerIndex(), len);
+    in.writerIndex(in.writerIndex() + len);
+    return out.writerIndex(0);
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/ChannelCrypterNettyTestBase.java b/alts/src/test/java/io/grpc/alts/internal/ChannelCrypterNettyTestBase.java
new file mode 100644
index 0000000..6de3c59
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/ChannelCrypterNettyTestBase.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.alts.internal.ByteBufTestUtils.getDirectBuffer;
+import static io.grpc.alts.internal.ByteBufTestUtils.getRandom;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.fail;
+
+import io.grpc.alts.internal.ByteBufTestUtils.RegisterRef;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.util.ReferenceCounted;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.crypto.AEADBadTagException;
+import org.junit.Test;
+
+/** Abstract class for unit tests of {@link ChannelCrypterNetty}. */
+public abstract class ChannelCrypterNettyTestBase {
+  private static final String DECRYPTION_FAILURE_MESSAGE = "Tag mismatch";
+
+  protected final List<ReferenceCounted> references = new ArrayList<>();
+  public ChannelCrypterNetty client;
+  public ChannelCrypterNetty server;
+  private final RegisterRef ref =
+      new RegisterRef() {
+        @Override
+        public ByteBuf register(ByteBuf buf) {
+          if (buf != null) {
+            references.add(buf);
+          }
+          return buf;
+        }
+      };
+
+  static final class FrameEncrypt {
+    List<ByteBuf> plain;
+    ByteBuf out;
+  }
+
+  static final class FrameDecrypt {
+    List<ByteBuf> ciphertext;
+    ByteBuf out;
+    ByteBuf tag;
+  }
+
+  FrameEncrypt createFrameEncrypt(String message) {
+    byte[] messageBytes = message.getBytes(UTF_8);
+    FrameEncrypt frame = new FrameEncrypt();
+    ByteBuf plain = getDirectBuffer(messageBytes.length, ref);
+    plain.writeBytes(messageBytes);
+    frame.plain = Collections.singletonList(plain);
+    frame.out = getDirectBuffer(messageBytes.length + client.getSuffixLength(), ref);
+    return frame;
+  }
+
+  FrameDecrypt frameDecryptOfEncrypt(FrameEncrypt frameEncrypt) {
+    int tagLen = client.getSuffixLength();
+    FrameDecrypt frameDecrypt = new FrameDecrypt();
+    ByteBuf out = frameEncrypt.out;
+    frameDecrypt.ciphertext =
+        Collections.singletonList(out.slice(out.readerIndex(), out.readableBytes() - tagLen));
+    frameDecrypt.tag = out.slice(out.readerIndex() + out.readableBytes() - tagLen, tagLen);
+    frameDecrypt.out = getDirectBuffer(out.readableBytes(), ref);
+    return frameDecrypt;
+  }
+
+  @Test
+  public void encryptDecrypt() throws GeneralSecurityException {
+    String message = "Hello world";
+    FrameEncrypt frameEncrypt = createFrameEncrypt(message);
+    client.encrypt(frameEncrypt.out, frameEncrypt.plain);
+    FrameDecrypt frameDecrypt = frameDecryptOfEncrypt(frameEncrypt);
+
+    server.decrypt(frameDecrypt.out, frameDecrypt.tag, frameDecrypt.ciphertext);
+    assertThat(frameEncrypt.plain.get(0).slice(0, frameDecrypt.out.readableBytes()))
+        .isEqualTo(frameDecrypt.out);
+  }
+
+  @Test
+  public void encryptDecryptLarge() throws GeneralSecurityException {
+    FrameEncrypt frameEncrypt = new FrameEncrypt();
+    ByteBuf plain = getRandom(17 * 1024, ref);
+    frameEncrypt.plain = Collections.singletonList(plain);
+    frameEncrypt.out = getDirectBuffer(plain.readableBytes() + client.getSuffixLength(), ref);
+
+    client.encrypt(frameEncrypt.out, frameEncrypt.plain);
+    FrameDecrypt frameDecrypt = frameDecryptOfEncrypt(frameEncrypt);
+
+    // Call decrypt overload that takes ciphertext and tag.
+    server.decrypt(frameDecrypt.out, frameEncrypt.out);
+    assertThat(frameEncrypt.plain.get(0).slice(0, frameDecrypt.out.readableBytes()))
+        .isEqualTo(frameDecrypt.out);
+  }
+
+  @Test
+  public void encryptDecryptMultiple() throws GeneralSecurityException {
+    String message = "Hello world";
+    for (int i = 0; i < 512; ++i) {
+      FrameEncrypt frameEncrypt = createFrameEncrypt(message);
+      client.encrypt(frameEncrypt.out, frameEncrypt.plain);
+      FrameDecrypt frameDecrypt = frameDecryptOfEncrypt(frameEncrypt);
+
+      server.decrypt(frameDecrypt.out, frameDecrypt.tag, frameDecrypt.ciphertext);
+      assertThat(frameEncrypt.plain.get(0).slice(0, frameDecrypt.out.readableBytes()))
+          .isEqualTo(frameDecrypt.out);
+    }
+  }
+
+  @Test
+  public void encryptDecryptComposite() throws GeneralSecurityException {
+    String message = "Hello world";
+    int lastLen = 2;
+    byte[] messageBytes = message.getBytes(UTF_8);
+    FrameEncrypt frameEncrypt = new FrameEncrypt();
+    ByteBuf plain1 = getDirectBuffer(messageBytes.length - lastLen, ref);
+    ByteBuf plain2 = getDirectBuffer(lastLen, ref);
+    plain1.writeBytes(messageBytes, 0, messageBytes.length - lastLen);
+    plain2.writeBytes(messageBytes, messageBytes.length - lastLen, lastLen);
+    ByteBuf plain = Unpooled.wrappedBuffer(plain1, plain2);
+    frameEncrypt.plain = Collections.singletonList(plain);
+    frameEncrypt.out = getDirectBuffer(messageBytes.length + client.getSuffixLength(), ref);
+
+    client.encrypt(frameEncrypt.out, frameEncrypt.plain);
+
+    int tagLen = client.getSuffixLength();
+    FrameDecrypt frameDecrypt = new FrameDecrypt();
+    ByteBuf out = frameEncrypt.out;
+    int outLen = out.readableBytes();
+    ByteBuf cipher1 = getDirectBuffer(outLen - lastLen - tagLen, ref);
+    ByteBuf cipher2 = getDirectBuffer(lastLen, ref);
+    cipher1.writeBytes(out, 0, outLen - lastLen - tagLen);
+    cipher2.writeBytes(out, outLen - tagLen - lastLen, lastLen);
+    ByteBuf cipher = Unpooled.wrappedBuffer(cipher1, cipher2);
+    frameDecrypt.ciphertext = Collections.singletonList(cipher);
+    frameDecrypt.tag = out.slice(out.readerIndex() + out.readableBytes() - tagLen, tagLen);
+    frameDecrypt.out = getDirectBuffer(out.readableBytes(), ref);
+
+    server.decrypt(frameDecrypt.out, frameDecrypt.tag, frameDecrypt.ciphertext);
+    assertThat(frameEncrypt.plain.get(0).slice(0, frameDecrypt.out.readableBytes()))
+        .isEqualTo(frameDecrypt.out);
+  }
+
+  @Test
+  public void reflection() throws GeneralSecurityException {
+    String message = "Hello world";
+    FrameEncrypt frameEncrypt = createFrameEncrypt(message);
+    client.encrypt(frameEncrypt.out, frameEncrypt.plain);
+    FrameDecrypt frameDecrypt = frameDecryptOfEncrypt(frameEncrypt);
+    try {
+      client.decrypt(frameDecrypt.out, frameDecrypt.tag, frameDecrypt.ciphertext);
+      fail("Exception expected");
+    } catch (AEADBadTagException ex) {
+      assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_MESSAGE);
+    }
+  }
+
+  @Test
+  public void skipMessage() throws GeneralSecurityException {
+    String message = "Hello world";
+    FrameEncrypt frameEncrypt1 = createFrameEncrypt(message);
+    client.encrypt(frameEncrypt1.out, frameEncrypt1.plain);
+    FrameEncrypt frameEncrypt2 = createFrameEncrypt(message);
+    client.encrypt(frameEncrypt2.out, frameEncrypt2.plain);
+    FrameDecrypt frameDecrypt = frameDecryptOfEncrypt(frameEncrypt2);
+
+    try {
+      client.decrypt(frameDecrypt.out, frameDecrypt.tag, frameDecrypt.ciphertext);
+      fail("Exception expected");
+    } catch (AEADBadTagException ex) {
+      assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_MESSAGE);
+    }
+  }
+
+  @Test
+  public void corruptMessage() throws GeneralSecurityException {
+    String message = "Hello world";
+    FrameEncrypt frameEncrypt = createFrameEncrypt(message);
+    client.encrypt(frameEncrypt.out, frameEncrypt.plain);
+    FrameDecrypt frameDecrypt = frameDecryptOfEncrypt(frameEncrypt);
+    frameEncrypt.out.setByte(3, frameEncrypt.out.getByte(3) + 1);
+
+    try {
+      client.decrypt(frameDecrypt.out, frameDecrypt.tag, frameDecrypt.ciphertext);
+      fail("Exception expected");
+    } catch (AEADBadTagException ex) {
+      assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_MESSAGE);
+    }
+  }
+
+  @Test
+  public void replayMessage() throws GeneralSecurityException {
+    String message = "Hello world";
+    FrameEncrypt frameEncrypt = createFrameEncrypt(message);
+    client.encrypt(frameEncrypt.out, frameEncrypt.plain);
+    FrameDecrypt frameDecrypt1 = frameDecryptOfEncrypt(frameEncrypt);
+    FrameDecrypt frameDecrypt2 = frameDecryptOfEncrypt(frameEncrypt);
+
+    server.decrypt(frameDecrypt1.out, frameDecrypt1.tag, frameDecrypt1.ciphertext);
+
+    try {
+      server.decrypt(frameDecrypt2.out, frameDecrypt2.tag, frameDecrypt2.ciphertext);
+      fail("Exception expected");
+    } catch (AEADBadTagException ex) {
+      assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_MESSAGE);
+    }
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/FakeChannelCrypter.java b/alts/src/test/java/io/grpc/alts/internal/FakeChannelCrypter.java
new file mode 100644
index 0000000..037af01
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/FakeChannelCrypter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import io.netty.buffer.ByteBuf;
+import java.security.GeneralSecurityException;
+import java.util.Collections;
+import java.util.List;
+import javax.crypto.AEADBadTagException;
+
+public final class FakeChannelCrypter implements ChannelCrypterNetty {
+  private static final int TAG_BYTES = 16;
+  private static final byte TAG_BYTE = (byte) 0xa1;
+
+  private boolean destroyCalled = false;
+
+  public static int getTagBytes() {
+    return TAG_BYTES;
+  }
+
+  @Override
+  public void encrypt(ByteBuf out, List<ByteBuf> plain) throws GeneralSecurityException {
+    checkState(!destroyCalled);
+    for (ByteBuf buf : plain) {
+      out.writeBytes(buf);
+      for (int i = 0; i < TAG_BYTES; ++i) {
+        out.writeByte(TAG_BYTE);
+      }
+    }
+  }
+
+  @Override
+  public void decrypt(ByteBuf out, ByteBuf tag, List<ByteBuf> ciphertext)
+      throws GeneralSecurityException {
+    checkState(!destroyCalled);
+    for (ByteBuf buf : ciphertext) {
+      out.writeBytes(buf);
+    }
+    while (tag.isReadable()) {
+      if (tag.readByte() != TAG_BYTE) {
+        throw new AEADBadTagException("Tag mismatch!");
+      }
+    }
+  }
+
+  @Override
+  public void decrypt(ByteBuf out, ByteBuf ciphertextAndTag) throws GeneralSecurityException {
+    checkState(!destroyCalled);
+    ByteBuf ciphertext = ciphertextAndTag.readSlice(ciphertextAndTag.readableBytes() - TAG_BYTES);
+    decrypt(out, /*tag=*/ ciphertextAndTag, Collections.singletonList(ciphertext));
+  }
+
+  @Override
+  public int getSuffixLength() {
+    return TAG_BYTES;
+  }
+
+  @Override
+  public void destroy() {
+    destroyCalled = true;
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/FakeTsiHandshaker.java b/alts/src/test/java/io/grpc/alts/internal/FakeTsiHandshaker.java
new file mode 100644
index 0000000..ea728e3
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/FakeTsiHandshaker.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Preconditions;
+import io.grpc.alts.internal.TsiPeer.Property;
+import io.netty.buffer.ByteBufAllocator;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.Collections;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A fake handshaker compatible with security/transport_security/fake_transport_security.h See
+ * {@link TsiHandshaker} for documentation.
+ */
+public class FakeTsiHandshaker implements TsiHandshaker {
+  private static final Logger logger = Logger.getLogger(FakeTsiHandshaker.class.getName());
+
+  private static final TsiHandshakerFactory clientHandshakerFactory =
+      new TsiHandshakerFactory() {
+        @Override
+        public TsiHandshaker newHandshaker() {
+          return new FakeTsiHandshaker(true);
+        }
+      };
+
+  private static final TsiHandshakerFactory serverHandshakerFactory =
+      new TsiHandshakerFactory() {
+        @Override
+        public TsiHandshaker newHandshaker() {
+          return new FakeTsiHandshaker(false);
+        }
+      };
+
+  private boolean isClient;
+  private ByteBuffer sendBuffer = null;
+  private AltsFraming.Parser frameParser = new AltsFraming.Parser();
+
+  private State sendState;
+  private State receiveState;
+
+  enum State {
+    CLIENT_NONE,
+    SERVER_NONE,
+    CLIENT_INIT,
+    SERVER_INIT,
+    CLIENT_FINISHED,
+    SERVER_FINISHED;
+
+    // Returns the next State. In order to advance to sendState=N, receiveState must be N-1.
+    public State next() {
+      if (ordinal() + 1 < values().length) {
+        return values()[ordinal() + 1];
+      }
+      throw new UnsupportedOperationException("Can't call next() on last element: " + this);
+    }
+  }
+
+  public static TsiHandshakerFactory clientHandshakerFactory() {
+    return clientHandshakerFactory;
+  }
+
+  public static TsiHandshakerFactory serverHandshakerFactory() {
+    return serverHandshakerFactory;
+  }
+
+  public static TsiHandshaker newFakeHandshakerClient() {
+    return clientHandshakerFactory.newHandshaker();
+  }
+
+  public static TsiHandshaker newFakeHandshakerServer() {
+    return serverHandshakerFactory.newHandshaker();
+  }
+
+  protected FakeTsiHandshaker(boolean isClient) {
+    this.isClient = isClient;
+    if (isClient) {
+      sendState = State.CLIENT_NONE;
+      receiveState = State.SERVER_NONE;
+    } else {
+      sendState = State.SERVER_NONE;
+      receiveState = State.CLIENT_NONE;
+    }
+  }
+
+  private State getNextState(State state) {
+    switch (state) {
+      case CLIENT_NONE:
+        return State.CLIENT_INIT;
+      case SERVER_NONE:
+        return State.SERVER_INIT;
+      case CLIENT_INIT:
+        return State.CLIENT_FINISHED;
+      case SERVER_INIT:
+        return State.SERVER_FINISHED;
+      default:
+        return null;
+    }
+  }
+
+  private String getNextMessage() {
+    State result = getNextState(sendState);
+    return result == null ? "BAD STATE" : result.toString();
+  }
+
+  private String getExpectedMessage() {
+    State result = getNextState(receiveState);
+    return result == null ? "BAD STATE" : result.toString();
+  }
+
+  private void incrementSendState() {
+    sendState = getNextState(sendState);
+  }
+
+  private void incrementReceiveState() {
+    receiveState = getNextState(receiveState);
+  }
+
+  @Override
+  public void getBytesToSendToPeer(ByteBuffer bytes) throws GeneralSecurityException {
+    Preconditions.checkNotNull(bytes);
+
+    // If we're done, return nothing.
+    if (sendState == State.CLIENT_FINISHED || sendState == State.SERVER_FINISHED) {
+      return;
+    }
+
+    // Prepare the next message, if neeeded.
+    if (sendBuffer == null) {
+      if (sendState.next() != receiveState) {
+        // We're still waiting for bytes from the peer, so bail.
+        return;
+      }
+      ByteBuffer payload = ByteBuffer.wrap(getNextMessage().getBytes(UTF_8));
+      sendBuffer = AltsFraming.toFrame(payload, payload.remaining());
+      logger.log(Level.FINE, "Buffered message: {0}", getNextMessage());
+    }
+    while (bytes.hasRemaining() && sendBuffer.hasRemaining()) {
+      bytes.put(sendBuffer.get());
+    }
+    if (!sendBuffer.hasRemaining()) {
+      // Get ready to send the next message.
+      sendBuffer = null;
+      incrementSendState();
+    }
+  }
+
+  @Override
+  public boolean processBytesFromPeer(ByteBuffer bytes) throws GeneralSecurityException {
+    Preconditions.checkNotNull(bytes);
+
+    frameParser.readBytes(bytes);
+    if (frameParser.isComplete()) {
+      ByteBuffer messageBytes = frameParser.getRawFrame();
+      int offset = AltsFraming.getFramingOverhead();
+      int length = messageBytes.limit() - offset;
+      String message = new String(messageBytes.array(), offset, length, UTF_8);
+      logger.log(Level.FINE, "Read message: {0}", message);
+
+      if (!message.equals(getExpectedMessage())) {
+        throw new IllegalArgumentException(
+            "Bad handshake message. Got "
+                + message
+                + " (length = "
+                + message.length()
+                + ") expected "
+                + getExpectedMessage()
+                + " (length = "
+                + getExpectedMessage().length()
+                + ")");
+      }
+      incrementReceiveState();
+      return true;
+    }
+    return false;
+  }
+
+  @Override
+  public boolean isInProgress() {
+    boolean finishedReceiving =
+        receiveState == State.CLIENT_FINISHED || receiveState == State.SERVER_FINISHED;
+    boolean finishedSending =
+        sendState == State.CLIENT_FINISHED || sendState == State.SERVER_FINISHED;
+    return !finishedSending || !finishedReceiving;
+  }
+
+  @Override
+  public TsiPeer extractPeer() {
+    return new TsiPeer(Collections.<Property<?>>emptyList());
+  }
+
+  @Override
+  public Object extractPeerObject() {
+    return AltsAuthContext.getDefaultInstance();
+  }
+
+  @Override
+  public TsiFrameProtector createFrameProtector(int maxFrameSize, ByteBufAllocator alloc) {
+    Preconditions.checkState(!isInProgress(), "Handshake is not complete.");
+
+    // We use an all-zero key, since this is the fake handshaker.
+    byte[] key = new byte[AltsChannelCrypter.getKeyLength()];
+    return new AltsTsiFrameProtector(maxFrameSize, new AltsChannelCrypter(key, isClient), alloc);
+  }
+
+  @Override
+  public TsiFrameProtector createFrameProtector(ByteBufAllocator alloc) {
+    return createFrameProtector(AltsTsiFrameProtector.getMaxAllowedFrameBytes(), alloc);
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/FakeTsiTest.java b/alts/src/test/java/io/grpc/alts/internal/FakeTsiTest.java
new file mode 100644
index 0000000..cf7c313
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/FakeTsiTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.google.common.testing.GcFinalization;
+import io.grpc.alts.internal.ByteBufTestUtils.RegisterRef;
+import io.grpc.alts.internal.TsiTest.Handshakers;
+import io.netty.buffer.ByteBuf;
+import io.netty.util.ReferenceCounted;
+import io.netty.util.ResourceLeakDetector;
+import io.netty.util.ResourceLeakDetector.Level;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link TsiHandshaker}. */
+@RunWith(JUnit4.class)
+public class FakeTsiTest {
+
+  private static final int OVERHEAD =
+      FakeChannelCrypter.getTagBytes() + AltsTsiFrameProtector.getHeaderBytes();
+
+  private final List<ReferenceCounted> references = new ArrayList<>();
+  private final RegisterRef ref =
+      new RegisterRef() {
+        @Override
+        public ByteBuf register(ByteBuf buf) {
+          if (buf != null) {
+            references.add(buf);
+          }
+          return buf;
+        }
+      };
+
+  private static Handshakers newHandshakers() {
+    TsiHandshaker clientHandshaker = FakeTsiHandshaker.newFakeHandshakerClient();
+    TsiHandshaker serverHandshaker = FakeTsiHandshaker.newFakeHandshakerServer();
+    return new Handshakers(clientHandshaker, serverHandshaker);
+  }
+
+  @Before
+  public void setUp() {
+    ResourceLeakDetector.setLevel(Level.PARANOID);
+  }
+
+  @After
+  public void tearDown() {
+    for (ReferenceCounted reference : references) {
+      reference.release();
+    }
+    references.clear();
+    // Increase our chances to detect ByteBuf leaks.
+    GcFinalization.awaitFullGc();
+  }
+
+  @Test
+  public void handshakeStateOrderTest() {
+    try {
+      Handshakers handshakers = newHandshakers();
+      TsiHandshaker clientHandshaker = handshakers.getClient();
+      TsiHandshaker serverHandshaker = handshakers.getServer();
+
+      byte[] transportBufferBytes = new byte[TsiTest.getDefaultTransportBufferSize()];
+      ByteBuffer transportBuffer = ByteBuffer.wrap(transportBufferBytes);
+      transportBuffer.limit(0); // Start off with an empty buffer
+
+      transportBuffer.clear();
+      clientHandshaker.getBytesToSendToPeer(transportBuffer);
+      transportBuffer.flip();
+      assertEquals(
+          FakeTsiHandshaker.State.CLIENT_INIT.toString().trim(),
+          new String(transportBufferBytes, 4, transportBuffer.remaining(), UTF_8).trim());
+
+      serverHandshaker.processBytesFromPeer(transportBuffer);
+      assertFalse(transportBuffer.hasRemaining());
+
+      // client shouldn't offer any more bytes
+      transportBuffer.clear();
+      clientHandshaker.getBytesToSendToPeer(transportBuffer);
+      transportBuffer.flip();
+      assertFalse(transportBuffer.hasRemaining());
+
+      transportBuffer.clear();
+      serverHandshaker.getBytesToSendToPeer(transportBuffer);
+      transportBuffer.flip();
+      assertEquals(
+          FakeTsiHandshaker.State.SERVER_INIT.toString().trim(),
+          new String(transportBufferBytes, 4, transportBuffer.remaining(), UTF_8).trim());
+
+      clientHandshaker.processBytesFromPeer(transportBuffer);
+      assertFalse(transportBuffer.hasRemaining());
+
+      // server shouldn't offer any more bytes
+      transportBuffer.clear();
+      serverHandshaker.getBytesToSendToPeer(transportBuffer);
+      transportBuffer.flip();
+      assertFalse(transportBuffer.hasRemaining());
+
+      transportBuffer.clear();
+      clientHandshaker.getBytesToSendToPeer(transportBuffer);
+      transportBuffer.flip();
+      assertEquals(
+          FakeTsiHandshaker.State.CLIENT_FINISHED.toString().trim(),
+          new String(transportBufferBytes, 4, transportBuffer.remaining(), UTF_8).trim());
+
+      serverHandshaker.processBytesFromPeer(transportBuffer);
+      assertFalse(transportBuffer.hasRemaining());
+
+      // client shouldn't offer any more bytes
+      transportBuffer.clear();
+      clientHandshaker.getBytesToSendToPeer(transportBuffer);
+      transportBuffer.flip();
+      assertFalse(transportBuffer.hasRemaining());
+
+      transportBuffer.clear();
+      serverHandshaker.getBytesToSendToPeer(transportBuffer);
+      transportBuffer.flip();
+      assertEquals(
+          FakeTsiHandshaker.State.SERVER_FINISHED.toString().trim(),
+          new String(transportBufferBytes, 4, transportBuffer.remaining(), UTF_8).trim());
+
+      clientHandshaker.processBytesFromPeer(transportBuffer);
+      assertFalse(transportBuffer.hasRemaining());
+
+      // server shouldn't offer any more bytes
+      transportBuffer.clear();
+      serverHandshaker.getBytesToSendToPeer(transportBuffer);
+      transportBuffer.flip();
+      assertFalse(transportBuffer.hasRemaining());
+    } catch (GeneralSecurityException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  @Test
+  public void handshake() throws GeneralSecurityException {
+    TsiTest.handshakeTest(newHandshakers());
+  }
+
+  @Test
+  public void handshakeSmallBuffer() throws GeneralSecurityException {
+    TsiTest.handshakeSmallBufferTest(newHandshakers());
+  }
+
+  @Test
+  public void pingPong() throws GeneralSecurityException {
+    TsiTest.pingPongTest(newHandshakers(), ref);
+  }
+
+  @Test
+  public void pingPongExactFrameSize() throws GeneralSecurityException {
+    TsiTest.pingPongExactFrameSizeTest(newHandshakers(), ref);
+  }
+
+  @Test
+  public void pingPongSmallBuffer() throws GeneralSecurityException {
+    TsiTest.pingPongSmallBufferTest(newHandshakers(), ref);
+  }
+
+  @Test
+  public void pingPongSmallFrame() throws GeneralSecurityException {
+    TsiTest.pingPongSmallFrameTest(OVERHEAD, newHandshakers(), ref);
+  }
+
+  @Test
+  public void pingPongSmallFrameSmallBuffer() throws GeneralSecurityException {
+    TsiTest.pingPongSmallFrameSmallBufferTest(OVERHEAD, newHandshakers(), ref);
+  }
+
+  @Test
+  public void corruptedCounter() throws GeneralSecurityException {
+    TsiTest.corruptedCounterTest(newHandshakers(), ref);
+  }
+
+  @Test
+  public void corruptedCiphertext() throws GeneralSecurityException {
+    TsiTest.corruptedCiphertextTest(newHandshakers(), ref);
+  }
+
+  @Test
+  public void corruptedTag() throws GeneralSecurityException {
+    TsiTest.corruptedTagTest(newHandshakers(), ref);
+  }
+
+  @Test
+  public void reflectedCiphertext() throws GeneralSecurityException {
+    TsiTest.reflectedCiphertextTest(newHandshakers(), ref);
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiatorTest.java b/alts/src/test/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiatorTest.java
new file mode 100644
index 0000000..74e342e
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/GoogleDefaultProtocolNegotiatorTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.grpc.Attributes;
+import io.grpc.internal.GrpcAttributes;
+import io.grpc.netty.GrpcHttp2ConnectionHandler;
+import io.grpc.netty.ProtocolNegotiator;
+import io.grpc.netty.ProtocolNegotiator.Handler;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class GoogleDefaultProtocolNegotiatorTest {
+  private ProtocolNegotiator altsProtocolNegotiator;
+  private ProtocolNegotiator tlsProtocolNegotiator;
+  private GoogleDefaultProtocolNegotiator googleProtocolNegotiator;
+
+  @Before
+  public void setUp() {
+    altsProtocolNegotiator = mock(ProtocolNegotiator.class);
+    tlsProtocolNegotiator = mock(ProtocolNegotiator.class);
+    googleProtocolNegotiator =
+        new GoogleDefaultProtocolNegotiator(altsProtocolNegotiator, tlsProtocolNegotiator);
+  }
+
+  @Test
+  public void altsHandler() {
+    Attributes eagAttributes =
+        Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_PROVIDED_BACKEND, true).build();
+    GrpcHttp2ConnectionHandler mockHandler = mock(GrpcHttp2ConnectionHandler.class);
+    when(mockHandler.getEagAttributes()).thenReturn(eagAttributes);
+    Handler handler = googleProtocolNegotiator.newHandler(mockHandler);
+    verify(altsProtocolNegotiator, times(1)).newHandler(mockHandler);
+    verify(tlsProtocolNegotiator, never()).newHandler(mockHandler);
+  }
+
+  @Test
+  public void tlsHandler() {
+    Attributes eagAttributes = Attributes.EMPTY;
+    GrpcHttp2ConnectionHandler mockHandler = mock(GrpcHttp2ConnectionHandler.class);
+    when(mockHandler.getEagAttributes()).thenReturn(eagAttributes);
+    Handler handler = googleProtocolNegotiator.newHandler(mockHandler);
+    verify(altsProtocolNegotiator, never()).newHandler(mockHandler);
+    verify(tlsProtocolNegotiator, times(1)).newHandler(mockHandler);
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/MockAltsHandshakerResp.java b/alts/src/test/java/io/grpc/alts/internal/MockAltsHandshakerResp.java
new file mode 100644
index 0000000..2057466
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/MockAltsHandshakerResp.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.protobuf.ByteString;
+import io.grpc.Status;
+import io.grpc.alts.internal.Handshaker.HandshakerResp;
+import io.grpc.alts.internal.Handshaker.HandshakerResult;
+import io.grpc.alts.internal.Handshaker.HandshakerStatus;
+import io.grpc.alts.internal.Handshaker.Identity;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.security.SecureRandom;
+import java.util.Random;
+
+/** A class for mocking ALTS Handshaker Responses. */
+class MockAltsHandshakerResp {
+  private static final String TEST_ERROR_DETAILS = "handshake error";
+  private static final String TEST_APPLICATION_PROTOCOL = "grpc";
+  private static final String TEST_RECORD_PROTOCOL = "ALTSRP_GCM_AES128";
+  private static final String TEST_OUT_FRAME = "output frame";
+  private static final String TEST_LOCAL_ACCOUNT = "local@developer.gserviceaccount.com";
+  private static final String TEST_PEER_ACCOUNT = "peer@developer.gserviceaccount.com";
+  private static final byte[] TEST_KEY_DATA = initializeTestKeyData();
+  private static final int FRAME_HEADER_SIZE = 4;
+
+  static String getTestErrorDetails() {
+    return TEST_ERROR_DETAILS;
+  }
+
+  static String getTestPeerAccount() {
+    return TEST_PEER_ACCOUNT;
+  }
+
+  private static byte[] initializeTestKeyData() {
+    Random random = new SecureRandom();
+    byte[] randombytes = new byte[AltsChannelCrypter.getKeyLength()];
+    random.nextBytes(randombytes);
+    return randombytes;
+  }
+
+  static byte[] getTestKeyData() {
+    return TEST_KEY_DATA;
+  }
+
+  /** Returns a mock output frame. */
+  static ByteString getOutFrame() {
+    int frameSize = TEST_OUT_FRAME.length();
+    ByteBuffer buffer = ByteBuffer.allocate(FRAME_HEADER_SIZE + frameSize);
+    buffer.order(ByteOrder.LITTLE_ENDIAN);
+    buffer.putInt(frameSize);
+    buffer.put(TEST_OUT_FRAME.getBytes(UTF_8));
+    buffer.flip();
+    return ByteString.copyFrom(buffer);
+  }
+
+  /** Returns a mock error handshaker response. */
+  static HandshakerResp getErrorResponse() {
+    HandshakerResp.Builder resp = HandshakerResp.newBuilder();
+    resp.setStatus(
+        HandshakerStatus.newBuilder()
+            .setCode(Status.Code.UNKNOWN.value())
+            .setDetails(TEST_ERROR_DETAILS)
+            .build());
+    return resp.build();
+  }
+
+  /** Returns a mock normal handshaker response. */
+  static HandshakerResp getOkResponse(int bytesConsumed) {
+    HandshakerResp.Builder resp = HandshakerResp.newBuilder();
+    resp.setOutFrames(getOutFrame());
+    resp.setBytesConsumed(bytesConsumed);
+    resp.setStatus(HandshakerStatus.newBuilder().setCode(Status.Code.OK.value()).build());
+    return resp.build();
+  }
+
+  /** Returns a mock normal handshaker response. */
+  static HandshakerResp getEmptyOutFrameResponse(int bytesConsumed) {
+    HandshakerResp.Builder resp = HandshakerResp.newBuilder();
+    resp.setBytesConsumed(bytesConsumed);
+    resp.setStatus(HandshakerStatus.newBuilder().setCode(Status.Code.OK.value()).build());
+    return resp.build();
+  }
+
+  /** Returns a mock final handshaker response with handshake result. */
+  static HandshakerResp getFinishedResponse(int bytesConsumed) {
+    HandshakerResp.Builder resp = HandshakerResp.newBuilder();
+    HandshakerResult.Builder result =
+        HandshakerResult.newBuilder()
+            .setApplicationProtocol(TEST_APPLICATION_PROTOCOL)
+            .setRecordProtocol(TEST_RECORD_PROTOCOL)
+            .setPeerIdentity(Identity.newBuilder().setServiceAccount(TEST_PEER_ACCOUNT).build())
+            .setLocalIdentity(Identity.newBuilder().setServiceAccount(TEST_LOCAL_ACCOUNT).build())
+            .setKeyData(ByteString.copyFrom(TEST_KEY_DATA));
+    resp.setOutFrames(getOutFrame());
+    resp.setBytesConsumed(bytesConsumed);
+    resp.setStatus(HandshakerStatus.newBuilder().setCode(Status.Code.OK.value()).build());
+    resp.setResult(result.build());
+    return resp.build();
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/NettyTsiHandshakerTest.java b/alts/src/test/java/io/grpc/alts/internal/NettyTsiHandshakerTest.java
new file mode 100644
index 0000000..2ef0ad2
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/NettyTsiHandshakerTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.util.ReferenceCounted;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class NettyTsiHandshakerTest {
+  private final UnpooledByteBufAllocator alloc = UnpooledByteBufAllocator.DEFAULT;
+  private final List<ReferenceCounted> references = new ArrayList<>();
+
+  private final NettyTsiHandshaker clientHandshaker =
+      new NettyTsiHandshaker(FakeTsiHandshaker.newFakeHandshakerClient());
+  private final NettyTsiHandshaker serverHandshaker =
+      new NettyTsiHandshaker(FakeTsiHandshaker.newFakeHandshakerServer());
+
+  @After
+  public void teardown() {
+    for (ReferenceCounted reference : references) {
+      reference.release(reference.refCnt());
+    }
+  }
+
+  @Test
+  public void failsOnNullHandshaker() {
+    try {
+      new NettyTsiHandshaker(null);
+      fail("Exception expected");
+    } catch (NullPointerException ex) {
+      // Do nothing.
+    }
+  }
+
+  @Test
+  public void processPeerHandshakeShouldAcceptPartialFrames() throws GeneralSecurityException {
+    for (int i = 0; i < 1024; i++) {
+      ByteBuf clientData = ref(alloc.buffer(1));
+      clientHandshaker.getBytesToSendToPeer(clientData);
+      if (clientData.isReadable()) {
+        if (serverHandshaker.processBytesFromPeer(clientData)) {
+          // Done.
+          return;
+        }
+      }
+    }
+    fail("Failed to process the handshake frame.");
+  }
+
+  @Test
+  public void handshakeShouldSucceed() throws GeneralSecurityException {
+    doHandshake();
+  }
+
+  @Test
+  public void isInProgress() throws GeneralSecurityException {
+    assertTrue(clientHandshaker.isInProgress());
+    assertTrue(serverHandshaker.isInProgress());
+
+    doHandshake();
+
+    assertFalse(clientHandshaker.isInProgress());
+    assertFalse(serverHandshaker.isInProgress());
+  }
+
+  @Test
+  public void extractPeer_notNull() throws GeneralSecurityException {
+    doHandshake();
+
+    assertNotNull(serverHandshaker.extractPeer());
+    assertNotNull(clientHandshaker.extractPeer());
+  }
+
+  @Test
+  public void extractPeer_failsBeforeHandshake() throws GeneralSecurityException {
+    try {
+      clientHandshaker.extractPeer();
+      fail("Exception expected");
+    } catch (IllegalStateException ex) {
+      // Do nothing.
+    }
+  }
+
+  @Test
+  public void extractPeerObject_notNull() throws GeneralSecurityException {
+    doHandshake();
+
+    assertNotNull(serverHandshaker.extractPeerObject());
+    assertNotNull(clientHandshaker.extractPeerObject());
+  }
+
+  @Test
+  public void extractPeerObject_failsBeforeHandshake() throws GeneralSecurityException {
+    try {
+      clientHandshaker.extractPeerObject();
+      fail("Exception expected");
+    } catch (IllegalStateException ex) {
+      // Do nothing.
+    }
+  }
+
+  /**
+   * NettyTsiHandshaker just converts {@link ByteBuffer} to {@link ByteBuf}, so check that the other
+   * methods are otherwise the same.
+   */
+  @Test
+  public void handshakerMethodsMatch() {
+    List<String> expectedMethods = new ArrayList<>();
+    for (Method m : TsiHandshaker.class.getDeclaredMethods()) {
+      expectedMethods.add(m.getName());
+    }
+
+    List<String> actualMethods = new ArrayList<>();
+    for (Method m : NettyTsiHandshaker.class.getDeclaredMethods()) {
+      actualMethods.add(m.getName());
+    }
+
+    assertThat(actualMethods).containsAllIn(expectedMethods);
+  }
+
+  static void doHandshake(
+      NettyTsiHandshaker clientHandshaker,
+      NettyTsiHandshaker serverHandshaker,
+      ByteBufAllocator alloc,
+      Function<ByteBuf, ByteBuf> ref)
+      throws GeneralSecurityException {
+    // Get the server response handshake frames.
+    for (int i = 0; i < 10; i++) {
+      if (!(clientHandshaker.isInProgress() || serverHandshaker.isInProgress())) {
+        return;
+      }
+
+      ByteBuf clientData = ref.apply(alloc.buffer());
+      clientHandshaker.getBytesToSendToPeer(clientData);
+      if (clientData.isReadable()) {
+        serverHandshaker.processBytesFromPeer(clientData);
+      }
+
+      ByteBuf serverData = ref.apply(alloc.buffer());
+      serverHandshaker.getBytesToSendToPeer(serverData);
+      if (serverData.isReadable()) {
+        clientHandshaker.processBytesFromPeer(serverData);
+      }
+    }
+
+    throw new AssertionError("Failed to complete the handshake.");
+  }
+
+  private void doHandshake() throws GeneralSecurityException {
+    doHandshake(
+        clientHandshaker,
+        serverHandshaker,
+        alloc,
+        new Function<ByteBuf, ByteBuf>() {
+          @Override
+          public ByteBuf apply(ByteBuf buf) {
+            return ref(buf);
+          }
+        });
+  }
+
+  private ByteBuf ref(ByteBuf buf) {
+    if (buf != null) {
+      references.add(buf);
+    }
+    return buf;
+  }
+
+  /** A mirror of java.util.function.Function without the Java 8 dependency. */
+  private interface Function<T, R> {
+    R apply(T t);
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/RpcProtocolVersionsUtilTest.java b/alts/src/test/java/io/grpc/alts/internal/RpcProtocolVersionsUtilTest.java
new file mode 100644
index 0000000..dc3c7eb
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/RpcProtocolVersionsUtilTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.alts.internal.RpcProtocolVersionsUtil.RpcVersionsCheckResult;
+import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions;
+import io.grpc.alts.internal.TransportSecurityCommon.RpcProtocolVersions.Version;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link RpcProtocolVersionsUtil}. */
+@RunWith(JUnit4.class)
+public final class RpcProtocolVersionsUtilTest {
+
+  @Test
+  public void compareVersions() throws Exception {
+    assertTrue(
+        RpcProtocolVersionsUtil.isGreaterThanOrEqualTo(
+            Version.newBuilder().setMajor(3).setMinor(2).build(),
+            Version.newBuilder().setMajor(2).setMinor(1).build()));
+    assertTrue(
+        RpcProtocolVersionsUtil.isGreaterThanOrEqualTo(
+            Version.newBuilder().setMajor(3).setMinor(2).build(),
+            Version.newBuilder().setMajor(2).setMinor(1).build()));
+    assertTrue(
+        RpcProtocolVersionsUtil.isGreaterThanOrEqualTo(
+            Version.newBuilder().setMajor(3).setMinor(2).build(),
+            Version.newBuilder().setMajor(3).setMinor(2).build()));
+    assertFalse(
+        RpcProtocolVersionsUtil.isGreaterThanOrEqualTo(
+            Version.newBuilder().setMajor(2).setMinor(3).build(),
+            Version.newBuilder().setMajor(3).setMinor(2).build()));
+    assertFalse(
+        RpcProtocolVersionsUtil.isGreaterThanOrEqualTo(
+            Version.newBuilder().setMajor(3).setMinor(1).build(),
+            Version.newBuilder().setMajor(3).setMinor(2).build()));
+  }
+
+  @Test
+  public void checkRpcVersions() throws Exception {
+    // local.max > peer.max and local.min > peer.min
+    RpcVersionsCheckResult checkResult =
+        RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(3).setMinor(2).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .build(),
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(1).setMinor(2).build())
+                .build());
+    assertTrue(checkResult.getResult());
+    assertEquals(
+        Version.newBuilder().setMajor(2).setMinor(1).build(),
+        checkResult.getHighestCommonVersion());
+    // local.max > peer.max and local.min < peer.min
+    checkResult =
+        RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(3).setMinor(2).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(1).setMinor(2).build())
+                .build(),
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .build());
+    assertTrue(checkResult.getResult());
+    assertEquals(
+        Version.newBuilder().setMajor(2).setMinor(1).build(),
+        checkResult.getHighestCommonVersion());
+    // local.max > peer.max and local.min = peer.min
+    checkResult =
+        RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(3).setMinor(2).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .build(),
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .build());
+    assertTrue(checkResult.getResult());
+    assertEquals(
+        Version.newBuilder().setMajor(2).setMinor(1).build(),
+        checkResult.getHighestCommonVersion());
+    // local.max < peer.max and local.min > peer.min
+    checkResult =
+        RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .build(),
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(3).setMinor(2).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(1).setMinor(2).build())
+                .build());
+    assertTrue(checkResult.getResult());
+    assertEquals(
+        Version.newBuilder().setMajor(2).setMinor(1).build(),
+        checkResult.getHighestCommonVersion());
+    // local.max = peer.max and local.min > peer.min
+    checkResult =
+        RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .build(),
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(1).setMinor(2).build())
+                .build());
+    assertTrue(checkResult.getResult());
+    assertEquals(
+        Version.newBuilder().setMajor(2).setMinor(1).build(),
+        checkResult.getHighestCommonVersion());
+    // local.max < peer.max and local.min < peer.min
+    checkResult =
+        RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(1).setMinor(2).build())
+                .build(),
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(3).setMinor(2).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .build());
+    assertTrue(checkResult.getResult());
+    assertEquals(
+        Version.newBuilder().setMajor(2).setMinor(1).build(),
+        checkResult.getHighestCommonVersion());
+    // local.max < peer.max and local.min = peer.min
+    checkResult =
+        RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(1).setMinor(2).build())
+                .build(),
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(3).setMinor(2).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(1).setMinor(2).build())
+                .build());
+    assertTrue(checkResult.getResult());
+    assertEquals(
+        Version.newBuilder().setMajor(2).setMinor(1).build(),
+        checkResult.getHighestCommonVersion());
+    // local.max = peer.max and local.min < peer.min
+    checkResult =
+        RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(1).setMinor(2).build())
+                .build(),
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(1).setMinor(2).build())
+                .build());
+    assertTrue(checkResult.getResult());
+    assertEquals(
+        Version.newBuilder().setMajor(2).setMinor(1).build(),
+        checkResult.getHighestCommonVersion());
+    // all equal
+    checkResult =
+        RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .build(),
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .build());
+    assertTrue(checkResult.getResult());
+    assertEquals(
+        Version.newBuilder().setMajor(2).setMinor(1).build(),
+        checkResult.getHighestCommonVersion());
+    // max is smaller than min
+    checkResult =
+        RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(1).setMinor(2).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .build(),
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(1).setMinor(2).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .build());
+    assertFalse(checkResult.getResult());
+    assertEquals(null, checkResult.getHighestCommonVersion());
+    // no overlap, local > peer
+    checkResult =
+        RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(6).setMinor(5).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(4).setMinor(3).build())
+                .build(),
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(0).setMinor(0).build())
+                .build());
+    assertFalse(checkResult.getResult());
+    assertEquals(null, checkResult.getHighestCommonVersion());
+    // no overlap, local < peer
+    checkResult =
+        RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(1).setMinor(0).build())
+                .build(),
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(6).setMinor(5).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(4).setMinor(3).build())
+                .build());
+    assertFalse(checkResult.getResult());
+    assertEquals(null, checkResult.getHighestCommonVersion());
+    // no overlap, max < min
+    checkResult =
+        RpcProtocolVersionsUtil.checkRpcProtocolVersions(
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(4).setMinor(3).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(6).setMinor(5).build())
+                .build(),
+            RpcProtocolVersions.newBuilder()
+                .setMaxRpcVersion(Version.newBuilder().setMajor(1).setMinor(0).build())
+                .setMinRpcVersion(Version.newBuilder().setMajor(2).setMinor(1).build())
+                .build());
+    assertFalse(checkResult.getResult());
+    assertEquals(null, checkResult.getHighestCommonVersion());
+  }
+}
diff --git a/alts/src/test/java/io/grpc/alts/internal/TsiTest.java b/alts/src/test/java/io/grpc/alts/internal/TsiTest.java
new file mode 100644
index 0000000..5c9cf26
--- /dev/null
+++ b/alts/src/test/java/io/grpc/alts/internal/TsiTest.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.alts.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.alts.internal.ByteBufTestUtils.getDirectBuffer;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.fail;
+
+import io.grpc.alts.internal.ByteBufTestUtils.RegisterRef;
+import io.grpc.alts.internal.TsiFrameProtector.Consumer;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import javax.crypto.AEADBadTagException;
+
+/** Utility class that provides tests for implementations of @{link TsiHandshaker}. */
+public final class TsiTest {
+  private static final String DECRYPTION_FAILURE_RE = "Tag mismatch!";
+
+  private TsiTest() {}
+
+  /** A @{code TsiHandshaker} pair for running tests. */
+  public static class Handshakers {
+    private final TsiHandshaker client;
+    private final TsiHandshaker server;
+
+    public Handshakers(TsiHandshaker client, TsiHandshaker server) {
+      this.client = client;
+      this.server = server;
+    }
+
+    public TsiHandshaker getClient() {
+      return client;
+    }
+
+    public TsiHandshaker getServer() {
+      return server;
+    }
+  }
+
+  private static final int DEFAULT_TRANSPORT_BUFFER_SIZE = 2048;
+
+  private static final UnpooledByteBufAllocator alloc = UnpooledByteBufAllocator.DEFAULT;
+
+  private static final String EXAMPLE_MESSAGE1 = "hello world";
+  private static final String EXAMPLE_MESSAGE2 = "oysteroystersoysterseateateat";
+
+  private static final int EXAMPLE_MESSAGE1_LEN = EXAMPLE_MESSAGE1.getBytes(UTF_8).length;
+  private static final int EXAMPLE_MESSAGE2_LEN = EXAMPLE_MESSAGE2.getBytes(UTF_8).length;
+
+  static int getDefaultTransportBufferSize() {
+    return DEFAULT_TRANSPORT_BUFFER_SIZE;
+  }
+
+  /**
+   * Performs a handshake between the client handshaker and server handshaker using a transport of
+   * length transportBufferSize.
+   */
+  static void performHandshake(int transportBufferSize, Handshakers handshakers)
+      throws GeneralSecurityException {
+    TsiHandshaker clientHandshaker = handshakers.getClient();
+    TsiHandshaker serverHandshaker = handshakers.getServer();
+
+    byte[] transportBufferBytes = new byte[transportBufferSize];
+    ByteBuffer transportBuffer = ByteBuffer.wrap(transportBufferBytes);
+    transportBuffer.limit(0); // Start off with an empty buffer
+
+    while (clientHandshaker.isInProgress() || serverHandshaker.isInProgress()) {
+      for (TsiHandshaker handshaker : new TsiHandshaker[] {clientHandshaker, serverHandshaker}) {
+        if (handshaker.isInProgress()) {
+          // Process any bytes on the wire.
+          if (transportBuffer.hasRemaining()) {
+            handshaker.processBytesFromPeer(transportBuffer);
+          }
+          // Put new bytes on the wire, if needed.
+          if (handshaker.isInProgress()) {
+            transportBuffer.clear();
+            handshaker.getBytesToSendToPeer(transportBuffer);
+            transportBuffer.flip();
+          }
+        }
+      }
+    }
+    clientHandshaker.extractPeer();
+    serverHandshaker.extractPeer();
+  }
+
+  public static void handshakeTest(Handshakers handshakers) throws GeneralSecurityException {
+    performHandshake(DEFAULT_TRANSPORT_BUFFER_SIZE, handshakers);
+  }
+
+  public static void handshakeSmallBufferTest(Handshakers handshakers)
+      throws GeneralSecurityException {
+    performHandshake(9, handshakers);
+  }
+
+  /** Sends a message between the sender and receiver. */
+  private static void sendMessage(
+      TsiFrameProtector sender,
+      TsiFrameProtector receiver,
+      int recvFragmentSize,
+      String message,
+      RegisterRef ref)
+      throws GeneralSecurityException {
+
+    ByteBuf plaintextBuffer = Unpooled.wrappedBuffer(message.getBytes(UTF_8));
+    final List<ByteBuf> protectOut = new ArrayList<>();
+    List<Object> unprotectOut = new ArrayList<>();
+
+    sender.protectFlush(
+        Collections.singletonList(plaintextBuffer),
+        new Consumer<ByteBuf>() {
+          @Override
+          public void accept(ByteBuf buf) {
+            protectOut.add(buf);
+          }
+        },
+        alloc);
+    assertThat(protectOut.size()).isEqualTo(1);
+
+    ByteBuf protect = ref.register(protectOut.get(0));
+    while (protect.isReadable()) {
+      ByteBuf buf = protect;
+      if (recvFragmentSize > 0) {
+        int size = Math.min(protect.readableBytes(), recvFragmentSize);
+        buf = protect.readSlice(size);
+      }
+      receiver.unprotect(buf, unprotectOut, alloc);
+    }
+    ByteBuf plaintextRecvd = getDirectBuffer(message.getBytes(UTF_8).length, ref);
+    for (Object unprotect : unprotectOut) {
+      ByteBuf unprotectBuf = ref.register((ByteBuf) unprotect);
+      plaintextRecvd.writeBytes(unprotectBuf);
+    }
+    assertThat(plaintextRecvd).isEqualTo(Unpooled.wrappedBuffer(message.getBytes(UTF_8)));
+  }
+
+  /** Ping pong test. */
+  public static void pingPongTest(Handshakers handshakers, RegisterRef ref)
+      throws GeneralSecurityException {
+    performHandshake(DEFAULT_TRANSPORT_BUFFER_SIZE, handshakers);
+
+    TsiFrameProtector clientProtector = handshakers.getClient().createFrameProtector(alloc);
+    TsiFrameProtector serverProtector = handshakers.getServer().createFrameProtector(alloc);
+
+    sendMessage(clientProtector, serverProtector, -1, EXAMPLE_MESSAGE1, ref);
+    sendMessage(serverProtector, clientProtector, -1, EXAMPLE_MESSAGE2, ref);
+
+    clientProtector.destroy();
+    serverProtector.destroy();
+  }
+
+  /** Ping pong test with exact frame size. */
+  public static void pingPongExactFrameSizeTest(Handshakers handshakers, RegisterRef ref)
+      throws GeneralSecurityException {
+    performHandshake(DEFAULT_TRANSPORT_BUFFER_SIZE, handshakers);
+
+    int frameSize =
+        EXAMPLE_MESSAGE1.getBytes(UTF_8).length
+            + AltsTsiFrameProtector.getHeaderBytes()
+            + FakeChannelCrypter.getTagBytes();
+
+    TsiFrameProtector clientProtector =
+        handshakers.getClient().createFrameProtector(frameSize, alloc);
+    TsiFrameProtector serverProtector =
+        handshakers.getServer().createFrameProtector(frameSize, alloc);
+
+    sendMessage(clientProtector, serverProtector, -1, EXAMPLE_MESSAGE1, ref);
+    sendMessage(serverProtector, clientProtector, -1, EXAMPLE_MESSAGE1, ref);
+
+    clientProtector.destroy();
+    serverProtector.destroy();
+  }
+
+  /** Ping pong test with small buffer size. */
+  public static void pingPongSmallBufferTest(Handshakers handshakers, RegisterRef ref)
+      throws GeneralSecurityException {
+    performHandshake(DEFAULT_TRANSPORT_BUFFER_SIZE, handshakers);
+
+    TsiFrameProtector clientProtector = handshakers.getClient().createFrameProtector(alloc);
+    TsiFrameProtector serverProtector = handshakers.getServer().createFrameProtector(alloc);
+
+    sendMessage(clientProtector, serverProtector, 1, EXAMPLE_MESSAGE1, ref);
+    sendMessage(serverProtector, clientProtector, 1, EXAMPLE_MESSAGE2, ref);
+
+    clientProtector.destroy();
+    serverProtector.destroy();
+  }
+
+  /** Ping pong test with small frame size. */
+  public static void pingPongSmallFrameTest(
+      int frameProtectorOverhead, Handshakers handshakers, RegisterRef ref)
+      throws GeneralSecurityException {
+    performHandshake(DEFAULT_TRANSPORT_BUFFER_SIZE, handshakers);
+
+    // We send messages using small non-aligned buffers. We use 3 and 5, small primes.
+    TsiFrameProtector clientProtector =
+        handshakers.getClient().createFrameProtector(frameProtectorOverhead + 3, alloc);
+    TsiFrameProtector serverProtector =
+        handshakers.getServer().createFrameProtector(frameProtectorOverhead + 5, alloc);
+
+    sendMessage(clientProtector, serverProtector, EXAMPLE_MESSAGE1_LEN, EXAMPLE_MESSAGE1, ref);
+    sendMessage(serverProtector, clientProtector, EXAMPLE_MESSAGE2_LEN, EXAMPLE_MESSAGE2, ref);
+
+    clientProtector.destroy();
+    serverProtector.destroy();
+  }
+
+  /** Ping pong test with small frame and small buffer. */
+  public static void pingPongSmallFrameSmallBufferTest(
+      int frameProtectorOverhead, Handshakers handshakers, RegisterRef ref)
+      throws GeneralSecurityException {
+    performHandshake(DEFAULT_TRANSPORT_BUFFER_SIZE, handshakers);
+
+    // We send messages using small non-aligned buffers. We use 3 and 5, small primes.
+    TsiFrameProtector clientProtector =
+        handshakers.getClient().createFrameProtector(frameProtectorOverhead + 3, alloc);
+    TsiFrameProtector serverProtector =
+        handshakers.getServer().createFrameProtector(frameProtectorOverhead + 5, alloc);
+
+    sendMessage(clientProtector, serverProtector, EXAMPLE_MESSAGE1_LEN, EXAMPLE_MESSAGE1, ref);
+    sendMessage(serverProtector, clientProtector, EXAMPLE_MESSAGE2_LEN, EXAMPLE_MESSAGE2, ref);
+
+    sendMessage(clientProtector, serverProtector, EXAMPLE_MESSAGE1_LEN, EXAMPLE_MESSAGE1, ref);
+    sendMessage(serverProtector, clientProtector, EXAMPLE_MESSAGE2_LEN, EXAMPLE_MESSAGE2, ref);
+
+    clientProtector.destroy();
+    serverProtector.destroy();
+  }
+
+  /** Test corrupted counter. */
+  public static void corruptedCounterTest(Handshakers handshakers, RegisterRef ref)
+      throws GeneralSecurityException {
+    performHandshake(DEFAULT_TRANSPORT_BUFFER_SIZE, handshakers);
+
+    TsiFrameProtector sender = handshakers.getClient().createFrameProtector(alloc);
+    TsiFrameProtector receiver = handshakers.getServer().createFrameProtector(alloc);
+
+    String message = "hello world";
+    ByteBuf plaintextBuffer = Unpooled.wrappedBuffer(message.getBytes(UTF_8));
+    final List<ByteBuf> protectOut = new ArrayList<>();
+    List<Object> unprotectOut = new ArrayList<>();
+
+    sender.protectFlush(
+        Collections.singletonList(plaintextBuffer),
+        new Consumer<ByteBuf>() {
+          @Override
+          public void accept(ByteBuf buf) {
+            protectOut.add(buf);
+          }
+        },
+        alloc);
+    assertThat(protectOut.size()).isEqualTo(1);
+
+    ByteBuf protect = ref.register(protectOut.get(0));
+    // Unprotect once to increase receiver counter.
+    receiver.unprotect(protect.slice(), unprotectOut, alloc);
+    assertThat(unprotectOut.size()).isEqualTo(1);
+    ref.register((ByteBuf) unprotectOut.get(0));
+
+    try {
+      receiver.unprotect(protect, unprotectOut, alloc);
+      fail("Exception expected");
+    } catch (AEADBadTagException ex) {
+      assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_RE);
+    }
+
+    sender.destroy();
+    receiver.destroy();
+  }
+
+  /** Test corrupted ciphertext. */
+  public static void corruptedCiphertextTest(Handshakers handshakers, RegisterRef ref)
+      throws GeneralSecurityException {
+    performHandshake(DEFAULT_TRANSPORT_BUFFER_SIZE, handshakers);
+
+    TsiFrameProtector sender = handshakers.getClient().createFrameProtector(alloc);
+    TsiFrameProtector receiver = handshakers.getServer().createFrameProtector(alloc);
+
+    String message = "hello world";
+    ByteBuf plaintextBuffer = Unpooled.wrappedBuffer(message.getBytes(UTF_8));
+    final List<ByteBuf> protectOut = new ArrayList<>();
+    List<Object> unprotectOut = new ArrayList<>();
+
+    sender.protectFlush(
+        Collections.singletonList(plaintextBuffer),
+        new Consumer<ByteBuf>() {
+          @Override
+          public void accept(ByteBuf buf) {
+            protectOut.add(buf);
+          }
+        },
+        alloc);
+    assertThat(protectOut.size()).isEqualTo(1);
+
+    ByteBuf protect = ref.register(protectOut.get(0));
+    int ciphertextIdx = protect.writerIndex() - FakeChannelCrypter.getTagBytes() - 2;
+    protect.setByte(ciphertextIdx, protect.getByte(ciphertextIdx) + 1);
+
+    try {
+      receiver.unprotect(protect, unprotectOut, alloc);
+      fail("Exception expected");
+    } catch (AEADBadTagException ex) {
+      assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_RE);
+    }
+
+    sender.destroy();
+    receiver.destroy();
+  }
+
+  /** Test corrupted tag. */
+  public static void corruptedTagTest(Handshakers handshakers, RegisterRef ref)
+      throws GeneralSecurityException {
+    performHandshake(DEFAULT_TRANSPORT_BUFFER_SIZE, handshakers);
+
+    TsiFrameProtector sender = handshakers.getClient().createFrameProtector(alloc);
+    TsiFrameProtector receiver = handshakers.getServer().createFrameProtector(alloc);
+
+    String message = "hello world";
+    ByteBuf plaintextBuffer = Unpooled.wrappedBuffer(message.getBytes(UTF_8));
+    final List<ByteBuf> protectOut = new ArrayList<>();
+    List<Object> unprotectOut = new ArrayList<>();
+
+    sender.protectFlush(
+        Collections.singletonList(plaintextBuffer),
+        new Consumer<ByteBuf>() {
+          @Override
+          public void accept(ByteBuf buf) {
+            protectOut.add(buf);
+          }
+        },
+        alloc);
+    assertThat(protectOut.size()).isEqualTo(1);
+
+    ByteBuf protect = ref.register(protectOut.get(0));
+    int tagIdx = protect.writerIndex() - 1;
+    protect.setByte(tagIdx, protect.getByte(tagIdx) + 1);
+
+    try {
+      receiver.unprotect(protect, unprotectOut, alloc);
+      fail("Exception expected");
+    } catch (AEADBadTagException ex) {
+      assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_RE);
+    }
+
+    sender.destroy();
+    receiver.destroy();
+  }
+
+  /** Test reflected ciphertext. */
+  public static void reflectedCiphertextTest(Handshakers handshakers, RegisterRef ref)
+      throws GeneralSecurityException {
+    performHandshake(DEFAULT_TRANSPORT_BUFFER_SIZE, handshakers);
+
+    TsiFrameProtector sender = handshakers.getClient().createFrameProtector(alloc);
+    TsiFrameProtector receiver = handshakers.getServer().createFrameProtector(alloc);
+
+    String message = "hello world";
+    ByteBuf plaintextBuffer = Unpooled.wrappedBuffer(message.getBytes(UTF_8));
+    final List<ByteBuf> protectOut = new ArrayList<>();
+    List<Object> unprotectOut = new ArrayList<>();
+
+    sender.protectFlush(
+        Collections.singletonList(plaintextBuffer),
+        new Consumer<ByteBuf>() {
+          @Override
+          public void accept(ByteBuf buf) {
+            protectOut.add(buf);
+          }
+        },
+        alloc);
+    assertThat(protectOut.size()).isEqualTo(1);
+
+    ByteBuf protect = ref.register(protectOut.get(0));
+    try {
+      sender.unprotect(protect.slice(), unprotectOut, alloc);
+      fail("Exception expected");
+    } catch (AEADBadTagException ex) {
+      assertThat(ex).hasMessageThat().contains(DECRYPTION_FAILURE_RE);
+    }
+
+    sender.destroy();
+    receiver.destroy();
+  }
+}
diff --git a/android-interop-testing/README.md b/android-interop-testing/README.md
new file mode 100644
index 0000000..c1abcee
--- /dev/null
+++ b/android-interop-testing/README.md
@@ -0,0 +1,53 @@
+gRPC Android test App
+=======================
+
+Implements gRPC integration tests in an Android App.
+
+TODO(madongfly) integrate this App into the gRPC-Java build system.
+
+In order to build this app, you need a local.properties file under this directory which specifies
+the location of your android sdk:
+```
+sdk.dir=/somepath/somepath/sdk
+```
+
+Connect your Android device or start the emulator:
+```
+$ ./start-emulator.sh <AVD name> & ./wait-for-emulator.sh
+```
+
+Start test server
+-----------------
+
+Start the test server by:
+```
+$ ../run-test-server.sh
+```
+
+
+Manually test
+-------------
+
+Install the App by:
+```
+$ ../gradlew installDebug
+```
+Then manually test it with the UI.
+
+
+Instrumentation tests
+----------------
+
+Instrumentation tests must be run on a connected device or emulator. Run with the
+following gradle command:
+
+```
+$ ../gradlew connectedAndroidTest \
+    -Pandroid.testInstrumentationRunnerArguments.server_host=10.0.2.2 \
+    -Pandroid.testInstrumentationRunnerArguments.server_port=8080 \
+    -Pandroid.testInstrumentationRunnerArguments.use_tls=true \
+    -Pandroid.testInstrumentationRunnerArguments.server_host_override=foo.test.google.fr \
+    -Pandroid.testInstrumentationRunnerArguments.use_test_ca=true \
+    -Pandroid.testInstrumentationRunnerArguments.test_case=all
+```
+
diff --git a/android-interop-testing/app/build.gradle b/android-interop-testing/app/build.gradle
new file mode 100644
index 0000000..37238ba
--- /dev/null
+++ b/android-interop-testing/app/build.gradle
@@ -0,0 +1,89 @@
+apply plugin: 'com.android.application'
+apply plugin: 'com.google.protobuf'
+
+android {
+    sourceSets {
+        main {
+            java {
+                srcDirs += "${projectDir}/../../interop-testing/src/main/java/"
+                setIncludes(["io/grpc/android/integrationtest/**",
+                             "io/grpc/testing/integration/AbstractInteropTest.java",
+                             "io/grpc/testing/integration/TestServiceImpl.java",
+                             "io/grpc/testing/integration/Util.java"])
+            }
+            proto {
+                srcDirs += "${projectDir}/../../interop-testing/src/main/proto/"
+            }
+        }
+    }
+    compileSdkVersion 26
+
+    defaultConfig {
+        applicationId "io.grpc.android.integrationtest"
+        // API level 14+ is required for TLS since Google Play Services v10.2
+        minSdkVersion 14
+        targetSdkVersion 26
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        multiDexEnabled true
+    }
+    buildTypes {
+        debug { minifyEnabled false }
+        release {
+            minifyEnabled true
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    lintOptions { disable 'InvalidPackage', 'HardcodedText' }
+}
+
+protobuf {
+    protoc { artifact = 'com.google.protobuf:protoc:3.5.1-1' }
+    plugins {
+        javalite { artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0" }
+        grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+        }
+    }
+    generateProtoTasks {
+        all().each { task ->
+            task.plugins {
+                javalite {}
+                grpc {
+                    // Options added to --grpc_out
+                    option 'lite'
+                }
+            }
+        }
+    }
+}
+
+dependencies {
+    implementation 'com.android.support:appcompat-v7:26.1.0'
+    implementation 'com.android.support:multidex:1.0.3'
+    implementation 'com.android.support:support-annotations:27.1.1'
+    implementation 'com.google.android.gms:play-services-base:15.0.1'
+    implementation ('com.google.auth:google-auth-library-oauth2-http:0.9.0') {
+        exclude group: 'org.apache.httpcomponents', module: 'httpclient'
+    }
+    implementation 'com.google.truth:truth:0.36'
+    implementation 'javax.annotation:javax.annotation-api:1.2'
+    implementation 'junit:junit:4.12'
+
+    // You need to build grpc-java to obtain the grpc libraries below.
+    implementation 'io.grpc:grpc-auth:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    implementation 'io.grpc:grpc-okhttp:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    implementation 'io.grpc:grpc-protobuf-lite:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    implementation 'io.grpc:grpc-stub:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    implementation 'io.grpc:grpc-testing:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+
+    // workaround for https://github.com/google/protobuf/issues/1889
+    protobuf 'com.google.protobuf:protobuf-java:3.0.2'
+
+    androidTestImplementation 'androidx.test:rules:1.1.0-alpha1'
+    androidTestImplementation 'androidx.test:runner:1.1.0-alpha1'
+}
+
+gradle.projectsEvaluated {
+    tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:deprecation" }
+}
diff --git a/android-interop-testing/app/proguard-rules.pro b/android-interop-testing/app/proguard-rules.pro
new file mode 100644
index 0000000..8af8035
--- /dev/null
+++ b/android-interop-testing/app/proguard-rules.pro
@@ -0,0 +1,23 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in $ANDROID_HOME/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+-dontwarn android.test.**
+-dontwarn com.google.common.**
+-dontwarn javax.naming.**
+-dontwarn okio.**
+-dontwarn org.junit.**
+-dontwarn org.mockito.**
+-dontwarn sun.reflect.**
+# Ignores: can't find referenced class javax.lang.model.element.Modifier
+-dontwarn com.google.errorprone.annotations.**
+
+# Ignores: can't find referenced method from grpc-testing's compileOnly dependency on Truth
+-dontwarn io.grpc.testing.DeadlineSubject
diff --git a/android-interop-testing/app/src/androidTest/AndroidManifest.xml b/android-interop-testing/app/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..340300c
--- /dev/null
+++ b/android-interop-testing/app/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.grpc.android.integrationtest.test" >
+
+    <!-- API level 14+ is required for TLS since Google Play Services v10.2 -->
+    <uses-sdk
+        android:minSdkVersion="14"
+        android:targetSdkVersion="22"/>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="io.grpc.android.integrationtest" />
+
+    <application android:debuggable="true" >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+</manifest>
diff --git a/android-interop-testing/app/src/androidTest/java/io/grpc/android/integrationtest/InteropInstrumentationTest.java b/android-interop-testing/app/src/androidTest/java/io/grpc/android/integrationtest/InteropInstrumentationTest.java
new file mode 100644
index 0000000..0d88341
--- /dev/null
+++ b/android-interop-testing/app/src/androidTest/java/io/grpc/android/integrationtest/InteropInstrumentationTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.android.integrationtest;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
+import com.google.android.gms.common.GooglePlayServicesRepairableException;
+import com.google.android.gms.security.ProviderInstaller;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.ClientInterceptor;
+import io.grpc.android.integrationtest.InteropTask.Listener;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class InteropInstrumentationTest {
+  private static final int TIMEOUT_SECONDS = 60;
+  private static final String LOG_TAG = "GrpcInteropInstrumentationTest";
+
+  private String host;
+  private int port;
+  private boolean useTls;
+  private String serverHostOverride;
+  private boolean useTestCa;
+  private String testCase;
+
+  // Ensures Looper is initialized for tests running on API level 15. Otherwise instantiating an
+  // AsyncTask throws an exception.
+  @Rule
+  public ActivityTestRule<TesterActivity> activityRule =
+      new ActivityTestRule<TesterActivity>(TesterActivity.class);
+
+  @Before
+  public void setUp() throws Exception {
+    host = InstrumentationRegistry.getArguments().getString("server_host", "10.0.2.2");
+    port =
+        Integer.parseInt(InstrumentationRegistry.getArguments().getString("server_port", "8080"));
+    useTls =
+        Boolean.parseBoolean(InstrumentationRegistry.getArguments().getString("use_tls", "true"));
+    serverHostOverride = InstrumentationRegistry.getArguments().getString("server_host_override");
+    useTestCa =
+        Boolean.parseBoolean(
+            InstrumentationRegistry.getArguments().getString("use_test_ca", "false"));
+    testCase = InstrumentationRegistry.getArguments().getString("test_case", "all");
+
+    if (useTls) {
+      try {
+        ProviderInstaller.installIfNeeded(InstrumentationRegistry.getTargetContext());
+      } catch (GooglePlayServicesRepairableException e) {
+        // The provider is helpful, but it is possible to succeed without it.
+        // Hope that the system-provided libraries are new enough.
+        Log.i(LOG_TAG, "Failed installing security provider", e);
+      } catch (GooglePlayServicesNotAvailableException e) {
+        // The provider is helpful, but it is possible to succeed without it.
+        // Hope that the system-provided libraries are new enough.
+        Log.i(LOG_TAG, "Failed installing security provider", e);
+      }
+    }
+  }
+
+  @Test
+  public void interopTests() throws Exception {
+    if (testCase.equals("all")) {
+      runTest("empty_unary");
+      runTest("large_unary");
+      runTest("client_streaming");
+      runTest("server_streaming");
+      runTest("ping_pong");
+      runTest("empty_stream");
+      runTest("cancel_after_begin");
+      runTest("cancel_after_first_response");
+      runTest("full_duplex_call_should_succeed");
+      runTest("half_duplex_call_should_succeed");
+      runTest("server_streaming_should_be_flow_controlled");
+      runTest("very_large_request");
+      runTest("very_large_response");
+      runTest("deadline_not_exceeded");
+      runTest("deadline_exceeded");
+      runTest("deadline_exceeded_server_streaming");
+      runTest("unimplemented_method");
+      runTest("timeout_on_sleeping_server");
+      runTest("graceful_shutdown");
+    } else {
+      runTest(testCase);
+    }
+  }
+
+  private void runTest(String testCase) throws Exception {
+    final SettableFuture<String> resultFuture = SettableFuture.create();
+    InteropTask.Listener listener =
+        new Listener() {
+          @Override
+          public void onComplete(String result) {
+            resultFuture.set(result);
+          }
+        };
+    InputStream testCa;
+    if (useTestCa) {
+      testCa = InstrumentationRegistry.getTargetContext().getResources().openRawResource(R.raw.ca);
+    } else {
+      testCa = null;
+    }
+    new InteropTask(
+            listener,
+            TesterOkHttpChannelBuilder.build(host, port, serverHostOverride, useTls, testCa),
+            new ArrayList<>(),
+            testCase)
+        .execute();
+    String result = resultFuture.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+    assertEquals(testCase + " failed", InteropTask.SUCCESS_MESSAGE, result);
+  }
+}
diff --git a/android-interop-testing/app/src/main/AndroidManifest.xml b/android-interop-testing/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..cb9f81e
--- /dev/null
+++ b/android-interop-testing/app/src/main/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.grpc.android.integrationtest" >
+
+    <!-- API level 14+ is required for TLS since Google Play Services v10.2 -->
+    <uses-sdk
+        android:minSdkVersion="14"
+        android:targetSdkVersion="22"/>
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/Base.V7.Theme.AppCompat.Light"
+        android:name="android.support.multidex.MultiDexApplication" >
+        <activity
+            android:name=".TesterActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/android-interop-testing/app/src/main/java/io/grpc/android/integrationtest/InteropTask.java b/android-interop-testing/app/src/main/java/io/grpc/android/integrationtest/InteropTask.java
new file mode 100644
index 0000000..d1d181d
--- /dev/null
+++ b/android-interop-testing/app/src/main/java/io/grpc/android/integrationtest/InteropTask.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.android.integrationtest;
+
+import android.os.AsyncTask;
+import android.util.Log;
+import io.grpc.ClientInterceptor;
+import io.grpc.ManagedChannel;
+import io.grpc.testing.integration.AbstractInteropTest;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.ref.WeakReference;
+import java.util.List;
+import org.junit.AssumptionViolatedException;
+
+/** AsyncTask for interop test cases. */
+final class InteropTask extends AsyncTask<Void, Void, String> {
+  private static final String LOG_TAG = "GrpcInteropTask";
+
+  interface Listener {
+    void onComplete(String result);
+  }
+
+  static final String SUCCESS_MESSAGE = "Success!";
+
+  private final WeakReference<Listener> listenerReference;
+  private final String testCase;
+  private final Tester tester;
+
+  InteropTask(
+      Listener listener,
+      ManagedChannel channel,
+      List<ClientInterceptor> interceptors,
+      String testCase) {
+    this.listenerReference = new WeakReference<Listener>(listener);
+    this.testCase = testCase;
+    this.tester = new Tester(channel, interceptors);
+  }
+
+  @Override
+  protected void onPreExecute() {
+    tester.setUp();
+  }
+
+  @Override
+  protected String doInBackground(Void... ignored) {
+    try {
+      runTest(testCase);
+      return SUCCESS_MESSAGE;
+    } catch (Throwable t) {
+      // Print the stack trace to logcat.
+      t.printStackTrace();
+      // Then print to the error message.
+      StringWriter sw = new StringWriter();
+      t.printStackTrace(new PrintWriter(sw));
+      return "Failed... : " + t.getMessage() + "\n" + sw;
+    } finally {
+      try {
+        tester.tearDown();
+      } catch (RuntimeException ex) {
+        throw ex;
+      } catch (Exception ex) {
+        throw new RuntimeException(ex);
+      }
+    }
+  }
+
+  private void runTest(String testCase) throws Exception {
+    Log.i(LOG_TAG, "Running test case: " + testCase);
+    if ("empty_unary".equals(testCase)) {
+      tester.emptyUnary();
+    } else if ("large_unary".equals(testCase)) {
+      try {
+        tester.largeUnary();
+      } catch (AssumptionViolatedException e) {
+        // This test case requires more memory than most Android devices have available
+        Log.w(LOG_TAG, "Skipping " + testCase + " due to assumption violation", e);
+      }
+    } else if ("client_streaming".equals(testCase)) {
+      tester.clientStreaming();
+    } else if ("server_streaming".equals(testCase)) {
+      tester.serverStreaming();
+    } else if ("ping_pong".equals(testCase)) {
+      tester.pingPong();
+    } else if ("empty_stream".equals(testCase)) {
+      tester.emptyStream();
+    } else if ("cancel_after_begin".equals(testCase)) {
+      tester.cancelAfterBegin();
+    } else if ("cancel_after_first_response".equals(testCase)) {
+      tester.cancelAfterFirstResponse();
+    } else if ("full_duplex_call_should_succeed".equals(testCase)) {
+      tester.fullDuplexCallShouldSucceed();
+    } else if ("half_duplex_call_should_succeed".equals(testCase)) {
+      tester.halfDuplexCallShouldSucceed();
+    } else if ("server_streaming_should_be_flow_controlled".equals(testCase)) {
+      tester.serverStreamingShouldBeFlowControlled();
+    } else if ("very_large_request".equals(testCase)) {
+      try {
+        tester.veryLargeRequest();
+      } catch (AssumptionViolatedException e) {
+        // This test case requires more memory than most Android devices have available
+        Log.w(LOG_TAG, "Skipping " + testCase + " due to assumption violation", e);
+      }
+    } else if ("very_large_response".equals(testCase)) {
+      try {
+        tester.veryLargeResponse();
+      } catch (AssumptionViolatedException e) {
+        // This test case requires more memory than most Android devices have available
+        Log.w(LOG_TAG, "Skipping " + testCase + " due to assumption violation", e);
+      }
+    } else if ("deadline_not_exceeded".equals(testCase)) {
+      tester.deadlineNotExceeded();
+    } else if ("deadline_exceeded".equals(testCase)) {
+      tester.deadlineExceeded();
+    } else if ("deadline_exceeded_server_streaming".equals(testCase)) {
+      tester.deadlineExceededServerStreaming();
+    } else if ("unimplemented_method".equals(testCase)) {
+      tester.unimplementedMethod();
+    } else if ("timeout_on_sleeping_server".equals(testCase)) {
+      tester.timeoutOnSleepingServer();
+    } else if ("graceful_shutdown".equals(testCase)) {
+      tester.gracefulShutdown();
+    } else {
+      throw new IllegalArgumentException("Unimplemented/Unknown test case: " + testCase);
+    }
+  }
+
+  @Override
+  protected void onPostExecute(String result) {
+    Listener listener = listenerReference.get();
+    if (listener != null) {
+      listener.onComplete(result);
+    }
+  }
+
+  private static class Tester extends AbstractInteropTest {
+    private final ManagedChannel channel;
+    private final List<ClientInterceptor> interceptors;
+
+    private Tester(ManagedChannel channel, List<ClientInterceptor> interceptors) {
+      this.channel = channel;
+      this.interceptors = interceptors;
+    }
+
+    @Override
+    protected ManagedChannel createChannel() {
+      return channel;
+    }
+
+    @Override
+    protected ClientInterceptor[] getAdditionalInterceptors() {
+      return interceptors.toArray(new ClientInterceptor[0]);
+    }
+
+    @Override
+    protected boolean metricsExpected() {
+      return false;
+    }
+  }
+}
diff --git a/android-interop-testing/app/src/main/java/io/grpc/android/integrationtest/TesterActivity.java b/android-interop-testing/app/src/main/java/io/grpc/android/integrationtest/TesterActivity.java
new file mode 100644
index 0000000..e365f04
--- /dev/null
+++ b/android-interop-testing/app/src/main/java/io/grpc/android/integrationtest/TesterActivity.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.android.integrationtest;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.TextView;
+import com.google.android.gms.security.ProviderInstaller;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ClientInterceptors.CheckedForwardingClientCall;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class TesterActivity extends AppCompatActivity
+    implements ProviderInstaller.ProviderInstallListener, InteropTask.Listener {
+  private static final String LOG_TAG = "GrpcTesterActivity";
+
+  private List<Button> buttons;
+  private EditText hostEdit;
+  private EditText portEdit;
+  private TextView resultText;
+  private CheckBox getCheckBox;
+  private CheckBox testCertCheckBox;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_tester);
+    buttons = new LinkedList<>();
+    buttons.add((Button) findViewById(R.id.empty_unary_button));
+    buttons.add((Button) findViewById(R.id.large_unary_button));
+    buttons.add((Button) findViewById(R.id.client_streaming_button));
+    buttons.add((Button) findViewById(R.id.server_streaming_button));
+    buttons.add((Button) findViewById(R.id.ping_pong_button));
+
+    hostEdit = (EditText) findViewById(R.id.host_edit_text);
+    portEdit = (EditText) findViewById(R.id.port_edit_text);
+    resultText = (TextView) findViewById(R.id.grpc_response_text);
+    getCheckBox = (CheckBox) findViewById(R.id.get_checkbox);
+    testCertCheckBox = (CheckBox) findViewById(R.id.test_cert_checkbox);
+
+    ProviderInstaller.installIfNeededAsync(this, this);
+    // Disable buttons until the security provider installing finishes.
+    enableButtons(false);
+  }
+
+  public void startEmptyUnary(View view) {
+    startTest("empty_unary");
+  }
+
+  public void startLargeUnary(View view) {
+    startTest("large_unary");
+  }
+
+  public void startClientStreaming(View view) {
+    startTest("client_streaming");
+  }
+
+  public void startServerStreaming(View view) {
+    startTest("server_streaming");
+  }
+
+  public void startPingPong(View view) {
+    startTest("ping_pong");
+  }
+
+  private void enableButtons(boolean enable) {
+    for (Button button : buttons) {
+      button.setEnabled(enable);
+    }
+  }
+
+  @Override
+  public void onComplete(String result) {
+    resultText.setText(result);
+    enableButtons(true);
+  }
+
+  private void startTest(String testCase) {
+    ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(
+        hostEdit.getWindowToken(), 0);
+    enableButtons(false);
+    resultText.setText("Testing...");
+
+    String host = hostEdit.getText().toString();
+    String portStr = portEdit.getText().toString();
+    int port = TextUtils.isEmpty(portStr) ? 8080 : Integer.valueOf(portStr);
+
+    String serverHostOverride;
+    InputStream testCert;
+    if (testCertCheckBox.isChecked()) {
+      serverHostOverride = "foo.test.google.fr";
+      testCert = getResources().openRawResource(R.raw.ca);
+    } else {
+      serverHostOverride = null;
+      testCert = null;
+    }
+    ManagedChannel channel =
+        TesterOkHttpChannelBuilder.build(host, port, serverHostOverride, true, testCert);
+
+    List<ClientInterceptor> interceptors = new ArrayList<>();
+    if (getCheckBox.isChecked()) {
+      interceptors.add(new SafeMethodChannelInterceptor());
+    }
+    new InteropTask(this, channel, interceptors, testCase).execute();
+  }
+
+  @Override
+  public void onProviderInstalled() {
+    // Provider is up-to-date, app can make secure network calls.
+    enableButtons(true);
+  }
+
+  @Override
+  public void onProviderInstallFailed(int errorCode, Intent recoveryIntent) {
+    // The provider is helpful, but it is possible to succeed without it.
+    // Hope that the system-provided libraries are new enough.
+    Log.w(LOG_TAG, "Failed installing security provider, error code: " + errorCode);
+    enableButtons(true);
+  }
+
+  private static final class SafeMethodChannelInterceptor implements ClientInterceptor {
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+        MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+      return new CheckedForwardingClientCall<ReqT, RespT>(
+          next.newCall(method.toBuilder().setSafe(true).build(), callOptions)) {
+        @Override
+        public void checkedStart(Listener<RespT> responseListener, Metadata headers) {
+          delegate().start(responseListener, headers);
+        }
+      };
+    }
+  }
+}
diff --git a/android-interop-testing/app/src/main/java/io/grpc/android/integrationtest/TesterOkHttpChannelBuilder.java b/android-interop-testing/app/src/main/java/io/grpc/android/integrationtest/TesterOkHttpChannelBuilder.java
new file mode 100644
index 0000000..417df61
--- /dev/null
+++ b/android-interop-testing/app/src/main/java/io/grpc/android/integrationtest/TesterOkHttpChannelBuilder.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.android.integrationtest;
+
+import android.support.annotation.Nullable;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.okhttp.OkHttpChannelBuilder;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * A helper class to create a OkHttp based channel.
+ */
+class TesterOkHttpChannelBuilder {
+  public static ManagedChannel build(
+      String host,
+      int port,
+      @Nullable String serverHostOverride,
+      boolean useTls,
+      @Nullable InputStream testCa) {
+    ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder.forAddress(host, port)
+        .maxInboundMessageSize(16 * 1024 * 1024);
+    if (serverHostOverride != null) {
+      // Force the hostname to match the cert the server uses.
+      channelBuilder.overrideAuthority(serverHostOverride);
+    }
+    if (useTls) {
+      try {
+        ((OkHttpChannelBuilder) channelBuilder).useTransportSecurity();
+        ((OkHttpChannelBuilder) channelBuilder).sslSocketFactory(getSslSocketFactory(testCa));
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    } else {
+      channelBuilder.usePlaintext();
+    }
+    return channelBuilder.build();
+  }
+
+  private static SSLSocketFactory getSslSocketFactory(@Nullable InputStream testCa)
+      throws Exception {
+    if (testCa == null) {
+      return (SSLSocketFactory) SSLSocketFactory.getDefault();
+    }
+
+    SSLContext context = SSLContext.getInstance("TLS");
+    context.init(null, getTrustManagers(testCa) , null);
+    return context.getSocketFactory();
+  }
+
+  private static TrustManager[] getTrustManagers(InputStream testCa) throws Exception {
+    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+    ks.load(null);
+    CertificateFactory cf = CertificateFactory.getInstance("X.509");
+    X509Certificate cert = (X509Certificate) cf.generateCertificate(testCa);
+    X500Principal principal = cert.getSubjectX500Principal();
+    ks.setCertificateEntry(principal.getName("RFC2253"), cert);
+    // Set up trust manager factory to use our key store.
+    TrustManagerFactory trustManagerFactory =
+        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+    trustManagerFactory.init(ks);
+    return trustManagerFactory.getTrustManagers();
+  }
+}
+
diff --git a/android-interop-testing/app/src/main/res/layout/activity_tester.xml b/android-interop-testing/app/src/main/res/layout/activity_tester.xml
new file mode 100644
index 0000000..1fd55dd
--- /dev/null
+++ b/android-interop-testing/app/src/main/res/layout/activity_tester.xml
@@ -0,0 +1,114 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity"
+    android:orientation="vertical"
+    >
+
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="horizontal"
+      >
+    <EditText
+        android:id="@+id/host_edit_text"
+        android:layout_weight="2"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="Enter Host"
+        />
+    <EditText
+        android:id="@+id/port_edit_text"
+        android:layout_weight="1"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:inputType="numberDecimal"
+        android:hint="Enter Port"
+        />
+  </LinearLayout>
+
+  <LinearLayout
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:orientation="horizontal"
+      >
+    <CheckBox android:id="@+id/get_checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/use_get"
+        />
+    <CheckBox android:id="@+id/test_cert_checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Use Test Cert"
+        />
+  </LinearLayout>
+
+  <Button
+      android:id="@+id/empty_unary_button"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:onClick="startEmptyUnary"
+      android:text="Empty Unary"
+      />
+
+  <Button
+      android:id="@+id/large_unary_button"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:onClick="startLargeUnary"
+      android:text="Large Unary"
+      />
+
+  <Button
+      android:id="@+id/client_streaming_button"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:onClick="startClientStreaming"
+      android:text="Client Streaming"
+      />
+
+  <Button
+      android:id="@+id/server_streaming_button"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:onClick="startServerStreaming"
+      android:text="Server Streaming"
+      />
+
+  <Button
+      android:id="@+id/ping_pong_button"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:onClick="startPingPong"
+      android:text="Ping Pong"
+      />
+
+  <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:paddingTop="12dp"
+      android:paddingBottom="12dp"
+      android:textSize="16sp"
+      android:text="Response:"
+      />
+
+  <ScrollView
+      android:id="@+id/result_area"
+      android:layout_width="fill_parent"
+      android:layout_height="wrap_content"
+      android:scrollbars="vertical"
+      android:fillViewport="true"
+      >
+
+    <TextView
+        android:id="@+id/grpc_response_text"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:textSize="16sp"
+        />
+
+  </ScrollView>
+
+</LinearLayout>
diff --git a/android-interop-testing/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android-interop-testing/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/android-interop-testing/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/android-interop-testing/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android-interop-testing/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/android-interop-testing/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/android-interop-testing/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android-interop-testing/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/android-interop-testing/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/android-interop-testing/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android-interop-testing/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/android-interop-testing/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/android-interop-testing/app/src/main/res/raw/ca.pem b/android-interop-testing/app/src/main/res/raw/ca.pem
new file mode 100644
index 0000000..6c8511a
--- /dev/null
+++ b/android-interop-testing/app/src/main/res/raw/ca.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla
+Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0
+YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT
+BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7
++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu
+g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd
+Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau
+sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m
+oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG
+Dfcog5wrJytaQ6UA0wE=
+-----END CERTIFICATE-----
diff --git a/android-interop-testing/app/src/main/res/values/strings.xml b/android-interop-testing/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c3e7d27
--- /dev/null
+++ b/android-interop-testing/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+<resources>
+    <string name="app_name">gRPC Integration Test</string>
+    <string name="use_get">Use GET</string>
+</resources>
diff --git a/android-interop-testing/build.gradle b/android-interop-testing/build.gradle
new file mode 100644
index 0000000..929c7a6
--- /dev/null
+++ b/android-interop-testing/build.gradle
@@ -0,0 +1,53 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+        google()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.1.2'
+        classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.5"
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        mavenLocal()
+        jcenter()
+        google()
+    }
+}
+
+subprojects {
+    apply plugin: "checkstyle"
+
+    checkstyle {
+        configDir = file("$rootDir/../buildscripts")
+        toolVersion = "6.17"
+        ignoreFailures = false
+        if (rootProject.hasProperty("checkstyle.ignoreFailures")) {
+            ignoreFailures = rootProject.properties["checkstyle.ignoreFailures"].toBoolean()
+        }
+    }
+
+    // Checkstyle doesn't run automatically with android
+    task checkStyleMain(type: Checkstyle) {
+        source 'src/main/java'
+        include '**/*.java'
+        classpath = files()
+    }
+
+    task checkStyleTest(type: Checkstyle) {
+        source 'src/test/java'
+        include '**/*.java'
+        classpath = files()
+    }
+
+    afterEvaluate { project ->
+        project.tasks['check'].dependsOn checkStyleMain, checkStyleTest
+    }
+}
diff --git a/android-interop-testing/settings.gradle b/android-interop-testing/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/android-interop-testing/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/android-interop-testing/start-emulator.sh b/android-interop-testing/start-emulator.sh
new file mode 100755
index 0000000..e596d18
--- /dev/null
+++ b/android-interop-testing/start-emulator.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# The only argument is the AVD name.
+# Note: This script will hang, you may want to run it in the background.
+
+if [ $# -eq 0 ]
+then
+    echo "Please specify the AVD name"
+    exit 1
+fi
+
+echo "[INFO] Starting emulator $1"
+emulator64-arm -avd $1 -netfast -no-skin -no-audio -no-window -port 5554
diff --git a/android-interop-testing/wait-for-emulator.sh b/android-interop-testing/wait-for-emulator.sh
new file mode 100755
index 0000000..14402d1
--- /dev/null
+++ b/android-interop-testing/wait-for-emulator.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+echo "Waiting for emulator to start..."
+
+bootanim=""
+failcounter=0
+until [[ "$bootanim" =~ "stopped" ]]; do
+   bootanim=`adb -e shell getprop init.svc.bootanim 2>&1`
+   let "failcounter += 1"
+   # Timeout after 5 minutes.
+   if [[ $failcounter -gt 300 ]]; then
+      echo "Can not find device after 5 minutes..."
+      exit 1
+   fi
+   sleep 1
+done
+
diff --git a/android/build.gradle b/android/build.gradle
new file mode 100644
index 0000000..d3303c4
--- /dev/null
+++ b/android/build.gradle
@@ -0,0 +1,145 @@
+apply plugin: 'com.android.library'
+
+group = "io.grpc"
+version = "1.16.0-SNAPSHOT" // CURRENT_GRPC_VERSION
+description = 'gRPC: Android'
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+        mavenCentral()
+        maven { url "https://plugins.gradle.org/m2/" }
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.0.1'
+        classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
+        classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
+    }
+}
+
+apply plugin: "net.ltgt.errorprone"
+apply plugin: 'com.github.dcendents.android-maven'
+apply plugin: "signing"
+
+android {
+    compileSdkVersion 27
+    defaultConfig {
+        consumerProguardFiles "proguard-rules.txt"
+        minSdkVersion 14
+        targetSdkVersion 27
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    lintOptions { abortOnError false }
+}
+
+repositories {
+    mavenCentral()
+    mavenLocal()
+}
+
+dependencies {
+    implementation 'io.grpc:grpc-core:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+
+    testImplementation 'io.grpc:grpc-okhttp:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    testImplementation 'junit:junit:4.12'
+    testImplementation 'org.robolectric:robolectric:3.7.1'
+    testImplementation 'com.google.truth:truth:0.39'
+}
+
+signing {
+    required false
+    sign configurations.archives
+}
+
+task javadocs(type: Javadoc) {
+    source = android.sourceSets.main.java.srcDirs
+    // TODO(ericgribkoff) Fix javadoc errors
+    failOnError false
+    options {
+        // Disable JavaDoc doclint on Java 8.
+        if (JavaVersion.current().isJava8Compatible()) {
+            addStringOption('Xdoclint:none', '-quiet')
+        }
+    }
+}
+
+task javadocsJar(type: Jar, dependsOn: javadocs) {
+    classifier = 'javadoc'
+    from javadocs.destinationDir
+}
+
+task sourcesJar(type: Jar) {
+    classifier = 'sources'
+    from android.sourceSets.main.java.srcDirs
+}
+
+artifacts {
+    archives sourcesJar
+    archives javadocsJar
+}
+
+uploadArchives.repositories.mavenDeployer {
+    beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+    if (rootProject.hasProperty('repositoryDir')) {
+        repository(url: new File(rootProject.repositoryDir).toURI())
+    } else {
+        String stagingUrl
+        if (rootProject.hasProperty('repositoryId')) {
+            stagingUrl = 'https://oss.sonatype.org/service/local/staging/deployByRepositoryId/' +
+                    rootProject.repositoryId
+        } else {
+            stagingUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
+        }
+        def configureAuth = {
+            if (rootProject.hasProperty('ossrhUsername') && rootProject.hasProperty('ossrhPassword')) {
+                authentication(userName: rootProject.ossrhUsername, password: rootProject.ossrhPassword)
+            }
+        }
+        repository(url: stagingUrl, configureAuth)
+        snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots/', configureAuth)
+    }
+}
+
+[
+    install.repositories.mavenInstaller,
+    uploadArchives.repositories.mavenDeployer,
+]*.pom*.whenConfigured { pom ->
+    pom.project {
+        name "$project.group:$project.name"
+        description project.description
+        url 'https://conscrypt.org/'
+
+        scm {
+            connection 'scm:git:https://github.com/grpc/grpc-java.git'
+            developerConnection 'scm:git:git@github.com:grpc/grpc-java.git'
+            url 'https://github.com/grpc/grpc-java'
+        }
+
+        licenses {
+            license {
+                name 'Apache 2.0'
+                url 'https://opensource.org/licenses/Apache-2.0'
+            }
+        }
+
+        developers {
+            developer {
+                id "grpc.io"
+                name "gRPC Contributors"
+                email "grpc-io@googlegroups.com"
+                url "https://grpc.io/"
+                // https://issues.gradle.org/browse/GRADLE-2719
+                organization = "gRPC Authors"
+                organizationUrl "https://www.google.com"
+            }
+        }
+    }
+    def core = pom.dependencies.find {dep -> dep.artifactId == 'grpc-core'}
+    if (core != null) {
+        // Depend on specific version of grpc-core because internal package is unstable
+        core.version = "[" + core.version + "]"
+    }
+}
diff --git a/android/proguard-rules.txt b/android/proguard-rules.txt
new file mode 100644
index 0000000..dc17f40
--- /dev/null
+++ b/android/proguard-rules.txt
@@ -0,0 +1,6 @@
+-keepclassmembers class io.grpc.okhttp.OkHttpChannelBuilder {
+  io.grpc.okhttp.OkHttpChannelBuilder forTarget(java.lang.String);
+  io.grpc.okhttp.OkHttpChannelBuilder scheduledExecutorService(java.util.concurrent.ScheduledExecutorService);
+  io.grpc.okhttp.OkHttpChannelBuilder sslSocketFactory(javax.net.ssl.SSLSocketFactory);
+  io.grpc.okhttp.OkHttpChannelBuilder transportExecutor(java.util.concurrent.Executor);
+}
diff --git a/android/settings.gradle b/android/settings.gradle
new file mode 100644
index 0000000..9b09956
--- /dev/null
+++ b/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'grpc-android'
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5383673
--- /dev/null
+++ b/android/src/main/AndroidManifest.xml
@@ -0,0 +1,6 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.grpc.android">
+
+  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
+</manifest>
diff --git a/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java b/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java
new file mode 100644
index 0000000..7d2fdea
--- /dev/null
+++ b/android/src/main/java/io/grpc/android/AndroidChannelBuilder.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.android;
+
+import android.annotation.TargetApi;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkInfo;
+import android.os.Build;
+import android.util.Log;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.ConnectivityState;
+import io.grpc.ExperimentalApi;
+import io.grpc.ForwardingChannelBuilder;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.MethodDescriptor;
+import io.grpc.internal.GrpcUtil;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Builds a {@link ManagedChannel} that, when provided with a {@link Context}, will automatically
+ * monitor the Android device's network state to smoothly handle intermittent network failures.
+ *
+ * <p>Currently only compatible with gRPC's OkHttp transport, which must be available at runtime.
+ *
+ * <p>Requires the Android ACCESS_NETWORK_STATE permission.
+ *
+ * @since 1.12.0
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4056")
+public final class AndroidChannelBuilder extends ForwardingChannelBuilder<AndroidChannelBuilder> {
+
+  private static final String LOG_TAG = "AndroidChannelBuilder";
+
+  @Nullable private static final Class<?> OKHTTP_CHANNEL_BUILDER_CLASS = findOkHttp();
+
+  private static final Class<?> findOkHttp() {
+    try {
+      return Class.forName("io.grpc.okhttp.OkHttpChannelBuilder");
+    } catch (ClassNotFoundException e) {
+      return null;
+    }
+  }
+
+  private final ManagedChannelBuilder<?> delegateBuilder;
+
+  @Nullable private Context context;
+
+  public static final AndroidChannelBuilder forTarget(String target) {
+    return new AndroidChannelBuilder(target);
+  }
+
+  public static AndroidChannelBuilder forAddress(String name, int port) {
+    return forTarget(GrpcUtil.authorityFromHostAndPort(name, port));
+  }
+
+  public static AndroidChannelBuilder fromBuilder(ManagedChannelBuilder<?> builder) {
+    return new AndroidChannelBuilder(builder);
+  }
+
+  private AndroidChannelBuilder(String target) {
+    if (OKHTTP_CHANNEL_BUILDER_CLASS == null) {
+      throw new UnsupportedOperationException("No ManagedChannelBuilder found on the classpath");
+    }
+    try {
+      delegateBuilder =
+          (ManagedChannelBuilder)
+              OKHTTP_CHANNEL_BUILDER_CLASS
+                  .getMethod("forTarget", String.class)
+                  .invoke(null, target);
+    } catch (Exception e) {
+      throw new RuntimeException("Failed to create ManagedChannelBuilder", e);
+    }
+  }
+
+  private AndroidChannelBuilder(ManagedChannelBuilder<?> delegateBuilder) {
+    this.delegateBuilder = Preconditions.checkNotNull(delegateBuilder, "delegateBuilder");
+  }
+
+  /** Enables automatic monitoring of the device's network state. */
+  public AndroidChannelBuilder context(Context context) {
+    this.context = context;
+    return this;
+  }
+
+  /**
+   * Set the delegate channel builder's transportExecutor.
+   *
+   * @deprecated Use {@link #fromBuilder(ManagedChannelBuilder)} with a pre-configured
+   *     ManagedChannelBuilder instead.
+   */
+  @Deprecated
+  public AndroidChannelBuilder transportExecutor(@Nullable Executor transportExecutor) {
+    try {
+      OKHTTP_CHANNEL_BUILDER_CLASS
+          .getMethod("transportExecutor", Executor.class)
+          .invoke(delegateBuilder, transportExecutor);
+      return this;
+    } catch (Exception e) {
+      throw new RuntimeException("Failed to invoke transportExecutor on delegate builder", e);
+    }
+  }
+
+  /**
+   * Set the delegate channel builder's sslSocketFactory.
+   *
+   * @deprecated Use {@link #fromBuilder(ManagedChannelBuilder)} with a pre-configured
+   *     ManagedChannelBuilder instead.
+   */
+  @Deprecated
+  public AndroidChannelBuilder sslSocketFactory(SSLSocketFactory factory) {
+    try {
+      OKHTTP_CHANNEL_BUILDER_CLASS
+          .getMethod("sslSocketFactory", SSLSocketFactory.class)
+          .invoke(delegateBuilder, factory);
+      return this;
+    } catch (Exception e) {
+      throw new RuntimeException("Failed to invoke sslSocketFactory on delegate builder", e);
+    }
+  }
+
+  /**
+   * Set the delegate channel builder's scheduledExecutorService.
+   *
+   * @deprecated Use {@link #fromBuilder(ManagedChannelBuilder)} with a pre-configured
+   *     ManagedChannelBuilder instead.
+   */
+  @Deprecated
+  public AndroidChannelBuilder scheduledExecutorService(
+      ScheduledExecutorService scheduledExecutorService) {
+    try {
+      OKHTTP_CHANNEL_BUILDER_CLASS
+          .getMethod("scheduledExecutorService", ScheduledExecutorService.class)
+          .invoke(delegateBuilder, scheduledExecutorService);
+      return this;
+    } catch (Exception e) {
+      throw new RuntimeException(
+          "Failed to invoke scheduledExecutorService on delegate builder", e);
+    }
+  }
+
+  @Override
+  protected ManagedChannelBuilder<?> delegate() {
+    return delegateBuilder;
+  }
+
+  @Override
+  public ManagedChannel build() {
+    return new AndroidChannel(delegateBuilder.build(), context);
+  }
+
+  /**
+   * Wraps an OkHttp channel and handles invoking the appropriate methods (e.g., {@link
+   * ManagedChannel#resetConnectBackoff}) when the device network state changes.
+   */
+  @VisibleForTesting
+  static final class AndroidChannel extends ManagedChannel {
+
+    private final ManagedChannel delegate;
+
+    @Nullable private final Context context;
+    @Nullable private final ConnectivityManager connectivityManager;
+
+    private final Object lock = new Object();
+
+    @GuardedBy("lock")
+    private Runnable unregisterRunnable;
+
+    @VisibleForTesting
+    AndroidChannel(final ManagedChannel delegate, @Nullable Context context) {
+      this.delegate = delegate;
+      this.context = context;
+
+      if (context != null) {
+        connectivityManager =
+            (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        try {
+          configureNetworkMonitoring();
+        } catch (SecurityException e) {
+          Log.w(
+              LOG_TAG,
+              "Failed to configure network monitoring. Does app have ACCESS_NETWORK_STATE"
+                  + " permission?",
+              e);
+        }
+      } else {
+        connectivityManager = null;
+      }
+    }
+
+    @GuardedBy("lock")
+    private void configureNetworkMonitoring() {
+      // Android N added the registerDefaultNetworkCallback API to listen to changes in the device's
+      // default network. For earlier Android API levels, use the BroadcastReceiver API.
+      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && connectivityManager != null) {
+        final DefaultNetworkCallback defaultNetworkCallback = new DefaultNetworkCallback();
+        connectivityManager.registerDefaultNetworkCallback(defaultNetworkCallback);
+        unregisterRunnable =
+            new Runnable() {
+              @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+              @Override
+              public void run() {
+                connectivityManager.unregisterNetworkCallback(defaultNetworkCallback);
+              }
+            };
+      } else {
+        final NetworkReceiver networkReceiver = new NetworkReceiver();
+        IntentFilter networkIntentFilter =
+            new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+        context.registerReceiver(networkReceiver, networkIntentFilter);
+        unregisterRunnable =
+            new Runnable() {
+              @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+              @Override
+              public void run() {
+                context.unregisterReceiver(networkReceiver);
+              }
+            };
+      }
+    }
+
+    private void unregisterNetworkListener() {
+      synchronized (lock) {
+        if (unregisterRunnable != null) {
+          unregisterRunnable.run();
+          unregisterRunnable = null;
+        }
+      }
+    }
+
+    @Override
+    public ManagedChannel shutdown() {
+      unregisterNetworkListener();
+      return delegate.shutdown();
+    }
+
+    @Override
+    public boolean isShutdown() {
+      return delegate.isShutdown();
+    }
+
+    @Override
+    public boolean isTerminated() {
+      return delegate.isTerminated();
+    }
+
+    @Override
+    public ManagedChannel shutdownNow() {
+      unregisterNetworkListener();
+      return delegate.shutdownNow();
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+      return delegate.awaitTermination(timeout, unit);
+    }
+
+    @Override
+    public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
+        MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) {
+      return delegate.newCall(methodDescriptor, callOptions);
+    }
+
+    @Override
+    public String authority() {
+      return delegate.authority();
+    }
+
+    @Override
+    public ConnectivityState getState(boolean requestConnection) {
+      return delegate.getState(requestConnection);
+    }
+
+    @Override
+    public void notifyWhenStateChanged(ConnectivityState source, Runnable callback) {
+      delegate.notifyWhenStateChanged(source, callback);
+    }
+
+    @Override
+    public void resetConnectBackoff() {
+      delegate.resetConnectBackoff();
+    }
+
+    @Override
+    public void enterIdle() {
+      delegate.enterIdle();
+    }
+
+    /** Respond to changes in the default network. Only used on API levels 24+. */
+    @TargetApi(Build.VERSION_CODES.N)
+    private class DefaultNetworkCallback extends ConnectivityManager.NetworkCallback {
+      // Registering a listener may immediate invoke onAvailable/onLost: the API docs do not specify
+      // if the methods are always invoked once, then again on any change, or only on change. When
+      // onAvailable() is invoked immediately without an actual network change, it's preferable to
+      // (spuriously) resetConnectBackoff() rather than enterIdle(), as the former is a no-op if the
+      // channel has already moved to CONNECTING.
+      private boolean isConnected = false;
+
+      @Override
+      public void onAvailable(Network network) {
+        if (isConnected) {
+          delegate.enterIdle();
+        } else {
+          delegate.resetConnectBackoff();
+        }
+        isConnected = true;
+      }
+
+      @Override
+      public void onLost(Network network) {
+        isConnected = false;
+      }
+    }
+
+    /** Respond to network changes. Only used on API levels < 24. */
+    private class NetworkReceiver extends BroadcastReceiver {
+      private boolean isConnected = false;
+
+      @Override
+      public void onReceive(Context context, Intent intent) {
+        ConnectivityManager conn =
+            (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo networkInfo = conn.getActiveNetworkInfo();
+        boolean wasConnected = isConnected;
+        isConnected = networkInfo != null && networkInfo.isConnected();
+        if (isConnected && !wasConnected) {
+          delegate.resetConnectBackoff();
+        }
+      }
+    }
+  }
+}
diff --git a/android/src/test/java/io/grpc/android/AndroidChannelBuilderTest.java b/android/src/test/java/io/grpc/android/AndroidChannelBuilderTest.java
new file mode 100644
index 0000000..456274d
--- /dev/null
+++ b/android/src/test/java/io/grpc/android/AndroidChannelBuilderTest.java
@@ -0,0 +1,504 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.android;
+
+import static android.os.Build.VERSION_CODES.LOLLIPOP;
+import static android.os.Build.VERSION_CODES.N;
+import static com.google.common.truth.Truth.assertThat;
+import static org.robolectric.RuntimeEnvironment.getApiLevel;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkInfo;
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.ManagedChannel;
+import io.grpc.MethodDescriptor;
+import io.grpc.okhttp.OkHttpChannelBuilder;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLSocketFactory;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowConnectivityManager;
+import org.robolectric.shadows.ShadowNetwork;
+import org.robolectric.shadows.ShadowNetworkInfo;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {AndroidChannelBuilderTest.ShadowDefaultNetworkListenerConnectivityManager.class})
+public final class AndroidChannelBuilderTest {
+  private final NetworkInfo WIFI_CONNECTED =
+      ShadowNetworkInfo.newInstance(
+          NetworkInfo.DetailedState.CONNECTED, ConnectivityManager.TYPE_WIFI, 0, true, true);
+  private final NetworkInfo WIFI_DISCONNECTED =
+      ShadowNetworkInfo.newInstance(
+          NetworkInfo.DetailedState.DISCONNECTED, ConnectivityManager.TYPE_WIFI, 0, true, false);
+  private final NetworkInfo MOBILE_CONNECTED =
+      ShadowNetworkInfo.newInstance(
+          NetworkInfo.DetailedState.CONNECTED,
+          ConnectivityManager.TYPE_MOBILE,
+          ConnectivityManager.TYPE_MOBILE_MMS,
+          true,
+          true);
+  private final NetworkInfo MOBILE_DISCONNECTED =
+      ShadowNetworkInfo.newInstance(
+          NetworkInfo.DetailedState.DISCONNECTED,
+          ConnectivityManager.TYPE_MOBILE,
+          ConnectivityManager.TYPE_MOBILE_MMS,
+          true,
+          false);
+
+  private ConnectivityManager connectivityManager;
+
+  @Before
+  public void setUp() {
+    connectivityManager =
+        (ConnectivityManager)
+            RuntimeEnvironment.application.getSystemService(Context.CONNECTIVITY_SERVICE);
+  }
+
+  @Test
+  public void channelBuilderClassFoundReflectively() {
+    // This should not throw with OkHttpChannelBuilder on the classpath
+    AndroidChannelBuilder.forTarget("target");
+  }
+
+  @Test
+  public void fromBuilderConstructor() {
+    OkHttpChannelBuilder wrappedBuilder = OkHttpChannelBuilder.forTarget("target");
+    AndroidChannelBuilder androidBuilder = AndroidChannelBuilder.fromBuilder(wrappedBuilder);
+    assertThat(androidBuilder.delegate()).isSameAs(wrappedBuilder);
+  }
+
+  @Test
+  public void transportExecutor() {
+    AndroidChannelBuilder.forTarget("target")
+        .transportExecutor(
+            new Executor() {
+              @Override
+              public void execute(Runnable r) {}
+            });
+  }
+
+  @Test
+  public void sslSocketFactory() {
+    AndroidChannelBuilder.forTarget("target")
+        .sslSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault());
+  }
+
+  @Test
+  public void scheduledExecutorService() {
+    AndroidChannelBuilder.forTarget("target").scheduledExecutorService(new ScheduledExecutorImpl());
+  }
+
+  @Test
+  @Config(sdk = 23)
+  public void nullContextDoesNotThrow_api23() {
+    TestChannel delegateChannel = new TestChannel();
+    ManagedChannel androidChannel = new AndroidChannelBuilder.AndroidChannel(delegateChannel, null);
+
+    // Network change and shutdown should be no-op for the channel without an Android Context
+    shadowOf(connectivityManager).setActiveNetworkInfo(WIFI_CONNECTED);
+    RuntimeEnvironment.application.sendBroadcast(
+        new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
+    androidChannel.shutdown();
+
+    assertThat(delegateChannel.resetCount).isEqualTo(0);
+  }
+
+  @Test
+  @Config(sdk = 24)
+  public void nullContextDoesNotThrow_api24() {
+    shadowOf(connectivityManager).setActiveNetworkInfo(MOBILE_DISCONNECTED);
+    TestChannel delegateChannel = new TestChannel();
+    ManagedChannel androidChannel = new AndroidChannelBuilder.AndroidChannel(delegateChannel, null);
+
+    // Network change and shutdown should be no-op for the channel without an Android Context
+    shadowOf(connectivityManager).setActiveNetworkInfo(MOBILE_CONNECTED);
+    androidChannel.shutdown();
+
+    assertThat(delegateChannel.resetCount).isEqualTo(0);
+    assertThat(delegateChannel.enterIdleCount).isEqualTo(0);
+  }
+
+  @Test
+  @Config(sdk = 23)
+  public void resetConnectBackoff_api23() {
+    TestChannel delegateChannel = new TestChannel();
+    ManagedChannel androidChannel =
+        new AndroidChannelBuilder.AndroidChannel(
+            delegateChannel, RuntimeEnvironment.application.getApplicationContext());
+    assertThat(delegateChannel.resetCount).isEqualTo(0);
+
+    // On API levels < 24, the broadcast receiver will invoke resetConnectBackoff() on the first
+    // connectivity action broadcast regardless of previous connection status
+    shadowOf(connectivityManager).setActiveNetworkInfo(WIFI_CONNECTED);
+    RuntimeEnvironment.application.sendBroadcast(
+        new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
+    assertThat(delegateChannel.resetCount).isEqualTo(1);
+
+    // The broadcast receiver may fire when the active network status has not actually changed
+    RuntimeEnvironment.application.sendBroadcast(
+        new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
+    assertThat(delegateChannel.resetCount).isEqualTo(1);
+
+    // Drop the connection
+    shadowOf(connectivityManager).setActiveNetworkInfo(null);
+    RuntimeEnvironment.application.sendBroadcast(
+        new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
+    assertThat(delegateChannel.resetCount).isEqualTo(1);
+
+    // Notify that a new but not connected network is available
+    shadowOf(connectivityManager).setActiveNetworkInfo(MOBILE_DISCONNECTED);
+    RuntimeEnvironment.application.sendBroadcast(
+        new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
+    assertThat(delegateChannel.resetCount).isEqualTo(1);
+
+    // Establish a connection
+    shadowOf(connectivityManager).setActiveNetworkInfo(MOBILE_CONNECTED);
+    RuntimeEnvironment.application.sendBroadcast(
+        new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
+    assertThat(delegateChannel.resetCount).isEqualTo(2);
+
+    // Disconnect, then shutdown the channel and verify that the broadcast receiver has been
+    // unregistered
+    shadowOf(connectivityManager).setActiveNetworkInfo(null);
+    RuntimeEnvironment.application.sendBroadcast(
+        new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
+    androidChannel.shutdown();
+    shadowOf(connectivityManager).setActiveNetworkInfo(MOBILE_CONNECTED);
+    RuntimeEnvironment.application.sendBroadcast(
+        new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
+
+    assertThat(delegateChannel.resetCount).isEqualTo(2);
+    // enterIdle is not called on API levels < 24
+    assertThat(delegateChannel.enterIdleCount).isEqualTo(0);
+  }
+
+  @Test
+  @Config(sdk = 24)
+  public void resetConnectBackoffAndEnterIdle_api24() {
+    shadowOf(connectivityManager).setActiveNetworkInfo(MOBILE_DISCONNECTED);
+    TestChannel delegateChannel = new TestChannel();
+    ManagedChannel androidChannel =
+        new AndroidChannelBuilder.AndroidChannel(
+            delegateChannel, RuntimeEnvironment.application.getApplicationContext());
+    assertThat(delegateChannel.resetCount).isEqualTo(0);
+    assertThat(delegateChannel.enterIdleCount).isEqualTo(0);
+
+    // Establish an initial network connection
+    shadowOf(connectivityManager).setActiveNetworkInfo(MOBILE_CONNECTED);
+    assertThat(delegateChannel.resetCount).isEqualTo(1);
+    assertThat(delegateChannel.enterIdleCount).isEqualTo(0);
+
+    // Switch to another network to trigger enterIdle()
+    shadowOf(connectivityManager).setActiveNetworkInfo(WIFI_CONNECTED);
+    assertThat(delegateChannel.resetCount).isEqualTo(1);
+    assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
+
+    // Switch to an offline network and then to null
+    shadowOf(connectivityManager).setActiveNetworkInfo(WIFI_DISCONNECTED);
+    shadowOf(connectivityManager).setActiveNetworkInfo(null);
+    assertThat(delegateChannel.resetCount).isEqualTo(1);
+    assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
+
+    // Establish a connection
+    shadowOf(connectivityManager).setActiveNetworkInfo(MOBILE_CONNECTED);
+    assertThat(delegateChannel.resetCount).isEqualTo(2);
+    assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
+
+    // Disconnect, then shutdown the channel and verify that the callback has been unregistered
+    shadowOf(connectivityManager).setActiveNetworkInfo(null);
+    androidChannel.shutdown();
+    shadowOf(connectivityManager).setActiveNetworkInfo(MOBILE_CONNECTED);
+
+    assertThat(delegateChannel.resetCount).isEqualTo(2);
+    assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
+  }
+
+  @Test
+  @Config(sdk = 24)
+  public void newChannelWithConnection_entersIdleOnSecondConnectionChange_api24() {
+    shadowOf(connectivityManager).setActiveNetworkInfo(MOBILE_CONNECTED);
+    TestChannel delegateChannel = new TestChannel();
+    ManagedChannel androidChannel =
+        new AndroidChannelBuilder.AndroidChannel(
+            delegateChannel, RuntimeEnvironment.application.getApplicationContext());
+
+    // The first onAvailable() may just signal that the device was connected when the callback is
+    // registered, rather than indicating a changed network, so we do not enter idle.
+    shadowOf(connectivityManager).setActiveNetworkInfo(WIFI_CONNECTED);
+    assertThat(delegateChannel.resetCount).isEqualTo(1);
+    assertThat(delegateChannel.enterIdleCount).isEqualTo(0);
+
+    shadowOf(connectivityManager).setActiveNetworkInfo(MOBILE_CONNECTED);
+    assertThat(delegateChannel.resetCount).isEqualTo(1);
+    assertThat(delegateChannel.enterIdleCount).isEqualTo(1);
+
+    androidChannel.shutdown();
+  }
+
+  @Test
+  @Config(sdk = 23)
+  public void shutdownNowUnregistersBroadcastReceiver_api23() {
+    TestChannel delegateChannel = new TestChannel();
+    ManagedChannel androidChannel =
+        new AndroidChannelBuilder.AndroidChannel(
+            delegateChannel, RuntimeEnvironment.application.getApplicationContext());
+
+    shadowOf(connectivityManager).setActiveNetworkInfo(null);
+    RuntimeEnvironment.application.sendBroadcast(
+        new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
+    androidChannel.shutdownNow();
+    shadowOf(connectivityManager).setActiveNetworkInfo(WIFI_CONNECTED);
+    RuntimeEnvironment.application.sendBroadcast(
+        new Intent(ConnectivityManager.CONNECTIVITY_ACTION));
+
+    assertThat(delegateChannel.resetCount).isEqualTo(0);
+  }
+
+  @Test
+  @Config(sdk = 24)
+  public void shutdownNowUnregistersNetworkCallback_api24() {
+    shadowOf(connectivityManager).setActiveNetworkInfo(null);
+    TestChannel delegateChannel = new TestChannel();
+    ManagedChannel androidChannel =
+        new AndroidChannelBuilder.AndroidChannel(
+            delegateChannel, RuntimeEnvironment.application.getApplicationContext());
+
+    androidChannel.shutdownNow();
+    shadowOf(connectivityManager).setActiveNetworkInfo(WIFI_CONNECTED);
+
+    assertThat(delegateChannel.resetCount).isEqualTo(0);
+  }
+
+  /**
+   * Extends Robolectric ShadowConnectivityManager to handle Android N's
+   * registerDefaultNetworkCallback API.
+   */
+  @Implements(value = ConnectivityManager.class)
+  public static class ShadowDefaultNetworkListenerConnectivityManager
+      extends ShadowConnectivityManager {
+    private HashSet<ConnectivityManager.NetworkCallback> defaultNetworkCallbacks = new HashSet<>();
+
+    public ShadowDefaultNetworkListenerConnectivityManager() {
+      super();
+    }
+
+    @Override
+    public void setActiveNetworkInfo(NetworkInfo activeNetworkInfo) {
+      if (getApiLevel() >= N) {
+        NetworkInfo previousNetworkInfo = getActiveNetworkInfo();
+        if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
+          notifyDefaultNetworkCallbacksOnAvailable(
+              ShadowNetwork.newInstance(activeNetworkInfo.getType() /* use type as network ID */));
+        } else if (previousNetworkInfo != null) {
+          notifyDefaultNetworkCallbacksOnLost(
+              ShadowNetwork.newInstance(
+                  previousNetworkInfo.getType() /* use type as network ID */));
+        }
+      }
+      super.setActiveNetworkInfo(activeNetworkInfo);
+    }
+
+    private void notifyDefaultNetworkCallbacksOnAvailable(Network network) {
+      for (ConnectivityManager.NetworkCallback networkCallback : defaultNetworkCallbacks) {
+        networkCallback.onAvailable(network);
+      }
+    }
+
+    private void notifyDefaultNetworkCallbacksOnLost(Network network) {
+      for (ConnectivityManager.NetworkCallback networkCallback : defaultNetworkCallbacks) {
+        networkCallback.onLost(network);
+      }
+    }
+
+    @Implementation(minSdk = N)
+    protected void registerDefaultNetworkCallback(
+        ConnectivityManager.NetworkCallback networkCallback) {
+      defaultNetworkCallbacks.add(networkCallback);
+    }
+
+    @Implementation(minSdk = LOLLIPOP)
+    @Override
+    public void unregisterNetworkCallback(ConnectivityManager.NetworkCallback networkCallback) {
+      if (getApiLevel() >= N) {
+        if (networkCallback != null || defaultNetworkCallbacks.contains(networkCallback)) {
+          defaultNetworkCallbacks.remove(networkCallback);
+        }
+      }
+      super.unregisterNetworkCallback(networkCallback);
+    }
+  }
+
+  private static class TestChannel extends ManagedChannel {
+    int resetCount;
+    int enterIdleCount;
+
+    @Override
+    public ManagedChannel shutdown() {
+      return null;
+    }
+
+    @Override
+    public boolean isShutdown() {
+      return false;
+    }
+
+    @Override
+    public boolean isTerminated() {
+      return false;
+    }
+
+    @Override
+    public ManagedChannel shutdownNow() {
+      return null;
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+      return false;
+    }
+
+    @Override
+    public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
+        MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) {
+      return null;
+    }
+
+    @Override
+    public String authority() {
+      return null;
+    }
+
+    @Override
+    public void resetConnectBackoff() {
+      resetCount++;
+    }
+
+    @Override
+    public void enterIdle() {
+      enterIdleCount++;
+    }
+  }
+
+  private static class ScheduledExecutorImpl implements ScheduledExecutorService {
+    @Override
+    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ScheduledFuture<?> schedule(Runnable cmd, long delay, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleAtFixedRate(
+        Runnable command, long initialDelay, long period, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleWithFixedDelay(
+        Runnable command, long initialDelay, long delay, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(
+        Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isShutdown() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isTerminated() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void shutdown() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public List<Runnable> shutdownNow() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T> Future<T> submit(Callable<T> task) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Future<?> submit(Runnable task) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T> Future<T> submit(Runnable task, T result) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void execute(Runnable command) {
+      throw new UnsupportedOperationException();
+    }
+  }
+}
diff --git a/auth/BUILD.bazel b/auth/BUILD.bazel
new file mode 100644
index 0000000..7cc6214
--- /dev/null
+++ b/auth/BUILD.bazel
@@ -0,0 +1,13 @@
+java_library(
+    name = "auth",
+    srcs = glob([
+        "src/main/java/io/grpc/auth/*.java",
+    ]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//core",
+        "@com_google_auth_google_auth_library_credentials//jar",
+        "@com_google_guava_guava//jar",
+        "@com_google_code_findbugs_jsr305//jar",
+    ],
+)
diff --git a/auth/build.gradle b/auth/build.gradle
new file mode 100644
index 0000000..957c9ec
--- /dev/null
+++ b/auth/build.gradle
@@ -0,0 +1,9 @@
+description = "gRpc: Auth"
+dependencies {
+    compile project(':grpc-core'),
+            libraries.google_auth_credentials
+    testCompile project(':grpc-testing'),
+            libraries.oauth_client
+    signature "org.codehaus.mojo.signature:java17:1.0@signature"
+    signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
+}
diff --git a/auth/src/main/java/io/grpc/auth/ClientAuthInterceptor.java b/auth/src/main/java/io/grpc/auth/ClientAuthInterceptor.java
new file mode 100644
index 0000000..adbf2cb
--- /dev/null
+++ b/auth/src/main/java/io/grpc/auth/ClientAuthInterceptor.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.auth;
+
+import com.google.auth.Credentials;
+import com.google.common.base.Preconditions;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ClientInterceptors.CheckedForwardingClientCall;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.StatusException;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Client interceptor that authenticates all calls by binding header data provided by a credential.
+ * Typically this will populate the Authorization header but other headers may also be filled out.
+ *
+ * <p>Uses the new and simplified Google auth library:
+ * https://github.com/google/google-auth-library-java
+ *
+ * @deprecated use {@link MoreCallCredentials#from(Credentials)} instead.
+ */
+@Deprecated
+public final class ClientAuthInterceptor implements ClientInterceptor {
+
+  private final Credentials credentials;
+
+  private Metadata cached;
+  private Map<String, List<String>> lastMetadata;
+
+  public ClientAuthInterceptor(
+      Credentials credentials, @SuppressWarnings("unused") Executor executor) {
+    this.credentials = Preconditions.checkNotNull(credentials, "credentials");
+    // TODO(louiscryan): refresh token asynchronously with this executor.
+  }
+
+  @Override
+  public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+      final MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, final Channel next) {
+    // TODO(ejona86): If the call fails for Auth reasons, this does not properly propagate info that
+    // would be in WWW-Authenticate, because it does not yet have access to the header.
+    return new CheckedForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
+      @Override
+      protected void checkedStart(Listener<RespT> responseListener, Metadata headers)
+          throws StatusException {
+        Metadata cachedSaved;
+        URI uri = serviceUri(next, method);
+        synchronized (ClientAuthInterceptor.this) {
+          // TODO(louiscryan): This is icky but the current auth library stores the same
+          // metadata map until the next refresh cycle. This will be fixed once
+          // https://github.com/google/google-auth-library-java/issues/3
+          // is resolved.
+          // getRequestMetadata() may return a different map based on the provided URI, i.e., for
+          // JWT. However, today it does not cache JWT and so we won't bother tring to cache its
+          // return value based on the URI.
+          Map<String, List<String>> latestMetadata = getRequestMetadata(uri);
+          if (lastMetadata == null || lastMetadata != latestMetadata) {
+            lastMetadata = latestMetadata;
+            cached = toHeaders(lastMetadata);
+          }
+          cachedSaved = cached;
+        }
+        headers.merge(cachedSaved);
+        delegate().start(responseListener, headers);
+      }
+    };
+  }
+
+  /**
+   * Generate a JWT-specific service URI. The URI is simply an identifier with enough information
+   * for a service to know that the JWT was intended for it. The URI will commonly be verified with
+   * a simple string equality check.
+   */
+  private URI serviceUri(Channel channel, MethodDescriptor<?, ?> method) throws StatusException {
+    String authority = channel.authority();
+    if (authority == null) {
+      throw Status.UNAUTHENTICATED.withDescription("Channel has no authority").asException();
+    }
+    // Always use HTTPS, by definition.
+    final String scheme = "https";
+    final int defaultPort = 443;
+    String path = "/" + MethodDescriptor.extractFullServiceName(method.getFullMethodName());
+    URI uri;
+    try {
+      uri = new URI(scheme, authority, path, null, null);
+    } catch (URISyntaxException e) {
+      throw Status.UNAUTHENTICATED.withDescription("Unable to construct service URI for auth")
+          .withCause(e).asException();
+    }
+    // The default port must not be present. Alternative ports should be present.
+    if (uri.getPort() == defaultPort) {
+      uri = removePort(uri);
+    }
+    return uri;
+  }
+
+  private URI removePort(URI uri) throws StatusException {
+    try {
+      return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), -1 /* port */,
+          uri.getPath(), uri.getQuery(), uri.getFragment());
+    } catch (URISyntaxException e) {
+      throw Status.UNAUTHENTICATED.withDescription(
+            "Unable to construct service URI after removing port")
+          .withCause(e).asException();
+    }
+  }
+
+  private Map<String, List<String>> getRequestMetadata(URI uri) throws StatusException {
+    try {
+      return credentials.getRequestMetadata(uri);
+    } catch (IOException e) {
+      throw Status.UNAUTHENTICATED.withDescription("Unable to get request metadata").withCause(e)
+          .asException();
+    }
+  }
+
+  private static final Metadata toHeaders(Map<String, List<String>> metadata) {
+    Metadata headers = new Metadata();
+    if (metadata != null) {
+      for (String key : metadata.keySet()) {
+        Metadata.Key<String> headerKey = Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER);
+        for (String value : metadata.get(key)) {
+          headers.put(headerKey, value);
+        }
+      }
+    }
+    return headers;
+  }
+}
diff --git a/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java
new file mode 100644
index 0000000..c258db1
--- /dev/null
+++ b/auth/src/main/java/io/grpc/auth/GoogleAuthLibraryCallCredentials.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.auth;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.auth.Credentials;
+import com.google.auth.RequestMetadataCallback;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.io.BaseEncoding;
+import io.grpc.Attributes;
+import io.grpc.CallCredentials;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.SecurityLevel;
+import io.grpc.Status;
+import io.grpc.StatusException;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.PrivateKey;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Wraps {@link Credentials} as a {@link CallCredentials}.
+ */
+final class GoogleAuthLibraryCallCredentials implements CallCredentials {
+  private static final Logger log
+      = Logger.getLogger(GoogleAuthLibraryCallCredentials.class.getName());
+  private static final JwtHelper jwtHelper
+      = createJwtHelperOrNull(GoogleAuthLibraryCallCredentials.class.getClassLoader());
+  private static final Class<? extends Credentials> googleCredentialsClass
+      = loadGoogleCredentialsClass();
+
+  private final boolean requirePrivacy;
+  @VisibleForTesting
+  final Credentials creds;
+
+  private Metadata lastHeaders;
+  private Map<String, List<String>> lastMetadata;
+
+  public GoogleAuthLibraryCallCredentials(Credentials creds) {
+    this(creds, jwtHelper);
+  }
+
+  @VisibleForTesting
+  GoogleAuthLibraryCallCredentials(Credentials creds, JwtHelper jwtHelper) {
+    checkNotNull(creds, "creds");
+    boolean requirePrivacy = false;
+    if (googleCredentialsClass != null) {
+      // All GoogleCredentials instances are bearer tokens and should only be used on private
+      // channels. This catches all return values from GoogleCredentials.getApplicationDefault().
+      // This should be checked before upgrading the Service Account to JWT, as JWT is also a bearer
+      // token.
+      requirePrivacy = googleCredentialsClass.isInstance(creds);
+    }
+    if (jwtHelper != null) {
+      creds = jwtHelper.tryServiceAccountToJwt(creds);
+    }
+    this.requirePrivacy = requirePrivacy;
+    this.creds = creds;
+  }
+
+  @Override
+  public void thisUsesUnstableApi() {}
+
+  @Override
+  public void applyRequestMetadata(MethodDescriptor<?, ?> method, Attributes attrs,
+      Executor appExecutor, final MetadataApplier applier) {
+    SecurityLevel security = attrs.get(ATTR_SECURITY_LEVEL);
+    if (security == null) {
+      // Although the API says ATTR_SECURITY_LEVEL is required, no one was really looking at it thus
+      // there may be transports that got away without setting it.  Now we start to check it, it'd
+      // be less disruptive to tolerate nulls.
+      security = SecurityLevel.NONE;
+    }
+    if (requirePrivacy && security != SecurityLevel.PRIVACY_AND_INTEGRITY) {
+      applier.fail(Status.UNAUTHENTICATED
+          .withDescription("Credentials require channel with PRIVACY_AND_INTEGRITY security level. "
+              + "Observed security level: " + security));
+      return;
+    }
+
+    String authority = checkNotNull(attrs.get(ATTR_AUTHORITY), "authority");
+    final URI uri;
+    try {
+      uri = serviceUri(authority, method);
+    } catch (StatusException e) {
+      applier.fail(e.getStatus());
+      return;
+    }
+    // Credentials is expected to manage caching internally if the metadata is fetched over
+    // the network.
+    creds.getRequestMetadata(uri, appExecutor, new RequestMetadataCallback() {
+      @Override
+      public void onSuccess(Map<String, List<String>> metadata) {
+        // Some implementations may pass null metadata.
+
+        // Re-use the headers if getRequestMetadata() returns the same map. It may return a
+        // different map based on the provided URI, i.e., for JWT. However, today it does not
+        // cache JWT and so we won't bother tring to save its return value based on the URI.
+        Metadata headers;
+        try {
+          synchronized (GoogleAuthLibraryCallCredentials.this) {
+            if (lastMetadata == null || lastMetadata != metadata) {
+              lastHeaders = toHeaders(metadata);
+              lastMetadata = metadata;
+            }
+            headers = lastHeaders;
+          }
+        } catch (Throwable t) {
+          applier.fail(Status.UNAUTHENTICATED
+              .withDescription("Failed to convert credential metadata")
+              .withCause(t));
+          return;
+        }
+        applier.apply(headers);
+      }
+
+      @Override
+      public void onFailure(Throwable e) {
+        if (e instanceof IOException) {
+          // Since it's an I/O failure, let the call be retried with UNAVAILABLE.
+          applier.fail(Status.UNAVAILABLE
+              .withDescription("Credentials failed to obtain metadata")
+              .withCause(e));
+        } else {
+          applier.fail(Status.UNAUTHENTICATED
+              .withDescription("Failed computing credential metadata")
+              .withCause(e));
+        }
+      }
+    });
+  }
+
+  /**
+   * Generate a JWT-specific service URI. The URI is simply an identifier with enough information
+   * for a service to know that the JWT was intended for it. The URI will commonly be verified with
+   * a simple string equality check.
+   */
+  private static URI serviceUri(String authority, MethodDescriptor<?, ?> method)
+      throws StatusException {
+    // Always use HTTPS, by definition.
+    final String scheme = "https";
+    final int defaultPort = 443;
+    String path = "/" + MethodDescriptor.extractFullServiceName(method.getFullMethodName());
+    URI uri;
+    try {
+      uri = new URI(scheme, authority, path, null, null);
+    } catch (URISyntaxException e) {
+      throw Status.UNAUTHENTICATED.withDescription("Unable to construct service URI for auth")
+          .withCause(e).asException();
+    }
+    // The default port must not be present. Alternative ports should be present.
+    if (uri.getPort() == defaultPort) {
+      uri = removePort(uri);
+    }
+    return uri;
+  }
+
+  private static URI removePort(URI uri) throws StatusException {
+    try {
+      return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), -1 /* port */,
+          uri.getPath(), uri.getQuery(), uri.getFragment());
+    } catch (URISyntaxException e) {
+      throw Status.UNAUTHENTICATED.withDescription(
+           "Unable to construct service URI after removing port").withCause(e).asException();
+    }
+  }
+
+  @SuppressWarnings("BetaApi") // BaseEncoding is stable in Guava 20.0
+  private static Metadata toHeaders(@Nullable Map<String, List<String>> metadata) {
+    Metadata headers = new Metadata();
+    if (metadata != null) {
+      for (String key : metadata.keySet()) {
+        if (key.endsWith("-bin")) {
+          Metadata.Key<byte[]> headerKey = Metadata.Key.of(key, Metadata.BINARY_BYTE_MARSHALLER);
+          for (String value : metadata.get(key)) {
+            headers.put(headerKey, BaseEncoding.base64().decode(value));
+          }
+        } else {
+          Metadata.Key<String> headerKey = Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER);
+          for (String value : metadata.get(key)) {
+            headers.put(headerKey, value);
+          }
+        }
+      }
+    }
+    return headers;
+  }
+
+  @VisibleForTesting
+  @Nullable
+  static JwtHelper createJwtHelperOrNull(ClassLoader loader) {
+    Class<?> rawServiceAccountClass;
+    try {
+      // Specify loader so it can be overridden in tests
+      rawServiceAccountClass
+          = Class.forName("com.google.auth.oauth2.ServiceAccountCredentials", false, loader);
+    } catch (ClassNotFoundException ex) {
+      return null;
+    }
+    Exception caughtException;
+    try {
+      return new JwtHelper(rawServiceAccountClass, loader);
+    } catch (ClassNotFoundException ex) {
+      caughtException = ex;
+    } catch (NoSuchMethodException ex) {
+      caughtException = ex;
+    }
+    if (caughtException != null) {
+      // Failure is a bug in this class, but we still choose to gracefully recover
+      log.log(Level.WARNING, "Failed to create JWT helper. This is unexpected", caughtException);
+    }
+    return null;
+  }
+
+  @Nullable
+  private static Class<? extends Credentials> loadGoogleCredentialsClass() {
+    Class<?> rawGoogleCredentialsClass;
+    try {
+      // Can't use a loader as it disables ProGuard's reference detection and would fail to rename
+      // this reference. Unfortunately this will initialize the class.
+      rawGoogleCredentialsClass = Class.forName("com.google.auth.oauth2.GoogleCredentials");
+    } catch (ClassNotFoundException ex) {
+      log.log(Level.FINE, "Failed to load GoogleCredentials", ex);
+      return null;
+    }
+    return rawGoogleCredentialsClass.asSubclass(Credentials.class);
+  }
+
+  @VisibleForTesting
+  static class JwtHelper {
+    private final Class<? extends Credentials> serviceAccountClass;
+    private final Constructor<? extends Credentials> jwtConstructor;
+    private final Method getScopes;
+    private final Method getClientId;
+    private final Method getClientEmail;
+    private final Method getPrivateKey;
+    private final Method getPrivateKeyId;
+
+    public JwtHelper(Class<?> rawServiceAccountClass, ClassLoader loader)
+        throws ClassNotFoundException, NoSuchMethodException {
+      serviceAccountClass = rawServiceAccountClass.asSubclass(Credentials.class);
+      getScopes = serviceAccountClass.getMethod("getScopes");
+      getClientId = serviceAccountClass.getMethod("getClientId");
+      getClientEmail = serviceAccountClass.getMethod("getClientEmail");
+      getPrivateKey = serviceAccountClass.getMethod("getPrivateKey");
+      getPrivateKeyId = serviceAccountClass.getMethod("getPrivateKeyId");
+      Class<? extends Credentials> jwtClass = Class.forName(
+          "com.google.auth.oauth2.ServiceAccountJwtAccessCredentials", false, loader)
+          .asSubclass(Credentials.class);
+      jwtConstructor
+          = jwtClass.getConstructor(String.class, String.class, PrivateKey.class, String.class);
+    }
+
+    public Credentials tryServiceAccountToJwt(Credentials creds) {
+      if (!serviceAccountClass.isInstance(creds)) {
+        return creds;
+      }
+      Exception caughtException;
+      try {
+        creds = serviceAccountClass.cast(creds);
+        Collection<?> scopes = (Collection<?>) getScopes.invoke(creds);
+        if (scopes.size() != 0) {
+          // Leave as-is, since the scopes may limit access within the service.
+          return creds;
+        }
+        return jwtConstructor.newInstance(
+            getClientId.invoke(creds),
+            getClientEmail.invoke(creds),
+            getPrivateKey.invoke(creds),
+            getPrivateKeyId.invoke(creds));
+      } catch (IllegalAccessException ex) {
+        caughtException = ex;
+      } catch (InvocationTargetException ex) {
+        caughtException = ex;
+      } catch (InstantiationException ex) {
+        caughtException = ex;
+      }
+      if (caughtException != null) {
+        // Failure is a bug in this class, but we still choose to gracefully recover
+        log.log(
+            Level.WARNING,
+            "Failed converting service account credential to JWT. This is unexpected",
+            caughtException);
+      }
+      return creds;
+    }
+  }
+}
diff --git a/auth/src/main/java/io/grpc/auth/MoreCallCredentials.java b/auth/src/main/java/io/grpc/auth/MoreCallCredentials.java
new file mode 100644
index 0000000..8c6f2dc
--- /dev/null
+++ b/auth/src/main/java/io/grpc/auth/MoreCallCredentials.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.auth;
+
+import com.google.auth.Credentials;
+import io.grpc.CallCredentials;
+
+/**
+ * A utility class that converts other types of credentials to {@link CallCredentials}.
+ */
+public final class MoreCallCredentials {
+  /**
+   * Converts a Google Auth Library {@link Credentials} to {@link CallCredentials}.
+   *
+   * <p>Although this is a stable API, note that the returned instance's API is not stable. You are
+   * free to use the class name {@code CallCredentials} and pass the instance to other code, but the
+   * instance can't be called directly from code expecting stable behavior. See {@link
+   * CallCredentials}.
+   */
+  public static CallCredentials from(Credentials creds) {
+    return new GoogleAuthLibraryCallCredentials(creds);
+  }
+
+  private MoreCallCredentials() {
+  }
+}
diff --git a/auth/src/main/java/io/grpc/auth/package-info.java b/auth/src/main/java/io/grpc/auth/package-info.java
new file mode 100644
index 0000000..fb707bd
--- /dev/null
+++ b/auth/src/main/java/io/grpc/auth/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Implementations of {@link io.grpc.CallCredentials} and authentication related API.
+ */
+package io.grpc.auth;
diff --git a/auth/src/test/java/io/grpc/auth/ClientAuthInterceptorTest.java b/auth/src/test/java/io/grpc/auth/ClientAuthInterceptorTest.java
new file mode 100644
index 0000000..66399f4
--- /dev/null
+++ b/auth/src/test/java/io/grpc/auth/ClientAuthInterceptorTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.auth;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.auth.Credentials;
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.OAuth2Credentials;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimaps;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.Status;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Date;
+import java.util.concurrent.Executor;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link ClientAuthInterceptor}.
+ */
+@RunWith(JUnit4.class)
+@Deprecated
+public class ClientAuthInterceptorTest {
+
+  private static final Metadata.Key<String> AUTHORIZATION = Metadata.Key.of("Authorization",
+      Metadata.ASCII_STRING_MARSHALLER);
+  private static final Metadata.Key<String> EXTRA_AUTHORIZATION = Metadata.Key.of(
+      "Extra-Authorization", Metadata.ASCII_STRING_MARSHALLER);
+
+  private final Executor executor = MoreExecutors.directExecutor();
+
+  @Mock
+  Credentials credentials;
+
+  @Mock
+  Marshaller<String> stringMarshaller;
+
+  @Mock
+  Marshaller<Integer> intMarshaller;
+
+  MethodDescriptor<String, Integer> descriptor;
+
+  @Mock
+  ClientCall.Listener<Integer> listener;
+
+  @Mock
+  Channel channel;
+
+  ClientCallRecorder call = new ClientCallRecorder();
+
+  ClientAuthInterceptor interceptor;
+
+  /** Set up for test. */
+  @Before
+  public void startUp() {
+    MockitoAnnotations.initMocks(this);
+    descriptor = MethodDescriptor.<String, Integer>newBuilder()
+        .setType(MethodDescriptor.MethodType.UNKNOWN)
+        .setFullMethodName("a.service/method")
+        .setRequestMarshaller(stringMarshaller)
+        .setResponseMarshaller(intMarshaller)
+        .build();
+    when(channel.newCall(same(descriptor), any(CallOptions.class))).thenReturn(call);
+    doReturn("localhost:443").when(channel).authority();
+    interceptor = new ClientAuthInterceptor(credentials, executor);
+  }
+
+  @Test
+  public void testCopyCredentialToHeaders() throws IOException {
+    ListMultimap<String, String> values = LinkedListMultimap.create();
+    values.put("Authorization", "token1");
+    values.put("Authorization", "token2");
+    values.put("Extra-Authorization", "token3");
+    values.put("Extra-Authorization", "token4");
+    when(credentials.getRequestMetadata(any(URI.class))).thenReturn(Multimaps.asMap(values));
+    ClientCall<String, Integer> interceptedCall =
+        interceptor.interceptCall(descriptor, CallOptions.DEFAULT, channel);
+    Metadata headers = new Metadata();
+    interceptedCall.start(listener, headers);
+    assertEquals(listener, call.responseListener);
+    assertEquals(headers, call.headers);
+
+    Iterable<String> authorization = headers.getAll(AUTHORIZATION);
+    Assert.assertArrayEquals(new String[]{"token1", "token2"},
+        Iterables.toArray(authorization, String.class));
+    Iterable<String> extraAuthorization = headers.getAll(EXTRA_AUTHORIZATION);
+    Assert.assertArrayEquals(new String[]{"token3", "token4"},
+        Iterables.toArray(extraAuthorization, String.class));
+  }
+
+  @Test
+  public void testCredentialsThrows() throws IOException {
+    when(credentials.getRequestMetadata(any(URI.class))).thenThrow(new IOException("Broken"));
+    ClientCall<String, Integer> interceptedCall =
+        interceptor.interceptCall(descriptor, CallOptions.DEFAULT, channel);
+    Metadata headers = new Metadata();
+    interceptedCall.start(listener, headers);
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    Mockito.verify(listener).onClose(statusCaptor.capture(), isA(Metadata.class));
+    Assert.assertNull(headers.getAll(AUTHORIZATION));
+    assertNull(call.responseListener);
+    assertNull(call.headers);
+    Assert.assertEquals(Status.Code.UNAUTHENTICATED, statusCaptor.getValue().getCode());
+    Assert.assertNotNull(statusCaptor.getValue().getCause());
+  }
+
+  @Test
+  public void testWithOAuth2Credential() {
+    final AccessToken token = new AccessToken("allyourbase", new Date(Long.MAX_VALUE));
+    final OAuth2Credentials oAuth2Credentials = new OAuth2Credentials() {
+      @Override
+      public AccessToken refreshAccessToken() throws IOException {
+        return token;
+      }
+    };
+    interceptor = new ClientAuthInterceptor(oAuth2Credentials, executor);
+    ClientCall<String, Integer> interceptedCall =
+        interceptor.interceptCall(descriptor, CallOptions.DEFAULT, channel);
+    Metadata headers = new Metadata();
+    interceptedCall.start(listener, headers);
+    assertEquals(listener, call.responseListener);
+    assertEquals(headers, call.headers);
+    Iterable<String> authorization = headers.getAll(AUTHORIZATION);
+    Assert.assertArrayEquals(new String[]{"Bearer allyourbase"},
+        Iterables.toArray(authorization, String.class));
+  }
+
+  @Test
+  public void verifyServiceUri() throws IOException {
+    ClientCall<String, Integer> interceptedCall;
+
+    doReturn("example.com:443").when(channel).authority();
+    interceptedCall = interceptor.interceptCall(descriptor, CallOptions.DEFAULT, channel);
+    interceptedCall.start(listener, new Metadata());
+    verify(credentials).getRequestMetadata(URI.create("https://example.com/a.service"));
+    interceptedCall.cancel("Cancel for test", null);
+
+    doReturn("example.com:123").when(channel).authority();
+    interceptedCall = interceptor.interceptCall(descriptor, CallOptions.DEFAULT, channel);
+    interceptedCall.start(listener, new Metadata());
+    verify(credentials).getRequestMetadata(URI.create("https://example.com:123/a.service"));
+    interceptedCall.cancel("Cancel for test", null);
+  }
+
+  private static final class ClientCallRecorder extends ClientCall<String, Integer> {
+    private ClientCall.Listener<Integer> responseListener;
+    private Metadata headers;
+    private int numMessages;
+    private String cancelMessage;
+    private Throwable cancelCause;
+    private boolean halfClosed;
+    private String sentMessage;
+
+    @Override
+    public void start(ClientCall.Listener<Integer> responseListener, Metadata headers) {
+      this.responseListener = responseListener;
+      this.headers = headers;
+    }
+
+    @Override
+    public void request(int numMessages) {
+      this.numMessages = numMessages;
+    }
+
+    @Override
+    public void cancel(String message, Throwable cause) {
+      this.cancelMessage = message;
+      this.cancelCause = cause;
+    }
+
+    @Override
+    public void halfClose() {
+      halfClosed = true;
+    }
+
+    @Override
+    public void sendMessage(String message) {
+      sentMessage = message;
+    }
+
+  }
+}
diff --git a/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java
new file mode 100644
index 0000000..438dab0
--- /dev/null
+++ b/auth/src/test/java/io/grpc/auth/GoogleAuthLibraryCallCredentialsTest.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.auth;
+
+import static com.google.common.base.Charsets.US_ASCII;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.auth.Credentials;
+import com.google.auth.RequestMetadataCallback;
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.OAuth2Credentials;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimaps;
+import io.grpc.Attributes;
+import io.grpc.CallCredentials;
+import io.grpc.CallCredentials.MetadataApplier;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.SecurityLevel;
+import io.grpc.Status;
+import io.grpc.testing.TestMethodDescriptors;
+import java.io.IOException;
+import java.net.URI;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Tests for {@link GoogleAuthLibraryCallCredentials}.
+ */
+@RunWith(JUnit4.class)
+public class GoogleAuthLibraryCallCredentialsTest {
+
+  private static final Metadata.Key<String> AUTHORIZATION = Metadata.Key.of("Authorization",
+      Metadata.ASCII_STRING_MARSHALLER);
+  private static final Metadata.Key<byte[]> EXTRA_AUTHORIZATION = Metadata.Key.of(
+      "Extra-Authorization-bin", Metadata.BINARY_BYTE_MARSHALLER);
+
+  @Mock
+  private Credentials credentials;
+
+  @Mock
+  private MetadataApplier applier;
+
+  private Executor executor = new Executor() {
+    @Override public void execute(Runnable r) {
+      pendingRunnables.add(r);
+    }
+  };
+
+  @Captor
+  private ArgumentCaptor<Metadata> headersCaptor;
+
+  @Captor
+  private ArgumentCaptor<Status> statusCaptor;
+
+  private MethodDescriptor<Void, Void> method = MethodDescriptor.<Void, Void>newBuilder()
+      .setType(MethodDescriptor.MethodType.UNKNOWN)
+      .setFullMethodName("a.service/method")
+      .setRequestMarshaller(TestMethodDescriptors.voidMarshaller())
+      .setResponseMarshaller(TestMethodDescriptors.voidMarshaller())
+      .build();
+  private URI expectedUri = URI.create("https://testauthority/a.service");
+
+  private final String authority = "testauthority";
+  private final Attributes attrs = Attributes.newBuilder()
+      .set(CallCredentials.ATTR_AUTHORITY, authority)
+      .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
+      .build();
+
+  private ArrayList<Runnable> pendingRunnables = new ArrayList<>();
+
+  @Before
+  public void setUp() throws Exception {
+    MockitoAnnotations.initMocks(this);
+    doAnswer(new Answer<Void>() {
+      @Override
+      public Void answer(InvocationOnMock invocation) {
+        Credentials mock = (Credentials) invocation.getMock();
+        URI uri = (URI) invocation.getArguments()[0];
+        RequestMetadataCallback callback = (RequestMetadataCallback) invocation.getArguments()[2];
+        Map<String, List<String>> metadata;
+        try {
+          // Default to calling the blocking method, since it is easier to mock
+          metadata = mock.getRequestMetadata(uri);
+        } catch (Exception ex) {
+          callback.onFailure(ex);
+          return null;
+        }
+        callback.onSuccess(metadata);
+        return null;
+      }
+    }).when(credentials).getRequestMetadata(
+        any(URI.class),
+        any(Executor.class),
+        any(RequestMetadataCallback.class));
+  }
+
+  @After
+  public void tearDown() {
+    assertEquals(0, pendingRunnables.size());
+  }
+
+  @Test
+  public void copyCredentialsToHeaders() throws Exception {
+    ListMultimap<String, String> values = LinkedListMultimap.create();
+    values.put("Authorization", "token1");
+    values.put("Authorization", "token2");
+    values.put("Extra-Authorization-bin", "dG9rZW4z");  // bytes "token3" in base64
+    values.put("Extra-Authorization-bin", "dG9rZW40");  // bytes "token4" in base64
+    when(credentials.getRequestMetadata(eq(expectedUri))).thenReturn(Multimaps.asMap(values));
+
+    GoogleAuthLibraryCallCredentials callCredentials =
+        new GoogleAuthLibraryCallCredentials(credentials);
+    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+
+    verify(credentials).getRequestMetadata(eq(expectedUri));
+    verify(applier).apply(headersCaptor.capture());
+    Metadata headers = headersCaptor.getValue();
+    Iterable<String> authorization = headers.getAll(AUTHORIZATION);
+    assertArrayEquals(new String[]{"token1", "token2"},
+        Iterables.toArray(authorization, String.class));
+    Iterable<byte[]> extraAuthorization = headers.getAll(EXTRA_AUTHORIZATION);
+    assertEquals(2, Iterables.size(extraAuthorization));
+    assertArrayEquals("token3".getBytes(US_ASCII), Iterables.get(extraAuthorization, 0));
+    assertArrayEquals("token4".getBytes(US_ASCII), Iterables.get(extraAuthorization, 1));
+  }
+
+  @Test
+  public void invalidBase64() throws Exception {
+    ListMultimap<String, String> values = LinkedListMultimap.create();
+    values.put("Extra-Authorization-bin", "dG9rZW4z1");  // invalid base64
+    when(credentials.getRequestMetadata(eq(expectedUri))).thenReturn(Multimaps.asMap(values));
+
+    GoogleAuthLibraryCallCredentials callCredentials =
+        new GoogleAuthLibraryCallCredentials(credentials);
+    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+
+    verify(credentials).getRequestMetadata(eq(expectedUri));
+    verify(applier).fail(statusCaptor.capture());
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.Code.UNAUTHENTICATED, status.getCode());
+    assertEquals(IllegalArgumentException.class, status.getCause().getClass());
+  }
+
+  @Test
+  public void credentialsFailsWithIoException() throws Exception {
+    Exception exception = new IOException("Broken");
+    when(credentials.getRequestMetadata(eq(expectedUri))).thenThrow(exception);
+
+    GoogleAuthLibraryCallCredentials callCredentials =
+        new GoogleAuthLibraryCallCredentials(credentials);
+    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+
+    verify(credentials).getRequestMetadata(eq(expectedUri));
+    verify(applier).fail(statusCaptor.capture());
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.Code.UNAVAILABLE, status.getCode());
+    assertEquals(exception, status.getCause());
+  }
+
+  @Test
+  public void credentialsFailsWithRuntimeException() throws Exception {
+    Exception exception = new RuntimeException("Broken");
+    when(credentials.getRequestMetadata(eq(expectedUri))).thenThrow(exception);
+
+    GoogleAuthLibraryCallCredentials callCredentials =
+        new GoogleAuthLibraryCallCredentials(credentials);
+    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+
+    verify(credentials).getRequestMetadata(eq(expectedUri));
+    verify(applier).fail(statusCaptor.capture());
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.Code.UNAUTHENTICATED, status.getCode());
+    assertEquals(exception, status.getCause());
+  }
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void credentialsReturnNullMetadata() throws Exception {
+    ListMultimap<String, String> values = LinkedListMultimap.create();
+    values.put("Authorization", "token1");
+    when(credentials.getRequestMetadata(eq(expectedUri)))
+        .thenReturn(null, Multimaps.<String, String>asMap(values), null);
+
+    GoogleAuthLibraryCallCredentials callCredentials =
+        new GoogleAuthLibraryCallCredentials(credentials);
+    for (int i = 0; i < 3; i++) {
+      callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+    }
+
+    verify(credentials, times(3)).getRequestMetadata(eq(expectedUri));
+
+    verify(applier, times(3)).apply(headersCaptor.capture());
+    List<Metadata> headerList = headersCaptor.getAllValues();
+    assertEquals(3, headerList.size());
+
+    assertEquals(0, headerList.get(0).keys().size());
+
+    Iterable<String> authorization = headerList.get(1).getAll(AUTHORIZATION);
+    assertArrayEquals(new String[]{"token1"}, Iterables.toArray(authorization, String.class));
+
+    assertEquals(0, headerList.get(2).keys().size());
+  }
+
+  @Test
+  public void oauth2Credential() {
+    final AccessToken token = new AccessToken("allyourbase", new Date(Long.MAX_VALUE));
+    final OAuth2Credentials credentials = new OAuth2Credentials() {
+      @Override
+      public AccessToken refreshAccessToken() throws IOException {
+        return token;
+      }
+    };
+    // Security level should not impact non-GoogleCredentials
+    Attributes securityNone = attrs.toBuilder()
+        .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.NONE)
+        .build();
+
+    GoogleAuthLibraryCallCredentials callCredentials =
+        new GoogleAuthLibraryCallCredentials(credentials);
+    callCredentials.applyRequestMetadata(method, securityNone, executor, applier);
+    assertEquals(1, runPendingRunnables());
+
+    verify(applier).apply(headersCaptor.capture());
+    Metadata headers = headersCaptor.getValue();
+    Iterable<String> authorization = headers.getAll(AUTHORIZATION);
+    assertArrayEquals(new String[]{"Bearer allyourbase"},
+        Iterables.toArray(authorization, String.class));
+  }
+
+  @Test
+  public void googleCredential_privacyAndIntegrityAllowed() {
+    final AccessToken token = new AccessToken("allyourbase", new Date(Long.MAX_VALUE));
+    final Credentials credentials = GoogleCredentials.create(token);
+    Attributes privacy = attrs.toBuilder()
+        .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
+        .build();
+
+    GoogleAuthLibraryCallCredentials callCredentials =
+        new GoogleAuthLibraryCallCredentials(credentials);
+    callCredentials.applyRequestMetadata(method, privacy, executor, applier);
+    runPendingRunnables();
+
+    verify(applier).apply(headersCaptor.capture());
+    Metadata headers = headersCaptor.getValue();
+    Iterable<String> authorization = headers.getAll(AUTHORIZATION);
+    assertArrayEquals(new String[]{"Bearer allyourbase"},
+        Iterables.toArray(authorization, String.class));
+  }
+
+  @Test
+  public void googleCredential_integrityDenied() {
+    final AccessToken token = new AccessToken("allyourbase", new Date(Long.MAX_VALUE));
+    final Credentials credentials = GoogleCredentials.create(token);
+    // Anything less than PRIVACY_AND_INTEGRITY should fail
+    Attributes integrity = attrs.toBuilder()
+        .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.INTEGRITY)
+        .build();
+
+    GoogleAuthLibraryCallCredentials callCredentials =
+        new GoogleAuthLibraryCallCredentials(credentials);
+    callCredentials.applyRequestMetadata(method, integrity, executor, applier);
+    runPendingRunnables();
+
+    verify(applier).fail(statusCaptor.capture());
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.Code.UNAUTHENTICATED, status.getCode());
+  }
+
+  @Test
+  public void googleCredential_nullSecurityDenied() {
+    final AccessToken token = new AccessToken("allyourbase", new Date(Long.MAX_VALUE));
+    final Credentials credentials = GoogleCredentials.create(token);
+    // Null should not (for the moment) crash in horrible ways. In the future this could be changed,
+    // since it technically isn't allowed per the API.
+    Attributes integrity = attrs.toBuilder()
+        .set(CallCredentials.ATTR_SECURITY_LEVEL, null)
+        .build();
+
+    GoogleAuthLibraryCallCredentials callCredentials =
+        new GoogleAuthLibraryCallCredentials(credentials);
+    callCredentials.applyRequestMetadata(method, integrity, executor, applier);
+    runPendingRunnables();
+
+    verify(applier).fail(statusCaptor.capture());
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.Code.UNAUTHENTICATED, status.getCode());
+  }
+
+  @Test
+  public void serviceUri() throws Exception {
+    GoogleAuthLibraryCallCredentials callCredentials =
+        new GoogleAuthLibraryCallCredentials(credentials);
+    callCredentials.applyRequestMetadata(method,
+        Attributes.newBuilder()
+            .setAll(attrs)
+            .set(CallCredentials.ATTR_AUTHORITY, "example.com:443")
+            .build(),
+        executor, applier);
+    verify(credentials).getRequestMetadata(eq(new URI("https://example.com/a.service")));
+
+    callCredentials.applyRequestMetadata(method,
+        Attributes.newBuilder()
+            .setAll(attrs)
+            .set(CallCredentials.ATTR_AUTHORITY, "example.com:123")
+            .build(),
+        executor, applier);
+    verify(credentials).getRequestMetadata(eq(new URI("https://example.com:123/a.service")));
+  }
+
+  @Test
+  public void serviceAccountToJwt() throws Exception {
+    KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
+    @SuppressWarnings("deprecation")
+    ServiceAccountCredentials credentials = new ServiceAccountCredentials(
+        null, "email@example.com", pair.getPrivate(), null, null) {
+      @Override
+      public AccessToken refreshAccessToken() {
+        throw new AssertionError();
+      }
+    };
+
+    GoogleAuthLibraryCallCredentials callCredentials =
+        new GoogleAuthLibraryCallCredentials(credentials);
+    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+    assertEquals(0, runPendingRunnables());
+
+    verify(applier).apply(headersCaptor.capture());
+    Metadata headers = headersCaptor.getValue();
+    String[] authorization = Iterables.toArray(headers.getAll(AUTHORIZATION), String.class);
+    assertEquals(1, authorization.length);
+    assertTrue(authorization[0], authorization[0].startsWith("Bearer "));
+    // JWT is reasonably long. Normal tokens aren't.
+    assertTrue(authorization[0], authorization[0].length() > 300);
+  }
+
+  @Test
+  public void serviceAccountWithScopeNotToJwt() throws Exception {
+    final AccessToken token = new AccessToken("allyourbase", new Date(Long.MAX_VALUE));
+    KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
+    @SuppressWarnings("deprecation")
+    ServiceAccountCredentials credentials = new ServiceAccountCredentials(
+        null, "email@example.com", pair.getPrivate(), null, Arrays.asList("somescope")) {
+      @Override
+      public AccessToken refreshAccessToken() {
+        return token;
+      }
+    };
+
+    GoogleAuthLibraryCallCredentials callCredentials =
+        new GoogleAuthLibraryCallCredentials(credentials);
+    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+    assertEquals(1, runPendingRunnables());
+
+    verify(applier).apply(headersCaptor.capture());
+    Metadata headers = headersCaptor.getValue();
+    Iterable<String> authorization = headers.getAll(AUTHORIZATION);
+    assertArrayEquals(new String[]{"Bearer allyourbase"},
+        Iterables.toArray(authorization, String.class));
+  }
+
+  @Test
+  public void oauthClassesNotInClassPath() throws Exception {
+    ListMultimap<String, String> values = LinkedListMultimap.create();
+    values.put("Authorization", "token1");
+    when(credentials.getRequestMetadata(eq(expectedUri))).thenReturn(Multimaps.asMap(values));
+
+    assertNull(GoogleAuthLibraryCallCredentials.createJwtHelperOrNull(null));
+    GoogleAuthLibraryCallCredentials callCredentials =
+        new GoogleAuthLibraryCallCredentials(credentials, null);
+    callCredentials.applyRequestMetadata(method, attrs, executor, applier);
+
+    verify(credentials).getRequestMetadata(eq(expectedUri));
+    verify(applier).apply(headersCaptor.capture());
+    Metadata headers = headersCaptor.getValue();
+    Iterable<String> authorization = headers.getAll(AUTHORIZATION);
+    assertArrayEquals(new String[]{"token1"},
+        Iterables.toArray(authorization, String.class));
+  }
+
+  private int runPendingRunnables() {
+    ArrayList<Runnable> savedPendingRunnables = pendingRunnables;
+    pendingRunnables = new ArrayList<>();
+    for (Runnable r : savedPendingRunnables) {
+      r.run();
+    }
+    return savedPendingRunnables.size();
+  }
+}
diff --git a/benchmarks/README.md b/benchmarks/README.md
new file mode 100644
index 0000000..edc2d4d
--- /dev/null
+++ b/benchmarks/README.md
@@ -0,0 +1,41 @@
+grpc Benchmarks
+==============================================
+
+## QPS Benchmark
+
+The "Queries Per Second Benchmark" allows you to get a quick overview of the throughput and latency characteristics of grpc.
+
+To build the benchmark type
+
+```
+$ ./gradlew :grpc-benchmarks:installDist
+```
+
+from the grpc-java directory.
+
+You can now find the client and the server executables in `benchmarks/build/install/grpc-benchmarks/bin`.
+
+The `C++` counterpart can be found at https://github.com/grpc/grpc/tree/master/test/cpp/qps
+
+## Visualizing the Latency Distribution
+
+The QPS client comes with the option `--save_histogram=FILE`, if set it serializes the histogram to `FILE` which can then be used with a plotter to visualize the latency distribution. The histogram is stored in the file format of [HdrHistogram](http://hdrhistogram.org/). That way it can be plotted very easily using a browser based tool like http://hdrhistogram.github.io/HdrHistogram/plotFiles.html. Simply upload the generated file and it will generate a beautiful graph for you. It also allows you to plot two or more histograms on the same surface in order two easily compare latency distributions.
+
+## JVM Options
+
+When running a benchmark it's often useful to adjust some JVM options to improve performance and to gain some insights into what's happening. Passing JVM options to the QPS server and client is as easy as setting the `JAVA_OPTS` environment variables. Below are some options that I find very useful:
+ - `-Xms` gives a lower bound on the heap to allocate and `-Xmx` gives an upper bound. If your program uses more than what's specified in `-Xmx` the JVM will exit with an `OutOfMemoryError`. When setting those always set `Xms` and `Xmx` to the same value. The reason for this is that the young and old generation are sized according to the total available heap space. So if the total heap gets resized, they will also have to be resized and this will then trigger a full GC.
+ - `-verbose:gc` prints some basic information about garbage collection. It will log to stdout whenever a GC happend and will tell you about the kind of GC, pause time and memory compaction.
+ - `-XX:+PrintGCDetails` prints out very detailed GC and heap usage information before the program terminates.
+ - `-XX:-HeapDumpOnOutOfMemoryError` and `-XX:HeapDumpPath=path` when you are pushing the JVM hard it sometimes happens that it will crash due to the lack of available heap space. This option will allow you to dive into the details of why it happened. The heap dump can be viewed with e.g. the [Eclipse Memory Analyzer](https://eclipse.org/mat/).
+ - `-XX:+PrintCompilation` will give you a detailed overview of what gets compiled, when it gets compiled, by which HotSpot compiler it gets compiled and such. It's a lot of output. I usually just redirect it to file and look at it with `less` and `grep`.
+ - `-XX:+PrintInlining` will give you a detailed overview of what gets inlined and why some methods didn't get inlined. The output is very verbose and like `-XX:+PrintCompilation` and useful to look at after some major changes or when a drop in performance occurs.
+ - It sometimes happens that a benchmark just doesn't make any progress, that is no bytes are transferred over the network, there is hardly any CPU utilization and low memory usage but the benchmark is still running. In that case it's useful to get a thread dump and see what's going on. HotSpot ships with a tool called `jps` and `jstack`. `jps` tells you the process id of all running JVMs on the machine, which you can then pass to `jstack` and it will print a thread dump of this JVM.
+ - Taking a heap dump of a running JVM is similarly straightforward. First get the process id with `jps` and then use `jmap` to take the heap dump. You will almost always want to run it with `-dump:live` in order to only dump live objects. If possible, try to size the heap of your JVM (`-Xmx`) as small as possible in order to also keep the heap dump small. Large heap dumps are very painful and slow to analyze.
+
+## Profiling
+
+Newer JVMs come with a built-in profiler called `Java Flight Recorder`. It's an excellent profiler and it can be used to start a recording directly on the command line,  from within `Java Mission Control` or
+with jcmd.
+
+A good introduction on how it works and how to use it are http://hirt.se/blog/?p=364 and http://hirt.se/blog/?p=370.
diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle
new file mode 100644
index 0000000..809f34e
--- /dev/null
+++ b/benchmarks/build.gradle
@@ -0,0 +1,96 @@
+buildscript {
+    repositories {
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    }
+    dependencies { classpath libraries.protobuf_plugin }
+}
+
+apply plugin: 'application'
+
+description = "grpc Benchmarks"
+
+startScripts.enabled = false
+run.enabled = false
+
+jmh {
+    jvmArgs = "-server -Xms2g -Xmx2g"
+    // Workaround
+    // https://github.com/melix/jmh-gradle-plugin/issues/97#issuecomment-316664026
+    includeTests = true
+}
+
+dependencies {
+    compile project(':grpc-core'),
+            project(':grpc-netty'),
+            project(':grpc-okhttp'),
+            project(':grpc-stub'),
+            project(':grpc-protobuf'),
+            project(':grpc-testing'),
+            libraries.junit,
+            libraries.mockito,
+            libraries.hdrhistogram,
+            libraries.netty_tcnative,
+            libraries.netty_epoll,
+            libraries.math
+    compileOnly libraries.javax_annotation
+}
+
+compileJava {
+    // The Control.Void protobuf clashes
+    options.compilerArgs += ["-Xep:JavaLangClash:OFF"]
+}
+
+configureProtoCompilation()
+
+def vmArgs = [
+    "-server",
+    "-Xms2g",
+    "-Xmx2g",
+    "-XX:+PrintGCDetails"
+]
+
+task qps_client(type: CreateStartScripts) {
+    mainClassName = "io.grpc.benchmarks.qps.AsyncClient"
+    applicationName = "qps_client"
+    defaultJvmOpts = [
+        "-javaagent:" + configurations.alpnagent.asPath
+    ].plus(vmArgs)
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+task openloop_client(type: CreateStartScripts) {
+    mainClassName = "io.grpc.benchmarks.qps.OpenLoopClient"
+    applicationName = "openloop_client"
+    defaultJvmOpts = [
+        "-javaagent:" + configurations.alpnagent.asPath
+    ].plus(vmArgs)
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+task qps_server(type: CreateStartScripts) {
+    mainClassName = "io.grpc.benchmarks.qps.AsyncServer"
+    applicationName = "qps_server"
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+task benchmark_worker(type: CreateStartScripts) {
+    mainClassName = "io.grpc.benchmarks.driver.LoadWorker"
+    applicationName = "benchmark_worker"
+    defaultJvmOpts = [
+        "-javaagent:" + configurations.alpnagent.asPath
+    ].plus(vmArgs)
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+applicationDistribution.into("bin") {
+    from(qps_client)
+    from(openloop_client)
+    from(qps_server)
+    from(benchmark_worker)
+    fileMode = 0755
+}
diff --git a/benchmarks/src/generated/main/grpc/io/grpc/benchmarks/proto/BenchmarkServiceGrpc.java b/benchmarks/src/generated/main/grpc/io/grpc/benchmarks/proto/BenchmarkServiceGrpc.java
new file mode 100644
index 0000000..6a9da0e
--- /dev/null
+++ b/benchmarks/src/generated/main/grpc/io/grpc/benchmarks/proto/BenchmarkServiceGrpc.java
@@ -0,0 +1,579 @@
+package io.grpc.benchmarks.proto;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: grpc/testing/services.proto")
+public final class BenchmarkServiceGrpc {
+
+  private BenchmarkServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.BenchmarkService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest,
+      io.grpc.benchmarks.proto.Messages.SimpleResponse> getUnaryCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "UnaryCall",
+      requestType = io.grpc.benchmarks.proto.Messages.SimpleRequest.class,
+      responseType = io.grpc.benchmarks.proto.Messages.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest,
+      io.grpc.benchmarks.proto.Messages.SimpleResponse> getUnaryCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest, io.grpc.benchmarks.proto.Messages.SimpleResponse> getUnaryCallMethod;
+    if ((getUnaryCallMethod = BenchmarkServiceGrpc.getUnaryCallMethod) == null) {
+      synchronized (BenchmarkServiceGrpc.class) {
+        if ((getUnaryCallMethod = BenchmarkServiceGrpc.getUnaryCallMethod) == null) {
+          BenchmarkServiceGrpc.getUnaryCallMethod = getUnaryCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.benchmarks.proto.Messages.SimpleRequest, io.grpc.benchmarks.proto.Messages.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.BenchmarkService", "UnaryCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Messages.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Messages.SimpleResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new BenchmarkServiceMethodDescriptorSupplier("UnaryCall"))
+                  .build();
+          }
+        }
+     }
+     return getUnaryCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest,
+      io.grpc.benchmarks.proto.Messages.SimpleResponse> getStreamingCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "StreamingCall",
+      requestType = io.grpc.benchmarks.proto.Messages.SimpleRequest.class,
+      responseType = io.grpc.benchmarks.proto.Messages.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest,
+      io.grpc.benchmarks.proto.Messages.SimpleResponse> getStreamingCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest, io.grpc.benchmarks.proto.Messages.SimpleResponse> getStreamingCallMethod;
+    if ((getStreamingCallMethod = BenchmarkServiceGrpc.getStreamingCallMethod) == null) {
+      synchronized (BenchmarkServiceGrpc.class) {
+        if ((getStreamingCallMethod = BenchmarkServiceGrpc.getStreamingCallMethod) == null) {
+          BenchmarkServiceGrpc.getStreamingCallMethod = getStreamingCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.benchmarks.proto.Messages.SimpleRequest, io.grpc.benchmarks.proto.Messages.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.BenchmarkService", "StreamingCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Messages.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Messages.SimpleResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new BenchmarkServiceMethodDescriptorSupplier("StreamingCall"))
+                  .build();
+          }
+        }
+     }
+     return getStreamingCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest,
+      io.grpc.benchmarks.proto.Messages.SimpleResponse> getStreamingFromClientMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "StreamingFromClient",
+      requestType = io.grpc.benchmarks.proto.Messages.SimpleRequest.class,
+      responseType = io.grpc.benchmarks.proto.Messages.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest,
+      io.grpc.benchmarks.proto.Messages.SimpleResponse> getStreamingFromClientMethod() {
+    io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest, io.grpc.benchmarks.proto.Messages.SimpleResponse> getStreamingFromClientMethod;
+    if ((getStreamingFromClientMethod = BenchmarkServiceGrpc.getStreamingFromClientMethod) == null) {
+      synchronized (BenchmarkServiceGrpc.class) {
+        if ((getStreamingFromClientMethod = BenchmarkServiceGrpc.getStreamingFromClientMethod) == null) {
+          BenchmarkServiceGrpc.getStreamingFromClientMethod = getStreamingFromClientMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.benchmarks.proto.Messages.SimpleRequest, io.grpc.benchmarks.proto.Messages.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.BenchmarkService", "StreamingFromClient"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Messages.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Messages.SimpleResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new BenchmarkServiceMethodDescriptorSupplier("StreamingFromClient"))
+                  .build();
+          }
+        }
+     }
+     return getStreamingFromClientMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest,
+      io.grpc.benchmarks.proto.Messages.SimpleResponse> getStreamingFromServerMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "StreamingFromServer",
+      requestType = io.grpc.benchmarks.proto.Messages.SimpleRequest.class,
+      responseType = io.grpc.benchmarks.proto.Messages.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest,
+      io.grpc.benchmarks.proto.Messages.SimpleResponse> getStreamingFromServerMethod() {
+    io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest, io.grpc.benchmarks.proto.Messages.SimpleResponse> getStreamingFromServerMethod;
+    if ((getStreamingFromServerMethod = BenchmarkServiceGrpc.getStreamingFromServerMethod) == null) {
+      synchronized (BenchmarkServiceGrpc.class) {
+        if ((getStreamingFromServerMethod = BenchmarkServiceGrpc.getStreamingFromServerMethod) == null) {
+          BenchmarkServiceGrpc.getStreamingFromServerMethod = getStreamingFromServerMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.benchmarks.proto.Messages.SimpleRequest, io.grpc.benchmarks.proto.Messages.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.BenchmarkService", "StreamingFromServer"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Messages.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Messages.SimpleResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new BenchmarkServiceMethodDescriptorSupplier("StreamingFromServer"))
+                  .build();
+          }
+        }
+     }
+     return getStreamingFromServerMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest,
+      io.grpc.benchmarks.proto.Messages.SimpleResponse> getStreamingBothWaysMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "StreamingBothWays",
+      requestType = io.grpc.benchmarks.proto.Messages.SimpleRequest.class,
+      responseType = io.grpc.benchmarks.proto.Messages.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest,
+      io.grpc.benchmarks.proto.Messages.SimpleResponse> getStreamingBothWaysMethod() {
+    io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Messages.SimpleRequest, io.grpc.benchmarks.proto.Messages.SimpleResponse> getStreamingBothWaysMethod;
+    if ((getStreamingBothWaysMethod = BenchmarkServiceGrpc.getStreamingBothWaysMethod) == null) {
+      synchronized (BenchmarkServiceGrpc.class) {
+        if ((getStreamingBothWaysMethod = BenchmarkServiceGrpc.getStreamingBothWaysMethod) == null) {
+          BenchmarkServiceGrpc.getStreamingBothWaysMethod = getStreamingBothWaysMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.benchmarks.proto.Messages.SimpleRequest, io.grpc.benchmarks.proto.Messages.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.BenchmarkService", "StreamingBothWays"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Messages.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Messages.SimpleResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new BenchmarkServiceMethodDescriptorSupplier("StreamingBothWays"))
+                  .build();
+          }
+        }
+     }
+     return getStreamingBothWaysMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static BenchmarkServiceStub newStub(io.grpc.Channel channel) {
+    return new BenchmarkServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static BenchmarkServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new BenchmarkServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static BenchmarkServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new BenchmarkServiceFutureStub(channel);
+  }
+
+  /**
+   */
+  public static abstract class BenchmarkServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public void unaryCall(io.grpc.benchmarks.proto.Messages.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getUnaryCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Repeated sequence of one request followed by one response.
+     * Should be called streaming ping-pong
+     * The server returns the client payload as-is on each response
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleRequest> streamingCall(
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getStreamingCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Single-sided unbounded streaming from client to server
+     * The server returns the client payload as-is once the client does WritesDone
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleRequest> streamingFromClient(
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getStreamingFromClientMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Single-sided unbounded streaming from server to client
+     * The server repeatedly returns the client payload as-is
+     * </pre>
+     */
+    public void streamingFromServer(io.grpc.benchmarks.proto.Messages.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getStreamingFromServerMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Two-sided unbounded streaming between server to client
+     * Both sides send the content of their own choice to the other
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleRequest> streamingBothWays(
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getStreamingBothWaysMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getUnaryCallMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.benchmarks.proto.Messages.SimpleRequest,
+                io.grpc.benchmarks.proto.Messages.SimpleResponse>(
+                  this, METHODID_UNARY_CALL)))
+          .addMethod(
+            getStreamingCallMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.benchmarks.proto.Messages.SimpleRequest,
+                io.grpc.benchmarks.proto.Messages.SimpleResponse>(
+                  this, METHODID_STREAMING_CALL)))
+          .addMethod(
+            getStreamingFromClientMethod(),
+            asyncClientStreamingCall(
+              new MethodHandlers<
+                io.grpc.benchmarks.proto.Messages.SimpleRequest,
+                io.grpc.benchmarks.proto.Messages.SimpleResponse>(
+                  this, METHODID_STREAMING_FROM_CLIENT)))
+          .addMethod(
+            getStreamingFromServerMethod(),
+            asyncServerStreamingCall(
+              new MethodHandlers<
+                io.grpc.benchmarks.proto.Messages.SimpleRequest,
+                io.grpc.benchmarks.proto.Messages.SimpleResponse>(
+                  this, METHODID_STREAMING_FROM_SERVER)))
+          .addMethod(
+            getStreamingBothWaysMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.benchmarks.proto.Messages.SimpleRequest,
+                io.grpc.benchmarks.proto.Messages.SimpleResponse>(
+                  this, METHODID_STREAMING_BOTH_WAYS)))
+          .build();
+    }
+  }
+
+  /**
+   */
+  public static final class BenchmarkServiceStub extends io.grpc.stub.AbstractStub<BenchmarkServiceStub> {
+    private BenchmarkServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private BenchmarkServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected BenchmarkServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new BenchmarkServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public void unaryCall(io.grpc.benchmarks.proto.Messages.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getUnaryCallMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Repeated sequence of one request followed by one response.
+     * Should be called streaming ping-pong
+     * The server returns the client payload as-is on each response
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleRequest> streamingCall(
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getStreamingCallMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Single-sided unbounded streaming from client to server
+     * The server returns the client payload as-is once the client does WritesDone
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleRequest> streamingFromClient(
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse> responseObserver) {
+      return asyncClientStreamingCall(
+          getChannel().newCall(getStreamingFromClientMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Single-sided unbounded streaming from server to client
+     * The server repeatedly returns the client payload as-is
+     * </pre>
+     */
+    public void streamingFromServer(io.grpc.benchmarks.proto.Messages.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse> responseObserver) {
+      asyncServerStreamingCall(
+          getChannel().newCall(getStreamingFromServerMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Two-sided unbounded streaming between server to client
+     * Both sides send the content of their own choice to the other
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleRequest> streamingBothWays(
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getStreamingBothWaysMethod(), getCallOptions()), responseObserver);
+    }
+  }
+
+  /**
+   */
+  public static final class BenchmarkServiceBlockingStub extends io.grpc.stub.AbstractStub<BenchmarkServiceBlockingStub> {
+    private BenchmarkServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private BenchmarkServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected BenchmarkServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new BenchmarkServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public io.grpc.benchmarks.proto.Messages.SimpleResponse unaryCall(io.grpc.benchmarks.proto.Messages.SimpleRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getUnaryCallMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * Single-sided unbounded streaming from server to client
+     * The server repeatedly returns the client payload as-is
+     * </pre>
+     */
+    public java.util.Iterator<io.grpc.benchmarks.proto.Messages.SimpleResponse> streamingFromServer(
+        io.grpc.benchmarks.proto.Messages.SimpleRequest request) {
+      return blockingServerStreamingCall(
+          getChannel(), getStreamingFromServerMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   */
+  public static final class BenchmarkServiceFutureStub extends io.grpc.stub.AbstractStub<BenchmarkServiceFutureStub> {
+    private BenchmarkServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private BenchmarkServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected BenchmarkServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new BenchmarkServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.benchmarks.proto.Messages.SimpleResponse> unaryCall(
+        io.grpc.benchmarks.proto.Messages.SimpleRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getUnaryCallMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_UNARY_CALL = 0;
+  private static final int METHODID_STREAMING_FROM_SERVER = 1;
+  private static final int METHODID_STREAMING_CALL = 2;
+  private static final int METHODID_STREAMING_FROM_CLIENT = 3;
+  private static final int METHODID_STREAMING_BOTH_WAYS = 4;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final BenchmarkServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(BenchmarkServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_UNARY_CALL:
+          serviceImpl.unaryCall((io.grpc.benchmarks.proto.Messages.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse>) responseObserver);
+          break;
+        case METHODID_STREAMING_FROM_SERVER:
+          serviceImpl.streamingFromServer((io.grpc.benchmarks.proto.Messages.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_STREAMING_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.streamingCall(
+              (io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse>) responseObserver);
+        case METHODID_STREAMING_FROM_CLIENT:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.streamingFromClient(
+              (io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse>) responseObserver);
+        case METHODID_STREAMING_BOTH_WAYS:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.streamingBothWays(
+              (io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Messages.SimpleResponse>) responseObserver);
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class BenchmarkServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    BenchmarkServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.benchmarks.proto.Services.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("BenchmarkService");
+    }
+  }
+
+  private static final class BenchmarkServiceFileDescriptorSupplier
+      extends BenchmarkServiceBaseDescriptorSupplier {
+    BenchmarkServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class BenchmarkServiceMethodDescriptorSupplier
+      extends BenchmarkServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    BenchmarkServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (BenchmarkServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new BenchmarkServiceFileDescriptorSupplier())
+              .addMethod(getUnaryCallMethod())
+              .addMethod(getStreamingCallMethod())
+              .addMethod(getStreamingFromClientMethod())
+              .addMethod(getStreamingFromServerMethod())
+              .addMethod(getStreamingBothWaysMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/benchmarks/src/generated/main/grpc/io/grpc/benchmarks/proto/ReportQpsScenarioServiceGrpc.java b/benchmarks/src/generated/main/grpc/io/grpc/benchmarks/proto/ReportQpsScenarioServiceGrpc.java
new file mode 100644
index 0000000..4c03b37
--- /dev/null
+++ b/benchmarks/src/generated/main/grpc/io/grpc/benchmarks/proto/ReportQpsScenarioServiceGrpc.java
@@ -0,0 +1,292 @@
+package io.grpc.benchmarks.proto;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: grpc/testing/services.proto")
+public final class ReportQpsScenarioServiceGrpc {
+
+  private ReportQpsScenarioServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.ReportQpsScenarioService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.ScenarioResult,
+      io.grpc.benchmarks.proto.Control.Void> getReportScenarioMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "ReportScenario",
+      requestType = io.grpc.benchmarks.proto.Control.ScenarioResult.class,
+      responseType = io.grpc.benchmarks.proto.Control.Void.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.ScenarioResult,
+      io.grpc.benchmarks.proto.Control.Void> getReportScenarioMethod() {
+    io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.ScenarioResult, io.grpc.benchmarks.proto.Control.Void> getReportScenarioMethod;
+    if ((getReportScenarioMethod = ReportQpsScenarioServiceGrpc.getReportScenarioMethod) == null) {
+      synchronized (ReportQpsScenarioServiceGrpc.class) {
+        if ((getReportScenarioMethod = ReportQpsScenarioServiceGrpc.getReportScenarioMethod) == null) {
+          ReportQpsScenarioServiceGrpc.getReportScenarioMethod = getReportScenarioMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.benchmarks.proto.Control.ScenarioResult, io.grpc.benchmarks.proto.Control.Void>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.ReportQpsScenarioService", "ReportScenario"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Control.ScenarioResult.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Control.Void.getDefaultInstance()))
+                  .setSchemaDescriptor(new ReportQpsScenarioServiceMethodDescriptorSupplier("ReportScenario"))
+                  .build();
+          }
+        }
+     }
+     return getReportScenarioMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static ReportQpsScenarioServiceStub newStub(io.grpc.Channel channel) {
+    return new ReportQpsScenarioServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static ReportQpsScenarioServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new ReportQpsScenarioServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static ReportQpsScenarioServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new ReportQpsScenarioServiceFutureStub(channel);
+  }
+
+  /**
+   */
+  public static abstract class ReportQpsScenarioServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * Report results of a QPS test benchmark scenario.
+     * </pre>
+     */
+    public void reportScenario(io.grpc.benchmarks.proto.Control.ScenarioResult request,
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.Void> responseObserver) {
+      asyncUnimplementedUnaryCall(getReportScenarioMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getReportScenarioMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.benchmarks.proto.Control.ScenarioResult,
+                io.grpc.benchmarks.proto.Control.Void>(
+                  this, METHODID_REPORT_SCENARIO)))
+          .build();
+    }
+  }
+
+  /**
+   */
+  public static final class ReportQpsScenarioServiceStub extends io.grpc.stub.AbstractStub<ReportQpsScenarioServiceStub> {
+    private ReportQpsScenarioServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ReportQpsScenarioServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ReportQpsScenarioServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ReportQpsScenarioServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Report results of a QPS test benchmark scenario.
+     * </pre>
+     */
+    public void reportScenario(io.grpc.benchmarks.proto.Control.ScenarioResult request,
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.Void> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getReportScenarioMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   */
+  public static final class ReportQpsScenarioServiceBlockingStub extends io.grpc.stub.AbstractStub<ReportQpsScenarioServiceBlockingStub> {
+    private ReportQpsScenarioServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ReportQpsScenarioServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ReportQpsScenarioServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ReportQpsScenarioServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Report results of a QPS test benchmark scenario.
+     * </pre>
+     */
+    public io.grpc.benchmarks.proto.Control.Void reportScenario(io.grpc.benchmarks.proto.Control.ScenarioResult request) {
+      return blockingUnaryCall(
+          getChannel(), getReportScenarioMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   */
+  public static final class ReportQpsScenarioServiceFutureStub extends io.grpc.stub.AbstractStub<ReportQpsScenarioServiceFutureStub> {
+    private ReportQpsScenarioServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ReportQpsScenarioServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ReportQpsScenarioServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ReportQpsScenarioServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Report results of a QPS test benchmark scenario.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.benchmarks.proto.Control.Void> reportScenario(
+        io.grpc.benchmarks.proto.Control.ScenarioResult request) {
+      return futureUnaryCall(
+          getChannel().newCall(getReportScenarioMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_REPORT_SCENARIO = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final ReportQpsScenarioServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(ReportQpsScenarioServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_REPORT_SCENARIO:
+          serviceImpl.reportScenario((io.grpc.benchmarks.proto.Control.ScenarioResult) request,
+              (io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.Void>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class ReportQpsScenarioServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    ReportQpsScenarioServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.benchmarks.proto.Services.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("ReportQpsScenarioService");
+    }
+  }
+
+  private static final class ReportQpsScenarioServiceFileDescriptorSupplier
+      extends ReportQpsScenarioServiceBaseDescriptorSupplier {
+    ReportQpsScenarioServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class ReportQpsScenarioServiceMethodDescriptorSupplier
+      extends ReportQpsScenarioServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    ReportQpsScenarioServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (ReportQpsScenarioServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new ReportQpsScenarioServiceFileDescriptorSupplier())
+              .addMethod(getReportScenarioMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/benchmarks/src/generated/main/grpc/io/grpc/benchmarks/proto/WorkerServiceGrpc.java b/benchmarks/src/generated/main/grpc/io/grpc/benchmarks/proto/WorkerServiceGrpc.java
new file mode 100644
index 0000000..0213533
--- /dev/null
+++ b/benchmarks/src/generated/main/grpc/io/grpc/benchmarks/proto/WorkerServiceGrpc.java
@@ -0,0 +1,529 @@
+package io.grpc.benchmarks.proto;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: grpc/testing/services.proto")
+public final class WorkerServiceGrpc {
+
+  private WorkerServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.WorkerService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.ServerArgs,
+      io.grpc.benchmarks.proto.Control.ServerStatus> getRunServerMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "RunServer",
+      requestType = io.grpc.benchmarks.proto.Control.ServerArgs.class,
+      responseType = io.grpc.benchmarks.proto.Control.ServerStatus.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.ServerArgs,
+      io.grpc.benchmarks.proto.Control.ServerStatus> getRunServerMethod() {
+    io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.ServerArgs, io.grpc.benchmarks.proto.Control.ServerStatus> getRunServerMethod;
+    if ((getRunServerMethod = WorkerServiceGrpc.getRunServerMethod) == null) {
+      synchronized (WorkerServiceGrpc.class) {
+        if ((getRunServerMethod = WorkerServiceGrpc.getRunServerMethod) == null) {
+          WorkerServiceGrpc.getRunServerMethod = getRunServerMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.benchmarks.proto.Control.ServerArgs, io.grpc.benchmarks.proto.Control.ServerStatus>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.WorkerService", "RunServer"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Control.ServerArgs.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Control.ServerStatus.getDefaultInstance()))
+                  .setSchemaDescriptor(new WorkerServiceMethodDescriptorSupplier("RunServer"))
+                  .build();
+          }
+        }
+     }
+     return getRunServerMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.ClientArgs,
+      io.grpc.benchmarks.proto.Control.ClientStatus> getRunClientMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "RunClient",
+      requestType = io.grpc.benchmarks.proto.Control.ClientArgs.class,
+      responseType = io.grpc.benchmarks.proto.Control.ClientStatus.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.ClientArgs,
+      io.grpc.benchmarks.proto.Control.ClientStatus> getRunClientMethod() {
+    io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.ClientArgs, io.grpc.benchmarks.proto.Control.ClientStatus> getRunClientMethod;
+    if ((getRunClientMethod = WorkerServiceGrpc.getRunClientMethod) == null) {
+      synchronized (WorkerServiceGrpc.class) {
+        if ((getRunClientMethod = WorkerServiceGrpc.getRunClientMethod) == null) {
+          WorkerServiceGrpc.getRunClientMethod = getRunClientMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.benchmarks.proto.Control.ClientArgs, io.grpc.benchmarks.proto.Control.ClientStatus>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.WorkerService", "RunClient"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Control.ClientArgs.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Control.ClientStatus.getDefaultInstance()))
+                  .setSchemaDescriptor(new WorkerServiceMethodDescriptorSupplier("RunClient"))
+                  .build();
+          }
+        }
+     }
+     return getRunClientMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.CoreRequest,
+      io.grpc.benchmarks.proto.Control.CoreResponse> getCoreCountMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "CoreCount",
+      requestType = io.grpc.benchmarks.proto.Control.CoreRequest.class,
+      responseType = io.grpc.benchmarks.proto.Control.CoreResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.CoreRequest,
+      io.grpc.benchmarks.proto.Control.CoreResponse> getCoreCountMethod() {
+    io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.CoreRequest, io.grpc.benchmarks.proto.Control.CoreResponse> getCoreCountMethod;
+    if ((getCoreCountMethod = WorkerServiceGrpc.getCoreCountMethod) == null) {
+      synchronized (WorkerServiceGrpc.class) {
+        if ((getCoreCountMethod = WorkerServiceGrpc.getCoreCountMethod) == null) {
+          WorkerServiceGrpc.getCoreCountMethod = getCoreCountMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.benchmarks.proto.Control.CoreRequest, io.grpc.benchmarks.proto.Control.CoreResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.WorkerService", "CoreCount"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Control.CoreRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Control.CoreResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new WorkerServiceMethodDescriptorSupplier("CoreCount"))
+                  .build();
+          }
+        }
+     }
+     return getCoreCountMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.Void,
+      io.grpc.benchmarks.proto.Control.Void> getQuitWorkerMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "QuitWorker",
+      requestType = io.grpc.benchmarks.proto.Control.Void.class,
+      responseType = io.grpc.benchmarks.proto.Control.Void.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.Void,
+      io.grpc.benchmarks.proto.Control.Void> getQuitWorkerMethod() {
+    io.grpc.MethodDescriptor<io.grpc.benchmarks.proto.Control.Void, io.grpc.benchmarks.proto.Control.Void> getQuitWorkerMethod;
+    if ((getQuitWorkerMethod = WorkerServiceGrpc.getQuitWorkerMethod) == null) {
+      synchronized (WorkerServiceGrpc.class) {
+        if ((getQuitWorkerMethod = WorkerServiceGrpc.getQuitWorkerMethod) == null) {
+          WorkerServiceGrpc.getQuitWorkerMethod = getQuitWorkerMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.benchmarks.proto.Control.Void, io.grpc.benchmarks.proto.Control.Void>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.WorkerService", "QuitWorker"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Control.Void.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.benchmarks.proto.Control.Void.getDefaultInstance()))
+                  .setSchemaDescriptor(new WorkerServiceMethodDescriptorSupplier("QuitWorker"))
+                  .build();
+          }
+        }
+     }
+     return getQuitWorkerMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static WorkerServiceStub newStub(io.grpc.Channel channel) {
+    return new WorkerServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static WorkerServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new WorkerServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static WorkerServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new WorkerServiceFutureStub(channel);
+  }
+
+  /**
+   */
+  public static abstract class WorkerServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * Start server with specified workload.
+     * First request sent specifies the ServerConfig followed by ServerStatus
+     * response. After that, a "Mark" can be sent anytime to request the latest
+     * stats. Closing the stream will initiate shutdown of the test server
+     * and once the shutdown has finished, the OK status is sent to terminate
+     * this RPC.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.ServerArgs> runServer(
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.ServerStatus> responseObserver) {
+      return asyncUnimplementedStreamingCall(getRunServerMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Start client with specified workload.
+     * First request sent specifies the ClientConfig followed by ClientStatus
+     * response. After that, a "Mark" can be sent anytime to request the latest
+     * stats. Closing the stream will initiate shutdown of the test client
+     * and once the shutdown has finished, the OK status is sent to terminate
+     * this RPC.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.ClientArgs> runClient(
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.ClientStatus> responseObserver) {
+      return asyncUnimplementedStreamingCall(getRunClientMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Just return the core count - unary call
+     * </pre>
+     */
+    public void coreCount(io.grpc.benchmarks.proto.Control.CoreRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.CoreResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getCoreCountMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Quit this worker
+     * </pre>
+     */
+    public void quitWorker(io.grpc.benchmarks.proto.Control.Void request,
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.Void> responseObserver) {
+      asyncUnimplementedUnaryCall(getQuitWorkerMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getRunServerMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.benchmarks.proto.Control.ServerArgs,
+                io.grpc.benchmarks.proto.Control.ServerStatus>(
+                  this, METHODID_RUN_SERVER)))
+          .addMethod(
+            getRunClientMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.benchmarks.proto.Control.ClientArgs,
+                io.grpc.benchmarks.proto.Control.ClientStatus>(
+                  this, METHODID_RUN_CLIENT)))
+          .addMethod(
+            getCoreCountMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.benchmarks.proto.Control.CoreRequest,
+                io.grpc.benchmarks.proto.Control.CoreResponse>(
+                  this, METHODID_CORE_COUNT)))
+          .addMethod(
+            getQuitWorkerMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.benchmarks.proto.Control.Void,
+                io.grpc.benchmarks.proto.Control.Void>(
+                  this, METHODID_QUIT_WORKER)))
+          .build();
+    }
+  }
+
+  /**
+   */
+  public static final class WorkerServiceStub extends io.grpc.stub.AbstractStub<WorkerServiceStub> {
+    private WorkerServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private WorkerServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected WorkerServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new WorkerServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Start server with specified workload.
+     * First request sent specifies the ServerConfig followed by ServerStatus
+     * response. After that, a "Mark" can be sent anytime to request the latest
+     * stats. Closing the stream will initiate shutdown of the test server
+     * and once the shutdown has finished, the OK status is sent to terminate
+     * this RPC.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.ServerArgs> runServer(
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.ServerStatus> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getRunServerMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Start client with specified workload.
+     * First request sent specifies the ClientConfig followed by ClientStatus
+     * response. After that, a "Mark" can be sent anytime to request the latest
+     * stats. Closing the stream will initiate shutdown of the test client
+     * and once the shutdown has finished, the OK status is sent to terminate
+     * this RPC.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.ClientArgs> runClient(
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.ClientStatus> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getRunClientMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Just return the core count - unary call
+     * </pre>
+     */
+    public void coreCount(io.grpc.benchmarks.proto.Control.CoreRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.CoreResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getCoreCountMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Quit this worker
+     * </pre>
+     */
+    public void quitWorker(io.grpc.benchmarks.proto.Control.Void request,
+        io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.Void> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getQuitWorkerMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   */
+  public static final class WorkerServiceBlockingStub extends io.grpc.stub.AbstractStub<WorkerServiceBlockingStub> {
+    private WorkerServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private WorkerServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected WorkerServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new WorkerServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Just return the core count - unary call
+     * </pre>
+     */
+    public io.grpc.benchmarks.proto.Control.CoreResponse coreCount(io.grpc.benchmarks.proto.Control.CoreRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getCoreCountMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * Quit this worker
+     * </pre>
+     */
+    public io.grpc.benchmarks.proto.Control.Void quitWorker(io.grpc.benchmarks.proto.Control.Void request) {
+      return blockingUnaryCall(
+          getChannel(), getQuitWorkerMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   */
+  public static final class WorkerServiceFutureStub extends io.grpc.stub.AbstractStub<WorkerServiceFutureStub> {
+    private WorkerServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private WorkerServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected WorkerServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new WorkerServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Just return the core count - unary call
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.benchmarks.proto.Control.CoreResponse> coreCount(
+        io.grpc.benchmarks.proto.Control.CoreRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getCoreCountMethod(), getCallOptions()), request);
+    }
+
+    /**
+     * <pre>
+     * Quit this worker
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.benchmarks.proto.Control.Void> quitWorker(
+        io.grpc.benchmarks.proto.Control.Void request) {
+      return futureUnaryCall(
+          getChannel().newCall(getQuitWorkerMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_CORE_COUNT = 0;
+  private static final int METHODID_QUIT_WORKER = 1;
+  private static final int METHODID_RUN_SERVER = 2;
+  private static final int METHODID_RUN_CLIENT = 3;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final WorkerServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(WorkerServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_CORE_COUNT:
+          serviceImpl.coreCount((io.grpc.benchmarks.proto.Control.CoreRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.CoreResponse>) responseObserver);
+          break;
+        case METHODID_QUIT_WORKER:
+          serviceImpl.quitWorker((io.grpc.benchmarks.proto.Control.Void) request,
+              (io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.Void>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_RUN_SERVER:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.runServer(
+              (io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.ServerStatus>) responseObserver);
+        case METHODID_RUN_CLIENT:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.runClient(
+              (io.grpc.stub.StreamObserver<io.grpc.benchmarks.proto.Control.ClientStatus>) responseObserver);
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class WorkerServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    WorkerServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.benchmarks.proto.Services.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("WorkerService");
+    }
+  }
+
+  private static final class WorkerServiceFileDescriptorSupplier
+      extends WorkerServiceBaseDescriptorSupplier {
+    WorkerServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class WorkerServiceMethodDescriptorSupplier
+      extends WorkerServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    WorkerServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (WorkerServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new WorkerServiceFileDescriptorSupplier())
+              .addMethod(getRunServerMethod())
+              .addMethod(getRunClientMethod())
+              .addMethod(getCoreCountMethod())
+              .addMethod(getQuitWorkerMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Control.java b/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Control.java
new file mode 100644
index 0000000..de7d131
--- /dev/null
+++ b/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Control.java
@@ -0,0 +1,20424 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/testing/control.proto
+
+package io.grpc.benchmarks.proto;
+
+public final class Control {
+  private Control() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  /**
+   * Protobuf enum {@code grpc.testing.ClientType}
+   */
+  public enum ClientType
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <pre>
+     * Many languages support a basic distinction between using
+     * sync or async client, and this allows the specification
+     * </pre>
+     *
+     * <code>SYNC_CLIENT = 0;</code>
+     */
+    SYNC_CLIENT(0),
+    /**
+     * <code>ASYNC_CLIENT = 1;</code>
+     */
+    ASYNC_CLIENT(1),
+    /**
+     * <pre>
+     * used for some language-specific variants
+     * </pre>
+     *
+     * <code>OTHER_CLIENT = 2;</code>
+     */
+    OTHER_CLIENT(2),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <pre>
+     * Many languages support a basic distinction between using
+     * sync or async client, and this allows the specification
+     * </pre>
+     *
+     * <code>SYNC_CLIENT = 0;</code>
+     */
+    public static final int SYNC_CLIENT_VALUE = 0;
+    /**
+     * <code>ASYNC_CLIENT = 1;</code>
+     */
+    public static final int ASYNC_CLIENT_VALUE = 1;
+    /**
+     * <pre>
+     * used for some language-specific variants
+     * </pre>
+     *
+     * <code>OTHER_CLIENT = 2;</code>
+     */
+    public static final int OTHER_CLIENT_VALUE = 2;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static ClientType valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static ClientType forNumber(int value) {
+      switch (value) {
+        case 0: return SYNC_CLIENT;
+        case 1: return ASYNC_CLIENT;
+        case 2: return OTHER_CLIENT;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<ClientType>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        ClientType> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<ClientType>() {
+            public ClientType findValueByNumber(int number) {
+              return ClientType.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.getDescriptor().getEnumTypes().get(0);
+    }
+
+    private static final ClientType[] VALUES = values();
+
+    public static ClientType valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private ClientType(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.testing.ClientType)
+  }
+
+  /**
+   * Protobuf enum {@code grpc.testing.ServerType}
+   */
+  public enum ServerType
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>SYNC_SERVER = 0;</code>
+     */
+    SYNC_SERVER(0),
+    /**
+     * <code>ASYNC_SERVER = 1;</code>
+     */
+    ASYNC_SERVER(1),
+    /**
+     * <code>ASYNC_GENERIC_SERVER = 2;</code>
+     */
+    ASYNC_GENERIC_SERVER(2),
+    /**
+     * <pre>
+     * used for some language-specific variants
+     * </pre>
+     *
+     * <code>OTHER_SERVER = 3;</code>
+     */
+    OTHER_SERVER(3),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <code>SYNC_SERVER = 0;</code>
+     */
+    public static final int SYNC_SERVER_VALUE = 0;
+    /**
+     * <code>ASYNC_SERVER = 1;</code>
+     */
+    public static final int ASYNC_SERVER_VALUE = 1;
+    /**
+     * <code>ASYNC_GENERIC_SERVER = 2;</code>
+     */
+    public static final int ASYNC_GENERIC_SERVER_VALUE = 2;
+    /**
+     * <pre>
+     * used for some language-specific variants
+     * </pre>
+     *
+     * <code>OTHER_SERVER = 3;</code>
+     */
+    public static final int OTHER_SERVER_VALUE = 3;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static ServerType valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static ServerType forNumber(int value) {
+      switch (value) {
+        case 0: return SYNC_SERVER;
+        case 1: return ASYNC_SERVER;
+        case 2: return ASYNC_GENERIC_SERVER;
+        case 3: return OTHER_SERVER;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<ServerType>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        ServerType> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<ServerType>() {
+            public ServerType findValueByNumber(int number) {
+              return ServerType.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.getDescriptor().getEnumTypes().get(1);
+    }
+
+    private static final ServerType[] VALUES = values();
+
+    public static ServerType valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private ServerType(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.testing.ServerType)
+  }
+
+  /**
+   * Protobuf enum {@code grpc.testing.RpcType}
+   */
+  public enum RpcType
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>UNARY = 0;</code>
+     */
+    UNARY(0),
+    /**
+     * <code>STREAMING = 1;</code>
+     */
+    STREAMING(1),
+    /**
+     * <code>STREAMING_FROM_CLIENT = 2;</code>
+     */
+    STREAMING_FROM_CLIENT(2),
+    /**
+     * <code>STREAMING_FROM_SERVER = 3;</code>
+     */
+    STREAMING_FROM_SERVER(3),
+    /**
+     * <code>STREAMING_BOTH_WAYS = 4;</code>
+     */
+    STREAMING_BOTH_WAYS(4),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <code>UNARY = 0;</code>
+     */
+    public static final int UNARY_VALUE = 0;
+    /**
+     * <code>STREAMING = 1;</code>
+     */
+    public static final int STREAMING_VALUE = 1;
+    /**
+     * <code>STREAMING_FROM_CLIENT = 2;</code>
+     */
+    public static final int STREAMING_FROM_CLIENT_VALUE = 2;
+    /**
+     * <code>STREAMING_FROM_SERVER = 3;</code>
+     */
+    public static final int STREAMING_FROM_SERVER_VALUE = 3;
+    /**
+     * <code>STREAMING_BOTH_WAYS = 4;</code>
+     */
+    public static final int STREAMING_BOTH_WAYS_VALUE = 4;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static RpcType valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static RpcType forNumber(int value) {
+      switch (value) {
+        case 0: return UNARY;
+        case 1: return STREAMING;
+        case 2: return STREAMING_FROM_CLIENT;
+        case 3: return STREAMING_FROM_SERVER;
+        case 4: return STREAMING_BOTH_WAYS;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<RpcType>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        RpcType> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<RpcType>() {
+            public RpcType findValueByNumber(int number) {
+              return RpcType.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.getDescriptor().getEnumTypes().get(2);
+    }
+
+    private static final RpcType[] VALUES = values();
+
+    public static RpcType valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private RpcType(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.testing.RpcType)
+  }
+
+  public interface PoissonParamsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.PoissonParams)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * The rate of arrivals (a.k.a. lambda parameter of the exp distribution).
+     * </pre>
+     *
+     * <code>double offered_load = 1;</code>
+     */
+    double getOfferedLoad();
+  }
+  /**
+   * <pre>
+   * Parameters of poisson process distribution, which is a good representation
+   * of activity coming in from independent identical stationary sources.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.PoissonParams}
+   */
+  public  static final class PoissonParams extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.PoissonParams)
+      PoissonParamsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use PoissonParams.newBuilder() to construct.
+    private PoissonParams(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private PoissonParams() {
+      offeredLoad_ = 0D;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private PoissonParams(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 9: {
+
+              offeredLoad_ = input.readDouble();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_PoissonParams_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_PoissonParams_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.PoissonParams.class, io.grpc.benchmarks.proto.Control.PoissonParams.Builder.class);
+    }
+
+    public static final int OFFERED_LOAD_FIELD_NUMBER = 1;
+    private double offeredLoad_;
+    /**
+     * <pre>
+     * The rate of arrivals (a.k.a. lambda parameter of the exp distribution).
+     * </pre>
+     *
+     * <code>double offered_load = 1;</code>
+     */
+    public double getOfferedLoad() {
+      return offeredLoad_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (offeredLoad_ != 0D) {
+        output.writeDouble(1, offeredLoad_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (offeredLoad_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(1, offeredLoad_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.PoissonParams)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.PoissonParams other = (io.grpc.benchmarks.proto.Control.PoissonParams) obj;
+
+      boolean result = true;
+      result = result && (
+          java.lang.Double.doubleToLongBits(getOfferedLoad())
+          == java.lang.Double.doubleToLongBits(
+              other.getOfferedLoad()));
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + OFFERED_LOAD_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getOfferedLoad()));
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.PoissonParams parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.PoissonParams parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.PoissonParams parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.PoissonParams parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.PoissonParams parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.PoissonParams parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.PoissonParams parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.PoissonParams parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.PoissonParams parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.PoissonParams parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.PoissonParams parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.PoissonParams parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.PoissonParams prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Parameters of poisson process distribution, which is a good representation
+     * of activity coming in from independent identical stationary sources.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.PoissonParams}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.PoissonParams)
+        io.grpc.benchmarks.proto.Control.PoissonParamsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_PoissonParams_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_PoissonParams_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.PoissonParams.class, io.grpc.benchmarks.proto.Control.PoissonParams.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.PoissonParams.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        offeredLoad_ = 0D;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_PoissonParams_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.PoissonParams getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.PoissonParams.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.PoissonParams build() {
+        io.grpc.benchmarks.proto.Control.PoissonParams result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.PoissonParams buildPartial() {
+        io.grpc.benchmarks.proto.Control.PoissonParams result = new io.grpc.benchmarks.proto.Control.PoissonParams(this);
+        result.offeredLoad_ = offeredLoad_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.PoissonParams) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.PoissonParams)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.PoissonParams other) {
+        if (other == io.grpc.benchmarks.proto.Control.PoissonParams.getDefaultInstance()) return this;
+        if (other.getOfferedLoad() != 0D) {
+          setOfferedLoad(other.getOfferedLoad());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.PoissonParams parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.PoissonParams) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private double offeredLoad_ ;
+      /**
+       * <pre>
+       * The rate of arrivals (a.k.a. lambda parameter of the exp distribution).
+       * </pre>
+       *
+       * <code>double offered_load = 1;</code>
+       */
+      public double getOfferedLoad() {
+        return offeredLoad_;
+      }
+      /**
+       * <pre>
+       * The rate of arrivals (a.k.a. lambda parameter of the exp distribution).
+       * </pre>
+       *
+       * <code>double offered_load = 1;</code>
+       */
+      public Builder setOfferedLoad(double value) {
+        
+        offeredLoad_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The rate of arrivals (a.k.a. lambda parameter of the exp distribution).
+       * </pre>
+       *
+       * <code>double offered_load = 1;</code>
+       */
+      public Builder clearOfferedLoad() {
+        
+        offeredLoad_ = 0D;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.PoissonParams)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.PoissonParams)
+    private static final io.grpc.benchmarks.proto.Control.PoissonParams DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.PoissonParams();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.PoissonParams getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<PoissonParams>
+        PARSER = new com.google.protobuf.AbstractParser<PoissonParams>() {
+      public PoissonParams parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new PoissonParams(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<PoissonParams> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<PoissonParams> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.PoissonParams getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ClosedLoopParamsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ClosedLoopParams)
+      com.google.protobuf.MessageOrBuilder {
+  }
+  /**
+   * <pre>
+   * Once an RPC finishes, immediately start a new one.
+   * No configuration parameters needed.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.ClosedLoopParams}
+   */
+  public  static final class ClosedLoopParams extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ClosedLoopParams)
+      ClosedLoopParamsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ClosedLoopParams.newBuilder() to construct.
+    private ClosedLoopParams(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ClosedLoopParams() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ClosedLoopParams(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClosedLoopParams_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClosedLoopParams_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.ClosedLoopParams.class, io.grpc.benchmarks.proto.Control.ClosedLoopParams.Builder.class);
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.ClosedLoopParams)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.ClosedLoopParams other = (io.grpc.benchmarks.proto.Control.ClosedLoopParams) obj;
+
+      boolean result = true;
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ClosedLoopParams parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClosedLoopParams parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClosedLoopParams parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClosedLoopParams parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClosedLoopParams parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClosedLoopParams parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClosedLoopParams parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClosedLoopParams parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClosedLoopParams parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClosedLoopParams parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClosedLoopParams parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClosedLoopParams parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.ClosedLoopParams prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Once an RPC finishes, immediately start a new one.
+     * No configuration parameters needed.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.ClosedLoopParams}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ClosedLoopParams)
+        io.grpc.benchmarks.proto.Control.ClosedLoopParamsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClosedLoopParams_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClosedLoopParams_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.ClosedLoopParams.class, io.grpc.benchmarks.proto.Control.ClosedLoopParams.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.ClosedLoopParams.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClosedLoopParams_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ClosedLoopParams getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.ClosedLoopParams.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.ClosedLoopParams build() {
+        io.grpc.benchmarks.proto.Control.ClosedLoopParams result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ClosedLoopParams buildPartial() {
+        io.grpc.benchmarks.proto.Control.ClosedLoopParams result = new io.grpc.benchmarks.proto.Control.ClosedLoopParams(this);
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.ClosedLoopParams) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.ClosedLoopParams)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.ClosedLoopParams other) {
+        if (other == io.grpc.benchmarks.proto.Control.ClosedLoopParams.getDefaultInstance()) return this;
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.ClosedLoopParams parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.ClosedLoopParams) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ClosedLoopParams)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ClosedLoopParams)
+    private static final io.grpc.benchmarks.proto.Control.ClosedLoopParams DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.ClosedLoopParams();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ClosedLoopParams getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ClosedLoopParams>
+        PARSER = new com.google.protobuf.AbstractParser<ClosedLoopParams>() {
+      public ClosedLoopParams parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ClosedLoopParams(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ClosedLoopParams> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ClosedLoopParams> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.ClosedLoopParams getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface LoadParamsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.LoadParams)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+     */
+    boolean hasClosedLoop();
+    /**
+     * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ClosedLoopParams getClosedLoop();
+    /**
+     * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ClosedLoopParamsOrBuilder getClosedLoopOrBuilder();
+
+    /**
+     * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+     */
+    boolean hasPoisson();
+    /**
+     * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Control.PoissonParams getPoisson();
+    /**
+     * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Control.PoissonParamsOrBuilder getPoissonOrBuilder();
+
+    public io.grpc.benchmarks.proto.Control.LoadParams.LoadCase getLoadCase();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.LoadParams}
+   */
+  public  static final class LoadParams extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.LoadParams)
+      LoadParamsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use LoadParams.newBuilder() to construct.
+    private LoadParams(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private LoadParams() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private LoadParams(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.benchmarks.proto.Control.ClosedLoopParams.Builder subBuilder = null;
+              if (loadCase_ == 1) {
+                subBuilder = ((io.grpc.benchmarks.proto.Control.ClosedLoopParams) load_).toBuilder();
+              }
+              load_ =
+                  input.readMessage(io.grpc.benchmarks.proto.Control.ClosedLoopParams.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom((io.grpc.benchmarks.proto.Control.ClosedLoopParams) load_);
+                load_ = subBuilder.buildPartial();
+              }
+              loadCase_ = 1;
+              break;
+            }
+            case 18: {
+              io.grpc.benchmarks.proto.Control.PoissonParams.Builder subBuilder = null;
+              if (loadCase_ == 2) {
+                subBuilder = ((io.grpc.benchmarks.proto.Control.PoissonParams) load_).toBuilder();
+              }
+              load_ =
+                  input.readMessage(io.grpc.benchmarks.proto.Control.PoissonParams.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom((io.grpc.benchmarks.proto.Control.PoissonParams) load_);
+                load_ = subBuilder.buildPartial();
+              }
+              loadCase_ = 2;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_LoadParams_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_LoadParams_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.LoadParams.class, io.grpc.benchmarks.proto.Control.LoadParams.Builder.class);
+    }
+
+    private int loadCase_ = 0;
+    private java.lang.Object load_;
+    public enum LoadCase
+        implements com.google.protobuf.Internal.EnumLite {
+      CLOSED_LOOP(1),
+      POISSON(2),
+      LOAD_NOT_SET(0);
+      private final int value;
+      private LoadCase(int value) {
+        this.value = value;
+      }
+      /**
+       * @deprecated Use {@link #forNumber(int)} instead.
+       */
+      @java.lang.Deprecated
+      public static LoadCase valueOf(int value) {
+        return forNumber(value);
+      }
+
+      public static LoadCase forNumber(int value) {
+        switch (value) {
+          case 1: return CLOSED_LOOP;
+          case 2: return POISSON;
+          case 0: return LOAD_NOT_SET;
+          default: return null;
+        }
+      }
+      public int getNumber() {
+        return this.value;
+      }
+    };
+
+    public LoadCase
+    getLoadCase() {
+      return LoadCase.forNumber(
+          loadCase_);
+    }
+
+    public static final int CLOSED_LOOP_FIELD_NUMBER = 1;
+    /**
+     * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+     */
+    public boolean hasClosedLoop() {
+      return loadCase_ == 1;
+    }
+    /**
+     * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ClosedLoopParams getClosedLoop() {
+      if (loadCase_ == 1) {
+         return (io.grpc.benchmarks.proto.Control.ClosedLoopParams) load_;
+      }
+      return io.grpc.benchmarks.proto.Control.ClosedLoopParams.getDefaultInstance();
+    }
+    /**
+     * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ClosedLoopParamsOrBuilder getClosedLoopOrBuilder() {
+      if (loadCase_ == 1) {
+         return (io.grpc.benchmarks.proto.Control.ClosedLoopParams) load_;
+      }
+      return io.grpc.benchmarks.proto.Control.ClosedLoopParams.getDefaultInstance();
+    }
+
+    public static final int POISSON_FIELD_NUMBER = 2;
+    /**
+     * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+     */
+    public boolean hasPoisson() {
+      return loadCase_ == 2;
+    }
+    /**
+     * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.PoissonParams getPoisson() {
+      if (loadCase_ == 2) {
+         return (io.grpc.benchmarks.proto.Control.PoissonParams) load_;
+      }
+      return io.grpc.benchmarks.proto.Control.PoissonParams.getDefaultInstance();
+    }
+    /**
+     * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.PoissonParamsOrBuilder getPoissonOrBuilder() {
+      if (loadCase_ == 2) {
+         return (io.grpc.benchmarks.proto.Control.PoissonParams) load_;
+      }
+      return io.grpc.benchmarks.proto.Control.PoissonParams.getDefaultInstance();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (loadCase_ == 1) {
+        output.writeMessage(1, (io.grpc.benchmarks.proto.Control.ClosedLoopParams) load_);
+      }
+      if (loadCase_ == 2) {
+        output.writeMessage(2, (io.grpc.benchmarks.proto.Control.PoissonParams) load_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (loadCase_ == 1) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, (io.grpc.benchmarks.proto.Control.ClosedLoopParams) load_);
+      }
+      if (loadCase_ == 2) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, (io.grpc.benchmarks.proto.Control.PoissonParams) load_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.LoadParams)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.LoadParams other = (io.grpc.benchmarks.proto.Control.LoadParams) obj;
+
+      boolean result = true;
+      result = result && getLoadCase().equals(
+          other.getLoadCase());
+      if (!result) return false;
+      switch (loadCase_) {
+        case 1:
+          result = result && getClosedLoop()
+              .equals(other.getClosedLoop());
+          break;
+        case 2:
+          result = result && getPoisson()
+              .equals(other.getPoisson());
+          break;
+        case 0:
+        default:
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      switch (loadCase_) {
+        case 1:
+          hash = (37 * hash) + CLOSED_LOOP_FIELD_NUMBER;
+          hash = (53 * hash) + getClosedLoop().hashCode();
+          break;
+        case 2:
+          hash = (37 * hash) + POISSON_FIELD_NUMBER;
+          hash = (53 * hash) + getPoisson().hashCode();
+          break;
+        case 0:
+        default:
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.LoadParams parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.LoadParams parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.LoadParams parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.LoadParams parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.LoadParams parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.LoadParams parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.LoadParams parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.LoadParams parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.LoadParams parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.LoadParams parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.LoadParams parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.LoadParams parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.LoadParams prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.LoadParams}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.LoadParams)
+        io.grpc.benchmarks.proto.Control.LoadParamsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_LoadParams_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_LoadParams_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.LoadParams.class, io.grpc.benchmarks.proto.Control.LoadParams.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.LoadParams.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        loadCase_ = 0;
+        load_ = null;
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_LoadParams_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.LoadParams getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.LoadParams.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.LoadParams build() {
+        io.grpc.benchmarks.proto.Control.LoadParams result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.LoadParams buildPartial() {
+        io.grpc.benchmarks.proto.Control.LoadParams result = new io.grpc.benchmarks.proto.Control.LoadParams(this);
+        if (loadCase_ == 1) {
+          if (closedLoopBuilder_ == null) {
+            result.load_ = load_;
+          } else {
+            result.load_ = closedLoopBuilder_.build();
+          }
+        }
+        if (loadCase_ == 2) {
+          if (poissonBuilder_ == null) {
+            result.load_ = load_;
+          } else {
+            result.load_ = poissonBuilder_.build();
+          }
+        }
+        result.loadCase_ = loadCase_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.LoadParams) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.LoadParams)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.LoadParams other) {
+        if (other == io.grpc.benchmarks.proto.Control.LoadParams.getDefaultInstance()) return this;
+        switch (other.getLoadCase()) {
+          case CLOSED_LOOP: {
+            mergeClosedLoop(other.getClosedLoop());
+            break;
+          }
+          case POISSON: {
+            mergePoisson(other.getPoisson());
+            break;
+          }
+          case LOAD_NOT_SET: {
+            break;
+          }
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.LoadParams parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.LoadParams) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int loadCase_ = 0;
+      private java.lang.Object load_;
+      public LoadCase
+          getLoadCase() {
+        return LoadCase.forNumber(
+            loadCase_);
+      }
+
+      public Builder clearLoad() {
+        loadCase_ = 0;
+        load_ = null;
+        onChanged();
+        return this;
+      }
+
+
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ClosedLoopParams, io.grpc.benchmarks.proto.Control.ClosedLoopParams.Builder, io.grpc.benchmarks.proto.Control.ClosedLoopParamsOrBuilder> closedLoopBuilder_;
+      /**
+       * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+       */
+      public boolean hasClosedLoop() {
+        return loadCase_ == 1;
+      }
+      /**
+       * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ClosedLoopParams getClosedLoop() {
+        if (closedLoopBuilder_ == null) {
+          if (loadCase_ == 1) {
+            return (io.grpc.benchmarks.proto.Control.ClosedLoopParams) load_;
+          }
+          return io.grpc.benchmarks.proto.Control.ClosedLoopParams.getDefaultInstance();
+        } else {
+          if (loadCase_ == 1) {
+            return closedLoopBuilder_.getMessage();
+          }
+          return io.grpc.benchmarks.proto.Control.ClosedLoopParams.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+       */
+      public Builder setClosedLoop(io.grpc.benchmarks.proto.Control.ClosedLoopParams value) {
+        if (closedLoopBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          load_ = value;
+          onChanged();
+        } else {
+          closedLoopBuilder_.setMessage(value);
+        }
+        loadCase_ = 1;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+       */
+      public Builder setClosedLoop(
+          io.grpc.benchmarks.proto.Control.ClosedLoopParams.Builder builderForValue) {
+        if (closedLoopBuilder_ == null) {
+          load_ = builderForValue.build();
+          onChanged();
+        } else {
+          closedLoopBuilder_.setMessage(builderForValue.build());
+        }
+        loadCase_ = 1;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+       */
+      public Builder mergeClosedLoop(io.grpc.benchmarks.proto.Control.ClosedLoopParams value) {
+        if (closedLoopBuilder_ == null) {
+          if (loadCase_ == 1 &&
+              load_ != io.grpc.benchmarks.proto.Control.ClosedLoopParams.getDefaultInstance()) {
+            load_ = io.grpc.benchmarks.proto.Control.ClosedLoopParams.newBuilder((io.grpc.benchmarks.proto.Control.ClosedLoopParams) load_)
+                .mergeFrom(value).buildPartial();
+          } else {
+            load_ = value;
+          }
+          onChanged();
+        } else {
+          if (loadCase_ == 1) {
+            closedLoopBuilder_.mergeFrom(value);
+          }
+          closedLoopBuilder_.setMessage(value);
+        }
+        loadCase_ = 1;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+       */
+      public Builder clearClosedLoop() {
+        if (closedLoopBuilder_ == null) {
+          if (loadCase_ == 1) {
+            loadCase_ = 0;
+            load_ = null;
+            onChanged();
+          }
+        } else {
+          if (loadCase_ == 1) {
+            loadCase_ = 0;
+            load_ = null;
+          }
+          closedLoopBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ClosedLoopParams.Builder getClosedLoopBuilder() {
+        return getClosedLoopFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ClosedLoopParamsOrBuilder getClosedLoopOrBuilder() {
+        if ((loadCase_ == 1) && (closedLoopBuilder_ != null)) {
+          return closedLoopBuilder_.getMessageOrBuilder();
+        } else {
+          if (loadCase_ == 1) {
+            return (io.grpc.benchmarks.proto.Control.ClosedLoopParams) load_;
+          }
+          return io.grpc.benchmarks.proto.Control.ClosedLoopParams.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.ClosedLoopParams closed_loop = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ClosedLoopParams, io.grpc.benchmarks.proto.Control.ClosedLoopParams.Builder, io.grpc.benchmarks.proto.Control.ClosedLoopParamsOrBuilder> 
+          getClosedLoopFieldBuilder() {
+        if (closedLoopBuilder_ == null) {
+          if (!(loadCase_ == 1)) {
+            load_ = io.grpc.benchmarks.proto.Control.ClosedLoopParams.getDefaultInstance();
+          }
+          closedLoopBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.ClosedLoopParams, io.grpc.benchmarks.proto.Control.ClosedLoopParams.Builder, io.grpc.benchmarks.proto.Control.ClosedLoopParamsOrBuilder>(
+                  (io.grpc.benchmarks.proto.Control.ClosedLoopParams) load_,
+                  getParentForChildren(),
+                  isClean());
+          load_ = null;
+        }
+        loadCase_ = 1;
+        onChanged();;
+        return closedLoopBuilder_;
+      }
+
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.PoissonParams, io.grpc.benchmarks.proto.Control.PoissonParams.Builder, io.grpc.benchmarks.proto.Control.PoissonParamsOrBuilder> poissonBuilder_;
+      /**
+       * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+       */
+      public boolean hasPoisson() {
+        return loadCase_ == 2;
+      }
+      /**
+       * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.PoissonParams getPoisson() {
+        if (poissonBuilder_ == null) {
+          if (loadCase_ == 2) {
+            return (io.grpc.benchmarks.proto.Control.PoissonParams) load_;
+          }
+          return io.grpc.benchmarks.proto.Control.PoissonParams.getDefaultInstance();
+        } else {
+          if (loadCase_ == 2) {
+            return poissonBuilder_.getMessage();
+          }
+          return io.grpc.benchmarks.proto.Control.PoissonParams.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+       */
+      public Builder setPoisson(io.grpc.benchmarks.proto.Control.PoissonParams value) {
+        if (poissonBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          load_ = value;
+          onChanged();
+        } else {
+          poissonBuilder_.setMessage(value);
+        }
+        loadCase_ = 2;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+       */
+      public Builder setPoisson(
+          io.grpc.benchmarks.proto.Control.PoissonParams.Builder builderForValue) {
+        if (poissonBuilder_ == null) {
+          load_ = builderForValue.build();
+          onChanged();
+        } else {
+          poissonBuilder_.setMessage(builderForValue.build());
+        }
+        loadCase_ = 2;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+       */
+      public Builder mergePoisson(io.grpc.benchmarks.proto.Control.PoissonParams value) {
+        if (poissonBuilder_ == null) {
+          if (loadCase_ == 2 &&
+              load_ != io.grpc.benchmarks.proto.Control.PoissonParams.getDefaultInstance()) {
+            load_ = io.grpc.benchmarks.proto.Control.PoissonParams.newBuilder((io.grpc.benchmarks.proto.Control.PoissonParams) load_)
+                .mergeFrom(value).buildPartial();
+          } else {
+            load_ = value;
+          }
+          onChanged();
+        } else {
+          if (loadCase_ == 2) {
+            poissonBuilder_.mergeFrom(value);
+          }
+          poissonBuilder_.setMessage(value);
+        }
+        loadCase_ = 2;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+       */
+      public Builder clearPoisson() {
+        if (poissonBuilder_ == null) {
+          if (loadCase_ == 2) {
+            loadCase_ = 0;
+            load_ = null;
+            onChanged();
+          }
+        } else {
+          if (loadCase_ == 2) {
+            loadCase_ = 0;
+            load_ = null;
+          }
+          poissonBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.PoissonParams.Builder getPoissonBuilder() {
+        return getPoissonFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.PoissonParamsOrBuilder getPoissonOrBuilder() {
+        if ((loadCase_ == 2) && (poissonBuilder_ != null)) {
+          return poissonBuilder_.getMessageOrBuilder();
+        } else {
+          if (loadCase_ == 2) {
+            return (io.grpc.benchmarks.proto.Control.PoissonParams) load_;
+          }
+          return io.grpc.benchmarks.proto.Control.PoissonParams.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.PoissonParams poisson = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.PoissonParams, io.grpc.benchmarks.proto.Control.PoissonParams.Builder, io.grpc.benchmarks.proto.Control.PoissonParamsOrBuilder> 
+          getPoissonFieldBuilder() {
+        if (poissonBuilder_ == null) {
+          if (!(loadCase_ == 2)) {
+            load_ = io.grpc.benchmarks.proto.Control.PoissonParams.getDefaultInstance();
+          }
+          poissonBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.PoissonParams, io.grpc.benchmarks.proto.Control.PoissonParams.Builder, io.grpc.benchmarks.proto.Control.PoissonParamsOrBuilder>(
+                  (io.grpc.benchmarks.proto.Control.PoissonParams) load_,
+                  getParentForChildren(),
+                  isClean());
+          load_ = null;
+        }
+        loadCase_ = 2;
+        onChanged();;
+        return poissonBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.LoadParams)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.LoadParams)
+    private static final io.grpc.benchmarks.proto.Control.LoadParams DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.LoadParams();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.LoadParams getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<LoadParams>
+        PARSER = new com.google.protobuf.AbstractParser<LoadParams>() {
+      public LoadParams parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new LoadParams(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<LoadParams> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<LoadParams> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.LoadParams getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface SecurityParamsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.SecurityParams)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>bool use_test_ca = 1;</code>
+     */
+    boolean getUseTestCa();
+
+    /**
+     * <code>string server_host_override = 2;</code>
+     */
+    java.lang.String getServerHostOverride();
+    /**
+     * <code>string server_host_override = 2;</code>
+     */
+    com.google.protobuf.ByteString
+        getServerHostOverrideBytes();
+  }
+  /**
+   * <pre>
+   * presence of SecurityParams implies use of TLS
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.SecurityParams}
+   */
+  public  static final class SecurityParams extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.SecurityParams)
+      SecurityParamsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use SecurityParams.newBuilder() to construct.
+    private SecurityParams(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private SecurityParams() {
+      useTestCa_ = false;
+      serverHostOverride_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private SecurityParams(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              useTestCa_ = input.readBool();
+              break;
+            }
+            case 18: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              serverHostOverride_ = s;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_SecurityParams_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_SecurityParams_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.SecurityParams.class, io.grpc.benchmarks.proto.Control.SecurityParams.Builder.class);
+    }
+
+    public static final int USE_TEST_CA_FIELD_NUMBER = 1;
+    private boolean useTestCa_;
+    /**
+     * <code>bool use_test_ca = 1;</code>
+     */
+    public boolean getUseTestCa() {
+      return useTestCa_;
+    }
+
+    public static final int SERVER_HOST_OVERRIDE_FIELD_NUMBER = 2;
+    private volatile java.lang.Object serverHostOverride_;
+    /**
+     * <code>string server_host_override = 2;</code>
+     */
+    public java.lang.String getServerHostOverride() {
+      java.lang.Object ref = serverHostOverride_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        serverHostOverride_ = s;
+        return s;
+      }
+    }
+    /**
+     * <code>string server_host_override = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getServerHostOverrideBytes() {
+      java.lang.Object ref = serverHostOverride_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        serverHostOverride_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (useTestCa_ != false) {
+        output.writeBool(1, useTestCa_);
+      }
+      if (!getServerHostOverrideBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, serverHostOverride_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (useTestCa_ != false) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBoolSize(1, useTestCa_);
+      }
+      if (!getServerHostOverrideBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, serverHostOverride_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.SecurityParams)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.SecurityParams other = (io.grpc.benchmarks.proto.Control.SecurityParams) obj;
+
+      boolean result = true;
+      result = result && (getUseTestCa()
+          == other.getUseTestCa());
+      result = result && getServerHostOverride()
+          .equals(other.getServerHostOverride());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + USE_TEST_CA_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+          getUseTestCa());
+      hash = (37 * hash) + SERVER_HOST_OVERRIDE_FIELD_NUMBER;
+      hash = (53 * hash) + getServerHostOverride().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.SecurityParams parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.SecurityParams parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.SecurityParams parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.SecurityParams parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.SecurityParams parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.SecurityParams parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.SecurityParams parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.SecurityParams parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.SecurityParams parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.SecurityParams parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.SecurityParams parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.SecurityParams parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.SecurityParams prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * presence of SecurityParams implies use of TLS
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.SecurityParams}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.SecurityParams)
+        io.grpc.benchmarks.proto.Control.SecurityParamsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_SecurityParams_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_SecurityParams_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.SecurityParams.class, io.grpc.benchmarks.proto.Control.SecurityParams.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.SecurityParams.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        useTestCa_ = false;
+
+        serverHostOverride_ = "";
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_SecurityParams_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.SecurityParams getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.SecurityParams.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.SecurityParams build() {
+        io.grpc.benchmarks.proto.Control.SecurityParams result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.SecurityParams buildPartial() {
+        io.grpc.benchmarks.proto.Control.SecurityParams result = new io.grpc.benchmarks.proto.Control.SecurityParams(this);
+        result.useTestCa_ = useTestCa_;
+        result.serverHostOverride_ = serverHostOverride_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.SecurityParams) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.SecurityParams)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.SecurityParams other) {
+        if (other == io.grpc.benchmarks.proto.Control.SecurityParams.getDefaultInstance()) return this;
+        if (other.getUseTestCa() != false) {
+          setUseTestCa(other.getUseTestCa());
+        }
+        if (!other.getServerHostOverride().isEmpty()) {
+          serverHostOverride_ = other.serverHostOverride_;
+          onChanged();
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.SecurityParams parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.SecurityParams) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private boolean useTestCa_ ;
+      /**
+       * <code>bool use_test_ca = 1;</code>
+       */
+      public boolean getUseTestCa() {
+        return useTestCa_;
+      }
+      /**
+       * <code>bool use_test_ca = 1;</code>
+       */
+      public Builder setUseTestCa(boolean value) {
+        
+        useTestCa_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>bool use_test_ca = 1;</code>
+       */
+      public Builder clearUseTestCa() {
+        
+        useTestCa_ = false;
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object serverHostOverride_ = "";
+      /**
+       * <code>string server_host_override = 2;</code>
+       */
+      public java.lang.String getServerHostOverride() {
+        java.lang.Object ref = serverHostOverride_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          serverHostOverride_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <code>string server_host_override = 2;</code>
+       */
+      public com.google.protobuf.ByteString
+          getServerHostOverrideBytes() {
+        java.lang.Object ref = serverHostOverride_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          serverHostOverride_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <code>string server_host_override = 2;</code>
+       */
+      public Builder setServerHostOverride(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        serverHostOverride_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string server_host_override = 2;</code>
+       */
+      public Builder clearServerHostOverride() {
+        
+        serverHostOverride_ = getDefaultInstance().getServerHostOverride();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string server_host_override = 2;</code>
+       */
+      public Builder setServerHostOverrideBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        serverHostOverride_ = value;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.SecurityParams)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.SecurityParams)
+    private static final io.grpc.benchmarks.proto.Control.SecurityParams DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.SecurityParams();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.SecurityParams getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<SecurityParams>
+        PARSER = new com.google.protobuf.AbstractParser<SecurityParams>() {
+      public SecurityParams parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new SecurityParams(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<SecurityParams> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<SecurityParams> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.SecurityParams getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ChannelArgOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ChannelArg)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>string name = 1;</code>
+     */
+    java.lang.String getName();
+    /**
+     * <code>string name = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getNameBytes();
+
+    /**
+     * <code>string str_value = 2;</code>
+     */
+    java.lang.String getStrValue();
+    /**
+     * <code>string str_value = 2;</code>
+     */
+    com.google.protobuf.ByteString
+        getStrValueBytes();
+
+    /**
+     * <code>int32 int_value = 3;</code>
+     */
+    int getIntValue();
+
+    public io.grpc.benchmarks.proto.Control.ChannelArg.ValueCase getValueCase();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.ChannelArg}
+   */
+  public  static final class ChannelArg extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ChannelArg)
+      ChannelArgOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ChannelArg.newBuilder() to construct.
+    private ChannelArg(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ChannelArg() {
+      name_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ChannelArg(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              name_ = s;
+              break;
+            }
+            case 18: {
+              java.lang.String s = input.readStringRequireUtf8();
+              valueCase_ = 2;
+              value_ = s;
+              break;
+            }
+            case 24: {
+              valueCase_ = 3;
+              value_ = input.readInt32();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ChannelArg_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ChannelArg_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.ChannelArg.class, io.grpc.benchmarks.proto.Control.ChannelArg.Builder.class);
+    }
+
+    private int valueCase_ = 0;
+    private java.lang.Object value_;
+    public enum ValueCase
+        implements com.google.protobuf.Internal.EnumLite {
+      STR_VALUE(2),
+      INT_VALUE(3),
+      VALUE_NOT_SET(0);
+      private final int value;
+      private ValueCase(int value) {
+        this.value = value;
+      }
+      /**
+       * @deprecated Use {@link #forNumber(int)} instead.
+       */
+      @java.lang.Deprecated
+      public static ValueCase valueOf(int value) {
+        return forNumber(value);
+      }
+
+      public static ValueCase forNumber(int value) {
+        switch (value) {
+          case 2: return STR_VALUE;
+          case 3: return INT_VALUE;
+          case 0: return VALUE_NOT_SET;
+          default: return null;
+        }
+      }
+      public int getNumber() {
+        return this.value;
+      }
+    };
+
+    public ValueCase
+    getValueCase() {
+      return ValueCase.forNumber(
+          valueCase_);
+    }
+
+    public static final int NAME_FIELD_NUMBER = 1;
+    private volatile java.lang.Object name_;
+    /**
+     * <code>string name = 1;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        name_ = s;
+        return s;
+      }
+    }
+    /**
+     * <code>string name = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int STR_VALUE_FIELD_NUMBER = 2;
+    /**
+     * <code>string str_value = 2;</code>
+     */
+    public java.lang.String getStrValue() {
+      java.lang.Object ref = "";
+      if (valueCase_ == 2) {
+        ref = value_;
+      }
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (valueCase_ == 2) {
+          value_ = s;
+        }
+        return s;
+      }
+    }
+    /**
+     * <code>string str_value = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getStrValueBytes() {
+      java.lang.Object ref = "";
+      if (valueCase_ == 2) {
+        ref = value_;
+      }
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        if (valueCase_ == 2) {
+          value_ = b;
+        }
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int INT_VALUE_FIELD_NUMBER = 3;
+    /**
+     * <code>int32 int_value = 3;</code>
+     */
+    public int getIntValue() {
+      if (valueCase_ == 3) {
+        return (java.lang.Integer) value_;
+      }
+      return 0;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!getNameBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_);
+      }
+      if (valueCase_ == 2) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, value_);
+      }
+      if (valueCase_ == 3) {
+        output.writeInt32(
+            3, (int)((java.lang.Integer) value_));
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!getNameBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_);
+      }
+      if (valueCase_ == 2) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, value_);
+      }
+      if (valueCase_ == 3) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(
+              3, (int)((java.lang.Integer) value_));
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.ChannelArg)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.ChannelArg other = (io.grpc.benchmarks.proto.Control.ChannelArg) obj;
+
+      boolean result = true;
+      result = result && getName()
+          .equals(other.getName());
+      result = result && getValueCase().equals(
+          other.getValueCase());
+      if (!result) return false;
+      switch (valueCase_) {
+        case 2:
+          result = result && getStrValue()
+              .equals(other.getStrValue());
+          break;
+        case 3:
+          result = result && (getIntValue()
+              == other.getIntValue());
+          break;
+        case 0:
+        default:
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + NAME_FIELD_NUMBER;
+      hash = (53 * hash) + getName().hashCode();
+      switch (valueCase_) {
+        case 2:
+          hash = (37 * hash) + STR_VALUE_FIELD_NUMBER;
+          hash = (53 * hash) + getStrValue().hashCode();
+          break;
+        case 3:
+          hash = (37 * hash) + INT_VALUE_FIELD_NUMBER;
+          hash = (53 * hash) + getIntValue();
+          break;
+        case 0:
+        default:
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ChannelArg parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ChannelArg parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ChannelArg parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ChannelArg parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ChannelArg parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ChannelArg parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ChannelArg parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ChannelArg parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ChannelArg parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ChannelArg parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ChannelArg parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ChannelArg parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.ChannelArg prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.ChannelArg}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ChannelArg)
+        io.grpc.benchmarks.proto.Control.ChannelArgOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ChannelArg_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ChannelArg_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.ChannelArg.class, io.grpc.benchmarks.proto.Control.ChannelArg.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.ChannelArg.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        name_ = "";
+
+        valueCase_ = 0;
+        value_ = null;
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ChannelArg_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ChannelArg getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.ChannelArg.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.ChannelArg build() {
+        io.grpc.benchmarks.proto.Control.ChannelArg result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ChannelArg buildPartial() {
+        io.grpc.benchmarks.proto.Control.ChannelArg result = new io.grpc.benchmarks.proto.Control.ChannelArg(this);
+        result.name_ = name_;
+        if (valueCase_ == 2) {
+          result.value_ = value_;
+        }
+        if (valueCase_ == 3) {
+          result.value_ = value_;
+        }
+        result.valueCase_ = valueCase_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.ChannelArg) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.ChannelArg)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.ChannelArg other) {
+        if (other == io.grpc.benchmarks.proto.Control.ChannelArg.getDefaultInstance()) return this;
+        if (!other.getName().isEmpty()) {
+          name_ = other.name_;
+          onChanged();
+        }
+        switch (other.getValueCase()) {
+          case STR_VALUE: {
+            valueCase_ = 2;
+            value_ = other.value_;
+            onChanged();
+            break;
+          }
+          case INT_VALUE: {
+            setIntValue(other.getIntValue());
+            break;
+          }
+          case VALUE_NOT_SET: {
+            break;
+          }
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.ChannelArg parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.ChannelArg) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int valueCase_ = 0;
+      private java.lang.Object value_;
+      public ValueCase
+          getValueCase() {
+        return ValueCase.forNumber(
+            valueCase_);
+      }
+
+      public Builder clearValue() {
+        valueCase_ = 0;
+        value_ = null;
+        onChanged();
+        return this;
+      }
+
+
+      private java.lang.Object name_ = "";
+      /**
+       * <code>string name = 1;</code>
+       */
+      public java.lang.String getName() {
+        java.lang.Object ref = name_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          name_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <code>string name = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getNameBytes() {
+        java.lang.Object ref = name_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          name_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <code>string name = 1;</code>
+       */
+      public Builder setName(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        name_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string name = 1;</code>
+       */
+      public Builder clearName() {
+        
+        name_ = getDefaultInstance().getName();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string name = 1;</code>
+       */
+      public Builder setNameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        name_ = value;
+        onChanged();
+        return this;
+      }
+
+      /**
+       * <code>string str_value = 2;</code>
+       */
+      public java.lang.String getStrValue() {
+        java.lang.Object ref = "";
+        if (valueCase_ == 2) {
+          ref = value_;
+        }
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          if (valueCase_ == 2) {
+            value_ = s;
+          }
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <code>string str_value = 2;</code>
+       */
+      public com.google.protobuf.ByteString
+          getStrValueBytes() {
+        java.lang.Object ref = "";
+        if (valueCase_ == 2) {
+          ref = value_;
+        }
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          if (valueCase_ == 2) {
+            value_ = b;
+          }
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <code>string str_value = 2;</code>
+       */
+      public Builder setStrValue(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  valueCase_ = 2;
+        value_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string str_value = 2;</code>
+       */
+      public Builder clearStrValue() {
+        if (valueCase_ == 2) {
+          valueCase_ = 0;
+          value_ = null;
+          onChanged();
+        }
+        return this;
+      }
+      /**
+       * <code>string str_value = 2;</code>
+       */
+      public Builder setStrValueBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        valueCase_ = 2;
+        value_ = value;
+        onChanged();
+        return this;
+      }
+
+      /**
+       * <code>int32 int_value = 3;</code>
+       */
+      public int getIntValue() {
+        if (valueCase_ == 3) {
+          return (java.lang.Integer) value_;
+        }
+        return 0;
+      }
+      /**
+       * <code>int32 int_value = 3;</code>
+       */
+      public Builder setIntValue(int value) {
+        valueCase_ = 3;
+        value_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>int32 int_value = 3;</code>
+       */
+      public Builder clearIntValue() {
+        if (valueCase_ == 3) {
+          valueCase_ = 0;
+          value_ = null;
+          onChanged();
+        }
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ChannelArg)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ChannelArg)
+    private static final io.grpc.benchmarks.proto.Control.ChannelArg DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.ChannelArg();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ChannelArg getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ChannelArg>
+        PARSER = new com.google.protobuf.AbstractParser<ChannelArg>() {
+      public ChannelArg parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ChannelArg(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ChannelArg> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ChannelArg> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.ChannelArg getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ClientConfigOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ClientConfig)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * List of targets to connect to. At least one target needs to be specified.
+     * </pre>
+     *
+     * <code>repeated string server_targets = 1;</code>
+     */
+    java.util.List<java.lang.String>
+        getServerTargetsList();
+    /**
+     * <pre>
+     * List of targets to connect to. At least one target needs to be specified.
+     * </pre>
+     *
+     * <code>repeated string server_targets = 1;</code>
+     */
+    int getServerTargetsCount();
+    /**
+     * <pre>
+     * List of targets to connect to. At least one target needs to be specified.
+     * </pre>
+     *
+     * <code>repeated string server_targets = 1;</code>
+     */
+    java.lang.String getServerTargets(int index);
+    /**
+     * <pre>
+     * List of targets to connect to. At least one target needs to be specified.
+     * </pre>
+     *
+     * <code>repeated string server_targets = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getServerTargetsBytes(int index);
+
+    /**
+     * <code>.grpc.testing.ClientType client_type = 2;</code>
+     */
+    int getClientTypeValue();
+    /**
+     * <code>.grpc.testing.ClientType client_type = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ClientType getClientType();
+
+    /**
+     * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+     */
+    boolean hasSecurityParams();
+    /**
+     * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+     */
+    io.grpc.benchmarks.proto.Control.SecurityParams getSecurityParams();
+    /**
+     * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+     */
+    io.grpc.benchmarks.proto.Control.SecurityParamsOrBuilder getSecurityParamsOrBuilder();
+
+    /**
+     * <pre>
+     * How many concurrent RPCs to start for each channel.
+     * For synchronous client, use a separate thread for each outstanding RPC.
+     * </pre>
+     *
+     * <code>int32 outstanding_rpcs_per_channel = 4;</code>
+     */
+    int getOutstandingRpcsPerChannel();
+
+    /**
+     * <pre>
+     * Number of independent client channels to create.
+     * i-th channel will connect to server_target[i % server_targets.size()]
+     * </pre>
+     *
+     * <code>int32 client_channels = 5;</code>
+     */
+    int getClientChannels();
+
+    /**
+     * <pre>
+     * Only for async client. Number of threads to use to start/manage RPCs.
+     * </pre>
+     *
+     * <code>int32 async_client_threads = 7;</code>
+     */
+    int getAsyncClientThreads();
+
+    /**
+     * <code>.grpc.testing.RpcType rpc_type = 8;</code>
+     */
+    int getRpcTypeValue();
+    /**
+     * <code>.grpc.testing.RpcType rpc_type = 8;</code>
+     */
+    io.grpc.benchmarks.proto.Control.RpcType getRpcType();
+
+    /**
+     * <pre>
+     * The requested load for the entire client (aggregated over all the threads).
+     * </pre>
+     *
+     * <code>.grpc.testing.LoadParams load_params = 10;</code>
+     */
+    boolean hasLoadParams();
+    /**
+     * <pre>
+     * The requested load for the entire client (aggregated over all the threads).
+     * </pre>
+     *
+     * <code>.grpc.testing.LoadParams load_params = 10;</code>
+     */
+    io.grpc.benchmarks.proto.Control.LoadParams getLoadParams();
+    /**
+     * <pre>
+     * The requested load for the entire client (aggregated over all the threads).
+     * </pre>
+     *
+     * <code>.grpc.testing.LoadParams load_params = 10;</code>
+     */
+    io.grpc.benchmarks.proto.Control.LoadParamsOrBuilder getLoadParamsOrBuilder();
+
+    /**
+     * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+     */
+    boolean hasPayloadConfig();
+    /**
+     * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+     */
+    io.grpc.benchmarks.proto.Payloads.PayloadConfig getPayloadConfig();
+    /**
+     * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+     */
+    io.grpc.benchmarks.proto.Payloads.PayloadConfigOrBuilder getPayloadConfigOrBuilder();
+
+    /**
+     * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+     */
+    boolean hasHistogramParams();
+    /**
+     * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.HistogramParams getHistogramParams();
+    /**
+     * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.HistogramParamsOrBuilder getHistogramParamsOrBuilder();
+
+    /**
+     * <pre>
+     * Specify the cores we should run the client on, if desired
+     * </pre>
+     *
+     * <code>repeated int32 core_list = 13;</code>
+     */
+    java.util.List<java.lang.Integer> getCoreListList();
+    /**
+     * <pre>
+     * Specify the cores we should run the client on, if desired
+     * </pre>
+     *
+     * <code>repeated int32 core_list = 13;</code>
+     */
+    int getCoreListCount();
+    /**
+     * <pre>
+     * Specify the cores we should run the client on, if desired
+     * </pre>
+     *
+     * <code>repeated int32 core_list = 13;</code>
+     */
+    int getCoreList(int index);
+
+    /**
+     * <code>int32 core_limit = 14;</code>
+     */
+    int getCoreLimit();
+
+    /**
+     * <pre>
+     * If we use an OTHER_CLIENT client_type, this string gives more detail
+     * </pre>
+     *
+     * <code>string other_client_api = 15;</code>
+     */
+    java.lang.String getOtherClientApi();
+    /**
+     * <pre>
+     * If we use an OTHER_CLIENT client_type, this string gives more detail
+     * </pre>
+     *
+     * <code>string other_client_api = 15;</code>
+     */
+    com.google.protobuf.ByteString
+        getOtherClientApiBytes();
+
+    /**
+     * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+     */
+    java.util.List<io.grpc.benchmarks.proto.Control.ChannelArg> 
+        getChannelArgsList();
+    /**
+     * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ChannelArg getChannelArgs(int index);
+    /**
+     * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+     */
+    int getChannelArgsCount();
+    /**
+     * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+     */
+    java.util.List<? extends io.grpc.benchmarks.proto.Control.ChannelArgOrBuilder> 
+        getChannelArgsOrBuilderList();
+    /**
+     * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ChannelArgOrBuilder getChannelArgsOrBuilder(
+        int index);
+
+    /**
+     * <pre>
+     * Number of messages on a stream before it gets finished/restarted
+     * </pre>
+     *
+     * <code>int32 messages_per_stream = 18;</code>
+     */
+    int getMessagesPerStream();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.ClientConfig}
+   */
+  public  static final class ClientConfig extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ClientConfig)
+      ClientConfigOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ClientConfig.newBuilder() to construct.
+    private ClientConfig(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ClientConfig() {
+      serverTargets_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+      clientType_ = 0;
+      outstandingRpcsPerChannel_ = 0;
+      clientChannels_ = 0;
+      asyncClientThreads_ = 0;
+      rpcType_ = 0;
+      coreList_ = java.util.Collections.emptyList();
+      coreLimit_ = 0;
+      otherClientApi_ = "";
+      channelArgs_ = java.util.Collections.emptyList();
+      messagesPerStream_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ClientConfig(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+              if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+                serverTargets_ = new com.google.protobuf.LazyStringArrayList();
+                mutable_bitField0_ |= 0x00000001;
+              }
+              serverTargets_.add(s);
+              break;
+            }
+            case 16: {
+              int rawValue = input.readEnum();
+
+              clientType_ = rawValue;
+              break;
+            }
+            case 26: {
+              io.grpc.benchmarks.proto.Control.SecurityParams.Builder subBuilder = null;
+              if (securityParams_ != null) {
+                subBuilder = securityParams_.toBuilder();
+              }
+              securityParams_ = input.readMessage(io.grpc.benchmarks.proto.Control.SecurityParams.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(securityParams_);
+                securityParams_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 32: {
+
+              outstandingRpcsPerChannel_ = input.readInt32();
+              break;
+            }
+            case 40: {
+
+              clientChannels_ = input.readInt32();
+              break;
+            }
+            case 56: {
+
+              asyncClientThreads_ = input.readInt32();
+              break;
+            }
+            case 64: {
+              int rawValue = input.readEnum();
+
+              rpcType_ = rawValue;
+              break;
+            }
+            case 82: {
+              io.grpc.benchmarks.proto.Control.LoadParams.Builder subBuilder = null;
+              if (loadParams_ != null) {
+                subBuilder = loadParams_.toBuilder();
+              }
+              loadParams_ = input.readMessage(io.grpc.benchmarks.proto.Control.LoadParams.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(loadParams_);
+                loadParams_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 90: {
+              io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder subBuilder = null;
+              if (payloadConfig_ != null) {
+                subBuilder = payloadConfig_.toBuilder();
+              }
+              payloadConfig_ = input.readMessage(io.grpc.benchmarks.proto.Payloads.PayloadConfig.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(payloadConfig_);
+                payloadConfig_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 98: {
+              io.grpc.benchmarks.proto.Stats.HistogramParams.Builder subBuilder = null;
+              if (histogramParams_ != null) {
+                subBuilder = histogramParams_.toBuilder();
+              }
+              histogramParams_ = input.readMessage(io.grpc.benchmarks.proto.Stats.HistogramParams.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(histogramParams_);
+                histogramParams_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 104: {
+              if (!((mutable_bitField0_ & 0x00000400) == 0x00000400)) {
+                coreList_ = new java.util.ArrayList<java.lang.Integer>();
+                mutable_bitField0_ |= 0x00000400;
+              }
+              coreList_.add(input.readInt32());
+              break;
+            }
+            case 106: {
+              int length = input.readRawVarint32();
+              int limit = input.pushLimit(length);
+              if (!((mutable_bitField0_ & 0x00000400) == 0x00000400) && input.getBytesUntilLimit() > 0) {
+                coreList_ = new java.util.ArrayList<java.lang.Integer>();
+                mutable_bitField0_ |= 0x00000400;
+              }
+              while (input.getBytesUntilLimit() > 0) {
+                coreList_.add(input.readInt32());
+              }
+              input.popLimit(limit);
+              break;
+            }
+            case 112: {
+
+              coreLimit_ = input.readInt32();
+              break;
+            }
+            case 122: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              otherClientApi_ = s;
+              break;
+            }
+            case 130: {
+              if (!((mutable_bitField0_ & 0x00002000) == 0x00002000)) {
+                channelArgs_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Control.ChannelArg>();
+                mutable_bitField0_ |= 0x00002000;
+              }
+              channelArgs_.add(
+                  input.readMessage(io.grpc.benchmarks.proto.Control.ChannelArg.parser(), extensionRegistry));
+              break;
+            }
+            case 144: {
+
+              messagesPerStream_ = input.readInt32();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+          serverTargets_ = serverTargets_.getUnmodifiableView();
+        }
+        if (((mutable_bitField0_ & 0x00000400) == 0x00000400)) {
+          coreList_ = java.util.Collections.unmodifiableList(coreList_);
+        }
+        if (((mutable_bitField0_ & 0x00002000) == 0x00002000)) {
+          channelArgs_ = java.util.Collections.unmodifiableList(channelArgs_);
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientConfig_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientConfig_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.ClientConfig.class, io.grpc.benchmarks.proto.Control.ClientConfig.Builder.class);
+    }
+
+    private int bitField0_;
+    public static final int SERVER_TARGETS_FIELD_NUMBER = 1;
+    private com.google.protobuf.LazyStringList serverTargets_;
+    /**
+     * <pre>
+     * List of targets to connect to. At least one target needs to be specified.
+     * </pre>
+     *
+     * <code>repeated string server_targets = 1;</code>
+     */
+    public com.google.protobuf.ProtocolStringList
+        getServerTargetsList() {
+      return serverTargets_;
+    }
+    /**
+     * <pre>
+     * List of targets to connect to. At least one target needs to be specified.
+     * </pre>
+     *
+     * <code>repeated string server_targets = 1;</code>
+     */
+    public int getServerTargetsCount() {
+      return serverTargets_.size();
+    }
+    /**
+     * <pre>
+     * List of targets to connect to. At least one target needs to be specified.
+     * </pre>
+     *
+     * <code>repeated string server_targets = 1;</code>
+     */
+    public java.lang.String getServerTargets(int index) {
+      return serverTargets_.get(index);
+    }
+    /**
+     * <pre>
+     * List of targets to connect to. At least one target needs to be specified.
+     * </pre>
+     *
+     * <code>repeated string server_targets = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getServerTargetsBytes(int index) {
+      return serverTargets_.getByteString(index);
+    }
+
+    public static final int CLIENT_TYPE_FIELD_NUMBER = 2;
+    private int clientType_;
+    /**
+     * <code>.grpc.testing.ClientType client_type = 2;</code>
+     */
+    public int getClientTypeValue() {
+      return clientType_;
+    }
+    /**
+     * <code>.grpc.testing.ClientType client_type = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ClientType getClientType() {
+      io.grpc.benchmarks.proto.Control.ClientType result = io.grpc.benchmarks.proto.Control.ClientType.valueOf(clientType_);
+      return result == null ? io.grpc.benchmarks.proto.Control.ClientType.UNRECOGNIZED : result;
+    }
+
+    public static final int SECURITY_PARAMS_FIELD_NUMBER = 3;
+    private io.grpc.benchmarks.proto.Control.SecurityParams securityParams_;
+    /**
+     * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+     */
+    public boolean hasSecurityParams() {
+      return securityParams_ != null;
+    }
+    /**
+     * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.SecurityParams getSecurityParams() {
+      return securityParams_ == null ? io.grpc.benchmarks.proto.Control.SecurityParams.getDefaultInstance() : securityParams_;
+    }
+    /**
+     * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.SecurityParamsOrBuilder getSecurityParamsOrBuilder() {
+      return getSecurityParams();
+    }
+
+    public static final int OUTSTANDING_RPCS_PER_CHANNEL_FIELD_NUMBER = 4;
+    private int outstandingRpcsPerChannel_;
+    /**
+     * <pre>
+     * How many concurrent RPCs to start for each channel.
+     * For synchronous client, use a separate thread for each outstanding RPC.
+     * </pre>
+     *
+     * <code>int32 outstanding_rpcs_per_channel = 4;</code>
+     */
+    public int getOutstandingRpcsPerChannel() {
+      return outstandingRpcsPerChannel_;
+    }
+
+    public static final int CLIENT_CHANNELS_FIELD_NUMBER = 5;
+    private int clientChannels_;
+    /**
+     * <pre>
+     * Number of independent client channels to create.
+     * i-th channel will connect to server_target[i % server_targets.size()]
+     * </pre>
+     *
+     * <code>int32 client_channels = 5;</code>
+     */
+    public int getClientChannels() {
+      return clientChannels_;
+    }
+
+    public static final int ASYNC_CLIENT_THREADS_FIELD_NUMBER = 7;
+    private int asyncClientThreads_;
+    /**
+     * <pre>
+     * Only for async client. Number of threads to use to start/manage RPCs.
+     * </pre>
+     *
+     * <code>int32 async_client_threads = 7;</code>
+     */
+    public int getAsyncClientThreads() {
+      return asyncClientThreads_;
+    }
+
+    public static final int RPC_TYPE_FIELD_NUMBER = 8;
+    private int rpcType_;
+    /**
+     * <code>.grpc.testing.RpcType rpc_type = 8;</code>
+     */
+    public int getRpcTypeValue() {
+      return rpcType_;
+    }
+    /**
+     * <code>.grpc.testing.RpcType rpc_type = 8;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.RpcType getRpcType() {
+      io.grpc.benchmarks.proto.Control.RpcType result = io.grpc.benchmarks.proto.Control.RpcType.valueOf(rpcType_);
+      return result == null ? io.grpc.benchmarks.proto.Control.RpcType.UNRECOGNIZED : result;
+    }
+
+    public static final int LOAD_PARAMS_FIELD_NUMBER = 10;
+    private io.grpc.benchmarks.proto.Control.LoadParams loadParams_;
+    /**
+     * <pre>
+     * The requested load for the entire client (aggregated over all the threads).
+     * </pre>
+     *
+     * <code>.grpc.testing.LoadParams load_params = 10;</code>
+     */
+    public boolean hasLoadParams() {
+      return loadParams_ != null;
+    }
+    /**
+     * <pre>
+     * The requested load for the entire client (aggregated over all the threads).
+     * </pre>
+     *
+     * <code>.grpc.testing.LoadParams load_params = 10;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.LoadParams getLoadParams() {
+      return loadParams_ == null ? io.grpc.benchmarks.proto.Control.LoadParams.getDefaultInstance() : loadParams_;
+    }
+    /**
+     * <pre>
+     * The requested load for the entire client (aggregated over all the threads).
+     * </pre>
+     *
+     * <code>.grpc.testing.LoadParams load_params = 10;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.LoadParamsOrBuilder getLoadParamsOrBuilder() {
+      return getLoadParams();
+    }
+
+    public static final int PAYLOAD_CONFIG_FIELD_NUMBER = 11;
+    private io.grpc.benchmarks.proto.Payloads.PayloadConfig payloadConfig_;
+    /**
+     * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+     */
+    public boolean hasPayloadConfig() {
+      return payloadConfig_ != null;
+    }
+    /**
+     * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+     */
+    public io.grpc.benchmarks.proto.Payloads.PayloadConfig getPayloadConfig() {
+      return payloadConfig_ == null ? io.grpc.benchmarks.proto.Payloads.PayloadConfig.getDefaultInstance() : payloadConfig_;
+    }
+    /**
+     * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+     */
+    public io.grpc.benchmarks.proto.Payloads.PayloadConfigOrBuilder getPayloadConfigOrBuilder() {
+      return getPayloadConfig();
+    }
+
+    public static final int HISTOGRAM_PARAMS_FIELD_NUMBER = 12;
+    private io.grpc.benchmarks.proto.Stats.HistogramParams histogramParams_;
+    /**
+     * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+     */
+    public boolean hasHistogramParams() {
+      return histogramParams_ != null;
+    }
+    /**
+     * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.HistogramParams getHistogramParams() {
+      return histogramParams_ == null ? io.grpc.benchmarks.proto.Stats.HistogramParams.getDefaultInstance() : histogramParams_;
+    }
+    /**
+     * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.HistogramParamsOrBuilder getHistogramParamsOrBuilder() {
+      return getHistogramParams();
+    }
+
+    public static final int CORE_LIST_FIELD_NUMBER = 13;
+    private java.util.List<java.lang.Integer> coreList_;
+    /**
+     * <pre>
+     * Specify the cores we should run the client on, if desired
+     * </pre>
+     *
+     * <code>repeated int32 core_list = 13;</code>
+     */
+    public java.util.List<java.lang.Integer>
+        getCoreListList() {
+      return coreList_;
+    }
+    /**
+     * <pre>
+     * Specify the cores we should run the client on, if desired
+     * </pre>
+     *
+     * <code>repeated int32 core_list = 13;</code>
+     */
+    public int getCoreListCount() {
+      return coreList_.size();
+    }
+    /**
+     * <pre>
+     * Specify the cores we should run the client on, if desired
+     * </pre>
+     *
+     * <code>repeated int32 core_list = 13;</code>
+     */
+    public int getCoreList(int index) {
+      return coreList_.get(index);
+    }
+    private int coreListMemoizedSerializedSize = -1;
+
+    public static final int CORE_LIMIT_FIELD_NUMBER = 14;
+    private int coreLimit_;
+    /**
+     * <code>int32 core_limit = 14;</code>
+     */
+    public int getCoreLimit() {
+      return coreLimit_;
+    }
+
+    public static final int OTHER_CLIENT_API_FIELD_NUMBER = 15;
+    private volatile java.lang.Object otherClientApi_;
+    /**
+     * <pre>
+     * If we use an OTHER_CLIENT client_type, this string gives more detail
+     * </pre>
+     *
+     * <code>string other_client_api = 15;</code>
+     */
+    public java.lang.String getOtherClientApi() {
+      java.lang.Object ref = otherClientApi_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        otherClientApi_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * If we use an OTHER_CLIENT client_type, this string gives more detail
+     * </pre>
+     *
+     * <code>string other_client_api = 15;</code>
+     */
+    public com.google.protobuf.ByteString
+        getOtherClientApiBytes() {
+      java.lang.Object ref = otherClientApi_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        otherClientApi_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int CHANNEL_ARGS_FIELD_NUMBER = 16;
+    private java.util.List<io.grpc.benchmarks.proto.Control.ChannelArg> channelArgs_;
+    /**
+     * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+     */
+    public java.util.List<io.grpc.benchmarks.proto.Control.ChannelArg> getChannelArgsList() {
+      return channelArgs_;
+    }
+    /**
+     * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+     */
+    public java.util.List<? extends io.grpc.benchmarks.proto.Control.ChannelArgOrBuilder> 
+        getChannelArgsOrBuilderList() {
+      return channelArgs_;
+    }
+    /**
+     * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+     */
+    public int getChannelArgsCount() {
+      return channelArgs_.size();
+    }
+    /**
+     * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ChannelArg getChannelArgs(int index) {
+      return channelArgs_.get(index);
+    }
+    /**
+     * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ChannelArgOrBuilder getChannelArgsOrBuilder(
+        int index) {
+      return channelArgs_.get(index);
+    }
+
+    public static final int MESSAGES_PER_STREAM_FIELD_NUMBER = 18;
+    private int messagesPerStream_;
+    /**
+     * <pre>
+     * Number of messages on a stream before it gets finished/restarted
+     * </pre>
+     *
+     * <code>int32 messages_per_stream = 18;</code>
+     */
+    public int getMessagesPerStream() {
+      return messagesPerStream_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      getSerializedSize();
+      for (int i = 0; i < serverTargets_.size(); i++) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, serverTargets_.getRaw(i));
+      }
+      if (clientType_ != io.grpc.benchmarks.proto.Control.ClientType.SYNC_CLIENT.getNumber()) {
+        output.writeEnum(2, clientType_);
+      }
+      if (securityParams_ != null) {
+        output.writeMessage(3, getSecurityParams());
+      }
+      if (outstandingRpcsPerChannel_ != 0) {
+        output.writeInt32(4, outstandingRpcsPerChannel_);
+      }
+      if (clientChannels_ != 0) {
+        output.writeInt32(5, clientChannels_);
+      }
+      if (asyncClientThreads_ != 0) {
+        output.writeInt32(7, asyncClientThreads_);
+      }
+      if (rpcType_ != io.grpc.benchmarks.proto.Control.RpcType.UNARY.getNumber()) {
+        output.writeEnum(8, rpcType_);
+      }
+      if (loadParams_ != null) {
+        output.writeMessage(10, getLoadParams());
+      }
+      if (payloadConfig_ != null) {
+        output.writeMessage(11, getPayloadConfig());
+      }
+      if (histogramParams_ != null) {
+        output.writeMessage(12, getHistogramParams());
+      }
+      if (getCoreListList().size() > 0) {
+        output.writeUInt32NoTag(106);
+        output.writeUInt32NoTag(coreListMemoizedSerializedSize);
+      }
+      for (int i = 0; i < coreList_.size(); i++) {
+        output.writeInt32NoTag(coreList_.get(i));
+      }
+      if (coreLimit_ != 0) {
+        output.writeInt32(14, coreLimit_);
+      }
+      if (!getOtherClientApiBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 15, otherClientApi_);
+      }
+      for (int i = 0; i < channelArgs_.size(); i++) {
+        output.writeMessage(16, channelArgs_.get(i));
+      }
+      if (messagesPerStream_ != 0) {
+        output.writeInt32(18, messagesPerStream_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      {
+        int dataSize = 0;
+        for (int i = 0; i < serverTargets_.size(); i++) {
+          dataSize += computeStringSizeNoTag(serverTargets_.getRaw(i));
+        }
+        size += dataSize;
+        size += 1 * getServerTargetsList().size();
+      }
+      if (clientType_ != io.grpc.benchmarks.proto.Control.ClientType.SYNC_CLIENT.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(2, clientType_);
+      }
+      if (securityParams_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(3, getSecurityParams());
+      }
+      if (outstandingRpcsPerChannel_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(4, outstandingRpcsPerChannel_);
+      }
+      if (clientChannels_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(5, clientChannels_);
+      }
+      if (asyncClientThreads_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(7, asyncClientThreads_);
+      }
+      if (rpcType_ != io.grpc.benchmarks.proto.Control.RpcType.UNARY.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(8, rpcType_);
+      }
+      if (loadParams_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(10, getLoadParams());
+      }
+      if (payloadConfig_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(11, getPayloadConfig());
+      }
+      if (histogramParams_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(12, getHistogramParams());
+      }
+      {
+        int dataSize = 0;
+        for (int i = 0; i < coreList_.size(); i++) {
+          dataSize += com.google.protobuf.CodedOutputStream
+            .computeInt32SizeNoTag(coreList_.get(i));
+        }
+        size += dataSize;
+        if (!getCoreListList().isEmpty()) {
+          size += 1;
+          size += com.google.protobuf.CodedOutputStream
+              .computeInt32SizeNoTag(dataSize);
+        }
+        coreListMemoizedSerializedSize = dataSize;
+      }
+      if (coreLimit_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(14, coreLimit_);
+      }
+      if (!getOtherClientApiBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(15, otherClientApi_);
+      }
+      for (int i = 0; i < channelArgs_.size(); i++) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(16, channelArgs_.get(i));
+      }
+      if (messagesPerStream_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(18, messagesPerStream_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.ClientConfig)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.ClientConfig other = (io.grpc.benchmarks.proto.Control.ClientConfig) obj;
+
+      boolean result = true;
+      result = result && getServerTargetsList()
+          .equals(other.getServerTargetsList());
+      result = result && clientType_ == other.clientType_;
+      result = result && (hasSecurityParams() == other.hasSecurityParams());
+      if (hasSecurityParams()) {
+        result = result && getSecurityParams()
+            .equals(other.getSecurityParams());
+      }
+      result = result && (getOutstandingRpcsPerChannel()
+          == other.getOutstandingRpcsPerChannel());
+      result = result && (getClientChannels()
+          == other.getClientChannels());
+      result = result && (getAsyncClientThreads()
+          == other.getAsyncClientThreads());
+      result = result && rpcType_ == other.rpcType_;
+      result = result && (hasLoadParams() == other.hasLoadParams());
+      if (hasLoadParams()) {
+        result = result && getLoadParams()
+            .equals(other.getLoadParams());
+      }
+      result = result && (hasPayloadConfig() == other.hasPayloadConfig());
+      if (hasPayloadConfig()) {
+        result = result && getPayloadConfig()
+            .equals(other.getPayloadConfig());
+      }
+      result = result && (hasHistogramParams() == other.hasHistogramParams());
+      if (hasHistogramParams()) {
+        result = result && getHistogramParams()
+            .equals(other.getHistogramParams());
+      }
+      result = result && getCoreListList()
+          .equals(other.getCoreListList());
+      result = result && (getCoreLimit()
+          == other.getCoreLimit());
+      result = result && getOtherClientApi()
+          .equals(other.getOtherClientApi());
+      result = result && getChannelArgsList()
+          .equals(other.getChannelArgsList());
+      result = result && (getMessagesPerStream()
+          == other.getMessagesPerStream());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (getServerTargetsCount() > 0) {
+        hash = (37 * hash) + SERVER_TARGETS_FIELD_NUMBER;
+        hash = (53 * hash) + getServerTargetsList().hashCode();
+      }
+      hash = (37 * hash) + CLIENT_TYPE_FIELD_NUMBER;
+      hash = (53 * hash) + clientType_;
+      if (hasSecurityParams()) {
+        hash = (37 * hash) + SECURITY_PARAMS_FIELD_NUMBER;
+        hash = (53 * hash) + getSecurityParams().hashCode();
+      }
+      hash = (37 * hash) + OUTSTANDING_RPCS_PER_CHANNEL_FIELD_NUMBER;
+      hash = (53 * hash) + getOutstandingRpcsPerChannel();
+      hash = (37 * hash) + CLIENT_CHANNELS_FIELD_NUMBER;
+      hash = (53 * hash) + getClientChannels();
+      hash = (37 * hash) + ASYNC_CLIENT_THREADS_FIELD_NUMBER;
+      hash = (53 * hash) + getAsyncClientThreads();
+      hash = (37 * hash) + RPC_TYPE_FIELD_NUMBER;
+      hash = (53 * hash) + rpcType_;
+      if (hasLoadParams()) {
+        hash = (37 * hash) + LOAD_PARAMS_FIELD_NUMBER;
+        hash = (53 * hash) + getLoadParams().hashCode();
+      }
+      if (hasPayloadConfig()) {
+        hash = (37 * hash) + PAYLOAD_CONFIG_FIELD_NUMBER;
+        hash = (53 * hash) + getPayloadConfig().hashCode();
+      }
+      if (hasHistogramParams()) {
+        hash = (37 * hash) + HISTOGRAM_PARAMS_FIELD_NUMBER;
+        hash = (53 * hash) + getHistogramParams().hashCode();
+      }
+      if (getCoreListCount() > 0) {
+        hash = (37 * hash) + CORE_LIST_FIELD_NUMBER;
+        hash = (53 * hash) + getCoreListList().hashCode();
+      }
+      hash = (37 * hash) + CORE_LIMIT_FIELD_NUMBER;
+      hash = (53 * hash) + getCoreLimit();
+      hash = (37 * hash) + OTHER_CLIENT_API_FIELD_NUMBER;
+      hash = (53 * hash) + getOtherClientApi().hashCode();
+      if (getChannelArgsCount() > 0) {
+        hash = (37 * hash) + CHANNEL_ARGS_FIELD_NUMBER;
+        hash = (53 * hash) + getChannelArgsList().hashCode();
+      }
+      hash = (37 * hash) + MESSAGES_PER_STREAM_FIELD_NUMBER;
+      hash = (53 * hash) + getMessagesPerStream();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ClientConfig parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientConfig parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientConfig parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientConfig parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientConfig parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientConfig parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientConfig parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientConfig parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientConfig parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientConfig parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientConfig parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientConfig parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.ClientConfig prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.ClientConfig}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ClientConfig)
+        io.grpc.benchmarks.proto.Control.ClientConfigOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientConfig_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientConfig_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.ClientConfig.class, io.grpc.benchmarks.proto.Control.ClientConfig.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.ClientConfig.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+          getChannelArgsFieldBuilder();
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        serverTargets_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+        bitField0_ = (bitField0_ & ~0x00000001);
+        clientType_ = 0;
+
+        if (securityParamsBuilder_ == null) {
+          securityParams_ = null;
+        } else {
+          securityParams_ = null;
+          securityParamsBuilder_ = null;
+        }
+        outstandingRpcsPerChannel_ = 0;
+
+        clientChannels_ = 0;
+
+        asyncClientThreads_ = 0;
+
+        rpcType_ = 0;
+
+        if (loadParamsBuilder_ == null) {
+          loadParams_ = null;
+        } else {
+          loadParams_ = null;
+          loadParamsBuilder_ = null;
+        }
+        if (payloadConfigBuilder_ == null) {
+          payloadConfig_ = null;
+        } else {
+          payloadConfig_ = null;
+          payloadConfigBuilder_ = null;
+        }
+        if (histogramParamsBuilder_ == null) {
+          histogramParams_ = null;
+        } else {
+          histogramParams_ = null;
+          histogramParamsBuilder_ = null;
+        }
+        coreList_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000400);
+        coreLimit_ = 0;
+
+        otherClientApi_ = "";
+
+        if (channelArgsBuilder_ == null) {
+          channelArgs_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00002000);
+        } else {
+          channelArgsBuilder_.clear();
+        }
+        messagesPerStream_ = 0;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientConfig_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ClientConfig getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.ClientConfig.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.ClientConfig build() {
+        io.grpc.benchmarks.proto.Control.ClientConfig result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ClientConfig buildPartial() {
+        io.grpc.benchmarks.proto.Control.ClientConfig result = new io.grpc.benchmarks.proto.Control.ClientConfig(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          serverTargets_ = serverTargets_.getUnmodifiableView();
+          bitField0_ = (bitField0_ & ~0x00000001);
+        }
+        result.serverTargets_ = serverTargets_;
+        result.clientType_ = clientType_;
+        if (securityParamsBuilder_ == null) {
+          result.securityParams_ = securityParams_;
+        } else {
+          result.securityParams_ = securityParamsBuilder_.build();
+        }
+        result.outstandingRpcsPerChannel_ = outstandingRpcsPerChannel_;
+        result.clientChannels_ = clientChannels_;
+        result.asyncClientThreads_ = asyncClientThreads_;
+        result.rpcType_ = rpcType_;
+        if (loadParamsBuilder_ == null) {
+          result.loadParams_ = loadParams_;
+        } else {
+          result.loadParams_ = loadParamsBuilder_.build();
+        }
+        if (payloadConfigBuilder_ == null) {
+          result.payloadConfig_ = payloadConfig_;
+        } else {
+          result.payloadConfig_ = payloadConfigBuilder_.build();
+        }
+        if (histogramParamsBuilder_ == null) {
+          result.histogramParams_ = histogramParams_;
+        } else {
+          result.histogramParams_ = histogramParamsBuilder_.build();
+        }
+        if (((bitField0_ & 0x00000400) == 0x00000400)) {
+          coreList_ = java.util.Collections.unmodifiableList(coreList_);
+          bitField0_ = (bitField0_ & ~0x00000400);
+        }
+        result.coreList_ = coreList_;
+        result.coreLimit_ = coreLimit_;
+        result.otherClientApi_ = otherClientApi_;
+        if (channelArgsBuilder_ == null) {
+          if (((bitField0_ & 0x00002000) == 0x00002000)) {
+            channelArgs_ = java.util.Collections.unmodifiableList(channelArgs_);
+            bitField0_ = (bitField0_ & ~0x00002000);
+          }
+          result.channelArgs_ = channelArgs_;
+        } else {
+          result.channelArgs_ = channelArgsBuilder_.build();
+        }
+        result.messagesPerStream_ = messagesPerStream_;
+        result.bitField0_ = to_bitField0_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.ClientConfig) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.ClientConfig)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.ClientConfig other) {
+        if (other == io.grpc.benchmarks.proto.Control.ClientConfig.getDefaultInstance()) return this;
+        if (!other.serverTargets_.isEmpty()) {
+          if (serverTargets_.isEmpty()) {
+            serverTargets_ = other.serverTargets_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+          } else {
+            ensureServerTargetsIsMutable();
+            serverTargets_.addAll(other.serverTargets_);
+          }
+          onChanged();
+        }
+        if (other.clientType_ != 0) {
+          setClientTypeValue(other.getClientTypeValue());
+        }
+        if (other.hasSecurityParams()) {
+          mergeSecurityParams(other.getSecurityParams());
+        }
+        if (other.getOutstandingRpcsPerChannel() != 0) {
+          setOutstandingRpcsPerChannel(other.getOutstandingRpcsPerChannel());
+        }
+        if (other.getClientChannels() != 0) {
+          setClientChannels(other.getClientChannels());
+        }
+        if (other.getAsyncClientThreads() != 0) {
+          setAsyncClientThreads(other.getAsyncClientThreads());
+        }
+        if (other.rpcType_ != 0) {
+          setRpcTypeValue(other.getRpcTypeValue());
+        }
+        if (other.hasLoadParams()) {
+          mergeLoadParams(other.getLoadParams());
+        }
+        if (other.hasPayloadConfig()) {
+          mergePayloadConfig(other.getPayloadConfig());
+        }
+        if (other.hasHistogramParams()) {
+          mergeHistogramParams(other.getHistogramParams());
+        }
+        if (!other.coreList_.isEmpty()) {
+          if (coreList_.isEmpty()) {
+            coreList_ = other.coreList_;
+            bitField0_ = (bitField0_ & ~0x00000400);
+          } else {
+            ensureCoreListIsMutable();
+            coreList_.addAll(other.coreList_);
+          }
+          onChanged();
+        }
+        if (other.getCoreLimit() != 0) {
+          setCoreLimit(other.getCoreLimit());
+        }
+        if (!other.getOtherClientApi().isEmpty()) {
+          otherClientApi_ = other.otherClientApi_;
+          onChanged();
+        }
+        if (channelArgsBuilder_ == null) {
+          if (!other.channelArgs_.isEmpty()) {
+            if (channelArgs_.isEmpty()) {
+              channelArgs_ = other.channelArgs_;
+              bitField0_ = (bitField0_ & ~0x00002000);
+            } else {
+              ensureChannelArgsIsMutable();
+              channelArgs_.addAll(other.channelArgs_);
+            }
+            onChanged();
+          }
+        } else {
+          if (!other.channelArgs_.isEmpty()) {
+            if (channelArgsBuilder_.isEmpty()) {
+              channelArgsBuilder_.dispose();
+              channelArgsBuilder_ = null;
+              channelArgs_ = other.channelArgs_;
+              bitField0_ = (bitField0_ & ~0x00002000);
+              channelArgsBuilder_ = 
+                com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                   getChannelArgsFieldBuilder() : null;
+            } else {
+              channelArgsBuilder_.addAllMessages(other.channelArgs_);
+            }
+          }
+        }
+        if (other.getMessagesPerStream() != 0) {
+          setMessagesPerStream(other.getMessagesPerStream());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.ClientConfig parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.ClientConfig) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private com.google.protobuf.LazyStringList serverTargets_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+      private void ensureServerTargetsIsMutable() {
+        if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+          serverTargets_ = new com.google.protobuf.LazyStringArrayList(serverTargets_);
+          bitField0_ |= 0x00000001;
+         }
+      }
+      /**
+       * <pre>
+       * List of targets to connect to. At least one target needs to be specified.
+       * </pre>
+       *
+       * <code>repeated string server_targets = 1;</code>
+       */
+      public com.google.protobuf.ProtocolStringList
+          getServerTargetsList() {
+        return serverTargets_.getUnmodifiableView();
+      }
+      /**
+       * <pre>
+       * List of targets to connect to. At least one target needs to be specified.
+       * </pre>
+       *
+       * <code>repeated string server_targets = 1;</code>
+       */
+      public int getServerTargetsCount() {
+        return serverTargets_.size();
+      }
+      /**
+       * <pre>
+       * List of targets to connect to. At least one target needs to be specified.
+       * </pre>
+       *
+       * <code>repeated string server_targets = 1;</code>
+       */
+      public java.lang.String getServerTargets(int index) {
+        return serverTargets_.get(index);
+      }
+      /**
+       * <pre>
+       * List of targets to connect to. At least one target needs to be specified.
+       * </pre>
+       *
+       * <code>repeated string server_targets = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getServerTargetsBytes(int index) {
+        return serverTargets_.getByteString(index);
+      }
+      /**
+       * <pre>
+       * List of targets to connect to. At least one target needs to be specified.
+       * </pre>
+       *
+       * <code>repeated string server_targets = 1;</code>
+       */
+      public Builder setServerTargets(
+          int index, java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureServerTargetsIsMutable();
+        serverTargets_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * List of targets to connect to. At least one target needs to be specified.
+       * </pre>
+       *
+       * <code>repeated string server_targets = 1;</code>
+       */
+      public Builder addServerTargets(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureServerTargetsIsMutable();
+        serverTargets_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * List of targets to connect to. At least one target needs to be specified.
+       * </pre>
+       *
+       * <code>repeated string server_targets = 1;</code>
+       */
+      public Builder addAllServerTargets(
+          java.lang.Iterable<java.lang.String> values) {
+        ensureServerTargetsIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, serverTargets_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * List of targets to connect to. At least one target needs to be specified.
+       * </pre>
+       *
+       * <code>repeated string server_targets = 1;</code>
+       */
+      public Builder clearServerTargets() {
+        serverTargets_ = com.google.protobuf.LazyStringArrayList.EMPTY;
+        bitField0_ = (bitField0_ & ~0x00000001);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * List of targets to connect to. At least one target needs to be specified.
+       * </pre>
+       *
+       * <code>repeated string server_targets = 1;</code>
+       */
+      public Builder addServerTargetsBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        ensureServerTargetsIsMutable();
+        serverTargets_.add(value);
+        onChanged();
+        return this;
+      }
+
+      private int clientType_ = 0;
+      /**
+       * <code>.grpc.testing.ClientType client_type = 2;</code>
+       */
+      public int getClientTypeValue() {
+        return clientType_;
+      }
+      /**
+       * <code>.grpc.testing.ClientType client_type = 2;</code>
+       */
+      public Builder setClientTypeValue(int value) {
+        clientType_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClientType client_type = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ClientType getClientType() {
+        io.grpc.benchmarks.proto.Control.ClientType result = io.grpc.benchmarks.proto.Control.ClientType.valueOf(clientType_);
+        return result == null ? io.grpc.benchmarks.proto.Control.ClientType.UNRECOGNIZED : result;
+      }
+      /**
+       * <code>.grpc.testing.ClientType client_type = 2;</code>
+       */
+      public Builder setClientType(io.grpc.benchmarks.proto.Control.ClientType value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        clientType_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClientType client_type = 2;</code>
+       */
+      public Builder clearClientType() {
+        
+        clientType_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Control.SecurityParams securityParams_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.SecurityParams, io.grpc.benchmarks.proto.Control.SecurityParams.Builder, io.grpc.benchmarks.proto.Control.SecurityParamsOrBuilder> securityParamsBuilder_;
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+       */
+      public boolean hasSecurityParams() {
+        return securityParamsBuilder_ != null || securityParams_ != null;
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.SecurityParams getSecurityParams() {
+        if (securityParamsBuilder_ == null) {
+          return securityParams_ == null ? io.grpc.benchmarks.proto.Control.SecurityParams.getDefaultInstance() : securityParams_;
+        } else {
+          return securityParamsBuilder_.getMessage();
+        }
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+       */
+      public Builder setSecurityParams(io.grpc.benchmarks.proto.Control.SecurityParams value) {
+        if (securityParamsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          securityParams_ = value;
+          onChanged();
+        } else {
+          securityParamsBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+       */
+      public Builder setSecurityParams(
+          io.grpc.benchmarks.proto.Control.SecurityParams.Builder builderForValue) {
+        if (securityParamsBuilder_ == null) {
+          securityParams_ = builderForValue.build();
+          onChanged();
+        } else {
+          securityParamsBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+       */
+      public Builder mergeSecurityParams(io.grpc.benchmarks.proto.Control.SecurityParams value) {
+        if (securityParamsBuilder_ == null) {
+          if (securityParams_ != null) {
+            securityParams_ =
+              io.grpc.benchmarks.proto.Control.SecurityParams.newBuilder(securityParams_).mergeFrom(value).buildPartial();
+          } else {
+            securityParams_ = value;
+          }
+          onChanged();
+        } else {
+          securityParamsBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+       */
+      public Builder clearSecurityParams() {
+        if (securityParamsBuilder_ == null) {
+          securityParams_ = null;
+          onChanged();
+        } else {
+          securityParams_ = null;
+          securityParamsBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.SecurityParams.Builder getSecurityParamsBuilder() {
+        
+        onChanged();
+        return getSecurityParamsFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.SecurityParamsOrBuilder getSecurityParamsOrBuilder() {
+        if (securityParamsBuilder_ != null) {
+          return securityParamsBuilder_.getMessageOrBuilder();
+        } else {
+          return securityParams_ == null ?
+              io.grpc.benchmarks.proto.Control.SecurityParams.getDefaultInstance() : securityParams_;
+        }
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 3;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.SecurityParams, io.grpc.benchmarks.proto.Control.SecurityParams.Builder, io.grpc.benchmarks.proto.Control.SecurityParamsOrBuilder> 
+          getSecurityParamsFieldBuilder() {
+        if (securityParamsBuilder_ == null) {
+          securityParamsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.SecurityParams, io.grpc.benchmarks.proto.Control.SecurityParams.Builder, io.grpc.benchmarks.proto.Control.SecurityParamsOrBuilder>(
+                  getSecurityParams(),
+                  getParentForChildren(),
+                  isClean());
+          securityParams_ = null;
+        }
+        return securityParamsBuilder_;
+      }
+
+      private int outstandingRpcsPerChannel_ ;
+      /**
+       * <pre>
+       * How many concurrent RPCs to start for each channel.
+       * For synchronous client, use a separate thread for each outstanding RPC.
+       * </pre>
+       *
+       * <code>int32 outstanding_rpcs_per_channel = 4;</code>
+       */
+      public int getOutstandingRpcsPerChannel() {
+        return outstandingRpcsPerChannel_;
+      }
+      /**
+       * <pre>
+       * How many concurrent RPCs to start for each channel.
+       * For synchronous client, use a separate thread for each outstanding RPC.
+       * </pre>
+       *
+       * <code>int32 outstanding_rpcs_per_channel = 4;</code>
+       */
+      public Builder setOutstandingRpcsPerChannel(int value) {
+        
+        outstandingRpcsPerChannel_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * How many concurrent RPCs to start for each channel.
+       * For synchronous client, use a separate thread for each outstanding RPC.
+       * </pre>
+       *
+       * <code>int32 outstanding_rpcs_per_channel = 4;</code>
+       */
+      public Builder clearOutstandingRpcsPerChannel() {
+        
+        outstandingRpcsPerChannel_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int clientChannels_ ;
+      /**
+       * <pre>
+       * Number of independent client channels to create.
+       * i-th channel will connect to server_target[i % server_targets.size()]
+       * </pre>
+       *
+       * <code>int32 client_channels = 5;</code>
+       */
+      public int getClientChannels() {
+        return clientChannels_;
+      }
+      /**
+       * <pre>
+       * Number of independent client channels to create.
+       * i-th channel will connect to server_target[i % server_targets.size()]
+       * </pre>
+       *
+       * <code>int32 client_channels = 5;</code>
+       */
+      public Builder setClientChannels(int value) {
+        
+        clientChannels_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of independent client channels to create.
+       * i-th channel will connect to server_target[i % server_targets.size()]
+       * </pre>
+       *
+       * <code>int32 client_channels = 5;</code>
+       */
+      public Builder clearClientChannels() {
+        
+        clientChannels_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int asyncClientThreads_ ;
+      /**
+       * <pre>
+       * Only for async client. Number of threads to use to start/manage RPCs.
+       * </pre>
+       *
+       * <code>int32 async_client_threads = 7;</code>
+       */
+      public int getAsyncClientThreads() {
+        return asyncClientThreads_;
+      }
+      /**
+       * <pre>
+       * Only for async client. Number of threads to use to start/manage RPCs.
+       * </pre>
+       *
+       * <code>int32 async_client_threads = 7;</code>
+       */
+      public Builder setAsyncClientThreads(int value) {
+        
+        asyncClientThreads_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Only for async client. Number of threads to use to start/manage RPCs.
+       * </pre>
+       *
+       * <code>int32 async_client_threads = 7;</code>
+       */
+      public Builder clearAsyncClientThreads() {
+        
+        asyncClientThreads_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int rpcType_ = 0;
+      /**
+       * <code>.grpc.testing.RpcType rpc_type = 8;</code>
+       */
+      public int getRpcTypeValue() {
+        return rpcType_;
+      }
+      /**
+       * <code>.grpc.testing.RpcType rpc_type = 8;</code>
+       */
+      public Builder setRpcTypeValue(int value) {
+        rpcType_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.RpcType rpc_type = 8;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.RpcType getRpcType() {
+        io.grpc.benchmarks.proto.Control.RpcType result = io.grpc.benchmarks.proto.Control.RpcType.valueOf(rpcType_);
+        return result == null ? io.grpc.benchmarks.proto.Control.RpcType.UNRECOGNIZED : result;
+      }
+      /**
+       * <code>.grpc.testing.RpcType rpc_type = 8;</code>
+       */
+      public Builder setRpcType(io.grpc.benchmarks.proto.Control.RpcType value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        rpcType_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.RpcType rpc_type = 8;</code>
+       */
+      public Builder clearRpcType() {
+        
+        rpcType_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Control.LoadParams loadParams_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.LoadParams, io.grpc.benchmarks.proto.Control.LoadParams.Builder, io.grpc.benchmarks.proto.Control.LoadParamsOrBuilder> loadParamsBuilder_;
+      /**
+       * <pre>
+       * The requested load for the entire client (aggregated over all the threads).
+       * </pre>
+       *
+       * <code>.grpc.testing.LoadParams load_params = 10;</code>
+       */
+      public boolean hasLoadParams() {
+        return loadParamsBuilder_ != null || loadParams_ != null;
+      }
+      /**
+       * <pre>
+       * The requested load for the entire client (aggregated over all the threads).
+       * </pre>
+       *
+       * <code>.grpc.testing.LoadParams load_params = 10;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.LoadParams getLoadParams() {
+        if (loadParamsBuilder_ == null) {
+          return loadParams_ == null ? io.grpc.benchmarks.proto.Control.LoadParams.getDefaultInstance() : loadParams_;
+        } else {
+          return loadParamsBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * The requested load for the entire client (aggregated over all the threads).
+       * </pre>
+       *
+       * <code>.grpc.testing.LoadParams load_params = 10;</code>
+       */
+      public Builder setLoadParams(io.grpc.benchmarks.proto.Control.LoadParams value) {
+        if (loadParamsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          loadParams_ = value;
+          onChanged();
+        } else {
+          loadParamsBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The requested load for the entire client (aggregated over all the threads).
+       * </pre>
+       *
+       * <code>.grpc.testing.LoadParams load_params = 10;</code>
+       */
+      public Builder setLoadParams(
+          io.grpc.benchmarks.proto.Control.LoadParams.Builder builderForValue) {
+        if (loadParamsBuilder_ == null) {
+          loadParams_ = builderForValue.build();
+          onChanged();
+        } else {
+          loadParamsBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The requested load for the entire client (aggregated over all the threads).
+       * </pre>
+       *
+       * <code>.grpc.testing.LoadParams load_params = 10;</code>
+       */
+      public Builder mergeLoadParams(io.grpc.benchmarks.proto.Control.LoadParams value) {
+        if (loadParamsBuilder_ == null) {
+          if (loadParams_ != null) {
+            loadParams_ =
+              io.grpc.benchmarks.proto.Control.LoadParams.newBuilder(loadParams_).mergeFrom(value).buildPartial();
+          } else {
+            loadParams_ = value;
+          }
+          onChanged();
+        } else {
+          loadParamsBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The requested load for the entire client (aggregated over all the threads).
+       * </pre>
+       *
+       * <code>.grpc.testing.LoadParams load_params = 10;</code>
+       */
+      public Builder clearLoadParams() {
+        if (loadParamsBuilder_ == null) {
+          loadParams_ = null;
+          onChanged();
+        } else {
+          loadParams_ = null;
+          loadParamsBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The requested load for the entire client (aggregated over all the threads).
+       * </pre>
+       *
+       * <code>.grpc.testing.LoadParams load_params = 10;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.LoadParams.Builder getLoadParamsBuilder() {
+        
+        onChanged();
+        return getLoadParamsFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * The requested load for the entire client (aggregated over all the threads).
+       * </pre>
+       *
+       * <code>.grpc.testing.LoadParams load_params = 10;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.LoadParamsOrBuilder getLoadParamsOrBuilder() {
+        if (loadParamsBuilder_ != null) {
+          return loadParamsBuilder_.getMessageOrBuilder();
+        } else {
+          return loadParams_ == null ?
+              io.grpc.benchmarks.proto.Control.LoadParams.getDefaultInstance() : loadParams_;
+        }
+      }
+      /**
+       * <pre>
+       * The requested load for the entire client (aggregated over all the threads).
+       * </pre>
+       *
+       * <code>.grpc.testing.LoadParams load_params = 10;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.LoadParams, io.grpc.benchmarks.proto.Control.LoadParams.Builder, io.grpc.benchmarks.proto.Control.LoadParamsOrBuilder> 
+          getLoadParamsFieldBuilder() {
+        if (loadParamsBuilder_ == null) {
+          loadParamsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.LoadParams, io.grpc.benchmarks.proto.Control.LoadParams.Builder, io.grpc.benchmarks.proto.Control.LoadParamsOrBuilder>(
+                  getLoadParams(),
+                  getParentForChildren(),
+                  isClean());
+          loadParams_ = null;
+        }
+        return loadParamsBuilder_;
+      }
+
+      private io.grpc.benchmarks.proto.Payloads.PayloadConfig payloadConfig_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Payloads.PayloadConfig, io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder, io.grpc.benchmarks.proto.Payloads.PayloadConfigOrBuilder> payloadConfigBuilder_;
+      /**
+       * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+       */
+      public boolean hasPayloadConfig() {
+        return payloadConfigBuilder_ != null || payloadConfig_ != null;
+      }
+      /**
+       * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.PayloadConfig getPayloadConfig() {
+        if (payloadConfigBuilder_ == null) {
+          return payloadConfig_ == null ? io.grpc.benchmarks.proto.Payloads.PayloadConfig.getDefaultInstance() : payloadConfig_;
+        } else {
+          return payloadConfigBuilder_.getMessage();
+        }
+      }
+      /**
+       * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+       */
+      public Builder setPayloadConfig(io.grpc.benchmarks.proto.Payloads.PayloadConfig value) {
+        if (payloadConfigBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payloadConfig_ = value;
+          onChanged();
+        } else {
+          payloadConfigBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+       */
+      public Builder setPayloadConfig(
+          io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder builderForValue) {
+        if (payloadConfigBuilder_ == null) {
+          payloadConfig_ = builderForValue.build();
+          onChanged();
+        } else {
+          payloadConfigBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+       */
+      public Builder mergePayloadConfig(io.grpc.benchmarks.proto.Payloads.PayloadConfig value) {
+        if (payloadConfigBuilder_ == null) {
+          if (payloadConfig_ != null) {
+            payloadConfig_ =
+              io.grpc.benchmarks.proto.Payloads.PayloadConfig.newBuilder(payloadConfig_).mergeFrom(value).buildPartial();
+          } else {
+            payloadConfig_ = value;
+          }
+          onChanged();
+        } else {
+          payloadConfigBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+       */
+      public Builder clearPayloadConfig() {
+        if (payloadConfigBuilder_ == null) {
+          payloadConfig_ = null;
+          onChanged();
+        } else {
+          payloadConfig_ = null;
+          payloadConfigBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder getPayloadConfigBuilder() {
+        
+        onChanged();
+        return getPayloadConfigFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.PayloadConfigOrBuilder getPayloadConfigOrBuilder() {
+        if (payloadConfigBuilder_ != null) {
+          return payloadConfigBuilder_.getMessageOrBuilder();
+        } else {
+          return payloadConfig_ == null ?
+              io.grpc.benchmarks.proto.Payloads.PayloadConfig.getDefaultInstance() : payloadConfig_;
+        }
+      }
+      /**
+       * <code>.grpc.testing.PayloadConfig payload_config = 11;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Payloads.PayloadConfig, io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder, io.grpc.benchmarks.proto.Payloads.PayloadConfigOrBuilder> 
+          getPayloadConfigFieldBuilder() {
+        if (payloadConfigBuilder_ == null) {
+          payloadConfigBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Payloads.PayloadConfig, io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder, io.grpc.benchmarks.proto.Payloads.PayloadConfigOrBuilder>(
+                  getPayloadConfig(),
+                  getParentForChildren(),
+                  isClean());
+          payloadConfig_ = null;
+        }
+        return payloadConfigBuilder_;
+      }
+
+      private io.grpc.benchmarks.proto.Stats.HistogramParams histogramParams_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.HistogramParams, io.grpc.benchmarks.proto.Stats.HistogramParams.Builder, io.grpc.benchmarks.proto.Stats.HistogramParamsOrBuilder> histogramParamsBuilder_;
+      /**
+       * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+       */
+      public boolean hasHistogramParams() {
+        return histogramParamsBuilder_ != null || histogramParams_ != null;
+      }
+      /**
+       * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.HistogramParams getHistogramParams() {
+        if (histogramParamsBuilder_ == null) {
+          return histogramParams_ == null ? io.grpc.benchmarks.proto.Stats.HistogramParams.getDefaultInstance() : histogramParams_;
+        } else {
+          return histogramParamsBuilder_.getMessage();
+        }
+      }
+      /**
+       * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+       */
+      public Builder setHistogramParams(io.grpc.benchmarks.proto.Stats.HistogramParams value) {
+        if (histogramParamsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          histogramParams_ = value;
+          onChanged();
+        } else {
+          histogramParamsBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+       */
+      public Builder setHistogramParams(
+          io.grpc.benchmarks.proto.Stats.HistogramParams.Builder builderForValue) {
+        if (histogramParamsBuilder_ == null) {
+          histogramParams_ = builderForValue.build();
+          onChanged();
+        } else {
+          histogramParamsBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+       */
+      public Builder mergeHistogramParams(io.grpc.benchmarks.proto.Stats.HistogramParams value) {
+        if (histogramParamsBuilder_ == null) {
+          if (histogramParams_ != null) {
+            histogramParams_ =
+              io.grpc.benchmarks.proto.Stats.HistogramParams.newBuilder(histogramParams_).mergeFrom(value).buildPartial();
+          } else {
+            histogramParams_ = value;
+          }
+          onChanged();
+        } else {
+          histogramParamsBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+       */
+      public Builder clearHistogramParams() {
+        if (histogramParamsBuilder_ == null) {
+          histogramParams_ = null;
+          onChanged();
+        } else {
+          histogramParams_ = null;
+          histogramParamsBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.HistogramParams.Builder getHistogramParamsBuilder() {
+        
+        onChanged();
+        return getHistogramParamsFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.HistogramParamsOrBuilder getHistogramParamsOrBuilder() {
+        if (histogramParamsBuilder_ != null) {
+          return histogramParamsBuilder_.getMessageOrBuilder();
+        } else {
+          return histogramParams_ == null ?
+              io.grpc.benchmarks.proto.Stats.HistogramParams.getDefaultInstance() : histogramParams_;
+        }
+      }
+      /**
+       * <code>.grpc.testing.HistogramParams histogram_params = 12;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.HistogramParams, io.grpc.benchmarks.proto.Stats.HistogramParams.Builder, io.grpc.benchmarks.proto.Stats.HistogramParamsOrBuilder> 
+          getHistogramParamsFieldBuilder() {
+        if (histogramParamsBuilder_ == null) {
+          histogramParamsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Stats.HistogramParams, io.grpc.benchmarks.proto.Stats.HistogramParams.Builder, io.grpc.benchmarks.proto.Stats.HistogramParamsOrBuilder>(
+                  getHistogramParams(),
+                  getParentForChildren(),
+                  isClean());
+          histogramParams_ = null;
+        }
+        return histogramParamsBuilder_;
+      }
+
+      private java.util.List<java.lang.Integer> coreList_ = java.util.Collections.emptyList();
+      private void ensureCoreListIsMutable() {
+        if (!((bitField0_ & 0x00000400) == 0x00000400)) {
+          coreList_ = new java.util.ArrayList<java.lang.Integer>(coreList_);
+          bitField0_ |= 0x00000400;
+         }
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the client on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 13;</code>
+       */
+      public java.util.List<java.lang.Integer>
+          getCoreListList() {
+        return java.util.Collections.unmodifiableList(coreList_);
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the client on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 13;</code>
+       */
+      public int getCoreListCount() {
+        return coreList_.size();
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the client on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 13;</code>
+       */
+      public int getCoreList(int index) {
+        return coreList_.get(index);
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the client on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 13;</code>
+       */
+      public Builder setCoreList(
+          int index, int value) {
+        ensureCoreListIsMutable();
+        coreList_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the client on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 13;</code>
+       */
+      public Builder addCoreList(int value) {
+        ensureCoreListIsMutable();
+        coreList_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the client on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 13;</code>
+       */
+      public Builder addAllCoreList(
+          java.lang.Iterable<? extends java.lang.Integer> values) {
+        ensureCoreListIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, coreList_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the client on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 13;</code>
+       */
+      public Builder clearCoreList() {
+        coreList_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000400);
+        onChanged();
+        return this;
+      }
+
+      private int coreLimit_ ;
+      /**
+       * <code>int32 core_limit = 14;</code>
+       */
+      public int getCoreLimit() {
+        return coreLimit_;
+      }
+      /**
+       * <code>int32 core_limit = 14;</code>
+       */
+      public Builder setCoreLimit(int value) {
+        
+        coreLimit_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>int32 core_limit = 14;</code>
+       */
+      public Builder clearCoreLimit() {
+        
+        coreLimit_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object otherClientApi_ = "";
+      /**
+       * <pre>
+       * If we use an OTHER_CLIENT client_type, this string gives more detail
+       * </pre>
+       *
+       * <code>string other_client_api = 15;</code>
+       */
+      public java.lang.String getOtherClientApi() {
+        java.lang.Object ref = otherClientApi_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          otherClientApi_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * If we use an OTHER_CLIENT client_type, this string gives more detail
+       * </pre>
+       *
+       * <code>string other_client_api = 15;</code>
+       */
+      public com.google.protobuf.ByteString
+          getOtherClientApiBytes() {
+        java.lang.Object ref = otherClientApi_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          otherClientApi_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * If we use an OTHER_CLIENT client_type, this string gives more detail
+       * </pre>
+       *
+       * <code>string other_client_api = 15;</code>
+       */
+      public Builder setOtherClientApi(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        otherClientApi_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * If we use an OTHER_CLIENT client_type, this string gives more detail
+       * </pre>
+       *
+       * <code>string other_client_api = 15;</code>
+       */
+      public Builder clearOtherClientApi() {
+        
+        otherClientApi_ = getDefaultInstance().getOtherClientApi();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * If we use an OTHER_CLIENT client_type, this string gives more detail
+       * </pre>
+       *
+       * <code>string other_client_api = 15;</code>
+       */
+      public Builder setOtherClientApiBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        otherClientApi_ = value;
+        onChanged();
+        return this;
+      }
+
+      private java.util.List<io.grpc.benchmarks.proto.Control.ChannelArg> channelArgs_ =
+        java.util.Collections.emptyList();
+      private void ensureChannelArgsIsMutable() {
+        if (!((bitField0_ & 0x00002000) == 0x00002000)) {
+          channelArgs_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Control.ChannelArg>(channelArgs_);
+          bitField0_ |= 0x00002000;
+         }
+      }
+
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ChannelArg, io.grpc.benchmarks.proto.Control.ChannelArg.Builder, io.grpc.benchmarks.proto.Control.ChannelArgOrBuilder> channelArgsBuilder_;
+
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Control.ChannelArg> getChannelArgsList() {
+        if (channelArgsBuilder_ == null) {
+          return java.util.Collections.unmodifiableList(channelArgs_);
+        } else {
+          return channelArgsBuilder_.getMessageList();
+        }
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public int getChannelArgsCount() {
+        if (channelArgsBuilder_ == null) {
+          return channelArgs_.size();
+        } else {
+          return channelArgsBuilder_.getCount();
+        }
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ChannelArg getChannelArgs(int index) {
+        if (channelArgsBuilder_ == null) {
+          return channelArgs_.get(index);
+        } else {
+          return channelArgsBuilder_.getMessage(index);
+        }
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public Builder setChannelArgs(
+          int index, io.grpc.benchmarks.proto.Control.ChannelArg value) {
+        if (channelArgsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureChannelArgsIsMutable();
+          channelArgs_.set(index, value);
+          onChanged();
+        } else {
+          channelArgsBuilder_.setMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public Builder setChannelArgs(
+          int index, io.grpc.benchmarks.proto.Control.ChannelArg.Builder builderForValue) {
+        if (channelArgsBuilder_ == null) {
+          ensureChannelArgsIsMutable();
+          channelArgs_.set(index, builderForValue.build());
+          onChanged();
+        } else {
+          channelArgsBuilder_.setMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public Builder addChannelArgs(io.grpc.benchmarks.proto.Control.ChannelArg value) {
+        if (channelArgsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureChannelArgsIsMutable();
+          channelArgs_.add(value);
+          onChanged();
+        } else {
+          channelArgsBuilder_.addMessage(value);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public Builder addChannelArgs(
+          int index, io.grpc.benchmarks.proto.Control.ChannelArg value) {
+        if (channelArgsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureChannelArgsIsMutable();
+          channelArgs_.add(index, value);
+          onChanged();
+        } else {
+          channelArgsBuilder_.addMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public Builder addChannelArgs(
+          io.grpc.benchmarks.proto.Control.ChannelArg.Builder builderForValue) {
+        if (channelArgsBuilder_ == null) {
+          ensureChannelArgsIsMutable();
+          channelArgs_.add(builderForValue.build());
+          onChanged();
+        } else {
+          channelArgsBuilder_.addMessage(builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public Builder addChannelArgs(
+          int index, io.grpc.benchmarks.proto.Control.ChannelArg.Builder builderForValue) {
+        if (channelArgsBuilder_ == null) {
+          ensureChannelArgsIsMutable();
+          channelArgs_.add(index, builderForValue.build());
+          onChanged();
+        } else {
+          channelArgsBuilder_.addMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public Builder addAllChannelArgs(
+          java.lang.Iterable<? extends io.grpc.benchmarks.proto.Control.ChannelArg> values) {
+        if (channelArgsBuilder_ == null) {
+          ensureChannelArgsIsMutable();
+          com.google.protobuf.AbstractMessageLite.Builder.addAll(
+              values, channelArgs_);
+          onChanged();
+        } else {
+          channelArgsBuilder_.addAllMessages(values);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public Builder clearChannelArgs() {
+        if (channelArgsBuilder_ == null) {
+          channelArgs_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00002000);
+          onChanged();
+        } else {
+          channelArgsBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public Builder removeChannelArgs(int index) {
+        if (channelArgsBuilder_ == null) {
+          ensureChannelArgsIsMutable();
+          channelArgs_.remove(index);
+          onChanged();
+        } else {
+          channelArgsBuilder_.remove(index);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ChannelArg.Builder getChannelArgsBuilder(
+          int index) {
+        return getChannelArgsFieldBuilder().getBuilder(index);
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ChannelArgOrBuilder getChannelArgsOrBuilder(
+          int index) {
+        if (channelArgsBuilder_ == null) {
+          return channelArgs_.get(index);  } else {
+          return channelArgsBuilder_.getMessageOrBuilder(index);
+        }
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public java.util.List<? extends io.grpc.benchmarks.proto.Control.ChannelArgOrBuilder> 
+           getChannelArgsOrBuilderList() {
+        if (channelArgsBuilder_ != null) {
+          return channelArgsBuilder_.getMessageOrBuilderList();
+        } else {
+          return java.util.Collections.unmodifiableList(channelArgs_);
+        }
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ChannelArg.Builder addChannelArgsBuilder() {
+        return getChannelArgsFieldBuilder().addBuilder(
+            io.grpc.benchmarks.proto.Control.ChannelArg.getDefaultInstance());
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ChannelArg.Builder addChannelArgsBuilder(
+          int index) {
+        return getChannelArgsFieldBuilder().addBuilder(
+            index, io.grpc.benchmarks.proto.Control.ChannelArg.getDefaultInstance());
+      }
+      /**
+       * <code>repeated .grpc.testing.ChannelArg channel_args = 16;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Control.ChannelArg.Builder> 
+           getChannelArgsBuilderList() {
+        return getChannelArgsFieldBuilder().getBuilderList();
+      }
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ChannelArg, io.grpc.benchmarks.proto.Control.ChannelArg.Builder, io.grpc.benchmarks.proto.Control.ChannelArgOrBuilder> 
+          getChannelArgsFieldBuilder() {
+        if (channelArgsBuilder_ == null) {
+          channelArgsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.ChannelArg, io.grpc.benchmarks.proto.Control.ChannelArg.Builder, io.grpc.benchmarks.proto.Control.ChannelArgOrBuilder>(
+                  channelArgs_,
+                  ((bitField0_ & 0x00002000) == 0x00002000),
+                  getParentForChildren(),
+                  isClean());
+          channelArgs_ = null;
+        }
+        return channelArgsBuilder_;
+      }
+
+      private int messagesPerStream_ ;
+      /**
+       * <pre>
+       * Number of messages on a stream before it gets finished/restarted
+       * </pre>
+       *
+       * <code>int32 messages_per_stream = 18;</code>
+       */
+      public int getMessagesPerStream() {
+        return messagesPerStream_;
+      }
+      /**
+       * <pre>
+       * Number of messages on a stream before it gets finished/restarted
+       * </pre>
+       *
+       * <code>int32 messages_per_stream = 18;</code>
+       */
+      public Builder setMessagesPerStream(int value) {
+        
+        messagesPerStream_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of messages on a stream before it gets finished/restarted
+       * </pre>
+       *
+       * <code>int32 messages_per_stream = 18;</code>
+       */
+      public Builder clearMessagesPerStream() {
+        
+        messagesPerStream_ = 0;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ClientConfig)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ClientConfig)
+    private static final io.grpc.benchmarks.proto.Control.ClientConfig DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.ClientConfig();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ClientConfig getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ClientConfig>
+        PARSER = new com.google.protobuf.AbstractParser<ClientConfig>() {
+      public ClientConfig parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ClientConfig(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ClientConfig> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ClientConfig> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.ClientConfig getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ClientStatusOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ClientStatus)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>.grpc.testing.ClientStats stats = 1;</code>
+     */
+    boolean hasStats();
+    /**
+     * <code>.grpc.testing.ClientStats stats = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.ClientStats getStats();
+    /**
+     * <code>.grpc.testing.ClientStats stats = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder getStatsOrBuilder();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.ClientStatus}
+   */
+  public  static final class ClientStatus extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ClientStatus)
+      ClientStatusOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ClientStatus.newBuilder() to construct.
+    private ClientStatus(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ClientStatus() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ClientStatus(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.benchmarks.proto.Stats.ClientStats.Builder subBuilder = null;
+              if (stats_ != null) {
+                subBuilder = stats_.toBuilder();
+              }
+              stats_ = input.readMessage(io.grpc.benchmarks.proto.Stats.ClientStats.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(stats_);
+                stats_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientStatus_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientStatus_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.ClientStatus.class, io.grpc.benchmarks.proto.Control.ClientStatus.Builder.class);
+    }
+
+    public static final int STATS_FIELD_NUMBER = 1;
+    private io.grpc.benchmarks.proto.Stats.ClientStats stats_;
+    /**
+     * <code>.grpc.testing.ClientStats stats = 1;</code>
+     */
+    public boolean hasStats() {
+      return stats_ != null;
+    }
+    /**
+     * <code>.grpc.testing.ClientStats stats = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.ClientStats getStats() {
+      return stats_ == null ? io.grpc.benchmarks.proto.Stats.ClientStats.getDefaultInstance() : stats_;
+    }
+    /**
+     * <code>.grpc.testing.ClientStats stats = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder getStatsOrBuilder() {
+      return getStats();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (stats_ != null) {
+        output.writeMessage(1, getStats());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (stats_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, getStats());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.ClientStatus)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.ClientStatus other = (io.grpc.benchmarks.proto.Control.ClientStatus) obj;
+
+      boolean result = true;
+      result = result && (hasStats() == other.hasStats());
+      if (hasStats()) {
+        result = result && getStats()
+            .equals(other.getStats());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (hasStats()) {
+        hash = (37 * hash) + STATS_FIELD_NUMBER;
+        hash = (53 * hash) + getStats().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ClientStatus parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientStatus parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientStatus parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientStatus parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientStatus parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientStatus parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientStatus parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientStatus parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientStatus parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientStatus parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientStatus parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientStatus parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.ClientStatus prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.ClientStatus}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ClientStatus)
+        io.grpc.benchmarks.proto.Control.ClientStatusOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientStatus_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientStatus_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.ClientStatus.class, io.grpc.benchmarks.proto.Control.ClientStatus.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.ClientStatus.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        if (statsBuilder_ == null) {
+          stats_ = null;
+        } else {
+          stats_ = null;
+          statsBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientStatus_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ClientStatus getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.ClientStatus.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.ClientStatus build() {
+        io.grpc.benchmarks.proto.Control.ClientStatus result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ClientStatus buildPartial() {
+        io.grpc.benchmarks.proto.Control.ClientStatus result = new io.grpc.benchmarks.proto.Control.ClientStatus(this);
+        if (statsBuilder_ == null) {
+          result.stats_ = stats_;
+        } else {
+          result.stats_ = statsBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.ClientStatus) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.ClientStatus)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.ClientStatus other) {
+        if (other == io.grpc.benchmarks.proto.Control.ClientStatus.getDefaultInstance()) return this;
+        if (other.hasStats()) {
+          mergeStats(other.getStats());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.ClientStatus parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.ClientStatus) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Stats.ClientStats stats_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.ClientStats, io.grpc.benchmarks.proto.Stats.ClientStats.Builder, io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder> statsBuilder_;
+      /**
+       * <code>.grpc.testing.ClientStats stats = 1;</code>
+       */
+      public boolean hasStats() {
+        return statsBuilder_ != null || stats_ != null;
+      }
+      /**
+       * <code>.grpc.testing.ClientStats stats = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ClientStats getStats() {
+        if (statsBuilder_ == null) {
+          return stats_ == null ? io.grpc.benchmarks.proto.Stats.ClientStats.getDefaultInstance() : stats_;
+        } else {
+          return statsBuilder_.getMessage();
+        }
+      }
+      /**
+       * <code>.grpc.testing.ClientStats stats = 1;</code>
+       */
+      public Builder setStats(io.grpc.benchmarks.proto.Stats.ClientStats value) {
+        if (statsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          stats_ = value;
+          onChanged();
+        } else {
+          statsBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClientStats stats = 1;</code>
+       */
+      public Builder setStats(
+          io.grpc.benchmarks.proto.Stats.ClientStats.Builder builderForValue) {
+        if (statsBuilder_ == null) {
+          stats_ = builderForValue.build();
+          onChanged();
+        } else {
+          statsBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClientStats stats = 1;</code>
+       */
+      public Builder mergeStats(io.grpc.benchmarks.proto.Stats.ClientStats value) {
+        if (statsBuilder_ == null) {
+          if (stats_ != null) {
+            stats_ =
+              io.grpc.benchmarks.proto.Stats.ClientStats.newBuilder(stats_).mergeFrom(value).buildPartial();
+          } else {
+            stats_ = value;
+          }
+          onChanged();
+        } else {
+          statsBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClientStats stats = 1;</code>
+       */
+      public Builder clearStats() {
+        if (statsBuilder_ == null) {
+          stats_ = null;
+          onChanged();
+        } else {
+          stats_ = null;
+          statsBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClientStats stats = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ClientStats.Builder getStatsBuilder() {
+        
+        onChanged();
+        return getStatsFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.ClientStats stats = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder getStatsOrBuilder() {
+        if (statsBuilder_ != null) {
+          return statsBuilder_.getMessageOrBuilder();
+        } else {
+          return stats_ == null ?
+              io.grpc.benchmarks.proto.Stats.ClientStats.getDefaultInstance() : stats_;
+        }
+      }
+      /**
+       * <code>.grpc.testing.ClientStats stats = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.ClientStats, io.grpc.benchmarks.proto.Stats.ClientStats.Builder, io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder> 
+          getStatsFieldBuilder() {
+        if (statsBuilder_ == null) {
+          statsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Stats.ClientStats, io.grpc.benchmarks.proto.Stats.ClientStats.Builder, io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder>(
+                  getStats(),
+                  getParentForChildren(),
+                  isClean());
+          stats_ = null;
+        }
+        return statsBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ClientStatus)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ClientStatus)
+    private static final io.grpc.benchmarks.proto.Control.ClientStatus DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.ClientStatus();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ClientStatus getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ClientStatus>
+        PARSER = new com.google.protobuf.AbstractParser<ClientStatus>() {
+      public ClientStatus parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ClientStatus(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ClientStatus> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ClientStatus> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.ClientStatus getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface MarkOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.Mark)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * if true, the stats will be reset after taking their snapshot.
+     * </pre>
+     *
+     * <code>bool reset = 1;</code>
+     */
+    boolean getReset();
+  }
+  /**
+   * <pre>
+   * Request current stats
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.Mark}
+   */
+  public  static final class Mark extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.Mark)
+      MarkOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use Mark.newBuilder() to construct.
+    private Mark(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private Mark() {
+      reset_ = false;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private Mark(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              reset_ = input.readBool();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Mark_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Mark_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.Mark.class, io.grpc.benchmarks.proto.Control.Mark.Builder.class);
+    }
+
+    public static final int RESET_FIELD_NUMBER = 1;
+    private boolean reset_;
+    /**
+     * <pre>
+     * if true, the stats will be reset after taking their snapshot.
+     * </pre>
+     *
+     * <code>bool reset = 1;</code>
+     */
+    public boolean getReset() {
+      return reset_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (reset_ != false) {
+        output.writeBool(1, reset_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (reset_ != false) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBoolSize(1, reset_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.Mark)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.Mark other = (io.grpc.benchmarks.proto.Control.Mark) obj;
+
+      boolean result = true;
+      result = result && (getReset()
+          == other.getReset());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + RESET_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+          getReset());
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.Mark parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.Mark parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Mark parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.Mark parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Mark parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.Mark parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Mark parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.Mark parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Mark parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.Mark parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Mark parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.Mark parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.Mark prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Request current stats
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.Mark}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.Mark)
+        io.grpc.benchmarks.proto.Control.MarkOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Mark_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Mark_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.Mark.class, io.grpc.benchmarks.proto.Control.Mark.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.Mark.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        reset_ = false;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Mark_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.Mark getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.Mark build() {
+        io.grpc.benchmarks.proto.Control.Mark result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.Mark buildPartial() {
+        io.grpc.benchmarks.proto.Control.Mark result = new io.grpc.benchmarks.proto.Control.Mark(this);
+        result.reset_ = reset_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.Mark) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.Mark)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.Mark other) {
+        if (other == io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance()) return this;
+        if (other.getReset() != false) {
+          setReset(other.getReset());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.Mark parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.Mark) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private boolean reset_ ;
+      /**
+       * <pre>
+       * if true, the stats will be reset after taking their snapshot.
+       * </pre>
+       *
+       * <code>bool reset = 1;</code>
+       */
+      public boolean getReset() {
+        return reset_;
+      }
+      /**
+       * <pre>
+       * if true, the stats will be reset after taking their snapshot.
+       * </pre>
+       *
+       * <code>bool reset = 1;</code>
+       */
+      public Builder setReset(boolean value) {
+        
+        reset_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * if true, the stats will be reset after taking their snapshot.
+       * </pre>
+       *
+       * <code>bool reset = 1;</code>
+       */
+      public Builder clearReset() {
+        
+        reset_ = false;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.Mark)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.Mark)
+    private static final io.grpc.benchmarks.proto.Control.Mark DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.Mark();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.Mark getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<Mark>
+        PARSER = new com.google.protobuf.AbstractParser<Mark>() {
+      public Mark parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new Mark(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<Mark> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<Mark> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.Mark getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ClientArgsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ClientArgs)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>.grpc.testing.ClientConfig setup = 1;</code>
+     */
+    boolean hasSetup();
+    /**
+     * <code>.grpc.testing.ClientConfig setup = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ClientConfig getSetup();
+    /**
+     * <code>.grpc.testing.ClientConfig setup = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ClientConfigOrBuilder getSetupOrBuilder();
+
+    /**
+     * <code>.grpc.testing.Mark mark = 2;</code>
+     */
+    boolean hasMark();
+    /**
+     * <code>.grpc.testing.Mark mark = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Control.Mark getMark();
+    /**
+     * <code>.grpc.testing.Mark mark = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Control.MarkOrBuilder getMarkOrBuilder();
+
+    public io.grpc.benchmarks.proto.Control.ClientArgs.ArgtypeCase getArgtypeCase();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.ClientArgs}
+   */
+  public  static final class ClientArgs extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ClientArgs)
+      ClientArgsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ClientArgs.newBuilder() to construct.
+    private ClientArgs(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ClientArgs() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ClientArgs(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.benchmarks.proto.Control.ClientConfig.Builder subBuilder = null;
+              if (argtypeCase_ == 1) {
+                subBuilder = ((io.grpc.benchmarks.proto.Control.ClientConfig) argtype_).toBuilder();
+              }
+              argtype_ =
+                  input.readMessage(io.grpc.benchmarks.proto.Control.ClientConfig.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom((io.grpc.benchmarks.proto.Control.ClientConfig) argtype_);
+                argtype_ = subBuilder.buildPartial();
+              }
+              argtypeCase_ = 1;
+              break;
+            }
+            case 18: {
+              io.grpc.benchmarks.proto.Control.Mark.Builder subBuilder = null;
+              if (argtypeCase_ == 2) {
+                subBuilder = ((io.grpc.benchmarks.proto.Control.Mark) argtype_).toBuilder();
+              }
+              argtype_ =
+                  input.readMessage(io.grpc.benchmarks.proto.Control.Mark.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom((io.grpc.benchmarks.proto.Control.Mark) argtype_);
+                argtype_ = subBuilder.buildPartial();
+              }
+              argtypeCase_ = 2;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientArgs_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientArgs_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.ClientArgs.class, io.grpc.benchmarks.proto.Control.ClientArgs.Builder.class);
+    }
+
+    private int argtypeCase_ = 0;
+    private java.lang.Object argtype_;
+    public enum ArgtypeCase
+        implements com.google.protobuf.Internal.EnumLite {
+      SETUP(1),
+      MARK(2),
+      ARGTYPE_NOT_SET(0);
+      private final int value;
+      private ArgtypeCase(int value) {
+        this.value = value;
+      }
+      /**
+       * @deprecated Use {@link #forNumber(int)} instead.
+       */
+      @java.lang.Deprecated
+      public static ArgtypeCase valueOf(int value) {
+        return forNumber(value);
+      }
+
+      public static ArgtypeCase forNumber(int value) {
+        switch (value) {
+          case 1: return SETUP;
+          case 2: return MARK;
+          case 0: return ARGTYPE_NOT_SET;
+          default: return null;
+        }
+      }
+      public int getNumber() {
+        return this.value;
+      }
+    };
+
+    public ArgtypeCase
+    getArgtypeCase() {
+      return ArgtypeCase.forNumber(
+          argtypeCase_);
+    }
+
+    public static final int SETUP_FIELD_NUMBER = 1;
+    /**
+     * <code>.grpc.testing.ClientConfig setup = 1;</code>
+     */
+    public boolean hasSetup() {
+      return argtypeCase_ == 1;
+    }
+    /**
+     * <code>.grpc.testing.ClientConfig setup = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ClientConfig getSetup() {
+      if (argtypeCase_ == 1) {
+         return (io.grpc.benchmarks.proto.Control.ClientConfig) argtype_;
+      }
+      return io.grpc.benchmarks.proto.Control.ClientConfig.getDefaultInstance();
+    }
+    /**
+     * <code>.grpc.testing.ClientConfig setup = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ClientConfigOrBuilder getSetupOrBuilder() {
+      if (argtypeCase_ == 1) {
+         return (io.grpc.benchmarks.proto.Control.ClientConfig) argtype_;
+      }
+      return io.grpc.benchmarks.proto.Control.ClientConfig.getDefaultInstance();
+    }
+
+    public static final int MARK_FIELD_NUMBER = 2;
+    /**
+     * <code>.grpc.testing.Mark mark = 2;</code>
+     */
+    public boolean hasMark() {
+      return argtypeCase_ == 2;
+    }
+    /**
+     * <code>.grpc.testing.Mark mark = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.Mark getMark() {
+      if (argtypeCase_ == 2) {
+         return (io.grpc.benchmarks.proto.Control.Mark) argtype_;
+      }
+      return io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance();
+    }
+    /**
+     * <code>.grpc.testing.Mark mark = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.MarkOrBuilder getMarkOrBuilder() {
+      if (argtypeCase_ == 2) {
+         return (io.grpc.benchmarks.proto.Control.Mark) argtype_;
+      }
+      return io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (argtypeCase_ == 1) {
+        output.writeMessage(1, (io.grpc.benchmarks.proto.Control.ClientConfig) argtype_);
+      }
+      if (argtypeCase_ == 2) {
+        output.writeMessage(2, (io.grpc.benchmarks.proto.Control.Mark) argtype_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (argtypeCase_ == 1) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, (io.grpc.benchmarks.proto.Control.ClientConfig) argtype_);
+      }
+      if (argtypeCase_ == 2) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, (io.grpc.benchmarks.proto.Control.Mark) argtype_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.ClientArgs)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.ClientArgs other = (io.grpc.benchmarks.proto.Control.ClientArgs) obj;
+
+      boolean result = true;
+      result = result && getArgtypeCase().equals(
+          other.getArgtypeCase());
+      if (!result) return false;
+      switch (argtypeCase_) {
+        case 1:
+          result = result && getSetup()
+              .equals(other.getSetup());
+          break;
+        case 2:
+          result = result && getMark()
+              .equals(other.getMark());
+          break;
+        case 0:
+        default:
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      switch (argtypeCase_) {
+        case 1:
+          hash = (37 * hash) + SETUP_FIELD_NUMBER;
+          hash = (53 * hash) + getSetup().hashCode();
+          break;
+        case 2:
+          hash = (37 * hash) + MARK_FIELD_NUMBER;
+          hash = (53 * hash) + getMark().hashCode();
+          break;
+        case 0:
+        default:
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ClientArgs parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientArgs parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientArgs parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientArgs parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientArgs parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientArgs parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientArgs parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientArgs parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientArgs parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientArgs parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientArgs parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ClientArgs parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.ClientArgs prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.ClientArgs}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ClientArgs)
+        io.grpc.benchmarks.proto.Control.ClientArgsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientArgs_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientArgs_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.ClientArgs.class, io.grpc.benchmarks.proto.Control.ClientArgs.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.ClientArgs.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        argtypeCase_ = 0;
+        argtype_ = null;
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ClientArgs_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ClientArgs getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.ClientArgs.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.ClientArgs build() {
+        io.grpc.benchmarks.proto.Control.ClientArgs result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ClientArgs buildPartial() {
+        io.grpc.benchmarks.proto.Control.ClientArgs result = new io.grpc.benchmarks.proto.Control.ClientArgs(this);
+        if (argtypeCase_ == 1) {
+          if (setupBuilder_ == null) {
+            result.argtype_ = argtype_;
+          } else {
+            result.argtype_ = setupBuilder_.build();
+          }
+        }
+        if (argtypeCase_ == 2) {
+          if (markBuilder_ == null) {
+            result.argtype_ = argtype_;
+          } else {
+            result.argtype_ = markBuilder_.build();
+          }
+        }
+        result.argtypeCase_ = argtypeCase_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.ClientArgs) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.ClientArgs)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.ClientArgs other) {
+        if (other == io.grpc.benchmarks.proto.Control.ClientArgs.getDefaultInstance()) return this;
+        switch (other.getArgtypeCase()) {
+          case SETUP: {
+            mergeSetup(other.getSetup());
+            break;
+          }
+          case MARK: {
+            mergeMark(other.getMark());
+            break;
+          }
+          case ARGTYPE_NOT_SET: {
+            break;
+          }
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.ClientArgs parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.ClientArgs) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int argtypeCase_ = 0;
+      private java.lang.Object argtype_;
+      public ArgtypeCase
+          getArgtypeCase() {
+        return ArgtypeCase.forNumber(
+            argtypeCase_);
+      }
+
+      public Builder clearArgtype() {
+        argtypeCase_ = 0;
+        argtype_ = null;
+        onChanged();
+        return this;
+      }
+
+
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ClientConfig, io.grpc.benchmarks.proto.Control.ClientConfig.Builder, io.grpc.benchmarks.proto.Control.ClientConfigOrBuilder> setupBuilder_;
+      /**
+       * <code>.grpc.testing.ClientConfig setup = 1;</code>
+       */
+      public boolean hasSetup() {
+        return argtypeCase_ == 1;
+      }
+      /**
+       * <code>.grpc.testing.ClientConfig setup = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ClientConfig getSetup() {
+        if (setupBuilder_ == null) {
+          if (argtypeCase_ == 1) {
+            return (io.grpc.benchmarks.proto.Control.ClientConfig) argtype_;
+          }
+          return io.grpc.benchmarks.proto.Control.ClientConfig.getDefaultInstance();
+        } else {
+          if (argtypeCase_ == 1) {
+            return setupBuilder_.getMessage();
+          }
+          return io.grpc.benchmarks.proto.Control.ClientConfig.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.ClientConfig setup = 1;</code>
+       */
+      public Builder setSetup(io.grpc.benchmarks.proto.Control.ClientConfig value) {
+        if (setupBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          argtype_ = value;
+          onChanged();
+        } else {
+          setupBuilder_.setMessage(value);
+        }
+        argtypeCase_ = 1;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClientConfig setup = 1;</code>
+       */
+      public Builder setSetup(
+          io.grpc.benchmarks.proto.Control.ClientConfig.Builder builderForValue) {
+        if (setupBuilder_ == null) {
+          argtype_ = builderForValue.build();
+          onChanged();
+        } else {
+          setupBuilder_.setMessage(builderForValue.build());
+        }
+        argtypeCase_ = 1;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClientConfig setup = 1;</code>
+       */
+      public Builder mergeSetup(io.grpc.benchmarks.proto.Control.ClientConfig value) {
+        if (setupBuilder_ == null) {
+          if (argtypeCase_ == 1 &&
+              argtype_ != io.grpc.benchmarks.proto.Control.ClientConfig.getDefaultInstance()) {
+            argtype_ = io.grpc.benchmarks.proto.Control.ClientConfig.newBuilder((io.grpc.benchmarks.proto.Control.ClientConfig) argtype_)
+                .mergeFrom(value).buildPartial();
+          } else {
+            argtype_ = value;
+          }
+          onChanged();
+        } else {
+          if (argtypeCase_ == 1) {
+            setupBuilder_.mergeFrom(value);
+          }
+          setupBuilder_.setMessage(value);
+        }
+        argtypeCase_ = 1;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClientConfig setup = 1;</code>
+       */
+      public Builder clearSetup() {
+        if (setupBuilder_ == null) {
+          if (argtypeCase_ == 1) {
+            argtypeCase_ = 0;
+            argtype_ = null;
+            onChanged();
+          }
+        } else {
+          if (argtypeCase_ == 1) {
+            argtypeCase_ = 0;
+            argtype_ = null;
+          }
+          setupBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ClientConfig setup = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ClientConfig.Builder getSetupBuilder() {
+        return getSetupFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.ClientConfig setup = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ClientConfigOrBuilder getSetupOrBuilder() {
+        if ((argtypeCase_ == 1) && (setupBuilder_ != null)) {
+          return setupBuilder_.getMessageOrBuilder();
+        } else {
+          if (argtypeCase_ == 1) {
+            return (io.grpc.benchmarks.proto.Control.ClientConfig) argtype_;
+          }
+          return io.grpc.benchmarks.proto.Control.ClientConfig.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.ClientConfig setup = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ClientConfig, io.grpc.benchmarks.proto.Control.ClientConfig.Builder, io.grpc.benchmarks.proto.Control.ClientConfigOrBuilder> 
+          getSetupFieldBuilder() {
+        if (setupBuilder_ == null) {
+          if (!(argtypeCase_ == 1)) {
+            argtype_ = io.grpc.benchmarks.proto.Control.ClientConfig.getDefaultInstance();
+          }
+          setupBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.ClientConfig, io.grpc.benchmarks.proto.Control.ClientConfig.Builder, io.grpc.benchmarks.proto.Control.ClientConfigOrBuilder>(
+                  (io.grpc.benchmarks.proto.Control.ClientConfig) argtype_,
+                  getParentForChildren(),
+                  isClean());
+          argtype_ = null;
+        }
+        argtypeCase_ = 1;
+        onChanged();;
+        return setupBuilder_;
+      }
+
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.Mark, io.grpc.benchmarks.proto.Control.Mark.Builder, io.grpc.benchmarks.proto.Control.MarkOrBuilder> markBuilder_;
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public boolean hasMark() {
+        return argtypeCase_ == 2;
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.Mark getMark() {
+        if (markBuilder_ == null) {
+          if (argtypeCase_ == 2) {
+            return (io.grpc.benchmarks.proto.Control.Mark) argtype_;
+          }
+          return io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance();
+        } else {
+          if (argtypeCase_ == 2) {
+            return markBuilder_.getMessage();
+          }
+          return io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public Builder setMark(io.grpc.benchmarks.proto.Control.Mark value) {
+        if (markBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          argtype_ = value;
+          onChanged();
+        } else {
+          markBuilder_.setMessage(value);
+        }
+        argtypeCase_ = 2;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public Builder setMark(
+          io.grpc.benchmarks.proto.Control.Mark.Builder builderForValue) {
+        if (markBuilder_ == null) {
+          argtype_ = builderForValue.build();
+          onChanged();
+        } else {
+          markBuilder_.setMessage(builderForValue.build());
+        }
+        argtypeCase_ = 2;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public Builder mergeMark(io.grpc.benchmarks.proto.Control.Mark value) {
+        if (markBuilder_ == null) {
+          if (argtypeCase_ == 2 &&
+              argtype_ != io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance()) {
+            argtype_ = io.grpc.benchmarks.proto.Control.Mark.newBuilder((io.grpc.benchmarks.proto.Control.Mark) argtype_)
+                .mergeFrom(value).buildPartial();
+          } else {
+            argtype_ = value;
+          }
+          onChanged();
+        } else {
+          if (argtypeCase_ == 2) {
+            markBuilder_.mergeFrom(value);
+          }
+          markBuilder_.setMessage(value);
+        }
+        argtypeCase_ = 2;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public Builder clearMark() {
+        if (markBuilder_ == null) {
+          if (argtypeCase_ == 2) {
+            argtypeCase_ = 0;
+            argtype_ = null;
+            onChanged();
+          }
+        } else {
+          if (argtypeCase_ == 2) {
+            argtypeCase_ = 0;
+            argtype_ = null;
+          }
+          markBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.Mark.Builder getMarkBuilder() {
+        return getMarkFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.MarkOrBuilder getMarkOrBuilder() {
+        if ((argtypeCase_ == 2) && (markBuilder_ != null)) {
+          return markBuilder_.getMessageOrBuilder();
+        } else {
+          if (argtypeCase_ == 2) {
+            return (io.grpc.benchmarks.proto.Control.Mark) argtype_;
+          }
+          return io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.Mark, io.grpc.benchmarks.proto.Control.Mark.Builder, io.grpc.benchmarks.proto.Control.MarkOrBuilder> 
+          getMarkFieldBuilder() {
+        if (markBuilder_ == null) {
+          if (!(argtypeCase_ == 2)) {
+            argtype_ = io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance();
+          }
+          markBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.Mark, io.grpc.benchmarks.proto.Control.Mark.Builder, io.grpc.benchmarks.proto.Control.MarkOrBuilder>(
+                  (io.grpc.benchmarks.proto.Control.Mark) argtype_,
+                  getParentForChildren(),
+                  isClean());
+          argtype_ = null;
+        }
+        argtypeCase_ = 2;
+        onChanged();;
+        return markBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ClientArgs)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ClientArgs)
+    private static final io.grpc.benchmarks.proto.Control.ClientArgs DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.ClientArgs();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ClientArgs getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ClientArgs>
+        PARSER = new com.google.protobuf.AbstractParser<ClientArgs>() {
+      public ClientArgs parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ClientArgs(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ClientArgs> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ClientArgs> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.ClientArgs getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ServerConfigOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ServerConfig)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>.grpc.testing.ServerType server_type = 1;</code>
+     */
+    int getServerTypeValue();
+    /**
+     * <code>.grpc.testing.ServerType server_type = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ServerType getServerType();
+
+    /**
+     * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+     */
+    boolean hasSecurityParams();
+    /**
+     * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Control.SecurityParams getSecurityParams();
+    /**
+     * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Control.SecurityParamsOrBuilder getSecurityParamsOrBuilder();
+
+    /**
+     * <pre>
+     * Port on which to listen. Zero means pick unused port.
+     * </pre>
+     *
+     * <code>int32 port = 4;</code>
+     */
+    int getPort();
+
+    /**
+     * <pre>
+     * Only for async server. Number of threads used to serve the requests.
+     * </pre>
+     *
+     * <code>int32 async_server_threads = 7;</code>
+     */
+    int getAsyncServerThreads();
+
+    /**
+     * <pre>
+     * Specify the number of cores to limit server to, if desired
+     * </pre>
+     *
+     * <code>int32 core_limit = 8;</code>
+     */
+    int getCoreLimit();
+
+    /**
+     * <pre>
+     * payload config, used in generic server.
+     * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+     * 'response sizes' must be configured from the 'response_size' field of the
+     * 'SimpleRequest' objects in RPC requests.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+     */
+    boolean hasPayloadConfig();
+    /**
+     * <pre>
+     * payload config, used in generic server.
+     * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+     * 'response sizes' must be configured from the 'response_size' field of the
+     * 'SimpleRequest' objects in RPC requests.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+     */
+    io.grpc.benchmarks.proto.Payloads.PayloadConfig getPayloadConfig();
+    /**
+     * <pre>
+     * payload config, used in generic server.
+     * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+     * 'response sizes' must be configured from the 'response_size' field of the
+     * 'SimpleRequest' objects in RPC requests.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+     */
+    io.grpc.benchmarks.proto.Payloads.PayloadConfigOrBuilder getPayloadConfigOrBuilder();
+
+    /**
+     * <pre>
+     * Specify the cores we should run the server on, if desired
+     * </pre>
+     *
+     * <code>repeated int32 core_list = 10;</code>
+     */
+    java.util.List<java.lang.Integer> getCoreListList();
+    /**
+     * <pre>
+     * Specify the cores we should run the server on, if desired
+     * </pre>
+     *
+     * <code>repeated int32 core_list = 10;</code>
+     */
+    int getCoreListCount();
+    /**
+     * <pre>
+     * Specify the cores we should run the server on, if desired
+     * </pre>
+     *
+     * <code>repeated int32 core_list = 10;</code>
+     */
+    int getCoreList(int index);
+
+    /**
+     * <pre>
+     * If we use an OTHER_SERVER client_type, this string gives more detail
+     * </pre>
+     *
+     * <code>string other_server_api = 11;</code>
+     */
+    java.lang.String getOtherServerApi();
+    /**
+     * <pre>
+     * If we use an OTHER_SERVER client_type, this string gives more detail
+     * </pre>
+     *
+     * <code>string other_server_api = 11;</code>
+     */
+    com.google.protobuf.ByteString
+        getOtherServerApiBytes();
+
+    /**
+     * <pre>
+     * Buffer pool size (no buffer pool specified if unset)
+     * </pre>
+     *
+     * <code>int32 resource_quota_size = 1001;</code>
+     */
+    int getResourceQuotaSize();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.ServerConfig}
+   */
+  public  static final class ServerConfig extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ServerConfig)
+      ServerConfigOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ServerConfig.newBuilder() to construct.
+    private ServerConfig(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ServerConfig() {
+      serverType_ = 0;
+      port_ = 0;
+      asyncServerThreads_ = 0;
+      coreLimit_ = 0;
+      coreList_ = java.util.Collections.emptyList();
+      otherServerApi_ = "";
+      resourceQuotaSize_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ServerConfig(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+              int rawValue = input.readEnum();
+
+              serverType_ = rawValue;
+              break;
+            }
+            case 18: {
+              io.grpc.benchmarks.proto.Control.SecurityParams.Builder subBuilder = null;
+              if (securityParams_ != null) {
+                subBuilder = securityParams_.toBuilder();
+              }
+              securityParams_ = input.readMessage(io.grpc.benchmarks.proto.Control.SecurityParams.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(securityParams_);
+                securityParams_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 32: {
+
+              port_ = input.readInt32();
+              break;
+            }
+            case 56: {
+
+              asyncServerThreads_ = input.readInt32();
+              break;
+            }
+            case 64: {
+
+              coreLimit_ = input.readInt32();
+              break;
+            }
+            case 74: {
+              io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder subBuilder = null;
+              if (payloadConfig_ != null) {
+                subBuilder = payloadConfig_.toBuilder();
+              }
+              payloadConfig_ = input.readMessage(io.grpc.benchmarks.proto.Payloads.PayloadConfig.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(payloadConfig_);
+                payloadConfig_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 80: {
+              if (!((mutable_bitField0_ & 0x00000040) == 0x00000040)) {
+                coreList_ = new java.util.ArrayList<java.lang.Integer>();
+                mutable_bitField0_ |= 0x00000040;
+              }
+              coreList_.add(input.readInt32());
+              break;
+            }
+            case 82: {
+              int length = input.readRawVarint32();
+              int limit = input.pushLimit(length);
+              if (!((mutable_bitField0_ & 0x00000040) == 0x00000040) && input.getBytesUntilLimit() > 0) {
+                coreList_ = new java.util.ArrayList<java.lang.Integer>();
+                mutable_bitField0_ |= 0x00000040;
+              }
+              while (input.getBytesUntilLimit() > 0) {
+                coreList_.add(input.readInt32());
+              }
+              input.popLimit(limit);
+              break;
+            }
+            case 90: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              otherServerApi_ = s;
+              break;
+            }
+            case 8008: {
+
+              resourceQuotaSize_ = input.readInt32();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000040) == 0x00000040)) {
+          coreList_ = java.util.Collections.unmodifiableList(coreList_);
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerConfig_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerConfig_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.ServerConfig.class, io.grpc.benchmarks.proto.Control.ServerConfig.Builder.class);
+    }
+
+    private int bitField0_;
+    public static final int SERVER_TYPE_FIELD_NUMBER = 1;
+    private int serverType_;
+    /**
+     * <code>.grpc.testing.ServerType server_type = 1;</code>
+     */
+    public int getServerTypeValue() {
+      return serverType_;
+    }
+    /**
+     * <code>.grpc.testing.ServerType server_type = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ServerType getServerType() {
+      io.grpc.benchmarks.proto.Control.ServerType result = io.grpc.benchmarks.proto.Control.ServerType.valueOf(serverType_);
+      return result == null ? io.grpc.benchmarks.proto.Control.ServerType.UNRECOGNIZED : result;
+    }
+
+    public static final int SECURITY_PARAMS_FIELD_NUMBER = 2;
+    private io.grpc.benchmarks.proto.Control.SecurityParams securityParams_;
+    /**
+     * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+     */
+    public boolean hasSecurityParams() {
+      return securityParams_ != null;
+    }
+    /**
+     * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.SecurityParams getSecurityParams() {
+      return securityParams_ == null ? io.grpc.benchmarks.proto.Control.SecurityParams.getDefaultInstance() : securityParams_;
+    }
+    /**
+     * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.SecurityParamsOrBuilder getSecurityParamsOrBuilder() {
+      return getSecurityParams();
+    }
+
+    public static final int PORT_FIELD_NUMBER = 4;
+    private int port_;
+    /**
+     * <pre>
+     * Port on which to listen. Zero means pick unused port.
+     * </pre>
+     *
+     * <code>int32 port = 4;</code>
+     */
+    public int getPort() {
+      return port_;
+    }
+
+    public static final int ASYNC_SERVER_THREADS_FIELD_NUMBER = 7;
+    private int asyncServerThreads_;
+    /**
+     * <pre>
+     * Only for async server. Number of threads used to serve the requests.
+     * </pre>
+     *
+     * <code>int32 async_server_threads = 7;</code>
+     */
+    public int getAsyncServerThreads() {
+      return asyncServerThreads_;
+    }
+
+    public static final int CORE_LIMIT_FIELD_NUMBER = 8;
+    private int coreLimit_;
+    /**
+     * <pre>
+     * Specify the number of cores to limit server to, if desired
+     * </pre>
+     *
+     * <code>int32 core_limit = 8;</code>
+     */
+    public int getCoreLimit() {
+      return coreLimit_;
+    }
+
+    public static final int PAYLOAD_CONFIG_FIELD_NUMBER = 9;
+    private io.grpc.benchmarks.proto.Payloads.PayloadConfig payloadConfig_;
+    /**
+     * <pre>
+     * payload config, used in generic server.
+     * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+     * 'response sizes' must be configured from the 'response_size' field of the
+     * 'SimpleRequest' objects in RPC requests.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+     */
+    public boolean hasPayloadConfig() {
+      return payloadConfig_ != null;
+    }
+    /**
+     * <pre>
+     * payload config, used in generic server.
+     * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+     * 'response sizes' must be configured from the 'response_size' field of the
+     * 'SimpleRequest' objects in RPC requests.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+     */
+    public io.grpc.benchmarks.proto.Payloads.PayloadConfig getPayloadConfig() {
+      return payloadConfig_ == null ? io.grpc.benchmarks.proto.Payloads.PayloadConfig.getDefaultInstance() : payloadConfig_;
+    }
+    /**
+     * <pre>
+     * payload config, used in generic server.
+     * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+     * 'response sizes' must be configured from the 'response_size' field of the
+     * 'SimpleRequest' objects in RPC requests.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+     */
+    public io.grpc.benchmarks.proto.Payloads.PayloadConfigOrBuilder getPayloadConfigOrBuilder() {
+      return getPayloadConfig();
+    }
+
+    public static final int CORE_LIST_FIELD_NUMBER = 10;
+    private java.util.List<java.lang.Integer> coreList_;
+    /**
+     * <pre>
+     * Specify the cores we should run the server on, if desired
+     * </pre>
+     *
+     * <code>repeated int32 core_list = 10;</code>
+     */
+    public java.util.List<java.lang.Integer>
+        getCoreListList() {
+      return coreList_;
+    }
+    /**
+     * <pre>
+     * Specify the cores we should run the server on, if desired
+     * </pre>
+     *
+     * <code>repeated int32 core_list = 10;</code>
+     */
+    public int getCoreListCount() {
+      return coreList_.size();
+    }
+    /**
+     * <pre>
+     * Specify the cores we should run the server on, if desired
+     * </pre>
+     *
+     * <code>repeated int32 core_list = 10;</code>
+     */
+    public int getCoreList(int index) {
+      return coreList_.get(index);
+    }
+    private int coreListMemoizedSerializedSize = -1;
+
+    public static final int OTHER_SERVER_API_FIELD_NUMBER = 11;
+    private volatile java.lang.Object otherServerApi_;
+    /**
+     * <pre>
+     * If we use an OTHER_SERVER client_type, this string gives more detail
+     * </pre>
+     *
+     * <code>string other_server_api = 11;</code>
+     */
+    public java.lang.String getOtherServerApi() {
+      java.lang.Object ref = otherServerApi_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        otherServerApi_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * If we use an OTHER_SERVER client_type, this string gives more detail
+     * </pre>
+     *
+     * <code>string other_server_api = 11;</code>
+     */
+    public com.google.protobuf.ByteString
+        getOtherServerApiBytes() {
+      java.lang.Object ref = otherServerApi_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        otherServerApi_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int RESOURCE_QUOTA_SIZE_FIELD_NUMBER = 1001;
+    private int resourceQuotaSize_;
+    /**
+     * <pre>
+     * Buffer pool size (no buffer pool specified if unset)
+     * </pre>
+     *
+     * <code>int32 resource_quota_size = 1001;</code>
+     */
+    public int getResourceQuotaSize() {
+      return resourceQuotaSize_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      getSerializedSize();
+      if (serverType_ != io.grpc.benchmarks.proto.Control.ServerType.SYNC_SERVER.getNumber()) {
+        output.writeEnum(1, serverType_);
+      }
+      if (securityParams_ != null) {
+        output.writeMessage(2, getSecurityParams());
+      }
+      if (port_ != 0) {
+        output.writeInt32(4, port_);
+      }
+      if (asyncServerThreads_ != 0) {
+        output.writeInt32(7, asyncServerThreads_);
+      }
+      if (coreLimit_ != 0) {
+        output.writeInt32(8, coreLimit_);
+      }
+      if (payloadConfig_ != null) {
+        output.writeMessage(9, getPayloadConfig());
+      }
+      if (getCoreListList().size() > 0) {
+        output.writeUInt32NoTag(82);
+        output.writeUInt32NoTag(coreListMemoizedSerializedSize);
+      }
+      for (int i = 0; i < coreList_.size(); i++) {
+        output.writeInt32NoTag(coreList_.get(i));
+      }
+      if (!getOtherServerApiBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 11, otherServerApi_);
+      }
+      if (resourceQuotaSize_ != 0) {
+        output.writeInt32(1001, resourceQuotaSize_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (serverType_ != io.grpc.benchmarks.proto.Control.ServerType.SYNC_SERVER.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(1, serverType_);
+      }
+      if (securityParams_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, getSecurityParams());
+      }
+      if (port_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(4, port_);
+      }
+      if (asyncServerThreads_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(7, asyncServerThreads_);
+      }
+      if (coreLimit_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(8, coreLimit_);
+      }
+      if (payloadConfig_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(9, getPayloadConfig());
+      }
+      {
+        int dataSize = 0;
+        for (int i = 0; i < coreList_.size(); i++) {
+          dataSize += com.google.protobuf.CodedOutputStream
+            .computeInt32SizeNoTag(coreList_.get(i));
+        }
+        size += dataSize;
+        if (!getCoreListList().isEmpty()) {
+          size += 1;
+          size += com.google.protobuf.CodedOutputStream
+              .computeInt32SizeNoTag(dataSize);
+        }
+        coreListMemoizedSerializedSize = dataSize;
+      }
+      if (!getOtherServerApiBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(11, otherServerApi_);
+      }
+      if (resourceQuotaSize_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1001, resourceQuotaSize_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.ServerConfig)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.ServerConfig other = (io.grpc.benchmarks.proto.Control.ServerConfig) obj;
+
+      boolean result = true;
+      result = result && serverType_ == other.serverType_;
+      result = result && (hasSecurityParams() == other.hasSecurityParams());
+      if (hasSecurityParams()) {
+        result = result && getSecurityParams()
+            .equals(other.getSecurityParams());
+      }
+      result = result && (getPort()
+          == other.getPort());
+      result = result && (getAsyncServerThreads()
+          == other.getAsyncServerThreads());
+      result = result && (getCoreLimit()
+          == other.getCoreLimit());
+      result = result && (hasPayloadConfig() == other.hasPayloadConfig());
+      if (hasPayloadConfig()) {
+        result = result && getPayloadConfig()
+            .equals(other.getPayloadConfig());
+      }
+      result = result && getCoreListList()
+          .equals(other.getCoreListList());
+      result = result && getOtherServerApi()
+          .equals(other.getOtherServerApi());
+      result = result && (getResourceQuotaSize()
+          == other.getResourceQuotaSize());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + SERVER_TYPE_FIELD_NUMBER;
+      hash = (53 * hash) + serverType_;
+      if (hasSecurityParams()) {
+        hash = (37 * hash) + SECURITY_PARAMS_FIELD_NUMBER;
+        hash = (53 * hash) + getSecurityParams().hashCode();
+      }
+      hash = (37 * hash) + PORT_FIELD_NUMBER;
+      hash = (53 * hash) + getPort();
+      hash = (37 * hash) + ASYNC_SERVER_THREADS_FIELD_NUMBER;
+      hash = (53 * hash) + getAsyncServerThreads();
+      hash = (37 * hash) + CORE_LIMIT_FIELD_NUMBER;
+      hash = (53 * hash) + getCoreLimit();
+      if (hasPayloadConfig()) {
+        hash = (37 * hash) + PAYLOAD_CONFIG_FIELD_NUMBER;
+        hash = (53 * hash) + getPayloadConfig().hashCode();
+      }
+      if (getCoreListCount() > 0) {
+        hash = (37 * hash) + CORE_LIST_FIELD_NUMBER;
+        hash = (53 * hash) + getCoreListList().hashCode();
+      }
+      hash = (37 * hash) + OTHER_SERVER_API_FIELD_NUMBER;
+      hash = (53 * hash) + getOtherServerApi().hashCode();
+      hash = (37 * hash) + RESOURCE_QUOTA_SIZE_FIELD_NUMBER;
+      hash = (53 * hash) + getResourceQuotaSize();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ServerConfig parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerConfig parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerConfig parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerConfig parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerConfig parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerConfig parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerConfig parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerConfig parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerConfig parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerConfig parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerConfig parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerConfig parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.ServerConfig prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.ServerConfig}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ServerConfig)
+        io.grpc.benchmarks.proto.Control.ServerConfigOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerConfig_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerConfig_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.ServerConfig.class, io.grpc.benchmarks.proto.Control.ServerConfig.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.ServerConfig.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        serverType_ = 0;
+
+        if (securityParamsBuilder_ == null) {
+          securityParams_ = null;
+        } else {
+          securityParams_ = null;
+          securityParamsBuilder_ = null;
+        }
+        port_ = 0;
+
+        asyncServerThreads_ = 0;
+
+        coreLimit_ = 0;
+
+        if (payloadConfigBuilder_ == null) {
+          payloadConfig_ = null;
+        } else {
+          payloadConfig_ = null;
+          payloadConfigBuilder_ = null;
+        }
+        coreList_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000040);
+        otherServerApi_ = "";
+
+        resourceQuotaSize_ = 0;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerConfig_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ServerConfig getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.ServerConfig.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.ServerConfig build() {
+        io.grpc.benchmarks.proto.Control.ServerConfig result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ServerConfig buildPartial() {
+        io.grpc.benchmarks.proto.Control.ServerConfig result = new io.grpc.benchmarks.proto.Control.ServerConfig(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        result.serverType_ = serverType_;
+        if (securityParamsBuilder_ == null) {
+          result.securityParams_ = securityParams_;
+        } else {
+          result.securityParams_ = securityParamsBuilder_.build();
+        }
+        result.port_ = port_;
+        result.asyncServerThreads_ = asyncServerThreads_;
+        result.coreLimit_ = coreLimit_;
+        if (payloadConfigBuilder_ == null) {
+          result.payloadConfig_ = payloadConfig_;
+        } else {
+          result.payloadConfig_ = payloadConfigBuilder_.build();
+        }
+        if (((bitField0_ & 0x00000040) == 0x00000040)) {
+          coreList_ = java.util.Collections.unmodifiableList(coreList_);
+          bitField0_ = (bitField0_ & ~0x00000040);
+        }
+        result.coreList_ = coreList_;
+        result.otherServerApi_ = otherServerApi_;
+        result.resourceQuotaSize_ = resourceQuotaSize_;
+        result.bitField0_ = to_bitField0_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.ServerConfig) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.ServerConfig)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.ServerConfig other) {
+        if (other == io.grpc.benchmarks.proto.Control.ServerConfig.getDefaultInstance()) return this;
+        if (other.serverType_ != 0) {
+          setServerTypeValue(other.getServerTypeValue());
+        }
+        if (other.hasSecurityParams()) {
+          mergeSecurityParams(other.getSecurityParams());
+        }
+        if (other.getPort() != 0) {
+          setPort(other.getPort());
+        }
+        if (other.getAsyncServerThreads() != 0) {
+          setAsyncServerThreads(other.getAsyncServerThreads());
+        }
+        if (other.getCoreLimit() != 0) {
+          setCoreLimit(other.getCoreLimit());
+        }
+        if (other.hasPayloadConfig()) {
+          mergePayloadConfig(other.getPayloadConfig());
+        }
+        if (!other.coreList_.isEmpty()) {
+          if (coreList_.isEmpty()) {
+            coreList_ = other.coreList_;
+            bitField0_ = (bitField0_ & ~0x00000040);
+          } else {
+            ensureCoreListIsMutable();
+            coreList_.addAll(other.coreList_);
+          }
+          onChanged();
+        }
+        if (!other.getOtherServerApi().isEmpty()) {
+          otherServerApi_ = other.otherServerApi_;
+          onChanged();
+        }
+        if (other.getResourceQuotaSize() != 0) {
+          setResourceQuotaSize(other.getResourceQuotaSize());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.ServerConfig parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.ServerConfig) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private int serverType_ = 0;
+      /**
+       * <code>.grpc.testing.ServerType server_type = 1;</code>
+       */
+      public int getServerTypeValue() {
+        return serverType_;
+      }
+      /**
+       * <code>.grpc.testing.ServerType server_type = 1;</code>
+       */
+      public Builder setServerTypeValue(int value) {
+        serverType_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ServerType server_type = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ServerType getServerType() {
+        io.grpc.benchmarks.proto.Control.ServerType result = io.grpc.benchmarks.proto.Control.ServerType.valueOf(serverType_);
+        return result == null ? io.grpc.benchmarks.proto.Control.ServerType.UNRECOGNIZED : result;
+      }
+      /**
+       * <code>.grpc.testing.ServerType server_type = 1;</code>
+       */
+      public Builder setServerType(io.grpc.benchmarks.proto.Control.ServerType value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        serverType_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ServerType server_type = 1;</code>
+       */
+      public Builder clearServerType() {
+        
+        serverType_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Control.SecurityParams securityParams_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.SecurityParams, io.grpc.benchmarks.proto.Control.SecurityParams.Builder, io.grpc.benchmarks.proto.Control.SecurityParamsOrBuilder> securityParamsBuilder_;
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+       */
+      public boolean hasSecurityParams() {
+        return securityParamsBuilder_ != null || securityParams_ != null;
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.SecurityParams getSecurityParams() {
+        if (securityParamsBuilder_ == null) {
+          return securityParams_ == null ? io.grpc.benchmarks.proto.Control.SecurityParams.getDefaultInstance() : securityParams_;
+        } else {
+          return securityParamsBuilder_.getMessage();
+        }
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+       */
+      public Builder setSecurityParams(io.grpc.benchmarks.proto.Control.SecurityParams value) {
+        if (securityParamsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          securityParams_ = value;
+          onChanged();
+        } else {
+          securityParamsBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+       */
+      public Builder setSecurityParams(
+          io.grpc.benchmarks.proto.Control.SecurityParams.Builder builderForValue) {
+        if (securityParamsBuilder_ == null) {
+          securityParams_ = builderForValue.build();
+          onChanged();
+        } else {
+          securityParamsBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+       */
+      public Builder mergeSecurityParams(io.grpc.benchmarks.proto.Control.SecurityParams value) {
+        if (securityParamsBuilder_ == null) {
+          if (securityParams_ != null) {
+            securityParams_ =
+              io.grpc.benchmarks.proto.Control.SecurityParams.newBuilder(securityParams_).mergeFrom(value).buildPartial();
+          } else {
+            securityParams_ = value;
+          }
+          onChanged();
+        } else {
+          securityParamsBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+       */
+      public Builder clearSecurityParams() {
+        if (securityParamsBuilder_ == null) {
+          securityParams_ = null;
+          onChanged();
+        } else {
+          securityParams_ = null;
+          securityParamsBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.SecurityParams.Builder getSecurityParamsBuilder() {
+        
+        onChanged();
+        return getSecurityParamsFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.SecurityParamsOrBuilder getSecurityParamsOrBuilder() {
+        if (securityParamsBuilder_ != null) {
+          return securityParamsBuilder_.getMessageOrBuilder();
+        } else {
+          return securityParams_ == null ?
+              io.grpc.benchmarks.proto.Control.SecurityParams.getDefaultInstance() : securityParams_;
+        }
+      }
+      /**
+       * <code>.grpc.testing.SecurityParams security_params = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.SecurityParams, io.grpc.benchmarks.proto.Control.SecurityParams.Builder, io.grpc.benchmarks.proto.Control.SecurityParamsOrBuilder> 
+          getSecurityParamsFieldBuilder() {
+        if (securityParamsBuilder_ == null) {
+          securityParamsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.SecurityParams, io.grpc.benchmarks.proto.Control.SecurityParams.Builder, io.grpc.benchmarks.proto.Control.SecurityParamsOrBuilder>(
+                  getSecurityParams(),
+                  getParentForChildren(),
+                  isClean());
+          securityParams_ = null;
+        }
+        return securityParamsBuilder_;
+      }
+
+      private int port_ ;
+      /**
+       * <pre>
+       * Port on which to listen. Zero means pick unused port.
+       * </pre>
+       *
+       * <code>int32 port = 4;</code>
+       */
+      public int getPort() {
+        return port_;
+      }
+      /**
+       * <pre>
+       * Port on which to listen. Zero means pick unused port.
+       * </pre>
+       *
+       * <code>int32 port = 4;</code>
+       */
+      public Builder setPort(int value) {
+        
+        port_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Port on which to listen. Zero means pick unused port.
+       * </pre>
+       *
+       * <code>int32 port = 4;</code>
+       */
+      public Builder clearPort() {
+        
+        port_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int asyncServerThreads_ ;
+      /**
+       * <pre>
+       * Only for async server. Number of threads used to serve the requests.
+       * </pre>
+       *
+       * <code>int32 async_server_threads = 7;</code>
+       */
+      public int getAsyncServerThreads() {
+        return asyncServerThreads_;
+      }
+      /**
+       * <pre>
+       * Only for async server. Number of threads used to serve the requests.
+       * </pre>
+       *
+       * <code>int32 async_server_threads = 7;</code>
+       */
+      public Builder setAsyncServerThreads(int value) {
+        
+        asyncServerThreads_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Only for async server. Number of threads used to serve the requests.
+       * </pre>
+       *
+       * <code>int32 async_server_threads = 7;</code>
+       */
+      public Builder clearAsyncServerThreads() {
+        
+        asyncServerThreads_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int coreLimit_ ;
+      /**
+       * <pre>
+       * Specify the number of cores to limit server to, if desired
+       * </pre>
+       *
+       * <code>int32 core_limit = 8;</code>
+       */
+      public int getCoreLimit() {
+        return coreLimit_;
+      }
+      /**
+       * <pre>
+       * Specify the number of cores to limit server to, if desired
+       * </pre>
+       *
+       * <code>int32 core_limit = 8;</code>
+       */
+      public Builder setCoreLimit(int value) {
+        
+        coreLimit_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Specify the number of cores to limit server to, if desired
+       * </pre>
+       *
+       * <code>int32 core_limit = 8;</code>
+       */
+      public Builder clearCoreLimit() {
+        
+        coreLimit_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Payloads.PayloadConfig payloadConfig_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Payloads.PayloadConfig, io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder, io.grpc.benchmarks.proto.Payloads.PayloadConfigOrBuilder> payloadConfigBuilder_;
+      /**
+       * <pre>
+       * payload config, used in generic server.
+       * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+       * 'response sizes' must be configured from the 'response_size' field of the
+       * 'SimpleRequest' objects in RPC requests.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+       */
+      public boolean hasPayloadConfig() {
+        return payloadConfigBuilder_ != null || payloadConfig_ != null;
+      }
+      /**
+       * <pre>
+       * payload config, used in generic server.
+       * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+       * 'response sizes' must be configured from the 'response_size' field of the
+       * 'SimpleRequest' objects in RPC requests.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.PayloadConfig getPayloadConfig() {
+        if (payloadConfigBuilder_ == null) {
+          return payloadConfig_ == null ? io.grpc.benchmarks.proto.Payloads.PayloadConfig.getDefaultInstance() : payloadConfig_;
+        } else {
+          return payloadConfigBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * payload config, used in generic server.
+       * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+       * 'response sizes' must be configured from the 'response_size' field of the
+       * 'SimpleRequest' objects in RPC requests.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+       */
+      public Builder setPayloadConfig(io.grpc.benchmarks.proto.Payloads.PayloadConfig value) {
+        if (payloadConfigBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payloadConfig_ = value;
+          onChanged();
+        } else {
+          payloadConfigBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * payload config, used in generic server.
+       * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+       * 'response sizes' must be configured from the 'response_size' field of the
+       * 'SimpleRequest' objects in RPC requests.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+       */
+      public Builder setPayloadConfig(
+          io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder builderForValue) {
+        if (payloadConfigBuilder_ == null) {
+          payloadConfig_ = builderForValue.build();
+          onChanged();
+        } else {
+          payloadConfigBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * payload config, used in generic server.
+       * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+       * 'response sizes' must be configured from the 'response_size' field of the
+       * 'SimpleRequest' objects in RPC requests.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+       */
+      public Builder mergePayloadConfig(io.grpc.benchmarks.proto.Payloads.PayloadConfig value) {
+        if (payloadConfigBuilder_ == null) {
+          if (payloadConfig_ != null) {
+            payloadConfig_ =
+              io.grpc.benchmarks.proto.Payloads.PayloadConfig.newBuilder(payloadConfig_).mergeFrom(value).buildPartial();
+          } else {
+            payloadConfig_ = value;
+          }
+          onChanged();
+        } else {
+          payloadConfigBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * payload config, used in generic server.
+       * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+       * 'response sizes' must be configured from the 'response_size' field of the
+       * 'SimpleRequest' objects in RPC requests.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+       */
+      public Builder clearPayloadConfig() {
+        if (payloadConfigBuilder_ == null) {
+          payloadConfig_ = null;
+          onChanged();
+        } else {
+          payloadConfig_ = null;
+          payloadConfigBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * payload config, used in generic server.
+       * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+       * 'response sizes' must be configured from the 'response_size' field of the
+       * 'SimpleRequest' objects in RPC requests.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder getPayloadConfigBuilder() {
+        
+        onChanged();
+        return getPayloadConfigFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * payload config, used in generic server.
+       * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+       * 'response sizes' must be configured from the 'response_size' field of the
+       * 'SimpleRequest' objects in RPC requests.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.PayloadConfigOrBuilder getPayloadConfigOrBuilder() {
+        if (payloadConfigBuilder_ != null) {
+          return payloadConfigBuilder_.getMessageOrBuilder();
+        } else {
+          return payloadConfig_ == null ?
+              io.grpc.benchmarks.proto.Payloads.PayloadConfig.getDefaultInstance() : payloadConfig_;
+        }
+      }
+      /**
+       * <pre>
+       * payload config, used in generic server.
+       * Note this must NOT be used in proto (non-generic) servers. For proto servers,
+       * 'response sizes' must be configured from the 'response_size' field of the
+       * 'SimpleRequest' objects in RPC requests.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadConfig payload_config = 9;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Payloads.PayloadConfig, io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder, io.grpc.benchmarks.proto.Payloads.PayloadConfigOrBuilder> 
+          getPayloadConfigFieldBuilder() {
+        if (payloadConfigBuilder_ == null) {
+          payloadConfigBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Payloads.PayloadConfig, io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder, io.grpc.benchmarks.proto.Payloads.PayloadConfigOrBuilder>(
+                  getPayloadConfig(),
+                  getParentForChildren(),
+                  isClean());
+          payloadConfig_ = null;
+        }
+        return payloadConfigBuilder_;
+      }
+
+      private java.util.List<java.lang.Integer> coreList_ = java.util.Collections.emptyList();
+      private void ensureCoreListIsMutable() {
+        if (!((bitField0_ & 0x00000040) == 0x00000040)) {
+          coreList_ = new java.util.ArrayList<java.lang.Integer>(coreList_);
+          bitField0_ |= 0x00000040;
+         }
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the server on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 10;</code>
+       */
+      public java.util.List<java.lang.Integer>
+          getCoreListList() {
+        return java.util.Collections.unmodifiableList(coreList_);
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the server on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 10;</code>
+       */
+      public int getCoreListCount() {
+        return coreList_.size();
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the server on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 10;</code>
+       */
+      public int getCoreList(int index) {
+        return coreList_.get(index);
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the server on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 10;</code>
+       */
+      public Builder setCoreList(
+          int index, int value) {
+        ensureCoreListIsMutable();
+        coreList_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the server on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 10;</code>
+       */
+      public Builder addCoreList(int value) {
+        ensureCoreListIsMutable();
+        coreList_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the server on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 10;</code>
+       */
+      public Builder addAllCoreList(
+          java.lang.Iterable<? extends java.lang.Integer> values) {
+        ensureCoreListIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, coreList_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Specify the cores we should run the server on, if desired
+       * </pre>
+       *
+       * <code>repeated int32 core_list = 10;</code>
+       */
+      public Builder clearCoreList() {
+        coreList_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000040);
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object otherServerApi_ = "";
+      /**
+       * <pre>
+       * If we use an OTHER_SERVER client_type, this string gives more detail
+       * </pre>
+       *
+       * <code>string other_server_api = 11;</code>
+       */
+      public java.lang.String getOtherServerApi() {
+        java.lang.Object ref = otherServerApi_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          otherServerApi_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * If we use an OTHER_SERVER client_type, this string gives more detail
+       * </pre>
+       *
+       * <code>string other_server_api = 11;</code>
+       */
+      public com.google.protobuf.ByteString
+          getOtherServerApiBytes() {
+        java.lang.Object ref = otherServerApi_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          otherServerApi_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * If we use an OTHER_SERVER client_type, this string gives more detail
+       * </pre>
+       *
+       * <code>string other_server_api = 11;</code>
+       */
+      public Builder setOtherServerApi(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        otherServerApi_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * If we use an OTHER_SERVER client_type, this string gives more detail
+       * </pre>
+       *
+       * <code>string other_server_api = 11;</code>
+       */
+      public Builder clearOtherServerApi() {
+        
+        otherServerApi_ = getDefaultInstance().getOtherServerApi();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * If we use an OTHER_SERVER client_type, this string gives more detail
+       * </pre>
+       *
+       * <code>string other_server_api = 11;</code>
+       */
+      public Builder setOtherServerApiBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        otherServerApi_ = value;
+        onChanged();
+        return this;
+      }
+
+      private int resourceQuotaSize_ ;
+      /**
+       * <pre>
+       * Buffer pool size (no buffer pool specified if unset)
+       * </pre>
+       *
+       * <code>int32 resource_quota_size = 1001;</code>
+       */
+      public int getResourceQuotaSize() {
+        return resourceQuotaSize_;
+      }
+      /**
+       * <pre>
+       * Buffer pool size (no buffer pool specified if unset)
+       * </pre>
+       *
+       * <code>int32 resource_quota_size = 1001;</code>
+       */
+      public Builder setResourceQuotaSize(int value) {
+        
+        resourceQuotaSize_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Buffer pool size (no buffer pool specified if unset)
+       * </pre>
+       *
+       * <code>int32 resource_quota_size = 1001;</code>
+       */
+      public Builder clearResourceQuotaSize() {
+        
+        resourceQuotaSize_ = 0;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ServerConfig)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ServerConfig)
+    private static final io.grpc.benchmarks.proto.Control.ServerConfig DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.ServerConfig();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ServerConfig getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ServerConfig>
+        PARSER = new com.google.protobuf.AbstractParser<ServerConfig>() {
+      public ServerConfig parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ServerConfig(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ServerConfig> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ServerConfig> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.ServerConfig getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ServerArgsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ServerArgs)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>.grpc.testing.ServerConfig setup = 1;</code>
+     */
+    boolean hasSetup();
+    /**
+     * <code>.grpc.testing.ServerConfig setup = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ServerConfig getSetup();
+    /**
+     * <code>.grpc.testing.ServerConfig setup = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ServerConfigOrBuilder getSetupOrBuilder();
+
+    /**
+     * <code>.grpc.testing.Mark mark = 2;</code>
+     */
+    boolean hasMark();
+    /**
+     * <code>.grpc.testing.Mark mark = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Control.Mark getMark();
+    /**
+     * <code>.grpc.testing.Mark mark = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Control.MarkOrBuilder getMarkOrBuilder();
+
+    public io.grpc.benchmarks.proto.Control.ServerArgs.ArgtypeCase getArgtypeCase();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.ServerArgs}
+   */
+  public  static final class ServerArgs extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ServerArgs)
+      ServerArgsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ServerArgs.newBuilder() to construct.
+    private ServerArgs(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ServerArgs() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ServerArgs(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.benchmarks.proto.Control.ServerConfig.Builder subBuilder = null;
+              if (argtypeCase_ == 1) {
+                subBuilder = ((io.grpc.benchmarks.proto.Control.ServerConfig) argtype_).toBuilder();
+              }
+              argtype_ =
+                  input.readMessage(io.grpc.benchmarks.proto.Control.ServerConfig.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom((io.grpc.benchmarks.proto.Control.ServerConfig) argtype_);
+                argtype_ = subBuilder.buildPartial();
+              }
+              argtypeCase_ = 1;
+              break;
+            }
+            case 18: {
+              io.grpc.benchmarks.proto.Control.Mark.Builder subBuilder = null;
+              if (argtypeCase_ == 2) {
+                subBuilder = ((io.grpc.benchmarks.proto.Control.Mark) argtype_).toBuilder();
+              }
+              argtype_ =
+                  input.readMessage(io.grpc.benchmarks.proto.Control.Mark.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom((io.grpc.benchmarks.proto.Control.Mark) argtype_);
+                argtype_ = subBuilder.buildPartial();
+              }
+              argtypeCase_ = 2;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerArgs_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerArgs_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.ServerArgs.class, io.grpc.benchmarks.proto.Control.ServerArgs.Builder.class);
+    }
+
+    private int argtypeCase_ = 0;
+    private java.lang.Object argtype_;
+    public enum ArgtypeCase
+        implements com.google.protobuf.Internal.EnumLite {
+      SETUP(1),
+      MARK(2),
+      ARGTYPE_NOT_SET(0);
+      private final int value;
+      private ArgtypeCase(int value) {
+        this.value = value;
+      }
+      /**
+       * @deprecated Use {@link #forNumber(int)} instead.
+       */
+      @java.lang.Deprecated
+      public static ArgtypeCase valueOf(int value) {
+        return forNumber(value);
+      }
+
+      public static ArgtypeCase forNumber(int value) {
+        switch (value) {
+          case 1: return SETUP;
+          case 2: return MARK;
+          case 0: return ARGTYPE_NOT_SET;
+          default: return null;
+        }
+      }
+      public int getNumber() {
+        return this.value;
+      }
+    };
+
+    public ArgtypeCase
+    getArgtypeCase() {
+      return ArgtypeCase.forNumber(
+          argtypeCase_);
+    }
+
+    public static final int SETUP_FIELD_NUMBER = 1;
+    /**
+     * <code>.grpc.testing.ServerConfig setup = 1;</code>
+     */
+    public boolean hasSetup() {
+      return argtypeCase_ == 1;
+    }
+    /**
+     * <code>.grpc.testing.ServerConfig setup = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ServerConfig getSetup() {
+      if (argtypeCase_ == 1) {
+         return (io.grpc.benchmarks.proto.Control.ServerConfig) argtype_;
+      }
+      return io.grpc.benchmarks.proto.Control.ServerConfig.getDefaultInstance();
+    }
+    /**
+     * <code>.grpc.testing.ServerConfig setup = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ServerConfigOrBuilder getSetupOrBuilder() {
+      if (argtypeCase_ == 1) {
+         return (io.grpc.benchmarks.proto.Control.ServerConfig) argtype_;
+      }
+      return io.grpc.benchmarks.proto.Control.ServerConfig.getDefaultInstance();
+    }
+
+    public static final int MARK_FIELD_NUMBER = 2;
+    /**
+     * <code>.grpc.testing.Mark mark = 2;</code>
+     */
+    public boolean hasMark() {
+      return argtypeCase_ == 2;
+    }
+    /**
+     * <code>.grpc.testing.Mark mark = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.Mark getMark() {
+      if (argtypeCase_ == 2) {
+         return (io.grpc.benchmarks.proto.Control.Mark) argtype_;
+      }
+      return io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance();
+    }
+    /**
+     * <code>.grpc.testing.Mark mark = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.MarkOrBuilder getMarkOrBuilder() {
+      if (argtypeCase_ == 2) {
+         return (io.grpc.benchmarks.proto.Control.Mark) argtype_;
+      }
+      return io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (argtypeCase_ == 1) {
+        output.writeMessage(1, (io.grpc.benchmarks.proto.Control.ServerConfig) argtype_);
+      }
+      if (argtypeCase_ == 2) {
+        output.writeMessage(2, (io.grpc.benchmarks.proto.Control.Mark) argtype_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (argtypeCase_ == 1) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, (io.grpc.benchmarks.proto.Control.ServerConfig) argtype_);
+      }
+      if (argtypeCase_ == 2) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, (io.grpc.benchmarks.proto.Control.Mark) argtype_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.ServerArgs)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.ServerArgs other = (io.grpc.benchmarks.proto.Control.ServerArgs) obj;
+
+      boolean result = true;
+      result = result && getArgtypeCase().equals(
+          other.getArgtypeCase());
+      if (!result) return false;
+      switch (argtypeCase_) {
+        case 1:
+          result = result && getSetup()
+              .equals(other.getSetup());
+          break;
+        case 2:
+          result = result && getMark()
+              .equals(other.getMark());
+          break;
+        case 0:
+        default:
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      switch (argtypeCase_) {
+        case 1:
+          hash = (37 * hash) + SETUP_FIELD_NUMBER;
+          hash = (53 * hash) + getSetup().hashCode();
+          break;
+        case 2:
+          hash = (37 * hash) + MARK_FIELD_NUMBER;
+          hash = (53 * hash) + getMark().hashCode();
+          break;
+        case 0:
+        default:
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ServerArgs parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerArgs parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerArgs parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerArgs parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerArgs parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerArgs parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerArgs parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerArgs parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerArgs parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerArgs parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerArgs parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerArgs parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.ServerArgs prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.ServerArgs}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ServerArgs)
+        io.grpc.benchmarks.proto.Control.ServerArgsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerArgs_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerArgs_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.ServerArgs.class, io.grpc.benchmarks.proto.Control.ServerArgs.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.ServerArgs.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        argtypeCase_ = 0;
+        argtype_ = null;
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerArgs_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ServerArgs getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.ServerArgs.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.ServerArgs build() {
+        io.grpc.benchmarks.proto.Control.ServerArgs result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ServerArgs buildPartial() {
+        io.grpc.benchmarks.proto.Control.ServerArgs result = new io.grpc.benchmarks.proto.Control.ServerArgs(this);
+        if (argtypeCase_ == 1) {
+          if (setupBuilder_ == null) {
+            result.argtype_ = argtype_;
+          } else {
+            result.argtype_ = setupBuilder_.build();
+          }
+        }
+        if (argtypeCase_ == 2) {
+          if (markBuilder_ == null) {
+            result.argtype_ = argtype_;
+          } else {
+            result.argtype_ = markBuilder_.build();
+          }
+        }
+        result.argtypeCase_ = argtypeCase_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.ServerArgs) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.ServerArgs)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.ServerArgs other) {
+        if (other == io.grpc.benchmarks.proto.Control.ServerArgs.getDefaultInstance()) return this;
+        switch (other.getArgtypeCase()) {
+          case SETUP: {
+            mergeSetup(other.getSetup());
+            break;
+          }
+          case MARK: {
+            mergeMark(other.getMark());
+            break;
+          }
+          case ARGTYPE_NOT_SET: {
+            break;
+          }
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.ServerArgs parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.ServerArgs) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int argtypeCase_ = 0;
+      private java.lang.Object argtype_;
+      public ArgtypeCase
+          getArgtypeCase() {
+        return ArgtypeCase.forNumber(
+            argtypeCase_);
+      }
+
+      public Builder clearArgtype() {
+        argtypeCase_ = 0;
+        argtype_ = null;
+        onChanged();
+        return this;
+      }
+
+
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ServerConfig, io.grpc.benchmarks.proto.Control.ServerConfig.Builder, io.grpc.benchmarks.proto.Control.ServerConfigOrBuilder> setupBuilder_;
+      /**
+       * <code>.grpc.testing.ServerConfig setup = 1;</code>
+       */
+      public boolean hasSetup() {
+        return argtypeCase_ == 1;
+      }
+      /**
+       * <code>.grpc.testing.ServerConfig setup = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ServerConfig getSetup() {
+        if (setupBuilder_ == null) {
+          if (argtypeCase_ == 1) {
+            return (io.grpc.benchmarks.proto.Control.ServerConfig) argtype_;
+          }
+          return io.grpc.benchmarks.proto.Control.ServerConfig.getDefaultInstance();
+        } else {
+          if (argtypeCase_ == 1) {
+            return setupBuilder_.getMessage();
+          }
+          return io.grpc.benchmarks.proto.Control.ServerConfig.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.ServerConfig setup = 1;</code>
+       */
+      public Builder setSetup(io.grpc.benchmarks.proto.Control.ServerConfig value) {
+        if (setupBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          argtype_ = value;
+          onChanged();
+        } else {
+          setupBuilder_.setMessage(value);
+        }
+        argtypeCase_ = 1;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ServerConfig setup = 1;</code>
+       */
+      public Builder setSetup(
+          io.grpc.benchmarks.proto.Control.ServerConfig.Builder builderForValue) {
+        if (setupBuilder_ == null) {
+          argtype_ = builderForValue.build();
+          onChanged();
+        } else {
+          setupBuilder_.setMessage(builderForValue.build());
+        }
+        argtypeCase_ = 1;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ServerConfig setup = 1;</code>
+       */
+      public Builder mergeSetup(io.grpc.benchmarks.proto.Control.ServerConfig value) {
+        if (setupBuilder_ == null) {
+          if (argtypeCase_ == 1 &&
+              argtype_ != io.grpc.benchmarks.proto.Control.ServerConfig.getDefaultInstance()) {
+            argtype_ = io.grpc.benchmarks.proto.Control.ServerConfig.newBuilder((io.grpc.benchmarks.proto.Control.ServerConfig) argtype_)
+                .mergeFrom(value).buildPartial();
+          } else {
+            argtype_ = value;
+          }
+          onChanged();
+        } else {
+          if (argtypeCase_ == 1) {
+            setupBuilder_.mergeFrom(value);
+          }
+          setupBuilder_.setMessage(value);
+        }
+        argtypeCase_ = 1;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ServerConfig setup = 1;</code>
+       */
+      public Builder clearSetup() {
+        if (setupBuilder_ == null) {
+          if (argtypeCase_ == 1) {
+            argtypeCase_ = 0;
+            argtype_ = null;
+            onChanged();
+          }
+        } else {
+          if (argtypeCase_ == 1) {
+            argtypeCase_ = 0;
+            argtype_ = null;
+          }
+          setupBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ServerConfig setup = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ServerConfig.Builder getSetupBuilder() {
+        return getSetupFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.ServerConfig setup = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ServerConfigOrBuilder getSetupOrBuilder() {
+        if ((argtypeCase_ == 1) && (setupBuilder_ != null)) {
+          return setupBuilder_.getMessageOrBuilder();
+        } else {
+          if (argtypeCase_ == 1) {
+            return (io.grpc.benchmarks.proto.Control.ServerConfig) argtype_;
+          }
+          return io.grpc.benchmarks.proto.Control.ServerConfig.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.ServerConfig setup = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ServerConfig, io.grpc.benchmarks.proto.Control.ServerConfig.Builder, io.grpc.benchmarks.proto.Control.ServerConfigOrBuilder> 
+          getSetupFieldBuilder() {
+        if (setupBuilder_ == null) {
+          if (!(argtypeCase_ == 1)) {
+            argtype_ = io.grpc.benchmarks.proto.Control.ServerConfig.getDefaultInstance();
+          }
+          setupBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.ServerConfig, io.grpc.benchmarks.proto.Control.ServerConfig.Builder, io.grpc.benchmarks.proto.Control.ServerConfigOrBuilder>(
+                  (io.grpc.benchmarks.proto.Control.ServerConfig) argtype_,
+                  getParentForChildren(),
+                  isClean());
+          argtype_ = null;
+        }
+        argtypeCase_ = 1;
+        onChanged();;
+        return setupBuilder_;
+      }
+
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.Mark, io.grpc.benchmarks.proto.Control.Mark.Builder, io.grpc.benchmarks.proto.Control.MarkOrBuilder> markBuilder_;
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public boolean hasMark() {
+        return argtypeCase_ == 2;
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.Mark getMark() {
+        if (markBuilder_ == null) {
+          if (argtypeCase_ == 2) {
+            return (io.grpc.benchmarks.proto.Control.Mark) argtype_;
+          }
+          return io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance();
+        } else {
+          if (argtypeCase_ == 2) {
+            return markBuilder_.getMessage();
+          }
+          return io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public Builder setMark(io.grpc.benchmarks.proto.Control.Mark value) {
+        if (markBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          argtype_ = value;
+          onChanged();
+        } else {
+          markBuilder_.setMessage(value);
+        }
+        argtypeCase_ = 2;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public Builder setMark(
+          io.grpc.benchmarks.proto.Control.Mark.Builder builderForValue) {
+        if (markBuilder_ == null) {
+          argtype_ = builderForValue.build();
+          onChanged();
+        } else {
+          markBuilder_.setMessage(builderForValue.build());
+        }
+        argtypeCase_ = 2;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public Builder mergeMark(io.grpc.benchmarks.proto.Control.Mark value) {
+        if (markBuilder_ == null) {
+          if (argtypeCase_ == 2 &&
+              argtype_ != io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance()) {
+            argtype_ = io.grpc.benchmarks.proto.Control.Mark.newBuilder((io.grpc.benchmarks.proto.Control.Mark) argtype_)
+                .mergeFrom(value).buildPartial();
+          } else {
+            argtype_ = value;
+          }
+          onChanged();
+        } else {
+          if (argtypeCase_ == 2) {
+            markBuilder_.mergeFrom(value);
+          }
+          markBuilder_.setMessage(value);
+        }
+        argtypeCase_ = 2;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public Builder clearMark() {
+        if (markBuilder_ == null) {
+          if (argtypeCase_ == 2) {
+            argtypeCase_ = 0;
+            argtype_ = null;
+            onChanged();
+          }
+        } else {
+          if (argtypeCase_ == 2) {
+            argtypeCase_ = 0;
+            argtype_ = null;
+          }
+          markBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.Mark.Builder getMarkBuilder() {
+        return getMarkFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.MarkOrBuilder getMarkOrBuilder() {
+        if ((argtypeCase_ == 2) && (markBuilder_ != null)) {
+          return markBuilder_.getMessageOrBuilder();
+        } else {
+          if (argtypeCase_ == 2) {
+            return (io.grpc.benchmarks.proto.Control.Mark) argtype_;
+          }
+          return io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.Mark mark = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.Mark, io.grpc.benchmarks.proto.Control.Mark.Builder, io.grpc.benchmarks.proto.Control.MarkOrBuilder> 
+          getMarkFieldBuilder() {
+        if (markBuilder_ == null) {
+          if (!(argtypeCase_ == 2)) {
+            argtype_ = io.grpc.benchmarks.proto.Control.Mark.getDefaultInstance();
+          }
+          markBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.Mark, io.grpc.benchmarks.proto.Control.Mark.Builder, io.grpc.benchmarks.proto.Control.MarkOrBuilder>(
+                  (io.grpc.benchmarks.proto.Control.Mark) argtype_,
+                  getParentForChildren(),
+                  isClean());
+          argtype_ = null;
+        }
+        argtypeCase_ = 2;
+        onChanged();;
+        return markBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ServerArgs)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ServerArgs)
+    private static final io.grpc.benchmarks.proto.Control.ServerArgs DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.ServerArgs();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ServerArgs getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ServerArgs>
+        PARSER = new com.google.protobuf.AbstractParser<ServerArgs>() {
+      public ServerArgs parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ServerArgs(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ServerArgs> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ServerArgs> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.ServerArgs getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ServerStatusOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ServerStatus)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>.grpc.testing.ServerStats stats = 1;</code>
+     */
+    boolean hasStats();
+    /**
+     * <code>.grpc.testing.ServerStats stats = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.ServerStats getStats();
+    /**
+     * <code>.grpc.testing.ServerStats stats = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder getStatsOrBuilder();
+
+    /**
+     * <pre>
+     * the port bound by the server
+     * </pre>
+     *
+     * <code>int32 port = 2;</code>
+     */
+    int getPort();
+
+    /**
+     * <pre>
+     * Number of cores available to the server
+     * </pre>
+     *
+     * <code>int32 cores = 3;</code>
+     */
+    int getCores();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.ServerStatus}
+   */
+  public  static final class ServerStatus extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ServerStatus)
+      ServerStatusOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ServerStatus.newBuilder() to construct.
+    private ServerStatus(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ServerStatus() {
+      port_ = 0;
+      cores_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ServerStatus(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.benchmarks.proto.Stats.ServerStats.Builder subBuilder = null;
+              if (stats_ != null) {
+                subBuilder = stats_.toBuilder();
+              }
+              stats_ = input.readMessage(io.grpc.benchmarks.proto.Stats.ServerStats.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(stats_);
+                stats_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 16: {
+
+              port_ = input.readInt32();
+              break;
+            }
+            case 24: {
+
+              cores_ = input.readInt32();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerStatus_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerStatus_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.ServerStatus.class, io.grpc.benchmarks.proto.Control.ServerStatus.Builder.class);
+    }
+
+    public static final int STATS_FIELD_NUMBER = 1;
+    private io.grpc.benchmarks.proto.Stats.ServerStats stats_;
+    /**
+     * <code>.grpc.testing.ServerStats stats = 1;</code>
+     */
+    public boolean hasStats() {
+      return stats_ != null;
+    }
+    /**
+     * <code>.grpc.testing.ServerStats stats = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.ServerStats getStats() {
+      return stats_ == null ? io.grpc.benchmarks.proto.Stats.ServerStats.getDefaultInstance() : stats_;
+    }
+    /**
+     * <code>.grpc.testing.ServerStats stats = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder getStatsOrBuilder() {
+      return getStats();
+    }
+
+    public static final int PORT_FIELD_NUMBER = 2;
+    private int port_;
+    /**
+     * <pre>
+     * the port bound by the server
+     * </pre>
+     *
+     * <code>int32 port = 2;</code>
+     */
+    public int getPort() {
+      return port_;
+    }
+
+    public static final int CORES_FIELD_NUMBER = 3;
+    private int cores_;
+    /**
+     * <pre>
+     * Number of cores available to the server
+     * </pre>
+     *
+     * <code>int32 cores = 3;</code>
+     */
+    public int getCores() {
+      return cores_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (stats_ != null) {
+        output.writeMessage(1, getStats());
+      }
+      if (port_ != 0) {
+        output.writeInt32(2, port_);
+      }
+      if (cores_ != 0) {
+        output.writeInt32(3, cores_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (stats_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, getStats());
+      }
+      if (port_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(2, port_);
+      }
+      if (cores_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(3, cores_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.ServerStatus)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.ServerStatus other = (io.grpc.benchmarks.proto.Control.ServerStatus) obj;
+
+      boolean result = true;
+      result = result && (hasStats() == other.hasStats());
+      if (hasStats()) {
+        result = result && getStats()
+            .equals(other.getStats());
+      }
+      result = result && (getPort()
+          == other.getPort());
+      result = result && (getCores()
+          == other.getCores());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (hasStats()) {
+        hash = (37 * hash) + STATS_FIELD_NUMBER;
+        hash = (53 * hash) + getStats().hashCode();
+      }
+      hash = (37 * hash) + PORT_FIELD_NUMBER;
+      hash = (53 * hash) + getPort();
+      hash = (37 * hash) + CORES_FIELD_NUMBER;
+      hash = (53 * hash) + getCores();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ServerStatus parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerStatus parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerStatus parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerStatus parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerStatus parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerStatus parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerStatus parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerStatus parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerStatus parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerStatus parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerStatus parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ServerStatus parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.ServerStatus prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.ServerStatus}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ServerStatus)
+        io.grpc.benchmarks.proto.Control.ServerStatusOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerStatus_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerStatus_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.ServerStatus.class, io.grpc.benchmarks.proto.Control.ServerStatus.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.ServerStatus.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        if (statsBuilder_ == null) {
+          stats_ = null;
+        } else {
+          stats_ = null;
+          statsBuilder_ = null;
+        }
+        port_ = 0;
+
+        cores_ = 0;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ServerStatus_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ServerStatus getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.ServerStatus.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.ServerStatus build() {
+        io.grpc.benchmarks.proto.Control.ServerStatus result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ServerStatus buildPartial() {
+        io.grpc.benchmarks.proto.Control.ServerStatus result = new io.grpc.benchmarks.proto.Control.ServerStatus(this);
+        if (statsBuilder_ == null) {
+          result.stats_ = stats_;
+        } else {
+          result.stats_ = statsBuilder_.build();
+        }
+        result.port_ = port_;
+        result.cores_ = cores_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.ServerStatus) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.ServerStatus)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.ServerStatus other) {
+        if (other == io.grpc.benchmarks.proto.Control.ServerStatus.getDefaultInstance()) return this;
+        if (other.hasStats()) {
+          mergeStats(other.getStats());
+        }
+        if (other.getPort() != 0) {
+          setPort(other.getPort());
+        }
+        if (other.getCores() != 0) {
+          setCores(other.getCores());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.ServerStatus parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.ServerStatus) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Stats.ServerStats stats_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.ServerStats, io.grpc.benchmarks.proto.Stats.ServerStats.Builder, io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder> statsBuilder_;
+      /**
+       * <code>.grpc.testing.ServerStats stats = 1;</code>
+       */
+      public boolean hasStats() {
+        return statsBuilder_ != null || stats_ != null;
+      }
+      /**
+       * <code>.grpc.testing.ServerStats stats = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ServerStats getStats() {
+        if (statsBuilder_ == null) {
+          return stats_ == null ? io.grpc.benchmarks.proto.Stats.ServerStats.getDefaultInstance() : stats_;
+        } else {
+          return statsBuilder_.getMessage();
+        }
+      }
+      /**
+       * <code>.grpc.testing.ServerStats stats = 1;</code>
+       */
+      public Builder setStats(io.grpc.benchmarks.proto.Stats.ServerStats value) {
+        if (statsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          stats_ = value;
+          onChanged();
+        } else {
+          statsBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ServerStats stats = 1;</code>
+       */
+      public Builder setStats(
+          io.grpc.benchmarks.proto.Stats.ServerStats.Builder builderForValue) {
+        if (statsBuilder_ == null) {
+          stats_ = builderForValue.build();
+          onChanged();
+        } else {
+          statsBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ServerStats stats = 1;</code>
+       */
+      public Builder mergeStats(io.grpc.benchmarks.proto.Stats.ServerStats value) {
+        if (statsBuilder_ == null) {
+          if (stats_ != null) {
+            stats_ =
+              io.grpc.benchmarks.proto.Stats.ServerStats.newBuilder(stats_).mergeFrom(value).buildPartial();
+          } else {
+            stats_ = value;
+          }
+          onChanged();
+        } else {
+          statsBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ServerStats stats = 1;</code>
+       */
+      public Builder clearStats() {
+        if (statsBuilder_ == null) {
+          stats_ = null;
+          onChanged();
+        } else {
+          stats_ = null;
+          statsBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ServerStats stats = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ServerStats.Builder getStatsBuilder() {
+        
+        onChanged();
+        return getStatsFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.ServerStats stats = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder getStatsOrBuilder() {
+        if (statsBuilder_ != null) {
+          return statsBuilder_.getMessageOrBuilder();
+        } else {
+          return stats_ == null ?
+              io.grpc.benchmarks.proto.Stats.ServerStats.getDefaultInstance() : stats_;
+        }
+      }
+      /**
+       * <code>.grpc.testing.ServerStats stats = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.ServerStats, io.grpc.benchmarks.proto.Stats.ServerStats.Builder, io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder> 
+          getStatsFieldBuilder() {
+        if (statsBuilder_ == null) {
+          statsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Stats.ServerStats, io.grpc.benchmarks.proto.Stats.ServerStats.Builder, io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder>(
+                  getStats(),
+                  getParentForChildren(),
+                  isClean());
+          stats_ = null;
+        }
+        return statsBuilder_;
+      }
+
+      private int port_ ;
+      /**
+       * <pre>
+       * the port bound by the server
+       * </pre>
+       *
+       * <code>int32 port = 2;</code>
+       */
+      public int getPort() {
+        return port_;
+      }
+      /**
+       * <pre>
+       * the port bound by the server
+       * </pre>
+       *
+       * <code>int32 port = 2;</code>
+       */
+      public Builder setPort(int value) {
+        
+        port_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * the port bound by the server
+       * </pre>
+       *
+       * <code>int32 port = 2;</code>
+       */
+      public Builder clearPort() {
+        
+        port_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int cores_ ;
+      /**
+       * <pre>
+       * Number of cores available to the server
+       * </pre>
+       *
+       * <code>int32 cores = 3;</code>
+       */
+      public int getCores() {
+        return cores_;
+      }
+      /**
+       * <pre>
+       * Number of cores available to the server
+       * </pre>
+       *
+       * <code>int32 cores = 3;</code>
+       */
+      public Builder setCores(int value) {
+        
+        cores_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of cores available to the server
+       * </pre>
+       *
+       * <code>int32 cores = 3;</code>
+       */
+      public Builder clearCores() {
+        
+        cores_ = 0;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ServerStatus)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ServerStatus)
+    private static final io.grpc.benchmarks.proto.Control.ServerStatus DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.ServerStatus();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ServerStatus getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ServerStatus>
+        PARSER = new com.google.protobuf.AbstractParser<ServerStatus>() {
+      public ServerStatus parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ServerStatus(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ServerStatus> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ServerStatus> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.ServerStatus getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface CoreRequestOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.CoreRequest)
+      com.google.protobuf.MessageOrBuilder {
+  }
+  /**
+   * Protobuf type {@code grpc.testing.CoreRequest}
+   */
+  public  static final class CoreRequest extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.CoreRequest)
+      CoreRequestOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use CoreRequest.newBuilder() to construct.
+    private CoreRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private CoreRequest() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private CoreRequest(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_CoreRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_CoreRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.CoreRequest.class, io.grpc.benchmarks.proto.Control.CoreRequest.Builder.class);
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.CoreRequest)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.CoreRequest other = (io.grpc.benchmarks.proto.Control.CoreRequest) obj;
+
+      boolean result = true;
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.CoreRequest parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreRequest parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreRequest parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreRequest parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreRequest parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreRequest parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreRequest parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreRequest parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreRequest parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreRequest parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreRequest parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreRequest parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.CoreRequest prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.CoreRequest}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.CoreRequest)
+        io.grpc.benchmarks.proto.Control.CoreRequestOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_CoreRequest_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_CoreRequest_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.CoreRequest.class, io.grpc.benchmarks.proto.Control.CoreRequest.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.CoreRequest.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_CoreRequest_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.CoreRequest getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.CoreRequest.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.CoreRequest build() {
+        io.grpc.benchmarks.proto.Control.CoreRequest result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.CoreRequest buildPartial() {
+        io.grpc.benchmarks.proto.Control.CoreRequest result = new io.grpc.benchmarks.proto.Control.CoreRequest(this);
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.CoreRequest) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.CoreRequest)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.CoreRequest other) {
+        if (other == io.grpc.benchmarks.proto.Control.CoreRequest.getDefaultInstance()) return this;
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.CoreRequest parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.CoreRequest) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.CoreRequest)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.CoreRequest)
+    private static final io.grpc.benchmarks.proto.Control.CoreRequest DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.CoreRequest();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.CoreRequest getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<CoreRequest>
+        PARSER = new com.google.protobuf.AbstractParser<CoreRequest>() {
+      public CoreRequest parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new CoreRequest(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<CoreRequest> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<CoreRequest> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.CoreRequest getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface CoreResponseOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.CoreResponse)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Number of cores available on the server
+     * </pre>
+     *
+     * <code>int32 cores = 1;</code>
+     */
+    int getCores();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.CoreResponse}
+   */
+  public  static final class CoreResponse extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.CoreResponse)
+      CoreResponseOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use CoreResponse.newBuilder() to construct.
+    private CoreResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private CoreResponse() {
+      cores_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private CoreResponse(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              cores_ = input.readInt32();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_CoreResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_CoreResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.CoreResponse.class, io.grpc.benchmarks.proto.Control.CoreResponse.Builder.class);
+    }
+
+    public static final int CORES_FIELD_NUMBER = 1;
+    private int cores_;
+    /**
+     * <pre>
+     * Number of cores available on the server
+     * </pre>
+     *
+     * <code>int32 cores = 1;</code>
+     */
+    public int getCores() {
+      return cores_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (cores_ != 0) {
+        output.writeInt32(1, cores_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (cores_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1, cores_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.CoreResponse)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.CoreResponse other = (io.grpc.benchmarks.proto.Control.CoreResponse) obj;
+
+      boolean result = true;
+      result = result && (getCores()
+          == other.getCores());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + CORES_FIELD_NUMBER;
+      hash = (53 * hash) + getCores();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.CoreResponse parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreResponse parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreResponse parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreResponse parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreResponse parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreResponse parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreResponse parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreResponse parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreResponse parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreResponse parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreResponse parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.CoreResponse parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.CoreResponse prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.CoreResponse}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.CoreResponse)
+        io.grpc.benchmarks.proto.Control.CoreResponseOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_CoreResponse_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_CoreResponse_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.CoreResponse.class, io.grpc.benchmarks.proto.Control.CoreResponse.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.CoreResponse.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        cores_ = 0;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_CoreResponse_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.CoreResponse getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.CoreResponse.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.CoreResponse build() {
+        io.grpc.benchmarks.proto.Control.CoreResponse result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.CoreResponse buildPartial() {
+        io.grpc.benchmarks.proto.Control.CoreResponse result = new io.grpc.benchmarks.proto.Control.CoreResponse(this);
+        result.cores_ = cores_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.CoreResponse) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.CoreResponse)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.CoreResponse other) {
+        if (other == io.grpc.benchmarks.proto.Control.CoreResponse.getDefaultInstance()) return this;
+        if (other.getCores() != 0) {
+          setCores(other.getCores());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.CoreResponse parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.CoreResponse) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int cores_ ;
+      /**
+       * <pre>
+       * Number of cores available on the server
+       * </pre>
+       *
+       * <code>int32 cores = 1;</code>
+       */
+      public int getCores() {
+        return cores_;
+      }
+      /**
+       * <pre>
+       * Number of cores available on the server
+       * </pre>
+       *
+       * <code>int32 cores = 1;</code>
+       */
+      public Builder setCores(int value) {
+        
+        cores_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of cores available on the server
+       * </pre>
+       *
+       * <code>int32 cores = 1;</code>
+       */
+      public Builder clearCores() {
+        
+        cores_ = 0;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.CoreResponse)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.CoreResponse)
+    private static final io.grpc.benchmarks.proto.Control.CoreResponse DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.CoreResponse();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.CoreResponse getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<CoreResponse>
+        PARSER = new com.google.protobuf.AbstractParser<CoreResponse>() {
+      public CoreResponse parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new CoreResponse(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<CoreResponse> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<CoreResponse> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.CoreResponse getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface VoidOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.Void)
+      com.google.protobuf.MessageOrBuilder {
+  }
+  /**
+   * Protobuf type {@code grpc.testing.Void}
+   */
+  public  static final class Void extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.Void)
+      VoidOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use Void.newBuilder() to construct.
+    private Void(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private Void() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private Void(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Void_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Void_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.Void.class, io.grpc.benchmarks.proto.Control.Void.Builder.class);
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.Void)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.Void other = (io.grpc.benchmarks.proto.Control.Void) obj;
+
+      boolean result = true;
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.Void parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.Void parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Void parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.Void parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Void parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.Void parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Void parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.Void parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Void parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.Void parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Void parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.Void parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.Void prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.Void}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.Void)
+        io.grpc.benchmarks.proto.Control.VoidOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Void_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Void_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.Void.class, io.grpc.benchmarks.proto.Control.Void.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.Void.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Void_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.Void getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.Void.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.Void build() {
+        io.grpc.benchmarks.proto.Control.Void result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.Void buildPartial() {
+        io.grpc.benchmarks.proto.Control.Void result = new io.grpc.benchmarks.proto.Control.Void(this);
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.Void) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.Void)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.Void other) {
+        if (other == io.grpc.benchmarks.proto.Control.Void.getDefaultInstance()) return this;
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.Void parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.Void) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.Void)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.Void)
+    private static final io.grpc.benchmarks.proto.Control.Void DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.Void();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.Void getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<Void>
+        PARSER = new com.google.protobuf.AbstractParser<Void>() {
+      public Void parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new Void(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<Void> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<Void> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.Void getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ScenarioOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.Scenario)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Human readable name for this scenario
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    java.lang.String getName();
+    /**
+     * <pre>
+     * Human readable name for this scenario
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getNameBytes();
+
+    /**
+     * <pre>
+     * Client configuration
+     * </pre>
+     *
+     * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+     */
+    boolean hasClientConfig();
+    /**
+     * <pre>
+     * Client configuration
+     * </pre>
+     *
+     * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ClientConfig getClientConfig();
+    /**
+     * <pre>
+     * Client configuration
+     * </pre>
+     *
+     * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ClientConfigOrBuilder getClientConfigOrBuilder();
+
+    /**
+     * <pre>
+     * Number of clients to start for the test
+     * </pre>
+     *
+     * <code>int32 num_clients = 3;</code>
+     */
+    int getNumClients();
+
+    /**
+     * <pre>
+     * Server configuration
+     * </pre>
+     *
+     * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+     */
+    boolean hasServerConfig();
+    /**
+     * <pre>
+     * Server configuration
+     * </pre>
+     *
+     * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ServerConfig getServerConfig();
+    /**
+     * <pre>
+     * Server configuration
+     * </pre>
+     *
+     * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ServerConfigOrBuilder getServerConfigOrBuilder();
+
+    /**
+     * <pre>
+     * Number of servers to start for the test
+     * </pre>
+     *
+     * <code>int32 num_servers = 5;</code>
+     */
+    int getNumServers();
+
+    /**
+     * <pre>
+     * Warmup period, in seconds
+     * </pre>
+     *
+     * <code>int32 warmup_seconds = 6;</code>
+     */
+    int getWarmupSeconds();
+
+    /**
+     * <pre>
+     * Benchmark time, in seconds
+     * </pre>
+     *
+     * <code>int32 benchmark_seconds = 7;</code>
+     */
+    int getBenchmarkSeconds();
+
+    /**
+     * <pre>
+     * Number of workers to spawn locally (usually zero)
+     * </pre>
+     *
+     * <code>int32 spawn_local_worker_count = 8;</code>
+     */
+    int getSpawnLocalWorkerCount();
+  }
+  /**
+   * <pre>
+   * A single performance scenario: input to qps_json_driver
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.Scenario}
+   */
+  public  static final class Scenario extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.Scenario)
+      ScenarioOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use Scenario.newBuilder() to construct.
+    private Scenario(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private Scenario() {
+      name_ = "";
+      numClients_ = 0;
+      numServers_ = 0;
+      warmupSeconds_ = 0;
+      benchmarkSeconds_ = 0;
+      spawnLocalWorkerCount_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private Scenario(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              name_ = s;
+              break;
+            }
+            case 18: {
+              io.grpc.benchmarks.proto.Control.ClientConfig.Builder subBuilder = null;
+              if (clientConfig_ != null) {
+                subBuilder = clientConfig_.toBuilder();
+              }
+              clientConfig_ = input.readMessage(io.grpc.benchmarks.proto.Control.ClientConfig.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(clientConfig_);
+                clientConfig_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 24: {
+
+              numClients_ = input.readInt32();
+              break;
+            }
+            case 34: {
+              io.grpc.benchmarks.proto.Control.ServerConfig.Builder subBuilder = null;
+              if (serverConfig_ != null) {
+                subBuilder = serverConfig_.toBuilder();
+              }
+              serverConfig_ = input.readMessage(io.grpc.benchmarks.proto.Control.ServerConfig.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(serverConfig_);
+                serverConfig_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 40: {
+
+              numServers_ = input.readInt32();
+              break;
+            }
+            case 48: {
+
+              warmupSeconds_ = input.readInt32();
+              break;
+            }
+            case 56: {
+
+              benchmarkSeconds_ = input.readInt32();
+              break;
+            }
+            case 64: {
+
+              spawnLocalWorkerCount_ = input.readInt32();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Scenario_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Scenario_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.Scenario.class, io.grpc.benchmarks.proto.Control.Scenario.Builder.class);
+    }
+
+    public static final int NAME_FIELD_NUMBER = 1;
+    private volatile java.lang.Object name_;
+    /**
+     * <pre>
+     * Human readable name for this scenario
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        name_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * Human readable name for this scenario
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int CLIENT_CONFIG_FIELD_NUMBER = 2;
+    private io.grpc.benchmarks.proto.Control.ClientConfig clientConfig_;
+    /**
+     * <pre>
+     * Client configuration
+     * </pre>
+     *
+     * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+     */
+    public boolean hasClientConfig() {
+      return clientConfig_ != null;
+    }
+    /**
+     * <pre>
+     * Client configuration
+     * </pre>
+     *
+     * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ClientConfig getClientConfig() {
+      return clientConfig_ == null ? io.grpc.benchmarks.proto.Control.ClientConfig.getDefaultInstance() : clientConfig_;
+    }
+    /**
+     * <pre>
+     * Client configuration
+     * </pre>
+     *
+     * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ClientConfigOrBuilder getClientConfigOrBuilder() {
+      return getClientConfig();
+    }
+
+    public static final int NUM_CLIENTS_FIELD_NUMBER = 3;
+    private int numClients_;
+    /**
+     * <pre>
+     * Number of clients to start for the test
+     * </pre>
+     *
+     * <code>int32 num_clients = 3;</code>
+     */
+    public int getNumClients() {
+      return numClients_;
+    }
+
+    public static final int SERVER_CONFIG_FIELD_NUMBER = 4;
+    private io.grpc.benchmarks.proto.Control.ServerConfig serverConfig_;
+    /**
+     * <pre>
+     * Server configuration
+     * </pre>
+     *
+     * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+     */
+    public boolean hasServerConfig() {
+      return serverConfig_ != null;
+    }
+    /**
+     * <pre>
+     * Server configuration
+     * </pre>
+     *
+     * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ServerConfig getServerConfig() {
+      return serverConfig_ == null ? io.grpc.benchmarks.proto.Control.ServerConfig.getDefaultInstance() : serverConfig_;
+    }
+    /**
+     * <pre>
+     * Server configuration
+     * </pre>
+     *
+     * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ServerConfigOrBuilder getServerConfigOrBuilder() {
+      return getServerConfig();
+    }
+
+    public static final int NUM_SERVERS_FIELD_NUMBER = 5;
+    private int numServers_;
+    /**
+     * <pre>
+     * Number of servers to start for the test
+     * </pre>
+     *
+     * <code>int32 num_servers = 5;</code>
+     */
+    public int getNumServers() {
+      return numServers_;
+    }
+
+    public static final int WARMUP_SECONDS_FIELD_NUMBER = 6;
+    private int warmupSeconds_;
+    /**
+     * <pre>
+     * Warmup period, in seconds
+     * </pre>
+     *
+     * <code>int32 warmup_seconds = 6;</code>
+     */
+    public int getWarmupSeconds() {
+      return warmupSeconds_;
+    }
+
+    public static final int BENCHMARK_SECONDS_FIELD_NUMBER = 7;
+    private int benchmarkSeconds_;
+    /**
+     * <pre>
+     * Benchmark time, in seconds
+     * </pre>
+     *
+     * <code>int32 benchmark_seconds = 7;</code>
+     */
+    public int getBenchmarkSeconds() {
+      return benchmarkSeconds_;
+    }
+
+    public static final int SPAWN_LOCAL_WORKER_COUNT_FIELD_NUMBER = 8;
+    private int spawnLocalWorkerCount_;
+    /**
+     * <pre>
+     * Number of workers to spawn locally (usually zero)
+     * </pre>
+     *
+     * <code>int32 spawn_local_worker_count = 8;</code>
+     */
+    public int getSpawnLocalWorkerCount() {
+      return spawnLocalWorkerCount_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!getNameBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_);
+      }
+      if (clientConfig_ != null) {
+        output.writeMessage(2, getClientConfig());
+      }
+      if (numClients_ != 0) {
+        output.writeInt32(3, numClients_);
+      }
+      if (serverConfig_ != null) {
+        output.writeMessage(4, getServerConfig());
+      }
+      if (numServers_ != 0) {
+        output.writeInt32(5, numServers_);
+      }
+      if (warmupSeconds_ != 0) {
+        output.writeInt32(6, warmupSeconds_);
+      }
+      if (benchmarkSeconds_ != 0) {
+        output.writeInt32(7, benchmarkSeconds_);
+      }
+      if (spawnLocalWorkerCount_ != 0) {
+        output.writeInt32(8, spawnLocalWorkerCount_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!getNameBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_);
+      }
+      if (clientConfig_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, getClientConfig());
+      }
+      if (numClients_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(3, numClients_);
+      }
+      if (serverConfig_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(4, getServerConfig());
+      }
+      if (numServers_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(5, numServers_);
+      }
+      if (warmupSeconds_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(6, warmupSeconds_);
+      }
+      if (benchmarkSeconds_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(7, benchmarkSeconds_);
+      }
+      if (spawnLocalWorkerCount_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(8, spawnLocalWorkerCount_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.Scenario)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.Scenario other = (io.grpc.benchmarks.proto.Control.Scenario) obj;
+
+      boolean result = true;
+      result = result && getName()
+          .equals(other.getName());
+      result = result && (hasClientConfig() == other.hasClientConfig());
+      if (hasClientConfig()) {
+        result = result && getClientConfig()
+            .equals(other.getClientConfig());
+      }
+      result = result && (getNumClients()
+          == other.getNumClients());
+      result = result && (hasServerConfig() == other.hasServerConfig());
+      if (hasServerConfig()) {
+        result = result && getServerConfig()
+            .equals(other.getServerConfig());
+      }
+      result = result && (getNumServers()
+          == other.getNumServers());
+      result = result && (getWarmupSeconds()
+          == other.getWarmupSeconds());
+      result = result && (getBenchmarkSeconds()
+          == other.getBenchmarkSeconds());
+      result = result && (getSpawnLocalWorkerCount()
+          == other.getSpawnLocalWorkerCount());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + NAME_FIELD_NUMBER;
+      hash = (53 * hash) + getName().hashCode();
+      if (hasClientConfig()) {
+        hash = (37 * hash) + CLIENT_CONFIG_FIELD_NUMBER;
+        hash = (53 * hash) + getClientConfig().hashCode();
+      }
+      hash = (37 * hash) + NUM_CLIENTS_FIELD_NUMBER;
+      hash = (53 * hash) + getNumClients();
+      if (hasServerConfig()) {
+        hash = (37 * hash) + SERVER_CONFIG_FIELD_NUMBER;
+        hash = (53 * hash) + getServerConfig().hashCode();
+      }
+      hash = (37 * hash) + NUM_SERVERS_FIELD_NUMBER;
+      hash = (53 * hash) + getNumServers();
+      hash = (37 * hash) + WARMUP_SECONDS_FIELD_NUMBER;
+      hash = (53 * hash) + getWarmupSeconds();
+      hash = (37 * hash) + BENCHMARK_SECONDS_FIELD_NUMBER;
+      hash = (53 * hash) + getBenchmarkSeconds();
+      hash = (37 * hash) + SPAWN_LOCAL_WORKER_COUNT_FIELD_NUMBER;
+      hash = (53 * hash) + getSpawnLocalWorkerCount();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.Scenario parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenario parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenario parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenario parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenario parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenario parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenario parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenario parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenario parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenario parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenario parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenario parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.Scenario prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * A single performance scenario: input to qps_json_driver
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.Scenario}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.Scenario)
+        io.grpc.benchmarks.proto.Control.ScenarioOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Scenario_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Scenario_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.Scenario.class, io.grpc.benchmarks.proto.Control.Scenario.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.Scenario.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        name_ = "";
+
+        if (clientConfigBuilder_ == null) {
+          clientConfig_ = null;
+        } else {
+          clientConfig_ = null;
+          clientConfigBuilder_ = null;
+        }
+        numClients_ = 0;
+
+        if (serverConfigBuilder_ == null) {
+          serverConfig_ = null;
+        } else {
+          serverConfig_ = null;
+          serverConfigBuilder_ = null;
+        }
+        numServers_ = 0;
+
+        warmupSeconds_ = 0;
+
+        benchmarkSeconds_ = 0;
+
+        spawnLocalWorkerCount_ = 0;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Scenario_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.Scenario getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.Scenario.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.Scenario build() {
+        io.grpc.benchmarks.proto.Control.Scenario result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.Scenario buildPartial() {
+        io.grpc.benchmarks.proto.Control.Scenario result = new io.grpc.benchmarks.proto.Control.Scenario(this);
+        result.name_ = name_;
+        if (clientConfigBuilder_ == null) {
+          result.clientConfig_ = clientConfig_;
+        } else {
+          result.clientConfig_ = clientConfigBuilder_.build();
+        }
+        result.numClients_ = numClients_;
+        if (serverConfigBuilder_ == null) {
+          result.serverConfig_ = serverConfig_;
+        } else {
+          result.serverConfig_ = serverConfigBuilder_.build();
+        }
+        result.numServers_ = numServers_;
+        result.warmupSeconds_ = warmupSeconds_;
+        result.benchmarkSeconds_ = benchmarkSeconds_;
+        result.spawnLocalWorkerCount_ = spawnLocalWorkerCount_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.Scenario) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.Scenario)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.Scenario other) {
+        if (other == io.grpc.benchmarks.proto.Control.Scenario.getDefaultInstance()) return this;
+        if (!other.getName().isEmpty()) {
+          name_ = other.name_;
+          onChanged();
+        }
+        if (other.hasClientConfig()) {
+          mergeClientConfig(other.getClientConfig());
+        }
+        if (other.getNumClients() != 0) {
+          setNumClients(other.getNumClients());
+        }
+        if (other.hasServerConfig()) {
+          mergeServerConfig(other.getServerConfig());
+        }
+        if (other.getNumServers() != 0) {
+          setNumServers(other.getNumServers());
+        }
+        if (other.getWarmupSeconds() != 0) {
+          setWarmupSeconds(other.getWarmupSeconds());
+        }
+        if (other.getBenchmarkSeconds() != 0) {
+          setBenchmarkSeconds(other.getBenchmarkSeconds());
+        }
+        if (other.getSpawnLocalWorkerCount() != 0) {
+          setSpawnLocalWorkerCount(other.getSpawnLocalWorkerCount());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.Scenario parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.Scenario) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private java.lang.Object name_ = "";
+      /**
+       * <pre>
+       * Human readable name for this scenario
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public java.lang.String getName() {
+        java.lang.Object ref = name_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          name_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * Human readable name for this scenario
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getNameBytes() {
+        java.lang.Object ref = name_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          name_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * Human readable name for this scenario
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public Builder setName(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        name_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Human readable name for this scenario
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public Builder clearName() {
+        
+        name_ = getDefaultInstance().getName();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Human readable name for this scenario
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public Builder setNameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        name_ = value;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Control.ClientConfig clientConfig_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ClientConfig, io.grpc.benchmarks.proto.Control.ClientConfig.Builder, io.grpc.benchmarks.proto.Control.ClientConfigOrBuilder> clientConfigBuilder_;
+      /**
+       * <pre>
+       * Client configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+       */
+      public boolean hasClientConfig() {
+        return clientConfigBuilder_ != null || clientConfig_ != null;
+      }
+      /**
+       * <pre>
+       * Client configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ClientConfig getClientConfig() {
+        if (clientConfigBuilder_ == null) {
+          return clientConfig_ == null ? io.grpc.benchmarks.proto.Control.ClientConfig.getDefaultInstance() : clientConfig_;
+        } else {
+          return clientConfigBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Client configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+       */
+      public Builder setClientConfig(io.grpc.benchmarks.proto.Control.ClientConfig value) {
+        if (clientConfigBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          clientConfig_ = value;
+          onChanged();
+        } else {
+          clientConfigBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Client configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+       */
+      public Builder setClientConfig(
+          io.grpc.benchmarks.proto.Control.ClientConfig.Builder builderForValue) {
+        if (clientConfigBuilder_ == null) {
+          clientConfig_ = builderForValue.build();
+          onChanged();
+        } else {
+          clientConfigBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Client configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+       */
+      public Builder mergeClientConfig(io.grpc.benchmarks.proto.Control.ClientConfig value) {
+        if (clientConfigBuilder_ == null) {
+          if (clientConfig_ != null) {
+            clientConfig_ =
+              io.grpc.benchmarks.proto.Control.ClientConfig.newBuilder(clientConfig_).mergeFrom(value).buildPartial();
+          } else {
+            clientConfig_ = value;
+          }
+          onChanged();
+        } else {
+          clientConfigBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Client configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+       */
+      public Builder clearClientConfig() {
+        if (clientConfigBuilder_ == null) {
+          clientConfig_ = null;
+          onChanged();
+        } else {
+          clientConfig_ = null;
+          clientConfigBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Client configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ClientConfig.Builder getClientConfigBuilder() {
+        
+        onChanged();
+        return getClientConfigFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Client configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ClientConfigOrBuilder getClientConfigOrBuilder() {
+        if (clientConfigBuilder_ != null) {
+          return clientConfigBuilder_.getMessageOrBuilder();
+        } else {
+          return clientConfig_ == null ?
+              io.grpc.benchmarks.proto.Control.ClientConfig.getDefaultInstance() : clientConfig_;
+        }
+      }
+      /**
+       * <pre>
+       * Client configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ClientConfig client_config = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ClientConfig, io.grpc.benchmarks.proto.Control.ClientConfig.Builder, io.grpc.benchmarks.proto.Control.ClientConfigOrBuilder> 
+          getClientConfigFieldBuilder() {
+        if (clientConfigBuilder_ == null) {
+          clientConfigBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.ClientConfig, io.grpc.benchmarks.proto.Control.ClientConfig.Builder, io.grpc.benchmarks.proto.Control.ClientConfigOrBuilder>(
+                  getClientConfig(),
+                  getParentForChildren(),
+                  isClean());
+          clientConfig_ = null;
+        }
+        return clientConfigBuilder_;
+      }
+
+      private int numClients_ ;
+      /**
+       * <pre>
+       * Number of clients to start for the test
+       * </pre>
+       *
+       * <code>int32 num_clients = 3;</code>
+       */
+      public int getNumClients() {
+        return numClients_;
+      }
+      /**
+       * <pre>
+       * Number of clients to start for the test
+       * </pre>
+       *
+       * <code>int32 num_clients = 3;</code>
+       */
+      public Builder setNumClients(int value) {
+        
+        numClients_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of clients to start for the test
+       * </pre>
+       *
+       * <code>int32 num_clients = 3;</code>
+       */
+      public Builder clearNumClients() {
+        
+        numClients_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Control.ServerConfig serverConfig_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ServerConfig, io.grpc.benchmarks.proto.Control.ServerConfig.Builder, io.grpc.benchmarks.proto.Control.ServerConfigOrBuilder> serverConfigBuilder_;
+      /**
+       * <pre>
+       * Server configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+       */
+      public boolean hasServerConfig() {
+        return serverConfigBuilder_ != null || serverConfig_ != null;
+      }
+      /**
+       * <pre>
+       * Server configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ServerConfig getServerConfig() {
+        if (serverConfigBuilder_ == null) {
+          return serverConfig_ == null ? io.grpc.benchmarks.proto.Control.ServerConfig.getDefaultInstance() : serverConfig_;
+        } else {
+          return serverConfigBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Server configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+       */
+      public Builder setServerConfig(io.grpc.benchmarks.proto.Control.ServerConfig value) {
+        if (serverConfigBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          serverConfig_ = value;
+          onChanged();
+        } else {
+          serverConfigBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Server configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+       */
+      public Builder setServerConfig(
+          io.grpc.benchmarks.proto.Control.ServerConfig.Builder builderForValue) {
+        if (serverConfigBuilder_ == null) {
+          serverConfig_ = builderForValue.build();
+          onChanged();
+        } else {
+          serverConfigBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Server configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+       */
+      public Builder mergeServerConfig(io.grpc.benchmarks.proto.Control.ServerConfig value) {
+        if (serverConfigBuilder_ == null) {
+          if (serverConfig_ != null) {
+            serverConfig_ =
+              io.grpc.benchmarks.proto.Control.ServerConfig.newBuilder(serverConfig_).mergeFrom(value).buildPartial();
+          } else {
+            serverConfig_ = value;
+          }
+          onChanged();
+        } else {
+          serverConfigBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Server configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+       */
+      public Builder clearServerConfig() {
+        if (serverConfigBuilder_ == null) {
+          serverConfig_ = null;
+          onChanged();
+        } else {
+          serverConfig_ = null;
+          serverConfigBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Server configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ServerConfig.Builder getServerConfigBuilder() {
+        
+        onChanged();
+        return getServerConfigFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Server configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ServerConfigOrBuilder getServerConfigOrBuilder() {
+        if (serverConfigBuilder_ != null) {
+          return serverConfigBuilder_.getMessageOrBuilder();
+        } else {
+          return serverConfig_ == null ?
+              io.grpc.benchmarks.proto.Control.ServerConfig.getDefaultInstance() : serverConfig_;
+        }
+      }
+      /**
+       * <pre>
+       * Server configuration
+       * </pre>
+       *
+       * <code>.grpc.testing.ServerConfig server_config = 4;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ServerConfig, io.grpc.benchmarks.proto.Control.ServerConfig.Builder, io.grpc.benchmarks.proto.Control.ServerConfigOrBuilder> 
+          getServerConfigFieldBuilder() {
+        if (serverConfigBuilder_ == null) {
+          serverConfigBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.ServerConfig, io.grpc.benchmarks.proto.Control.ServerConfig.Builder, io.grpc.benchmarks.proto.Control.ServerConfigOrBuilder>(
+                  getServerConfig(),
+                  getParentForChildren(),
+                  isClean());
+          serverConfig_ = null;
+        }
+        return serverConfigBuilder_;
+      }
+
+      private int numServers_ ;
+      /**
+       * <pre>
+       * Number of servers to start for the test
+       * </pre>
+       *
+       * <code>int32 num_servers = 5;</code>
+       */
+      public int getNumServers() {
+        return numServers_;
+      }
+      /**
+       * <pre>
+       * Number of servers to start for the test
+       * </pre>
+       *
+       * <code>int32 num_servers = 5;</code>
+       */
+      public Builder setNumServers(int value) {
+        
+        numServers_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of servers to start for the test
+       * </pre>
+       *
+       * <code>int32 num_servers = 5;</code>
+       */
+      public Builder clearNumServers() {
+        
+        numServers_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int warmupSeconds_ ;
+      /**
+       * <pre>
+       * Warmup period, in seconds
+       * </pre>
+       *
+       * <code>int32 warmup_seconds = 6;</code>
+       */
+      public int getWarmupSeconds() {
+        return warmupSeconds_;
+      }
+      /**
+       * <pre>
+       * Warmup period, in seconds
+       * </pre>
+       *
+       * <code>int32 warmup_seconds = 6;</code>
+       */
+      public Builder setWarmupSeconds(int value) {
+        
+        warmupSeconds_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Warmup period, in seconds
+       * </pre>
+       *
+       * <code>int32 warmup_seconds = 6;</code>
+       */
+      public Builder clearWarmupSeconds() {
+        
+        warmupSeconds_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int benchmarkSeconds_ ;
+      /**
+       * <pre>
+       * Benchmark time, in seconds
+       * </pre>
+       *
+       * <code>int32 benchmark_seconds = 7;</code>
+       */
+      public int getBenchmarkSeconds() {
+        return benchmarkSeconds_;
+      }
+      /**
+       * <pre>
+       * Benchmark time, in seconds
+       * </pre>
+       *
+       * <code>int32 benchmark_seconds = 7;</code>
+       */
+      public Builder setBenchmarkSeconds(int value) {
+        
+        benchmarkSeconds_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Benchmark time, in seconds
+       * </pre>
+       *
+       * <code>int32 benchmark_seconds = 7;</code>
+       */
+      public Builder clearBenchmarkSeconds() {
+        
+        benchmarkSeconds_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int spawnLocalWorkerCount_ ;
+      /**
+       * <pre>
+       * Number of workers to spawn locally (usually zero)
+       * </pre>
+       *
+       * <code>int32 spawn_local_worker_count = 8;</code>
+       */
+      public int getSpawnLocalWorkerCount() {
+        return spawnLocalWorkerCount_;
+      }
+      /**
+       * <pre>
+       * Number of workers to spawn locally (usually zero)
+       * </pre>
+       *
+       * <code>int32 spawn_local_worker_count = 8;</code>
+       */
+      public Builder setSpawnLocalWorkerCount(int value) {
+        
+        spawnLocalWorkerCount_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of workers to spawn locally (usually zero)
+       * </pre>
+       *
+       * <code>int32 spawn_local_worker_count = 8;</code>
+       */
+      public Builder clearSpawnLocalWorkerCount() {
+        
+        spawnLocalWorkerCount_ = 0;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.Scenario)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.Scenario)
+    private static final io.grpc.benchmarks.proto.Control.Scenario DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.Scenario();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.Scenario getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<Scenario>
+        PARSER = new com.google.protobuf.AbstractParser<Scenario>() {
+      public Scenario parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new Scenario(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<Scenario> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<Scenario> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.Scenario getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ScenariosOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.Scenarios)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+     */
+    java.util.List<io.grpc.benchmarks.proto.Control.Scenario> 
+        getScenariosList();
+    /**
+     * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Control.Scenario getScenarios(int index);
+    /**
+     * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+     */
+    int getScenariosCount();
+    /**
+     * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+     */
+    java.util.List<? extends io.grpc.benchmarks.proto.Control.ScenarioOrBuilder> 
+        getScenariosOrBuilderList();
+    /**
+     * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ScenarioOrBuilder getScenariosOrBuilder(
+        int index);
+  }
+  /**
+   * <pre>
+   * A set of scenarios to be run with qps_json_driver
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.Scenarios}
+   */
+  public  static final class Scenarios extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.Scenarios)
+      ScenariosOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use Scenarios.newBuilder() to construct.
+    private Scenarios(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private Scenarios() {
+      scenarios_ = java.util.Collections.emptyList();
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private Scenarios(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+                scenarios_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Control.Scenario>();
+                mutable_bitField0_ |= 0x00000001;
+              }
+              scenarios_.add(
+                  input.readMessage(io.grpc.benchmarks.proto.Control.Scenario.parser(), extensionRegistry));
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+          scenarios_ = java.util.Collections.unmodifiableList(scenarios_);
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Scenarios_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Scenarios_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.Scenarios.class, io.grpc.benchmarks.proto.Control.Scenarios.Builder.class);
+    }
+
+    public static final int SCENARIOS_FIELD_NUMBER = 1;
+    private java.util.List<io.grpc.benchmarks.proto.Control.Scenario> scenarios_;
+    /**
+     * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+     */
+    public java.util.List<io.grpc.benchmarks.proto.Control.Scenario> getScenariosList() {
+      return scenarios_;
+    }
+    /**
+     * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+     */
+    public java.util.List<? extends io.grpc.benchmarks.proto.Control.ScenarioOrBuilder> 
+        getScenariosOrBuilderList() {
+      return scenarios_;
+    }
+    /**
+     * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+     */
+    public int getScenariosCount() {
+      return scenarios_.size();
+    }
+    /**
+     * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.Scenario getScenarios(int index) {
+      return scenarios_.get(index);
+    }
+    /**
+     * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ScenarioOrBuilder getScenariosOrBuilder(
+        int index) {
+      return scenarios_.get(index);
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      for (int i = 0; i < scenarios_.size(); i++) {
+        output.writeMessage(1, scenarios_.get(i));
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      for (int i = 0; i < scenarios_.size(); i++) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, scenarios_.get(i));
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.Scenarios)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.Scenarios other = (io.grpc.benchmarks.proto.Control.Scenarios) obj;
+
+      boolean result = true;
+      result = result && getScenariosList()
+          .equals(other.getScenariosList());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (getScenariosCount() > 0) {
+        hash = (37 * hash) + SCENARIOS_FIELD_NUMBER;
+        hash = (53 * hash) + getScenariosList().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.Scenarios parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenarios parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenarios parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenarios parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenarios parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenarios parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenarios parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenarios parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenarios parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenarios parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenarios parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.Scenarios parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.Scenarios prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * A set of scenarios to be run with qps_json_driver
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.Scenarios}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.Scenarios)
+        io.grpc.benchmarks.proto.Control.ScenariosOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Scenarios_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Scenarios_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.Scenarios.class, io.grpc.benchmarks.proto.Control.Scenarios.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.Scenarios.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+          getScenariosFieldBuilder();
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        if (scenariosBuilder_ == null) {
+          scenarios_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000001);
+        } else {
+          scenariosBuilder_.clear();
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_Scenarios_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.Scenarios getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.Scenarios.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.Scenarios build() {
+        io.grpc.benchmarks.proto.Control.Scenarios result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.Scenarios buildPartial() {
+        io.grpc.benchmarks.proto.Control.Scenarios result = new io.grpc.benchmarks.proto.Control.Scenarios(this);
+        int from_bitField0_ = bitField0_;
+        if (scenariosBuilder_ == null) {
+          if (((bitField0_ & 0x00000001) == 0x00000001)) {
+            scenarios_ = java.util.Collections.unmodifiableList(scenarios_);
+            bitField0_ = (bitField0_ & ~0x00000001);
+          }
+          result.scenarios_ = scenarios_;
+        } else {
+          result.scenarios_ = scenariosBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.Scenarios) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.Scenarios)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.Scenarios other) {
+        if (other == io.grpc.benchmarks.proto.Control.Scenarios.getDefaultInstance()) return this;
+        if (scenariosBuilder_ == null) {
+          if (!other.scenarios_.isEmpty()) {
+            if (scenarios_.isEmpty()) {
+              scenarios_ = other.scenarios_;
+              bitField0_ = (bitField0_ & ~0x00000001);
+            } else {
+              ensureScenariosIsMutable();
+              scenarios_.addAll(other.scenarios_);
+            }
+            onChanged();
+          }
+        } else {
+          if (!other.scenarios_.isEmpty()) {
+            if (scenariosBuilder_.isEmpty()) {
+              scenariosBuilder_.dispose();
+              scenariosBuilder_ = null;
+              scenarios_ = other.scenarios_;
+              bitField0_ = (bitField0_ & ~0x00000001);
+              scenariosBuilder_ = 
+                com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                   getScenariosFieldBuilder() : null;
+            } else {
+              scenariosBuilder_.addAllMessages(other.scenarios_);
+            }
+          }
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.Scenarios parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.Scenarios) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private java.util.List<io.grpc.benchmarks.proto.Control.Scenario> scenarios_ =
+        java.util.Collections.emptyList();
+      private void ensureScenariosIsMutable() {
+        if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+          scenarios_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Control.Scenario>(scenarios_);
+          bitField0_ |= 0x00000001;
+         }
+      }
+
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.Scenario, io.grpc.benchmarks.proto.Control.Scenario.Builder, io.grpc.benchmarks.proto.Control.ScenarioOrBuilder> scenariosBuilder_;
+
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Control.Scenario> getScenariosList() {
+        if (scenariosBuilder_ == null) {
+          return java.util.Collections.unmodifiableList(scenarios_);
+        } else {
+          return scenariosBuilder_.getMessageList();
+        }
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public int getScenariosCount() {
+        if (scenariosBuilder_ == null) {
+          return scenarios_.size();
+        } else {
+          return scenariosBuilder_.getCount();
+        }
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.Scenario getScenarios(int index) {
+        if (scenariosBuilder_ == null) {
+          return scenarios_.get(index);
+        } else {
+          return scenariosBuilder_.getMessage(index);
+        }
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public Builder setScenarios(
+          int index, io.grpc.benchmarks.proto.Control.Scenario value) {
+        if (scenariosBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureScenariosIsMutable();
+          scenarios_.set(index, value);
+          onChanged();
+        } else {
+          scenariosBuilder_.setMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public Builder setScenarios(
+          int index, io.grpc.benchmarks.proto.Control.Scenario.Builder builderForValue) {
+        if (scenariosBuilder_ == null) {
+          ensureScenariosIsMutable();
+          scenarios_.set(index, builderForValue.build());
+          onChanged();
+        } else {
+          scenariosBuilder_.setMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public Builder addScenarios(io.grpc.benchmarks.proto.Control.Scenario value) {
+        if (scenariosBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureScenariosIsMutable();
+          scenarios_.add(value);
+          onChanged();
+        } else {
+          scenariosBuilder_.addMessage(value);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public Builder addScenarios(
+          int index, io.grpc.benchmarks.proto.Control.Scenario value) {
+        if (scenariosBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureScenariosIsMutable();
+          scenarios_.add(index, value);
+          onChanged();
+        } else {
+          scenariosBuilder_.addMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public Builder addScenarios(
+          io.grpc.benchmarks.proto.Control.Scenario.Builder builderForValue) {
+        if (scenariosBuilder_ == null) {
+          ensureScenariosIsMutable();
+          scenarios_.add(builderForValue.build());
+          onChanged();
+        } else {
+          scenariosBuilder_.addMessage(builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public Builder addScenarios(
+          int index, io.grpc.benchmarks.proto.Control.Scenario.Builder builderForValue) {
+        if (scenariosBuilder_ == null) {
+          ensureScenariosIsMutable();
+          scenarios_.add(index, builderForValue.build());
+          onChanged();
+        } else {
+          scenariosBuilder_.addMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public Builder addAllScenarios(
+          java.lang.Iterable<? extends io.grpc.benchmarks.proto.Control.Scenario> values) {
+        if (scenariosBuilder_ == null) {
+          ensureScenariosIsMutable();
+          com.google.protobuf.AbstractMessageLite.Builder.addAll(
+              values, scenarios_);
+          onChanged();
+        } else {
+          scenariosBuilder_.addAllMessages(values);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public Builder clearScenarios() {
+        if (scenariosBuilder_ == null) {
+          scenarios_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000001);
+          onChanged();
+        } else {
+          scenariosBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public Builder removeScenarios(int index) {
+        if (scenariosBuilder_ == null) {
+          ensureScenariosIsMutable();
+          scenarios_.remove(index);
+          onChanged();
+        } else {
+          scenariosBuilder_.remove(index);
+        }
+        return this;
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.Scenario.Builder getScenariosBuilder(
+          int index) {
+        return getScenariosFieldBuilder().getBuilder(index);
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ScenarioOrBuilder getScenariosOrBuilder(
+          int index) {
+        if (scenariosBuilder_ == null) {
+          return scenarios_.get(index);  } else {
+          return scenariosBuilder_.getMessageOrBuilder(index);
+        }
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public java.util.List<? extends io.grpc.benchmarks.proto.Control.ScenarioOrBuilder> 
+           getScenariosOrBuilderList() {
+        if (scenariosBuilder_ != null) {
+          return scenariosBuilder_.getMessageOrBuilderList();
+        } else {
+          return java.util.Collections.unmodifiableList(scenarios_);
+        }
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.Scenario.Builder addScenariosBuilder() {
+        return getScenariosFieldBuilder().addBuilder(
+            io.grpc.benchmarks.proto.Control.Scenario.getDefaultInstance());
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.Scenario.Builder addScenariosBuilder(
+          int index) {
+        return getScenariosFieldBuilder().addBuilder(
+            index, io.grpc.benchmarks.proto.Control.Scenario.getDefaultInstance());
+      }
+      /**
+       * <code>repeated .grpc.testing.Scenario scenarios = 1;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Control.Scenario.Builder> 
+           getScenariosBuilderList() {
+        return getScenariosFieldBuilder().getBuilderList();
+      }
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.Scenario, io.grpc.benchmarks.proto.Control.Scenario.Builder, io.grpc.benchmarks.proto.Control.ScenarioOrBuilder> 
+          getScenariosFieldBuilder() {
+        if (scenariosBuilder_ == null) {
+          scenariosBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.Scenario, io.grpc.benchmarks.proto.Control.Scenario.Builder, io.grpc.benchmarks.proto.Control.ScenarioOrBuilder>(
+                  scenarios_,
+                  ((bitField0_ & 0x00000001) == 0x00000001),
+                  getParentForChildren(),
+                  isClean());
+          scenarios_ = null;
+        }
+        return scenariosBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.Scenarios)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.Scenarios)
+    private static final io.grpc.benchmarks.proto.Control.Scenarios DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.Scenarios();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.Scenarios getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<Scenarios>
+        PARSER = new com.google.protobuf.AbstractParser<Scenarios>() {
+      public Scenarios parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new Scenarios(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<Scenarios> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<Scenarios> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.Scenarios getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ScenarioResultSummaryOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ScenarioResultSummary)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Total number of operations per second over all clients.
+     * </pre>
+     *
+     * <code>double qps = 1;</code>
+     */
+    double getQps();
+
+    /**
+     * <pre>
+     * QPS per one server core.
+     * </pre>
+     *
+     * <code>double qps_per_server_core = 2;</code>
+     */
+    double getQpsPerServerCore();
+
+    /**
+     * <pre>
+     * server load based on system_time (0.85 =&gt; 85%)
+     * </pre>
+     *
+     * <code>double server_system_time = 3;</code>
+     */
+    double getServerSystemTime();
+
+    /**
+     * <pre>
+     * server load based on user_time (0.85 =&gt; 85%)
+     * </pre>
+     *
+     * <code>double server_user_time = 4;</code>
+     */
+    double getServerUserTime();
+
+    /**
+     * <pre>
+     * client load based on system_time (0.85 =&gt; 85%)
+     * </pre>
+     *
+     * <code>double client_system_time = 5;</code>
+     */
+    double getClientSystemTime();
+
+    /**
+     * <pre>
+     * client load based on user_time (0.85 =&gt; 85%)
+     * </pre>
+     *
+     * <code>double client_user_time = 6;</code>
+     */
+    double getClientUserTime();
+
+    /**
+     * <pre>
+     * X% latency percentiles (in nanoseconds)
+     * </pre>
+     *
+     * <code>double latency_50 = 7;</code>
+     */
+    double getLatency50();
+
+    /**
+     * <code>double latency_90 = 8;</code>
+     */
+    double getLatency90();
+
+    /**
+     * <code>double latency_95 = 9;</code>
+     */
+    double getLatency95();
+
+    /**
+     * <code>double latency_99 = 10;</code>
+     */
+    double getLatency99();
+
+    /**
+     * <code>double latency_999 = 11;</code>
+     */
+    double getLatency999();
+
+    /**
+     * <pre>
+     * server cpu usage percentage
+     * </pre>
+     *
+     * <code>double server_cpu_usage = 12;</code>
+     */
+    double getServerCpuUsage();
+
+    /**
+     * <pre>
+     * Number of requests that succeeded/failed
+     * </pre>
+     *
+     * <code>double successful_requests_per_second = 13;</code>
+     */
+    double getSuccessfulRequestsPerSecond();
+
+    /**
+     * <code>double failed_requests_per_second = 14;</code>
+     */
+    double getFailedRequestsPerSecond();
+
+    /**
+     * <pre>
+     * Number of polls called inside completion queue per request
+     * </pre>
+     *
+     * <code>double client_polls_per_request = 15;</code>
+     */
+    double getClientPollsPerRequest();
+
+    /**
+     * <code>double server_polls_per_request = 16;</code>
+     */
+    double getServerPollsPerRequest();
+  }
+  /**
+   * <pre>
+   * Basic summary that can be computed from ClientStats and ServerStats
+   * once the scenario has finished.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.ScenarioResultSummary}
+   */
+  public  static final class ScenarioResultSummary extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ScenarioResultSummary)
+      ScenarioResultSummaryOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ScenarioResultSummary.newBuilder() to construct.
+    private ScenarioResultSummary(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ScenarioResultSummary() {
+      qps_ = 0D;
+      qpsPerServerCore_ = 0D;
+      serverSystemTime_ = 0D;
+      serverUserTime_ = 0D;
+      clientSystemTime_ = 0D;
+      clientUserTime_ = 0D;
+      latency50_ = 0D;
+      latency90_ = 0D;
+      latency95_ = 0D;
+      latency99_ = 0D;
+      latency999_ = 0D;
+      serverCpuUsage_ = 0D;
+      successfulRequestsPerSecond_ = 0D;
+      failedRequestsPerSecond_ = 0D;
+      clientPollsPerRequest_ = 0D;
+      serverPollsPerRequest_ = 0D;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ScenarioResultSummary(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 9: {
+
+              qps_ = input.readDouble();
+              break;
+            }
+            case 17: {
+
+              qpsPerServerCore_ = input.readDouble();
+              break;
+            }
+            case 25: {
+
+              serverSystemTime_ = input.readDouble();
+              break;
+            }
+            case 33: {
+
+              serverUserTime_ = input.readDouble();
+              break;
+            }
+            case 41: {
+
+              clientSystemTime_ = input.readDouble();
+              break;
+            }
+            case 49: {
+
+              clientUserTime_ = input.readDouble();
+              break;
+            }
+            case 57: {
+
+              latency50_ = input.readDouble();
+              break;
+            }
+            case 65: {
+
+              latency90_ = input.readDouble();
+              break;
+            }
+            case 73: {
+
+              latency95_ = input.readDouble();
+              break;
+            }
+            case 81: {
+
+              latency99_ = input.readDouble();
+              break;
+            }
+            case 89: {
+
+              latency999_ = input.readDouble();
+              break;
+            }
+            case 97: {
+
+              serverCpuUsage_ = input.readDouble();
+              break;
+            }
+            case 105: {
+
+              successfulRequestsPerSecond_ = input.readDouble();
+              break;
+            }
+            case 113: {
+
+              failedRequestsPerSecond_ = input.readDouble();
+              break;
+            }
+            case 121: {
+
+              clientPollsPerRequest_ = input.readDouble();
+              break;
+            }
+            case 129: {
+
+              serverPollsPerRequest_ = input.readDouble();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ScenarioResultSummary_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ScenarioResultSummary_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.ScenarioResultSummary.class, io.grpc.benchmarks.proto.Control.ScenarioResultSummary.Builder.class);
+    }
+
+    public static final int QPS_FIELD_NUMBER = 1;
+    private double qps_;
+    /**
+     * <pre>
+     * Total number of operations per second over all clients.
+     * </pre>
+     *
+     * <code>double qps = 1;</code>
+     */
+    public double getQps() {
+      return qps_;
+    }
+
+    public static final int QPS_PER_SERVER_CORE_FIELD_NUMBER = 2;
+    private double qpsPerServerCore_;
+    /**
+     * <pre>
+     * QPS per one server core.
+     * </pre>
+     *
+     * <code>double qps_per_server_core = 2;</code>
+     */
+    public double getQpsPerServerCore() {
+      return qpsPerServerCore_;
+    }
+
+    public static final int SERVER_SYSTEM_TIME_FIELD_NUMBER = 3;
+    private double serverSystemTime_;
+    /**
+     * <pre>
+     * server load based on system_time (0.85 =&gt; 85%)
+     * </pre>
+     *
+     * <code>double server_system_time = 3;</code>
+     */
+    public double getServerSystemTime() {
+      return serverSystemTime_;
+    }
+
+    public static final int SERVER_USER_TIME_FIELD_NUMBER = 4;
+    private double serverUserTime_;
+    /**
+     * <pre>
+     * server load based on user_time (0.85 =&gt; 85%)
+     * </pre>
+     *
+     * <code>double server_user_time = 4;</code>
+     */
+    public double getServerUserTime() {
+      return serverUserTime_;
+    }
+
+    public static final int CLIENT_SYSTEM_TIME_FIELD_NUMBER = 5;
+    private double clientSystemTime_;
+    /**
+     * <pre>
+     * client load based on system_time (0.85 =&gt; 85%)
+     * </pre>
+     *
+     * <code>double client_system_time = 5;</code>
+     */
+    public double getClientSystemTime() {
+      return clientSystemTime_;
+    }
+
+    public static final int CLIENT_USER_TIME_FIELD_NUMBER = 6;
+    private double clientUserTime_;
+    /**
+     * <pre>
+     * client load based on user_time (0.85 =&gt; 85%)
+     * </pre>
+     *
+     * <code>double client_user_time = 6;</code>
+     */
+    public double getClientUserTime() {
+      return clientUserTime_;
+    }
+
+    public static final int LATENCY_50_FIELD_NUMBER = 7;
+    private double latency50_;
+    /**
+     * <pre>
+     * X% latency percentiles (in nanoseconds)
+     * </pre>
+     *
+     * <code>double latency_50 = 7;</code>
+     */
+    public double getLatency50() {
+      return latency50_;
+    }
+
+    public static final int LATENCY_90_FIELD_NUMBER = 8;
+    private double latency90_;
+    /**
+     * <code>double latency_90 = 8;</code>
+     */
+    public double getLatency90() {
+      return latency90_;
+    }
+
+    public static final int LATENCY_95_FIELD_NUMBER = 9;
+    private double latency95_;
+    /**
+     * <code>double latency_95 = 9;</code>
+     */
+    public double getLatency95() {
+      return latency95_;
+    }
+
+    public static final int LATENCY_99_FIELD_NUMBER = 10;
+    private double latency99_;
+    /**
+     * <code>double latency_99 = 10;</code>
+     */
+    public double getLatency99() {
+      return latency99_;
+    }
+
+    public static final int LATENCY_999_FIELD_NUMBER = 11;
+    private double latency999_;
+    /**
+     * <code>double latency_999 = 11;</code>
+     */
+    public double getLatency999() {
+      return latency999_;
+    }
+
+    public static final int SERVER_CPU_USAGE_FIELD_NUMBER = 12;
+    private double serverCpuUsage_;
+    /**
+     * <pre>
+     * server cpu usage percentage
+     * </pre>
+     *
+     * <code>double server_cpu_usage = 12;</code>
+     */
+    public double getServerCpuUsage() {
+      return serverCpuUsage_;
+    }
+
+    public static final int SUCCESSFUL_REQUESTS_PER_SECOND_FIELD_NUMBER = 13;
+    private double successfulRequestsPerSecond_;
+    /**
+     * <pre>
+     * Number of requests that succeeded/failed
+     * </pre>
+     *
+     * <code>double successful_requests_per_second = 13;</code>
+     */
+    public double getSuccessfulRequestsPerSecond() {
+      return successfulRequestsPerSecond_;
+    }
+
+    public static final int FAILED_REQUESTS_PER_SECOND_FIELD_NUMBER = 14;
+    private double failedRequestsPerSecond_;
+    /**
+     * <code>double failed_requests_per_second = 14;</code>
+     */
+    public double getFailedRequestsPerSecond() {
+      return failedRequestsPerSecond_;
+    }
+
+    public static final int CLIENT_POLLS_PER_REQUEST_FIELD_NUMBER = 15;
+    private double clientPollsPerRequest_;
+    /**
+     * <pre>
+     * Number of polls called inside completion queue per request
+     * </pre>
+     *
+     * <code>double client_polls_per_request = 15;</code>
+     */
+    public double getClientPollsPerRequest() {
+      return clientPollsPerRequest_;
+    }
+
+    public static final int SERVER_POLLS_PER_REQUEST_FIELD_NUMBER = 16;
+    private double serverPollsPerRequest_;
+    /**
+     * <code>double server_polls_per_request = 16;</code>
+     */
+    public double getServerPollsPerRequest() {
+      return serverPollsPerRequest_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (qps_ != 0D) {
+        output.writeDouble(1, qps_);
+      }
+      if (qpsPerServerCore_ != 0D) {
+        output.writeDouble(2, qpsPerServerCore_);
+      }
+      if (serverSystemTime_ != 0D) {
+        output.writeDouble(3, serverSystemTime_);
+      }
+      if (serverUserTime_ != 0D) {
+        output.writeDouble(4, serverUserTime_);
+      }
+      if (clientSystemTime_ != 0D) {
+        output.writeDouble(5, clientSystemTime_);
+      }
+      if (clientUserTime_ != 0D) {
+        output.writeDouble(6, clientUserTime_);
+      }
+      if (latency50_ != 0D) {
+        output.writeDouble(7, latency50_);
+      }
+      if (latency90_ != 0D) {
+        output.writeDouble(8, latency90_);
+      }
+      if (latency95_ != 0D) {
+        output.writeDouble(9, latency95_);
+      }
+      if (latency99_ != 0D) {
+        output.writeDouble(10, latency99_);
+      }
+      if (latency999_ != 0D) {
+        output.writeDouble(11, latency999_);
+      }
+      if (serverCpuUsage_ != 0D) {
+        output.writeDouble(12, serverCpuUsage_);
+      }
+      if (successfulRequestsPerSecond_ != 0D) {
+        output.writeDouble(13, successfulRequestsPerSecond_);
+      }
+      if (failedRequestsPerSecond_ != 0D) {
+        output.writeDouble(14, failedRequestsPerSecond_);
+      }
+      if (clientPollsPerRequest_ != 0D) {
+        output.writeDouble(15, clientPollsPerRequest_);
+      }
+      if (serverPollsPerRequest_ != 0D) {
+        output.writeDouble(16, serverPollsPerRequest_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (qps_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(1, qps_);
+      }
+      if (qpsPerServerCore_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(2, qpsPerServerCore_);
+      }
+      if (serverSystemTime_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(3, serverSystemTime_);
+      }
+      if (serverUserTime_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(4, serverUserTime_);
+      }
+      if (clientSystemTime_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(5, clientSystemTime_);
+      }
+      if (clientUserTime_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(6, clientUserTime_);
+      }
+      if (latency50_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(7, latency50_);
+      }
+      if (latency90_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(8, latency90_);
+      }
+      if (latency95_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(9, latency95_);
+      }
+      if (latency99_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(10, latency99_);
+      }
+      if (latency999_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(11, latency999_);
+      }
+      if (serverCpuUsage_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(12, serverCpuUsage_);
+      }
+      if (successfulRequestsPerSecond_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(13, successfulRequestsPerSecond_);
+      }
+      if (failedRequestsPerSecond_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(14, failedRequestsPerSecond_);
+      }
+      if (clientPollsPerRequest_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(15, clientPollsPerRequest_);
+      }
+      if (serverPollsPerRequest_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(16, serverPollsPerRequest_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.ScenarioResultSummary)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.ScenarioResultSummary other = (io.grpc.benchmarks.proto.Control.ScenarioResultSummary) obj;
+
+      boolean result = true;
+      result = result && (
+          java.lang.Double.doubleToLongBits(getQps())
+          == java.lang.Double.doubleToLongBits(
+              other.getQps()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getQpsPerServerCore())
+          == java.lang.Double.doubleToLongBits(
+              other.getQpsPerServerCore()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getServerSystemTime())
+          == java.lang.Double.doubleToLongBits(
+              other.getServerSystemTime()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getServerUserTime())
+          == java.lang.Double.doubleToLongBits(
+              other.getServerUserTime()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getClientSystemTime())
+          == java.lang.Double.doubleToLongBits(
+              other.getClientSystemTime()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getClientUserTime())
+          == java.lang.Double.doubleToLongBits(
+              other.getClientUserTime()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getLatency50())
+          == java.lang.Double.doubleToLongBits(
+              other.getLatency50()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getLatency90())
+          == java.lang.Double.doubleToLongBits(
+              other.getLatency90()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getLatency95())
+          == java.lang.Double.doubleToLongBits(
+              other.getLatency95()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getLatency99())
+          == java.lang.Double.doubleToLongBits(
+              other.getLatency99()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getLatency999())
+          == java.lang.Double.doubleToLongBits(
+              other.getLatency999()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getServerCpuUsage())
+          == java.lang.Double.doubleToLongBits(
+              other.getServerCpuUsage()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getSuccessfulRequestsPerSecond())
+          == java.lang.Double.doubleToLongBits(
+              other.getSuccessfulRequestsPerSecond()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getFailedRequestsPerSecond())
+          == java.lang.Double.doubleToLongBits(
+              other.getFailedRequestsPerSecond()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getClientPollsPerRequest())
+          == java.lang.Double.doubleToLongBits(
+              other.getClientPollsPerRequest()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getServerPollsPerRequest())
+          == java.lang.Double.doubleToLongBits(
+              other.getServerPollsPerRequest()));
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + QPS_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getQps()));
+      hash = (37 * hash) + QPS_PER_SERVER_CORE_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getQpsPerServerCore()));
+      hash = (37 * hash) + SERVER_SYSTEM_TIME_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getServerSystemTime()));
+      hash = (37 * hash) + SERVER_USER_TIME_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getServerUserTime()));
+      hash = (37 * hash) + CLIENT_SYSTEM_TIME_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getClientSystemTime()));
+      hash = (37 * hash) + CLIENT_USER_TIME_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getClientUserTime()));
+      hash = (37 * hash) + LATENCY_50_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getLatency50()));
+      hash = (37 * hash) + LATENCY_90_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getLatency90()));
+      hash = (37 * hash) + LATENCY_95_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getLatency95()));
+      hash = (37 * hash) + LATENCY_99_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getLatency99()));
+      hash = (37 * hash) + LATENCY_999_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getLatency999()));
+      hash = (37 * hash) + SERVER_CPU_USAGE_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getServerCpuUsage()));
+      hash = (37 * hash) + SUCCESSFUL_REQUESTS_PER_SECOND_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getSuccessfulRequestsPerSecond()));
+      hash = (37 * hash) + FAILED_REQUESTS_PER_SECOND_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getFailedRequestsPerSecond()));
+      hash = (37 * hash) + CLIENT_POLLS_PER_REQUEST_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getClientPollsPerRequest()));
+      hash = (37 * hash) + SERVER_POLLS_PER_REQUEST_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getServerPollsPerRequest()));
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ScenarioResultSummary parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResultSummary parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResultSummary parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResultSummary parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResultSummary parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResultSummary parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResultSummary parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResultSummary parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResultSummary parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResultSummary parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResultSummary parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResultSummary parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.ScenarioResultSummary prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Basic summary that can be computed from ClientStats and ServerStats
+     * once the scenario has finished.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.ScenarioResultSummary}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ScenarioResultSummary)
+        io.grpc.benchmarks.proto.Control.ScenarioResultSummaryOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ScenarioResultSummary_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ScenarioResultSummary_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.ScenarioResultSummary.class, io.grpc.benchmarks.proto.Control.ScenarioResultSummary.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.ScenarioResultSummary.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        qps_ = 0D;
+
+        qpsPerServerCore_ = 0D;
+
+        serverSystemTime_ = 0D;
+
+        serverUserTime_ = 0D;
+
+        clientSystemTime_ = 0D;
+
+        clientUserTime_ = 0D;
+
+        latency50_ = 0D;
+
+        latency90_ = 0D;
+
+        latency95_ = 0D;
+
+        latency99_ = 0D;
+
+        latency999_ = 0D;
+
+        serverCpuUsage_ = 0D;
+
+        successfulRequestsPerSecond_ = 0D;
+
+        failedRequestsPerSecond_ = 0D;
+
+        clientPollsPerRequest_ = 0D;
+
+        serverPollsPerRequest_ = 0D;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ScenarioResultSummary_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ScenarioResultSummary getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.ScenarioResultSummary.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.ScenarioResultSummary build() {
+        io.grpc.benchmarks.proto.Control.ScenarioResultSummary result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ScenarioResultSummary buildPartial() {
+        io.grpc.benchmarks.proto.Control.ScenarioResultSummary result = new io.grpc.benchmarks.proto.Control.ScenarioResultSummary(this);
+        result.qps_ = qps_;
+        result.qpsPerServerCore_ = qpsPerServerCore_;
+        result.serverSystemTime_ = serverSystemTime_;
+        result.serverUserTime_ = serverUserTime_;
+        result.clientSystemTime_ = clientSystemTime_;
+        result.clientUserTime_ = clientUserTime_;
+        result.latency50_ = latency50_;
+        result.latency90_ = latency90_;
+        result.latency95_ = latency95_;
+        result.latency99_ = latency99_;
+        result.latency999_ = latency999_;
+        result.serverCpuUsage_ = serverCpuUsage_;
+        result.successfulRequestsPerSecond_ = successfulRequestsPerSecond_;
+        result.failedRequestsPerSecond_ = failedRequestsPerSecond_;
+        result.clientPollsPerRequest_ = clientPollsPerRequest_;
+        result.serverPollsPerRequest_ = serverPollsPerRequest_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.ScenarioResultSummary) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.ScenarioResultSummary)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.ScenarioResultSummary other) {
+        if (other == io.grpc.benchmarks.proto.Control.ScenarioResultSummary.getDefaultInstance()) return this;
+        if (other.getQps() != 0D) {
+          setQps(other.getQps());
+        }
+        if (other.getQpsPerServerCore() != 0D) {
+          setQpsPerServerCore(other.getQpsPerServerCore());
+        }
+        if (other.getServerSystemTime() != 0D) {
+          setServerSystemTime(other.getServerSystemTime());
+        }
+        if (other.getServerUserTime() != 0D) {
+          setServerUserTime(other.getServerUserTime());
+        }
+        if (other.getClientSystemTime() != 0D) {
+          setClientSystemTime(other.getClientSystemTime());
+        }
+        if (other.getClientUserTime() != 0D) {
+          setClientUserTime(other.getClientUserTime());
+        }
+        if (other.getLatency50() != 0D) {
+          setLatency50(other.getLatency50());
+        }
+        if (other.getLatency90() != 0D) {
+          setLatency90(other.getLatency90());
+        }
+        if (other.getLatency95() != 0D) {
+          setLatency95(other.getLatency95());
+        }
+        if (other.getLatency99() != 0D) {
+          setLatency99(other.getLatency99());
+        }
+        if (other.getLatency999() != 0D) {
+          setLatency999(other.getLatency999());
+        }
+        if (other.getServerCpuUsage() != 0D) {
+          setServerCpuUsage(other.getServerCpuUsage());
+        }
+        if (other.getSuccessfulRequestsPerSecond() != 0D) {
+          setSuccessfulRequestsPerSecond(other.getSuccessfulRequestsPerSecond());
+        }
+        if (other.getFailedRequestsPerSecond() != 0D) {
+          setFailedRequestsPerSecond(other.getFailedRequestsPerSecond());
+        }
+        if (other.getClientPollsPerRequest() != 0D) {
+          setClientPollsPerRequest(other.getClientPollsPerRequest());
+        }
+        if (other.getServerPollsPerRequest() != 0D) {
+          setServerPollsPerRequest(other.getServerPollsPerRequest());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.ScenarioResultSummary parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.ScenarioResultSummary) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private double qps_ ;
+      /**
+       * <pre>
+       * Total number of operations per second over all clients.
+       * </pre>
+       *
+       * <code>double qps = 1;</code>
+       */
+      public double getQps() {
+        return qps_;
+      }
+      /**
+       * <pre>
+       * Total number of operations per second over all clients.
+       * </pre>
+       *
+       * <code>double qps = 1;</code>
+       */
+      public Builder setQps(double value) {
+        
+        qps_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Total number of operations per second over all clients.
+       * </pre>
+       *
+       * <code>double qps = 1;</code>
+       */
+      public Builder clearQps() {
+        
+        qps_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double qpsPerServerCore_ ;
+      /**
+       * <pre>
+       * QPS per one server core.
+       * </pre>
+       *
+       * <code>double qps_per_server_core = 2;</code>
+       */
+      public double getQpsPerServerCore() {
+        return qpsPerServerCore_;
+      }
+      /**
+       * <pre>
+       * QPS per one server core.
+       * </pre>
+       *
+       * <code>double qps_per_server_core = 2;</code>
+       */
+      public Builder setQpsPerServerCore(double value) {
+        
+        qpsPerServerCore_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * QPS per one server core.
+       * </pre>
+       *
+       * <code>double qps_per_server_core = 2;</code>
+       */
+      public Builder clearQpsPerServerCore() {
+        
+        qpsPerServerCore_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double serverSystemTime_ ;
+      /**
+       * <pre>
+       * server load based on system_time (0.85 =&gt; 85%)
+       * </pre>
+       *
+       * <code>double server_system_time = 3;</code>
+       */
+      public double getServerSystemTime() {
+        return serverSystemTime_;
+      }
+      /**
+       * <pre>
+       * server load based on system_time (0.85 =&gt; 85%)
+       * </pre>
+       *
+       * <code>double server_system_time = 3;</code>
+       */
+      public Builder setServerSystemTime(double value) {
+        
+        serverSystemTime_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * server load based on system_time (0.85 =&gt; 85%)
+       * </pre>
+       *
+       * <code>double server_system_time = 3;</code>
+       */
+      public Builder clearServerSystemTime() {
+        
+        serverSystemTime_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double serverUserTime_ ;
+      /**
+       * <pre>
+       * server load based on user_time (0.85 =&gt; 85%)
+       * </pre>
+       *
+       * <code>double server_user_time = 4;</code>
+       */
+      public double getServerUserTime() {
+        return serverUserTime_;
+      }
+      /**
+       * <pre>
+       * server load based on user_time (0.85 =&gt; 85%)
+       * </pre>
+       *
+       * <code>double server_user_time = 4;</code>
+       */
+      public Builder setServerUserTime(double value) {
+        
+        serverUserTime_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * server load based on user_time (0.85 =&gt; 85%)
+       * </pre>
+       *
+       * <code>double server_user_time = 4;</code>
+       */
+      public Builder clearServerUserTime() {
+        
+        serverUserTime_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double clientSystemTime_ ;
+      /**
+       * <pre>
+       * client load based on system_time (0.85 =&gt; 85%)
+       * </pre>
+       *
+       * <code>double client_system_time = 5;</code>
+       */
+      public double getClientSystemTime() {
+        return clientSystemTime_;
+      }
+      /**
+       * <pre>
+       * client load based on system_time (0.85 =&gt; 85%)
+       * </pre>
+       *
+       * <code>double client_system_time = 5;</code>
+       */
+      public Builder setClientSystemTime(double value) {
+        
+        clientSystemTime_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * client load based on system_time (0.85 =&gt; 85%)
+       * </pre>
+       *
+       * <code>double client_system_time = 5;</code>
+       */
+      public Builder clearClientSystemTime() {
+        
+        clientSystemTime_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double clientUserTime_ ;
+      /**
+       * <pre>
+       * client load based on user_time (0.85 =&gt; 85%)
+       * </pre>
+       *
+       * <code>double client_user_time = 6;</code>
+       */
+      public double getClientUserTime() {
+        return clientUserTime_;
+      }
+      /**
+       * <pre>
+       * client load based on user_time (0.85 =&gt; 85%)
+       * </pre>
+       *
+       * <code>double client_user_time = 6;</code>
+       */
+      public Builder setClientUserTime(double value) {
+        
+        clientUserTime_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * client load based on user_time (0.85 =&gt; 85%)
+       * </pre>
+       *
+       * <code>double client_user_time = 6;</code>
+       */
+      public Builder clearClientUserTime() {
+        
+        clientUserTime_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double latency50_ ;
+      /**
+       * <pre>
+       * X% latency percentiles (in nanoseconds)
+       * </pre>
+       *
+       * <code>double latency_50 = 7;</code>
+       */
+      public double getLatency50() {
+        return latency50_;
+      }
+      /**
+       * <pre>
+       * X% latency percentiles (in nanoseconds)
+       * </pre>
+       *
+       * <code>double latency_50 = 7;</code>
+       */
+      public Builder setLatency50(double value) {
+        
+        latency50_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * X% latency percentiles (in nanoseconds)
+       * </pre>
+       *
+       * <code>double latency_50 = 7;</code>
+       */
+      public Builder clearLatency50() {
+        
+        latency50_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double latency90_ ;
+      /**
+       * <code>double latency_90 = 8;</code>
+       */
+      public double getLatency90() {
+        return latency90_;
+      }
+      /**
+       * <code>double latency_90 = 8;</code>
+       */
+      public Builder setLatency90(double value) {
+        
+        latency90_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double latency_90 = 8;</code>
+       */
+      public Builder clearLatency90() {
+        
+        latency90_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double latency95_ ;
+      /**
+       * <code>double latency_95 = 9;</code>
+       */
+      public double getLatency95() {
+        return latency95_;
+      }
+      /**
+       * <code>double latency_95 = 9;</code>
+       */
+      public Builder setLatency95(double value) {
+        
+        latency95_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double latency_95 = 9;</code>
+       */
+      public Builder clearLatency95() {
+        
+        latency95_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double latency99_ ;
+      /**
+       * <code>double latency_99 = 10;</code>
+       */
+      public double getLatency99() {
+        return latency99_;
+      }
+      /**
+       * <code>double latency_99 = 10;</code>
+       */
+      public Builder setLatency99(double value) {
+        
+        latency99_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double latency_99 = 10;</code>
+       */
+      public Builder clearLatency99() {
+        
+        latency99_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double latency999_ ;
+      /**
+       * <code>double latency_999 = 11;</code>
+       */
+      public double getLatency999() {
+        return latency999_;
+      }
+      /**
+       * <code>double latency_999 = 11;</code>
+       */
+      public Builder setLatency999(double value) {
+        
+        latency999_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double latency_999 = 11;</code>
+       */
+      public Builder clearLatency999() {
+        
+        latency999_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double serverCpuUsage_ ;
+      /**
+       * <pre>
+       * server cpu usage percentage
+       * </pre>
+       *
+       * <code>double server_cpu_usage = 12;</code>
+       */
+      public double getServerCpuUsage() {
+        return serverCpuUsage_;
+      }
+      /**
+       * <pre>
+       * server cpu usage percentage
+       * </pre>
+       *
+       * <code>double server_cpu_usage = 12;</code>
+       */
+      public Builder setServerCpuUsage(double value) {
+        
+        serverCpuUsage_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * server cpu usage percentage
+       * </pre>
+       *
+       * <code>double server_cpu_usage = 12;</code>
+       */
+      public Builder clearServerCpuUsage() {
+        
+        serverCpuUsage_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double successfulRequestsPerSecond_ ;
+      /**
+       * <pre>
+       * Number of requests that succeeded/failed
+       * </pre>
+       *
+       * <code>double successful_requests_per_second = 13;</code>
+       */
+      public double getSuccessfulRequestsPerSecond() {
+        return successfulRequestsPerSecond_;
+      }
+      /**
+       * <pre>
+       * Number of requests that succeeded/failed
+       * </pre>
+       *
+       * <code>double successful_requests_per_second = 13;</code>
+       */
+      public Builder setSuccessfulRequestsPerSecond(double value) {
+        
+        successfulRequestsPerSecond_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of requests that succeeded/failed
+       * </pre>
+       *
+       * <code>double successful_requests_per_second = 13;</code>
+       */
+      public Builder clearSuccessfulRequestsPerSecond() {
+        
+        successfulRequestsPerSecond_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double failedRequestsPerSecond_ ;
+      /**
+       * <code>double failed_requests_per_second = 14;</code>
+       */
+      public double getFailedRequestsPerSecond() {
+        return failedRequestsPerSecond_;
+      }
+      /**
+       * <code>double failed_requests_per_second = 14;</code>
+       */
+      public Builder setFailedRequestsPerSecond(double value) {
+        
+        failedRequestsPerSecond_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double failed_requests_per_second = 14;</code>
+       */
+      public Builder clearFailedRequestsPerSecond() {
+        
+        failedRequestsPerSecond_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double clientPollsPerRequest_ ;
+      /**
+       * <pre>
+       * Number of polls called inside completion queue per request
+       * </pre>
+       *
+       * <code>double client_polls_per_request = 15;</code>
+       */
+      public double getClientPollsPerRequest() {
+        return clientPollsPerRequest_;
+      }
+      /**
+       * <pre>
+       * Number of polls called inside completion queue per request
+       * </pre>
+       *
+       * <code>double client_polls_per_request = 15;</code>
+       */
+      public Builder setClientPollsPerRequest(double value) {
+        
+        clientPollsPerRequest_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of polls called inside completion queue per request
+       * </pre>
+       *
+       * <code>double client_polls_per_request = 15;</code>
+       */
+      public Builder clearClientPollsPerRequest() {
+        
+        clientPollsPerRequest_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double serverPollsPerRequest_ ;
+      /**
+       * <code>double server_polls_per_request = 16;</code>
+       */
+      public double getServerPollsPerRequest() {
+        return serverPollsPerRequest_;
+      }
+      /**
+       * <code>double server_polls_per_request = 16;</code>
+       */
+      public Builder setServerPollsPerRequest(double value) {
+        
+        serverPollsPerRequest_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double server_polls_per_request = 16;</code>
+       */
+      public Builder clearServerPollsPerRequest() {
+        
+        serverPollsPerRequest_ = 0D;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ScenarioResultSummary)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ScenarioResultSummary)
+    private static final io.grpc.benchmarks.proto.Control.ScenarioResultSummary DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.ScenarioResultSummary();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ScenarioResultSummary getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ScenarioResultSummary>
+        PARSER = new com.google.protobuf.AbstractParser<ScenarioResultSummary>() {
+      public ScenarioResultSummary parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ScenarioResultSummary(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ScenarioResultSummary> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ScenarioResultSummary> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.ScenarioResultSummary getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ScenarioResultOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ScenarioResult)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Inputs used to run the scenario.
+     * </pre>
+     *
+     * <code>.grpc.testing.Scenario scenario = 1;</code>
+     */
+    boolean hasScenario();
+    /**
+     * <pre>
+     * Inputs used to run the scenario.
+     * </pre>
+     *
+     * <code>.grpc.testing.Scenario scenario = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Control.Scenario getScenario();
+    /**
+     * <pre>
+     * Inputs used to run the scenario.
+     * </pre>
+     *
+     * <code>.grpc.testing.Scenario scenario = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ScenarioOrBuilder getScenarioOrBuilder();
+
+    /**
+     * <pre>
+     * Histograms from all clients merged into one histogram.
+     * </pre>
+     *
+     * <code>.grpc.testing.HistogramData latencies = 2;</code>
+     */
+    boolean hasLatencies();
+    /**
+     * <pre>
+     * Histograms from all clients merged into one histogram.
+     * </pre>
+     *
+     * <code>.grpc.testing.HistogramData latencies = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.HistogramData getLatencies();
+    /**
+     * <pre>
+     * Histograms from all clients merged into one histogram.
+     * </pre>
+     *
+     * <code>.grpc.testing.HistogramData latencies = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.HistogramDataOrBuilder getLatenciesOrBuilder();
+
+    /**
+     * <pre>
+     * Client stats for each client
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+     */
+    java.util.List<io.grpc.benchmarks.proto.Stats.ClientStats> 
+        getClientStatsList();
+    /**
+     * <pre>
+     * Client stats for each client
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.ClientStats getClientStats(int index);
+    /**
+     * <pre>
+     * Client stats for each client
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+     */
+    int getClientStatsCount();
+    /**
+     * <pre>
+     * Client stats for each client
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+     */
+    java.util.List<? extends io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder> 
+        getClientStatsOrBuilderList();
+    /**
+     * <pre>
+     * Client stats for each client
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder getClientStatsOrBuilder(
+        int index);
+
+    /**
+     * <pre>
+     * Server stats for each server
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+     */
+    java.util.List<io.grpc.benchmarks.proto.Stats.ServerStats> 
+        getServerStatsList();
+    /**
+     * <pre>
+     * Server stats for each server
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.ServerStats getServerStats(int index);
+    /**
+     * <pre>
+     * Server stats for each server
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+     */
+    int getServerStatsCount();
+    /**
+     * <pre>
+     * Server stats for each server
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+     */
+    java.util.List<? extends io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder> 
+        getServerStatsOrBuilderList();
+    /**
+     * <pre>
+     * Server stats for each server
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder getServerStatsOrBuilder(
+        int index);
+
+    /**
+     * <pre>
+     * Number of cores available to each server
+     * </pre>
+     *
+     * <code>repeated int32 server_cores = 5;</code>
+     */
+    java.util.List<java.lang.Integer> getServerCoresList();
+    /**
+     * <pre>
+     * Number of cores available to each server
+     * </pre>
+     *
+     * <code>repeated int32 server_cores = 5;</code>
+     */
+    int getServerCoresCount();
+    /**
+     * <pre>
+     * Number of cores available to each server
+     * </pre>
+     *
+     * <code>repeated int32 server_cores = 5;</code>
+     */
+    int getServerCores(int index);
+
+    /**
+     * <pre>
+     * An after-the-fact computed summary
+     * </pre>
+     *
+     * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+     */
+    boolean hasSummary();
+    /**
+     * <pre>
+     * An after-the-fact computed summary
+     * </pre>
+     *
+     * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ScenarioResultSummary getSummary();
+    /**
+     * <pre>
+     * An after-the-fact computed summary
+     * </pre>
+     *
+     * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+     */
+    io.grpc.benchmarks.proto.Control.ScenarioResultSummaryOrBuilder getSummaryOrBuilder();
+
+    /**
+     * <pre>
+     * Information on success or failure of each worker
+     * </pre>
+     *
+     * <code>repeated bool client_success = 7;</code>
+     */
+    java.util.List<java.lang.Boolean> getClientSuccessList();
+    /**
+     * <pre>
+     * Information on success or failure of each worker
+     * </pre>
+     *
+     * <code>repeated bool client_success = 7;</code>
+     */
+    int getClientSuccessCount();
+    /**
+     * <pre>
+     * Information on success or failure of each worker
+     * </pre>
+     *
+     * <code>repeated bool client_success = 7;</code>
+     */
+    boolean getClientSuccess(int index);
+
+    /**
+     * <code>repeated bool server_success = 8;</code>
+     */
+    java.util.List<java.lang.Boolean> getServerSuccessList();
+    /**
+     * <code>repeated bool server_success = 8;</code>
+     */
+    int getServerSuccessCount();
+    /**
+     * <code>repeated bool server_success = 8;</code>
+     */
+    boolean getServerSuccess(int index);
+
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+     */
+    java.util.List<io.grpc.benchmarks.proto.Stats.RequestResultCount> 
+        getRequestResultsList();
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.RequestResultCount getRequestResults(int index);
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+     */
+    int getRequestResultsCount();
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+     */
+    java.util.List<? extends io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder> 
+        getRequestResultsOrBuilderList();
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder getRequestResultsOrBuilder(
+        int index);
+  }
+  /**
+   * <pre>
+   * Results of a single benchmark scenario.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.ScenarioResult}
+   */
+  public  static final class ScenarioResult extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ScenarioResult)
+      ScenarioResultOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ScenarioResult.newBuilder() to construct.
+    private ScenarioResult(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ScenarioResult() {
+      clientStats_ = java.util.Collections.emptyList();
+      serverStats_ = java.util.Collections.emptyList();
+      serverCores_ = java.util.Collections.emptyList();
+      clientSuccess_ = java.util.Collections.emptyList();
+      serverSuccess_ = java.util.Collections.emptyList();
+      requestResults_ = java.util.Collections.emptyList();
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ScenarioResult(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.benchmarks.proto.Control.Scenario.Builder subBuilder = null;
+              if (scenario_ != null) {
+                subBuilder = scenario_.toBuilder();
+              }
+              scenario_ = input.readMessage(io.grpc.benchmarks.proto.Control.Scenario.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(scenario_);
+                scenario_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 18: {
+              io.grpc.benchmarks.proto.Stats.HistogramData.Builder subBuilder = null;
+              if (latencies_ != null) {
+                subBuilder = latencies_.toBuilder();
+              }
+              latencies_ = input.readMessage(io.grpc.benchmarks.proto.Stats.HistogramData.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(latencies_);
+                latencies_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 26: {
+              if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) {
+                clientStats_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Stats.ClientStats>();
+                mutable_bitField0_ |= 0x00000004;
+              }
+              clientStats_.add(
+                  input.readMessage(io.grpc.benchmarks.proto.Stats.ClientStats.parser(), extensionRegistry));
+              break;
+            }
+            case 34: {
+              if (!((mutable_bitField0_ & 0x00000008) == 0x00000008)) {
+                serverStats_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Stats.ServerStats>();
+                mutable_bitField0_ |= 0x00000008;
+              }
+              serverStats_.add(
+                  input.readMessage(io.grpc.benchmarks.proto.Stats.ServerStats.parser(), extensionRegistry));
+              break;
+            }
+            case 40: {
+              if (!((mutable_bitField0_ & 0x00000010) == 0x00000010)) {
+                serverCores_ = new java.util.ArrayList<java.lang.Integer>();
+                mutable_bitField0_ |= 0x00000010;
+              }
+              serverCores_.add(input.readInt32());
+              break;
+            }
+            case 42: {
+              int length = input.readRawVarint32();
+              int limit = input.pushLimit(length);
+              if (!((mutable_bitField0_ & 0x00000010) == 0x00000010) && input.getBytesUntilLimit() > 0) {
+                serverCores_ = new java.util.ArrayList<java.lang.Integer>();
+                mutable_bitField0_ |= 0x00000010;
+              }
+              while (input.getBytesUntilLimit() > 0) {
+                serverCores_.add(input.readInt32());
+              }
+              input.popLimit(limit);
+              break;
+            }
+            case 50: {
+              io.grpc.benchmarks.proto.Control.ScenarioResultSummary.Builder subBuilder = null;
+              if (summary_ != null) {
+                subBuilder = summary_.toBuilder();
+              }
+              summary_ = input.readMessage(io.grpc.benchmarks.proto.Control.ScenarioResultSummary.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(summary_);
+                summary_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 56: {
+              if (!((mutable_bitField0_ & 0x00000040) == 0x00000040)) {
+                clientSuccess_ = new java.util.ArrayList<java.lang.Boolean>();
+                mutable_bitField0_ |= 0x00000040;
+              }
+              clientSuccess_.add(input.readBool());
+              break;
+            }
+            case 58: {
+              int length = input.readRawVarint32();
+              int limit = input.pushLimit(length);
+              if (!((mutable_bitField0_ & 0x00000040) == 0x00000040) && input.getBytesUntilLimit() > 0) {
+                clientSuccess_ = new java.util.ArrayList<java.lang.Boolean>();
+                mutable_bitField0_ |= 0x00000040;
+              }
+              while (input.getBytesUntilLimit() > 0) {
+                clientSuccess_.add(input.readBool());
+              }
+              input.popLimit(limit);
+              break;
+            }
+            case 64: {
+              if (!((mutable_bitField0_ & 0x00000080) == 0x00000080)) {
+                serverSuccess_ = new java.util.ArrayList<java.lang.Boolean>();
+                mutable_bitField0_ |= 0x00000080;
+              }
+              serverSuccess_.add(input.readBool());
+              break;
+            }
+            case 66: {
+              int length = input.readRawVarint32();
+              int limit = input.pushLimit(length);
+              if (!((mutable_bitField0_ & 0x00000080) == 0x00000080) && input.getBytesUntilLimit() > 0) {
+                serverSuccess_ = new java.util.ArrayList<java.lang.Boolean>();
+                mutable_bitField0_ |= 0x00000080;
+              }
+              while (input.getBytesUntilLimit() > 0) {
+                serverSuccess_.add(input.readBool());
+              }
+              input.popLimit(limit);
+              break;
+            }
+            case 74: {
+              if (!((mutable_bitField0_ & 0x00000100) == 0x00000100)) {
+                requestResults_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Stats.RequestResultCount>();
+                mutable_bitField0_ |= 0x00000100;
+              }
+              requestResults_.add(
+                  input.readMessage(io.grpc.benchmarks.proto.Stats.RequestResultCount.parser(), extensionRegistry));
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) {
+          clientStats_ = java.util.Collections.unmodifiableList(clientStats_);
+        }
+        if (((mutable_bitField0_ & 0x00000008) == 0x00000008)) {
+          serverStats_ = java.util.Collections.unmodifiableList(serverStats_);
+        }
+        if (((mutable_bitField0_ & 0x00000010) == 0x00000010)) {
+          serverCores_ = java.util.Collections.unmodifiableList(serverCores_);
+        }
+        if (((mutable_bitField0_ & 0x00000040) == 0x00000040)) {
+          clientSuccess_ = java.util.Collections.unmodifiableList(clientSuccess_);
+        }
+        if (((mutable_bitField0_ & 0x00000080) == 0x00000080)) {
+          serverSuccess_ = java.util.Collections.unmodifiableList(serverSuccess_);
+        }
+        if (((mutable_bitField0_ & 0x00000100) == 0x00000100)) {
+          requestResults_ = java.util.Collections.unmodifiableList(requestResults_);
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ScenarioResult_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ScenarioResult_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Control.ScenarioResult.class, io.grpc.benchmarks.proto.Control.ScenarioResult.Builder.class);
+    }
+
+    private int bitField0_;
+    public static final int SCENARIO_FIELD_NUMBER = 1;
+    private io.grpc.benchmarks.proto.Control.Scenario scenario_;
+    /**
+     * <pre>
+     * Inputs used to run the scenario.
+     * </pre>
+     *
+     * <code>.grpc.testing.Scenario scenario = 1;</code>
+     */
+    public boolean hasScenario() {
+      return scenario_ != null;
+    }
+    /**
+     * <pre>
+     * Inputs used to run the scenario.
+     * </pre>
+     *
+     * <code>.grpc.testing.Scenario scenario = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.Scenario getScenario() {
+      return scenario_ == null ? io.grpc.benchmarks.proto.Control.Scenario.getDefaultInstance() : scenario_;
+    }
+    /**
+     * <pre>
+     * Inputs used to run the scenario.
+     * </pre>
+     *
+     * <code>.grpc.testing.Scenario scenario = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ScenarioOrBuilder getScenarioOrBuilder() {
+      return getScenario();
+    }
+
+    public static final int LATENCIES_FIELD_NUMBER = 2;
+    private io.grpc.benchmarks.proto.Stats.HistogramData latencies_;
+    /**
+     * <pre>
+     * Histograms from all clients merged into one histogram.
+     * </pre>
+     *
+     * <code>.grpc.testing.HistogramData latencies = 2;</code>
+     */
+    public boolean hasLatencies() {
+      return latencies_ != null;
+    }
+    /**
+     * <pre>
+     * Histograms from all clients merged into one histogram.
+     * </pre>
+     *
+     * <code>.grpc.testing.HistogramData latencies = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.HistogramData getLatencies() {
+      return latencies_ == null ? io.grpc.benchmarks.proto.Stats.HistogramData.getDefaultInstance() : latencies_;
+    }
+    /**
+     * <pre>
+     * Histograms from all clients merged into one histogram.
+     * </pre>
+     *
+     * <code>.grpc.testing.HistogramData latencies = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.HistogramDataOrBuilder getLatenciesOrBuilder() {
+      return getLatencies();
+    }
+
+    public static final int CLIENT_STATS_FIELD_NUMBER = 3;
+    private java.util.List<io.grpc.benchmarks.proto.Stats.ClientStats> clientStats_;
+    /**
+     * <pre>
+     * Client stats for each client
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+     */
+    public java.util.List<io.grpc.benchmarks.proto.Stats.ClientStats> getClientStatsList() {
+      return clientStats_;
+    }
+    /**
+     * <pre>
+     * Client stats for each client
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+     */
+    public java.util.List<? extends io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder> 
+        getClientStatsOrBuilderList() {
+      return clientStats_;
+    }
+    /**
+     * <pre>
+     * Client stats for each client
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+     */
+    public int getClientStatsCount() {
+      return clientStats_.size();
+    }
+    /**
+     * <pre>
+     * Client stats for each client
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.ClientStats getClientStats(int index) {
+      return clientStats_.get(index);
+    }
+    /**
+     * <pre>
+     * Client stats for each client
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder getClientStatsOrBuilder(
+        int index) {
+      return clientStats_.get(index);
+    }
+
+    public static final int SERVER_STATS_FIELD_NUMBER = 4;
+    private java.util.List<io.grpc.benchmarks.proto.Stats.ServerStats> serverStats_;
+    /**
+     * <pre>
+     * Server stats for each server
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+     */
+    public java.util.List<io.grpc.benchmarks.proto.Stats.ServerStats> getServerStatsList() {
+      return serverStats_;
+    }
+    /**
+     * <pre>
+     * Server stats for each server
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+     */
+    public java.util.List<? extends io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder> 
+        getServerStatsOrBuilderList() {
+      return serverStats_;
+    }
+    /**
+     * <pre>
+     * Server stats for each server
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+     */
+    public int getServerStatsCount() {
+      return serverStats_.size();
+    }
+    /**
+     * <pre>
+     * Server stats for each server
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.ServerStats getServerStats(int index) {
+      return serverStats_.get(index);
+    }
+    /**
+     * <pre>
+     * Server stats for each server
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder getServerStatsOrBuilder(
+        int index) {
+      return serverStats_.get(index);
+    }
+
+    public static final int SERVER_CORES_FIELD_NUMBER = 5;
+    private java.util.List<java.lang.Integer> serverCores_;
+    /**
+     * <pre>
+     * Number of cores available to each server
+     * </pre>
+     *
+     * <code>repeated int32 server_cores = 5;</code>
+     */
+    public java.util.List<java.lang.Integer>
+        getServerCoresList() {
+      return serverCores_;
+    }
+    /**
+     * <pre>
+     * Number of cores available to each server
+     * </pre>
+     *
+     * <code>repeated int32 server_cores = 5;</code>
+     */
+    public int getServerCoresCount() {
+      return serverCores_.size();
+    }
+    /**
+     * <pre>
+     * Number of cores available to each server
+     * </pre>
+     *
+     * <code>repeated int32 server_cores = 5;</code>
+     */
+    public int getServerCores(int index) {
+      return serverCores_.get(index);
+    }
+    private int serverCoresMemoizedSerializedSize = -1;
+
+    public static final int SUMMARY_FIELD_NUMBER = 6;
+    private io.grpc.benchmarks.proto.Control.ScenarioResultSummary summary_;
+    /**
+     * <pre>
+     * An after-the-fact computed summary
+     * </pre>
+     *
+     * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+     */
+    public boolean hasSummary() {
+      return summary_ != null;
+    }
+    /**
+     * <pre>
+     * An after-the-fact computed summary
+     * </pre>
+     *
+     * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ScenarioResultSummary getSummary() {
+      return summary_ == null ? io.grpc.benchmarks.proto.Control.ScenarioResultSummary.getDefaultInstance() : summary_;
+    }
+    /**
+     * <pre>
+     * An after-the-fact computed summary
+     * </pre>
+     *
+     * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+     */
+    public io.grpc.benchmarks.proto.Control.ScenarioResultSummaryOrBuilder getSummaryOrBuilder() {
+      return getSummary();
+    }
+
+    public static final int CLIENT_SUCCESS_FIELD_NUMBER = 7;
+    private java.util.List<java.lang.Boolean> clientSuccess_;
+    /**
+     * <pre>
+     * Information on success or failure of each worker
+     * </pre>
+     *
+     * <code>repeated bool client_success = 7;</code>
+     */
+    public java.util.List<java.lang.Boolean>
+        getClientSuccessList() {
+      return clientSuccess_;
+    }
+    /**
+     * <pre>
+     * Information on success or failure of each worker
+     * </pre>
+     *
+     * <code>repeated bool client_success = 7;</code>
+     */
+    public int getClientSuccessCount() {
+      return clientSuccess_.size();
+    }
+    /**
+     * <pre>
+     * Information on success or failure of each worker
+     * </pre>
+     *
+     * <code>repeated bool client_success = 7;</code>
+     */
+    public boolean getClientSuccess(int index) {
+      return clientSuccess_.get(index);
+    }
+    private int clientSuccessMemoizedSerializedSize = -1;
+
+    public static final int SERVER_SUCCESS_FIELD_NUMBER = 8;
+    private java.util.List<java.lang.Boolean> serverSuccess_;
+    /**
+     * <code>repeated bool server_success = 8;</code>
+     */
+    public java.util.List<java.lang.Boolean>
+        getServerSuccessList() {
+      return serverSuccess_;
+    }
+    /**
+     * <code>repeated bool server_success = 8;</code>
+     */
+    public int getServerSuccessCount() {
+      return serverSuccess_.size();
+    }
+    /**
+     * <code>repeated bool server_success = 8;</code>
+     */
+    public boolean getServerSuccess(int index) {
+      return serverSuccess_.get(index);
+    }
+    private int serverSuccessMemoizedSerializedSize = -1;
+
+    public static final int REQUEST_RESULTS_FIELD_NUMBER = 9;
+    private java.util.List<io.grpc.benchmarks.proto.Stats.RequestResultCount> requestResults_;
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+     */
+    public java.util.List<io.grpc.benchmarks.proto.Stats.RequestResultCount> getRequestResultsList() {
+      return requestResults_;
+    }
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+     */
+    public java.util.List<? extends io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder> 
+        getRequestResultsOrBuilderList() {
+      return requestResults_;
+    }
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+     */
+    public int getRequestResultsCount() {
+      return requestResults_.size();
+    }
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.RequestResultCount getRequestResults(int index) {
+      return requestResults_.get(index);
+    }
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder getRequestResultsOrBuilder(
+        int index) {
+      return requestResults_.get(index);
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      getSerializedSize();
+      if (scenario_ != null) {
+        output.writeMessage(1, getScenario());
+      }
+      if (latencies_ != null) {
+        output.writeMessage(2, getLatencies());
+      }
+      for (int i = 0; i < clientStats_.size(); i++) {
+        output.writeMessage(3, clientStats_.get(i));
+      }
+      for (int i = 0; i < serverStats_.size(); i++) {
+        output.writeMessage(4, serverStats_.get(i));
+      }
+      if (getServerCoresList().size() > 0) {
+        output.writeUInt32NoTag(42);
+        output.writeUInt32NoTag(serverCoresMemoizedSerializedSize);
+      }
+      for (int i = 0; i < serverCores_.size(); i++) {
+        output.writeInt32NoTag(serverCores_.get(i));
+      }
+      if (summary_ != null) {
+        output.writeMessage(6, getSummary());
+      }
+      if (getClientSuccessList().size() > 0) {
+        output.writeUInt32NoTag(58);
+        output.writeUInt32NoTag(clientSuccessMemoizedSerializedSize);
+      }
+      for (int i = 0; i < clientSuccess_.size(); i++) {
+        output.writeBoolNoTag(clientSuccess_.get(i));
+      }
+      if (getServerSuccessList().size() > 0) {
+        output.writeUInt32NoTag(66);
+        output.writeUInt32NoTag(serverSuccessMemoizedSerializedSize);
+      }
+      for (int i = 0; i < serverSuccess_.size(); i++) {
+        output.writeBoolNoTag(serverSuccess_.get(i));
+      }
+      for (int i = 0; i < requestResults_.size(); i++) {
+        output.writeMessage(9, requestResults_.get(i));
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (scenario_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, getScenario());
+      }
+      if (latencies_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, getLatencies());
+      }
+      for (int i = 0; i < clientStats_.size(); i++) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(3, clientStats_.get(i));
+      }
+      for (int i = 0; i < serverStats_.size(); i++) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(4, serverStats_.get(i));
+      }
+      {
+        int dataSize = 0;
+        for (int i = 0; i < serverCores_.size(); i++) {
+          dataSize += com.google.protobuf.CodedOutputStream
+            .computeInt32SizeNoTag(serverCores_.get(i));
+        }
+        size += dataSize;
+        if (!getServerCoresList().isEmpty()) {
+          size += 1;
+          size += com.google.protobuf.CodedOutputStream
+              .computeInt32SizeNoTag(dataSize);
+        }
+        serverCoresMemoizedSerializedSize = dataSize;
+      }
+      if (summary_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(6, getSummary());
+      }
+      {
+        int dataSize = 0;
+        dataSize = 1 * getClientSuccessList().size();
+        size += dataSize;
+        if (!getClientSuccessList().isEmpty()) {
+          size += 1;
+          size += com.google.protobuf.CodedOutputStream
+              .computeInt32SizeNoTag(dataSize);
+        }
+        clientSuccessMemoizedSerializedSize = dataSize;
+      }
+      {
+        int dataSize = 0;
+        dataSize = 1 * getServerSuccessList().size();
+        size += dataSize;
+        if (!getServerSuccessList().isEmpty()) {
+          size += 1;
+          size += com.google.protobuf.CodedOutputStream
+              .computeInt32SizeNoTag(dataSize);
+        }
+        serverSuccessMemoizedSerializedSize = dataSize;
+      }
+      for (int i = 0; i < requestResults_.size(); i++) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(9, requestResults_.get(i));
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Control.ScenarioResult)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Control.ScenarioResult other = (io.grpc.benchmarks.proto.Control.ScenarioResult) obj;
+
+      boolean result = true;
+      result = result && (hasScenario() == other.hasScenario());
+      if (hasScenario()) {
+        result = result && getScenario()
+            .equals(other.getScenario());
+      }
+      result = result && (hasLatencies() == other.hasLatencies());
+      if (hasLatencies()) {
+        result = result && getLatencies()
+            .equals(other.getLatencies());
+      }
+      result = result && getClientStatsList()
+          .equals(other.getClientStatsList());
+      result = result && getServerStatsList()
+          .equals(other.getServerStatsList());
+      result = result && getServerCoresList()
+          .equals(other.getServerCoresList());
+      result = result && (hasSummary() == other.hasSummary());
+      if (hasSummary()) {
+        result = result && getSummary()
+            .equals(other.getSummary());
+      }
+      result = result && getClientSuccessList()
+          .equals(other.getClientSuccessList());
+      result = result && getServerSuccessList()
+          .equals(other.getServerSuccessList());
+      result = result && getRequestResultsList()
+          .equals(other.getRequestResultsList());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (hasScenario()) {
+        hash = (37 * hash) + SCENARIO_FIELD_NUMBER;
+        hash = (53 * hash) + getScenario().hashCode();
+      }
+      if (hasLatencies()) {
+        hash = (37 * hash) + LATENCIES_FIELD_NUMBER;
+        hash = (53 * hash) + getLatencies().hashCode();
+      }
+      if (getClientStatsCount() > 0) {
+        hash = (37 * hash) + CLIENT_STATS_FIELD_NUMBER;
+        hash = (53 * hash) + getClientStatsList().hashCode();
+      }
+      if (getServerStatsCount() > 0) {
+        hash = (37 * hash) + SERVER_STATS_FIELD_NUMBER;
+        hash = (53 * hash) + getServerStatsList().hashCode();
+      }
+      if (getServerCoresCount() > 0) {
+        hash = (37 * hash) + SERVER_CORES_FIELD_NUMBER;
+        hash = (53 * hash) + getServerCoresList().hashCode();
+      }
+      if (hasSummary()) {
+        hash = (37 * hash) + SUMMARY_FIELD_NUMBER;
+        hash = (53 * hash) + getSummary().hashCode();
+      }
+      if (getClientSuccessCount() > 0) {
+        hash = (37 * hash) + CLIENT_SUCCESS_FIELD_NUMBER;
+        hash = (53 * hash) + getClientSuccessList().hashCode();
+      }
+      if (getServerSuccessCount() > 0) {
+        hash = (37 * hash) + SERVER_SUCCESS_FIELD_NUMBER;
+        hash = (53 * hash) + getServerSuccessList().hashCode();
+      }
+      if (getRequestResultsCount() > 0) {
+        hash = (37 * hash) + REQUEST_RESULTS_FIELD_NUMBER;
+        hash = (53 * hash) + getRequestResultsList().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ScenarioResult parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResult parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResult parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResult parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResult parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResult parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResult parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResult parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResult parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResult parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResult parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Control.ScenarioResult parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Control.ScenarioResult prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Results of a single benchmark scenario.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.ScenarioResult}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ScenarioResult)
+        io.grpc.benchmarks.proto.Control.ScenarioResultOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ScenarioResult_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ScenarioResult_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Control.ScenarioResult.class, io.grpc.benchmarks.proto.Control.ScenarioResult.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Control.ScenarioResult.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+          getClientStatsFieldBuilder();
+          getServerStatsFieldBuilder();
+          getRequestResultsFieldBuilder();
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        if (scenarioBuilder_ == null) {
+          scenario_ = null;
+        } else {
+          scenario_ = null;
+          scenarioBuilder_ = null;
+        }
+        if (latenciesBuilder_ == null) {
+          latencies_ = null;
+        } else {
+          latencies_ = null;
+          latenciesBuilder_ = null;
+        }
+        if (clientStatsBuilder_ == null) {
+          clientStats_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000004);
+        } else {
+          clientStatsBuilder_.clear();
+        }
+        if (serverStatsBuilder_ == null) {
+          serverStats_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000008);
+        } else {
+          serverStatsBuilder_.clear();
+        }
+        serverCores_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000010);
+        if (summaryBuilder_ == null) {
+          summary_ = null;
+        } else {
+          summary_ = null;
+          summaryBuilder_ = null;
+        }
+        clientSuccess_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000040);
+        serverSuccess_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000080);
+        if (requestResultsBuilder_ == null) {
+          requestResults_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000100);
+        } else {
+          requestResultsBuilder_.clear();
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Control.internal_static_grpc_testing_ScenarioResult_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ScenarioResult getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Control.ScenarioResult.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Control.ScenarioResult build() {
+        io.grpc.benchmarks.proto.Control.ScenarioResult result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Control.ScenarioResult buildPartial() {
+        io.grpc.benchmarks.proto.Control.ScenarioResult result = new io.grpc.benchmarks.proto.Control.ScenarioResult(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        if (scenarioBuilder_ == null) {
+          result.scenario_ = scenario_;
+        } else {
+          result.scenario_ = scenarioBuilder_.build();
+        }
+        if (latenciesBuilder_ == null) {
+          result.latencies_ = latencies_;
+        } else {
+          result.latencies_ = latenciesBuilder_.build();
+        }
+        if (clientStatsBuilder_ == null) {
+          if (((bitField0_ & 0x00000004) == 0x00000004)) {
+            clientStats_ = java.util.Collections.unmodifiableList(clientStats_);
+            bitField0_ = (bitField0_ & ~0x00000004);
+          }
+          result.clientStats_ = clientStats_;
+        } else {
+          result.clientStats_ = clientStatsBuilder_.build();
+        }
+        if (serverStatsBuilder_ == null) {
+          if (((bitField0_ & 0x00000008) == 0x00000008)) {
+            serverStats_ = java.util.Collections.unmodifiableList(serverStats_);
+            bitField0_ = (bitField0_ & ~0x00000008);
+          }
+          result.serverStats_ = serverStats_;
+        } else {
+          result.serverStats_ = serverStatsBuilder_.build();
+        }
+        if (((bitField0_ & 0x00000010) == 0x00000010)) {
+          serverCores_ = java.util.Collections.unmodifiableList(serverCores_);
+          bitField0_ = (bitField0_ & ~0x00000010);
+        }
+        result.serverCores_ = serverCores_;
+        if (summaryBuilder_ == null) {
+          result.summary_ = summary_;
+        } else {
+          result.summary_ = summaryBuilder_.build();
+        }
+        if (((bitField0_ & 0x00000040) == 0x00000040)) {
+          clientSuccess_ = java.util.Collections.unmodifiableList(clientSuccess_);
+          bitField0_ = (bitField0_ & ~0x00000040);
+        }
+        result.clientSuccess_ = clientSuccess_;
+        if (((bitField0_ & 0x00000080) == 0x00000080)) {
+          serverSuccess_ = java.util.Collections.unmodifiableList(serverSuccess_);
+          bitField0_ = (bitField0_ & ~0x00000080);
+        }
+        result.serverSuccess_ = serverSuccess_;
+        if (requestResultsBuilder_ == null) {
+          if (((bitField0_ & 0x00000100) == 0x00000100)) {
+            requestResults_ = java.util.Collections.unmodifiableList(requestResults_);
+            bitField0_ = (bitField0_ & ~0x00000100);
+          }
+          result.requestResults_ = requestResults_;
+        } else {
+          result.requestResults_ = requestResultsBuilder_.build();
+        }
+        result.bitField0_ = to_bitField0_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Control.ScenarioResult) {
+          return mergeFrom((io.grpc.benchmarks.proto.Control.ScenarioResult)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Control.ScenarioResult other) {
+        if (other == io.grpc.benchmarks.proto.Control.ScenarioResult.getDefaultInstance()) return this;
+        if (other.hasScenario()) {
+          mergeScenario(other.getScenario());
+        }
+        if (other.hasLatencies()) {
+          mergeLatencies(other.getLatencies());
+        }
+        if (clientStatsBuilder_ == null) {
+          if (!other.clientStats_.isEmpty()) {
+            if (clientStats_.isEmpty()) {
+              clientStats_ = other.clientStats_;
+              bitField0_ = (bitField0_ & ~0x00000004);
+            } else {
+              ensureClientStatsIsMutable();
+              clientStats_.addAll(other.clientStats_);
+            }
+            onChanged();
+          }
+        } else {
+          if (!other.clientStats_.isEmpty()) {
+            if (clientStatsBuilder_.isEmpty()) {
+              clientStatsBuilder_.dispose();
+              clientStatsBuilder_ = null;
+              clientStats_ = other.clientStats_;
+              bitField0_ = (bitField0_ & ~0x00000004);
+              clientStatsBuilder_ = 
+                com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                   getClientStatsFieldBuilder() : null;
+            } else {
+              clientStatsBuilder_.addAllMessages(other.clientStats_);
+            }
+          }
+        }
+        if (serverStatsBuilder_ == null) {
+          if (!other.serverStats_.isEmpty()) {
+            if (serverStats_.isEmpty()) {
+              serverStats_ = other.serverStats_;
+              bitField0_ = (bitField0_ & ~0x00000008);
+            } else {
+              ensureServerStatsIsMutable();
+              serverStats_.addAll(other.serverStats_);
+            }
+            onChanged();
+          }
+        } else {
+          if (!other.serverStats_.isEmpty()) {
+            if (serverStatsBuilder_.isEmpty()) {
+              serverStatsBuilder_.dispose();
+              serverStatsBuilder_ = null;
+              serverStats_ = other.serverStats_;
+              bitField0_ = (bitField0_ & ~0x00000008);
+              serverStatsBuilder_ = 
+                com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                   getServerStatsFieldBuilder() : null;
+            } else {
+              serverStatsBuilder_.addAllMessages(other.serverStats_);
+            }
+          }
+        }
+        if (!other.serverCores_.isEmpty()) {
+          if (serverCores_.isEmpty()) {
+            serverCores_ = other.serverCores_;
+            bitField0_ = (bitField0_ & ~0x00000010);
+          } else {
+            ensureServerCoresIsMutable();
+            serverCores_.addAll(other.serverCores_);
+          }
+          onChanged();
+        }
+        if (other.hasSummary()) {
+          mergeSummary(other.getSummary());
+        }
+        if (!other.clientSuccess_.isEmpty()) {
+          if (clientSuccess_.isEmpty()) {
+            clientSuccess_ = other.clientSuccess_;
+            bitField0_ = (bitField0_ & ~0x00000040);
+          } else {
+            ensureClientSuccessIsMutable();
+            clientSuccess_.addAll(other.clientSuccess_);
+          }
+          onChanged();
+        }
+        if (!other.serverSuccess_.isEmpty()) {
+          if (serverSuccess_.isEmpty()) {
+            serverSuccess_ = other.serverSuccess_;
+            bitField0_ = (bitField0_ & ~0x00000080);
+          } else {
+            ensureServerSuccessIsMutable();
+            serverSuccess_.addAll(other.serverSuccess_);
+          }
+          onChanged();
+        }
+        if (requestResultsBuilder_ == null) {
+          if (!other.requestResults_.isEmpty()) {
+            if (requestResults_.isEmpty()) {
+              requestResults_ = other.requestResults_;
+              bitField0_ = (bitField0_ & ~0x00000100);
+            } else {
+              ensureRequestResultsIsMutable();
+              requestResults_.addAll(other.requestResults_);
+            }
+            onChanged();
+          }
+        } else {
+          if (!other.requestResults_.isEmpty()) {
+            if (requestResultsBuilder_.isEmpty()) {
+              requestResultsBuilder_.dispose();
+              requestResultsBuilder_ = null;
+              requestResults_ = other.requestResults_;
+              bitField0_ = (bitField0_ & ~0x00000100);
+              requestResultsBuilder_ = 
+                com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                   getRequestResultsFieldBuilder() : null;
+            } else {
+              requestResultsBuilder_.addAllMessages(other.requestResults_);
+            }
+          }
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Control.ScenarioResult parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Control.ScenarioResult) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private io.grpc.benchmarks.proto.Control.Scenario scenario_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.Scenario, io.grpc.benchmarks.proto.Control.Scenario.Builder, io.grpc.benchmarks.proto.Control.ScenarioOrBuilder> scenarioBuilder_;
+      /**
+       * <pre>
+       * Inputs used to run the scenario.
+       * </pre>
+       *
+       * <code>.grpc.testing.Scenario scenario = 1;</code>
+       */
+      public boolean hasScenario() {
+        return scenarioBuilder_ != null || scenario_ != null;
+      }
+      /**
+       * <pre>
+       * Inputs used to run the scenario.
+       * </pre>
+       *
+       * <code>.grpc.testing.Scenario scenario = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.Scenario getScenario() {
+        if (scenarioBuilder_ == null) {
+          return scenario_ == null ? io.grpc.benchmarks.proto.Control.Scenario.getDefaultInstance() : scenario_;
+        } else {
+          return scenarioBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Inputs used to run the scenario.
+       * </pre>
+       *
+       * <code>.grpc.testing.Scenario scenario = 1;</code>
+       */
+      public Builder setScenario(io.grpc.benchmarks.proto.Control.Scenario value) {
+        if (scenarioBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          scenario_ = value;
+          onChanged();
+        } else {
+          scenarioBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Inputs used to run the scenario.
+       * </pre>
+       *
+       * <code>.grpc.testing.Scenario scenario = 1;</code>
+       */
+      public Builder setScenario(
+          io.grpc.benchmarks.proto.Control.Scenario.Builder builderForValue) {
+        if (scenarioBuilder_ == null) {
+          scenario_ = builderForValue.build();
+          onChanged();
+        } else {
+          scenarioBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Inputs used to run the scenario.
+       * </pre>
+       *
+       * <code>.grpc.testing.Scenario scenario = 1;</code>
+       */
+      public Builder mergeScenario(io.grpc.benchmarks.proto.Control.Scenario value) {
+        if (scenarioBuilder_ == null) {
+          if (scenario_ != null) {
+            scenario_ =
+              io.grpc.benchmarks.proto.Control.Scenario.newBuilder(scenario_).mergeFrom(value).buildPartial();
+          } else {
+            scenario_ = value;
+          }
+          onChanged();
+        } else {
+          scenarioBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Inputs used to run the scenario.
+       * </pre>
+       *
+       * <code>.grpc.testing.Scenario scenario = 1;</code>
+       */
+      public Builder clearScenario() {
+        if (scenarioBuilder_ == null) {
+          scenario_ = null;
+          onChanged();
+        } else {
+          scenario_ = null;
+          scenarioBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Inputs used to run the scenario.
+       * </pre>
+       *
+       * <code>.grpc.testing.Scenario scenario = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.Scenario.Builder getScenarioBuilder() {
+        
+        onChanged();
+        return getScenarioFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Inputs used to run the scenario.
+       * </pre>
+       *
+       * <code>.grpc.testing.Scenario scenario = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ScenarioOrBuilder getScenarioOrBuilder() {
+        if (scenarioBuilder_ != null) {
+          return scenarioBuilder_.getMessageOrBuilder();
+        } else {
+          return scenario_ == null ?
+              io.grpc.benchmarks.proto.Control.Scenario.getDefaultInstance() : scenario_;
+        }
+      }
+      /**
+       * <pre>
+       * Inputs used to run the scenario.
+       * </pre>
+       *
+       * <code>.grpc.testing.Scenario scenario = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.Scenario, io.grpc.benchmarks.proto.Control.Scenario.Builder, io.grpc.benchmarks.proto.Control.ScenarioOrBuilder> 
+          getScenarioFieldBuilder() {
+        if (scenarioBuilder_ == null) {
+          scenarioBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.Scenario, io.grpc.benchmarks.proto.Control.Scenario.Builder, io.grpc.benchmarks.proto.Control.ScenarioOrBuilder>(
+                  getScenario(),
+                  getParentForChildren(),
+                  isClean());
+          scenario_ = null;
+        }
+        return scenarioBuilder_;
+      }
+
+      private io.grpc.benchmarks.proto.Stats.HistogramData latencies_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.HistogramData, io.grpc.benchmarks.proto.Stats.HistogramData.Builder, io.grpc.benchmarks.proto.Stats.HistogramDataOrBuilder> latenciesBuilder_;
+      /**
+       * <pre>
+       * Histograms from all clients merged into one histogram.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 2;</code>
+       */
+      public boolean hasLatencies() {
+        return latenciesBuilder_ != null || latencies_ != null;
+      }
+      /**
+       * <pre>
+       * Histograms from all clients merged into one histogram.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.HistogramData getLatencies() {
+        if (latenciesBuilder_ == null) {
+          return latencies_ == null ? io.grpc.benchmarks.proto.Stats.HistogramData.getDefaultInstance() : latencies_;
+        } else {
+          return latenciesBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Histograms from all clients merged into one histogram.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 2;</code>
+       */
+      public Builder setLatencies(io.grpc.benchmarks.proto.Stats.HistogramData value) {
+        if (latenciesBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          latencies_ = value;
+          onChanged();
+        } else {
+          latenciesBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Histograms from all clients merged into one histogram.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 2;</code>
+       */
+      public Builder setLatencies(
+          io.grpc.benchmarks.proto.Stats.HistogramData.Builder builderForValue) {
+        if (latenciesBuilder_ == null) {
+          latencies_ = builderForValue.build();
+          onChanged();
+        } else {
+          latenciesBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Histograms from all clients merged into one histogram.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 2;</code>
+       */
+      public Builder mergeLatencies(io.grpc.benchmarks.proto.Stats.HistogramData value) {
+        if (latenciesBuilder_ == null) {
+          if (latencies_ != null) {
+            latencies_ =
+              io.grpc.benchmarks.proto.Stats.HistogramData.newBuilder(latencies_).mergeFrom(value).buildPartial();
+          } else {
+            latencies_ = value;
+          }
+          onChanged();
+        } else {
+          latenciesBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Histograms from all clients merged into one histogram.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 2;</code>
+       */
+      public Builder clearLatencies() {
+        if (latenciesBuilder_ == null) {
+          latencies_ = null;
+          onChanged();
+        } else {
+          latencies_ = null;
+          latenciesBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Histograms from all clients merged into one histogram.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.HistogramData.Builder getLatenciesBuilder() {
+        
+        onChanged();
+        return getLatenciesFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Histograms from all clients merged into one histogram.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.HistogramDataOrBuilder getLatenciesOrBuilder() {
+        if (latenciesBuilder_ != null) {
+          return latenciesBuilder_.getMessageOrBuilder();
+        } else {
+          return latencies_ == null ?
+              io.grpc.benchmarks.proto.Stats.HistogramData.getDefaultInstance() : latencies_;
+        }
+      }
+      /**
+       * <pre>
+       * Histograms from all clients merged into one histogram.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.HistogramData, io.grpc.benchmarks.proto.Stats.HistogramData.Builder, io.grpc.benchmarks.proto.Stats.HistogramDataOrBuilder> 
+          getLatenciesFieldBuilder() {
+        if (latenciesBuilder_ == null) {
+          latenciesBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Stats.HistogramData, io.grpc.benchmarks.proto.Stats.HistogramData.Builder, io.grpc.benchmarks.proto.Stats.HistogramDataOrBuilder>(
+                  getLatencies(),
+                  getParentForChildren(),
+                  isClean());
+          latencies_ = null;
+        }
+        return latenciesBuilder_;
+      }
+
+      private java.util.List<io.grpc.benchmarks.proto.Stats.ClientStats> clientStats_ =
+        java.util.Collections.emptyList();
+      private void ensureClientStatsIsMutable() {
+        if (!((bitField0_ & 0x00000004) == 0x00000004)) {
+          clientStats_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Stats.ClientStats>(clientStats_);
+          bitField0_ |= 0x00000004;
+         }
+      }
+
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.ClientStats, io.grpc.benchmarks.proto.Stats.ClientStats.Builder, io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder> clientStatsBuilder_;
+
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Stats.ClientStats> getClientStatsList() {
+        if (clientStatsBuilder_ == null) {
+          return java.util.Collections.unmodifiableList(clientStats_);
+        } else {
+          return clientStatsBuilder_.getMessageList();
+        }
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public int getClientStatsCount() {
+        if (clientStatsBuilder_ == null) {
+          return clientStats_.size();
+        } else {
+          return clientStatsBuilder_.getCount();
+        }
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ClientStats getClientStats(int index) {
+        if (clientStatsBuilder_ == null) {
+          return clientStats_.get(index);
+        } else {
+          return clientStatsBuilder_.getMessage(index);
+        }
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public Builder setClientStats(
+          int index, io.grpc.benchmarks.proto.Stats.ClientStats value) {
+        if (clientStatsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureClientStatsIsMutable();
+          clientStats_.set(index, value);
+          onChanged();
+        } else {
+          clientStatsBuilder_.setMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public Builder setClientStats(
+          int index, io.grpc.benchmarks.proto.Stats.ClientStats.Builder builderForValue) {
+        if (clientStatsBuilder_ == null) {
+          ensureClientStatsIsMutable();
+          clientStats_.set(index, builderForValue.build());
+          onChanged();
+        } else {
+          clientStatsBuilder_.setMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public Builder addClientStats(io.grpc.benchmarks.proto.Stats.ClientStats value) {
+        if (clientStatsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureClientStatsIsMutable();
+          clientStats_.add(value);
+          onChanged();
+        } else {
+          clientStatsBuilder_.addMessage(value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public Builder addClientStats(
+          int index, io.grpc.benchmarks.proto.Stats.ClientStats value) {
+        if (clientStatsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureClientStatsIsMutable();
+          clientStats_.add(index, value);
+          onChanged();
+        } else {
+          clientStatsBuilder_.addMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public Builder addClientStats(
+          io.grpc.benchmarks.proto.Stats.ClientStats.Builder builderForValue) {
+        if (clientStatsBuilder_ == null) {
+          ensureClientStatsIsMutable();
+          clientStats_.add(builderForValue.build());
+          onChanged();
+        } else {
+          clientStatsBuilder_.addMessage(builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public Builder addClientStats(
+          int index, io.grpc.benchmarks.proto.Stats.ClientStats.Builder builderForValue) {
+        if (clientStatsBuilder_ == null) {
+          ensureClientStatsIsMutable();
+          clientStats_.add(index, builderForValue.build());
+          onChanged();
+        } else {
+          clientStatsBuilder_.addMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public Builder addAllClientStats(
+          java.lang.Iterable<? extends io.grpc.benchmarks.proto.Stats.ClientStats> values) {
+        if (clientStatsBuilder_ == null) {
+          ensureClientStatsIsMutable();
+          com.google.protobuf.AbstractMessageLite.Builder.addAll(
+              values, clientStats_);
+          onChanged();
+        } else {
+          clientStatsBuilder_.addAllMessages(values);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public Builder clearClientStats() {
+        if (clientStatsBuilder_ == null) {
+          clientStats_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000004);
+          onChanged();
+        } else {
+          clientStatsBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public Builder removeClientStats(int index) {
+        if (clientStatsBuilder_ == null) {
+          ensureClientStatsIsMutable();
+          clientStats_.remove(index);
+          onChanged();
+        } else {
+          clientStatsBuilder_.remove(index);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ClientStats.Builder getClientStatsBuilder(
+          int index) {
+        return getClientStatsFieldBuilder().getBuilder(index);
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder getClientStatsOrBuilder(
+          int index) {
+        if (clientStatsBuilder_ == null) {
+          return clientStats_.get(index);  } else {
+          return clientStatsBuilder_.getMessageOrBuilder(index);
+        }
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public java.util.List<? extends io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder> 
+           getClientStatsOrBuilderList() {
+        if (clientStatsBuilder_ != null) {
+          return clientStatsBuilder_.getMessageOrBuilderList();
+        } else {
+          return java.util.Collections.unmodifiableList(clientStats_);
+        }
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ClientStats.Builder addClientStatsBuilder() {
+        return getClientStatsFieldBuilder().addBuilder(
+            io.grpc.benchmarks.proto.Stats.ClientStats.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ClientStats.Builder addClientStatsBuilder(
+          int index) {
+        return getClientStatsFieldBuilder().addBuilder(
+            index, io.grpc.benchmarks.proto.Stats.ClientStats.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * Client stats for each client
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ClientStats client_stats = 3;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Stats.ClientStats.Builder> 
+           getClientStatsBuilderList() {
+        return getClientStatsFieldBuilder().getBuilderList();
+      }
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.ClientStats, io.grpc.benchmarks.proto.Stats.ClientStats.Builder, io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder> 
+          getClientStatsFieldBuilder() {
+        if (clientStatsBuilder_ == null) {
+          clientStatsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+              io.grpc.benchmarks.proto.Stats.ClientStats, io.grpc.benchmarks.proto.Stats.ClientStats.Builder, io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder>(
+                  clientStats_,
+                  ((bitField0_ & 0x00000004) == 0x00000004),
+                  getParentForChildren(),
+                  isClean());
+          clientStats_ = null;
+        }
+        return clientStatsBuilder_;
+      }
+
+      private java.util.List<io.grpc.benchmarks.proto.Stats.ServerStats> serverStats_ =
+        java.util.Collections.emptyList();
+      private void ensureServerStatsIsMutable() {
+        if (!((bitField0_ & 0x00000008) == 0x00000008)) {
+          serverStats_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Stats.ServerStats>(serverStats_);
+          bitField0_ |= 0x00000008;
+         }
+      }
+
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.ServerStats, io.grpc.benchmarks.proto.Stats.ServerStats.Builder, io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder> serverStatsBuilder_;
+
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Stats.ServerStats> getServerStatsList() {
+        if (serverStatsBuilder_ == null) {
+          return java.util.Collections.unmodifiableList(serverStats_);
+        } else {
+          return serverStatsBuilder_.getMessageList();
+        }
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public int getServerStatsCount() {
+        if (serverStatsBuilder_ == null) {
+          return serverStats_.size();
+        } else {
+          return serverStatsBuilder_.getCount();
+        }
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ServerStats getServerStats(int index) {
+        if (serverStatsBuilder_ == null) {
+          return serverStats_.get(index);
+        } else {
+          return serverStatsBuilder_.getMessage(index);
+        }
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public Builder setServerStats(
+          int index, io.grpc.benchmarks.proto.Stats.ServerStats value) {
+        if (serverStatsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureServerStatsIsMutable();
+          serverStats_.set(index, value);
+          onChanged();
+        } else {
+          serverStatsBuilder_.setMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public Builder setServerStats(
+          int index, io.grpc.benchmarks.proto.Stats.ServerStats.Builder builderForValue) {
+        if (serverStatsBuilder_ == null) {
+          ensureServerStatsIsMutable();
+          serverStats_.set(index, builderForValue.build());
+          onChanged();
+        } else {
+          serverStatsBuilder_.setMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public Builder addServerStats(io.grpc.benchmarks.proto.Stats.ServerStats value) {
+        if (serverStatsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureServerStatsIsMutable();
+          serverStats_.add(value);
+          onChanged();
+        } else {
+          serverStatsBuilder_.addMessage(value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public Builder addServerStats(
+          int index, io.grpc.benchmarks.proto.Stats.ServerStats value) {
+        if (serverStatsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureServerStatsIsMutable();
+          serverStats_.add(index, value);
+          onChanged();
+        } else {
+          serverStatsBuilder_.addMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public Builder addServerStats(
+          io.grpc.benchmarks.proto.Stats.ServerStats.Builder builderForValue) {
+        if (serverStatsBuilder_ == null) {
+          ensureServerStatsIsMutable();
+          serverStats_.add(builderForValue.build());
+          onChanged();
+        } else {
+          serverStatsBuilder_.addMessage(builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public Builder addServerStats(
+          int index, io.grpc.benchmarks.proto.Stats.ServerStats.Builder builderForValue) {
+        if (serverStatsBuilder_ == null) {
+          ensureServerStatsIsMutable();
+          serverStats_.add(index, builderForValue.build());
+          onChanged();
+        } else {
+          serverStatsBuilder_.addMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public Builder addAllServerStats(
+          java.lang.Iterable<? extends io.grpc.benchmarks.proto.Stats.ServerStats> values) {
+        if (serverStatsBuilder_ == null) {
+          ensureServerStatsIsMutable();
+          com.google.protobuf.AbstractMessageLite.Builder.addAll(
+              values, serverStats_);
+          onChanged();
+        } else {
+          serverStatsBuilder_.addAllMessages(values);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public Builder clearServerStats() {
+        if (serverStatsBuilder_ == null) {
+          serverStats_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000008);
+          onChanged();
+        } else {
+          serverStatsBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public Builder removeServerStats(int index) {
+        if (serverStatsBuilder_ == null) {
+          ensureServerStatsIsMutable();
+          serverStats_.remove(index);
+          onChanged();
+        } else {
+          serverStatsBuilder_.remove(index);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ServerStats.Builder getServerStatsBuilder(
+          int index) {
+        return getServerStatsFieldBuilder().getBuilder(index);
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder getServerStatsOrBuilder(
+          int index) {
+        if (serverStatsBuilder_ == null) {
+          return serverStats_.get(index);  } else {
+          return serverStatsBuilder_.getMessageOrBuilder(index);
+        }
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public java.util.List<? extends io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder> 
+           getServerStatsOrBuilderList() {
+        if (serverStatsBuilder_ != null) {
+          return serverStatsBuilder_.getMessageOrBuilderList();
+        } else {
+          return java.util.Collections.unmodifiableList(serverStats_);
+        }
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ServerStats.Builder addServerStatsBuilder() {
+        return getServerStatsFieldBuilder().addBuilder(
+            io.grpc.benchmarks.proto.Stats.ServerStats.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.ServerStats.Builder addServerStatsBuilder(
+          int index) {
+        return getServerStatsFieldBuilder().addBuilder(
+            index, io.grpc.benchmarks.proto.Stats.ServerStats.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * Server stats for each server
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ServerStats server_stats = 4;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Stats.ServerStats.Builder> 
+           getServerStatsBuilderList() {
+        return getServerStatsFieldBuilder().getBuilderList();
+      }
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.ServerStats, io.grpc.benchmarks.proto.Stats.ServerStats.Builder, io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder> 
+          getServerStatsFieldBuilder() {
+        if (serverStatsBuilder_ == null) {
+          serverStatsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+              io.grpc.benchmarks.proto.Stats.ServerStats, io.grpc.benchmarks.proto.Stats.ServerStats.Builder, io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder>(
+                  serverStats_,
+                  ((bitField0_ & 0x00000008) == 0x00000008),
+                  getParentForChildren(),
+                  isClean());
+          serverStats_ = null;
+        }
+        return serverStatsBuilder_;
+      }
+
+      private java.util.List<java.lang.Integer> serverCores_ = java.util.Collections.emptyList();
+      private void ensureServerCoresIsMutable() {
+        if (!((bitField0_ & 0x00000010) == 0x00000010)) {
+          serverCores_ = new java.util.ArrayList<java.lang.Integer>(serverCores_);
+          bitField0_ |= 0x00000010;
+         }
+      }
+      /**
+       * <pre>
+       * Number of cores available to each server
+       * </pre>
+       *
+       * <code>repeated int32 server_cores = 5;</code>
+       */
+      public java.util.List<java.lang.Integer>
+          getServerCoresList() {
+        return java.util.Collections.unmodifiableList(serverCores_);
+      }
+      /**
+       * <pre>
+       * Number of cores available to each server
+       * </pre>
+       *
+       * <code>repeated int32 server_cores = 5;</code>
+       */
+      public int getServerCoresCount() {
+        return serverCores_.size();
+      }
+      /**
+       * <pre>
+       * Number of cores available to each server
+       * </pre>
+       *
+       * <code>repeated int32 server_cores = 5;</code>
+       */
+      public int getServerCores(int index) {
+        return serverCores_.get(index);
+      }
+      /**
+       * <pre>
+       * Number of cores available to each server
+       * </pre>
+       *
+       * <code>repeated int32 server_cores = 5;</code>
+       */
+      public Builder setServerCores(
+          int index, int value) {
+        ensureServerCoresIsMutable();
+        serverCores_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of cores available to each server
+       * </pre>
+       *
+       * <code>repeated int32 server_cores = 5;</code>
+       */
+      public Builder addServerCores(int value) {
+        ensureServerCoresIsMutable();
+        serverCores_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of cores available to each server
+       * </pre>
+       *
+       * <code>repeated int32 server_cores = 5;</code>
+       */
+      public Builder addAllServerCores(
+          java.lang.Iterable<? extends java.lang.Integer> values) {
+        ensureServerCoresIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, serverCores_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of cores available to each server
+       * </pre>
+       *
+       * <code>repeated int32 server_cores = 5;</code>
+       */
+      public Builder clearServerCores() {
+        serverCores_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000010);
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Control.ScenarioResultSummary summary_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ScenarioResultSummary, io.grpc.benchmarks.proto.Control.ScenarioResultSummary.Builder, io.grpc.benchmarks.proto.Control.ScenarioResultSummaryOrBuilder> summaryBuilder_;
+      /**
+       * <pre>
+       * An after-the-fact computed summary
+       * </pre>
+       *
+       * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+       */
+      public boolean hasSummary() {
+        return summaryBuilder_ != null || summary_ != null;
+      }
+      /**
+       * <pre>
+       * An after-the-fact computed summary
+       * </pre>
+       *
+       * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ScenarioResultSummary getSummary() {
+        if (summaryBuilder_ == null) {
+          return summary_ == null ? io.grpc.benchmarks.proto.Control.ScenarioResultSummary.getDefaultInstance() : summary_;
+        } else {
+          return summaryBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * An after-the-fact computed summary
+       * </pre>
+       *
+       * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+       */
+      public Builder setSummary(io.grpc.benchmarks.proto.Control.ScenarioResultSummary value) {
+        if (summaryBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          summary_ = value;
+          onChanged();
+        } else {
+          summaryBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * An after-the-fact computed summary
+       * </pre>
+       *
+       * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+       */
+      public Builder setSummary(
+          io.grpc.benchmarks.proto.Control.ScenarioResultSummary.Builder builderForValue) {
+        if (summaryBuilder_ == null) {
+          summary_ = builderForValue.build();
+          onChanged();
+        } else {
+          summaryBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * An after-the-fact computed summary
+       * </pre>
+       *
+       * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+       */
+      public Builder mergeSummary(io.grpc.benchmarks.proto.Control.ScenarioResultSummary value) {
+        if (summaryBuilder_ == null) {
+          if (summary_ != null) {
+            summary_ =
+              io.grpc.benchmarks.proto.Control.ScenarioResultSummary.newBuilder(summary_).mergeFrom(value).buildPartial();
+          } else {
+            summary_ = value;
+          }
+          onChanged();
+        } else {
+          summaryBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * An after-the-fact computed summary
+       * </pre>
+       *
+       * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+       */
+      public Builder clearSummary() {
+        if (summaryBuilder_ == null) {
+          summary_ = null;
+          onChanged();
+        } else {
+          summary_ = null;
+          summaryBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * An after-the-fact computed summary
+       * </pre>
+       *
+       * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ScenarioResultSummary.Builder getSummaryBuilder() {
+        
+        onChanged();
+        return getSummaryFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * An after-the-fact computed summary
+       * </pre>
+       *
+       * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+       */
+      public io.grpc.benchmarks.proto.Control.ScenarioResultSummaryOrBuilder getSummaryOrBuilder() {
+        if (summaryBuilder_ != null) {
+          return summaryBuilder_.getMessageOrBuilder();
+        } else {
+          return summary_ == null ?
+              io.grpc.benchmarks.proto.Control.ScenarioResultSummary.getDefaultInstance() : summary_;
+        }
+      }
+      /**
+       * <pre>
+       * An after-the-fact computed summary
+       * </pre>
+       *
+       * <code>.grpc.testing.ScenarioResultSummary summary = 6;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Control.ScenarioResultSummary, io.grpc.benchmarks.proto.Control.ScenarioResultSummary.Builder, io.grpc.benchmarks.proto.Control.ScenarioResultSummaryOrBuilder> 
+          getSummaryFieldBuilder() {
+        if (summaryBuilder_ == null) {
+          summaryBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Control.ScenarioResultSummary, io.grpc.benchmarks.proto.Control.ScenarioResultSummary.Builder, io.grpc.benchmarks.proto.Control.ScenarioResultSummaryOrBuilder>(
+                  getSummary(),
+                  getParentForChildren(),
+                  isClean());
+          summary_ = null;
+        }
+        return summaryBuilder_;
+      }
+
+      private java.util.List<java.lang.Boolean> clientSuccess_ = java.util.Collections.emptyList();
+      private void ensureClientSuccessIsMutable() {
+        if (!((bitField0_ & 0x00000040) == 0x00000040)) {
+          clientSuccess_ = new java.util.ArrayList<java.lang.Boolean>(clientSuccess_);
+          bitField0_ |= 0x00000040;
+         }
+      }
+      /**
+       * <pre>
+       * Information on success or failure of each worker
+       * </pre>
+       *
+       * <code>repeated bool client_success = 7;</code>
+       */
+      public java.util.List<java.lang.Boolean>
+          getClientSuccessList() {
+        return java.util.Collections.unmodifiableList(clientSuccess_);
+      }
+      /**
+       * <pre>
+       * Information on success or failure of each worker
+       * </pre>
+       *
+       * <code>repeated bool client_success = 7;</code>
+       */
+      public int getClientSuccessCount() {
+        return clientSuccess_.size();
+      }
+      /**
+       * <pre>
+       * Information on success or failure of each worker
+       * </pre>
+       *
+       * <code>repeated bool client_success = 7;</code>
+       */
+      public boolean getClientSuccess(int index) {
+        return clientSuccess_.get(index);
+      }
+      /**
+       * <pre>
+       * Information on success or failure of each worker
+       * </pre>
+       *
+       * <code>repeated bool client_success = 7;</code>
+       */
+      public Builder setClientSuccess(
+          int index, boolean value) {
+        ensureClientSuccessIsMutable();
+        clientSuccess_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Information on success or failure of each worker
+       * </pre>
+       *
+       * <code>repeated bool client_success = 7;</code>
+       */
+      public Builder addClientSuccess(boolean value) {
+        ensureClientSuccessIsMutable();
+        clientSuccess_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Information on success or failure of each worker
+       * </pre>
+       *
+       * <code>repeated bool client_success = 7;</code>
+       */
+      public Builder addAllClientSuccess(
+          java.lang.Iterable<? extends java.lang.Boolean> values) {
+        ensureClientSuccessIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, clientSuccess_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Information on success or failure of each worker
+       * </pre>
+       *
+       * <code>repeated bool client_success = 7;</code>
+       */
+      public Builder clearClientSuccess() {
+        clientSuccess_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000040);
+        onChanged();
+        return this;
+      }
+
+      private java.util.List<java.lang.Boolean> serverSuccess_ = java.util.Collections.emptyList();
+      private void ensureServerSuccessIsMutable() {
+        if (!((bitField0_ & 0x00000080) == 0x00000080)) {
+          serverSuccess_ = new java.util.ArrayList<java.lang.Boolean>(serverSuccess_);
+          bitField0_ |= 0x00000080;
+         }
+      }
+      /**
+       * <code>repeated bool server_success = 8;</code>
+       */
+      public java.util.List<java.lang.Boolean>
+          getServerSuccessList() {
+        return java.util.Collections.unmodifiableList(serverSuccess_);
+      }
+      /**
+       * <code>repeated bool server_success = 8;</code>
+       */
+      public int getServerSuccessCount() {
+        return serverSuccess_.size();
+      }
+      /**
+       * <code>repeated bool server_success = 8;</code>
+       */
+      public boolean getServerSuccess(int index) {
+        return serverSuccess_.get(index);
+      }
+      /**
+       * <code>repeated bool server_success = 8;</code>
+       */
+      public Builder setServerSuccess(
+          int index, boolean value) {
+        ensureServerSuccessIsMutable();
+        serverSuccess_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated bool server_success = 8;</code>
+       */
+      public Builder addServerSuccess(boolean value) {
+        ensureServerSuccessIsMutable();
+        serverSuccess_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated bool server_success = 8;</code>
+       */
+      public Builder addAllServerSuccess(
+          java.lang.Iterable<? extends java.lang.Boolean> values) {
+        ensureServerSuccessIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, serverSuccess_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated bool server_success = 8;</code>
+       */
+      public Builder clearServerSuccess() {
+        serverSuccess_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000080);
+        onChanged();
+        return this;
+      }
+
+      private java.util.List<io.grpc.benchmarks.proto.Stats.RequestResultCount> requestResults_ =
+        java.util.Collections.emptyList();
+      private void ensureRequestResultsIsMutable() {
+        if (!((bitField0_ & 0x00000100) == 0x00000100)) {
+          requestResults_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Stats.RequestResultCount>(requestResults_);
+          bitField0_ |= 0x00000100;
+         }
+      }
+
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.RequestResultCount, io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder, io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder> requestResultsBuilder_;
+
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Stats.RequestResultCount> getRequestResultsList() {
+        if (requestResultsBuilder_ == null) {
+          return java.util.Collections.unmodifiableList(requestResults_);
+        } else {
+          return requestResultsBuilder_.getMessageList();
+        }
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public int getRequestResultsCount() {
+        if (requestResultsBuilder_ == null) {
+          return requestResults_.size();
+        } else {
+          return requestResultsBuilder_.getCount();
+        }
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.RequestResultCount getRequestResults(int index) {
+        if (requestResultsBuilder_ == null) {
+          return requestResults_.get(index);
+        } else {
+          return requestResultsBuilder_.getMessage(index);
+        }
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public Builder setRequestResults(
+          int index, io.grpc.benchmarks.proto.Stats.RequestResultCount value) {
+        if (requestResultsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureRequestResultsIsMutable();
+          requestResults_.set(index, value);
+          onChanged();
+        } else {
+          requestResultsBuilder_.setMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public Builder setRequestResults(
+          int index, io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder builderForValue) {
+        if (requestResultsBuilder_ == null) {
+          ensureRequestResultsIsMutable();
+          requestResults_.set(index, builderForValue.build());
+          onChanged();
+        } else {
+          requestResultsBuilder_.setMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public Builder addRequestResults(io.grpc.benchmarks.proto.Stats.RequestResultCount value) {
+        if (requestResultsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureRequestResultsIsMutable();
+          requestResults_.add(value);
+          onChanged();
+        } else {
+          requestResultsBuilder_.addMessage(value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public Builder addRequestResults(
+          int index, io.grpc.benchmarks.proto.Stats.RequestResultCount value) {
+        if (requestResultsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureRequestResultsIsMutable();
+          requestResults_.add(index, value);
+          onChanged();
+        } else {
+          requestResultsBuilder_.addMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public Builder addRequestResults(
+          io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder builderForValue) {
+        if (requestResultsBuilder_ == null) {
+          ensureRequestResultsIsMutable();
+          requestResults_.add(builderForValue.build());
+          onChanged();
+        } else {
+          requestResultsBuilder_.addMessage(builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public Builder addRequestResults(
+          int index, io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder builderForValue) {
+        if (requestResultsBuilder_ == null) {
+          ensureRequestResultsIsMutable();
+          requestResults_.add(index, builderForValue.build());
+          onChanged();
+        } else {
+          requestResultsBuilder_.addMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public Builder addAllRequestResults(
+          java.lang.Iterable<? extends io.grpc.benchmarks.proto.Stats.RequestResultCount> values) {
+        if (requestResultsBuilder_ == null) {
+          ensureRequestResultsIsMutable();
+          com.google.protobuf.AbstractMessageLite.Builder.addAll(
+              values, requestResults_);
+          onChanged();
+        } else {
+          requestResultsBuilder_.addAllMessages(values);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public Builder clearRequestResults() {
+        if (requestResultsBuilder_ == null) {
+          requestResults_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000100);
+          onChanged();
+        } else {
+          requestResultsBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public Builder removeRequestResults(int index) {
+        if (requestResultsBuilder_ == null) {
+          ensureRequestResultsIsMutable();
+          requestResults_.remove(index);
+          onChanged();
+        } else {
+          requestResultsBuilder_.remove(index);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder getRequestResultsBuilder(
+          int index) {
+        return getRequestResultsFieldBuilder().getBuilder(index);
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder getRequestResultsOrBuilder(
+          int index) {
+        if (requestResultsBuilder_ == null) {
+          return requestResults_.get(index);  } else {
+          return requestResultsBuilder_.getMessageOrBuilder(index);
+        }
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public java.util.List<? extends io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder> 
+           getRequestResultsOrBuilderList() {
+        if (requestResultsBuilder_ != null) {
+          return requestResultsBuilder_.getMessageOrBuilderList();
+        } else {
+          return java.util.Collections.unmodifiableList(requestResults_);
+        }
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder addRequestResultsBuilder() {
+        return getRequestResultsFieldBuilder().addBuilder(
+            io.grpc.benchmarks.proto.Stats.RequestResultCount.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder addRequestResultsBuilder(
+          int index) {
+        return getRequestResultsFieldBuilder().addBuilder(
+            index, io.grpc.benchmarks.proto.Stats.RequestResultCount.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 9;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder> 
+           getRequestResultsBuilderList() {
+        return getRequestResultsFieldBuilder().getBuilderList();
+      }
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.RequestResultCount, io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder, io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder> 
+          getRequestResultsFieldBuilder() {
+        if (requestResultsBuilder_ == null) {
+          requestResultsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+              io.grpc.benchmarks.proto.Stats.RequestResultCount, io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder, io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder>(
+                  requestResults_,
+                  ((bitField0_ & 0x00000100) == 0x00000100),
+                  getParentForChildren(),
+                  isClean());
+          requestResults_ = null;
+        }
+        return requestResultsBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ScenarioResult)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ScenarioResult)
+    private static final io.grpc.benchmarks.proto.Control.ScenarioResult DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Control.ScenarioResult();
+    }
+
+    public static io.grpc.benchmarks.proto.Control.ScenarioResult getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ScenarioResult>
+        PARSER = new com.google.protobuf.AbstractParser<ScenarioResult>() {
+      public ScenarioResult parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ScenarioResult(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ScenarioResult> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ScenarioResult> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Control.ScenarioResult getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_PoissonParams_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_PoissonParams_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ClosedLoopParams_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ClosedLoopParams_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_LoadParams_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_LoadParams_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_SecurityParams_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_SecurityParams_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ChannelArg_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ChannelArg_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ClientConfig_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ClientConfig_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ClientStatus_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ClientStatus_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_Mark_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_Mark_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ClientArgs_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ClientArgs_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ServerConfig_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ServerConfig_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ServerArgs_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ServerArgs_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ServerStatus_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ServerStatus_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_CoreRequest_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_CoreRequest_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_CoreResponse_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_CoreResponse_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_Void_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_Void_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_Scenario_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_Scenario_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_Scenarios_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_Scenarios_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ScenarioResultSummary_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ScenarioResultSummary_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ScenarioResult_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ScenarioResult_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\032grpc/testing/control.proto\022\014grpc.testi" +
+      "ng\032\033grpc/testing/payloads.proto\032\030grpc/te" +
+      "sting/stats.proto\"%\n\rPoissonParams\022\024\n\014of" +
+      "fered_load\030\001 \001(\001\"\022\n\020ClosedLoopParams\"{\n\n" +
+      "LoadParams\0225\n\013closed_loop\030\001 \001(\0132\036.grpc.t" +
+      "esting.ClosedLoopParamsH\000\022.\n\007poisson\030\002 \001" +
+      "(\0132\033.grpc.testing.PoissonParamsH\000B\006\n\004loa" +
+      "d\"C\n\016SecurityParams\022\023\n\013use_test_ca\030\001 \001(\010" +
+      "\022\034\n\024server_host_override\030\002 \001(\t\"M\n\nChanne" +
+      "lArg\022\014\n\004name\030\001 \001(\t\022\023\n\tstr_value\030\002 \001(\tH\000\022" +
+      "\023\n\tint_value\030\003 \001(\005H\000B\007\n\005value\"\275\004\n\014Client" +
+      "Config\022\026\n\016server_targets\030\001 \003(\t\022-\n\013client" +
+      "_type\030\002 \001(\0162\030.grpc.testing.ClientType\0225\n" +
+      "\017security_params\030\003 \001(\0132\034.grpc.testing.Se" +
+      "curityParams\022$\n\034outstanding_rpcs_per_cha" +
+      "nnel\030\004 \001(\005\022\027\n\017client_channels\030\005 \001(\005\022\034\n\024a" +
+      "sync_client_threads\030\007 \001(\005\022\'\n\010rpc_type\030\010 " +
+      "\001(\0162\025.grpc.testing.RpcType\022-\n\013load_param" +
+      "s\030\n \001(\0132\030.grpc.testing.LoadParams\0223\n\016pay" +
+      "load_config\030\013 \001(\0132\033.grpc.testing.Payload" +
+      "Config\0227\n\020histogram_params\030\014 \001(\0132\035.grpc." +
+      "testing.HistogramParams\022\021\n\tcore_list\030\r \003" +
+      "(\005\022\022\n\ncore_limit\030\016 \001(\005\022\030\n\020other_client_a" +
+      "pi\030\017 \001(\t\022.\n\014channel_args\030\020 \003(\0132\030.grpc.te" +
+      "sting.ChannelArg\022\033\n\023messages_per_stream\030" +
+      "\022 \001(\005\"8\n\014ClientStatus\022(\n\005stats\030\001 \001(\0132\031.g" +
+      "rpc.testing.ClientStats\"\025\n\004Mark\022\r\n\005reset" +
+      "\030\001 \001(\010\"h\n\nClientArgs\022+\n\005setup\030\001 \001(\0132\032.gr" +
+      "pc.testing.ClientConfigH\000\022\"\n\004mark\030\002 \001(\0132" +
+      "\022.grpc.testing.MarkH\000B\t\n\007argtype\"\264\002\n\014Ser" +
+      "verConfig\022-\n\013server_type\030\001 \001(\0162\030.grpc.te" +
+      "sting.ServerType\0225\n\017security_params\030\002 \001(" +
+      "\0132\034.grpc.testing.SecurityParams\022\014\n\004port\030" +
+      "\004 \001(\005\022\034\n\024async_server_threads\030\007 \001(\005\022\022\n\nc" +
+      "ore_limit\030\010 \001(\005\0223\n\016payload_config\030\t \001(\0132" +
+      "\033.grpc.testing.PayloadConfig\022\021\n\tcore_lis" +
+      "t\030\n \003(\005\022\030\n\020other_server_api\030\013 \001(\t\022\034\n\023res" +
+      "ource_quota_size\030\351\007 \001(\005\"h\n\nServerArgs\022+\n" +
+      "\005setup\030\001 \001(\0132\032.grpc.testing.ServerConfig" +
+      "H\000\022\"\n\004mark\030\002 \001(\0132\022.grpc.testing.MarkH\000B\t" +
+      "\n\007argtype\"U\n\014ServerStatus\022(\n\005stats\030\001 \001(\013" +
+      "2\031.grpc.testing.ServerStats\022\014\n\004port\030\002 \001(" +
+      "\005\022\r\n\005cores\030\003 \001(\005\"\r\n\013CoreRequest\"\035\n\014CoreR" +
+      "esponse\022\r\n\005cores\030\001 \001(\005\"\006\n\004Void\"\375\001\n\010Scena" +
+      "rio\022\014\n\004name\030\001 \001(\t\0221\n\rclient_config\030\002 \001(\013" +
+      "2\032.grpc.testing.ClientConfig\022\023\n\013num_clie" +
+      "nts\030\003 \001(\005\0221\n\rserver_config\030\004 \001(\0132\032.grpc." +
+      "testing.ServerConfig\022\023\n\013num_servers\030\005 \001(" +
+      "\005\022\026\n\016warmup_seconds\030\006 \001(\005\022\031\n\021benchmark_s" +
+      "econds\030\007 \001(\005\022 \n\030spawn_local_worker_count" +
+      "\030\010 \001(\005\"6\n\tScenarios\022)\n\tscenarios\030\001 \003(\0132\026" +
+      ".grpc.testing.Scenario\"\274\003\n\025ScenarioResul" +
+      "tSummary\022\013\n\003qps\030\001 \001(\001\022\033\n\023qps_per_server_" +
+      "core\030\002 \001(\001\022\032\n\022server_system_time\030\003 \001(\001\022\030" +
+      "\n\020server_user_time\030\004 \001(\001\022\032\n\022client_syste" +
+      "m_time\030\005 \001(\001\022\030\n\020client_user_time\030\006 \001(\001\022\022" +
+      "\n\nlatency_50\030\007 \001(\001\022\022\n\nlatency_90\030\010 \001(\001\022\022" +
+      "\n\nlatency_95\030\t \001(\001\022\022\n\nlatency_99\030\n \001(\001\022\023" +
+      "\n\013latency_999\030\013 \001(\001\022\030\n\020server_cpu_usage\030" +
+      "\014 \001(\001\022&\n\036successful_requests_per_second\030" +
+      "\r \001(\001\022\"\n\032failed_requests_per_second\030\016 \001(" +
+      "\001\022 \n\030client_polls_per_request\030\017 \001(\001\022 \n\030s" +
+      "erver_polls_per_request\030\020 \001(\001\"\203\003\n\016Scenar" +
+      "ioResult\022(\n\010scenario\030\001 \001(\0132\026.grpc.testin" +
+      "g.Scenario\022.\n\tlatencies\030\002 \001(\0132\033.grpc.tes" +
+      "ting.HistogramData\022/\n\014client_stats\030\003 \003(\013" +
+      "2\031.grpc.testing.ClientStats\022/\n\014server_st" +
+      "ats\030\004 \003(\0132\031.grpc.testing.ServerStats\022\024\n\014" +
+      "server_cores\030\005 \003(\005\0224\n\007summary\030\006 \001(\0132#.gr" +
+      "pc.testing.ScenarioResultSummary\022\026\n\016clie" +
+      "nt_success\030\007 \003(\010\022\026\n\016server_success\030\010 \003(\010" +
+      "\0229\n\017request_results\030\t \003(\0132 .grpc.testing" +
+      ".RequestResultCount*A\n\nClientType\022\017\n\013SYN" +
+      "C_CLIENT\020\000\022\020\n\014ASYNC_CLIENT\020\001\022\020\n\014OTHER_CL" +
+      "IENT\020\002*[\n\nServerType\022\017\n\013SYNC_SERVER\020\000\022\020\n" +
+      "\014ASYNC_SERVER\020\001\022\030\n\024ASYNC_GENERIC_SERVER\020" +
+      "\002\022\020\n\014OTHER_SERVER\020\003*r\n\007RpcType\022\t\n\005UNARY\020" +
+      "\000\022\r\n\tSTREAMING\020\001\022\031\n\025STREAMING_FROM_CLIEN" +
+      "T\020\002\022\031\n\025STREAMING_FROM_SERVER\020\003\022\027\n\023STREAM" +
+      "ING_BOTH_WAYS\020\004B#\n\030io.grpc.benchmarks.pr" +
+      "otoB\007Controlb\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          io.grpc.benchmarks.proto.Payloads.getDescriptor(),
+          io.grpc.benchmarks.proto.Stats.getDescriptor(),
+        }, assigner);
+    internal_static_grpc_testing_PoissonParams_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_testing_PoissonParams_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_PoissonParams_descriptor,
+        new java.lang.String[] { "OfferedLoad", });
+    internal_static_grpc_testing_ClosedLoopParams_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_testing_ClosedLoopParams_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ClosedLoopParams_descriptor,
+        new java.lang.String[] { });
+    internal_static_grpc_testing_LoadParams_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_testing_LoadParams_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_LoadParams_descriptor,
+        new java.lang.String[] { "ClosedLoop", "Poisson", "Load", });
+    internal_static_grpc_testing_SecurityParams_descriptor =
+      getDescriptor().getMessageTypes().get(3);
+    internal_static_grpc_testing_SecurityParams_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_SecurityParams_descriptor,
+        new java.lang.String[] { "UseTestCa", "ServerHostOverride", });
+    internal_static_grpc_testing_ChannelArg_descriptor =
+      getDescriptor().getMessageTypes().get(4);
+    internal_static_grpc_testing_ChannelArg_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ChannelArg_descriptor,
+        new java.lang.String[] { "Name", "StrValue", "IntValue", "Value", });
+    internal_static_grpc_testing_ClientConfig_descriptor =
+      getDescriptor().getMessageTypes().get(5);
+    internal_static_grpc_testing_ClientConfig_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ClientConfig_descriptor,
+        new java.lang.String[] { "ServerTargets", "ClientType", "SecurityParams", "OutstandingRpcsPerChannel", "ClientChannels", "AsyncClientThreads", "RpcType", "LoadParams", "PayloadConfig", "HistogramParams", "CoreList", "CoreLimit", "OtherClientApi", "ChannelArgs", "MessagesPerStream", });
+    internal_static_grpc_testing_ClientStatus_descriptor =
+      getDescriptor().getMessageTypes().get(6);
+    internal_static_grpc_testing_ClientStatus_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ClientStatus_descriptor,
+        new java.lang.String[] { "Stats", });
+    internal_static_grpc_testing_Mark_descriptor =
+      getDescriptor().getMessageTypes().get(7);
+    internal_static_grpc_testing_Mark_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_Mark_descriptor,
+        new java.lang.String[] { "Reset", });
+    internal_static_grpc_testing_ClientArgs_descriptor =
+      getDescriptor().getMessageTypes().get(8);
+    internal_static_grpc_testing_ClientArgs_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ClientArgs_descriptor,
+        new java.lang.String[] { "Setup", "Mark", "Argtype", });
+    internal_static_grpc_testing_ServerConfig_descriptor =
+      getDescriptor().getMessageTypes().get(9);
+    internal_static_grpc_testing_ServerConfig_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ServerConfig_descriptor,
+        new java.lang.String[] { "ServerType", "SecurityParams", "Port", "AsyncServerThreads", "CoreLimit", "PayloadConfig", "CoreList", "OtherServerApi", "ResourceQuotaSize", });
+    internal_static_grpc_testing_ServerArgs_descriptor =
+      getDescriptor().getMessageTypes().get(10);
+    internal_static_grpc_testing_ServerArgs_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ServerArgs_descriptor,
+        new java.lang.String[] { "Setup", "Mark", "Argtype", });
+    internal_static_grpc_testing_ServerStatus_descriptor =
+      getDescriptor().getMessageTypes().get(11);
+    internal_static_grpc_testing_ServerStatus_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ServerStatus_descriptor,
+        new java.lang.String[] { "Stats", "Port", "Cores", });
+    internal_static_grpc_testing_CoreRequest_descriptor =
+      getDescriptor().getMessageTypes().get(12);
+    internal_static_grpc_testing_CoreRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_CoreRequest_descriptor,
+        new java.lang.String[] { });
+    internal_static_grpc_testing_CoreResponse_descriptor =
+      getDescriptor().getMessageTypes().get(13);
+    internal_static_grpc_testing_CoreResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_CoreResponse_descriptor,
+        new java.lang.String[] { "Cores", });
+    internal_static_grpc_testing_Void_descriptor =
+      getDescriptor().getMessageTypes().get(14);
+    internal_static_grpc_testing_Void_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_Void_descriptor,
+        new java.lang.String[] { });
+    internal_static_grpc_testing_Scenario_descriptor =
+      getDescriptor().getMessageTypes().get(15);
+    internal_static_grpc_testing_Scenario_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_Scenario_descriptor,
+        new java.lang.String[] { "Name", "ClientConfig", "NumClients", "ServerConfig", "NumServers", "WarmupSeconds", "BenchmarkSeconds", "SpawnLocalWorkerCount", });
+    internal_static_grpc_testing_Scenarios_descriptor =
+      getDescriptor().getMessageTypes().get(16);
+    internal_static_grpc_testing_Scenarios_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_Scenarios_descriptor,
+        new java.lang.String[] { "Scenarios", });
+    internal_static_grpc_testing_ScenarioResultSummary_descriptor =
+      getDescriptor().getMessageTypes().get(17);
+    internal_static_grpc_testing_ScenarioResultSummary_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ScenarioResultSummary_descriptor,
+        new java.lang.String[] { "Qps", "QpsPerServerCore", "ServerSystemTime", "ServerUserTime", "ClientSystemTime", "ClientUserTime", "Latency50", "Latency90", "Latency95", "Latency99", "Latency999", "ServerCpuUsage", "SuccessfulRequestsPerSecond", "FailedRequestsPerSecond", "ClientPollsPerRequest", "ServerPollsPerRequest", });
+    internal_static_grpc_testing_ScenarioResult_descriptor =
+      getDescriptor().getMessageTypes().get(18);
+    internal_static_grpc_testing_ScenarioResult_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ScenarioResult_descriptor,
+        new java.lang.String[] { "Scenario", "Latencies", "ClientStats", "ServerStats", "ServerCores", "Summary", "ClientSuccess", "ServerSuccess", "RequestResults", });
+    io.grpc.benchmarks.proto.Payloads.getDescriptor();
+    io.grpc.benchmarks.proto.Stats.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Messages.java b/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Messages.java
new file mode 100644
index 0000000..5dfcb91
--- /dev/null
+++ b/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Messages.java
@@ -0,0 +1,10469 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/testing/messages.proto
+
+package io.grpc.benchmarks.proto;
+
+public final class Messages {
+  private Messages() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  /**
+   * <pre>
+   * DEPRECATED, don't use. To be removed shortly.
+   * The type of payload that should be returned.
+   * </pre>
+   *
+   * Protobuf enum {@code grpc.testing.PayloadType}
+   */
+  public enum PayloadType
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <pre>
+     * Compressable text format.
+     * </pre>
+     *
+     * <code>COMPRESSABLE = 0;</code>
+     */
+    COMPRESSABLE(0),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <pre>
+     * Compressable text format.
+     * </pre>
+     *
+     * <code>COMPRESSABLE = 0;</code>
+     */
+    public static final int COMPRESSABLE_VALUE = 0;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static PayloadType valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static PayloadType forNumber(int value) {
+      switch (value) {
+        case 0: return COMPRESSABLE;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<PayloadType>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        PayloadType> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<PayloadType>() {
+            public PayloadType findValueByNumber(int number) {
+              return PayloadType.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Messages.getDescriptor().getEnumTypes().get(0);
+    }
+
+    private static final PayloadType[] VALUES = values();
+
+    public static PayloadType valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private PayloadType(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.testing.PayloadType)
+  }
+
+  public interface BoolValueOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.BoolValue)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * The bool value.
+     * </pre>
+     *
+     * <code>bool value = 1;</code>
+     */
+    boolean getValue();
+  }
+  /**
+   * <pre>
+   * TODO(dgq): Go back to using well-known types once
+   * https://github.com/grpc/grpc/issues/6980 has been fixed.
+   * import "google/protobuf/wrappers.proto";
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.BoolValue}
+   */
+  public  static final class BoolValue extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.BoolValue)
+      BoolValueOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use BoolValue.newBuilder() to construct.
+    private BoolValue(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private BoolValue() {
+      value_ = false;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private BoolValue(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              value_ = input.readBool();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_BoolValue_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_BoolValue_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Messages.BoolValue.class, io.grpc.benchmarks.proto.Messages.BoolValue.Builder.class);
+    }
+
+    public static final int VALUE_FIELD_NUMBER = 1;
+    private boolean value_;
+    /**
+     * <pre>
+     * The bool value.
+     * </pre>
+     *
+     * <code>bool value = 1;</code>
+     */
+    public boolean getValue() {
+      return value_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (value_ != false) {
+        output.writeBool(1, value_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (value_ != false) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBoolSize(1, value_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Messages.BoolValue)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Messages.BoolValue other = (io.grpc.benchmarks.proto.Messages.BoolValue) obj;
+
+      boolean result = true;
+      result = result && (getValue()
+          == other.getValue());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + VALUE_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+          getValue());
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.BoolValue parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.BoolValue parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.BoolValue parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.BoolValue parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.BoolValue parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.BoolValue parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.BoolValue parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.BoolValue parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.BoolValue parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.BoolValue parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.BoolValue parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.BoolValue parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Messages.BoolValue prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * TODO(dgq): Go back to using well-known types once
+     * https://github.com/grpc/grpc/issues/6980 has been fixed.
+     * import "google/protobuf/wrappers.proto";
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.BoolValue}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.BoolValue)
+        io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_BoolValue_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_BoolValue_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Messages.BoolValue.class, io.grpc.benchmarks.proto.Messages.BoolValue.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Messages.BoolValue.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        value_ = false;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_BoolValue_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.BoolValue getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Messages.BoolValue build() {
+        io.grpc.benchmarks.proto.Messages.BoolValue result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.BoolValue buildPartial() {
+        io.grpc.benchmarks.proto.Messages.BoolValue result = new io.grpc.benchmarks.proto.Messages.BoolValue(this);
+        result.value_ = value_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Messages.BoolValue) {
+          return mergeFrom((io.grpc.benchmarks.proto.Messages.BoolValue)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Messages.BoolValue other) {
+        if (other == io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance()) return this;
+        if (other.getValue() != false) {
+          setValue(other.getValue());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Messages.BoolValue parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Messages.BoolValue) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private boolean value_ ;
+      /**
+       * <pre>
+       * The bool value.
+       * </pre>
+       *
+       * <code>bool value = 1;</code>
+       */
+      public boolean getValue() {
+        return value_;
+      }
+      /**
+       * <pre>
+       * The bool value.
+       * </pre>
+       *
+       * <code>bool value = 1;</code>
+       */
+      public Builder setValue(boolean value) {
+        
+        value_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The bool value.
+       * </pre>
+       *
+       * <code>bool value = 1;</code>
+       */
+      public Builder clearValue() {
+        
+        value_ = false;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.BoolValue)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.BoolValue)
+    private static final io.grpc.benchmarks.proto.Messages.BoolValue DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Messages.BoolValue();
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.BoolValue getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<BoolValue>
+        PARSER = new com.google.protobuf.AbstractParser<BoolValue>() {
+      public BoolValue parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new BoolValue(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<BoolValue> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<BoolValue> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Messages.BoolValue getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface PayloadOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.Payload)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * The type of data in body.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType type = 1;</code>
+     */
+    int getTypeValue();
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * The type of data in body.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType type = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.PayloadType getType();
+
+    /**
+     * <pre>
+     * Primary contents of payload.
+     * </pre>
+     *
+     * <code>bytes body = 2;</code>
+     */
+    com.google.protobuf.ByteString getBody();
+  }
+  /**
+   * <pre>
+   * A block of data, to simply increase gRPC message size.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.Payload}
+   */
+  public  static final class Payload extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.Payload)
+      PayloadOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use Payload.newBuilder() to construct.
+    private Payload(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private Payload() {
+      type_ = 0;
+      body_ = com.google.protobuf.ByteString.EMPTY;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private Payload(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+              int rawValue = input.readEnum();
+
+              type_ = rawValue;
+              break;
+            }
+            case 18: {
+
+              body_ = input.readBytes();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_Payload_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_Payload_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Messages.Payload.class, io.grpc.benchmarks.proto.Messages.Payload.Builder.class);
+    }
+
+    public static final int TYPE_FIELD_NUMBER = 1;
+    private int type_;
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * The type of data in body.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType type = 1;</code>
+     */
+    public int getTypeValue() {
+      return type_;
+    }
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * The type of data in body.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType type = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.PayloadType getType() {
+      io.grpc.benchmarks.proto.Messages.PayloadType result = io.grpc.benchmarks.proto.Messages.PayloadType.valueOf(type_);
+      return result == null ? io.grpc.benchmarks.proto.Messages.PayloadType.UNRECOGNIZED : result;
+    }
+
+    public static final int BODY_FIELD_NUMBER = 2;
+    private com.google.protobuf.ByteString body_;
+    /**
+     * <pre>
+     * Primary contents of payload.
+     * </pre>
+     *
+     * <code>bytes body = 2;</code>
+     */
+    public com.google.protobuf.ByteString getBody() {
+      return body_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (type_ != io.grpc.benchmarks.proto.Messages.PayloadType.COMPRESSABLE.getNumber()) {
+        output.writeEnum(1, type_);
+      }
+      if (!body_.isEmpty()) {
+        output.writeBytes(2, body_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (type_ != io.grpc.benchmarks.proto.Messages.PayloadType.COMPRESSABLE.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(1, type_);
+      }
+      if (!body_.isEmpty()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBytesSize(2, body_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Messages.Payload)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Messages.Payload other = (io.grpc.benchmarks.proto.Messages.Payload) obj;
+
+      boolean result = true;
+      result = result && type_ == other.type_;
+      result = result && getBody()
+          .equals(other.getBody());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + TYPE_FIELD_NUMBER;
+      hash = (53 * hash) + type_;
+      hash = (37 * hash) + BODY_FIELD_NUMBER;
+      hash = (53 * hash) + getBody().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.Payload parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.Payload parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.Payload parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.Payload parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.Payload parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.Payload parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.Payload parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.Payload parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.Payload parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.Payload parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.Payload parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.Payload parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Messages.Payload prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * A block of data, to simply increase gRPC message size.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.Payload}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.Payload)
+        io.grpc.benchmarks.proto.Messages.PayloadOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_Payload_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_Payload_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Messages.Payload.class, io.grpc.benchmarks.proto.Messages.Payload.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Messages.Payload.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        type_ = 0;
+
+        body_ = com.google.protobuf.ByteString.EMPTY;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_Payload_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.Payload getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Messages.Payload build() {
+        io.grpc.benchmarks.proto.Messages.Payload result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.Payload buildPartial() {
+        io.grpc.benchmarks.proto.Messages.Payload result = new io.grpc.benchmarks.proto.Messages.Payload(this);
+        result.type_ = type_;
+        result.body_ = body_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Messages.Payload) {
+          return mergeFrom((io.grpc.benchmarks.proto.Messages.Payload)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Messages.Payload other) {
+        if (other == io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance()) return this;
+        if (other.type_ != 0) {
+          setTypeValue(other.getTypeValue());
+        }
+        if (other.getBody() != com.google.protobuf.ByteString.EMPTY) {
+          setBody(other.getBody());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Messages.Payload parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Messages.Payload) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int type_ = 0;
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * The type of data in body.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType type = 1;</code>
+       */
+      public int getTypeValue() {
+        return type_;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * The type of data in body.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType type = 1;</code>
+       */
+      public Builder setTypeValue(int value) {
+        type_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * The type of data in body.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType type = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.PayloadType getType() {
+        io.grpc.benchmarks.proto.Messages.PayloadType result = io.grpc.benchmarks.proto.Messages.PayloadType.valueOf(type_);
+        return result == null ? io.grpc.benchmarks.proto.Messages.PayloadType.UNRECOGNIZED : result;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * The type of data in body.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType type = 1;</code>
+       */
+      public Builder setType(io.grpc.benchmarks.proto.Messages.PayloadType value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        type_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * The type of data in body.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType type = 1;</code>
+       */
+      public Builder clearType() {
+        
+        type_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private com.google.protobuf.ByteString body_ = com.google.protobuf.ByteString.EMPTY;
+      /**
+       * <pre>
+       * Primary contents of payload.
+       * </pre>
+       *
+       * <code>bytes body = 2;</code>
+       */
+      public com.google.protobuf.ByteString getBody() {
+        return body_;
+      }
+      /**
+       * <pre>
+       * Primary contents of payload.
+       * </pre>
+       *
+       * <code>bytes body = 2;</code>
+       */
+      public Builder setBody(com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        body_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Primary contents of payload.
+       * </pre>
+       *
+       * <code>bytes body = 2;</code>
+       */
+      public Builder clearBody() {
+        
+        body_ = getDefaultInstance().getBody();
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.Payload)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.Payload)
+    private static final io.grpc.benchmarks.proto.Messages.Payload DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Messages.Payload();
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.Payload getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<Payload>
+        PARSER = new com.google.protobuf.AbstractParser<Payload>() {
+      public Payload parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new Payload(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<Payload> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<Payload> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Messages.Payload getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface EchoStatusOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.EchoStatus)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>int32 code = 1;</code>
+     */
+    int getCode();
+
+    /**
+     * <code>string message = 2;</code>
+     */
+    java.lang.String getMessage();
+    /**
+     * <code>string message = 2;</code>
+     */
+    com.google.protobuf.ByteString
+        getMessageBytes();
+  }
+  /**
+   * <pre>
+   * A protobuf representation for grpc status. This is used by test
+   * clients to specify a status that the server should attempt to return.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.EchoStatus}
+   */
+  public  static final class EchoStatus extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.EchoStatus)
+      EchoStatusOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use EchoStatus.newBuilder() to construct.
+    private EchoStatus(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private EchoStatus() {
+      code_ = 0;
+      message_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private EchoStatus(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              code_ = input.readInt32();
+              break;
+            }
+            case 18: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              message_ = s;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_EchoStatus_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_EchoStatus_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Messages.EchoStatus.class, io.grpc.benchmarks.proto.Messages.EchoStatus.Builder.class);
+    }
+
+    public static final int CODE_FIELD_NUMBER = 1;
+    private int code_;
+    /**
+     * <code>int32 code = 1;</code>
+     */
+    public int getCode() {
+      return code_;
+    }
+
+    public static final int MESSAGE_FIELD_NUMBER = 2;
+    private volatile java.lang.Object message_;
+    /**
+     * <code>string message = 2;</code>
+     */
+    public java.lang.String getMessage() {
+      java.lang.Object ref = message_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        message_ = s;
+        return s;
+      }
+    }
+    /**
+     * <code>string message = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getMessageBytes() {
+      java.lang.Object ref = message_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        message_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (code_ != 0) {
+        output.writeInt32(1, code_);
+      }
+      if (!getMessageBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, message_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (code_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1, code_);
+      }
+      if (!getMessageBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, message_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Messages.EchoStatus)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Messages.EchoStatus other = (io.grpc.benchmarks.proto.Messages.EchoStatus) obj;
+
+      boolean result = true;
+      result = result && (getCode()
+          == other.getCode());
+      result = result && getMessage()
+          .equals(other.getMessage());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + CODE_FIELD_NUMBER;
+      hash = (53 * hash) + getCode();
+      hash = (37 * hash) + MESSAGE_FIELD_NUMBER;
+      hash = (53 * hash) + getMessage().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.EchoStatus parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.EchoStatus parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.EchoStatus parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.EchoStatus parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.EchoStatus parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.EchoStatus parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.EchoStatus parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.EchoStatus parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.EchoStatus parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.EchoStatus parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.EchoStatus parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.EchoStatus parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Messages.EchoStatus prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * A protobuf representation for grpc status. This is used by test
+     * clients to specify a status that the server should attempt to return.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.EchoStatus}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.EchoStatus)
+        io.grpc.benchmarks.proto.Messages.EchoStatusOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_EchoStatus_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_EchoStatus_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Messages.EchoStatus.class, io.grpc.benchmarks.proto.Messages.EchoStatus.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Messages.EchoStatus.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        code_ = 0;
+
+        message_ = "";
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_EchoStatus_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.EchoStatus getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Messages.EchoStatus.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Messages.EchoStatus build() {
+        io.grpc.benchmarks.proto.Messages.EchoStatus result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.EchoStatus buildPartial() {
+        io.grpc.benchmarks.proto.Messages.EchoStatus result = new io.grpc.benchmarks.proto.Messages.EchoStatus(this);
+        result.code_ = code_;
+        result.message_ = message_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Messages.EchoStatus) {
+          return mergeFrom((io.grpc.benchmarks.proto.Messages.EchoStatus)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Messages.EchoStatus other) {
+        if (other == io.grpc.benchmarks.proto.Messages.EchoStatus.getDefaultInstance()) return this;
+        if (other.getCode() != 0) {
+          setCode(other.getCode());
+        }
+        if (!other.getMessage().isEmpty()) {
+          message_ = other.message_;
+          onChanged();
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Messages.EchoStatus parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Messages.EchoStatus) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int code_ ;
+      /**
+       * <code>int32 code = 1;</code>
+       */
+      public int getCode() {
+        return code_;
+      }
+      /**
+       * <code>int32 code = 1;</code>
+       */
+      public Builder setCode(int value) {
+        
+        code_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>int32 code = 1;</code>
+       */
+      public Builder clearCode() {
+        
+        code_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object message_ = "";
+      /**
+       * <code>string message = 2;</code>
+       */
+      public java.lang.String getMessage() {
+        java.lang.Object ref = message_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          message_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <code>string message = 2;</code>
+       */
+      public com.google.protobuf.ByteString
+          getMessageBytes() {
+        java.lang.Object ref = message_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          message_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <code>string message = 2;</code>
+       */
+      public Builder setMessage(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        message_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string message = 2;</code>
+       */
+      public Builder clearMessage() {
+        
+        message_ = getDefaultInstance().getMessage();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string message = 2;</code>
+       */
+      public Builder setMessageBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        message_ = value;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.EchoStatus)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.EchoStatus)
+    private static final io.grpc.benchmarks.proto.Messages.EchoStatus DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Messages.EchoStatus();
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.EchoStatus getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<EchoStatus>
+        PARSER = new com.google.protobuf.AbstractParser<EchoStatus>() {
+      public EchoStatus parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new EchoStatus(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<EchoStatus> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<EchoStatus> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Messages.EchoStatus getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface SimpleRequestOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.SimpleRequest)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, server randomly chooses one from other formats.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    int getResponseTypeValue();
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, server randomly chooses one from other formats.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.PayloadType getResponseType();
+
+    /**
+     * <pre>
+     * Desired payload size in the response from the server.
+     * </pre>
+     *
+     * <code>int32 response_size = 2;</code>
+     */
+    int getResponseSize();
+
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    boolean hasPayload();
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.Payload getPayload();
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder();
+
+    /**
+     * <pre>
+     * Whether SimpleResponse should include username.
+     * </pre>
+     *
+     * <code>bool fill_username = 4;</code>
+     */
+    boolean getFillUsername();
+
+    /**
+     * <pre>
+     * Whether SimpleResponse should include OAuth scope.
+     * </pre>
+     *
+     * <code>bool fill_oauth_scope = 5;</code>
+     */
+    boolean getFillOauthScope();
+
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+     */
+    boolean hasResponseCompressed();
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.BoolValue getResponseCompressed();
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder getResponseCompressedOrBuilder();
+
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    boolean hasResponseStatus();
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.EchoStatus getResponseStatus();
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.EchoStatusOrBuilder getResponseStatusOrBuilder();
+
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+     */
+    boolean hasExpectCompressed();
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.BoolValue getExpectCompressed();
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder getExpectCompressedOrBuilder();
+  }
+  /**
+   * <pre>
+   * Unary request.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.SimpleRequest}
+   */
+  public  static final class SimpleRequest extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.SimpleRequest)
+      SimpleRequestOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use SimpleRequest.newBuilder() to construct.
+    private SimpleRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private SimpleRequest() {
+      responseType_ = 0;
+      responseSize_ = 0;
+      fillUsername_ = false;
+      fillOauthScope_ = false;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private SimpleRequest(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+              int rawValue = input.readEnum();
+
+              responseType_ = rawValue;
+              break;
+            }
+            case 16: {
+
+              responseSize_ = input.readInt32();
+              break;
+            }
+            case 26: {
+              io.grpc.benchmarks.proto.Messages.Payload.Builder subBuilder = null;
+              if (payload_ != null) {
+                subBuilder = payload_.toBuilder();
+              }
+              payload_ = input.readMessage(io.grpc.benchmarks.proto.Messages.Payload.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(payload_);
+                payload_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 32: {
+
+              fillUsername_ = input.readBool();
+              break;
+            }
+            case 40: {
+
+              fillOauthScope_ = input.readBool();
+              break;
+            }
+            case 50: {
+              io.grpc.benchmarks.proto.Messages.BoolValue.Builder subBuilder = null;
+              if (responseCompressed_ != null) {
+                subBuilder = responseCompressed_.toBuilder();
+              }
+              responseCompressed_ = input.readMessage(io.grpc.benchmarks.proto.Messages.BoolValue.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(responseCompressed_);
+                responseCompressed_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 58: {
+              io.grpc.benchmarks.proto.Messages.EchoStatus.Builder subBuilder = null;
+              if (responseStatus_ != null) {
+                subBuilder = responseStatus_.toBuilder();
+              }
+              responseStatus_ = input.readMessage(io.grpc.benchmarks.proto.Messages.EchoStatus.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(responseStatus_);
+                responseStatus_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 66: {
+              io.grpc.benchmarks.proto.Messages.BoolValue.Builder subBuilder = null;
+              if (expectCompressed_ != null) {
+                subBuilder = expectCompressed_.toBuilder();
+              }
+              expectCompressed_ = input.readMessage(io.grpc.benchmarks.proto.Messages.BoolValue.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(expectCompressed_);
+                expectCompressed_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_SimpleRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_SimpleRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Messages.SimpleRequest.class, io.grpc.benchmarks.proto.Messages.SimpleRequest.Builder.class);
+    }
+
+    public static final int RESPONSE_TYPE_FIELD_NUMBER = 1;
+    private int responseType_;
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, server randomly chooses one from other formats.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    public int getResponseTypeValue() {
+      return responseType_;
+    }
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, server randomly chooses one from other formats.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.PayloadType getResponseType() {
+      io.grpc.benchmarks.proto.Messages.PayloadType result = io.grpc.benchmarks.proto.Messages.PayloadType.valueOf(responseType_);
+      return result == null ? io.grpc.benchmarks.proto.Messages.PayloadType.UNRECOGNIZED : result;
+    }
+
+    public static final int RESPONSE_SIZE_FIELD_NUMBER = 2;
+    private int responseSize_;
+    /**
+     * <pre>
+     * Desired payload size in the response from the server.
+     * </pre>
+     *
+     * <code>int32 response_size = 2;</code>
+     */
+    public int getResponseSize() {
+      return responseSize_;
+    }
+
+    public static final int PAYLOAD_FIELD_NUMBER = 3;
+    private io.grpc.benchmarks.proto.Messages.Payload payload_;
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    public boolean hasPayload() {
+      return payload_ != null;
+    }
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.Payload getPayload() {
+      return payload_ == null ? io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+    }
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+      return getPayload();
+    }
+
+    public static final int FILL_USERNAME_FIELD_NUMBER = 4;
+    private boolean fillUsername_;
+    /**
+     * <pre>
+     * Whether SimpleResponse should include username.
+     * </pre>
+     *
+     * <code>bool fill_username = 4;</code>
+     */
+    public boolean getFillUsername() {
+      return fillUsername_;
+    }
+
+    public static final int FILL_OAUTH_SCOPE_FIELD_NUMBER = 5;
+    private boolean fillOauthScope_;
+    /**
+     * <pre>
+     * Whether SimpleResponse should include OAuth scope.
+     * </pre>
+     *
+     * <code>bool fill_oauth_scope = 5;</code>
+     */
+    public boolean getFillOauthScope() {
+      return fillOauthScope_;
+    }
+
+    public static final int RESPONSE_COMPRESSED_FIELD_NUMBER = 6;
+    private io.grpc.benchmarks.proto.Messages.BoolValue responseCompressed_;
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+     */
+    public boolean hasResponseCompressed() {
+      return responseCompressed_ != null;
+    }
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.BoolValue getResponseCompressed() {
+      return responseCompressed_ == null ? io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance() : responseCompressed_;
+    }
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder getResponseCompressedOrBuilder() {
+      return getResponseCompressed();
+    }
+
+    public static final int RESPONSE_STATUS_FIELD_NUMBER = 7;
+    private io.grpc.benchmarks.proto.Messages.EchoStatus responseStatus_;
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    public boolean hasResponseStatus() {
+      return responseStatus_ != null;
+    }
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.EchoStatus getResponseStatus() {
+      return responseStatus_ == null ? io.grpc.benchmarks.proto.Messages.EchoStatus.getDefaultInstance() : responseStatus_;
+    }
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.EchoStatusOrBuilder getResponseStatusOrBuilder() {
+      return getResponseStatus();
+    }
+
+    public static final int EXPECT_COMPRESSED_FIELD_NUMBER = 8;
+    private io.grpc.benchmarks.proto.Messages.BoolValue expectCompressed_;
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+     */
+    public boolean hasExpectCompressed() {
+      return expectCompressed_ != null;
+    }
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.BoolValue getExpectCompressed() {
+      return expectCompressed_ == null ? io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance() : expectCompressed_;
+    }
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder getExpectCompressedOrBuilder() {
+      return getExpectCompressed();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (responseType_ != io.grpc.benchmarks.proto.Messages.PayloadType.COMPRESSABLE.getNumber()) {
+        output.writeEnum(1, responseType_);
+      }
+      if (responseSize_ != 0) {
+        output.writeInt32(2, responseSize_);
+      }
+      if (payload_ != null) {
+        output.writeMessage(3, getPayload());
+      }
+      if (fillUsername_ != false) {
+        output.writeBool(4, fillUsername_);
+      }
+      if (fillOauthScope_ != false) {
+        output.writeBool(5, fillOauthScope_);
+      }
+      if (responseCompressed_ != null) {
+        output.writeMessage(6, getResponseCompressed());
+      }
+      if (responseStatus_ != null) {
+        output.writeMessage(7, getResponseStatus());
+      }
+      if (expectCompressed_ != null) {
+        output.writeMessage(8, getExpectCompressed());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (responseType_ != io.grpc.benchmarks.proto.Messages.PayloadType.COMPRESSABLE.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(1, responseType_);
+      }
+      if (responseSize_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(2, responseSize_);
+      }
+      if (payload_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(3, getPayload());
+      }
+      if (fillUsername_ != false) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBoolSize(4, fillUsername_);
+      }
+      if (fillOauthScope_ != false) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBoolSize(5, fillOauthScope_);
+      }
+      if (responseCompressed_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(6, getResponseCompressed());
+      }
+      if (responseStatus_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(7, getResponseStatus());
+      }
+      if (expectCompressed_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(8, getExpectCompressed());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Messages.SimpleRequest)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Messages.SimpleRequest other = (io.grpc.benchmarks.proto.Messages.SimpleRequest) obj;
+
+      boolean result = true;
+      result = result && responseType_ == other.responseType_;
+      result = result && (getResponseSize()
+          == other.getResponseSize());
+      result = result && (hasPayload() == other.hasPayload());
+      if (hasPayload()) {
+        result = result && getPayload()
+            .equals(other.getPayload());
+      }
+      result = result && (getFillUsername()
+          == other.getFillUsername());
+      result = result && (getFillOauthScope()
+          == other.getFillOauthScope());
+      result = result && (hasResponseCompressed() == other.hasResponseCompressed());
+      if (hasResponseCompressed()) {
+        result = result && getResponseCompressed()
+            .equals(other.getResponseCompressed());
+      }
+      result = result && (hasResponseStatus() == other.hasResponseStatus());
+      if (hasResponseStatus()) {
+        result = result && getResponseStatus()
+            .equals(other.getResponseStatus());
+      }
+      result = result && (hasExpectCompressed() == other.hasExpectCompressed());
+      if (hasExpectCompressed()) {
+        result = result && getExpectCompressed()
+            .equals(other.getExpectCompressed());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + RESPONSE_TYPE_FIELD_NUMBER;
+      hash = (53 * hash) + responseType_;
+      hash = (37 * hash) + RESPONSE_SIZE_FIELD_NUMBER;
+      hash = (53 * hash) + getResponseSize();
+      if (hasPayload()) {
+        hash = (37 * hash) + PAYLOAD_FIELD_NUMBER;
+        hash = (53 * hash) + getPayload().hashCode();
+      }
+      hash = (37 * hash) + FILL_USERNAME_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+          getFillUsername());
+      hash = (37 * hash) + FILL_OAUTH_SCOPE_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+          getFillOauthScope());
+      if (hasResponseCompressed()) {
+        hash = (37 * hash) + RESPONSE_COMPRESSED_FIELD_NUMBER;
+        hash = (53 * hash) + getResponseCompressed().hashCode();
+      }
+      if (hasResponseStatus()) {
+        hash = (37 * hash) + RESPONSE_STATUS_FIELD_NUMBER;
+        hash = (53 * hash) + getResponseStatus().hashCode();
+      }
+      if (hasExpectCompressed()) {
+        hash = (37 * hash) + EXPECT_COMPRESSED_FIELD_NUMBER;
+        hash = (53 * hash) + getExpectCompressed().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.SimpleRequest parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleRequest parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleRequest parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleRequest parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleRequest parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleRequest parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleRequest parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleRequest parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleRequest parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleRequest parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleRequest parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleRequest parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Messages.SimpleRequest prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Unary request.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.SimpleRequest}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.SimpleRequest)
+        io.grpc.benchmarks.proto.Messages.SimpleRequestOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_SimpleRequest_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_SimpleRequest_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Messages.SimpleRequest.class, io.grpc.benchmarks.proto.Messages.SimpleRequest.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Messages.SimpleRequest.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        responseType_ = 0;
+
+        responseSize_ = 0;
+
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+        fillUsername_ = false;
+
+        fillOauthScope_ = false;
+
+        if (responseCompressedBuilder_ == null) {
+          responseCompressed_ = null;
+        } else {
+          responseCompressed_ = null;
+          responseCompressedBuilder_ = null;
+        }
+        if (responseStatusBuilder_ == null) {
+          responseStatus_ = null;
+        } else {
+          responseStatus_ = null;
+          responseStatusBuilder_ = null;
+        }
+        if (expectCompressedBuilder_ == null) {
+          expectCompressed_ = null;
+        } else {
+          expectCompressed_ = null;
+          expectCompressedBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_SimpleRequest_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.SimpleRequest getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Messages.SimpleRequest.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Messages.SimpleRequest build() {
+        io.grpc.benchmarks.proto.Messages.SimpleRequest result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.SimpleRequest buildPartial() {
+        io.grpc.benchmarks.proto.Messages.SimpleRequest result = new io.grpc.benchmarks.proto.Messages.SimpleRequest(this);
+        result.responseType_ = responseType_;
+        result.responseSize_ = responseSize_;
+        if (payloadBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = payloadBuilder_.build();
+        }
+        result.fillUsername_ = fillUsername_;
+        result.fillOauthScope_ = fillOauthScope_;
+        if (responseCompressedBuilder_ == null) {
+          result.responseCompressed_ = responseCompressed_;
+        } else {
+          result.responseCompressed_ = responseCompressedBuilder_.build();
+        }
+        if (responseStatusBuilder_ == null) {
+          result.responseStatus_ = responseStatus_;
+        } else {
+          result.responseStatus_ = responseStatusBuilder_.build();
+        }
+        if (expectCompressedBuilder_ == null) {
+          result.expectCompressed_ = expectCompressed_;
+        } else {
+          result.expectCompressed_ = expectCompressedBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Messages.SimpleRequest) {
+          return mergeFrom((io.grpc.benchmarks.proto.Messages.SimpleRequest)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Messages.SimpleRequest other) {
+        if (other == io.grpc.benchmarks.proto.Messages.SimpleRequest.getDefaultInstance()) return this;
+        if (other.responseType_ != 0) {
+          setResponseTypeValue(other.getResponseTypeValue());
+        }
+        if (other.getResponseSize() != 0) {
+          setResponseSize(other.getResponseSize());
+        }
+        if (other.hasPayload()) {
+          mergePayload(other.getPayload());
+        }
+        if (other.getFillUsername() != false) {
+          setFillUsername(other.getFillUsername());
+        }
+        if (other.getFillOauthScope() != false) {
+          setFillOauthScope(other.getFillOauthScope());
+        }
+        if (other.hasResponseCompressed()) {
+          mergeResponseCompressed(other.getResponseCompressed());
+        }
+        if (other.hasResponseStatus()) {
+          mergeResponseStatus(other.getResponseStatus());
+        }
+        if (other.hasExpectCompressed()) {
+          mergeExpectCompressed(other.getExpectCompressed());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Messages.SimpleRequest parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Messages.SimpleRequest) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int responseType_ = 0;
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, server randomly chooses one from other formats.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public int getResponseTypeValue() {
+        return responseType_;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, server randomly chooses one from other formats.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public Builder setResponseTypeValue(int value) {
+        responseType_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, server randomly chooses one from other formats.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.PayloadType getResponseType() {
+        io.grpc.benchmarks.proto.Messages.PayloadType result = io.grpc.benchmarks.proto.Messages.PayloadType.valueOf(responseType_);
+        return result == null ? io.grpc.benchmarks.proto.Messages.PayloadType.UNRECOGNIZED : result;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, server randomly chooses one from other formats.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public Builder setResponseType(io.grpc.benchmarks.proto.Messages.PayloadType value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        responseType_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, server randomly chooses one from other formats.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public Builder clearResponseType() {
+        
+        responseType_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int responseSize_ ;
+      /**
+       * <pre>
+       * Desired payload size in the response from the server.
+       * </pre>
+       *
+       * <code>int32 response_size = 2;</code>
+       */
+      public int getResponseSize() {
+        return responseSize_;
+      }
+      /**
+       * <pre>
+       * Desired payload size in the response from the server.
+       * </pre>
+       *
+       * <code>int32 response_size = 2;</code>
+       */
+      public Builder setResponseSize(int value) {
+        
+        responseSize_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Desired payload size in the response from the server.
+       * </pre>
+       *
+       * <code>int32 response_size = 2;</code>
+       */
+      public Builder clearResponseSize() {
+        
+        responseSize_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Messages.Payload payload_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder> payloadBuilder_;
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public boolean hasPayload() {
+        return payloadBuilder_ != null || payload_ != null;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.Payload getPayload() {
+        if (payloadBuilder_ == null) {
+          return payload_ == null ? io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+        } else {
+          return payloadBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder setPayload(io.grpc.benchmarks.proto.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payload_ = value;
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder setPayload(
+          io.grpc.benchmarks.proto.Messages.Payload.Builder builderForValue) {
+        if (payloadBuilder_ == null) {
+          payload_ = builderForValue.build();
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder mergePayload(io.grpc.benchmarks.proto.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (payload_ != null) {
+            payload_ =
+              io.grpc.benchmarks.proto.Messages.Payload.newBuilder(payload_).mergeFrom(value).buildPartial();
+          } else {
+            payload_ = value;
+          }
+          onChanged();
+        } else {
+          payloadBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder clearPayload() {
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+          onChanged();
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.Payload.Builder getPayloadBuilder() {
+        
+        onChanged();
+        return getPayloadFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+        if (payloadBuilder_ != null) {
+          return payloadBuilder_.getMessageOrBuilder();
+        } else {
+          return payload_ == null ?
+              io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+        }
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder> 
+          getPayloadFieldBuilder() {
+        if (payloadBuilder_ == null) {
+          payloadBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder>(
+                  getPayload(),
+                  getParentForChildren(),
+                  isClean());
+          payload_ = null;
+        }
+        return payloadBuilder_;
+      }
+
+      private boolean fillUsername_ ;
+      /**
+       * <pre>
+       * Whether SimpleResponse should include username.
+       * </pre>
+       *
+       * <code>bool fill_username = 4;</code>
+       */
+      public boolean getFillUsername() {
+        return fillUsername_;
+      }
+      /**
+       * <pre>
+       * Whether SimpleResponse should include username.
+       * </pre>
+       *
+       * <code>bool fill_username = 4;</code>
+       */
+      public Builder setFillUsername(boolean value) {
+        
+        fillUsername_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether SimpleResponse should include username.
+       * </pre>
+       *
+       * <code>bool fill_username = 4;</code>
+       */
+      public Builder clearFillUsername() {
+        
+        fillUsername_ = false;
+        onChanged();
+        return this;
+      }
+
+      private boolean fillOauthScope_ ;
+      /**
+       * <pre>
+       * Whether SimpleResponse should include OAuth scope.
+       * </pre>
+       *
+       * <code>bool fill_oauth_scope = 5;</code>
+       */
+      public boolean getFillOauthScope() {
+        return fillOauthScope_;
+      }
+      /**
+       * <pre>
+       * Whether SimpleResponse should include OAuth scope.
+       * </pre>
+       *
+       * <code>bool fill_oauth_scope = 5;</code>
+       */
+      public Builder setFillOauthScope(boolean value) {
+        
+        fillOauthScope_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether SimpleResponse should include OAuth scope.
+       * </pre>
+       *
+       * <code>bool fill_oauth_scope = 5;</code>
+       */
+      public Builder clearFillOauthScope() {
+        
+        fillOauthScope_ = false;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Messages.BoolValue responseCompressed_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.BoolValue, io.grpc.benchmarks.proto.Messages.BoolValue.Builder, io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder> responseCompressedBuilder_;
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+       */
+      public boolean hasResponseCompressed() {
+        return responseCompressedBuilder_ != null || responseCompressed_ != null;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.BoolValue getResponseCompressed() {
+        if (responseCompressedBuilder_ == null) {
+          return responseCompressed_ == null ? io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance() : responseCompressed_;
+        } else {
+          return responseCompressedBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+       */
+      public Builder setResponseCompressed(io.grpc.benchmarks.proto.Messages.BoolValue value) {
+        if (responseCompressedBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          responseCompressed_ = value;
+          onChanged();
+        } else {
+          responseCompressedBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+       */
+      public Builder setResponseCompressed(
+          io.grpc.benchmarks.proto.Messages.BoolValue.Builder builderForValue) {
+        if (responseCompressedBuilder_ == null) {
+          responseCompressed_ = builderForValue.build();
+          onChanged();
+        } else {
+          responseCompressedBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+       */
+      public Builder mergeResponseCompressed(io.grpc.benchmarks.proto.Messages.BoolValue value) {
+        if (responseCompressedBuilder_ == null) {
+          if (responseCompressed_ != null) {
+            responseCompressed_ =
+              io.grpc.benchmarks.proto.Messages.BoolValue.newBuilder(responseCompressed_).mergeFrom(value).buildPartial();
+          } else {
+            responseCompressed_ = value;
+          }
+          onChanged();
+        } else {
+          responseCompressedBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+       */
+      public Builder clearResponseCompressed() {
+        if (responseCompressedBuilder_ == null) {
+          responseCompressed_ = null;
+          onChanged();
+        } else {
+          responseCompressed_ = null;
+          responseCompressedBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.BoolValue.Builder getResponseCompressedBuilder() {
+        
+        onChanged();
+        return getResponseCompressedFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder getResponseCompressedOrBuilder() {
+        if (responseCompressedBuilder_ != null) {
+          return responseCompressedBuilder_.getMessageOrBuilder();
+        } else {
+          return responseCompressed_ == null ?
+              io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance() : responseCompressed_;
+        }
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue response_compressed = 6;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.BoolValue, io.grpc.benchmarks.proto.Messages.BoolValue.Builder, io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder> 
+          getResponseCompressedFieldBuilder() {
+        if (responseCompressedBuilder_ == null) {
+          responseCompressedBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Messages.BoolValue, io.grpc.benchmarks.proto.Messages.BoolValue.Builder, io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder>(
+                  getResponseCompressed(),
+                  getParentForChildren(),
+                  isClean());
+          responseCompressed_ = null;
+        }
+        return responseCompressedBuilder_;
+      }
+
+      private io.grpc.benchmarks.proto.Messages.EchoStatus responseStatus_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.EchoStatus, io.grpc.benchmarks.proto.Messages.EchoStatus.Builder, io.grpc.benchmarks.proto.Messages.EchoStatusOrBuilder> responseStatusBuilder_;
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public boolean hasResponseStatus() {
+        return responseStatusBuilder_ != null || responseStatus_ != null;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.EchoStatus getResponseStatus() {
+        if (responseStatusBuilder_ == null) {
+          return responseStatus_ == null ? io.grpc.benchmarks.proto.Messages.EchoStatus.getDefaultInstance() : responseStatus_;
+        } else {
+          return responseStatusBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder setResponseStatus(io.grpc.benchmarks.proto.Messages.EchoStatus value) {
+        if (responseStatusBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          responseStatus_ = value;
+          onChanged();
+        } else {
+          responseStatusBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder setResponseStatus(
+          io.grpc.benchmarks.proto.Messages.EchoStatus.Builder builderForValue) {
+        if (responseStatusBuilder_ == null) {
+          responseStatus_ = builderForValue.build();
+          onChanged();
+        } else {
+          responseStatusBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder mergeResponseStatus(io.grpc.benchmarks.proto.Messages.EchoStatus value) {
+        if (responseStatusBuilder_ == null) {
+          if (responseStatus_ != null) {
+            responseStatus_ =
+              io.grpc.benchmarks.proto.Messages.EchoStatus.newBuilder(responseStatus_).mergeFrom(value).buildPartial();
+          } else {
+            responseStatus_ = value;
+          }
+          onChanged();
+        } else {
+          responseStatusBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder clearResponseStatus() {
+        if (responseStatusBuilder_ == null) {
+          responseStatus_ = null;
+          onChanged();
+        } else {
+          responseStatus_ = null;
+          responseStatusBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.EchoStatus.Builder getResponseStatusBuilder() {
+        
+        onChanged();
+        return getResponseStatusFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.EchoStatusOrBuilder getResponseStatusOrBuilder() {
+        if (responseStatusBuilder_ != null) {
+          return responseStatusBuilder_.getMessageOrBuilder();
+        } else {
+          return responseStatus_ == null ?
+              io.grpc.benchmarks.proto.Messages.EchoStatus.getDefaultInstance() : responseStatus_;
+        }
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.EchoStatus, io.grpc.benchmarks.proto.Messages.EchoStatus.Builder, io.grpc.benchmarks.proto.Messages.EchoStatusOrBuilder> 
+          getResponseStatusFieldBuilder() {
+        if (responseStatusBuilder_ == null) {
+          responseStatusBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Messages.EchoStatus, io.grpc.benchmarks.proto.Messages.EchoStatus.Builder, io.grpc.benchmarks.proto.Messages.EchoStatusOrBuilder>(
+                  getResponseStatus(),
+                  getParentForChildren(),
+                  isClean());
+          responseStatus_ = null;
+        }
+        return responseStatusBuilder_;
+      }
+
+      private io.grpc.benchmarks.proto.Messages.BoolValue expectCompressed_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.BoolValue, io.grpc.benchmarks.proto.Messages.BoolValue.Builder, io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder> expectCompressedBuilder_;
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+       */
+      public boolean hasExpectCompressed() {
+        return expectCompressedBuilder_ != null || expectCompressed_ != null;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.BoolValue getExpectCompressed() {
+        if (expectCompressedBuilder_ == null) {
+          return expectCompressed_ == null ? io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance() : expectCompressed_;
+        } else {
+          return expectCompressedBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+       */
+      public Builder setExpectCompressed(io.grpc.benchmarks.proto.Messages.BoolValue value) {
+        if (expectCompressedBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          expectCompressed_ = value;
+          onChanged();
+        } else {
+          expectCompressedBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+       */
+      public Builder setExpectCompressed(
+          io.grpc.benchmarks.proto.Messages.BoolValue.Builder builderForValue) {
+        if (expectCompressedBuilder_ == null) {
+          expectCompressed_ = builderForValue.build();
+          onChanged();
+        } else {
+          expectCompressedBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+       */
+      public Builder mergeExpectCompressed(io.grpc.benchmarks.proto.Messages.BoolValue value) {
+        if (expectCompressedBuilder_ == null) {
+          if (expectCompressed_ != null) {
+            expectCompressed_ =
+              io.grpc.benchmarks.proto.Messages.BoolValue.newBuilder(expectCompressed_).mergeFrom(value).buildPartial();
+          } else {
+            expectCompressed_ = value;
+          }
+          onChanged();
+        } else {
+          expectCompressedBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+       */
+      public Builder clearExpectCompressed() {
+        if (expectCompressedBuilder_ == null) {
+          expectCompressed_ = null;
+          onChanged();
+        } else {
+          expectCompressed_ = null;
+          expectCompressedBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.BoolValue.Builder getExpectCompressedBuilder() {
+        
+        onChanged();
+        return getExpectCompressedFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder getExpectCompressedOrBuilder() {
+        if (expectCompressedBuilder_ != null) {
+          return expectCompressedBuilder_.getMessageOrBuilder();
+        } else {
+          return expectCompressed_ == null ?
+              io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance() : expectCompressed_;
+        }
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 8;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.BoolValue, io.grpc.benchmarks.proto.Messages.BoolValue.Builder, io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder> 
+          getExpectCompressedFieldBuilder() {
+        if (expectCompressedBuilder_ == null) {
+          expectCompressedBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Messages.BoolValue, io.grpc.benchmarks.proto.Messages.BoolValue.Builder, io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder>(
+                  getExpectCompressed(),
+                  getParentForChildren(),
+                  isClean());
+          expectCompressed_ = null;
+        }
+        return expectCompressedBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.SimpleRequest)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.SimpleRequest)
+    private static final io.grpc.benchmarks.proto.Messages.SimpleRequest DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Messages.SimpleRequest();
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.SimpleRequest getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<SimpleRequest>
+        PARSER = new com.google.protobuf.AbstractParser<SimpleRequest>() {
+      public SimpleRequest parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new SimpleRequest(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<SimpleRequest> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<SimpleRequest> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Messages.SimpleRequest getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface SimpleResponseOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.SimpleResponse)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Payload to increase message size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    boolean hasPayload();
+    /**
+     * <pre>
+     * Payload to increase message size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.Payload getPayload();
+    /**
+     * <pre>
+     * Payload to increase message size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder();
+
+    /**
+     * <pre>
+     * The user the request came from, for verifying authentication was
+     * successful when the client expected it.
+     * </pre>
+     *
+     * <code>string username = 2;</code>
+     */
+    java.lang.String getUsername();
+    /**
+     * <pre>
+     * The user the request came from, for verifying authentication was
+     * successful when the client expected it.
+     * </pre>
+     *
+     * <code>string username = 2;</code>
+     */
+    com.google.protobuf.ByteString
+        getUsernameBytes();
+
+    /**
+     * <pre>
+     * OAuth scope.
+     * </pre>
+     *
+     * <code>string oauth_scope = 3;</code>
+     */
+    java.lang.String getOauthScope();
+    /**
+     * <pre>
+     * OAuth scope.
+     * </pre>
+     *
+     * <code>string oauth_scope = 3;</code>
+     */
+    com.google.protobuf.ByteString
+        getOauthScopeBytes();
+  }
+  /**
+   * <pre>
+   * Unary response, as configured by the request.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.SimpleResponse}
+   */
+  public  static final class SimpleResponse extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.SimpleResponse)
+      SimpleResponseOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use SimpleResponse.newBuilder() to construct.
+    private SimpleResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private SimpleResponse() {
+      username_ = "";
+      oauthScope_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private SimpleResponse(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.benchmarks.proto.Messages.Payload.Builder subBuilder = null;
+              if (payload_ != null) {
+                subBuilder = payload_.toBuilder();
+              }
+              payload_ = input.readMessage(io.grpc.benchmarks.proto.Messages.Payload.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(payload_);
+                payload_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 18: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              username_ = s;
+              break;
+            }
+            case 26: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              oauthScope_ = s;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_SimpleResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_SimpleResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Messages.SimpleResponse.class, io.grpc.benchmarks.proto.Messages.SimpleResponse.Builder.class);
+    }
+
+    public static final int PAYLOAD_FIELD_NUMBER = 1;
+    private io.grpc.benchmarks.proto.Messages.Payload payload_;
+    /**
+     * <pre>
+     * Payload to increase message size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public boolean hasPayload() {
+      return payload_ != null;
+    }
+    /**
+     * <pre>
+     * Payload to increase message size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.Payload getPayload() {
+      return payload_ == null ? io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+    }
+    /**
+     * <pre>
+     * Payload to increase message size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+      return getPayload();
+    }
+
+    public static final int USERNAME_FIELD_NUMBER = 2;
+    private volatile java.lang.Object username_;
+    /**
+     * <pre>
+     * The user the request came from, for verifying authentication was
+     * successful when the client expected it.
+     * </pre>
+     *
+     * <code>string username = 2;</code>
+     */
+    public java.lang.String getUsername() {
+      java.lang.Object ref = username_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        username_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * The user the request came from, for verifying authentication was
+     * successful when the client expected it.
+     * </pre>
+     *
+     * <code>string username = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getUsernameBytes() {
+      java.lang.Object ref = username_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        username_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int OAUTH_SCOPE_FIELD_NUMBER = 3;
+    private volatile java.lang.Object oauthScope_;
+    /**
+     * <pre>
+     * OAuth scope.
+     * </pre>
+     *
+     * <code>string oauth_scope = 3;</code>
+     */
+    public java.lang.String getOauthScope() {
+      java.lang.Object ref = oauthScope_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        oauthScope_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * OAuth scope.
+     * </pre>
+     *
+     * <code>string oauth_scope = 3;</code>
+     */
+    public com.google.protobuf.ByteString
+        getOauthScopeBytes() {
+      java.lang.Object ref = oauthScope_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        oauthScope_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (payload_ != null) {
+        output.writeMessage(1, getPayload());
+      }
+      if (!getUsernameBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, username_);
+      }
+      if (!getOauthScopeBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 3, oauthScope_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (payload_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, getPayload());
+      }
+      if (!getUsernameBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, username_);
+      }
+      if (!getOauthScopeBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, oauthScope_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Messages.SimpleResponse)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Messages.SimpleResponse other = (io.grpc.benchmarks.proto.Messages.SimpleResponse) obj;
+
+      boolean result = true;
+      result = result && (hasPayload() == other.hasPayload());
+      if (hasPayload()) {
+        result = result && getPayload()
+            .equals(other.getPayload());
+      }
+      result = result && getUsername()
+          .equals(other.getUsername());
+      result = result && getOauthScope()
+          .equals(other.getOauthScope());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (hasPayload()) {
+        hash = (37 * hash) + PAYLOAD_FIELD_NUMBER;
+        hash = (53 * hash) + getPayload().hashCode();
+      }
+      hash = (37 * hash) + USERNAME_FIELD_NUMBER;
+      hash = (53 * hash) + getUsername().hashCode();
+      hash = (37 * hash) + OAUTH_SCOPE_FIELD_NUMBER;
+      hash = (53 * hash) + getOauthScope().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.SimpleResponse parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleResponse parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleResponse parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleResponse parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleResponse parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleResponse parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleResponse parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleResponse parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleResponse parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleResponse parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleResponse parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.SimpleResponse parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Messages.SimpleResponse prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Unary response, as configured by the request.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.SimpleResponse}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.SimpleResponse)
+        io.grpc.benchmarks.proto.Messages.SimpleResponseOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_SimpleResponse_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_SimpleResponse_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Messages.SimpleResponse.class, io.grpc.benchmarks.proto.Messages.SimpleResponse.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Messages.SimpleResponse.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+        username_ = "";
+
+        oauthScope_ = "";
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_SimpleResponse_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.SimpleResponse getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Messages.SimpleResponse.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Messages.SimpleResponse build() {
+        io.grpc.benchmarks.proto.Messages.SimpleResponse result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.SimpleResponse buildPartial() {
+        io.grpc.benchmarks.proto.Messages.SimpleResponse result = new io.grpc.benchmarks.proto.Messages.SimpleResponse(this);
+        if (payloadBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = payloadBuilder_.build();
+        }
+        result.username_ = username_;
+        result.oauthScope_ = oauthScope_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Messages.SimpleResponse) {
+          return mergeFrom((io.grpc.benchmarks.proto.Messages.SimpleResponse)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Messages.SimpleResponse other) {
+        if (other == io.grpc.benchmarks.proto.Messages.SimpleResponse.getDefaultInstance()) return this;
+        if (other.hasPayload()) {
+          mergePayload(other.getPayload());
+        }
+        if (!other.getUsername().isEmpty()) {
+          username_ = other.username_;
+          onChanged();
+        }
+        if (!other.getOauthScope().isEmpty()) {
+          oauthScope_ = other.oauthScope_;
+          onChanged();
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Messages.SimpleResponse parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Messages.SimpleResponse) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Messages.Payload payload_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder> payloadBuilder_;
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public boolean hasPayload() {
+        return payloadBuilder_ != null || payload_ != null;
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.Payload getPayload() {
+        if (payloadBuilder_ == null) {
+          return payload_ == null ? io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+        } else {
+          return payloadBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder setPayload(io.grpc.benchmarks.proto.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payload_ = value;
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder setPayload(
+          io.grpc.benchmarks.proto.Messages.Payload.Builder builderForValue) {
+        if (payloadBuilder_ == null) {
+          payload_ = builderForValue.build();
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder mergePayload(io.grpc.benchmarks.proto.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (payload_ != null) {
+            payload_ =
+              io.grpc.benchmarks.proto.Messages.Payload.newBuilder(payload_).mergeFrom(value).buildPartial();
+          } else {
+            payload_ = value;
+          }
+          onChanged();
+        } else {
+          payloadBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder clearPayload() {
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+          onChanged();
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.Payload.Builder getPayloadBuilder() {
+        
+        onChanged();
+        return getPayloadFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+        if (payloadBuilder_ != null) {
+          return payloadBuilder_.getMessageOrBuilder();
+        } else {
+          return payload_ == null ?
+              io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+        }
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder> 
+          getPayloadFieldBuilder() {
+        if (payloadBuilder_ == null) {
+          payloadBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder>(
+                  getPayload(),
+                  getParentForChildren(),
+                  isClean());
+          payload_ = null;
+        }
+        return payloadBuilder_;
+      }
+
+      private java.lang.Object username_ = "";
+      /**
+       * <pre>
+       * The user the request came from, for verifying authentication was
+       * successful when the client expected it.
+       * </pre>
+       *
+       * <code>string username = 2;</code>
+       */
+      public java.lang.String getUsername() {
+        java.lang.Object ref = username_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          username_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The user the request came from, for verifying authentication was
+       * successful when the client expected it.
+       * </pre>
+       *
+       * <code>string username = 2;</code>
+       */
+      public com.google.protobuf.ByteString
+          getUsernameBytes() {
+        java.lang.Object ref = username_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          username_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The user the request came from, for verifying authentication was
+       * successful when the client expected it.
+       * </pre>
+       *
+       * <code>string username = 2;</code>
+       */
+      public Builder setUsername(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        username_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The user the request came from, for verifying authentication was
+       * successful when the client expected it.
+       * </pre>
+       *
+       * <code>string username = 2;</code>
+       */
+      public Builder clearUsername() {
+        
+        username_ = getDefaultInstance().getUsername();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The user the request came from, for verifying authentication was
+       * successful when the client expected it.
+       * </pre>
+       *
+       * <code>string username = 2;</code>
+       */
+      public Builder setUsernameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        username_ = value;
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object oauthScope_ = "";
+      /**
+       * <pre>
+       * OAuth scope.
+       * </pre>
+       *
+       * <code>string oauth_scope = 3;</code>
+       */
+      public java.lang.String getOauthScope() {
+        java.lang.Object ref = oauthScope_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          oauthScope_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * OAuth scope.
+       * </pre>
+       *
+       * <code>string oauth_scope = 3;</code>
+       */
+      public com.google.protobuf.ByteString
+          getOauthScopeBytes() {
+        java.lang.Object ref = oauthScope_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          oauthScope_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * OAuth scope.
+       * </pre>
+       *
+       * <code>string oauth_scope = 3;</code>
+       */
+      public Builder setOauthScope(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        oauthScope_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * OAuth scope.
+       * </pre>
+       *
+       * <code>string oauth_scope = 3;</code>
+       */
+      public Builder clearOauthScope() {
+        
+        oauthScope_ = getDefaultInstance().getOauthScope();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * OAuth scope.
+       * </pre>
+       *
+       * <code>string oauth_scope = 3;</code>
+       */
+      public Builder setOauthScopeBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        oauthScope_ = value;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.SimpleResponse)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.SimpleResponse)
+    private static final io.grpc.benchmarks.proto.Messages.SimpleResponse DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Messages.SimpleResponse();
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.SimpleResponse getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<SimpleResponse>
+        PARSER = new com.google.protobuf.AbstractParser<SimpleResponse>() {
+      public SimpleResponse parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new SimpleResponse(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<SimpleResponse> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<SimpleResponse> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Messages.SimpleResponse getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface StreamingInputCallRequestOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.StreamingInputCallRequest)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    boolean hasPayload();
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.Payload getPayload();
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder();
+
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed. This field
+     * is "nullable" in order to interoperate seamlessly with servers not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the request's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+     */
+    boolean hasExpectCompressed();
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed. This field
+     * is "nullable" in order to interoperate seamlessly with servers not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the request's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.BoolValue getExpectCompressed();
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed. This field
+     * is "nullable" in order to interoperate seamlessly with servers not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the request's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder getExpectCompressedOrBuilder();
+  }
+  /**
+   * <pre>
+   * Client-streaming request.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.StreamingInputCallRequest}
+   */
+  public  static final class StreamingInputCallRequest extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.StreamingInputCallRequest)
+      StreamingInputCallRequestOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use StreamingInputCallRequest.newBuilder() to construct.
+    private StreamingInputCallRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private StreamingInputCallRequest() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private StreamingInputCallRequest(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.benchmarks.proto.Messages.Payload.Builder subBuilder = null;
+              if (payload_ != null) {
+                subBuilder = payload_.toBuilder();
+              }
+              payload_ = input.readMessage(io.grpc.benchmarks.proto.Messages.Payload.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(payload_);
+                payload_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 18: {
+              io.grpc.benchmarks.proto.Messages.BoolValue.Builder subBuilder = null;
+              if (expectCompressed_ != null) {
+                subBuilder = expectCompressed_.toBuilder();
+              }
+              expectCompressed_ = input.readMessage(io.grpc.benchmarks.proto.Messages.BoolValue.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(expectCompressed_);
+                expectCompressed_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingInputCallRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingInputCallRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest.class, io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest.Builder.class);
+    }
+
+    public static final int PAYLOAD_FIELD_NUMBER = 1;
+    private io.grpc.benchmarks.proto.Messages.Payload payload_;
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public boolean hasPayload() {
+      return payload_ != null;
+    }
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.Payload getPayload() {
+      return payload_ == null ? io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+    }
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+      return getPayload();
+    }
+
+    public static final int EXPECT_COMPRESSED_FIELD_NUMBER = 2;
+    private io.grpc.benchmarks.proto.Messages.BoolValue expectCompressed_;
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed. This field
+     * is "nullable" in order to interoperate seamlessly with servers not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the request's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+     */
+    public boolean hasExpectCompressed() {
+      return expectCompressed_ != null;
+    }
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed. This field
+     * is "nullable" in order to interoperate seamlessly with servers not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the request's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.BoolValue getExpectCompressed() {
+      return expectCompressed_ == null ? io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance() : expectCompressed_;
+    }
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed. This field
+     * is "nullable" in order to interoperate seamlessly with servers not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the request's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder getExpectCompressedOrBuilder() {
+      return getExpectCompressed();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (payload_ != null) {
+        output.writeMessage(1, getPayload());
+      }
+      if (expectCompressed_ != null) {
+        output.writeMessage(2, getExpectCompressed());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (payload_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, getPayload());
+      }
+      if (expectCompressed_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, getExpectCompressed());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest other = (io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest) obj;
+
+      boolean result = true;
+      result = result && (hasPayload() == other.hasPayload());
+      if (hasPayload()) {
+        result = result && getPayload()
+            .equals(other.getPayload());
+      }
+      result = result && (hasExpectCompressed() == other.hasExpectCompressed());
+      if (hasExpectCompressed()) {
+        result = result && getExpectCompressed()
+            .equals(other.getExpectCompressed());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (hasPayload()) {
+        hash = (37 * hash) + PAYLOAD_FIELD_NUMBER;
+        hash = (53 * hash) + getPayload().hashCode();
+      }
+      if (hasExpectCompressed()) {
+        hash = (37 * hash) + EXPECT_COMPRESSED_FIELD_NUMBER;
+        hash = (53 * hash) + getExpectCompressed().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Client-streaming request.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.StreamingInputCallRequest}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.StreamingInputCallRequest)
+        io.grpc.benchmarks.proto.Messages.StreamingInputCallRequestOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingInputCallRequest_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingInputCallRequest_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest.class, io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+        if (expectCompressedBuilder_ == null) {
+          expectCompressed_ = null;
+        } else {
+          expectCompressed_ = null;
+          expectCompressedBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingInputCallRequest_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest build() {
+        io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest buildPartial() {
+        io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest result = new io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest(this);
+        if (payloadBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = payloadBuilder_.build();
+        }
+        if (expectCompressedBuilder_ == null) {
+          result.expectCompressed_ = expectCompressed_;
+        } else {
+          result.expectCompressed_ = expectCompressedBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest) {
+          return mergeFrom((io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest other) {
+        if (other == io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest.getDefaultInstance()) return this;
+        if (other.hasPayload()) {
+          mergePayload(other.getPayload());
+        }
+        if (other.hasExpectCompressed()) {
+          mergeExpectCompressed(other.getExpectCompressed());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Messages.Payload payload_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder> payloadBuilder_;
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public boolean hasPayload() {
+        return payloadBuilder_ != null || payload_ != null;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.Payload getPayload() {
+        if (payloadBuilder_ == null) {
+          return payload_ == null ? io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+        } else {
+          return payloadBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder setPayload(io.grpc.benchmarks.proto.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payload_ = value;
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder setPayload(
+          io.grpc.benchmarks.proto.Messages.Payload.Builder builderForValue) {
+        if (payloadBuilder_ == null) {
+          payload_ = builderForValue.build();
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder mergePayload(io.grpc.benchmarks.proto.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (payload_ != null) {
+            payload_ =
+              io.grpc.benchmarks.proto.Messages.Payload.newBuilder(payload_).mergeFrom(value).buildPartial();
+          } else {
+            payload_ = value;
+          }
+          onChanged();
+        } else {
+          payloadBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder clearPayload() {
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+          onChanged();
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.Payload.Builder getPayloadBuilder() {
+        
+        onChanged();
+        return getPayloadFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+        if (payloadBuilder_ != null) {
+          return payloadBuilder_.getMessageOrBuilder();
+        } else {
+          return payload_ == null ?
+              io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+        }
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder> 
+          getPayloadFieldBuilder() {
+        if (payloadBuilder_ == null) {
+          payloadBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder>(
+                  getPayload(),
+                  getParentForChildren(),
+                  isClean());
+          payload_ = null;
+        }
+        return payloadBuilder_;
+      }
+
+      private io.grpc.benchmarks.proto.Messages.BoolValue expectCompressed_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.BoolValue, io.grpc.benchmarks.proto.Messages.BoolValue.Builder, io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder> expectCompressedBuilder_;
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+       */
+      public boolean hasExpectCompressed() {
+        return expectCompressedBuilder_ != null || expectCompressed_ != null;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.BoolValue getExpectCompressed() {
+        if (expectCompressedBuilder_ == null) {
+          return expectCompressed_ == null ? io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance() : expectCompressed_;
+        } else {
+          return expectCompressedBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+       */
+      public Builder setExpectCompressed(io.grpc.benchmarks.proto.Messages.BoolValue value) {
+        if (expectCompressedBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          expectCompressed_ = value;
+          onChanged();
+        } else {
+          expectCompressedBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+       */
+      public Builder setExpectCompressed(
+          io.grpc.benchmarks.proto.Messages.BoolValue.Builder builderForValue) {
+        if (expectCompressedBuilder_ == null) {
+          expectCompressed_ = builderForValue.build();
+          onChanged();
+        } else {
+          expectCompressedBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+       */
+      public Builder mergeExpectCompressed(io.grpc.benchmarks.proto.Messages.BoolValue value) {
+        if (expectCompressedBuilder_ == null) {
+          if (expectCompressed_ != null) {
+            expectCompressed_ =
+              io.grpc.benchmarks.proto.Messages.BoolValue.newBuilder(expectCompressed_).mergeFrom(value).buildPartial();
+          } else {
+            expectCompressed_ = value;
+          }
+          onChanged();
+        } else {
+          expectCompressedBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+       */
+      public Builder clearExpectCompressed() {
+        if (expectCompressedBuilder_ == null) {
+          expectCompressed_ = null;
+          onChanged();
+        } else {
+          expectCompressed_ = null;
+          expectCompressedBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.BoolValue.Builder getExpectCompressedBuilder() {
+        
+        onChanged();
+        return getExpectCompressedFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder getExpectCompressedOrBuilder() {
+        if (expectCompressedBuilder_ != null) {
+          return expectCompressedBuilder_.getMessageOrBuilder();
+        } else {
+          return expectCompressed_ == null ?
+              io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance() : expectCompressed_;
+        }
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue expect_compressed = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.BoolValue, io.grpc.benchmarks.proto.Messages.BoolValue.Builder, io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder> 
+          getExpectCompressedFieldBuilder() {
+        if (expectCompressedBuilder_ == null) {
+          expectCompressedBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Messages.BoolValue, io.grpc.benchmarks.proto.Messages.BoolValue.Builder, io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder>(
+                  getExpectCompressed(),
+                  getParentForChildren(),
+                  isClean());
+          expectCompressed_ = null;
+        }
+        return expectCompressedBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.StreamingInputCallRequest)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.StreamingInputCallRequest)
+    private static final io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest();
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<StreamingInputCallRequest>
+        PARSER = new com.google.protobuf.AbstractParser<StreamingInputCallRequest>() {
+      public StreamingInputCallRequest parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new StreamingInputCallRequest(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<StreamingInputCallRequest> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<StreamingInputCallRequest> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Messages.StreamingInputCallRequest getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface StreamingInputCallResponseOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.StreamingInputCallResponse)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Aggregated size of payloads received from the client.
+     * </pre>
+     *
+     * <code>int32 aggregated_payload_size = 1;</code>
+     */
+    int getAggregatedPayloadSize();
+  }
+  /**
+   * <pre>
+   * Client-streaming response.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.StreamingInputCallResponse}
+   */
+  public  static final class StreamingInputCallResponse extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.StreamingInputCallResponse)
+      StreamingInputCallResponseOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use StreamingInputCallResponse.newBuilder() to construct.
+    private StreamingInputCallResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private StreamingInputCallResponse() {
+      aggregatedPayloadSize_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private StreamingInputCallResponse(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              aggregatedPayloadSize_ = input.readInt32();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingInputCallResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingInputCallResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse.class, io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse.Builder.class);
+    }
+
+    public static final int AGGREGATED_PAYLOAD_SIZE_FIELD_NUMBER = 1;
+    private int aggregatedPayloadSize_;
+    /**
+     * <pre>
+     * Aggregated size of payloads received from the client.
+     * </pre>
+     *
+     * <code>int32 aggregated_payload_size = 1;</code>
+     */
+    public int getAggregatedPayloadSize() {
+      return aggregatedPayloadSize_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (aggregatedPayloadSize_ != 0) {
+        output.writeInt32(1, aggregatedPayloadSize_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (aggregatedPayloadSize_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1, aggregatedPayloadSize_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse other = (io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse) obj;
+
+      boolean result = true;
+      result = result && (getAggregatedPayloadSize()
+          == other.getAggregatedPayloadSize());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + AGGREGATED_PAYLOAD_SIZE_FIELD_NUMBER;
+      hash = (53 * hash) + getAggregatedPayloadSize();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Client-streaming response.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.StreamingInputCallResponse}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.StreamingInputCallResponse)
+        io.grpc.benchmarks.proto.Messages.StreamingInputCallResponseOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingInputCallResponse_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingInputCallResponse_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse.class, io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        aggregatedPayloadSize_ = 0;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingInputCallResponse_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse build() {
+        io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse buildPartial() {
+        io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse result = new io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse(this);
+        result.aggregatedPayloadSize_ = aggregatedPayloadSize_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse) {
+          return mergeFrom((io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse other) {
+        if (other == io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse.getDefaultInstance()) return this;
+        if (other.getAggregatedPayloadSize() != 0) {
+          setAggregatedPayloadSize(other.getAggregatedPayloadSize());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int aggregatedPayloadSize_ ;
+      /**
+       * <pre>
+       * Aggregated size of payloads received from the client.
+       * </pre>
+       *
+       * <code>int32 aggregated_payload_size = 1;</code>
+       */
+      public int getAggregatedPayloadSize() {
+        return aggregatedPayloadSize_;
+      }
+      /**
+       * <pre>
+       * Aggregated size of payloads received from the client.
+       * </pre>
+       *
+       * <code>int32 aggregated_payload_size = 1;</code>
+       */
+      public Builder setAggregatedPayloadSize(int value) {
+        
+        aggregatedPayloadSize_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Aggregated size of payloads received from the client.
+       * </pre>
+       *
+       * <code>int32 aggregated_payload_size = 1;</code>
+       */
+      public Builder clearAggregatedPayloadSize() {
+        
+        aggregatedPayloadSize_ = 0;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.StreamingInputCallResponse)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.StreamingInputCallResponse)
+    private static final io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse();
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<StreamingInputCallResponse>
+        PARSER = new com.google.protobuf.AbstractParser<StreamingInputCallResponse>() {
+      public StreamingInputCallResponse parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new StreamingInputCallResponse(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<StreamingInputCallResponse> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<StreamingInputCallResponse> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Messages.StreamingInputCallResponse getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ResponseParametersOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ResponseParameters)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Desired payload sizes in responses from the server.
+     * </pre>
+     *
+     * <code>int32 size = 1;</code>
+     */
+    int getSize();
+
+    /**
+     * <pre>
+     * Desired interval between consecutive responses in the response stream in
+     * microseconds.
+     * </pre>
+     *
+     * <code>int32 interval_us = 2;</code>
+     */
+    int getIntervalUs();
+
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue compressed = 3;</code>
+     */
+    boolean hasCompressed();
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue compressed = 3;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.BoolValue getCompressed();
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue compressed = 3;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder getCompressedOrBuilder();
+  }
+  /**
+   * <pre>
+   * Configuration for a particular response.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.ResponseParameters}
+   */
+  public  static final class ResponseParameters extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ResponseParameters)
+      ResponseParametersOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ResponseParameters.newBuilder() to construct.
+    private ResponseParameters(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ResponseParameters() {
+      size_ = 0;
+      intervalUs_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ResponseParameters(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              size_ = input.readInt32();
+              break;
+            }
+            case 16: {
+
+              intervalUs_ = input.readInt32();
+              break;
+            }
+            case 26: {
+              io.grpc.benchmarks.proto.Messages.BoolValue.Builder subBuilder = null;
+              if (compressed_ != null) {
+                subBuilder = compressed_.toBuilder();
+              }
+              compressed_ = input.readMessage(io.grpc.benchmarks.proto.Messages.BoolValue.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(compressed_);
+                compressed_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ResponseParameters_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ResponseParameters_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Messages.ResponseParameters.class, io.grpc.benchmarks.proto.Messages.ResponseParameters.Builder.class);
+    }
+
+    public static final int SIZE_FIELD_NUMBER = 1;
+    private int size_;
+    /**
+     * <pre>
+     * Desired payload sizes in responses from the server.
+     * </pre>
+     *
+     * <code>int32 size = 1;</code>
+     */
+    public int getSize() {
+      return size_;
+    }
+
+    public static final int INTERVAL_US_FIELD_NUMBER = 2;
+    private int intervalUs_;
+    /**
+     * <pre>
+     * Desired interval between consecutive responses in the response stream in
+     * microseconds.
+     * </pre>
+     *
+     * <code>int32 interval_us = 2;</code>
+     */
+    public int getIntervalUs() {
+      return intervalUs_;
+    }
+
+    public static final int COMPRESSED_FIELD_NUMBER = 3;
+    private io.grpc.benchmarks.proto.Messages.BoolValue compressed_;
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue compressed = 3;</code>
+     */
+    public boolean hasCompressed() {
+      return compressed_ != null;
+    }
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue compressed = 3;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.BoolValue getCompressed() {
+      return compressed_ == null ? io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance() : compressed_;
+    }
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.grpc.testing.BoolValue compressed = 3;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder getCompressedOrBuilder() {
+      return getCompressed();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (size_ != 0) {
+        output.writeInt32(1, size_);
+      }
+      if (intervalUs_ != 0) {
+        output.writeInt32(2, intervalUs_);
+      }
+      if (compressed_ != null) {
+        output.writeMessage(3, getCompressed());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (size_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1, size_);
+      }
+      if (intervalUs_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(2, intervalUs_);
+      }
+      if (compressed_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(3, getCompressed());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Messages.ResponseParameters)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Messages.ResponseParameters other = (io.grpc.benchmarks.proto.Messages.ResponseParameters) obj;
+
+      boolean result = true;
+      result = result && (getSize()
+          == other.getSize());
+      result = result && (getIntervalUs()
+          == other.getIntervalUs());
+      result = result && (hasCompressed() == other.hasCompressed());
+      if (hasCompressed()) {
+        result = result && getCompressed()
+            .equals(other.getCompressed());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + SIZE_FIELD_NUMBER;
+      hash = (53 * hash) + getSize();
+      hash = (37 * hash) + INTERVAL_US_FIELD_NUMBER;
+      hash = (53 * hash) + getIntervalUs();
+      if (hasCompressed()) {
+        hash = (37 * hash) + COMPRESSED_FIELD_NUMBER;
+        hash = (53 * hash) + getCompressed().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.ResponseParameters parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ResponseParameters parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ResponseParameters parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ResponseParameters parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ResponseParameters parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ResponseParameters parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ResponseParameters parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ResponseParameters parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ResponseParameters parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ResponseParameters parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ResponseParameters parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ResponseParameters parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Messages.ResponseParameters prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Configuration for a particular response.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.ResponseParameters}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ResponseParameters)
+        io.grpc.benchmarks.proto.Messages.ResponseParametersOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ResponseParameters_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ResponseParameters_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Messages.ResponseParameters.class, io.grpc.benchmarks.proto.Messages.ResponseParameters.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Messages.ResponseParameters.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        size_ = 0;
+
+        intervalUs_ = 0;
+
+        if (compressedBuilder_ == null) {
+          compressed_ = null;
+        } else {
+          compressed_ = null;
+          compressedBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ResponseParameters_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.ResponseParameters getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Messages.ResponseParameters.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Messages.ResponseParameters build() {
+        io.grpc.benchmarks.proto.Messages.ResponseParameters result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.ResponseParameters buildPartial() {
+        io.grpc.benchmarks.proto.Messages.ResponseParameters result = new io.grpc.benchmarks.proto.Messages.ResponseParameters(this);
+        result.size_ = size_;
+        result.intervalUs_ = intervalUs_;
+        if (compressedBuilder_ == null) {
+          result.compressed_ = compressed_;
+        } else {
+          result.compressed_ = compressedBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Messages.ResponseParameters) {
+          return mergeFrom((io.grpc.benchmarks.proto.Messages.ResponseParameters)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Messages.ResponseParameters other) {
+        if (other == io.grpc.benchmarks.proto.Messages.ResponseParameters.getDefaultInstance()) return this;
+        if (other.getSize() != 0) {
+          setSize(other.getSize());
+        }
+        if (other.getIntervalUs() != 0) {
+          setIntervalUs(other.getIntervalUs());
+        }
+        if (other.hasCompressed()) {
+          mergeCompressed(other.getCompressed());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Messages.ResponseParameters parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Messages.ResponseParameters) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int size_ ;
+      /**
+       * <pre>
+       * Desired payload sizes in responses from the server.
+       * </pre>
+       *
+       * <code>int32 size = 1;</code>
+       */
+      public int getSize() {
+        return size_;
+      }
+      /**
+       * <pre>
+       * Desired payload sizes in responses from the server.
+       * </pre>
+       *
+       * <code>int32 size = 1;</code>
+       */
+      public Builder setSize(int value) {
+        
+        size_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Desired payload sizes in responses from the server.
+       * </pre>
+       *
+       * <code>int32 size = 1;</code>
+       */
+      public Builder clearSize() {
+        
+        size_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int intervalUs_ ;
+      /**
+       * <pre>
+       * Desired interval between consecutive responses in the response stream in
+       * microseconds.
+       * </pre>
+       *
+       * <code>int32 interval_us = 2;</code>
+       */
+      public int getIntervalUs() {
+        return intervalUs_;
+      }
+      /**
+       * <pre>
+       * Desired interval between consecutive responses in the response stream in
+       * microseconds.
+       * </pre>
+       *
+       * <code>int32 interval_us = 2;</code>
+       */
+      public Builder setIntervalUs(int value) {
+        
+        intervalUs_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Desired interval between consecutive responses in the response stream in
+       * microseconds.
+       * </pre>
+       *
+       * <code>int32 interval_us = 2;</code>
+       */
+      public Builder clearIntervalUs() {
+        
+        intervalUs_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Messages.BoolValue compressed_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.BoolValue, io.grpc.benchmarks.proto.Messages.BoolValue.Builder, io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder> compressedBuilder_;
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue compressed = 3;</code>
+       */
+      public boolean hasCompressed() {
+        return compressedBuilder_ != null || compressed_ != null;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue compressed = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.BoolValue getCompressed() {
+        if (compressedBuilder_ == null) {
+          return compressed_ == null ? io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance() : compressed_;
+        } else {
+          return compressedBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue compressed = 3;</code>
+       */
+      public Builder setCompressed(io.grpc.benchmarks.proto.Messages.BoolValue value) {
+        if (compressedBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          compressed_ = value;
+          onChanged();
+        } else {
+          compressedBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue compressed = 3;</code>
+       */
+      public Builder setCompressed(
+          io.grpc.benchmarks.proto.Messages.BoolValue.Builder builderForValue) {
+        if (compressedBuilder_ == null) {
+          compressed_ = builderForValue.build();
+          onChanged();
+        } else {
+          compressedBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue compressed = 3;</code>
+       */
+      public Builder mergeCompressed(io.grpc.benchmarks.proto.Messages.BoolValue value) {
+        if (compressedBuilder_ == null) {
+          if (compressed_ != null) {
+            compressed_ =
+              io.grpc.benchmarks.proto.Messages.BoolValue.newBuilder(compressed_).mergeFrom(value).buildPartial();
+          } else {
+            compressed_ = value;
+          }
+          onChanged();
+        } else {
+          compressedBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue compressed = 3;</code>
+       */
+      public Builder clearCompressed() {
+        if (compressedBuilder_ == null) {
+          compressed_ = null;
+          onChanged();
+        } else {
+          compressed_ = null;
+          compressedBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue compressed = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.BoolValue.Builder getCompressedBuilder() {
+        
+        onChanged();
+        return getCompressedFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue compressed = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder getCompressedOrBuilder() {
+        if (compressedBuilder_ != null) {
+          return compressedBuilder_.getMessageOrBuilder();
+        } else {
+          return compressed_ == null ?
+              io.grpc.benchmarks.proto.Messages.BoolValue.getDefaultInstance() : compressed_;
+        }
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.grpc.testing.BoolValue compressed = 3;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.BoolValue, io.grpc.benchmarks.proto.Messages.BoolValue.Builder, io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder> 
+          getCompressedFieldBuilder() {
+        if (compressedBuilder_ == null) {
+          compressedBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Messages.BoolValue, io.grpc.benchmarks.proto.Messages.BoolValue.Builder, io.grpc.benchmarks.proto.Messages.BoolValueOrBuilder>(
+                  getCompressed(),
+                  getParentForChildren(),
+                  isClean());
+          compressed_ = null;
+        }
+        return compressedBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ResponseParameters)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ResponseParameters)
+    private static final io.grpc.benchmarks.proto.Messages.ResponseParameters DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Messages.ResponseParameters();
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.ResponseParameters getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ResponseParameters>
+        PARSER = new com.google.protobuf.AbstractParser<ResponseParameters>() {
+      public ResponseParameters parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ResponseParameters(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ResponseParameters> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ResponseParameters> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Messages.ResponseParameters getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface StreamingOutputCallRequestOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.StreamingOutputCallRequest)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, the payload from each response in the stream
+     * might be of different types. This is to simulate a mixed type of payload
+     * stream.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    int getResponseTypeValue();
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, the payload from each response in the stream
+     * might be of different types. This is to simulate a mixed type of payload
+     * stream.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.PayloadType getResponseType();
+
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    java.util.List<io.grpc.benchmarks.proto.Messages.ResponseParameters> 
+        getResponseParametersList();
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.ResponseParameters getResponseParameters(int index);
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    int getResponseParametersCount();
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    java.util.List<? extends io.grpc.benchmarks.proto.Messages.ResponseParametersOrBuilder> 
+        getResponseParametersOrBuilderList();
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.ResponseParametersOrBuilder getResponseParametersOrBuilder(
+        int index);
+
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    boolean hasPayload();
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.Payload getPayload();
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder();
+
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    boolean hasResponseStatus();
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.EchoStatus getResponseStatus();
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.EchoStatusOrBuilder getResponseStatusOrBuilder();
+  }
+  /**
+   * <pre>
+   * Server-streaming request.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.StreamingOutputCallRequest}
+   */
+  public  static final class StreamingOutputCallRequest extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.StreamingOutputCallRequest)
+      StreamingOutputCallRequestOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use StreamingOutputCallRequest.newBuilder() to construct.
+    private StreamingOutputCallRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private StreamingOutputCallRequest() {
+      responseType_ = 0;
+      responseParameters_ = java.util.Collections.emptyList();
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private StreamingOutputCallRequest(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+              int rawValue = input.readEnum();
+
+              responseType_ = rawValue;
+              break;
+            }
+            case 18: {
+              if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+                responseParameters_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Messages.ResponseParameters>();
+                mutable_bitField0_ |= 0x00000002;
+              }
+              responseParameters_.add(
+                  input.readMessage(io.grpc.benchmarks.proto.Messages.ResponseParameters.parser(), extensionRegistry));
+              break;
+            }
+            case 26: {
+              io.grpc.benchmarks.proto.Messages.Payload.Builder subBuilder = null;
+              if (payload_ != null) {
+                subBuilder = payload_.toBuilder();
+              }
+              payload_ = input.readMessage(io.grpc.benchmarks.proto.Messages.Payload.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(payload_);
+                payload_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 58: {
+              io.grpc.benchmarks.proto.Messages.EchoStatus.Builder subBuilder = null;
+              if (responseStatus_ != null) {
+                subBuilder = responseStatus_.toBuilder();
+              }
+              responseStatus_ = input.readMessage(io.grpc.benchmarks.proto.Messages.EchoStatus.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(responseStatus_);
+                responseStatus_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+          responseParameters_ = java.util.Collections.unmodifiableList(responseParameters_);
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingOutputCallRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingOutputCallRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest.class, io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest.Builder.class);
+    }
+
+    private int bitField0_;
+    public static final int RESPONSE_TYPE_FIELD_NUMBER = 1;
+    private int responseType_;
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, the payload from each response in the stream
+     * might be of different types. This is to simulate a mixed type of payload
+     * stream.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    public int getResponseTypeValue() {
+      return responseType_;
+    }
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, the payload from each response in the stream
+     * might be of different types. This is to simulate a mixed type of payload
+     * stream.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.PayloadType getResponseType() {
+      io.grpc.benchmarks.proto.Messages.PayloadType result = io.grpc.benchmarks.proto.Messages.PayloadType.valueOf(responseType_);
+      return result == null ? io.grpc.benchmarks.proto.Messages.PayloadType.UNRECOGNIZED : result;
+    }
+
+    public static final int RESPONSE_PARAMETERS_FIELD_NUMBER = 2;
+    private java.util.List<io.grpc.benchmarks.proto.Messages.ResponseParameters> responseParameters_;
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    public java.util.List<io.grpc.benchmarks.proto.Messages.ResponseParameters> getResponseParametersList() {
+      return responseParameters_;
+    }
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    public java.util.List<? extends io.grpc.benchmarks.proto.Messages.ResponseParametersOrBuilder> 
+        getResponseParametersOrBuilderList() {
+      return responseParameters_;
+    }
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    public int getResponseParametersCount() {
+      return responseParameters_.size();
+    }
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.ResponseParameters getResponseParameters(int index) {
+      return responseParameters_.get(index);
+    }
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.ResponseParametersOrBuilder getResponseParametersOrBuilder(
+        int index) {
+      return responseParameters_.get(index);
+    }
+
+    public static final int PAYLOAD_FIELD_NUMBER = 3;
+    private io.grpc.benchmarks.proto.Messages.Payload payload_;
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    public boolean hasPayload() {
+      return payload_ != null;
+    }
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.Payload getPayload() {
+      return payload_ == null ? io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+    }
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+      return getPayload();
+    }
+
+    public static final int RESPONSE_STATUS_FIELD_NUMBER = 7;
+    private io.grpc.benchmarks.proto.Messages.EchoStatus responseStatus_;
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    public boolean hasResponseStatus() {
+      return responseStatus_ != null;
+    }
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.EchoStatus getResponseStatus() {
+      return responseStatus_ == null ? io.grpc.benchmarks.proto.Messages.EchoStatus.getDefaultInstance() : responseStatus_;
+    }
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.EchoStatusOrBuilder getResponseStatusOrBuilder() {
+      return getResponseStatus();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (responseType_ != io.grpc.benchmarks.proto.Messages.PayloadType.COMPRESSABLE.getNumber()) {
+        output.writeEnum(1, responseType_);
+      }
+      for (int i = 0; i < responseParameters_.size(); i++) {
+        output.writeMessage(2, responseParameters_.get(i));
+      }
+      if (payload_ != null) {
+        output.writeMessage(3, getPayload());
+      }
+      if (responseStatus_ != null) {
+        output.writeMessage(7, getResponseStatus());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (responseType_ != io.grpc.benchmarks.proto.Messages.PayloadType.COMPRESSABLE.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(1, responseType_);
+      }
+      for (int i = 0; i < responseParameters_.size(); i++) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, responseParameters_.get(i));
+      }
+      if (payload_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(3, getPayload());
+      }
+      if (responseStatus_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(7, getResponseStatus());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest other = (io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest) obj;
+
+      boolean result = true;
+      result = result && responseType_ == other.responseType_;
+      result = result && getResponseParametersList()
+          .equals(other.getResponseParametersList());
+      result = result && (hasPayload() == other.hasPayload());
+      if (hasPayload()) {
+        result = result && getPayload()
+            .equals(other.getPayload());
+      }
+      result = result && (hasResponseStatus() == other.hasResponseStatus());
+      if (hasResponseStatus()) {
+        result = result && getResponseStatus()
+            .equals(other.getResponseStatus());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + RESPONSE_TYPE_FIELD_NUMBER;
+      hash = (53 * hash) + responseType_;
+      if (getResponseParametersCount() > 0) {
+        hash = (37 * hash) + RESPONSE_PARAMETERS_FIELD_NUMBER;
+        hash = (53 * hash) + getResponseParametersList().hashCode();
+      }
+      if (hasPayload()) {
+        hash = (37 * hash) + PAYLOAD_FIELD_NUMBER;
+        hash = (53 * hash) + getPayload().hashCode();
+      }
+      if (hasResponseStatus()) {
+        hash = (37 * hash) + RESPONSE_STATUS_FIELD_NUMBER;
+        hash = (53 * hash) + getResponseStatus().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Server-streaming request.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.StreamingOutputCallRequest}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.StreamingOutputCallRequest)
+        io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequestOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingOutputCallRequest_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingOutputCallRequest_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest.class, io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+          getResponseParametersFieldBuilder();
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        responseType_ = 0;
+
+        if (responseParametersBuilder_ == null) {
+          responseParameters_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000002);
+        } else {
+          responseParametersBuilder_.clear();
+        }
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+        if (responseStatusBuilder_ == null) {
+          responseStatus_ = null;
+        } else {
+          responseStatus_ = null;
+          responseStatusBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingOutputCallRequest_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest build() {
+        io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest buildPartial() {
+        io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest result = new io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        result.responseType_ = responseType_;
+        if (responseParametersBuilder_ == null) {
+          if (((bitField0_ & 0x00000002) == 0x00000002)) {
+            responseParameters_ = java.util.Collections.unmodifiableList(responseParameters_);
+            bitField0_ = (bitField0_ & ~0x00000002);
+          }
+          result.responseParameters_ = responseParameters_;
+        } else {
+          result.responseParameters_ = responseParametersBuilder_.build();
+        }
+        if (payloadBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = payloadBuilder_.build();
+        }
+        if (responseStatusBuilder_ == null) {
+          result.responseStatus_ = responseStatus_;
+        } else {
+          result.responseStatus_ = responseStatusBuilder_.build();
+        }
+        result.bitField0_ = to_bitField0_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest) {
+          return mergeFrom((io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest other) {
+        if (other == io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest.getDefaultInstance()) return this;
+        if (other.responseType_ != 0) {
+          setResponseTypeValue(other.getResponseTypeValue());
+        }
+        if (responseParametersBuilder_ == null) {
+          if (!other.responseParameters_.isEmpty()) {
+            if (responseParameters_.isEmpty()) {
+              responseParameters_ = other.responseParameters_;
+              bitField0_ = (bitField0_ & ~0x00000002);
+            } else {
+              ensureResponseParametersIsMutable();
+              responseParameters_.addAll(other.responseParameters_);
+            }
+            onChanged();
+          }
+        } else {
+          if (!other.responseParameters_.isEmpty()) {
+            if (responseParametersBuilder_.isEmpty()) {
+              responseParametersBuilder_.dispose();
+              responseParametersBuilder_ = null;
+              responseParameters_ = other.responseParameters_;
+              bitField0_ = (bitField0_ & ~0x00000002);
+              responseParametersBuilder_ = 
+                com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                   getResponseParametersFieldBuilder() : null;
+            } else {
+              responseParametersBuilder_.addAllMessages(other.responseParameters_);
+            }
+          }
+        }
+        if (other.hasPayload()) {
+          mergePayload(other.getPayload());
+        }
+        if (other.hasResponseStatus()) {
+          mergeResponseStatus(other.getResponseStatus());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private int responseType_ = 0;
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, the payload from each response in the stream
+       * might be of different types. This is to simulate a mixed type of payload
+       * stream.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public int getResponseTypeValue() {
+        return responseType_;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, the payload from each response in the stream
+       * might be of different types. This is to simulate a mixed type of payload
+       * stream.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public Builder setResponseTypeValue(int value) {
+        responseType_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, the payload from each response in the stream
+       * might be of different types. This is to simulate a mixed type of payload
+       * stream.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.PayloadType getResponseType() {
+        io.grpc.benchmarks.proto.Messages.PayloadType result = io.grpc.benchmarks.proto.Messages.PayloadType.valueOf(responseType_);
+        return result == null ? io.grpc.benchmarks.proto.Messages.PayloadType.UNRECOGNIZED : result;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, the payload from each response in the stream
+       * might be of different types. This is to simulate a mixed type of payload
+       * stream.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public Builder setResponseType(io.grpc.benchmarks.proto.Messages.PayloadType value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        responseType_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, the payload from each response in the stream
+       * might be of different types. This is to simulate a mixed type of payload
+       * stream.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public Builder clearResponseType() {
+        
+        responseType_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private java.util.List<io.grpc.benchmarks.proto.Messages.ResponseParameters> responseParameters_ =
+        java.util.Collections.emptyList();
+      private void ensureResponseParametersIsMutable() {
+        if (!((bitField0_ & 0x00000002) == 0x00000002)) {
+          responseParameters_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Messages.ResponseParameters>(responseParameters_);
+          bitField0_ |= 0x00000002;
+         }
+      }
+
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.ResponseParameters, io.grpc.benchmarks.proto.Messages.ResponseParameters.Builder, io.grpc.benchmarks.proto.Messages.ResponseParametersOrBuilder> responseParametersBuilder_;
+
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Messages.ResponseParameters> getResponseParametersList() {
+        if (responseParametersBuilder_ == null) {
+          return java.util.Collections.unmodifiableList(responseParameters_);
+        } else {
+          return responseParametersBuilder_.getMessageList();
+        }
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public int getResponseParametersCount() {
+        if (responseParametersBuilder_ == null) {
+          return responseParameters_.size();
+        } else {
+          return responseParametersBuilder_.getCount();
+        }
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.ResponseParameters getResponseParameters(int index) {
+        if (responseParametersBuilder_ == null) {
+          return responseParameters_.get(index);
+        } else {
+          return responseParametersBuilder_.getMessage(index);
+        }
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder setResponseParameters(
+          int index, io.grpc.benchmarks.proto.Messages.ResponseParameters value) {
+        if (responseParametersBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureResponseParametersIsMutable();
+          responseParameters_.set(index, value);
+          onChanged();
+        } else {
+          responseParametersBuilder_.setMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder setResponseParameters(
+          int index, io.grpc.benchmarks.proto.Messages.ResponseParameters.Builder builderForValue) {
+        if (responseParametersBuilder_ == null) {
+          ensureResponseParametersIsMutable();
+          responseParameters_.set(index, builderForValue.build());
+          onChanged();
+        } else {
+          responseParametersBuilder_.setMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder addResponseParameters(io.grpc.benchmarks.proto.Messages.ResponseParameters value) {
+        if (responseParametersBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureResponseParametersIsMutable();
+          responseParameters_.add(value);
+          onChanged();
+        } else {
+          responseParametersBuilder_.addMessage(value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder addResponseParameters(
+          int index, io.grpc.benchmarks.proto.Messages.ResponseParameters value) {
+        if (responseParametersBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureResponseParametersIsMutable();
+          responseParameters_.add(index, value);
+          onChanged();
+        } else {
+          responseParametersBuilder_.addMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder addResponseParameters(
+          io.grpc.benchmarks.proto.Messages.ResponseParameters.Builder builderForValue) {
+        if (responseParametersBuilder_ == null) {
+          ensureResponseParametersIsMutable();
+          responseParameters_.add(builderForValue.build());
+          onChanged();
+        } else {
+          responseParametersBuilder_.addMessage(builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder addResponseParameters(
+          int index, io.grpc.benchmarks.proto.Messages.ResponseParameters.Builder builderForValue) {
+        if (responseParametersBuilder_ == null) {
+          ensureResponseParametersIsMutable();
+          responseParameters_.add(index, builderForValue.build());
+          onChanged();
+        } else {
+          responseParametersBuilder_.addMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder addAllResponseParameters(
+          java.lang.Iterable<? extends io.grpc.benchmarks.proto.Messages.ResponseParameters> values) {
+        if (responseParametersBuilder_ == null) {
+          ensureResponseParametersIsMutable();
+          com.google.protobuf.AbstractMessageLite.Builder.addAll(
+              values, responseParameters_);
+          onChanged();
+        } else {
+          responseParametersBuilder_.addAllMessages(values);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder clearResponseParameters() {
+        if (responseParametersBuilder_ == null) {
+          responseParameters_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000002);
+          onChanged();
+        } else {
+          responseParametersBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder removeResponseParameters(int index) {
+        if (responseParametersBuilder_ == null) {
+          ensureResponseParametersIsMutable();
+          responseParameters_.remove(index);
+          onChanged();
+        } else {
+          responseParametersBuilder_.remove(index);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.ResponseParameters.Builder getResponseParametersBuilder(
+          int index) {
+        return getResponseParametersFieldBuilder().getBuilder(index);
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.ResponseParametersOrBuilder getResponseParametersOrBuilder(
+          int index) {
+        if (responseParametersBuilder_ == null) {
+          return responseParameters_.get(index);  } else {
+          return responseParametersBuilder_.getMessageOrBuilder(index);
+        }
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public java.util.List<? extends io.grpc.benchmarks.proto.Messages.ResponseParametersOrBuilder> 
+           getResponseParametersOrBuilderList() {
+        if (responseParametersBuilder_ != null) {
+          return responseParametersBuilder_.getMessageOrBuilderList();
+        } else {
+          return java.util.Collections.unmodifiableList(responseParameters_);
+        }
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.ResponseParameters.Builder addResponseParametersBuilder() {
+        return getResponseParametersFieldBuilder().addBuilder(
+            io.grpc.benchmarks.proto.Messages.ResponseParameters.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.ResponseParameters.Builder addResponseParametersBuilder(
+          int index) {
+        return getResponseParametersFieldBuilder().addBuilder(
+            index, io.grpc.benchmarks.proto.Messages.ResponseParameters.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Messages.ResponseParameters.Builder> 
+           getResponseParametersBuilderList() {
+        return getResponseParametersFieldBuilder().getBuilderList();
+      }
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.ResponseParameters, io.grpc.benchmarks.proto.Messages.ResponseParameters.Builder, io.grpc.benchmarks.proto.Messages.ResponseParametersOrBuilder> 
+          getResponseParametersFieldBuilder() {
+        if (responseParametersBuilder_ == null) {
+          responseParametersBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+              io.grpc.benchmarks.proto.Messages.ResponseParameters, io.grpc.benchmarks.proto.Messages.ResponseParameters.Builder, io.grpc.benchmarks.proto.Messages.ResponseParametersOrBuilder>(
+                  responseParameters_,
+                  ((bitField0_ & 0x00000002) == 0x00000002),
+                  getParentForChildren(),
+                  isClean());
+          responseParameters_ = null;
+        }
+        return responseParametersBuilder_;
+      }
+
+      private io.grpc.benchmarks.proto.Messages.Payload payload_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder> payloadBuilder_;
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public boolean hasPayload() {
+        return payloadBuilder_ != null || payload_ != null;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.Payload getPayload() {
+        if (payloadBuilder_ == null) {
+          return payload_ == null ? io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+        } else {
+          return payloadBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder setPayload(io.grpc.benchmarks.proto.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payload_ = value;
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder setPayload(
+          io.grpc.benchmarks.proto.Messages.Payload.Builder builderForValue) {
+        if (payloadBuilder_ == null) {
+          payload_ = builderForValue.build();
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder mergePayload(io.grpc.benchmarks.proto.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (payload_ != null) {
+            payload_ =
+              io.grpc.benchmarks.proto.Messages.Payload.newBuilder(payload_).mergeFrom(value).buildPartial();
+          } else {
+            payload_ = value;
+          }
+          onChanged();
+        } else {
+          payloadBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder clearPayload() {
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+          onChanged();
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.Payload.Builder getPayloadBuilder() {
+        
+        onChanged();
+        return getPayloadFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+        if (payloadBuilder_ != null) {
+          return payloadBuilder_.getMessageOrBuilder();
+        } else {
+          return payload_ == null ?
+              io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+        }
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder> 
+          getPayloadFieldBuilder() {
+        if (payloadBuilder_ == null) {
+          payloadBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder>(
+                  getPayload(),
+                  getParentForChildren(),
+                  isClean());
+          payload_ = null;
+        }
+        return payloadBuilder_;
+      }
+
+      private io.grpc.benchmarks.proto.Messages.EchoStatus responseStatus_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.EchoStatus, io.grpc.benchmarks.proto.Messages.EchoStatus.Builder, io.grpc.benchmarks.proto.Messages.EchoStatusOrBuilder> responseStatusBuilder_;
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public boolean hasResponseStatus() {
+        return responseStatusBuilder_ != null || responseStatus_ != null;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.EchoStatus getResponseStatus() {
+        if (responseStatusBuilder_ == null) {
+          return responseStatus_ == null ? io.grpc.benchmarks.proto.Messages.EchoStatus.getDefaultInstance() : responseStatus_;
+        } else {
+          return responseStatusBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder setResponseStatus(io.grpc.benchmarks.proto.Messages.EchoStatus value) {
+        if (responseStatusBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          responseStatus_ = value;
+          onChanged();
+        } else {
+          responseStatusBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder setResponseStatus(
+          io.grpc.benchmarks.proto.Messages.EchoStatus.Builder builderForValue) {
+        if (responseStatusBuilder_ == null) {
+          responseStatus_ = builderForValue.build();
+          onChanged();
+        } else {
+          responseStatusBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder mergeResponseStatus(io.grpc.benchmarks.proto.Messages.EchoStatus value) {
+        if (responseStatusBuilder_ == null) {
+          if (responseStatus_ != null) {
+            responseStatus_ =
+              io.grpc.benchmarks.proto.Messages.EchoStatus.newBuilder(responseStatus_).mergeFrom(value).buildPartial();
+          } else {
+            responseStatus_ = value;
+          }
+          onChanged();
+        } else {
+          responseStatusBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder clearResponseStatus() {
+        if (responseStatusBuilder_ == null) {
+          responseStatus_ = null;
+          onChanged();
+        } else {
+          responseStatus_ = null;
+          responseStatusBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.EchoStatus.Builder getResponseStatusBuilder() {
+        
+        onChanged();
+        return getResponseStatusFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.EchoStatusOrBuilder getResponseStatusOrBuilder() {
+        if (responseStatusBuilder_ != null) {
+          return responseStatusBuilder_.getMessageOrBuilder();
+        } else {
+          return responseStatus_ == null ?
+              io.grpc.benchmarks.proto.Messages.EchoStatus.getDefaultInstance() : responseStatus_;
+        }
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.EchoStatus, io.grpc.benchmarks.proto.Messages.EchoStatus.Builder, io.grpc.benchmarks.proto.Messages.EchoStatusOrBuilder> 
+          getResponseStatusFieldBuilder() {
+        if (responseStatusBuilder_ == null) {
+          responseStatusBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Messages.EchoStatus, io.grpc.benchmarks.proto.Messages.EchoStatus.Builder, io.grpc.benchmarks.proto.Messages.EchoStatusOrBuilder>(
+                  getResponseStatus(),
+                  getParentForChildren(),
+                  isClean());
+          responseStatus_ = null;
+        }
+        return responseStatusBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.StreamingOutputCallRequest)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.StreamingOutputCallRequest)
+    private static final io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest();
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<StreamingOutputCallRequest>
+        PARSER = new com.google.protobuf.AbstractParser<StreamingOutputCallRequest>() {
+      public StreamingOutputCallRequest parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new StreamingOutputCallRequest(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<StreamingOutputCallRequest> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<StreamingOutputCallRequest> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Messages.StreamingOutputCallRequest getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface StreamingOutputCallResponseOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.StreamingOutputCallResponse)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Payload to increase response size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    boolean hasPayload();
+    /**
+     * <pre>
+     * Payload to increase response size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.Payload getPayload();
+    /**
+     * <pre>
+     * Payload to increase response size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder();
+  }
+  /**
+   * <pre>
+   * Server-streaming response, as configured by the request and parameters.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.StreamingOutputCallResponse}
+   */
+  public  static final class StreamingOutputCallResponse extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.StreamingOutputCallResponse)
+      StreamingOutputCallResponseOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use StreamingOutputCallResponse.newBuilder() to construct.
+    private StreamingOutputCallResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private StreamingOutputCallResponse() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private StreamingOutputCallResponse(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.benchmarks.proto.Messages.Payload.Builder subBuilder = null;
+              if (payload_ != null) {
+                subBuilder = payload_.toBuilder();
+              }
+              payload_ = input.readMessage(io.grpc.benchmarks.proto.Messages.Payload.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(payload_);
+                payload_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingOutputCallResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingOutputCallResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse.class, io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse.Builder.class);
+    }
+
+    public static final int PAYLOAD_FIELD_NUMBER = 1;
+    private io.grpc.benchmarks.proto.Messages.Payload payload_;
+    /**
+     * <pre>
+     * Payload to increase response size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public boolean hasPayload() {
+      return payload_ != null;
+    }
+    /**
+     * <pre>
+     * Payload to increase response size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.Payload getPayload() {
+      return payload_ == null ? io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+    }
+    /**
+     * <pre>
+     * Payload to increase response size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+      return getPayload();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (payload_ != null) {
+        output.writeMessage(1, getPayload());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (payload_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, getPayload());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse other = (io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse) obj;
+
+      boolean result = true;
+      result = result && (hasPayload() == other.hasPayload());
+      if (hasPayload()) {
+        result = result && getPayload()
+            .equals(other.getPayload());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (hasPayload()) {
+        hash = (37 * hash) + PAYLOAD_FIELD_NUMBER;
+        hash = (53 * hash) + getPayload().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Server-streaming response, as configured by the request and parameters.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.StreamingOutputCallResponse}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.StreamingOutputCallResponse)
+        io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponseOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingOutputCallResponse_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingOutputCallResponse_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse.class, io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_StreamingOutputCallResponse_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse build() {
+        io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse buildPartial() {
+        io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse result = new io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse(this);
+        if (payloadBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = payloadBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse) {
+          return mergeFrom((io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse other) {
+        if (other == io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse.getDefaultInstance()) return this;
+        if (other.hasPayload()) {
+          mergePayload(other.getPayload());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private io.grpc.benchmarks.proto.Messages.Payload payload_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder> payloadBuilder_;
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public boolean hasPayload() {
+        return payloadBuilder_ != null || payload_ != null;
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.Payload getPayload() {
+        if (payloadBuilder_ == null) {
+          return payload_ == null ? io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+        } else {
+          return payloadBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder setPayload(io.grpc.benchmarks.proto.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payload_ = value;
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder setPayload(
+          io.grpc.benchmarks.proto.Messages.Payload.Builder builderForValue) {
+        if (payloadBuilder_ == null) {
+          payload_ = builderForValue.build();
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder mergePayload(io.grpc.benchmarks.proto.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (payload_ != null) {
+            payload_ =
+              io.grpc.benchmarks.proto.Messages.Payload.newBuilder(payload_).mergeFrom(value).buildPartial();
+          } else {
+            payload_ = value;
+          }
+          onChanged();
+        } else {
+          payloadBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder clearPayload() {
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+          onChanged();
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.Payload.Builder getPayloadBuilder() {
+        
+        onChanged();
+        return getPayloadFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+        if (payloadBuilder_ != null) {
+          return payloadBuilder_.getMessageOrBuilder();
+        } else {
+          return payload_ == null ?
+              io.grpc.benchmarks.proto.Messages.Payload.getDefaultInstance() : payload_;
+        }
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder> 
+          getPayloadFieldBuilder() {
+        if (payloadBuilder_ == null) {
+          payloadBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Messages.Payload, io.grpc.benchmarks.proto.Messages.Payload.Builder, io.grpc.benchmarks.proto.Messages.PayloadOrBuilder>(
+                  getPayload(),
+                  getParentForChildren(),
+                  isClean());
+          payload_ = null;
+        }
+        return payloadBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.StreamingOutputCallResponse)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.StreamingOutputCallResponse)
+    private static final io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse();
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<StreamingOutputCallResponse>
+        PARSER = new com.google.protobuf.AbstractParser<StreamingOutputCallResponse>() {
+      public StreamingOutputCallResponse parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new StreamingOutputCallResponse(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<StreamingOutputCallResponse> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<StreamingOutputCallResponse> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Messages.StreamingOutputCallResponse getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ReconnectParamsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ReconnectParams)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>int32 max_reconnect_backoff_ms = 1;</code>
+     */
+    int getMaxReconnectBackoffMs();
+  }
+  /**
+   * <pre>
+   * For reconnect interop test only.
+   * Client tells server what reconnection parameters it used.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.ReconnectParams}
+   */
+  public  static final class ReconnectParams extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ReconnectParams)
+      ReconnectParamsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ReconnectParams.newBuilder() to construct.
+    private ReconnectParams(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ReconnectParams() {
+      maxReconnectBackoffMs_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ReconnectParams(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              maxReconnectBackoffMs_ = input.readInt32();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ReconnectParams_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ReconnectParams_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Messages.ReconnectParams.class, io.grpc.benchmarks.proto.Messages.ReconnectParams.Builder.class);
+    }
+
+    public static final int MAX_RECONNECT_BACKOFF_MS_FIELD_NUMBER = 1;
+    private int maxReconnectBackoffMs_;
+    /**
+     * <code>int32 max_reconnect_backoff_ms = 1;</code>
+     */
+    public int getMaxReconnectBackoffMs() {
+      return maxReconnectBackoffMs_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (maxReconnectBackoffMs_ != 0) {
+        output.writeInt32(1, maxReconnectBackoffMs_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (maxReconnectBackoffMs_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1, maxReconnectBackoffMs_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Messages.ReconnectParams)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Messages.ReconnectParams other = (io.grpc.benchmarks.proto.Messages.ReconnectParams) obj;
+
+      boolean result = true;
+      result = result && (getMaxReconnectBackoffMs()
+          == other.getMaxReconnectBackoffMs());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + MAX_RECONNECT_BACKOFF_MS_FIELD_NUMBER;
+      hash = (53 * hash) + getMaxReconnectBackoffMs();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.ReconnectParams parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectParams parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectParams parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectParams parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectParams parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectParams parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectParams parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectParams parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectParams parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectParams parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectParams parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectParams parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Messages.ReconnectParams prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * For reconnect interop test only.
+     * Client tells server what reconnection parameters it used.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.ReconnectParams}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ReconnectParams)
+        io.grpc.benchmarks.proto.Messages.ReconnectParamsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ReconnectParams_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ReconnectParams_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Messages.ReconnectParams.class, io.grpc.benchmarks.proto.Messages.ReconnectParams.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Messages.ReconnectParams.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        maxReconnectBackoffMs_ = 0;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ReconnectParams_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.ReconnectParams getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Messages.ReconnectParams.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Messages.ReconnectParams build() {
+        io.grpc.benchmarks.proto.Messages.ReconnectParams result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.ReconnectParams buildPartial() {
+        io.grpc.benchmarks.proto.Messages.ReconnectParams result = new io.grpc.benchmarks.proto.Messages.ReconnectParams(this);
+        result.maxReconnectBackoffMs_ = maxReconnectBackoffMs_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Messages.ReconnectParams) {
+          return mergeFrom((io.grpc.benchmarks.proto.Messages.ReconnectParams)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Messages.ReconnectParams other) {
+        if (other == io.grpc.benchmarks.proto.Messages.ReconnectParams.getDefaultInstance()) return this;
+        if (other.getMaxReconnectBackoffMs() != 0) {
+          setMaxReconnectBackoffMs(other.getMaxReconnectBackoffMs());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Messages.ReconnectParams parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Messages.ReconnectParams) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int maxReconnectBackoffMs_ ;
+      /**
+       * <code>int32 max_reconnect_backoff_ms = 1;</code>
+       */
+      public int getMaxReconnectBackoffMs() {
+        return maxReconnectBackoffMs_;
+      }
+      /**
+       * <code>int32 max_reconnect_backoff_ms = 1;</code>
+       */
+      public Builder setMaxReconnectBackoffMs(int value) {
+        
+        maxReconnectBackoffMs_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>int32 max_reconnect_backoff_ms = 1;</code>
+       */
+      public Builder clearMaxReconnectBackoffMs() {
+        
+        maxReconnectBackoffMs_ = 0;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ReconnectParams)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ReconnectParams)
+    private static final io.grpc.benchmarks.proto.Messages.ReconnectParams DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Messages.ReconnectParams();
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.ReconnectParams getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ReconnectParams>
+        PARSER = new com.google.protobuf.AbstractParser<ReconnectParams>() {
+      public ReconnectParams parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ReconnectParams(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ReconnectParams> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ReconnectParams> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Messages.ReconnectParams getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ReconnectInfoOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ReconnectInfo)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>bool passed = 1;</code>
+     */
+    boolean getPassed();
+
+    /**
+     * <code>repeated int32 backoff_ms = 2;</code>
+     */
+    java.util.List<java.lang.Integer> getBackoffMsList();
+    /**
+     * <code>repeated int32 backoff_ms = 2;</code>
+     */
+    int getBackoffMsCount();
+    /**
+     * <code>repeated int32 backoff_ms = 2;</code>
+     */
+    int getBackoffMs(int index);
+  }
+  /**
+   * <pre>
+   * For reconnect interop test only.
+   * Server tells client whether its reconnects are following the spec and the
+   * reconnect backoffs it saw.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.ReconnectInfo}
+   */
+  public  static final class ReconnectInfo extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ReconnectInfo)
+      ReconnectInfoOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ReconnectInfo.newBuilder() to construct.
+    private ReconnectInfo(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ReconnectInfo() {
+      passed_ = false;
+      backoffMs_ = java.util.Collections.emptyList();
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ReconnectInfo(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              passed_ = input.readBool();
+              break;
+            }
+            case 16: {
+              if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+                backoffMs_ = new java.util.ArrayList<java.lang.Integer>();
+                mutable_bitField0_ |= 0x00000002;
+              }
+              backoffMs_.add(input.readInt32());
+              break;
+            }
+            case 18: {
+              int length = input.readRawVarint32();
+              int limit = input.pushLimit(length);
+              if (!((mutable_bitField0_ & 0x00000002) == 0x00000002) && input.getBytesUntilLimit() > 0) {
+                backoffMs_ = new java.util.ArrayList<java.lang.Integer>();
+                mutable_bitField0_ |= 0x00000002;
+              }
+              while (input.getBytesUntilLimit() > 0) {
+                backoffMs_.add(input.readInt32());
+              }
+              input.popLimit(limit);
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+          backoffMs_ = java.util.Collections.unmodifiableList(backoffMs_);
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ReconnectInfo_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ReconnectInfo_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Messages.ReconnectInfo.class, io.grpc.benchmarks.proto.Messages.ReconnectInfo.Builder.class);
+    }
+
+    private int bitField0_;
+    public static final int PASSED_FIELD_NUMBER = 1;
+    private boolean passed_;
+    /**
+     * <code>bool passed = 1;</code>
+     */
+    public boolean getPassed() {
+      return passed_;
+    }
+
+    public static final int BACKOFF_MS_FIELD_NUMBER = 2;
+    private java.util.List<java.lang.Integer> backoffMs_;
+    /**
+     * <code>repeated int32 backoff_ms = 2;</code>
+     */
+    public java.util.List<java.lang.Integer>
+        getBackoffMsList() {
+      return backoffMs_;
+    }
+    /**
+     * <code>repeated int32 backoff_ms = 2;</code>
+     */
+    public int getBackoffMsCount() {
+      return backoffMs_.size();
+    }
+    /**
+     * <code>repeated int32 backoff_ms = 2;</code>
+     */
+    public int getBackoffMs(int index) {
+      return backoffMs_.get(index);
+    }
+    private int backoffMsMemoizedSerializedSize = -1;
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      getSerializedSize();
+      if (passed_ != false) {
+        output.writeBool(1, passed_);
+      }
+      if (getBackoffMsList().size() > 0) {
+        output.writeUInt32NoTag(18);
+        output.writeUInt32NoTag(backoffMsMemoizedSerializedSize);
+      }
+      for (int i = 0; i < backoffMs_.size(); i++) {
+        output.writeInt32NoTag(backoffMs_.get(i));
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (passed_ != false) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBoolSize(1, passed_);
+      }
+      {
+        int dataSize = 0;
+        for (int i = 0; i < backoffMs_.size(); i++) {
+          dataSize += com.google.protobuf.CodedOutputStream
+            .computeInt32SizeNoTag(backoffMs_.get(i));
+        }
+        size += dataSize;
+        if (!getBackoffMsList().isEmpty()) {
+          size += 1;
+          size += com.google.protobuf.CodedOutputStream
+              .computeInt32SizeNoTag(dataSize);
+        }
+        backoffMsMemoizedSerializedSize = dataSize;
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Messages.ReconnectInfo)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Messages.ReconnectInfo other = (io.grpc.benchmarks.proto.Messages.ReconnectInfo) obj;
+
+      boolean result = true;
+      result = result && (getPassed()
+          == other.getPassed());
+      result = result && getBackoffMsList()
+          .equals(other.getBackoffMsList());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + PASSED_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+          getPassed());
+      if (getBackoffMsCount() > 0) {
+        hash = (37 * hash) + BACKOFF_MS_FIELD_NUMBER;
+        hash = (53 * hash) + getBackoffMsList().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.ReconnectInfo parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectInfo parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectInfo parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectInfo parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectInfo parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectInfo parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectInfo parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectInfo parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectInfo parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectInfo parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectInfo parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Messages.ReconnectInfo parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Messages.ReconnectInfo prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * For reconnect interop test only.
+     * Server tells client whether its reconnects are following the spec and the
+     * reconnect backoffs it saw.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.ReconnectInfo}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ReconnectInfo)
+        io.grpc.benchmarks.proto.Messages.ReconnectInfoOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ReconnectInfo_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ReconnectInfo_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Messages.ReconnectInfo.class, io.grpc.benchmarks.proto.Messages.ReconnectInfo.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Messages.ReconnectInfo.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        passed_ = false;
+
+        backoffMs_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000002);
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Messages.internal_static_grpc_testing_ReconnectInfo_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.ReconnectInfo getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Messages.ReconnectInfo.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Messages.ReconnectInfo build() {
+        io.grpc.benchmarks.proto.Messages.ReconnectInfo result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Messages.ReconnectInfo buildPartial() {
+        io.grpc.benchmarks.proto.Messages.ReconnectInfo result = new io.grpc.benchmarks.proto.Messages.ReconnectInfo(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        result.passed_ = passed_;
+        if (((bitField0_ & 0x00000002) == 0x00000002)) {
+          backoffMs_ = java.util.Collections.unmodifiableList(backoffMs_);
+          bitField0_ = (bitField0_ & ~0x00000002);
+        }
+        result.backoffMs_ = backoffMs_;
+        result.bitField0_ = to_bitField0_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Messages.ReconnectInfo) {
+          return mergeFrom((io.grpc.benchmarks.proto.Messages.ReconnectInfo)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Messages.ReconnectInfo other) {
+        if (other == io.grpc.benchmarks.proto.Messages.ReconnectInfo.getDefaultInstance()) return this;
+        if (other.getPassed() != false) {
+          setPassed(other.getPassed());
+        }
+        if (!other.backoffMs_.isEmpty()) {
+          if (backoffMs_.isEmpty()) {
+            backoffMs_ = other.backoffMs_;
+            bitField0_ = (bitField0_ & ~0x00000002);
+          } else {
+            ensureBackoffMsIsMutable();
+            backoffMs_.addAll(other.backoffMs_);
+          }
+          onChanged();
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Messages.ReconnectInfo parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Messages.ReconnectInfo) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private boolean passed_ ;
+      /**
+       * <code>bool passed = 1;</code>
+       */
+      public boolean getPassed() {
+        return passed_;
+      }
+      /**
+       * <code>bool passed = 1;</code>
+       */
+      public Builder setPassed(boolean value) {
+        
+        passed_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>bool passed = 1;</code>
+       */
+      public Builder clearPassed() {
+        
+        passed_ = false;
+        onChanged();
+        return this;
+      }
+
+      private java.util.List<java.lang.Integer> backoffMs_ = java.util.Collections.emptyList();
+      private void ensureBackoffMsIsMutable() {
+        if (!((bitField0_ & 0x00000002) == 0x00000002)) {
+          backoffMs_ = new java.util.ArrayList<java.lang.Integer>(backoffMs_);
+          bitField0_ |= 0x00000002;
+         }
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public java.util.List<java.lang.Integer>
+          getBackoffMsList() {
+        return java.util.Collections.unmodifiableList(backoffMs_);
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public int getBackoffMsCount() {
+        return backoffMs_.size();
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public int getBackoffMs(int index) {
+        return backoffMs_.get(index);
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public Builder setBackoffMs(
+          int index, int value) {
+        ensureBackoffMsIsMutable();
+        backoffMs_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public Builder addBackoffMs(int value) {
+        ensureBackoffMsIsMutable();
+        backoffMs_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public Builder addAllBackoffMs(
+          java.lang.Iterable<? extends java.lang.Integer> values) {
+        ensureBackoffMsIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, backoffMs_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public Builder clearBackoffMs() {
+        backoffMs_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000002);
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ReconnectInfo)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ReconnectInfo)
+    private static final io.grpc.benchmarks.proto.Messages.ReconnectInfo DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Messages.ReconnectInfo();
+    }
+
+    public static io.grpc.benchmarks.proto.Messages.ReconnectInfo getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ReconnectInfo>
+        PARSER = new com.google.protobuf.AbstractParser<ReconnectInfo>() {
+      public ReconnectInfo parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ReconnectInfo(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ReconnectInfo> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ReconnectInfo> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Messages.ReconnectInfo getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_BoolValue_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_BoolValue_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_Payload_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_Payload_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_EchoStatus_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_EchoStatus_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_SimpleRequest_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_SimpleRequest_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_SimpleResponse_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_SimpleResponse_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_StreamingInputCallRequest_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_StreamingInputCallRequest_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_StreamingInputCallResponse_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_StreamingInputCallResponse_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ResponseParameters_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ResponseParameters_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_StreamingOutputCallRequest_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_StreamingOutputCallRequest_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_StreamingOutputCallResponse_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_StreamingOutputCallResponse_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ReconnectParams_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ReconnectParams_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ReconnectInfo_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ReconnectInfo_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\033grpc/testing/messages.proto\022\014grpc.test" +
+      "ing\"\032\n\tBoolValue\022\r\n\005value\030\001 \001(\010\"@\n\007Paylo" +
+      "ad\022\'\n\004type\030\001 \001(\0162\031.grpc.testing.PayloadT" +
+      "ype\022\014\n\004body\030\002 \001(\014\"+\n\nEchoStatus\022\014\n\004code\030" +
+      "\001 \001(\005\022\017\n\007message\030\002 \001(\t\"\316\002\n\rSimpleRequest" +
+      "\0220\n\rresponse_type\030\001 \001(\0162\031.grpc.testing.P" +
+      "ayloadType\022\025\n\rresponse_size\030\002 \001(\005\022&\n\007pay" +
+      "load\030\003 \001(\0132\025.grpc.testing.Payload\022\025\n\rfil" +
+      "l_username\030\004 \001(\010\022\030\n\020fill_oauth_scope\030\005 \001" +
+      "(\010\0224\n\023response_compressed\030\006 \001(\0132\027.grpc.t" +
+      "esting.BoolValue\0221\n\017response_status\030\007 \001(" +
+      "\0132\030.grpc.testing.EchoStatus\0222\n\021expect_co" +
+      "mpressed\030\010 \001(\0132\027.grpc.testing.BoolValue\"" +
+      "_\n\016SimpleResponse\022&\n\007payload\030\001 \001(\0132\025.grp" +
+      "c.testing.Payload\022\020\n\010username\030\002 \001(\t\022\023\n\013o" +
+      "auth_scope\030\003 \001(\t\"w\n\031StreamingInputCallRe" +
+      "quest\022&\n\007payload\030\001 \001(\0132\025.grpc.testing.Pa" +
+      "yload\0222\n\021expect_compressed\030\002 \001(\0132\027.grpc." +
+      "testing.BoolValue\"=\n\032StreamingInputCallR" +
+      "esponse\022\037\n\027aggregated_payload_size\030\001 \001(\005" +
+      "\"d\n\022ResponseParameters\022\014\n\004size\030\001 \001(\005\022\023\n\013" +
+      "interval_us\030\002 \001(\005\022+\n\ncompressed\030\003 \001(\0132\027." +
+      "grpc.testing.BoolValue\"\350\001\n\032StreamingOutp" +
+      "utCallRequest\0220\n\rresponse_type\030\001 \001(\0162\031.g" +
+      "rpc.testing.PayloadType\022=\n\023response_para" +
+      "meters\030\002 \003(\0132 .grpc.testing.ResponsePara" +
+      "meters\022&\n\007payload\030\003 \001(\0132\025.grpc.testing.P" +
+      "ayload\0221\n\017response_status\030\007 \001(\0132\030.grpc.t" +
+      "esting.EchoStatus\"E\n\033StreamingOutputCall" +
+      "Response\022&\n\007payload\030\001 \001(\0132\025.grpc.testing" +
+      ".Payload\"3\n\017ReconnectParams\022 \n\030max_recon" +
+      "nect_backoff_ms\030\001 \001(\005\"3\n\rReconnectInfo\022\016" +
+      "\n\006passed\030\001 \001(\010\022\022\n\nbackoff_ms\030\002 \003(\005*\037\n\013Pa" +
+      "yloadType\022\020\n\014COMPRESSABLE\020\000B$\n\030io.grpc.b" +
+      "enchmarks.protoB\010Messagesb\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        }, assigner);
+    internal_static_grpc_testing_BoolValue_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_testing_BoolValue_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_BoolValue_descriptor,
+        new java.lang.String[] { "Value", });
+    internal_static_grpc_testing_Payload_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_testing_Payload_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_Payload_descriptor,
+        new java.lang.String[] { "Type", "Body", });
+    internal_static_grpc_testing_EchoStatus_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_testing_EchoStatus_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_EchoStatus_descriptor,
+        new java.lang.String[] { "Code", "Message", });
+    internal_static_grpc_testing_SimpleRequest_descriptor =
+      getDescriptor().getMessageTypes().get(3);
+    internal_static_grpc_testing_SimpleRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_SimpleRequest_descriptor,
+        new java.lang.String[] { "ResponseType", "ResponseSize", "Payload", "FillUsername", "FillOauthScope", "ResponseCompressed", "ResponseStatus", "ExpectCompressed", });
+    internal_static_grpc_testing_SimpleResponse_descriptor =
+      getDescriptor().getMessageTypes().get(4);
+    internal_static_grpc_testing_SimpleResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_SimpleResponse_descriptor,
+        new java.lang.String[] { "Payload", "Username", "OauthScope", });
+    internal_static_grpc_testing_StreamingInputCallRequest_descriptor =
+      getDescriptor().getMessageTypes().get(5);
+    internal_static_grpc_testing_StreamingInputCallRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_StreamingInputCallRequest_descriptor,
+        new java.lang.String[] { "Payload", "ExpectCompressed", });
+    internal_static_grpc_testing_StreamingInputCallResponse_descriptor =
+      getDescriptor().getMessageTypes().get(6);
+    internal_static_grpc_testing_StreamingInputCallResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_StreamingInputCallResponse_descriptor,
+        new java.lang.String[] { "AggregatedPayloadSize", });
+    internal_static_grpc_testing_ResponseParameters_descriptor =
+      getDescriptor().getMessageTypes().get(7);
+    internal_static_grpc_testing_ResponseParameters_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ResponseParameters_descriptor,
+        new java.lang.String[] { "Size", "IntervalUs", "Compressed", });
+    internal_static_grpc_testing_StreamingOutputCallRequest_descriptor =
+      getDescriptor().getMessageTypes().get(8);
+    internal_static_grpc_testing_StreamingOutputCallRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_StreamingOutputCallRequest_descriptor,
+        new java.lang.String[] { "ResponseType", "ResponseParameters", "Payload", "ResponseStatus", });
+    internal_static_grpc_testing_StreamingOutputCallResponse_descriptor =
+      getDescriptor().getMessageTypes().get(9);
+    internal_static_grpc_testing_StreamingOutputCallResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_StreamingOutputCallResponse_descriptor,
+        new java.lang.String[] { "Payload", });
+    internal_static_grpc_testing_ReconnectParams_descriptor =
+      getDescriptor().getMessageTypes().get(10);
+    internal_static_grpc_testing_ReconnectParams_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ReconnectParams_descriptor,
+        new java.lang.String[] { "MaxReconnectBackoffMs", });
+    internal_static_grpc_testing_ReconnectInfo_descriptor =
+      getDescriptor().getMessageTypes().get(11);
+    internal_static_grpc_testing_ReconnectInfo_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ReconnectInfo_descriptor,
+        new java.lang.String[] { "Passed", "BackoffMs", });
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Payloads.java b/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Payloads.java
new file mode 100644
index 0000000..0f5d3cd
--- /dev/null
+++ b/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Payloads.java
@@ -0,0 +1,2631 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/testing/payloads.proto
+
+package io.grpc.benchmarks.proto;
+
+public final class Payloads {
+  private Payloads() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  public interface ByteBufferParamsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ByteBufferParams)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>int32 req_size = 1;</code>
+     */
+    int getReqSize();
+
+    /**
+     * <code>int32 resp_size = 2;</code>
+     */
+    int getRespSize();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.ByteBufferParams}
+   */
+  public  static final class ByteBufferParams extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ByteBufferParams)
+      ByteBufferParamsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ByteBufferParams.newBuilder() to construct.
+    private ByteBufferParams(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ByteBufferParams() {
+      reqSize_ = 0;
+      respSize_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ByteBufferParams(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              reqSize_ = input.readInt32();
+              break;
+            }
+            case 16: {
+
+              respSize_ = input.readInt32();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_ByteBufferParams_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_ByteBufferParams_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Payloads.ByteBufferParams.class, io.grpc.benchmarks.proto.Payloads.ByteBufferParams.Builder.class);
+    }
+
+    public static final int REQ_SIZE_FIELD_NUMBER = 1;
+    private int reqSize_;
+    /**
+     * <code>int32 req_size = 1;</code>
+     */
+    public int getReqSize() {
+      return reqSize_;
+    }
+
+    public static final int RESP_SIZE_FIELD_NUMBER = 2;
+    private int respSize_;
+    /**
+     * <code>int32 resp_size = 2;</code>
+     */
+    public int getRespSize() {
+      return respSize_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (reqSize_ != 0) {
+        output.writeInt32(1, reqSize_);
+      }
+      if (respSize_ != 0) {
+        output.writeInt32(2, respSize_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (reqSize_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1, reqSize_);
+      }
+      if (respSize_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(2, respSize_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Payloads.ByteBufferParams)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Payloads.ByteBufferParams other = (io.grpc.benchmarks.proto.Payloads.ByteBufferParams) obj;
+
+      boolean result = true;
+      result = result && (getReqSize()
+          == other.getReqSize());
+      result = result && (getRespSize()
+          == other.getRespSize());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + REQ_SIZE_FIELD_NUMBER;
+      hash = (53 * hash) + getReqSize();
+      hash = (37 * hash) + RESP_SIZE_FIELD_NUMBER;
+      hash = (53 * hash) + getRespSize();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Payloads.ByteBufferParams parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ByteBufferParams parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ByteBufferParams parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ByteBufferParams parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ByteBufferParams parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ByteBufferParams parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ByteBufferParams parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ByteBufferParams parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ByteBufferParams parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ByteBufferParams parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ByteBufferParams parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ByteBufferParams parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Payloads.ByteBufferParams prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.ByteBufferParams}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ByteBufferParams)
+        io.grpc.benchmarks.proto.Payloads.ByteBufferParamsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_ByteBufferParams_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_ByteBufferParams_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Payloads.ByteBufferParams.class, io.grpc.benchmarks.proto.Payloads.ByteBufferParams.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Payloads.ByteBufferParams.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        reqSize_ = 0;
+
+        respSize_ = 0;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_ByteBufferParams_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Payloads.ByteBufferParams getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Payloads.ByteBufferParams.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Payloads.ByteBufferParams build() {
+        io.grpc.benchmarks.proto.Payloads.ByteBufferParams result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Payloads.ByteBufferParams buildPartial() {
+        io.grpc.benchmarks.proto.Payloads.ByteBufferParams result = new io.grpc.benchmarks.proto.Payloads.ByteBufferParams(this);
+        result.reqSize_ = reqSize_;
+        result.respSize_ = respSize_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Payloads.ByteBufferParams) {
+          return mergeFrom((io.grpc.benchmarks.proto.Payloads.ByteBufferParams)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Payloads.ByteBufferParams other) {
+        if (other == io.grpc.benchmarks.proto.Payloads.ByteBufferParams.getDefaultInstance()) return this;
+        if (other.getReqSize() != 0) {
+          setReqSize(other.getReqSize());
+        }
+        if (other.getRespSize() != 0) {
+          setRespSize(other.getRespSize());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Payloads.ByteBufferParams parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Payloads.ByteBufferParams) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int reqSize_ ;
+      /**
+       * <code>int32 req_size = 1;</code>
+       */
+      public int getReqSize() {
+        return reqSize_;
+      }
+      /**
+       * <code>int32 req_size = 1;</code>
+       */
+      public Builder setReqSize(int value) {
+        
+        reqSize_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>int32 req_size = 1;</code>
+       */
+      public Builder clearReqSize() {
+        
+        reqSize_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int respSize_ ;
+      /**
+       * <code>int32 resp_size = 2;</code>
+       */
+      public int getRespSize() {
+        return respSize_;
+      }
+      /**
+       * <code>int32 resp_size = 2;</code>
+       */
+      public Builder setRespSize(int value) {
+        
+        respSize_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>int32 resp_size = 2;</code>
+       */
+      public Builder clearRespSize() {
+        
+        respSize_ = 0;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ByteBufferParams)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ByteBufferParams)
+    private static final io.grpc.benchmarks.proto.Payloads.ByteBufferParams DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Payloads.ByteBufferParams();
+    }
+
+    public static io.grpc.benchmarks.proto.Payloads.ByteBufferParams getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ByteBufferParams>
+        PARSER = new com.google.protobuf.AbstractParser<ByteBufferParams>() {
+      public ByteBufferParams parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ByteBufferParams(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ByteBufferParams> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ByteBufferParams> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Payloads.ByteBufferParams getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface SimpleProtoParamsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.SimpleProtoParams)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>int32 req_size = 1;</code>
+     */
+    int getReqSize();
+
+    /**
+     * <code>int32 resp_size = 2;</code>
+     */
+    int getRespSize();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.SimpleProtoParams}
+   */
+  public  static final class SimpleProtoParams extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.SimpleProtoParams)
+      SimpleProtoParamsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use SimpleProtoParams.newBuilder() to construct.
+    private SimpleProtoParams(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private SimpleProtoParams() {
+      reqSize_ = 0;
+      respSize_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private SimpleProtoParams(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              reqSize_ = input.readInt32();
+              break;
+            }
+            case 16: {
+
+              respSize_ = input.readInt32();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_SimpleProtoParams_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_SimpleProtoParams_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.class, io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.Builder.class);
+    }
+
+    public static final int REQ_SIZE_FIELD_NUMBER = 1;
+    private int reqSize_;
+    /**
+     * <code>int32 req_size = 1;</code>
+     */
+    public int getReqSize() {
+      return reqSize_;
+    }
+
+    public static final int RESP_SIZE_FIELD_NUMBER = 2;
+    private int respSize_;
+    /**
+     * <code>int32 resp_size = 2;</code>
+     */
+    public int getRespSize() {
+      return respSize_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (reqSize_ != 0) {
+        output.writeInt32(1, reqSize_);
+      }
+      if (respSize_ != 0) {
+        output.writeInt32(2, respSize_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (reqSize_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1, reqSize_);
+      }
+      if (respSize_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(2, respSize_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Payloads.SimpleProtoParams)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Payloads.SimpleProtoParams other = (io.grpc.benchmarks.proto.Payloads.SimpleProtoParams) obj;
+
+      boolean result = true;
+      result = result && (getReqSize()
+          == other.getReqSize());
+      result = result && (getRespSize()
+          == other.getRespSize());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + REQ_SIZE_FIELD_NUMBER;
+      hash = (53 * hash) + getReqSize();
+      hash = (37 * hash) + RESP_SIZE_FIELD_NUMBER;
+      hash = (53 * hash) + getRespSize();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Payloads.SimpleProtoParams parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.SimpleProtoParams parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.SimpleProtoParams parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.SimpleProtoParams parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.SimpleProtoParams parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.SimpleProtoParams parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.SimpleProtoParams parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.SimpleProtoParams parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.SimpleProtoParams parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.SimpleProtoParams parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.SimpleProtoParams parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.SimpleProtoParams parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Payloads.SimpleProtoParams prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.SimpleProtoParams}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.SimpleProtoParams)
+        io.grpc.benchmarks.proto.Payloads.SimpleProtoParamsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_SimpleProtoParams_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_SimpleProtoParams_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.class, io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        reqSize_ = 0;
+
+        respSize_ = 0;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_SimpleProtoParams_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Payloads.SimpleProtoParams getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Payloads.SimpleProtoParams build() {
+        io.grpc.benchmarks.proto.Payloads.SimpleProtoParams result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Payloads.SimpleProtoParams buildPartial() {
+        io.grpc.benchmarks.proto.Payloads.SimpleProtoParams result = new io.grpc.benchmarks.proto.Payloads.SimpleProtoParams(this);
+        result.reqSize_ = reqSize_;
+        result.respSize_ = respSize_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Payloads.SimpleProtoParams) {
+          return mergeFrom((io.grpc.benchmarks.proto.Payloads.SimpleProtoParams)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Payloads.SimpleProtoParams other) {
+        if (other == io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.getDefaultInstance()) return this;
+        if (other.getReqSize() != 0) {
+          setReqSize(other.getReqSize());
+        }
+        if (other.getRespSize() != 0) {
+          setRespSize(other.getRespSize());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Payloads.SimpleProtoParams parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Payloads.SimpleProtoParams) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int reqSize_ ;
+      /**
+       * <code>int32 req_size = 1;</code>
+       */
+      public int getReqSize() {
+        return reqSize_;
+      }
+      /**
+       * <code>int32 req_size = 1;</code>
+       */
+      public Builder setReqSize(int value) {
+        
+        reqSize_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>int32 req_size = 1;</code>
+       */
+      public Builder clearReqSize() {
+        
+        reqSize_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int respSize_ ;
+      /**
+       * <code>int32 resp_size = 2;</code>
+       */
+      public int getRespSize() {
+        return respSize_;
+      }
+      /**
+       * <code>int32 resp_size = 2;</code>
+       */
+      public Builder setRespSize(int value) {
+        
+        respSize_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>int32 resp_size = 2;</code>
+       */
+      public Builder clearRespSize() {
+        
+        respSize_ = 0;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.SimpleProtoParams)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.SimpleProtoParams)
+    private static final io.grpc.benchmarks.proto.Payloads.SimpleProtoParams DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Payloads.SimpleProtoParams();
+    }
+
+    public static io.grpc.benchmarks.proto.Payloads.SimpleProtoParams getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<SimpleProtoParams>
+        PARSER = new com.google.protobuf.AbstractParser<SimpleProtoParams>() {
+      public SimpleProtoParams parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new SimpleProtoParams(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<SimpleProtoParams> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<SimpleProtoParams> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Payloads.SimpleProtoParams getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ComplexProtoParamsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ComplexProtoParams)
+      com.google.protobuf.MessageOrBuilder {
+  }
+  /**
+   * <pre>
+   * TODO (vpai): Fill this in once the details of complex, representative
+   *              protos are decided
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.ComplexProtoParams}
+   */
+  public  static final class ComplexProtoParams extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ComplexProtoParams)
+      ComplexProtoParamsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ComplexProtoParams.newBuilder() to construct.
+    private ComplexProtoParams(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ComplexProtoParams() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ComplexProtoParams(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_ComplexProtoParams_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_ComplexProtoParams_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.class, io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.Builder.class);
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Payloads.ComplexProtoParams)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Payloads.ComplexProtoParams other = (io.grpc.benchmarks.proto.Payloads.ComplexProtoParams) obj;
+
+      boolean result = true;
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Payloads.ComplexProtoParams parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ComplexProtoParams parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ComplexProtoParams parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ComplexProtoParams parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ComplexProtoParams parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ComplexProtoParams parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ComplexProtoParams parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ComplexProtoParams parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ComplexProtoParams parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ComplexProtoParams parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ComplexProtoParams parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.ComplexProtoParams parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Payloads.ComplexProtoParams prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * TODO (vpai): Fill this in once the details of complex, representative
+     *              protos are decided
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.ComplexProtoParams}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ComplexProtoParams)
+        io.grpc.benchmarks.proto.Payloads.ComplexProtoParamsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_ComplexProtoParams_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_ComplexProtoParams_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.class, io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_ComplexProtoParams_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Payloads.ComplexProtoParams getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Payloads.ComplexProtoParams build() {
+        io.grpc.benchmarks.proto.Payloads.ComplexProtoParams result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Payloads.ComplexProtoParams buildPartial() {
+        io.grpc.benchmarks.proto.Payloads.ComplexProtoParams result = new io.grpc.benchmarks.proto.Payloads.ComplexProtoParams(this);
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Payloads.ComplexProtoParams) {
+          return mergeFrom((io.grpc.benchmarks.proto.Payloads.ComplexProtoParams)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Payloads.ComplexProtoParams other) {
+        if (other == io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.getDefaultInstance()) return this;
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Payloads.ComplexProtoParams parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Payloads.ComplexProtoParams) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ComplexProtoParams)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ComplexProtoParams)
+    private static final io.grpc.benchmarks.proto.Payloads.ComplexProtoParams DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Payloads.ComplexProtoParams();
+    }
+
+    public static io.grpc.benchmarks.proto.Payloads.ComplexProtoParams getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ComplexProtoParams>
+        PARSER = new com.google.protobuf.AbstractParser<ComplexProtoParams>() {
+      public ComplexProtoParams parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ComplexProtoParams(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ComplexProtoParams> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ComplexProtoParams> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Payloads.ComplexProtoParams getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface PayloadConfigOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.PayloadConfig)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+     */
+    boolean hasBytebufParams();
+    /**
+     * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Payloads.ByteBufferParams getBytebufParams();
+    /**
+     * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Payloads.ByteBufferParamsOrBuilder getBytebufParamsOrBuilder();
+
+    /**
+     * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+     */
+    boolean hasSimpleParams();
+    /**
+     * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Payloads.SimpleProtoParams getSimpleParams();
+    /**
+     * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+     */
+    io.grpc.benchmarks.proto.Payloads.SimpleProtoParamsOrBuilder getSimpleParamsOrBuilder();
+
+    /**
+     * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+     */
+    boolean hasComplexParams();
+    /**
+     * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+     */
+    io.grpc.benchmarks.proto.Payloads.ComplexProtoParams getComplexParams();
+    /**
+     * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+     */
+    io.grpc.benchmarks.proto.Payloads.ComplexProtoParamsOrBuilder getComplexParamsOrBuilder();
+
+    public io.grpc.benchmarks.proto.Payloads.PayloadConfig.PayloadCase getPayloadCase();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.PayloadConfig}
+   */
+  public  static final class PayloadConfig extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.PayloadConfig)
+      PayloadConfigOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use PayloadConfig.newBuilder() to construct.
+    private PayloadConfig(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private PayloadConfig() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private PayloadConfig(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.benchmarks.proto.Payloads.ByteBufferParams.Builder subBuilder = null;
+              if (payloadCase_ == 1) {
+                subBuilder = ((io.grpc.benchmarks.proto.Payloads.ByteBufferParams) payload_).toBuilder();
+              }
+              payload_ =
+                  input.readMessage(io.grpc.benchmarks.proto.Payloads.ByteBufferParams.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom((io.grpc.benchmarks.proto.Payloads.ByteBufferParams) payload_);
+                payload_ = subBuilder.buildPartial();
+              }
+              payloadCase_ = 1;
+              break;
+            }
+            case 18: {
+              io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.Builder subBuilder = null;
+              if (payloadCase_ == 2) {
+                subBuilder = ((io.grpc.benchmarks.proto.Payloads.SimpleProtoParams) payload_).toBuilder();
+              }
+              payload_ =
+                  input.readMessage(io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom((io.grpc.benchmarks.proto.Payloads.SimpleProtoParams) payload_);
+                payload_ = subBuilder.buildPartial();
+              }
+              payloadCase_ = 2;
+              break;
+            }
+            case 26: {
+              io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.Builder subBuilder = null;
+              if (payloadCase_ == 3) {
+                subBuilder = ((io.grpc.benchmarks.proto.Payloads.ComplexProtoParams) payload_).toBuilder();
+              }
+              payload_ =
+                  input.readMessage(io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom((io.grpc.benchmarks.proto.Payloads.ComplexProtoParams) payload_);
+                payload_ = subBuilder.buildPartial();
+              }
+              payloadCase_ = 3;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_PayloadConfig_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_PayloadConfig_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Payloads.PayloadConfig.class, io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder.class);
+    }
+
+    private int payloadCase_ = 0;
+    private java.lang.Object payload_;
+    public enum PayloadCase
+        implements com.google.protobuf.Internal.EnumLite {
+      BYTEBUF_PARAMS(1),
+      SIMPLE_PARAMS(2),
+      COMPLEX_PARAMS(3),
+      PAYLOAD_NOT_SET(0);
+      private final int value;
+      private PayloadCase(int value) {
+        this.value = value;
+      }
+      /**
+       * @deprecated Use {@link #forNumber(int)} instead.
+       */
+      @java.lang.Deprecated
+      public static PayloadCase valueOf(int value) {
+        return forNumber(value);
+      }
+
+      public static PayloadCase forNumber(int value) {
+        switch (value) {
+          case 1: return BYTEBUF_PARAMS;
+          case 2: return SIMPLE_PARAMS;
+          case 3: return COMPLEX_PARAMS;
+          case 0: return PAYLOAD_NOT_SET;
+          default: return null;
+        }
+      }
+      public int getNumber() {
+        return this.value;
+      }
+    };
+
+    public PayloadCase
+    getPayloadCase() {
+      return PayloadCase.forNumber(
+          payloadCase_);
+    }
+
+    public static final int BYTEBUF_PARAMS_FIELD_NUMBER = 1;
+    /**
+     * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+     */
+    public boolean hasBytebufParams() {
+      return payloadCase_ == 1;
+    }
+    /**
+     * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Payloads.ByteBufferParams getBytebufParams() {
+      if (payloadCase_ == 1) {
+         return (io.grpc.benchmarks.proto.Payloads.ByteBufferParams) payload_;
+      }
+      return io.grpc.benchmarks.proto.Payloads.ByteBufferParams.getDefaultInstance();
+    }
+    /**
+     * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Payloads.ByteBufferParamsOrBuilder getBytebufParamsOrBuilder() {
+      if (payloadCase_ == 1) {
+         return (io.grpc.benchmarks.proto.Payloads.ByteBufferParams) payload_;
+      }
+      return io.grpc.benchmarks.proto.Payloads.ByteBufferParams.getDefaultInstance();
+    }
+
+    public static final int SIMPLE_PARAMS_FIELD_NUMBER = 2;
+    /**
+     * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+     */
+    public boolean hasSimpleParams() {
+      return payloadCase_ == 2;
+    }
+    /**
+     * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Payloads.SimpleProtoParams getSimpleParams() {
+      if (payloadCase_ == 2) {
+         return (io.grpc.benchmarks.proto.Payloads.SimpleProtoParams) payload_;
+      }
+      return io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.getDefaultInstance();
+    }
+    /**
+     * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+     */
+    public io.grpc.benchmarks.proto.Payloads.SimpleProtoParamsOrBuilder getSimpleParamsOrBuilder() {
+      if (payloadCase_ == 2) {
+         return (io.grpc.benchmarks.proto.Payloads.SimpleProtoParams) payload_;
+      }
+      return io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.getDefaultInstance();
+    }
+
+    public static final int COMPLEX_PARAMS_FIELD_NUMBER = 3;
+    /**
+     * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+     */
+    public boolean hasComplexParams() {
+      return payloadCase_ == 3;
+    }
+    /**
+     * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+     */
+    public io.grpc.benchmarks.proto.Payloads.ComplexProtoParams getComplexParams() {
+      if (payloadCase_ == 3) {
+         return (io.grpc.benchmarks.proto.Payloads.ComplexProtoParams) payload_;
+      }
+      return io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.getDefaultInstance();
+    }
+    /**
+     * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+     */
+    public io.grpc.benchmarks.proto.Payloads.ComplexProtoParamsOrBuilder getComplexParamsOrBuilder() {
+      if (payloadCase_ == 3) {
+         return (io.grpc.benchmarks.proto.Payloads.ComplexProtoParams) payload_;
+      }
+      return io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.getDefaultInstance();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (payloadCase_ == 1) {
+        output.writeMessage(1, (io.grpc.benchmarks.proto.Payloads.ByteBufferParams) payload_);
+      }
+      if (payloadCase_ == 2) {
+        output.writeMessage(2, (io.grpc.benchmarks.proto.Payloads.SimpleProtoParams) payload_);
+      }
+      if (payloadCase_ == 3) {
+        output.writeMessage(3, (io.grpc.benchmarks.proto.Payloads.ComplexProtoParams) payload_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (payloadCase_ == 1) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, (io.grpc.benchmarks.proto.Payloads.ByteBufferParams) payload_);
+      }
+      if (payloadCase_ == 2) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, (io.grpc.benchmarks.proto.Payloads.SimpleProtoParams) payload_);
+      }
+      if (payloadCase_ == 3) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(3, (io.grpc.benchmarks.proto.Payloads.ComplexProtoParams) payload_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Payloads.PayloadConfig)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Payloads.PayloadConfig other = (io.grpc.benchmarks.proto.Payloads.PayloadConfig) obj;
+
+      boolean result = true;
+      result = result && getPayloadCase().equals(
+          other.getPayloadCase());
+      if (!result) return false;
+      switch (payloadCase_) {
+        case 1:
+          result = result && getBytebufParams()
+              .equals(other.getBytebufParams());
+          break;
+        case 2:
+          result = result && getSimpleParams()
+              .equals(other.getSimpleParams());
+          break;
+        case 3:
+          result = result && getComplexParams()
+              .equals(other.getComplexParams());
+          break;
+        case 0:
+        default:
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      switch (payloadCase_) {
+        case 1:
+          hash = (37 * hash) + BYTEBUF_PARAMS_FIELD_NUMBER;
+          hash = (53 * hash) + getBytebufParams().hashCode();
+          break;
+        case 2:
+          hash = (37 * hash) + SIMPLE_PARAMS_FIELD_NUMBER;
+          hash = (53 * hash) + getSimpleParams().hashCode();
+          break;
+        case 3:
+          hash = (37 * hash) + COMPLEX_PARAMS_FIELD_NUMBER;
+          hash = (53 * hash) + getComplexParams().hashCode();
+          break;
+        case 0:
+        default:
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Payloads.PayloadConfig parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.PayloadConfig parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.PayloadConfig parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.PayloadConfig parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.PayloadConfig parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.PayloadConfig parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.PayloadConfig parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.PayloadConfig parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.PayloadConfig parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.PayloadConfig parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.PayloadConfig parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Payloads.PayloadConfig parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Payloads.PayloadConfig prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.PayloadConfig}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.PayloadConfig)
+        io.grpc.benchmarks.proto.Payloads.PayloadConfigOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_PayloadConfig_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_PayloadConfig_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Payloads.PayloadConfig.class, io.grpc.benchmarks.proto.Payloads.PayloadConfig.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Payloads.PayloadConfig.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        payloadCase_ = 0;
+        payload_ = null;
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Payloads.internal_static_grpc_testing_PayloadConfig_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Payloads.PayloadConfig getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Payloads.PayloadConfig.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Payloads.PayloadConfig build() {
+        io.grpc.benchmarks.proto.Payloads.PayloadConfig result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Payloads.PayloadConfig buildPartial() {
+        io.grpc.benchmarks.proto.Payloads.PayloadConfig result = new io.grpc.benchmarks.proto.Payloads.PayloadConfig(this);
+        if (payloadCase_ == 1) {
+          if (bytebufParamsBuilder_ == null) {
+            result.payload_ = payload_;
+          } else {
+            result.payload_ = bytebufParamsBuilder_.build();
+          }
+        }
+        if (payloadCase_ == 2) {
+          if (simpleParamsBuilder_ == null) {
+            result.payload_ = payload_;
+          } else {
+            result.payload_ = simpleParamsBuilder_.build();
+          }
+        }
+        if (payloadCase_ == 3) {
+          if (complexParamsBuilder_ == null) {
+            result.payload_ = payload_;
+          } else {
+            result.payload_ = complexParamsBuilder_.build();
+          }
+        }
+        result.payloadCase_ = payloadCase_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Payloads.PayloadConfig) {
+          return mergeFrom((io.grpc.benchmarks.proto.Payloads.PayloadConfig)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Payloads.PayloadConfig other) {
+        if (other == io.grpc.benchmarks.proto.Payloads.PayloadConfig.getDefaultInstance()) return this;
+        switch (other.getPayloadCase()) {
+          case BYTEBUF_PARAMS: {
+            mergeBytebufParams(other.getBytebufParams());
+            break;
+          }
+          case SIMPLE_PARAMS: {
+            mergeSimpleParams(other.getSimpleParams());
+            break;
+          }
+          case COMPLEX_PARAMS: {
+            mergeComplexParams(other.getComplexParams());
+            break;
+          }
+          case PAYLOAD_NOT_SET: {
+            break;
+          }
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Payloads.PayloadConfig parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Payloads.PayloadConfig) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int payloadCase_ = 0;
+      private java.lang.Object payload_;
+      public PayloadCase
+          getPayloadCase() {
+        return PayloadCase.forNumber(
+            payloadCase_);
+      }
+
+      public Builder clearPayload() {
+        payloadCase_ = 0;
+        payload_ = null;
+        onChanged();
+        return this;
+      }
+
+
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Payloads.ByteBufferParams, io.grpc.benchmarks.proto.Payloads.ByteBufferParams.Builder, io.grpc.benchmarks.proto.Payloads.ByteBufferParamsOrBuilder> bytebufParamsBuilder_;
+      /**
+       * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+       */
+      public boolean hasBytebufParams() {
+        return payloadCase_ == 1;
+      }
+      /**
+       * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.ByteBufferParams getBytebufParams() {
+        if (bytebufParamsBuilder_ == null) {
+          if (payloadCase_ == 1) {
+            return (io.grpc.benchmarks.proto.Payloads.ByteBufferParams) payload_;
+          }
+          return io.grpc.benchmarks.proto.Payloads.ByteBufferParams.getDefaultInstance();
+        } else {
+          if (payloadCase_ == 1) {
+            return bytebufParamsBuilder_.getMessage();
+          }
+          return io.grpc.benchmarks.proto.Payloads.ByteBufferParams.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+       */
+      public Builder setBytebufParams(io.grpc.benchmarks.proto.Payloads.ByteBufferParams value) {
+        if (bytebufParamsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payload_ = value;
+          onChanged();
+        } else {
+          bytebufParamsBuilder_.setMessage(value);
+        }
+        payloadCase_ = 1;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+       */
+      public Builder setBytebufParams(
+          io.grpc.benchmarks.proto.Payloads.ByteBufferParams.Builder builderForValue) {
+        if (bytebufParamsBuilder_ == null) {
+          payload_ = builderForValue.build();
+          onChanged();
+        } else {
+          bytebufParamsBuilder_.setMessage(builderForValue.build());
+        }
+        payloadCase_ = 1;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+       */
+      public Builder mergeBytebufParams(io.grpc.benchmarks.proto.Payloads.ByteBufferParams value) {
+        if (bytebufParamsBuilder_ == null) {
+          if (payloadCase_ == 1 &&
+              payload_ != io.grpc.benchmarks.proto.Payloads.ByteBufferParams.getDefaultInstance()) {
+            payload_ = io.grpc.benchmarks.proto.Payloads.ByteBufferParams.newBuilder((io.grpc.benchmarks.proto.Payloads.ByteBufferParams) payload_)
+                .mergeFrom(value).buildPartial();
+          } else {
+            payload_ = value;
+          }
+          onChanged();
+        } else {
+          if (payloadCase_ == 1) {
+            bytebufParamsBuilder_.mergeFrom(value);
+          }
+          bytebufParamsBuilder_.setMessage(value);
+        }
+        payloadCase_ = 1;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+       */
+      public Builder clearBytebufParams() {
+        if (bytebufParamsBuilder_ == null) {
+          if (payloadCase_ == 1) {
+            payloadCase_ = 0;
+            payload_ = null;
+            onChanged();
+          }
+        } else {
+          if (payloadCase_ == 1) {
+            payloadCase_ = 0;
+            payload_ = null;
+          }
+          bytebufParamsBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.ByteBufferParams.Builder getBytebufParamsBuilder() {
+        return getBytebufParamsFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.ByteBufferParamsOrBuilder getBytebufParamsOrBuilder() {
+        if ((payloadCase_ == 1) && (bytebufParamsBuilder_ != null)) {
+          return bytebufParamsBuilder_.getMessageOrBuilder();
+        } else {
+          if (payloadCase_ == 1) {
+            return (io.grpc.benchmarks.proto.Payloads.ByteBufferParams) payload_;
+          }
+          return io.grpc.benchmarks.proto.Payloads.ByteBufferParams.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.ByteBufferParams bytebuf_params = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Payloads.ByteBufferParams, io.grpc.benchmarks.proto.Payloads.ByteBufferParams.Builder, io.grpc.benchmarks.proto.Payloads.ByteBufferParamsOrBuilder> 
+          getBytebufParamsFieldBuilder() {
+        if (bytebufParamsBuilder_ == null) {
+          if (!(payloadCase_ == 1)) {
+            payload_ = io.grpc.benchmarks.proto.Payloads.ByteBufferParams.getDefaultInstance();
+          }
+          bytebufParamsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Payloads.ByteBufferParams, io.grpc.benchmarks.proto.Payloads.ByteBufferParams.Builder, io.grpc.benchmarks.proto.Payloads.ByteBufferParamsOrBuilder>(
+                  (io.grpc.benchmarks.proto.Payloads.ByteBufferParams) payload_,
+                  getParentForChildren(),
+                  isClean());
+          payload_ = null;
+        }
+        payloadCase_ = 1;
+        onChanged();;
+        return bytebufParamsBuilder_;
+      }
+
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Payloads.SimpleProtoParams, io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.Builder, io.grpc.benchmarks.proto.Payloads.SimpleProtoParamsOrBuilder> simpleParamsBuilder_;
+      /**
+       * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+       */
+      public boolean hasSimpleParams() {
+        return payloadCase_ == 2;
+      }
+      /**
+       * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.SimpleProtoParams getSimpleParams() {
+        if (simpleParamsBuilder_ == null) {
+          if (payloadCase_ == 2) {
+            return (io.grpc.benchmarks.proto.Payloads.SimpleProtoParams) payload_;
+          }
+          return io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.getDefaultInstance();
+        } else {
+          if (payloadCase_ == 2) {
+            return simpleParamsBuilder_.getMessage();
+          }
+          return io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+       */
+      public Builder setSimpleParams(io.grpc.benchmarks.proto.Payloads.SimpleProtoParams value) {
+        if (simpleParamsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payload_ = value;
+          onChanged();
+        } else {
+          simpleParamsBuilder_.setMessage(value);
+        }
+        payloadCase_ = 2;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+       */
+      public Builder setSimpleParams(
+          io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.Builder builderForValue) {
+        if (simpleParamsBuilder_ == null) {
+          payload_ = builderForValue.build();
+          onChanged();
+        } else {
+          simpleParamsBuilder_.setMessage(builderForValue.build());
+        }
+        payloadCase_ = 2;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+       */
+      public Builder mergeSimpleParams(io.grpc.benchmarks.proto.Payloads.SimpleProtoParams value) {
+        if (simpleParamsBuilder_ == null) {
+          if (payloadCase_ == 2 &&
+              payload_ != io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.getDefaultInstance()) {
+            payload_ = io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.newBuilder((io.grpc.benchmarks.proto.Payloads.SimpleProtoParams) payload_)
+                .mergeFrom(value).buildPartial();
+          } else {
+            payload_ = value;
+          }
+          onChanged();
+        } else {
+          if (payloadCase_ == 2) {
+            simpleParamsBuilder_.mergeFrom(value);
+          }
+          simpleParamsBuilder_.setMessage(value);
+        }
+        payloadCase_ = 2;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+       */
+      public Builder clearSimpleParams() {
+        if (simpleParamsBuilder_ == null) {
+          if (payloadCase_ == 2) {
+            payloadCase_ = 0;
+            payload_ = null;
+            onChanged();
+          }
+        } else {
+          if (payloadCase_ == 2) {
+            payloadCase_ = 0;
+            payload_ = null;
+          }
+          simpleParamsBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.Builder getSimpleParamsBuilder() {
+        return getSimpleParamsFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.SimpleProtoParamsOrBuilder getSimpleParamsOrBuilder() {
+        if ((payloadCase_ == 2) && (simpleParamsBuilder_ != null)) {
+          return simpleParamsBuilder_.getMessageOrBuilder();
+        } else {
+          if (payloadCase_ == 2) {
+            return (io.grpc.benchmarks.proto.Payloads.SimpleProtoParams) payload_;
+          }
+          return io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.SimpleProtoParams simple_params = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Payloads.SimpleProtoParams, io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.Builder, io.grpc.benchmarks.proto.Payloads.SimpleProtoParamsOrBuilder> 
+          getSimpleParamsFieldBuilder() {
+        if (simpleParamsBuilder_ == null) {
+          if (!(payloadCase_ == 2)) {
+            payload_ = io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.getDefaultInstance();
+          }
+          simpleParamsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Payloads.SimpleProtoParams, io.grpc.benchmarks.proto.Payloads.SimpleProtoParams.Builder, io.grpc.benchmarks.proto.Payloads.SimpleProtoParamsOrBuilder>(
+                  (io.grpc.benchmarks.proto.Payloads.SimpleProtoParams) payload_,
+                  getParentForChildren(),
+                  isClean());
+          payload_ = null;
+        }
+        payloadCase_ = 2;
+        onChanged();;
+        return simpleParamsBuilder_;
+      }
+
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Payloads.ComplexProtoParams, io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.Builder, io.grpc.benchmarks.proto.Payloads.ComplexProtoParamsOrBuilder> complexParamsBuilder_;
+      /**
+       * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+       */
+      public boolean hasComplexParams() {
+        return payloadCase_ == 3;
+      }
+      /**
+       * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.ComplexProtoParams getComplexParams() {
+        if (complexParamsBuilder_ == null) {
+          if (payloadCase_ == 3) {
+            return (io.grpc.benchmarks.proto.Payloads.ComplexProtoParams) payload_;
+          }
+          return io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.getDefaultInstance();
+        } else {
+          if (payloadCase_ == 3) {
+            return complexParamsBuilder_.getMessage();
+          }
+          return io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+       */
+      public Builder setComplexParams(io.grpc.benchmarks.proto.Payloads.ComplexProtoParams value) {
+        if (complexParamsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payload_ = value;
+          onChanged();
+        } else {
+          complexParamsBuilder_.setMessage(value);
+        }
+        payloadCase_ = 3;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+       */
+      public Builder setComplexParams(
+          io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.Builder builderForValue) {
+        if (complexParamsBuilder_ == null) {
+          payload_ = builderForValue.build();
+          onChanged();
+        } else {
+          complexParamsBuilder_.setMessage(builderForValue.build());
+        }
+        payloadCase_ = 3;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+       */
+      public Builder mergeComplexParams(io.grpc.benchmarks.proto.Payloads.ComplexProtoParams value) {
+        if (complexParamsBuilder_ == null) {
+          if (payloadCase_ == 3 &&
+              payload_ != io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.getDefaultInstance()) {
+            payload_ = io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.newBuilder((io.grpc.benchmarks.proto.Payloads.ComplexProtoParams) payload_)
+                .mergeFrom(value).buildPartial();
+          } else {
+            payload_ = value;
+          }
+          onChanged();
+        } else {
+          if (payloadCase_ == 3) {
+            complexParamsBuilder_.mergeFrom(value);
+          }
+          complexParamsBuilder_.setMessage(value);
+        }
+        payloadCase_ = 3;
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+       */
+      public Builder clearComplexParams() {
+        if (complexParamsBuilder_ == null) {
+          if (payloadCase_ == 3) {
+            payloadCase_ = 0;
+            payload_ = null;
+            onChanged();
+          }
+        } else {
+          if (payloadCase_ == 3) {
+            payloadCase_ = 0;
+            payload_ = null;
+          }
+          complexParamsBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.Builder getComplexParamsBuilder() {
+        return getComplexParamsFieldBuilder().getBuilder();
+      }
+      /**
+       * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+       */
+      public io.grpc.benchmarks.proto.Payloads.ComplexProtoParamsOrBuilder getComplexParamsOrBuilder() {
+        if ((payloadCase_ == 3) && (complexParamsBuilder_ != null)) {
+          return complexParamsBuilder_.getMessageOrBuilder();
+        } else {
+          if (payloadCase_ == 3) {
+            return (io.grpc.benchmarks.proto.Payloads.ComplexProtoParams) payload_;
+          }
+          return io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.getDefaultInstance();
+        }
+      }
+      /**
+       * <code>.grpc.testing.ComplexProtoParams complex_params = 3;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Payloads.ComplexProtoParams, io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.Builder, io.grpc.benchmarks.proto.Payloads.ComplexProtoParamsOrBuilder> 
+          getComplexParamsFieldBuilder() {
+        if (complexParamsBuilder_ == null) {
+          if (!(payloadCase_ == 3)) {
+            payload_ = io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.getDefaultInstance();
+          }
+          complexParamsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Payloads.ComplexProtoParams, io.grpc.benchmarks.proto.Payloads.ComplexProtoParams.Builder, io.grpc.benchmarks.proto.Payloads.ComplexProtoParamsOrBuilder>(
+                  (io.grpc.benchmarks.proto.Payloads.ComplexProtoParams) payload_,
+                  getParentForChildren(),
+                  isClean());
+          payload_ = null;
+        }
+        payloadCase_ = 3;
+        onChanged();;
+        return complexParamsBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.PayloadConfig)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.PayloadConfig)
+    private static final io.grpc.benchmarks.proto.Payloads.PayloadConfig DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Payloads.PayloadConfig();
+    }
+
+    public static io.grpc.benchmarks.proto.Payloads.PayloadConfig getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<PayloadConfig>
+        PARSER = new com.google.protobuf.AbstractParser<PayloadConfig>() {
+      public PayloadConfig parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new PayloadConfig(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<PayloadConfig> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<PayloadConfig> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Payloads.PayloadConfig getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ByteBufferParams_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ByteBufferParams_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_SimpleProtoParams_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_SimpleProtoParams_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ComplexProtoParams_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ComplexProtoParams_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_PayloadConfig_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_PayloadConfig_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\033grpc/testing/payloads.proto\022\014grpc.test" +
+      "ing\"7\n\020ByteBufferParams\022\020\n\010req_size\030\001 \001(" +
+      "\005\022\021\n\tresp_size\030\002 \001(\005\"8\n\021SimpleProtoParam" +
+      "s\022\020\n\010req_size\030\001 \001(\005\022\021\n\tresp_size\030\002 \001(\005\"\024" +
+      "\n\022ComplexProtoParams\"\312\001\n\rPayloadConfig\0228" +
+      "\n\016bytebuf_params\030\001 \001(\0132\036.grpc.testing.By" +
+      "teBufferParamsH\000\0228\n\rsimple_params\030\002 \001(\0132" +
+      "\037.grpc.testing.SimpleProtoParamsH\000\022:\n\016co" +
+      "mplex_params\030\003 \001(\0132 .grpc.testing.Comple" +
+      "xProtoParamsH\000B\t\n\007payloadB$\n\030io.grpc.ben" +
+      "chmarks.protoB\010Payloadsb\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        }, assigner);
+    internal_static_grpc_testing_ByteBufferParams_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_testing_ByteBufferParams_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ByteBufferParams_descriptor,
+        new java.lang.String[] { "ReqSize", "RespSize", });
+    internal_static_grpc_testing_SimpleProtoParams_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_testing_SimpleProtoParams_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_SimpleProtoParams_descriptor,
+        new java.lang.String[] { "ReqSize", "RespSize", });
+    internal_static_grpc_testing_ComplexProtoParams_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_testing_ComplexProtoParams_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ComplexProtoParams_descriptor,
+        new java.lang.String[] { });
+    internal_static_grpc_testing_PayloadConfig_descriptor =
+      getDescriptor().getMessageTypes().get(3);
+    internal_static_grpc_testing_PayloadConfig_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_PayloadConfig_descriptor,
+        new java.lang.String[] { "BytebufParams", "SimpleParams", "ComplexParams", "Payload", });
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Services.java b/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Services.java
new file mode 100644
index 0000000..c076e9a
--- /dev/null
+++ b/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Services.java
@@ -0,0 +1,70 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/testing/services.proto
+
+package io.grpc.benchmarks.proto;
+
+public final class Services {
+  private Services() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\033grpc/testing/services.proto\022\014grpc.test" +
+      "ing\032\033grpc/testing/messages.proto\032\032grpc/t" +
+      "esting/control.proto2\246\003\n\020BenchmarkServic" +
+      "e\022F\n\tUnaryCall\022\033.grpc.testing.SimpleRequ" +
+      "est\032\034.grpc.testing.SimpleResponse\022N\n\rStr" +
+      "eamingCall\022\033.grpc.testing.SimpleRequest\032" +
+      "\034.grpc.testing.SimpleResponse(\0010\001\022R\n\023Str" +
+      "eamingFromClient\022\033.grpc.testing.SimpleRe" +
+      "quest\032\034.grpc.testing.SimpleResponse(\001\022R\n" +
+      "\023StreamingFromServer\022\033.grpc.testing.Simp" +
+      "leRequest\032\034.grpc.testing.SimpleResponse0" +
+      "\001\022R\n\021StreamingBothWays\022\033.grpc.testing.Si" +
+      "mpleRequest\032\034.grpc.testing.SimpleRespons" +
+      "e(\0010\0012\227\002\n\rWorkerService\022E\n\tRunServer\022\030.g" +
+      "rpc.testing.ServerArgs\032\032.grpc.testing.Se" +
+      "rverStatus(\0010\001\022E\n\tRunClient\022\030.grpc.testi" +
+      "ng.ClientArgs\032\032.grpc.testing.ClientStatu" +
+      "s(\0010\001\022B\n\tCoreCount\022\031.grpc.testing.CoreRe" +
+      "quest\032\032.grpc.testing.CoreResponse\0224\n\nQui" +
+      "tWorker\022\022.grpc.testing.Void\032\022.grpc.testi" +
+      "ng.Void2^\n\030ReportQpsScenarioService\022B\n\016R" +
+      "eportScenario\022\034.grpc.testing.ScenarioRes" +
+      "ult\032\022.grpc.testing.VoidB$\n\030io.grpc.bench" +
+      "marks.protoB\010Servicesb\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          io.grpc.benchmarks.proto.Messages.getDescriptor(),
+          io.grpc.benchmarks.proto.Control.getDescriptor(),
+        }, assigner);
+    io.grpc.benchmarks.proto.Messages.getDescriptor();
+    io.grpc.benchmarks.proto.Control.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Stats.java b/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Stats.java
new file mode 100644
index 0000000..2a71076
--- /dev/null
+++ b/benchmarks/src/generated/main/java/io/grpc/benchmarks/proto/Stats.java
@@ -0,0 +1,4431 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/testing/stats.proto
+
+package io.grpc.benchmarks.proto;
+
+public final class Stats {
+  private Stats() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  public interface ServerStatsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ServerStats)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * wall clock time change in seconds since last reset
+     * </pre>
+     *
+     * <code>double time_elapsed = 1;</code>
+     */
+    double getTimeElapsed();
+
+    /**
+     * <pre>
+     * change in user time (in seconds) used by the server since last reset
+     * </pre>
+     *
+     * <code>double time_user = 2;</code>
+     */
+    double getTimeUser();
+
+    /**
+     * <pre>
+     * change in server time (in seconds) used by the server process and all
+     * threads since last reset
+     * </pre>
+     *
+     * <code>double time_system = 3;</code>
+     */
+    double getTimeSystem();
+
+    /**
+     * <pre>
+     * change in total cpu time of the server (data from proc/stat)
+     * </pre>
+     *
+     * <code>uint64 total_cpu_time = 4;</code>
+     */
+    long getTotalCpuTime();
+
+    /**
+     * <pre>
+     * change in idle time of the server (data from proc/stat)
+     * </pre>
+     *
+     * <code>uint64 idle_cpu_time = 5;</code>
+     */
+    long getIdleCpuTime();
+
+    /**
+     * <pre>
+     * Number of polls called inside completion queue
+     * </pre>
+     *
+     * <code>uint64 cq_poll_count = 6;</code>
+     */
+    long getCqPollCount();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.ServerStats}
+   */
+  public  static final class ServerStats extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ServerStats)
+      ServerStatsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ServerStats.newBuilder() to construct.
+    private ServerStats(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ServerStats() {
+      timeElapsed_ = 0D;
+      timeUser_ = 0D;
+      timeSystem_ = 0D;
+      totalCpuTime_ = 0L;
+      idleCpuTime_ = 0L;
+      cqPollCount_ = 0L;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ServerStats(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 9: {
+
+              timeElapsed_ = input.readDouble();
+              break;
+            }
+            case 17: {
+
+              timeUser_ = input.readDouble();
+              break;
+            }
+            case 25: {
+
+              timeSystem_ = input.readDouble();
+              break;
+            }
+            case 32: {
+
+              totalCpuTime_ = input.readUInt64();
+              break;
+            }
+            case 40: {
+
+              idleCpuTime_ = input.readUInt64();
+              break;
+            }
+            case 48: {
+
+              cqPollCount_ = input.readUInt64();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_ServerStats_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_ServerStats_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Stats.ServerStats.class, io.grpc.benchmarks.proto.Stats.ServerStats.Builder.class);
+    }
+
+    public static final int TIME_ELAPSED_FIELD_NUMBER = 1;
+    private double timeElapsed_;
+    /**
+     * <pre>
+     * wall clock time change in seconds since last reset
+     * </pre>
+     *
+     * <code>double time_elapsed = 1;</code>
+     */
+    public double getTimeElapsed() {
+      return timeElapsed_;
+    }
+
+    public static final int TIME_USER_FIELD_NUMBER = 2;
+    private double timeUser_;
+    /**
+     * <pre>
+     * change in user time (in seconds) used by the server since last reset
+     * </pre>
+     *
+     * <code>double time_user = 2;</code>
+     */
+    public double getTimeUser() {
+      return timeUser_;
+    }
+
+    public static final int TIME_SYSTEM_FIELD_NUMBER = 3;
+    private double timeSystem_;
+    /**
+     * <pre>
+     * change in server time (in seconds) used by the server process and all
+     * threads since last reset
+     * </pre>
+     *
+     * <code>double time_system = 3;</code>
+     */
+    public double getTimeSystem() {
+      return timeSystem_;
+    }
+
+    public static final int TOTAL_CPU_TIME_FIELD_NUMBER = 4;
+    private long totalCpuTime_;
+    /**
+     * <pre>
+     * change in total cpu time of the server (data from proc/stat)
+     * </pre>
+     *
+     * <code>uint64 total_cpu_time = 4;</code>
+     */
+    public long getTotalCpuTime() {
+      return totalCpuTime_;
+    }
+
+    public static final int IDLE_CPU_TIME_FIELD_NUMBER = 5;
+    private long idleCpuTime_;
+    /**
+     * <pre>
+     * change in idle time of the server (data from proc/stat)
+     * </pre>
+     *
+     * <code>uint64 idle_cpu_time = 5;</code>
+     */
+    public long getIdleCpuTime() {
+      return idleCpuTime_;
+    }
+
+    public static final int CQ_POLL_COUNT_FIELD_NUMBER = 6;
+    private long cqPollCount_;
+    /**
+     * <pre>
+     * Number of polls called inside completion queue
+     * </pre>
+     *
+     * <code>uint64 cq_poll_count = 6;</code>
+     */
+    public long getCqPollCount() {
+      return cqPollCount_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (timeElapsed_ != 0D) {
+        output.writeDouble(1, timeElapsed_);
+      }
+      if (timeUser_ != 0D) {
+        output.writeDouble(2, timeUser_);
+      }
+      if (timeSystem_ != 0D) {
+        output.writeDouble(3, timeSystem_);
+      }
+      if (totalCpuTime_ != 0L) {
+        output.writeUInt64(4, totalCpuTime_);
+      }
+      if (idleCpuTime_ != 0L) {
+        output.writeUInt64(5, idleCpuTime_);
+      }
+      if (cqPollCount_ != 0L) {
+        output.writeUInt64(6, cqPollCount_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (timeElapsed_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(1, timeElapsed_);
+      }
+      if (timeUser_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(2, timeUser_);
+      }
+      if (timeSystem_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(3, timeSystem_);
+      }
+      if (totalCpuTime_ != 0L) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeUInt64Size(4, totalCpuTime_);
+      }
+      if (idleCpuTime_ != 0L) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeUInt64Size(5, idleCpuTime_);
+      }
+      if (cqPollCount_ != 0L) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeUInt64Size(6, cqPollCount_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Stats.ServerStats)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Stats.ServerStats other = (io.grpc.benchmarks.proto.Stats.ServerStats) obj;
+
+      boolean result = true;
+      result = result && (
+          java.lang.Double.doubleToLongBits(getTimeElapsed())
+          == java.lang.Double.doubleToLongBits(
+              other.getTimeElapsed()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getTimeUser())
+          == java.lang.Double.doubleToLongBits(
+              other.getTimeUser()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getTimeSystem())
+          == java.lang.Double.doubleToLongBits(
+              other.getTimeSystem()));
+      result = result && (getTotalCpuTime()
+          == other.getTotalCpuTime());
+      result = result && (getIdleCpuTime()
+          == other.getIdleCpuTime());
+      result = result && (getCqPollCount()
+          == other.getCqPollCount());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + TIME_ELAPSED_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getTimeElapsed()));
+      hash = (37 * hash) + TIME_USER_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getTimeUser()));
+      hash = (37 * hash) + TIME_SYSTEM_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getTimeSystem()));
+      hash = (37 * hash) + TOTAL_CPU_TIME_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          getTotalCpuTime());
+      hash = (37 * hash) + IDLE_CPU_TIME_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          getIdleCpuTime());
+      hash = (37 * hash) + CQ_POLL_COUNT_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          getCqPollCount());
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Stats.ServerStats parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ServerStats parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ServerStats parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ServerStats parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ServerStats parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ServerStats parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ServerStats parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ServerStats parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ServerStats parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ServerStats parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ServerStats parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ServerStats parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Stats.ServerStats prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.ServerStats}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ServerStats)
+        io.grpc.benchmarks.proto.Stats.ServerStatsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_ServerStats_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_ServerStats_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Stats.ServerStats.class, io.grpc.benchmarks.proto.Stats.ServerStats.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Stats.ServerStats.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        timeElapsed_ = 0D;
+
+        timeUser_ = 0D;
+
+        timeSystem_ = 0D;
+
+        totalCpuTime_ = 0L;
+
+        idleCpuTime_ = 0L;
+
+        cqPollCount_ = 0L;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_ServerStats_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Stats.ServerStats getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Stats.ServerStats.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Stats.ServerStats build() {
+        io.grpc.benchmarks.proto.Stats.ServerStats result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Stats.ServerStats buildPartial() {
+        io.grpc.benchmarks.proto.Stats.ServerStats result = new io.grpc.benchmarks.proto.Stats.ServerStats(this);
+        result.timeElapsed_ = timeElapsed_;
+        result.timeUser_ = timeUser_;
+        result.timeSystem_ = timeSystem_;
+        result.totalCpuTime_ = totalCpuTime_;
+        result.idleCpuTime_ = idleCpuTime_;
+        result.cqPollCount_ = cqPollCount_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Stats.ServerStats) {
+          return mergeFrom((io.grpc.benchmarks.proto.Stats.ServerStats)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Stats.ServerStats other) {
+        if (other == io.grpc.benchmarks.proto.Stats.ServerStats.getDefaultInstance()) return this;
+        if (other.getTimeElapsed() != 0D) {
+          setTimeElapsed(other.getTimeElapsed());
+        }
+        if (other.getTimeUser() != 0D) {
+          setTimeUser(other.getTimeUser());
+        }
+        if (other.getTimeSystem() != 0D) {
+          setTimeSystem(other.getTimeSystem());
+        }
+        if (other.getTotalCpuTime() != 0L) {
+          setTotalCpuTime(other.getTotalCpuTime());
+        }
+        if (other.getIdleCpuTime() != 0L) {
+          setIdleCpuTime(other.getIdleCpuTime());
+        }
+        if (other.getCqPollCount() != 0L) {
+          setCqPollCount(other.getCqPollCount());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Stats.ServerStats parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Stats.ServerStats) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private double timeElapsed_ ;
+      /**
+       * <pre>
+       * wall clock time change in seconds since last reset
+       * </pre>
+       *
+       * <code>double time_elapsed = 1;</code>
+       */
+      public double getTimeElapsed() {
+        return timeElapsed_;
+      }
+      /**
+       * <pre>
+       * wall clock time change in seconds since last reset
+       * </pre>
+       *
+       * <code>double time_elapsed = 1;</code>
+       */
+      public Builder setTimeElapsed(double value) {
+        
+        timeElapsed_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * wall clock time change in seconds since last reset
+       * </pre>
+       *
+       * <code>double time_elapsed = 1;</code>
+       */
+      public Builder clearTimeElapsed() {
+        
+        timeElapsed_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double timeUser_ ;
+      /**
+       * <pre>
+       * change in user time (in seconds) used by the server since last reset
+       * </pre>
+       *
+       * <code>double time_user = 2;</code>
+       */
+      public double getTimeUser() {
+        return timeUser_;
+      }
+      /**
+       * <pre>
+       * change in user time (in seconds) used by the server since last reset
+       * </pre>
+       *
+       * <code>double time_user = 2;</code>
+       */
+      public Builder setTimeUser(double value) {
+        
+        timeUser_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * change in user time (in seconds) used by the server since last reset
+       * </pre>
+       *
+       * <code>double time_user = 2;</code>
+       */
+      public Builder clearTimeUser() {
+        
+        timeUser_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double timeSystem_ ;
+      /**
+       * <pre>
+       * change in server time (in seconds) used by the server process and all
+       * threads since last reset
+       * </pre>
+       *
+       * <code>double time_system = 3;</code>
+       */
+      public double getTimeSystem() {
+        return timeSystem_;
+      }
+      /**
+       * <pre>
+       * change in server time (in seconds) used by the server process and all
+       * threads since last reset
+       * </pre>
+       *
+       * <code>double time_system = 3;</code>
+       */
+      public Builder setTimeSystem(double value) {
+        
+        timeSystem_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * change in server time (in seconds) used by the server process and all
+       * threads since last reset
+       * </pre>
+       *
+       * <code>double time_system = 3;</code>
+       */
+      public Builder clearTimeSystem() {
+        
+        timeSystem_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private long totalCpuTime_ ;
+      /**
+       * <pre>
+       * change in total cpu time of the server (data from proc/stat)
+       * </pre>
+       *
+       * <code>uint64 total_cpu_time = 4;</code>
+       */
+      public long getTotalCpuTime() {
+        return totalCpuTime_;
+      }
+      /**
+       * <pre>
+       * change in total cpu time of the server (data from proc/stat)
+       * </pre>
+       *
+       * <code>uint64 total_cpu_time = 4;</code>
+       */
+      public Builder setTotalCpuTime(long value) {
+        
+        totalCpuTime_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * change in total cpu time of the server (data from proc/stat)
+       * </pre>
+       *
+       * <code>uint64 total_cpu_time = 4;</code>
+       */
+      public Builder clearTotalCpuTime() {
+        
+        totalCpuTime_ = 0L;
+        onChanged();
+        return this;
+      }
+
+      private long idleCpuTime_ ;
+      /**
+       * <pre>
+       * change in idle time of the server (data from proc/stat)
+       * </pre>
+       *
+       * <code>uint64 idle_cpu_time = 5;</code>
+       */
+      public long getIdleCpuTime() {
+        return idleCpuTime_;
+      }
+      /**
+       * <pre>
+       * change in idle time of the server (data from proc/stat)
+       * </pre>
+       *
+       * <code>uint64 idle_cpu_time = 5;</code>
+       */
+      public Builder setIdleCpuTime(long value) {
+        
+        idleCpuTime_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * change in idle time of the server (data from proc/stat)
+       * </pre>
+       *
+       * <code>uint64 idle_cpu_time = 5;</code>
+       */
+      public Builder clearIdleCpuTime() {
+        
+        idleCpuTime_ = 0L;
+        onChanged();
+        return this;
+      }
+
+      private long cqPollCount_ ;
+      /**
+       * <pre>
+       * Number of polls called inside completion queue
+       * </pre>
+       *
+       * <code>uint64 cq_poll_count = 6;</code>
+       */
+      public long getCqPollCount() {
+        return cqPollCount_;
+      }
+      /**
+       * <pre>
+       * Number of polls called inside completion queue
+       * </pre>
+       *
+       * <code>uint64 cq_poll_count = 6;</code>
+       */
+      public Builder setCqPollCount(long value) {
+        
+        cqPollCount_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of polls called inside completion queue
+       * </pre>
+       *
+       * <code>uint64 cq_poll_count = 6;</code>
+       */
+      public Builder clearCqPollCount() {
+        
+        cqPollCount_ = 0L;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ServerStats)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ServerStats)
+    private static final io.grpc.benchmarks.proto.Stats.ServerStats DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Stats.ServerStats();
+    }
+
+    public static io.grpc.benchmarks.proto.Stats.ServerStats getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ServerStats>
+        PARSER = new com.google.protobuf.AbstractParser<ServerStats>() {
+      public ServerStats parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ServerStats(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ServerStats> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ServerStats> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Stats.ServerStats getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface HistogramParamsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.HistogramParams)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * first bucket is [0, 1 + resolution)
+     * </pre>
+     *
+     * <code>double resolution = 1;</code>
+     */
+    double getResolution();
+
+    /**
+     * <pre>
+     * use enough buckets to allow this value
+     * </pre>
+     *
+     * <code>double max_possible = 2;</code>
+     */
+    double getMaxPossible();
+  }
+  /**
+   * <pre>
+   * Histogram params based on grpc/support/histogram.c
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.HistogramParams}
+   */
+  public  static final class HistogramParams extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.HistogramParams)
+      HistogramParamsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use HistogramParams.newBuilder() to construct.
+    private HistogramParams(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private HistogramParams() {
+      resolution_ = 0D;
+      maxPossible_ = 0D;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private HistogramParams(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 9: {
+
+              resolution_ = input.readDouble();
+              break;
+            }
+            case 17: {
+
+              maxPossible_ = input.readDouble();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_HistogramParams_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_HistogramParams_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Stats.HistogramParams.class, io.grpc.benchmarks.proto.Stats.HistogramParams.Builder.class);
+    }
+
+    public static final int RESOLUTION_FIELD_NUMBER = 1;
+    private double resolution_;
+    /**
+     * <pre>
+     * first bucket is [0, 1 + resolution)
+     * </pre>
+     *
+     * <code>double resolution = 1;</code>
+     */
+    public double getResolution() {
+      return resolution_;
+    }
+
+    public static final int MAX_POSSIBLE_FIELD_NUMBER = 2;
+    private double maxPossible_;
+    /**
+     * <pre>
+     * use enough buckets to allow this value
+     * </pre>
+     *
+     * <code>double max_possible = 2;</code>
+     */
+    public double getMaxPossible() {
+      return maxPossible_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (resolution_ != 0D) {
+        output.writeDouble(1, resolution_);
+      }
+      if (maxPossible_ != 0D) {
+        output.writeDouble(2, maxPossible_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (resolution_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(1, resolution_);
+      }
+      if (maxPossible_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(2, maxPossible_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Stats.HistogramParams)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Stats.HistogramParams other = (io.grpc.benchmarks.proto.Stats.HistogramParams) obj;
+
+      boolean result = true;
+      result = result && (
+          java.lang.Double.doubleToLongBits(getResolution())
+          == java.lang.Double.doubleToLongBits(
+              other.getResolution()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getMaxPossible())
+          == java.lang.Double.doubleToLongBits(
+              other.getMaxPossible()));
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + RESOLUTION_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getResolution()));
+      hash = (37 * hash) + MAX_POSSIBLE_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getMaxPossible()));
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Stats.HistogramParams parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramParams parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramParams parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramParams parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramParams parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramParams parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramParams parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramParams parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramParams parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramParams parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramParams parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramParams parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Stats.HistogramParams prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Histogram params based on grpc/support/histogram.c
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.HistogramParams}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.HistogramParams)
+        io.grpc.benchmarks.proto.Stats.HistogramParamsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_HistogramParams_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_HistogramParams_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Stats.HistogramParams.class, io.grpc.benchmarks.proto.Stats.HistogramParams.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Stats.HistogramParams.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        resolution_ = 0D;
+
+        maxPossible_ = 0D;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_HistogramParams_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Stats.HistogramParams getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Stats.HistogramParams.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Stats.HistogramParams build() {
+        io.grpc.benchmarks.proto.Stats.HistogramParams result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Stats.HistogramParams buildPartial() {
+        io.grpc.benchmarks.proto.Stats.HistogramParams result = new io.grpc.benchmarks.proto.Stats.HistogramParams(this);
+        result.resolution_ = resolution_;
+        result.maxPossible_ = maxPossible_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Stats.HistogramParams) {
+          return mergeFrom((io.grpc.benchmarks.proto.Stats.HistogramParams)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Stats.HistogramParams other) {
+        if (other == io.grpc.benchmarks.proto.Stats.HistogramParams.getDefaultInstance()) return this;
+        if (other.getResolution() != 0D) {
+          setResolution(other.getResolution());
+        }
+        if (other.getMaxPossible() != 0D) {
+          setMaxPossible(other.getMaxPossible());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Stats.HistogramParams parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Stats.HistogramParams) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private double resolution_ ;
+      /**
+       * <pre>
+       * first bucket is [0, 1 + resolution)
+       * </pre>
+       *
+       * <code>double resolution = 1;</code>
+       */
+      public double getResolution() {
+        return resolution_;
+      }
+      /**
+       * <pre>
+       * first bucket is [0, 1 + resolution)
+       * </pre>
+       *
+       * <code>double resolution = 1;</code>
+       */
+      public Builder setResolution(double value) {
+        
+        resolution_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * first bucket is [0, 1 + resolution)
+       * </pre>
+       *
+       * <code>double resolution = 1;</code>
+       */
+      public Builder clearResolution() {
+        
+        resolution_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double maxPossible_ ;
+      /**
+       * <pre>
+       * use enough buckets to allow this value
+       * </pre>
+       *
+       * <code>double max_possible = 2;</code>
+       */
+      public double getMaxPossible() {
+        return maxPossible_;
+      }
+      /**
+       * <pre>
+       * use enough buckets to allow this value
+       * </pre>
+       *
+       * <code>double max_possible = 2;</code>
+       */
+      public Builder setMaxPossible(double value) {
+        
+        maxPossible_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * use enough buckets to allow this value
+       * </pre>
+       *
+       * <code>double max_possible = 2;</code>
+       */
+      public Builder clearMaxPossible() {
+        
+        maxPossible_ = 0D;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.HistogramParams)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.HistogramParams)
+    private static final io.grpc.benchmarks.proto.Stats.HistogramParams DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Stats.HistogramParams();
+    }
+
+    public static io.grpc.benchmarks.proto.Stats.HistogramParams getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<HistogramParams>
+        PARSER = new com.google.protobuf.AbstractParser<HistogramParams>() {
+      public HistogramParams parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new HistogramParams(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<HistogramParams> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<HistogramParams> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Stats.HistogramParams getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface HistogramDataOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.HistogramData)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>repeated uint32 bucket = 1;</code>
+     */
+    java.util.List<java.lang.Integer> getBucketList();
+    /**
+     * <code>repeated uint32 bucket = 1;</code>
+     */
+    int getBucketCount();
+    /**
+     * <code>repeated uint32 bucket = 1;</code>
+     */
+    int getBucket(int index);
+
+    /**
+     * <code>double min_seen = 2;</code>
+     */
+    double getMinSeen();
+
+    /**
+     * <code>double max_seen = 3;</code>
+     */
+    double getMaxSeen();
+
+    /**
+     * <code>double sum = 4;</code>
+     */
+    double getSum();
+
+    /**
+     * <code>double sum_of_squares = 5;</code>
+     */
+    double getSumOfSquares();
+
+    /**
+     * <code>double count = 6;</code>
+     */
+    double getCount();
+  }
+  /**
+   * <pre>
+   * Histogram data based on grpc/support/histogram.c
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.HistogramData}
+   */
+  public  static final class HistogramData extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.HistogramData)
+      HistogramDataOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use HistogramData.newBuilder() to construct.
+    private HistogramData(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private HistogramData() {
+      bucket_ = java.util.Collections.emptyList();
+      minSeen_ = 0D;
+      maxSeen_ = 0D;
+      sum_ = 0D;
+      sumOfSquares_ = 0D;
+      count_ = 0D;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private HistogramData(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+              if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+                bucket_ = new java.util.ArrayList<java.lang.Integer>();
+                mutable_bitField0_ |= 0x00000001;
+              }
+              bucket_.add(input.readUInt32());
+              break;
+            }
+            case 10: {
+              int length = input.readRawVarint32();
+              int limit = input.pushLimit(length);
+              if (!((mutable_bitField0_ & 0x00000001) == 0x00000001) && input.getBytesUntilLimit() > 0) {
+                bucket_ = new java.util.ArrayList<java.lang.Integer>();
+                mutable_bitField0_ |= 0x00000001;
+              }
+              while (input.getBytesUntilLimit() > 0) {
+                bucket_.add(input.readUInt32());
+              }
+              input.popLimit(limit);
+              break;
+            }
+            case 17: {
+
+              minSeen_ = input.readDouble();
+              break;
+            }
+            case 25: {
+
+              maxSeen_ = input.readDouble();
+              break;
+            }
+            case 33: {
+
+              sum_ = input.readDouble();
+              break;
+            }
+            case 41: {
+
+              sumOfSquares_ = input.readDouble();
+              break;
+            }
+            case 49: {
+
+              count_ = input.readDouble();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+          bucket_ = java.util.Collections.unmodifiableList(bucket_);
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_HistogramData_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_HistogramData_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Stats.HistogramData.class, io.grpc.benchmarks.proto.Stats.HistogramData.Builder.class);
+    }
+
+    private int bitField0_;
+    public static final int BUCKET_FIELD_NUMBER = 1;
+    private java.util.List<java.lang.Integer> bucket_;
+    /**
+     * <code>repeated uint32 bucket = 1;</code>
+     */
+    public java.util.List<java.lang.Integer>
+        getBucketList() {
+      return bucket_;
+    }
+    /**
+     * <code>repeated uint32 bucket = 1;</code>
+     */
+    public int getBucketCount() {
+      return bucket_.size();
+    }
+    /**
+     * <code>repeated uint32 bucket = 1;</code>
+     */
+    public int getBucket(int index) {
+      return bucket_.get(index);
+    }
+    private int bucketMemoizedSerializedSize = -1;
+
+    public static final int MIN_SEEN_FIELD_NUMBER = 2;
+    private double minSeen_;
+    /**
+     * <code>double min_seen = 2;</code>
+     */
+    public double getMinSeen() {
+      return minSeen_;
+    }
+
+    public static final int MAX_SEEN_FIELD_NUMBER = 3;
+    private double maxSeen_;
+    /**
+     * <code>double max_seen = 3;</code>
+     */
+    public double getMaxSeen() {
+      return maxSeen_;
+    }
+
+    public static final int SUM_FIELD_NUMBER = 4;
+    private double sum_;
+    /**
+     * <code>double sum = 4;</code>
+     */
+    public double getSum() {
+      return sum_;
+    }
+
+    public static final int SUM_OF_SQUARES_FIELD_NUMBER = 5;
+    private double sumOfSquares_;
+    /**
+     * <code>double sum_of_squares = 5;</code>
+     */
+    public double getSumOfSquares() {
+      return sumOfSquares_;
+    }
+
+    public static final int COUNT_FIELD_NUMBER = 6;
+    private double count_;
+    /**
+     * <code>double count = 6;</code>
+     */
+    public double getCount() {
+      return count_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      getSerializedSize();
+      if (getBucketList().size() > 0) {
+        output.writeUInt32NoTag(10);
+        output.writeUInt32NoTag(bucketMemoizedSerializedSize);
+      }
+      for (int i = 0; i < bucket_.size(); i++) {
+        output.writeUInt32NoTag(bucket_.get(i));
+      }
+      if (minSeen_ != 0D) {
+        output.writeDouble(2, minSeen_);
+      }
+      if (maxSeen_ != 0D) {
+        output.writeDouble(3, maxSeen_);
+      }
+      if (sum_ != 0D) {
+        output.writeDouble(4, sum_);
+      }
+      if (sumOfSquares_ != 0D) {
+        output.writeDouble(5, sumOfSquares_);
+      }
+      if (count_ != 0D) {
+        output.writeDouble(6, count_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      {
+        int dataSize = 0;
+        for (int i = 0; i < bucket_.size(); i++) {
+          dataSize += com.google.protobuf.CodedOutputStream
+            .computeUInt32SizeNoTag(bucket_.get(i));
+        }
+        size += dataSize;
+        if (!getBucketList().isEmpty()) {
+          size += 1;
+          size += com.google.protobuf.CodedOutputStream
+              .computeInt32SizeNoTag(dataSize);
+        }
+        bucketMemoizedSerializedSize = dataSize;
+      }
+      if (minSeen_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(2, minSeen_);
+      }
+      if (maxSeen_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(3, maxSeen_);
+      }
+      if (sum_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(4, sum_);
+      }
+      if (sumOfSquares_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(5, sumOfSquares_);
+      }
+      if (count_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(6, count_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Stats.HistogramData)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Stats.HistogramData other = (io.grpc.benchmarks.proto.Stats.HistogramData) obj;
+
+      boolean result = true;
+      result = result && getBucketList()
+          .equals(other.getBucketList());
+      result = result && (
+          java.lang.Double.doubleToLongBits(getMinSeen())
+          == java.lang.Double.doubleToLongBits(
+              other.getMinSeen()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getMaxSeen())
+          == java.lang.Double.doubleToLongBits(
+              other.getMaxSeen()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getSum())
+          == java.lang.Double.doubleToLongBits(
+              other.getSum()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getSumOfSquares())
+          == java.lang.Double.doubleToLongBits(
+              other.getSumOfSquares()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getCount())
+          == java.lang.Double.doubleToLongBits(
+              other.getCount()));
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (getBucketCount() > 0) {
+        hash = (37 * hash) + BUCKET_FIELD_NUMBER;
+        hash = (53 * hash) + getBucketList().hashCode();
+      }
+      hash = (37 * hash) + MIN_SEEN_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getMinSeen()));
+      hash = (37 * hash) + MAX_SEEN_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getMaxSeen()));
+      hash = (37 * hash) + SUM_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getSum()));
+      hash = (37 * hash) + SUM_OF_SQUARES_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getSumOfSquares()));
+      hash = (37 * hash) + COUNT_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getCount()));
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Stats.HistogramData parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramData parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramData parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramData parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramData parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramData parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramData parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramData parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramData parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramData parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramData parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.HistogramData parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Stats.HistogramData prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Histogram data based on grpc/support/histogram.c
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.HistogramData}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.HistogramData)
+        io.grpc.benchmarks.proto.Stats.HistogramDataOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_HistogramData_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_HistogramData_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Stats.HistogramData.class, io.grpc.benchmarks.proto.Stats.HistogramData.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Stats.HistogramData.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        bucket_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+        minSeen_ = 0D;
+
+        maxSeen_ = 0D;
+
+        sum_ = 0D;
+
+        sumOfSquares_ = 0D;
+
+        count_ = 0D;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_HistogramData_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Stats.HistogramData getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Stats.HistogramData.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Stats.HistogramData build() {
+        io.grpc.benchmarks.proto.Stats.HistogramData result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Stats.HistogramData buildPartial() {
+        io.grpc.benchmarks.proto.Stats.HistogramData result = new io.grpc.benchmarks.proto.Stats.HistogramData(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          bucket_ = java.util.Collections.unmodifiableList(bucket_);
+          bitField0_ = (bitField0_ & ~0x00000001);
+        }
+        result.bucket_ = bucket_;
+        result.minSeen_ = minSeen_;
+        result.maxSeen_ = maxSeen_;
+        result.sum_ = sum_;
+        result.sumOfSquares_ = sumOfSquares_;
+        result.count_ = count_;
+        result.bitField0_ = to_bitField0_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Stats.HistogramData) {
+          return mergeFrom((io.grpc.benchmarks.proto.Stats.HistogramData)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Stats.HistogramData other) {
+        if (other == io.grpc.benchmarks.proto.Stats.HistogramData.getDefaultInstance()) return this;
+        if (!other.bucket_.isEmpty()) {
+          if (bucket_.isEmpty()) {
+            bucket_ = other.bucket_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+          } else {
+            ensureBucketIsMutable();
+            bucket_.addAll(other.bucket_);
+          }
+          onChanged();
+        }
+        if (other.getMinSeen() != 0D) {
+          setMinSeen(other.getMinSeen());
+        }
+        if (other.getMaxSeen() != 0D) {
+          setMaxSeen(other.getMaxSeen());
+        }
+        if (other.getSum() != 0D) {
+          setSum(other.getSum());
+        }
+        if (other.getSumOfSquares() != 0D) {
+          setSumOfSquares(other.getSumOfSquares());
+        }
+        if (other.getCount() != 0D) {
+          setCount(other.getCount());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Stats.HistogramData parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Stats.HistogramData) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private java.util.List<java.lang.Integer> bucket_ = java.util.Collections.emptyList();
+      private void ensureBucketIsMutable() {
+        if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+          bucket_ = new java.util.ArrayList<java.lang.Integer>(bucket_);
+          bitField0_ |= 0x00000001;
+         }
+      }
+      /**
+       * <code>repeated uint32 bucket = 1;</code>
+       */
+      public java.util.List<java.lang.Integer>
+          getBucketList() {
+        return java.util.Collections.unmodifiableList(bucket_);
+      }
+      /**
+       * <code>repeated uint32 bucket = 1;</code>
+       */
+      public int getBucketCount() {
+        return bucket_.size();
+      }
+      /**
+       * <code>repeated uint32 bucket = 1;</code>
+       */
+      public int getBucket(int index) {
+        return bucket_.get(index);
+      }
+      /**
+       * <code>repeated uint32 bucket = 1;</code>
+       */
+      public Builder setBucket(
+          int index, int value) {
+        ensureBucketIsMutable();
+        bucket_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated uint32 bucket = 1;</code>
+       */
+      public Builder addBucket(int value) {
+        ensureBucketIsMutable();
+        bucket_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated uint32 bucket = 1;</code>
+       */
+      public Builder addAllBucket(
+          java.lang.Iterable<? extends java.lang.Integer> values) {
+        ensureBucketIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, bucket_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated uint32 bucket = 1;</code>
+       */
+      public Builder clearBucket() {
+        bucket_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+        onChanged();
+        return this;
+      }
+
+      private double minSeen_ ;
+      /**
+       * <code>double min_seen = 2;</code>
+       */
+      public double getMinSeen() {
+        return minSeen_;
+      }
+      /**
+       * <code>double min_seen = 2;</code>
+       */
+      public Builder setMinSeen(double value) {
+        
+        minSeen_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double min_seen = 2;</code>
+       */
+      public Builder clearMinSeen() {
+        
+        minSeen_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double maxSeen_ ;
+      /**
+       * <code>double max_seen = 3;</code>
+       */
+      public double getMaxSeen() {
+        return maxSeen_;
+      }
+      /**
+       * <code>double max_seen = 3;</code>
+       */
+      public Builder setMaxSeen(double value) {
+        
+        maxSeen_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double max_seen = 3;</code>
+       */
+      public Builder clearMaxSeen() {
+        
+        maxSeen_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double sum_ ;
+      /**
+       * <code>double sum = 4;</code>
+       */
+      public double getSum() {
+        return sum_;
+      }
+      /**
+       * <code>double sum = 4;</code>
+       */
+      public Builder setSum(double value) {
+        
+        sum_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double sum = 4;</code>
+       */
+      public Builder clearSum() {
+        
+        sum_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double sumOfSquares_ ;
+      /**
+       * <code>double sum_of_squares = 5;</code>
+       */
+      public double getSumOfSquares() {
+        return sumOfSquares_;
+      }
+      /**
+       * <code>double sum_of_squares = 5;</code>
+       */
+      public Builder setSumOfSquares(double value) {
+        
+        sumOfSquares_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double sum_of_squares = 5;</code>
+       */
+      public Builder clearSumOfSquares() {
+        
+        sumOfSquares_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double count_ ;
+      /**
+       * <code>double count = 6;</code>
+       */
+      public double getCount() {
+        return count_;
+      }
+      /**
+       * <code>double count = 6;</code>
+       */
+      public Builder setCount(double value) {
+        
+        count_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double count = 6;</code>
+       */
+      public Builder clearCount() {
+        
+        count_ = 0D;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.HistogramData)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.HistogramData)
+    private static final io.grpc.benchmarks.proto.Stats.HistogramData DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Stats.HistogramData();
+    }
+
+    public static io.grpc.benchmarks.proto.Stats.HistogramData getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<HistogramData>
+        PARSER = new com.google.protobuf.AbstractParser<HistogramData>() {
+      public HistogramData parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new HistogramData(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<HistogramData> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<HistogramData> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Stats.HistogramData getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface RequestResultCountOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.RequestResultCount)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>int32 status_code = 1;</code>
+     */
+    int getStatusCode();
+
+    /**
+     * <code>int64 count = 2;</code>
+     */
+    long getCount();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.RequestResultCount}
+   */
+  public  static final class RequestResultCount extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.RequestResultCount)
+      RequestResultCountOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use RequestResultCount.newBuilder() to construct.
+    private RequestResultCount(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private RequestResultCount() {
+      statusCode_ = 0;
+      count_ = 0L;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private RequestResultCount(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              statusCode_ = input.readInt32();
+              break;
+            }
+            case 16: {
+
+              count_ = input.readInt64();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_RequestResultCount_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_RequestResultCount_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Stats.RequestResultCount.class, io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder.class);
+    }
+
+    public static final int STATUS_CODE_FIELD_NUMBER = 1;
+    private int statusCode_;
+    /**
+     * <code>int32 status_code = 1;</code>
+     */
+    public int getStatusCode() {
+      return statusCode_;
+    }
+
+    public static final int COUNT_FIELD_NUMBER = 2;
+    private long count_;
+    /**
+     * <code>int64 count = 2;</code>
+     */
+    public long getCount() {
+      return count_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (statusCode_ != 0) {
+        output.writeInt32(1, statusCode_);
+      }
+      if (count_ != 0L) {
+        output.writeInt64(2, count_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (statusCode_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1, statusCode_);
+      }
+      if (count_ != 0L) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt64Size(2, count_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Stats.RequestResultCount)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Stats.RequestResultCount other = (io.grpc.benchmarks.proto.Stats.RequestResultCount) obj;
+
+      boolean result = true;
+      result = result && (getStatusCode()
+          == other.getStatusCode());
+      result = result && (getCount()
+          == other.getCount());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + STATUS_CODE_FIELD_NUMBER;
+      hash = (53 * hash) + getStatusCode();
+      hash = (37 * hash) + COUNT_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          getCount());
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Stats.RequestResultCount parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.RequestResultCount parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.RequestResultCount parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.RequestResultCount parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.RequestResultCount parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.RequestResultCount parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.RequestResultCount parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.RequestResultCount parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.RequestResultCount parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.RequestResultCount parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.RequestResultCount parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.RequestResultCount parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Stats.RequestResultCount prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.RequestResultCount}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.RequestResultCount)
+        io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_RequestResultCount_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_RequestResultCount_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Stats.RequestResultCount.class, io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Stats.RequestResultCount.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        statusCode_ = 0;
+
+        count_ = 0L;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_RequestResultCount_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Stats.RequestResultCount getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Stats.RequestResultCount.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Stats.RequestResultCount build() {
+        io.grpc.benchmarks.proto.Stats.RequestResultCount result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Stats.RequestResultCount buildPartial() {
+        io.grpc.benchmarks.proto.Stats.RequestResultCount result = new io.grpc.benchmarks.proto.Stats.RequestResultCount(this);
+        result.statusCode_ = statusCode_;
+        result.count_ = count_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Stats.RequestResultCount) {
+          return mergeFrom((io.grpc.benchmarks.proto.Stats.RequestResultCount)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Stats.RequestResultCount other) {
+        if (other == io.grpc.benchmarks.proto.Stats.RequestResultCount.getDefaultInstance()) return this;
+        if (other.getStatusCode() != 0) {
+          setStatusCode(other.getStatusCode());
+        }
+        if (other.getCount() != 0L) {
+          setCount(other.getCount());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Stats.RequestResultCount parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Stats.RequestResultCount) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int statusCode_ ;
+      /**
+       * <code>int32 status_code = 1;</code>
+       */
+      public int getStatusCode() {
+        return statusCode_;
+      }
+      /**
+       * <code>int32 status_code = 1;</code>
+       */
+      public Builder setStatusCode(int value) {
+        
+        statusCode_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>int32 status_code = 1;</code>
+       */
+      public Builder clearStatusCode() {
+        
+        statusCode_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private long count_ ;
+      /**
+       * <code>int64 count = 2;</code>
+       */
+      public long getCount() {
+        return count_;
+      }
+      /**
+       * <code>int64 count = 2;</code>
+       */
+      public Builder setCount(long value) {
+        
+        count_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>int64 count = 2;</code>
+       */
+      public Builder clearCount() {
+        
+        count_ = 0L;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.RequestResultCount)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.RequestResultCount)
+    private static final io.grpc.benchmarks.proto.Stats.RequestResultCount DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Stats.RequestResultCount();
+    }
+
+    public static io.grpc.benchmarks.proto.Stats.RequestResultCount getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<RequestResultCount>
+        PARSER = new com.google.protobuf.AbstractParser<RequestResultCount>() {
+      public RequestResultCount parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new RequestResultCount(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<RequestResultCount> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<RequestResultCount> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Stats.RequestResultCount getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ClientStatsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ClientStats)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Latency histogram. Data points are in nanoseconds.
+     * </pre>
+     *
+     * <code>.grpc.testing.HistogramData latencies = 1;</code>
+     */
+    boolean hasLatencies();
+    /**
+     * <pre>
+     * Latency histogram. Data points are in nanoseconds.
+     * </pre>
+     *
+     * <code>.grpc.testing.HistogramData latencies = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.HistogramData getLatencies();
+    /**
+     * <pre>
+     * Latency histogram. Data points are in nanoseconds.
+     * </pre>
+     *
+     * <code>.grpc.testing.HistogramData latencies = 1;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.HistogramDataOrBuilder getLatenciesOrBuilder();
+
+    /**
+     * <pre>
+     * See ServerStats for details.
+     * </pre>
+     *
+     * <code>double time_elapsed = 2;</code>
+     */
+    double getTimeElapsed();
+
+    /**
+     * <code>double time_user = 3;</code>
+     */
+    double getTimeUser();
+
+    /**
+     * <code>double time_system = 4;</code>
+     */
+    double getTimeSystem();
+
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+     */
+    java.util.List<io.grpc.benchmarks.proto.Stats.RequestResultCount> 
+        getRequestResultsList();
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.RequestResultCount getRequestResults(int index);
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+     */
+    int getRequestResultsCount();
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+     */
+    java.util.List<? extends io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder> 
+        getRequestResultsOrBuilderList();
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+     */
+    io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder getRequestResultsOrBuilder(
+        int index);
+
+    /**
+     * <pre>
+     * Number of polls called inside completion queue
+     * </pre>
+     *
+     * <code>uint64 cq_poll_count = 6;</code>
+     */
+    long getCqPollCount();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.ClientStats}
+   */
+  public  static final class ClientStats extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ClientStats)
+      ClientStatsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ClientStats.newBuilder() to construct.
+    private ClientStats(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ClientStats() {
+      timeElapsed_ = 0D;
+      timeUser_ = 0D;
+      timeSystem_ = 0D;
+      requestResults_ = java.util.Collections.emptyList();
+      cqPollCount_ = 0L;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ClientStats(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.benchmarks.proto.Stats.HistogramData.Builder subBuilder = null;
+              if (latencies_ != null) {
+                subBuilder = latencies_.toBuilder();
+              }
+              latencies_ = input.readMessage(io.grpc.benchmarks.proto.Stats.HistogramData.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(latencies_);
+                latencies_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 17: {
+
+              timeElapsed_ = input.readDouble();
+              break;
+            }
+            case 25: {
+
+              timeUser_ = input.readDouble();
+              break;
+            }
+            case 33: {
+
+              timeSystem_ = input.readDouble();
+              break;
+            }
+            case 42: {
+              if (!((mutable_bitField0_ & 0x00000010) == 0x00000010)) {
+                requestResults_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Stats.RequestResultCount>();
+                mutable_bitField0_ |= 0x00000010;
+              }
+              requestResults_.add(
+                  input.readMessage(io.grpc.benchmarks.proto.Stats.RequestResultCount.parser(), extensionRegistry));
+              break;
+            }
+            case 48: {
+
+              cqPollCount_ = input.readUInt64();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000010) == 0x00000010)) {
+          requestResults_ = java.util.Collections.unmodifiableList(requestResults_);
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_ClientStats_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_ClientStats_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.benchmarks.proto.Stats.ClientStats.class, io.grpc.benchmarks.proto.Stats.ClientStats.Builder.class);
+    }
+
+    private int bitField0_;
+    public static final int LATENCIES_FIELD_NUMBER = 1;
+    private io.grpc.benchmarks.proto.Stats.HistogramData latencies_;
+    /**
+     * <pre>
+     * Latency histogram. Data points are in nanoseconds.
+     * </pre>
+     *
+     * <code>.grpc.testing.HistogramData latencies = 1;</code>
+     */
+    public boolean hasLatencies() {
+      return latencies_ != null;
+    }
+    /**
+     * <pre>
+     * Latency histogram. Data points are in nanoseconds.
+     * </pre>
+     *
+     * <code>.grpc.testing.HistogramData latencies = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.HistogramData getLatencies() {
+      return latencies_ == null ? io.grpc.benchmarks.proto.Stats.HistogramData.getDefaultInstance() : latencies_;
+    }
+    /**
+     * <pre>
+     * Latency histogram. Data points are in nanoseconds.
+     * </pre>
+     *
+     * <code>.grpc.testing.HistogramData latencies = 1;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.HistogramDataOrBuilder getLatenciesOrBuilder() {
+      return getLatencies();
+    }
+
+    public static final int TIME_ELAPSED_FIELD_NUMBER = 2;
+    private double timeElapsed_;
+    /**
+     * <pre>
+     * See ServerStats for details.
+     * </pre>
+     *
+     * <code>double time_elapsed = 2;</code>
+     */
+    public double getTimeElapsed() {
+      return timeElapsed_;
+    }
+
+    public static final int TIME_USER_FIELD_NUMBER = 3;
+    private double timeUser_;
+    /**
+     * <code>double time_user = 3;</code>
+     */
+    public double getTimeUser() {
+      return timeUser_;
+    }
+
+    public static final int TIME_SYSTEM_FIELD_NUMBER = 4;
+    private double timeSystem_;
+    /**
+     * <code>double time_system = 4;</code>
+     */
+    public double getTimeSystem() {
+      return timeSystem_;
+    }
+
+    public static final int REQUEST_RESULTS_FIELD_NUMBER = 5;
+    private java.util.List<io.grpc.benchmarks.proto.Stats.RequestResultCount> requestResults_;
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+     */
+    public java.util.List<io.grpc.benchmarks.proto.Stats.RequestResultCount> getRequestResultsList() {
+      return requestResults_;
+    }
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+     */
+    public java.util.List<? extends io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder> 
+        getRequestResultsOrBuilderList() {
+      return requestResults_;
+    }
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+     */
+    public int getRequestResultsCount() {
+      return requestResults_.size();
+    }
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.RequestResultCount getRequestResults(int index) {
+      return requestResults_.get(index);
+    }
+    /**
+     * <pre>
+     * Number of failed requests (one row per status code seen)
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+     */
+    public io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder getRequestResultsOrBuilder(
+        int index) {
+      return requestResults_.get(index);
+    }
+
+    public static final int CQ_POLL_COUNT_FIELD_NUMBER = 6;
+    private long cqPollCount_;
+    /**
+     * <pre>
+     * Number of polls called inside completion queue
+     * </pre>
+     *
+     * <code>uint64 cq_poll_count = 6;</code>
+     */
+    public long getCqPollCount() {
+      return cqPollCount_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (latencies_ != null) {
+        output.writeMessage(1, getLatencies());
+      }
+      if (timeElapsed_ != 0D) {
+        output.writeDouble(2, timeElapsed_);
+      }
+      if (timeUser_ != 0D) {
+        output.writeDouble(3, timeUser_);
+      }
+      if (timeSystem_ != 0D) {
+        output.writeDouble(4, timeSystem_);
+      }
+      for (int i = 0; i < requestResults_.size(); i++) {
+        output.writeMessage(5, requestResults_.get(i));
+      }
+      if (cqPollCount_ != 0L) {
+        output.writeUInt64(6, cqPollCount_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (latencies_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, getLatencies());
+      }
+      if (timeElapsed_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(2, timeElapsed_);
+      }
+      if (timeUser_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(3, timeUser_);
+      }
+      if (timeSystem_ != 0D) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(4, timeSystem_);
+      }
+      for (int i = 0; i < requestResults_.size(); i++) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(5, requestResults_.get(i));
+      }
+      if (cqPollCount_ != 0L) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeUInt64Size(6, cqPollCount_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.benchmarks.proto.Stats.ClientStats)) {
+        return super.equals(obj);
+      }
+      io.grpc.benchmarks.proto.Stats.ClientStats other = (io.grpc.benchmarks.proto.Stats.ClientStats) obj;
+
+      boolean result = true;
+      result = result && (hasLatencies() == other.hasLatencies());
+      if (hasLatencies()) {
+        result = result && getLatencies()
+            .equals(other.getLatencies());
+      }
+      result = result && (
+          java.lang.Double.doubleToLongBits(getTimeElapsed())
+          == java.lang.Double.doubleToLongBits(
+              other.getTimeElapsed()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getTimeUser())
+          == java.lang.Double.doubleToLongBits(
+              other.getTimeUser()));
+      result = result && (
+          java.lang.Double.doubleToLongBits(getTimeSystem())
+          == java.lang.Double.doubleToLongBits(
+              other.getTimeSystem()));
+      result = result && getRequestResultsList()
+          .equals(other.getRequestResultsList());
+      result = result && (getCqPollCount()
+          == other.getCqPollCount());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (hasLatencies()) {
+        hash = (37 * hash) + LATENCIES_FIELD_NUMBER;
+        hash = (53 * hash) + getLatencies().hashCode();
+      }
+      hash = (37 * hash) + TIME_ELAPSED_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getTimeElapsed()));
+      hash = (37 * hash) + TIME_USER_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getTimeUser()));
+      hash = (37 * hash) + TIME_SYSTEM_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          java.lang.Double.doubleToLongBits(getTimeSystem()));
+      if (getRequestResultsCount() > 0) {
+        hash = (37 * hash) + REQUEST_RESULTS_FIELD_NUMBER;
+        hash = (53 * hash) + getRequestResultsList().hashCode();
+      }
+      hash = (37 * hash) + CQ_POLL_COUNT_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+          getCqPollCount());
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.benchmarks.proto.Stats.ClientStats parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ClientStats parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ClientStats parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ClientStats parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ClientStats parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ClientStats parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ClientStats parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ClientStats parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ClientStats parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ClientStats parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ClientStats parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.benchmarks.proto.Stats.ClientStats parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.benchmarks.proto.Stats.ClientStats prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.ClientStats}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ClientStats)
+        io.grpc.benchmarks.proto.Stats.ClientStatsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_ClientStats_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_ClientStats_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.benchmarks.proto.Stats.ClientStats.class, io.grpc.benchmarks.proto.Stats.ClientStats.Builder.class);
+      }
+
+      // Construct using io.grpc.benchmarks.proto.Stats.ClientStats.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+          getRequestResultsFieldBuilder();
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        if (latenciesBuilder_ == null) {
+          latencies_ = null;
+        } else {
+          latencies_ = null;
+          latenciesBuilder_ = null;
+        }
+        timeElapsed_ = 0D;
+
+        timeUser_ = 0D;
+
+        timeSystem_ = 0D;
+
+        if (requestResultsBuilder_ == null) {
+          requestResults_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000010);
+        } else {
+          requestResultsBuilder_.clear();
+        }
+        cqPollCount_ = 0L;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.benchmarks.proto.Stats.internal_static_grpc_testing_ClientStats_descriptor;
+      }
+
+      public io.grpc.benchmarks.proto.Stats.ClientStats getDefaultInstanceForType() {
+        return io.grpc.benchmarks.proto.Stats.ClientStats.getDefaultInstance();
+      }
+
+      public io.grpc.benchmarks.proto.Stats.ClientStats build() {
+        io.grpc.benchmarks.proto.Stats.ClientStats result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.benchmarks.proto.Stats.ClientStats buildPartial() {
+        io.grpc.benchmarks.proto.Stats.ClientStats result = new io.grpc.benchmarks.proto.Stats.ClientStats(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        if (latenciesBuilder_ == null) {
+          result.latencies_ = latencies_;
+        } else {
+          result.latencies_ = latenciesBuilder_.build();
+        }
+        result.timeElapsed_ = timeElapsed_;
+        result.timeUser_ = timeUser_;
+        result.timeSystem_ = timeSystem_;
+        if (requestResultsBuilder_ == null) {
+          if (((bitField0_ & 0x00000010) == 0x00000010)) {
+            requestResults_ = java.util.Collections.unmodifiableList(requestResults_);
+            bitField0_ = (bitField0_ & ~0x00000010);
+          }
+          result.requestResults_ = requestResults_;
+        } else {
+          result.requestResults_ = requestResultsBuilder_.build();
+        }
+        result.cqPollCount_ = cqPollCount_;
+        result.bitField0_ = to_bitField0_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.benchmarks.proto.Stats.ClientStats) {
+          return mergeFrom((io.grpc.benchmarks.proto.Stats.ClientStats)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.benchmarks.proto.Stats.ClientStats other) {
+        if (other == io.grpc.benchmarks.proto.Stats.ClientStats.getDefaultInstance()) return this;
+        if (other.hasLatencies()) {
+          mergeLatencies(other.getLatencies());
+        }
+        if (other.getTimeElapsed() != 0D) {
+          setTimeElapsed(other.getTimeElapsed());
+        }
+        if (other.getTimeUser() != 0D) {
+          setTimeUser(other.getTimeUser());
+        }
+        if (other.getTimeSystem() != 0D) {
+          setTimeSystem(other.getTimeSystem());
+        }
+        if (requestResultsBuilder_ == null) {
+          if (!other.requestResults_.isEmpty()) {
+            if (requestResults_.isEmpty()) {
+              requestResults_ = other.requestResults_;
+              bitField0_ = (bitField0_ & ~0x00000010);
+            } else {
+              ensureRequestResultsIsMutable();
+              requestResults_.addAll(other.requestResults_);
+            }
+            onChanged();
+          }
+        } else {
+          if (!other.requestResults_.isEmpty()) {
+            if (requestResultsBuilder_.isEmpty()) {
+              requestResultsBuilder_.dispose();
+              requestResultsBuilder_ = null;
+              requestResults_ = other.requestResults_;
+              bitField0_ = (bitField0_ & ~0x00000010);
+              requestResultsBuilder_ = 
+                com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                   getRequestResultsFieldBuilder() : null;
+            } else {
+              requestResultsBuilder_.addAllMessages(other.requestResults_);
+            }
+          }
+        }
+        if (other.getCqPollCount() != 0L) {
+          setCqPollCount(other.getCqPollCount());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.benchmarks.proto.Stats.ClientStats parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.benchmarks.proto.Stats.ClientStats) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private io.grpc.benchmarks.proto.Stats.HistogramData latencies_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.HistogramData, io.grpc.benchmarks.proto.Stats.HistogramData.Builder, io.grpc.benchmarks.proto.Stats.HistogramDataOrBuilder> latenciesBuilder_;
+      /**
+       * <pre>
+       * Latency histogram. Data points are in nanoseconds.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 1;</code>
+       */
+      public boolean hasLatencies() {
+        return latenciesBuilder_ != null || latencies_ != null;
+      }
+      /**
+       * <pre>
+       * Latency histogram. Data points are in nanoseconds.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.HistogramData getLatencies() {
+        if (latenciesBuilder_ == null) {
+          return latencies_ == null ? io.grpc.benchmarks.proto.Stats.HistogramData.getDefaultInstance() : latencies_;
+        } else {
+          return latenciesBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Latency histogram. Data points are in nanoseconds.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 1;</code>
+       */
+      public Builder setLatencies(io.grpc.benchmarks.proto.Stats.HistogramData value) {
+        if (latenciesBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          latencies_ = value;
+          onChanged();
+        } else {
+          latenciesBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Latency histogram. Data points are in nanoseconds.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 1;</code>
+       */
+      public Builder setLatencies(
+          io.grpc.benchmarks.proto.Stats.HistogramData.Builder builderForValue) {
+        if (latenciesBuilder_ == null) {
+          latencies_ = builderForValue.build();
+          onChanged();
+        } else {
+          latenciesBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Latency histogram. Data points are in nanoseconds.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 1;</code>
+       */
+      public Builder mergeLatencies(io.grpc.benchmarks.proto.Stats.HistogramData value) {
+        if (latenciesBuilder_ == null) {
+          if (latencies_ != null) {
+            latencies_ =
+              io.grpc.benchmarks.proto.Stats.HistogramData.newBuilder(latencies_).mergeFrom(value).buildPartial();
+          } else {
+            latencies_ = value;
+          }
+          onChanged();
+        } else {
+          latenciesBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Latency histogram. Data points are in nanoseconds.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 1;</code>
+       */
+      public Builder clearLatencies() {
+        if (latenciesBuilder_ == null) {
+          latencies_ = null;
+          onChanged();
+        } else {
+          latencies_ = null;
+          latenciesBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Latency histogram. Data points are in nanoseconds.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.HistogramData.Builder getLatenciesBuilder() {
+        
+        onChanged();
+        return getLatenciesFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Latency histogram. Data points are in nanoseconds.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 1;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.HistogramDataOrBuilder getLatenciesOrBuilder() {
+        if (latenciesBuilder_ != null) {
+          return latenciesBuilder_.getMessageOrBuilder();
+        } else {
+          return latencies_ == null ?
+              io.grpc.benchmarks.proto.Stats.HistogramData.getDefaultInstance() : latencies_;
+        }
+      }
+      /**
+       * <pre>
+       * Latency histogram. Data points are in nanoseconds.
+       * </pre>
+       *
+       * <code>.grpc.testing.HistogramData latencies = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.HistogramData, io.grpc.benchmarks.proto.Stats.HistogramData.Builder, io.grpc.benchmarks.proto.Stats.HistogramDataOrBuilder> 
+          getLatenciesFieldBuilder() {
+        if (latenciesBuilder_ == null) {
+          latenciesBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.benchmarks.proto.Stats.HistogramData, io.grpc.benchmarks.proto.Stats.HistogramData.Builder, io.grpc.benchmarks.proto.Stats.HistogramDataOrBuilder>(
+                  getLatencies(),
+                  getParentForChildren(),
+                  isClean());
+          latencies_ = null;
+        }
+        return latenciesBuilder_;
+      }
+
+      private double timeElapsed_ ;
+      /**
+       * <pre>
+       * See ServerStats for details.
+       * </pre>
+       *
+       * <code>double time_elapsed = 2;</code>
+       */
+      public double getTimeElapsed() {
+        return timeElapsed_;
+      }
+      /**
+       * <pre>
+       * See ServerStats for details.
+       * </pre>
+       *
+       * <code>double time_elapsed = 2;</code>
+       */
+      public Builder setTimeElapsed(double value) {
+        
+        timeElapsed_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * See ServerStats for details.
+       * </pre>
+       *
+       * <code>double time_elapsed = 2;</code>
+       */
+      public Builder clearTimeElapsed() {
+        
+        timeElapsed_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double timeUser_ ;
+      /**
+       * <code>double time_user = 3;</code>
+       */
+      public double getTimeUser() {
+        return timeUser_;
+      }
+      /**
+       * <code>double time_user = 3;</code>
+       */
+      public Builder setTimeUser(double value) {
+        
+        timeUser_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double time_user = 3;</code>
+       */
+      public Builder clearTimeUser() {
+        
+        timeUser_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private double timeSystem_ ;
+      /**
+       * <code>double time_system = 4;</code>
+       */
+      public double getTimeSystem() {
+        return timeSystem_;
+      }
+      /**
+       * <code>double time_system = 4;</code>
+       */
+      public Builder setTimeSystem(double value) {
+        
+        timeSystem_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double time_system = 4;</code>
+       */
+      public Builder clearTimeSystem() {
+        
+        timeSystem_ = 0D;
+        onChanged();
+        return this;
+      }
+
+      private java.util.List<io.grpc.benchmarks.proto.Stats.RequestResultCount> requestResults_ =
+        java.util.Collections.emptyList();
+      private void ensureRequestResultsIsMutable() {
+        if (!((bitField0_ & 0x00000010) == 0x00000010)) {
+          requestResults_ = new java.util.ArrayList<io.grpc.benchmarks.proto.Stats.RequestResultCount>(requestResults_);
+          bitField0_ |= 0x00000010;
+         }
+      }
+
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.RequestResultCount, io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder, io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder> requestResultsBuilder_;
+
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Stats.RequestResultCount> getRequestResultsList() {
+        if (requestResultsBuilder_ == null) {
+          return java.util.Collections.unmodifiableList(requestResults_);
+        } else {
+          return requestResultsBuilder_.getMessageList();
+        }
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public int getRequestResultsCount() {
+        if (requestResultsBuilder_ == null) {
+          return requestResults_.size();
+        } else {
+          return requestResultsBuilder_.getCount();
+        }
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.RequestResultCount getRequestResults(int index) {
+        if (requestResultsBuilder_ == null) {
+          return requestResults_.get(index);
+        } else {
+          return requestResultsBuilder_.getMessage(index);
+        }
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public Builder setRequestResults(
+          int index, io.grpc.benchmarks.proto.Stats.RequestResultCount value) {
+        if (requestResultsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureRequestResultsIsMutable();
+          requestResults_.set(index, value);
+          onChanged();
+        } else {
+          requestResultsBuilder_.setMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public Builder setRequestResults(
+          int index, io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder builderForValue) {
+        if (requestResultsBuilder_ == null) {
+          ensureRequestResultsIsMutable();
+          requestResults_.set(index, builderForValue.build());
+          onChanged();
+        } else {
+          requestResultsBuilder_.setMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public Builder addRequestResults(io.grpc.benchmarks.proto.Stats.RequestResultCount value) {
+        if (requestResultsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureRequestResultsIsMutable();
+          requestResults_.add(value);
+          onChanged();
+        } else {
+          requestResultsBuilder_.addMessage(value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public Builder addRequestResults(
+          int index, io.grpc.benchmarks.proto.Stats.RequestResultCount value) {
+        if (requestResultsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureRequestResultsIsMutable();
+          requestResults_.add(index, value);
+          onChanged();
+        } else {
+          requestResultsBuilder_.addMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public Builder addRequestResults(
+          io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder builderForValue) {
+        if (requestResultsBuilder_ == null) {
+          ensureRequestResultsIsMutable();
+          requestResults_.add(builderForValue.build());
+          onChanged();
+        } else {
+          requestResultsBuilder_.addMessage(builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public Builder addRequestResults(
+          int index, io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder builderForValue) {
+        if (requestResultsBuilder_ == null) {
+          ensureRequestResultsIsMutable();
+          requestResults_.add(index, builderForValue.build());
+          onChanged();
+        } else {
+          requestResultsBuilder_.addMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public Builder addAllRequestResults(
+          java.lang.Iterable<? extends io.grpc.benchmarks.proto.Stats.RequestResultCount> values) {
+        if (requestResultsBuilder_ == null) {
+          ensureRequestResultsIsMutable();
+          com.google.protobuf.AbstractMessageLite.Builder.addAll(
+              values, requestResults_);
+          onChanged();
+        } else {
+          requestResultsBuilder_.addAllMessages(values);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public Builder clearRequestResults() {
+        if (requestResultsBuilder_ == null) {
+          requestResults_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000010);
+          onChanged();
+        } else {
+          requestResultsBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public Builder removeRequestResults(int index) {
+        if (requestResultsBuilder_ == null) {
+          ensureRequestResultsIsMutable();
+          requestResults_.remove(index);
+          onChanged();
+        } else {
+          requestResultsBuilder_.remove(index);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder getRequestResultsBuilder(
+          int index) {
+        return getRequestResultsFieldBuilder().getBuilder(index);
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder getRequestResultsOrBuilder(
+          int index) {
+        if (requestResultsBuilder_ == null) {
+          return requestResults_.get(index);  } else {
+          return requestResultsBuilder_.getMessageOrBuilder(index);
+        }
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public java.util.List<? extends io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder> 
+           getRequestResultsOrBuilderList() {
+        if (requestResultsBuilder_ != null) {
+          return requestResultsBuilder_.getMessageOrBuilderList();
+        } else {
+          return java.util.Collections.unmodifiableList(requestResults_);
+        }
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder addRequestResultsBuilder() {
+        return getRequestResultsFieldBuilder().addBuilder(
+            io.grpc.benchmarks.proto.Stats.RequestResultCount.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder addRequestResultsBuilder(
+          int index) {
+        return getRequestResultsFieldBuilder().addBuilder(
+            index, io.grpc.benchmarks.proto.Stats.RequestResultCount.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * Number of failed requests (one row per status code seen)
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.RequestResultCount request_results = 5;</code>
+       */
+      public java.util.List<io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder> 
+           getRequestResultsBuilderList() {
+        return getRequestResultsFieldBuilder().getBuilderList();
+      }
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.benchmarks.proto.Stats.RequestResultCount, io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder, io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder> 
+          getRequestResultsFieldBuilder() {
+        if (requestResultsBuilder_ == null) {
+          requestResultsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+              io.grpc.benchmarks.proto.Stats.RequestResultCount, io.grpc.benchmarks.proto.Stats.RequestResultCount.Builder, io.grpc.benchmarks.proto.Stats.RequestResultCountOrBuilder>(
+                  requestResults_,
+                  ((bitField0_ & 0x00000010) == 0x00000010),
+                  getParentForChildren(),
+                  isClean());
+          requestResults_ = null;
+        }
+        return requestResultsBuilder_;
+      }
+
+      private long cqPollCount_ ;
+      /**
+       * <pre>
+       * Number of polls called inside completion queue
+       * </pre>
+       *
+       * <code>uint64 cq_poll_count = 6;</code>
+       */
+      public long getCqPollCount() {
+        return cqPollCount_;
+      }
+      /**
+       * <pre>
+       * Number of polls called inside completion queue
+       * </pre>
+       *
+       * <code>uint64 cq_poll_count = 6;</code>
+       */
+      public Builder setCqPollCount(long value) {
+        
+        cqPollCount_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Number of polls called inside completion queue
+       * </pre>
+       *
+       * <code>uint64 cq_poll_count = 6;</code>
+       */
+      public Builder clearCqPollCount() {
+        
+        cqPollCount_ = 0L;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ClientStats)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ClientStats)
+    private static final io.grpc.benchmarks.proto.Stats.ClientStats DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.benchmarks.proto.Stats.ClientStats();
+    }
+
+    public static io.grpc.benchmarks.proto.Stats.ClientStats getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ClientStats>
+        PARSER = new com.google.protobuf.AbstractParser<ClientStats>() {
+      public ClientStats parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ClientStats(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ClientStats> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ClientStats> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.benchmarks.proto.Stats.ClientStats getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ServerStats_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ServerStats_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_HistogramParams_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_HistogramParams_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_HistogramData_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_HistogramData_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_RequestResultCount_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_RequestResultCount_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ClientStats_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ClientStats_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\030grpc/testing/stats.proto\022\014grpc.testing" +
+      "\"\221\001\n\013ServerStats\022\024\n\014time_elapsed\030\001 \001(\001\022\021" +
+      "\n\ttime_user\030\002 \001(\001\022\023\n\013time_system\030\003 \001(\001\022\026" +
+      "\n\016total_cpu_time\030\004 \001(\004\022\025\n\ridle_cpu_time\030" +
+      "\005 \001(\004\022\025\n\rcq_poll_count\030\006 \001(\004\";\n\017Histogra" +
+      "mParams\022\022\n\nresolution\030\001 \001(\001\022\024\n\014max_possi" +
+      "ble\030\002 \001(\001\"w\n\rHistogramData\022\016\n\006bucket\030\001 \003" +
+      "(\r\022\020\n\010min_seen\030\002 \001(\001\022\020\n\010max_seen\030\003 \001(\001\022\013" +
+      "\n\003sum\030\004 \001(\001\022\026\n\016sum_of_squares\030\005 \001(\001\022\r\n\005c" +
+      "ount\030\006 \001(\001\"8\n\022RequestResultCount\022\023\n\013stat" +
+      "us_code\030\001 \001(\005\022\r\n\005count\030\002 \001(\003\"\315\001\n\013ClientS" +
+      "tats\022.\n\tlatencies\030\001 \001(\0132\033.grpc.testing.H" +
+      "istogramData\022\024\n\014time_elapsed\030\002 \001(\001\022\021\n\tti" +
+      "me_user\030\003 \001(\001\022\023\n\013time_system\030\004 \001(\001\0229\n\017re" +
+      "quest_results\030\005 \003(\0132 .grpc.testing.Reque" +
+      "stResultCount\022\025\n\rcq_poll_count\030\006 \001(\004B!\n\030" +
+      "io.grpc.benchmarks.protoB\005Statsb\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        }, assigner);
+    internal_static_grpc_testing_ServerStats_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_testing_ServerStats_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ServerStats_descriptor,
+        new java.lang.String[] { "TimeElapsed", "TimeUser", "TimeSystem", "TotalCpuTime", "IdleCpuTime", "CqPollCount", });
+    internal_static_grpc_testing_HistogramParams_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_testing_HistogramParams_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_HistogramParams_descriptor,
+        new java.lang.String[] { "Resolution", "MaxPossible", });
+    internal_static_grpc_testing_HistogramData_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_testing_HistogramData_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_HistogramData_descriptor,
+        new java.lang.String[] { "Bucket", "MinSeen", "MaxSeen", "Sum", "SumOfSquares", "Count", });
+    internal_static_grpc_testing_RequestResultCount_descriptor =
+      getDescriptor().getMessageTypes().get(3);
+    internal_static_grpc_testing_RequestResultCount_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_RequestResultCount_descriptor,
+        new java.lang.String[] { "StatusCode", "Count", });
+    internal_static_grpc_testing_ClientStats_descriptor =
+      getDescriptor().getMessageTypes().get(4);
+    internal_static_grpc_testing_ClientStats_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ClientStats_descriptor,
+        new java.lang.String[] { "Latencies", "TimeElapsed", "TimeUser", "TimeSystem", "RequestResults", "CqPollCount", });
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/TransportBenchmark.java b/benchmarks/src/jmh/java/io/grpc/benchmarks/TransportBenchmark.java
new file mode 100644
index 0000000..ef1015c
--- /dev/null
+++ b/benchmarks/src/jmh/java/io/grpc/benchmarks/TransportBenchmark.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks;
+
+import static io.grpc.benchmarks.Utils.pickUnusedPort;
+
+import com.google.protobuf.ByteString;
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.benchmarks.proto.BenchmarkServiceGrpc;
+import io.grpc.benchmarks.proto.Messages.Payload;
+import io.grpc.benchmarks.proto.Messages.SimpleRequest;
+import io.grpc.benchmarks.proto.Messages.SimpleResponse;
+import io.grpc.benchmarks.qps.AsyncServer;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.internal.AbstractManagedChannelImplBuilder;
+import io.grpc.internal.AbstractServerImplBuilder;
+import io.grpc.netty.NegotiationType;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.netty.NettyServerBuilder;
+import io.grpc.okhttp.OkHttpChannelBuilder;
+import io.netty.channel.Channel;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.channel.local.LocalServerChannel;
+import java.net.InetSocketAddress;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+/** Some text. */
+@State(Scope.Benchmark)
+public class TransportBenchmark {
+  public enum Transport {
+    INPROCESS, NETTY, NETTY_LOCAL, NETTY_EPOLL, OKHTTP
+  }
+
+  @Param({"INPROCESS", "NETTY", "NETTY_LOCAL", "OKHTTP"})
+  public Transport transport;
+  @Param({"true", "false"})
+  public boolean direct;
+
+  private ManagedChannel channel;
+  private Server server;
+  private BenchmarkServiceGrpc.BenchmarkServiceBlockingStub stub;
+  private volatile EventLoopGroup groupToShutdown;
+
+  @Setup
+  @SuppressWarnings("LiteralClassName") // Epoll is not available on windows
+  public void setUp() throws Exception {
+    AbstractServerImplBuilder<?> serverBuilder;
+    AbstractManagedChannelImplBuilder<?> channelBuilder;
+    switch (transport) {
+      case INPROCESS:
+      {
+        String name = "bench" + Math.random();
+        serverBuilder = InProcessServerBuilder.forName(name);
+        channelBuilder = InProcessChannelBuilder.forName(name);
+        break;
+      }
+      case NETTY:
+      {
+        InetSocketAddress address = new InetSocketAddress("localhost", pickUnusedPort());
+        serverBuilder = NettyServerBuilder.forAddress(address);
+        channelBuilder = NettyChannelBuilder.forAddress(address)
+            .negotiationType(NegotiationType.PLAINTEXT);
+        break;
+      }
+      case NETTY_LOCAL:
+      {
+        String name = "bench" + Math.random();
+        LocalAddress address = new LocalAddress(name);
+        serverBuilder = NettyServerBuilder.forAddress(address)
+            .channelType(LocalServerChannel.class);
+        channelBuilder = NettyChannelBuilder.forAddress(address)
+            .channelType(LocalChannel.class)
+            .negotiationType(NegotiationType.PLAINTEXT);
+        break;
+      }
+      case NETTY_EPOLL:
+      {
+        InetSocketAddress address = new InetSocketAddress("localhost", pickUnusedPort());
+
+        // Reflection used since they are only available on linux.
+        Class<?> groupClass = Class.forName("io.netty.channel.epoll.EpollEventLoopGroup");
+        EventLoopGroup group = (EventLoopGroup) groupClass.getConstructor().newInstance();
+
+        @SuppressWarnings("unchecked")
+        Class<? extends ServerChannel> serverChannelClass = (Class<? extends ServerChannel>)
+            Class.forName("io.netty.channel.epoll.EpollServerSocketChannel");
+        serverBuilder = NettyServerBuilder.forAddress(address)
+            .bossEventLoopGroup(group)
+            .workerEventLoopGroup(group)
+            .channelType(serverChannelClass);
+        @SuppressWarnings("unchecked")
+        Class<? extends Channel> channelClass = (Class<? extends Channel>)
+            Class.forName("io.netty.channel.epoll.EpollSocketChannel");
+        channelBuilder = NettyChannelBuilder.forAddress(address)
+            .eventLoopGroup(group)
+            .channelType(channelClass)
+            .negotiationType(NegotiationType.PLAINTEXT);
+        groupToShutdown = group;
+        break;
+      }
+      case OKHTTP:
+      {
+        int port = pickUnusedPort();
+        InetSocketAddress address = new InetSocketAddress("localhost", port);
+        serverBuilder = NettyServerBuilder.forAddress(address);
+        channelBuilder = OkHttpChannelBuilder.forAddress("localhost", port).usePlaintext();
+        break;
+      }
+      default:
+        throw new Exception("Unknown transport: " + transport);
+    }
+
+    if (direct) {
+      serverBuilder.directExecutor();
+      // Because blocking stubs avoid the executor, this doesn't do much.
+      channelBuilder.directExecutor();
+    }
+
+    server = serverBuilder
+        .addService(new AsyncServer.BenchmarkServiceImpl())
+        .build();
+    server.start();
+    channel = channelBuilder.build();
+    stub = BenchmarkServiceGrpc.newBlockingStub(channel);
+    // Wait for channel to start
+    stub.unaryCall(SimpleRequest.getDefaultInstance());
+  }
+
+  @TearDown
+  public void tearDown() throws Exception {
+    channel.shutdown();
+    server.shutdown();
+    channel.awaitTermination(1, TimeUnit.SECONDS);
+    server.awaitTermination(1, TimeUnit.SECONDS);
+    if (!channel.isTerminated()) {
+      throw new Exception("failed to shut down channel");
+    }
+    if (!server.isTerminated()) {
+      throw new Exception("failed to shut down server");
+    }
+    if (groupToShutdown != null) {
+      Future<?> unused = groupToShutdown.shutdownGracefully(0, 1, TimeUnit.SECONDS);
+      groupToShutdown.awaitTermination(1, TimeUnit.SECONDS);
+      if (!groupToShutdown.isTerminated()) {
+        throw new Exception("failed to shut down event loop group.");
+      }
+    }
+  }
+
+  private SimpleRequest simpleRequest = SimpleRequest.newBuilder()
+      .setResponseSize(1024)
+      .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[1024])))
+      .build();
+
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public SimpleResponse unaryCall1024() {
+    return stub.unaryCall(simpleRequest);
+  }
+}
diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/AbstractBenchmark.java b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/AbstractBenchmark.java
new file mode 100644
index 0000000..37a817a
--- /dev/null
+++ b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/AbstractBenchmark.java
@@ -0,0 +1,552 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.netty;
+
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.Server;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.ServiceDescriptor;
+import io.grpc.Status;
+import io.grpc.benchmarks.ByteBufOutputMarshaller;
+import io.grpc.netty.NegotiationType;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.netty.NettyServerBuilder;
+import io.grpc.stub.ClientCalls;
+import io.grpc.stub.StreamObserver;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.PooledByteBufAllocator;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.channel.local.LocalServerChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.util.concurrent.DefaultThreadFactory;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.ServerSocket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Enumeration;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Abstract base class for Netty end-to-end benchmarks.
+ */
+public abstract class AbstractBenchmark {
+
+  private static final Logger logger = Logger.getLogger(AbstractBenchmark.class.getName());
+
+  /**
+   * Standard message sizes.
+   */
+  public enum MessageSize {
+    // Max out at 1MB to avoid creating messages larger than Netty's buffer pool can handle
+    // by default
+    SMALL(10), MEDIUM(1024), LARGE(65536), JUMBO(1048576);
+
+    private final int bytes;
+    MessageSize(int bytes) {
+      this.bytes = bytes;
+    }
+
+    public int bytes() {
+      return bytes;
+    }
+  }
+
+  /**
+   * Standard flow-control window sizes.
+   */
+  public enum FlowWindowSize {
+    SMALL(16383), MEDIUM(65535), LARGE(1048575), JUMBO(8388607);
+
+    private final int bytes;
+    FlowWindowSize(int bytes) {
+      this.bytes = bytes;
+    }
+
+    public int bytes() {
+      return bytes;
+    }
+  }
+
+  /**
+   * Executor types used by Channel & Server.
+   */
+  public enum ExecutorType {
+    DEFAULT, DIRECT;
+  }
+
+  /**
+   * Support channel types.
+   */
+  public enum ChannelType {
+    NIO, LOCAL;
+  }
+
+  private static final CallOptions CALL_OPTIONS = CallOptions.DEFAULT;
+
+  private static final InetAddress BENCHMARK_ADDR = buildBenchmarkAddr();
+
+  /**
+   * Resolve the address bound to the benchmark interface. Currently we assume it's a
+   * child interface of the loopback interface with the term 'benchmark' in its name.
+   *
+   * <p>>This allows traffic shaping to be applied to an IP address and to have the benchmarks
+   * detect it's presence and use it. E.g for Linux we can apply netem to a specific IP to
+   * do traffic shaping, bind that IP to the loopback adapter and then apply a label to that
+   * binding so that it appears as a child interface.
+   *
+   * <pre>
+   * sudo tc qdisc del dev lo root
+   * sudo tc qdisc add dev lo root handle 1: prio
+   * sudo tc qdisc add dev lo parent 1:1 handle 2: netem delay 0.1ms rate 10gbit
+   * sudo tc filter add dev lo parent 1:0 protocol ip prio 1  \
+   *            u32 match ip dst 127.127.127.127 flowid 2:1
+   * sudo ip addr add dev lo 127.127.127.127/32 label lo:benchmark
+   * </pre>
+   */
+  private static InetAddress buildBenchmarkAddr() {
+    InetAddress tmp = null;
+    try {
+      Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
+      outer: while (networkInterfaces.hasMoreElements()) {
+        NetworkInterface networkInterface = networkInterfaces.nextElement();
+        if (!networkInterface.isLoopback()) {
+          continue;
+        }
+        Enumeration<NetworkInterface> subInterfaces = networkInterface.getSubInterfaces();
+        while (subInterfaces.hasMoreElements()) {
+          NetworkInterface subLoopback = subInterfaces.nextElement();
+          if (subLoopback.getDisplayName().contains("benchmark")) {
+            tmp = subLoopback.getInetAddresses().nextElement();
+            System.out.println("\nResolved benchmark address to " + tmp + " on "
+                + subLoopback.getDisplayName() + "\n\n");
+            break outer;
+          }
+        }
+      }
+    } catch (SocketException se) {
+      System.out.println("\nWARNING: Error trying to resolve benchmark interface \n" +  se);
+    }
+    if (tmp == null) {
+      try {
+        System.out.println(
+            "\nWARNING: Unable to resolve benchmark interface, defaulting to localhost");
+        tmp = InetAddress.getLocalHost();
+      } catch (UnknownHostException uhe) {
+        throw new RuntimeException(uhe);
+      }
+    }
+    return tmp;
+  }
+
+  protected Server server;
+  protected ByteBuf request;
+  protected ByteBuf response;
+  protected MethodDescriptor<ByteBuf, ByteBuf> unaryMethod;
+  private MethodDescriptor<ByteBuf, ByteBuf> pingPongMethod;
+  private MethodDescriptor<ByteBuf, ByteBuf> flowControlledStreaming;
+  protected ManagedChannel[] channels;
+
+  public AbstractBenchmark() {
+  }
+
+  /**
+   * Initialize the environment for the executor.
+   */
+  public void setup(ExecutorType clientExecutor,
+                    ExecutorType serverExecutor,
+                    MessageSize requestSize,
+                    MessageSize responseSize,
+                    FlowWindowSize windowSize,
+                    ChannelType channelType,
+                    int maxConcurrentStreams,
+                    int channelCount) throws Exception {
+    NettyServerBuilder serverBuilder;
+    NettyChannelBuilder channelBuilder;
+    if (channelType == ChannelType.LOCAL) {
+      LocalAddress address = new LocalAddress("netty-e2e-benchmark");
+      serverBuilder = NettyServerBuilder.forAddress(address);
+      serverBuilder.channelType(LocalServerChannel.class);
+      channelBuilder = NettyChannelBuilder.forAddress(address);
+      channelBuilder.channelType(LocalChannel.class);
+    } else {
+      ServerSocket sock = new ServerSocket();
+      // Pick a port using an ephemeral socket.
+      sock.bind(new InetSocketAddress(BENCHMARK_ADDR, 0));
+      SocketAddress address = sock.getLocalSocketAddress();
+      sock.close();
+      serverBuilder = NettyServerBuilder.forAddress(address);
+      channelBuilder = NettyChannelBuilder.forAddress(address);
+    }
+
+    if (serverExecutor == ExecutorType.DIRECT) {
+      serverBuilder.directExecutor();
+    }
+    if (clientExecutor == ExecutorType.DIRECT) {
+      channelBuilder.directExecutor();
+    }
+
+    // Always use a different worker group from the client.
+    ThreadFactory serverThreadFactory = new DefaultThreadFactory("STF pool", true /* daemon */);
+    serverBuilder.workerEventLoopGroup(new NioEventLoopGroup(0, serverThreadFactory));
+
+    // Always set connection and stream window size to same value
+    serverBuilder.flowControlWindow(windowSize.bytes());
+    channelBuilder.flowControlWindow(windowSize.bytes());
+
+    channelBuilder.negotiationType(NegotiationType.PLAINTEXT);
+    serverBuilder.maxConcurrentCallsPerConnection(maxConcurrentStreams);
+
+    // Create buffers of the desired size for requests and responses.
+    PooledByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;
+    // Use a heap buffer for now, since MessageFramer doesn't know how to directly convert this
+    // into a WritableBuffer
+    // TODO(carl-mastrangelo): convert this into a regular buffer() call.  See
+    // https://github.com/grpc/grpc-java/issues/2062#issuecomment-234646216
+    request = alloc.heapBuffer(requestSize.bytes());
+    request.writerIndex(request.capacity() - 1);
+    response = alloc.heapBuffer(responseSize.bytes());
+    response.writerIndex(response.capacity() - 1);
+
+    // Simple method that sends and receives NettyByteBuf
+    unaryMethod = MethodDescriptor.<ByteBuf, ByteBuf>newBuilder()
+        .setType(MethodType.UNARY)
+        .setFullMethodName("benchmark/unary")
+        .setRequestMarshaller(new ByteBufOutputMarshaller())
+        .setResponseMarshaller(new ByteBufOutputMarshaller())
+        .build();
+
+    pingPongMethod = unaryMethod.toBuilder()
+        .setType(MethodType.BIDI_STREAMING)
+        .setFullMethodName("benchmark/pingPong")
+        .build();
+    flowControlledStreaming = pingPongMethod.toBuilder()
+        .setFullMethodName("benchmark/flowControlledStreaming")
+        .build();
+
+    // Server implementation of unary & streaming methods
+    serverBuilder.addService(
+        ServerServiceDefinition.builder(
+            new ServiceDescriptor("benchmark",
+                unaryMethod,
+                pingPongMethod,
+                flowControlledStreaming))
+            .addMethod(unaryMethod, new ServerCallHandler<ByteBuf, ByteBuf>() {
+                  @Override
+                  public ServerCall.Listener<ByteBuf> startCall(
+                      final ServerCall<ByteBuf, ByteBuf> call,
+                      Metadata headers) {
+                    call.sendHeaders(new Metadata());
+                    call.request(1);
+                    return new ServerCall.Listener<ByteBuf>() {
+                      @Override
+                      public void onMessage(ByteBuf message) {
+                        // no-op
+                        message.release();
+                        call.sendMessage(response.slice());
+                      }
+
+                      @Override
+                      public void onHalfClose() {
+                        call.close(Status.OK, new Metadata());
+                      }
+
+                      @Override
+                      public void onCancel() {
+
+                      }
+
+                      @Override
+                      public void onComplete() {
+                      }
+                    };
+                  }
+                })
+            .addMethod(pingPongMethod, new ServerCallHandler<ByteBuf, ByteBuf>() {
+                  @Override
+                  public ServerCall.Listener<ByteBuf> startCall(
+                      final ServerCall<ByteBuf, ByteBuf> call,
+                      Metadata headers) {
+                    call.sendHeaders(new Metadata());
+                    call.request(1);
+                    return new ServerCall.Listener<ByteBuf>() {
+                      @Override
+                      public void onMessage(ByteBuf message) {
+                        message.release();
+                        call.sendMessage(response.slice());
+                        // Request next message
+                        call.request(1);
+                      }
+
+                      @Override
+                      public void onHalfClose() {
+                        call.close(Status.OK, new Metadata());
+                      }
+
+                      @Override
+                      public void onCancel() {
+
+                      }
+
+                      @Override
+                      public void onComplete() {
+
+                      }
+                    };
+                  }
+                })
+            .addMethod(flowControlledStreaming, new ServerCallHandler<ByteBuf, ByteBuf>() {
+                  @Override
+                  public ServerCall.Listener<ByteBuf> startCall(
+                      final ServerCall<ByteBuf, ByteBuf> call,
+                      Metadata headers) {
+                    call.sendHeaders(new Metadata());
+                    call.request(1);
+                    return new ServerCall.Listener<ByteBuf>() {
+                      @Override
+                      public void onMessage(ByteBuf message) {
+                        message.release();
+                        while (call.isReady()) {
+                          call.sendMessage(response.slice());
+                        }
+                        // Request next message
+                        call.request(1);
+                      }
+
+                      @Override
+                      public void onHalfClose() {
+                        call.close(Status.OK, new Metadata());
+                      }
+
+                      @Override
+                      public void onCancel() {
+
+                      }
+
+                      @Override
+                      public void onComplete() {
+
+                      }
+
+                      @Override
+                      public void onReady() {
+                        while (call.isReady()) {
+                          call.sendMessage(response.slice());
+                        }
+                      }
+                    };
+                  }
+                })
+            .build());
+
+    // Build and start the clients and servers
+    server = serverBuilder.build();
+    server.start();
+    channels = new ManagedChannel[channelCount];
+    ThreadFactory clientThreadFactory = new DefaultThreadFactory("CTF pool", true /* daemon */);
+    for (int i = 0; i < channelCount; i++) {
+      // Use a dedicated event-loop for each channel
+      channels[i] = channelBuilder
+          .eventLoopGroup(new NioEventLoopGroup(1, clientThreadFactory))
+          .build();
+    }
+  }
+
+  /**
+   * Start a continuously executing set of unary calls that will terminate when
+   * {@code done.get()} is true. Each completed call will increment the counter by the specified
+   * delta which benchmarks can use to measure QPS or bandwidth.
+   */
+  protected void startUnaryCalls(int callsPerChannel,
+                                 final AtomicLong counter,
+                                 final AtomicBoolean done,
+                                 final long counterDelta) {
+    for (final ManagedChannel channel : channels) {
+      for (int i = 0; i < callsPerChannel; i++) {
+        StreamObserver<ByteBuf> observer = new StreamObserver<ByteBuf>() {
+          @Override
+          public void onNext(ByteBuf value) {
+            counter.addAndGet(counterDelta);
+          }
+
+          @Override
+          public void onError(Throwable t) {
+            done.set(true);
+          }
+
+          @Override
+          public void onCompleted() {
+            if (!done.get()) {
+              ByteBuf slice = request.slice();
+              ClientCalls.asyncUnaryCall(
+                  channel.newCall(unaryMethod, CALL_OPTIONS), slice, this);
+            }
+          }
+        };
+        observer.onCompleted();
+      }
+    }
+  }
+
+  /**
+   * Start a continuously executing set of duplex streaming ping-pong calls that will terminate when
+   * {@code done.get()} is true. Each completed call will increment the counter by the specified
+   * delta which benchmarks can use to measure messages per second or bandwidth.
+   */
+  protected CountDownLatch startStreamingCalls(int callsPerChannel, final AtomicLong counter,
+      final AtomicBoolean record, final AtomicBoolean done, final long counterDelta) {
+    final CountDownLatch latch = new CountDownLatch(callsPerChannel * channels.length);
+    for (final ManagedChannel channel : channels) {
+      for (int i = 0; i < callsPerChannel; i++) {
+        final ClientCall<ByteBuf, ByteBuf> streamingCall =
+            channel.newCall(pingPongMethod, CALL_OPTIONS);
+        final AtomicReference<StreamObserver<ByteBuf>> requestObserverRef =
+            new AtomicReference<StreamObserver<ByteBuf>>();
+        final AtomicBoolean ignoreMessages = new AtomicBoolean();
+        StreamObserver<ByteBuf> requestObserver = ClientCalls.asyncBidiStreamingCall(
+            streamingCall,
+            new StreamObserver<ByteBuf>() {
+              @Override
+              public void onNext(ByteBuf value) {
+                if (done.get()) {
+                  if (!ignoreMessages.getAndSet(true)) {
+                    requestObserverRef.get().onCompleted();
+                  }
+                  return;
+                }
+                requestObserverRef.get().onNext(request.slice());
+                if (record.get()) {
+                  counter.addAndGet(counterDelta);
+                }
+                // request is called automatically because the observer implicitly has auto
+                // inbound flow control
+              }
+
+              @Override
+              public void onError(Throwable t) {
+                logger.log(Level.WARNING, "call error", t);
+                latch.countDown();
+              }
+
+              @Override
+              public void onCompleted() {
+                latch.countDown();
+              }
+            });
+        requestObserverRef.set(requestObserver);
+        requestObserver.onNext(request.slice());
+        requestObserver.onNext(request.slice());
+      }
+    }
+    return latch;
+  }
+
+  /**
+   * Start a continuously executing set of duplex streaming ping-pong calls that will terminate when
+   * {@code done.get()} is true. Each completed call will increment the counter by the specified
+   * delta which benchmarks can use to measure messages per second or bandwidth.
+   */
+  protected CountDownLatch startFlowControlledStreamingCalls(int callsPerChannel,
+      final AtomicLong counter, final AtomicBoolean record, final AtomicBoolean done,
+      final long counterDelta) {
+    final CountDownLatch latch = new CountDownLatch(callsPerChannel * channels.length);
+    for (final ManagedChannel channel : channels) {
+      for (int i = 0; i < callsPerChannel; i++) {
+        final ClientCall<ByteBuf, ByteBuf> streamingCall =
+            channel.newCall(flowControlledStreaming, CALL_OPTIONS);
+        final AtomicReference<StreamObserver<ByteBuf>> requestObserverRef =
+            new AtomicReference<StreamObserver<ByteBuf>>();
+        final AtomicBoolean ignoreMessages = new AtomicBoolean();
+        StreamObserver<ByteBuf> requestObserver = ClientCalls.asyncBidiStreamingCall(
+            streamingCall,
+            new StreamObserver<ByteBuf>() {
+              @Override
+              public void onNext(ByteBuf value) {
+                StreamObserver<ByteBuf> obs = requestObserverRef.get();
+                if (done.get()) {
+                  if (!ignoreMessages.getAndSet(true)) {
+                    obs.onCompleted();
+                  }
+                  return;
+                }
+                if (record.get()) {
+                  counter.addAndGet(counterDelta);
+                }
+                // request is called automatically because the observer implicitly has auto
+                // inbound flow control
+              }
+
+              @Override
+              public void onError(Throwable t) {
+                logger.log(Level.WARNING, "call error", t);
+                latch.countDown();
+              }
+
+              @Override
+              public void onCompleted() {
+                latch.countDown();
+              }
+            });
+        requestObserverRef.set(requestObserver);
+
+        // Add some outstanding requests to ensure the server is filling the connection
+        streamingCall.request(5);
+        requestObserver.onNext(request.slice());
+      }
+    }
+    return latch;
+  }
+
+  /**
+   * Shutdown all the client channels and then shutdown the server.
+   */
+  protected void teardown() throws Exception {
+    logger.fine("shutting down channels");
+    for (ManagedChannel channel : channels) {
+      channel.shutdown();
+    }
+    logger.fine("shutting down server");
+    server.shutdown();
+    if (!server.awaitTermination(5, TimeUnit.SECONDS)) {
+      logger.warning("Failed to shutdown server");
+    }
+    logger.fine("server shut down");
+    for (ManagedChannel channel : channels) {
+      if (!channel.awaitTermination(1, TimeUnit.SECONDS)) {
+        logger.warning("Failed to shutdown client");
+      }
+    }
+    logger.fine("channels shut down");
+  }
+}
diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/FlowControlledMessagesPerSecondBenchmark.java b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/FlowControlledMessagesPerSecondBenchmark.java
new file mode 100644
index 0000000..3be6fe9
--- /dev/null
+++ b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/FlowControlledMessagesPerSecondBenchmark.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.netty;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Logger;
+import org.openjdk.jmh.annotations.AuxCounters;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+/**
+ * Benchmark measuring messages per second received from a streaming server. The server
+ * is obeying outbound flow-control.
+ */
+@State(Scope.Benchmark)
+@Fork(1)
+public class FlowControlledMessagesPerSecondBenchmark extends AbstractBenchmark {
+
+  private static final Logger logger =
+      Logger.getLogger(FlowControlledMessagesPerSecondBenchmark.class.getName());
+
+  @Param({"1", "2", "4"})
+  public int channelCount = 1;
+
+  @Param({"1", "2", "10", "100"})
+  public int maxConcurrentStreams = 1;
+
+  @Param
+  public ExecutorType clientExecutor = ExecutorType.DIRECT;
+
+  @Param({"SMALL"})
+  public MessageSize responseSize = MessageSize.SMALL;
+
+  private static AtomicLong callCounter;
+  private AtomicBoolean completed;
+  private AtomicBoolean record;
+  private CountDownLatch latch;
+
+  /**
+   * Use an AuxCounter so we can measure that calls as they occur without consuming CPU
+   * in the benchmark method.
+   */
+  @AuxCounters
+  @State(Scope.Thread)
+  public static class AdditionalCounters {
+
+    @Setup(Level.Iteration)
+    public void clean() {
+      callCounter.set(0);
+    }
+
+    public long messagesPerSecond() {
+      return callCounter.get();
+    }
+  }
+
+  /**
+   * Setup with direct executors, small payloads and the default flow-control window.
+   */
+  @Setup(Level.Trial)
+  public void setup() throws Exception {
+    super.setup(clientExecutor,
+        ExecutorType.DIRECT,
+        MessageSize.SMALL,
+        responseSize,
+        FlowWindowSize.MEDIUM,
+        ChannelType.NIO,
+        maxConcurrentStreams,
+        channelCount);
+    callCounter = new AtomicLong();
+    completed = new AtomicBoolean();
+    record = new AtomicBoolean();
+    latch =
+        startFlowControlledStreamingCalls(maxConcurrentStreams, callCounter, record, completed, 1);
+  }
+
+  /**
+   * Stop the running calls then stop the server and client channels.
+   */
+  @Override
+  @TearDown(Level.Trial)
+  public void teardown() throws Exception {
+    completed.set(true);
+    if (!latch.await(5, TimeUnit.SECONDS)) {
+      logger.warning("Failed to shutdown all calls.");
+    }
+
+    super.teardown();
+  }
+
+  /**
+   * Measure the rate of messages received. The calls are already running, we just observe a counter
+   * of received responses.
+   */
+  @Benchmark
+  public void stream(AdditionalCounters counters) throws Exception {
+    record.set(true);
+    // No need to do anything, just sleep here.
+    Thread.sleep(1001);
+    record.set(false);
+  }
+
+  /**
+   * Useful for triggering a subset of the benchmark in a profiler.
+   */
+  public static void main(String[] argv) throws Exception {
+    FlowControlledMessagesPerSecondBenchmark bench = new FlowControlledMessagesPerSecondBenchmark();
+    bench.setup();
+    Thread.sleep(30000);
+    bench.teardown();
+    System.exit(0);
+  }
+}
diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/HandlerRegistryBenchmark.java b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/HandlerRegistryBenchmark.java
new file mode 100644
index 0000000..ff84979
--- /dev/null
+++ b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/HandlerRegistryBenchmark.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.netty;
+
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerCall;
+import io.grpc.ServerCall.Listener;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.testing.TestMethodDescriptors;
+import io.grpc.util.MutableHandlerRegistry;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+
+/**
+ * Benchmark for {@link MutableHandlerRegistry}.
+ */
+@State(Scope.Benchmark)
+@Fork(1)
+public class HandlerRegistryBenchmark {
+
+  private static final String VALID_CHARACTERS =
+          "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.";
+
+  @Param({"50"})
+  public int nameLength;
+
+  @Param({"100"})
+  public int serviceCount;
+
+  @Param({"100"})
+  public int methodCountPerService;
+
+  private MutableHandlerRegistry registry;
+  private List<String> fullMethodNames;
+
+  /**
+   * Set up the registry.
+   */
+  @Setup(Level.Trial)
+  public void setup() throws Exception {
+    registry = new MutableHandlerRegistry();
+    fullMethodNames = new ArrayList<>(serviceCount * methodCountPerService);
+    for (int serviceIndex = 0; serviceIndex < serviceCount; ++serviceIndex) {
+      String serviceName = randomString();
+      ServerServiceDefinition.Builder serviceBuilder = ServerServiceDefinition.builder(serviceName);
+      for (int methodIndex = 0; methodIndex < methodCountPerService; ++methodIndex) {
+        String methodName = randomString();
+
+        MethodDescriptor<Void, Void> methodDescriptor = MethodDescriptor.<Void, Void>newBuilder()
+            .setType(MethodDescriptor.MethodType.UNKNOWN)
+            .setFullMethodName(MethodDescriptor.generateFullMethodName(serviceName, methodName))
+            .setRequestMarshaller(TestMethodDescriptors.voidMarshaller())
+            .setResponseMarshaller(TestMethodDescriptors.voidMarshaller())
+            .build();
+        serviceBuilder.addMethod(methodDescriptor,
+            new ServerCallHandler<Void, Void>() {
+              @Override
+              public Listener<Void> startCall(ServerCall<Void, Void> call,
+                  Metadata headers) {
+                return null;
+              }
+            });
+        fullMethodNames.add(methodDescriptor.getFullMethodName());
+      }
+      registry.addService(serviceBuilder.build());
+    }
+  }
+
+  /**
+   * Benchmark the {@link MutableHandlerRegistry#lookupMethod(String)} throughput.
+   */
+  @Benchmark
+  public void lookupMethod(Blackhole bh) {
+    for (String fullMethodName : fullMethodNames) {
+      bh.consume(registry.lookupMethod(fullMethodName));
+    }
+  }
+
+  private String randomString() {
+    Random r = new Random();
+    char[] bytes = new char[nameLength];
+    for (int ix = 0; ix < nameLength; ++ix) {
+      int charIx = r.nextInt(VALID_CHARACTERS.length());
+      bytes[ix] = VALID_CHARACTERS.charAt(charIx);
+    }
+    return new String(bytes);
+  }
+}
diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/README.md b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/README.md
new file mode 100644
index 0000000..ae594ff
--- /dev/null
+++ b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/README.md
@@ -0,0 +1,45 @@
+Benchmarks
+==========
+
+This directory contains the standard benchmarks used to assess the performance of GRPC. Since these
+benchmarks run on localhost over loopback the performance of the underlying network is considerably
+different to real networks and their behavior. To address this issue we recommend the use of
+a network emulator to make loopback behave more like a real network. To this end the benchmark
+code looks for a loopback interface with 'benchmark' in its name and attempts to use the address
+bound to that interface when creating the client and server. If it cannot find such an interface it
+will print a warning and continue with the default localhost address.
+
+To attempt to standardize benchmark behavior across machines we attempt to emulate a 10gbit
+ethernet interface with a packet delay of 0.1ms.
+
+
+Linux
+=====
+
+On Linux we can use [netem](http://www.linuxfoundation.org/collaborate/workgroups/networking/netem)  to shape the traffic appropriately.
+
+```sh
+# Remove all traffic shaping from loopback
+sudo tc qdisc del dev lo root
+# Add a priority traffic class to the root of loopback
+sudo tc qdisc add dev lo root handle 1: prio
+# Add a qdisc under the new class with the appropriate shaping
+sudo tc qdisc add dev lo parent 1:1 handle 2: netem delay 0.1ms rate 10gbit
+# Add a filter which selects the new qdisc class for traffic to 127.127.127.127
+sudo tc filter add dev lo parent 1:0 protocol ip prio 1 u32 match ip dst 127.127.127.127 flowid 2:1
+# Create an interface alias call 'lo:benchmark' that maps 127.127.127.127 to loopback
+sudo ip addr add dev lo 127.127.127.127/32 label lo:benchmark
+```
+
+to remove this configuration
+
+```sh
+sudo tc qdisc del dev lo root
+sudo ip addr del dev lo 127.127.127.127/32 label lo:benchmark
+```
+
+Other Platforms
+===============
+
+Contributions welcome!
+
diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/SingleThreadBlockingQpsBenchmark.java b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/SingleThreadBlockingQpsBenchmark.java
new file mode 100644
index 0000000..07f37d8
--- /dev/null
+++ b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/SingleThreadBlockingQpsBenchmark.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.netty;
+
+import io.grpc.CallOptions;
+import io.grpc.stub.ClientCalls;
+import io.netty.buffer.Unpooled;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+/**
+ * Benchmark showing performance of a linear sequence of blocking calls in a single thread which
+ * is the worst case for throughput. The benchmark permutes response payload size and
+ * client inbound flow-control window size.
+ */
+@State(Scope.Benchmark)
+@Fork(1)
+public class SingleThreadBlockingQpsBenchmark extends AbstractBenchmark {
+
+  /**
+   * Setup with direct executors, small payloads and the default flow control window.
+   */
+  @Setup(Level.Trial)
+  public void setup() throws Exception {
+    super.setup(ExecutorType.DIRECT,
+        ExecutorType.DIRECT,
+        MessageSize.SMALL,
+        MessageSize.SMALL,
+        FlowWindowSize.MEDIUM,
+        ChannelType.NIO,
+        1,
+        1);
+  }
+
+  /**
+   * Stop the server and client channels.
+   */
+  @Override
+  @TearDown(Level.Trial)
+  public void teardown() throws Exception {
+    Thread.sleep(5000);
+    super.teardown();
+  }
+
+  /**
+   * Issue a unary call and wait for the response.
+   */
+  @Benchmark
+  public Object blockingUnary() throws Exception {
+    return ClientCalls.blockingUnaryCall(
+        channels[0].newCall(unaryMethod, CallOptions.DEFAULT), Unpooled.EMPTY_BUFFER);
+  }
+
+  /**
+   * Useful for triggering a subset of the benchmark in a profiler.
+   */
+  public static void main(String[] argv) throws Exception {
+    SingleThreadBlockingQpsBenchmark bench = new SingleThreadBlockingQpsBenchmark();
+    bench.setup();
+    for  (int i = 0; i < 10000; i++) {
+      bench.blockingUnary();
+    }
+    Thread.sleep(30000);
+    bench.teardown();
+    System.exit(0);
+  }
+}
diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/StreamingPingPongsPerSecondBenchmark.java b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/StreamingPingPongsPerSecondBenchmark.java
new file mode 100644
index 0000000..1b56b76
--- /dev/null
+++ b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/StreamingPingPongsPerSecondBenchmark.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.netty;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Logger;
+import org.openjdk.jmh.annotations.AuxCounters;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+/**
+ * Benchmark measuring messages per second using a set of permanently open duplex streams which
+ * ping-pong messages.
+ */
+@State(Scope.Benchmark)
+@Fork(1)
+public class StreamingPingPongsPerSecondBenchmark extends AbstractBenchmark {
+  private static final Logger logger =
+      Logger.getLogger(StreamingPingPongsPerSecondBenchmark.class.getName());
+
+  @Param({"1", "2", "4", "8"})
+  public int channelCount = 1;
+
+  @Param({"1", "10", "100", "1000"})
+  public int maxConcurrentStreams = 1;
+
+  private static AtomicLong callCounter;
+  private AtomicBoolean completed;
+  private AtomicBoolean record;
+  private CountDownLatch latch;
+
+  /**
+   * Use an AuxCounter so we can measure that calls as they occur without consuming CPU
+   * in the benchmark method.
+   */
+  @AuxCounters
+  @State(Scope.Thread)
+  public static class AdditionalCounters {
+
+    @Setup(Level.Iteration)
+    public void clean() {
+      callCounter.set(0);
+    }
+
+    public long pingPongsPerSecond() {
+      return callCounter.get();
+    }
+  }
+
+  /**
+   * Setup with direct executors, small payloads and the default flow-control window.
+   */
+  @Setup(Level.Trial)
+  public void setup() throws Exception {
+    super.setup(ExecutorType.DIRECT,
+        ExecutorType.DIRECT,
+        MessageSize.SMALL,
+        MessageSize.SMALL,
+        FlowWindowSize.MEDIUM,
+        ChannelType.NIO,
+        maxConcurrentStreams,
+        channelCount);
+    callCounter = new AtomicLong();
+    completed = new AtomicBoolean();
+    record = new AtomicBoolean();
+    latch = startStreamingCalls(maxConcurrentStreams, callCounter, record, completed, 1);
+  }
+
+  /**
+   * Stop the running calls then stop the server and client channels.
+   */
+  @Override
+  @TearDown(Level.Trial)
+  public void teardown() throws Exception {
+    completed.set(true);
+    if (!latch.await(5, TimeUnit.SECONDS)) {
+      logger.warning("Failed to shutdown all calls.");
+    }
+    super.teardown();
+  }
+
+  /**
+   * Measure throughput of unary calls. The calls are already running, we just observe a counter
+   * of received responses.
+   */
+  @Benchmark
+  public void pingPong(AdditionalCounters counters) throws Exception {
+    record.set(true);
+    // No need to do anything, just sleep here.
+    Thread.sleep(1001);
+    record.set(false);
+  }
+
+  /**
+   * Useful for triggering a subset of the benchmark in a profiler.
+   */
+  public static void main(String[] argv) throws Exception {
+    StreamingPingPongsPerSecondBenchmark bench = new StreamingPingPongsPerSecondBenchmark();
+    bench.setup();
+    Thread.sleep(30000);
+    bench.teardown();
+    System.exit(0);
+  }
+}
diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/StreamingResponseBandwidthBenchmark.java b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/StreamingResponseBandwidthBenchmark.java
new file mode 100644
index 0000000..fcd4dff
--- /dev/null
+++ b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/StreamingResponseBandwidthBenchmark.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.netty;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import org.openjdk.jmh.annotations.AuxCounters;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+/**
+ * Benchmark intended to test response bandwidth in bytes/sec for streaming calls by permuting
+ * payload size and flow-control windows with number of concurrent calls. Async stubs are used
+ * to avoid context-switching overheads.
+ */
+@State(Scope.Benchmark)
+@Fork(1)
+public class StreamingResponseBandwidthBenchmark extends AbstractBenchmark {
+
+  @Param({"1", "10"})
+  public int maxConcurrentStreams = 1;
+
+  @Param({"LARGE", "JUMBO"})
+  public MessageSize responseSize = MessageSize.JUMBO;
+
+  @Param({"MEDIUM", "LARGE", "JUMBO"})
+  public FlowWindowSize clientInboundFlowWindow = FlowWindowSize.MEDIUM;
+
+  private static AtomicLong callCounter;
+  private AtomicBoolean completed;
+  private AtomicBoolean record;
+  private CountDownLatch latch;
+
+  /**
+   * Use an AuxCounter so we can measure that calls as they occur without consuming CPU
+   * in the benchmark method.
+   */
+  @AuxCounters
+  @State(Scope.Thread)
+  public static class AdditionalCounters {
+
+    @Setup(Level.Iteration)
+    public void clean() {
+      callCounter.set(0);
+    }
+
+    public long megabitsPerSecond() {
+      return (callCounter.get() * 8) >> 20;
+    }
+  }
+
+  /**
+   * Setup with direct executors and one channel.
+   */
+  @Setup(Level.Trial)
+  public void setup() throws Exception {
+    super.setup(ExecutorType.DIRECT,
+        ExecutorType.DIRECT,
+        MessageSize.SMALL,
+        responseSize,
+        clientInboundFlowWindow,
+        ChannelType.NIO,
+        maxConcurrentStreams,
+        1);
+    callCounter = new AtomicLong();
+    completed = new AtomicBoolean();
+    record = new AtomicBoolean();
+    latch = startFlowControlledStreamingCalls(maxConcurrentStreams, callCounter, record, completed,
+        responseSize.bytes());
+  }
+
+  /**
+   * Stop the running calls then stop the server and client channels.
+   */
+  @Override
+  @TearDown(Level.Trial)
+  public void teardown() throws Exception {
+    completed.set(true);
+    if (!latch.await(5, TimeUnit.SECONDS)) {
+      System.err.println("Failed to shutdown all calls.");
+    }
+    super.teardown();
+  }
+
+  /**
+   * Measure bandwidth of streamed responses.
+   */
+  @Benchmark
+  public void stream(AdditionalCounters counters) throws Exception {
+    record.set(true);
+    // No need to do anything, just sleep here.
+    Thread.sleep(1001);
+    record.set(false);
+  }
+
+  /**
+   * Useful for triggering a subset of the benchmark in a profiler.
+   */
+  public static void main(String[] argv) throws Exception {
+    StreamingResponseBandwidthBenchmark bench = new StreamingResponseBandwidthBenchmark();
+    bench.setup();
+    Thread.sleep(30000);
+    bench.teardown();
+    System.exit(0);
+  }
+}
diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/UnaryCallQpsBenchmark.java b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/UnaryCallQpsBenchmark.java
new file mode 100644
index 0000000..6e12513
--- /dev/null
+++ b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/UnaryCallQpsBenchmark.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.netty;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import org.openjdk.jmh.annotations.AuxCounters;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+/**
+ * Benchmark using configuration intended to allow maximum QPS for unary calls.
+ */
+@State(Scope.Benchmark)
+@Fork(1)
+public class UnaryCallQpsBenchmark extends AbstractBenchmark {
+
+  @Param({"1", "2", "4", "8"})
+  public int channelCount = 4;
+
+  @Param({"10", "100", "1000"})
+  public int maxConcurrentStreams = 100;
+
+  private static AtomicLong callCounter;
+  private AtomicBoolean completed;
+
+  /**
+   * Use an AuxCounter so we can measure that calls as they occur without consuming CPU
+   * in the benchmark method.
+   */
+  @AuxCounters
+  @State(Scope.Thread)
+  public static class AdditionalCounters {
+
+    @Setup(Level.Iteration)
+    public void clean() {
+      callCounter.set(0);
+    }
+
+    public long callsPerSecond() {
+      return callCounter.get();
+    }
+  }
+
+  /**
+   * Setup with direct executors, small payloads and a large flow control window.
+   */
+  @Setup(Level.Trial)
+  public void setup() throws Exception {
+    super.setup(ExecutorType.DIRECT,
+        ExecutorType.DIRECT,
+        MessageSize.SMALL,
+        MessageSize.SMALL,
+        FlowWindowSize.LARGE,
+        ChannelType.NIO,
+        maxConcurrentStreams,
+        channelCount);
+    callCounter = new AtomicLong();
+    completed = new AtomicBoolean();
+    startUnaryCalls(maxConcurrentStreams, callCounter, completed, 1);
+  }
+
+  /**
+   * Stop the running calls then stop the server and client channels.
+   */
+  @Override
+  @TearDown(Level.Trial)
+  public void teardown() throws Exception {
+    completed.set(true);
+    Thread.sleep(5000);
+    super.teardown();
+  }
+
+  /**
+   * Measure throughput of unary calls. The calls are already running, we just observe a counter
+   * of received responses.
+   */
+  @Benchmark
+  public void unary(AdditionalCounters counters) throws Exception {
+    // No need to do anything, just sleep here.
+    Thread.sleep(1001);
+  }
+
+  /**
+   * Useful for triggering a subset of the benchmark in a profiler.
+   */
+  public static void main(String[] argv) throws Exception {
+    UnaryCallQpsBenchmark bench = new UnaryCallQpsBenchmark();
+    bench.setup();
+    Thread.sleep(30000);
+    bench.teardown();
+    System.exit(0);
+  }
+}
diff --git a/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/UnaryCallResponseBandwidthBenchmark.java b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/UnaryCallResponseBandwidthBenchmark.java
new file mode 100644
index 0000000..80e0eec
--- /dev/null
+++ b/benchmarks/src/jmh/java/io/grpc/benchmarks/netty/UnaryCallResponseBandwidthBenchmark.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.netty;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import org.openjdk.jmh.annotations.AuxCounters;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+/**
+ * Benchmark intended to test response bandwidth in bytes/sec for unary calls  by permuting
+ * payload size and flow-control windows with number of concurrent calls. Async stubs are used
+ * to avoid context-switching overheads.
+ */
+@State(Scope.Benchmark)
+@Fork(1)
+public class UnaryCallResponseBandwidthBenchmark extends AbstractBenchmark {
+
+  @Param({"1", "10"})
+  public int maxConcurrentStreams = 1;
+
+  @Param({"LARGE", "JUMBO"})
+  public MessageSize responseSize = MessageSize.JUMBO;
+
+  @Param({"MEDIUM", "LARGE", "JUMBO"})
+  public FlowWindowSize clientInboundFlowWindow = FlowWindowSize.MEDIUM;
+
+  private static AtomicLong callCounter;
+  private AtomicBoolean completed;
+
+  /**
+   * Use an AuxCounter so we can measure that calls as they occur without consuming CPU
+   * in the benchmark method.
+   */
+  @AuxCounters
+  @State(Scope.Thread)
+  public static class AdditionalCounters {
+
+    @Setup(Level.Iteration)
+    public void clean() {
+      callCounter.set(0);
+    }
+
+    public long megabitsPerSecond() {
+      // Convert bytes into megabits
+      return (callCounter.get() * 8) >> 20;
+    }
+  }
+
+  /**
+   * Setup with direct executors, small payloads and a large flow control window.
+   */
+  @Setup(Level.Trial)
+  public void setup() throws Exception {
+    super.setup(ExecutorType.DIRECT,
+        ExecutorType.DIRECT,
+        MessageSize.SMALL,
+        responseSize,
+        clientInboundFlowWindow,
+        ChannelType.NIO,
+        maxConcurrentStreams,
+        1);
+    callCounter = new AtomicLong();
+    completed = new AtomicBoolean();
+    startUnaryCalls(maxConcurrentStreams, callCounter, completed, responseSize.bytes());
+  }
+
+  /**
+   * Stop the running calls then stop the server and client channels.
+   */
+  @Override
+  @TearDown(Level.Trial)
+  public void teardown() throws Exception {
+    completed.set(true);
+    Thread.sleep(5000);
+    super.teardown();
+  }
+
+  /**
+   * Measure bandwidth of unary call responses. The calls are already running, we just observe a
+   * counter of received responses.
+   */
+  @Benchmark
+  public void unary(AdditionalCounters counters) throws Exception {
+    // No need to do anything, just sleep here.
+    Thread.sleep(1001);
+  }
+
+  /**
+   * Useful for triggering a subset of the benchmark in a profiler.
+   */
+  public static void main(String[] argv) throws Exception {
+    UnaryCallResponseBandwidthBenchmark bench = new UnaryCallResponseBandwidthBenchmark();
+    bench.setup();
+    Thread.sleep(30000);
+    bench.teardown();
+    System.exit(0);
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/ByteBufInputStream.java b/benchmarks/src/main/java/io/grpc/benchmarks/ByteBufInputStream.java
new file mode 100644
index 0000000..c3e9e0a
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/ByteBufInputStream.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks;
+
+import io.grpc.Drainable;
+import io.grpc.KnownLength;
+import io.netty.buffer.ByteBuf;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A {@link Drainable} {@code InputStream} that reads an {@link ByteBuf}.
+ */
+@SuppressWarnings("InputStreamSlowMultibyteRead") // doesn't matter if slow. It'll throw
+public class ByteBufInputStream extends InputStream
+    implements Drainable, KnownLength {
+
+  private ByteBuf buf;
+
+  ByteBufInputStream(ByteBuf buf) {
+    this.buf = buf;
+  }
+
+  @Override
+  public int drainTo(OutputStream target) throws IOException {
+    int readableBytes = buf.readableBytes();
+    buf.readBytes(target, readableBytes);
+    buf = null;
+    return readableBytes;
+  }
+
+  @Override
+  public int available() throws IOException {
+    if (buf != null) {
+      return buf.readableBytes();
+    }
+    return 0;
+  }
+
+  @Override
+  public int read() throws IOException {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/ByteBufOutputMarshaller.java b/benchmarks/src/main/java/io/grpc/benchmarks/ByteBufOutputMarshaller.java
new file mode 100644
index 0000000..49ae105
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/ByteBufOutputMarshaller.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks;
+
+import io.grpc.MethodDescriptor;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.EmptyByteBuf;
+import io.netty.buffer.PooledByteBufAllocator;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Simple {@link io.grpc.MethodDescriptor.Marshaller} for Netty's {@link ByteBuf}.
+ */
+public class ByteBufOutputMarshaller implements MethodDescriptor.Marshaller<ByteBuf> {
+
+  public static final EmptyByteBuf EMPTY_BYTE_BUF =
+      new EmptyByteBuf(PooledByteBufAllocator.DEFAULT);
+
+  @Override
+  public InputStream stream(ByteBuf value) {
+    return new ByteBufInputStream(value);
+  }
+
+  @Override
+  public ByteBuf parse(InputStream stream) {
+    try {
+      // We don't do anything with the message and it's already been read into buffers
+      // so just skip copying it.
+      stream.skip(stream.available());
+      return EMPTY_BYTE_BUF;
+    } catch (IOException ioe) {
+      throw new RuntimeException(ioe);
+    }
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/SocketAddressValidator.java b/benchmarks/src/main/java/io/grpc/benchmarks/SocketAddressValidator.java
new file mode 100644
index 0000000..6c6d597
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/SocketAddressValidator.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks;
+
+import com.google.errorprone.annotations.Immutable;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+/**
+ * Verifies whether or not the given {@link SocketAddress} is valid.
+ */
+@Immutable
+public interface SocketAddressValidator {
+  /**
+   * Verifier for {@link InetSocketAddress}es.
+   */
+  SocketAddressValidator INET = new SocketAddressValidator() {
+    @Override
+    public boolean isValidSocketAddress(SocketAddress address) {
+      return address instanceof InetSocketAddress;
+    }
+  };
+
+  /**
+   * Verifier for Netty Unix Domain Socket addresses.
+   */
+  SocketAddressValidator UDS = new SocketAddressValidator() {
+    @Override
+    public boolean isValidSocketAddress(SocketAddress address) {
+      return "DomainSocketAddress".equals(address.getClass().getSimpleName());
+    }
+  };
+
+  /**
+   * Returns {@code true} if the given address is valid.
+   */
+  boolean isValidSocketAddress(SocketAddress address);
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/Transport.java b/benchmarks/src/main/java/io/grpc/benchmarks/Transport.java
new file mode 100644
index 0000000..0097bd9
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/Transport.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks;
+
+import java.net.SocketAddress;
+
+/**
+ * All of the supported transports.
+ */
+public enum Transport {
+  NETTY_NIO(true, "The Netty Java NIO transport. Using this with TLS requires "
+      + "that the Java bootclasspath be configured with Jetty ALPN boot.",
+      SocketAddressValidator.INET),
+  NETTY_EPOLL(true, "The Netty native EPOLL transport. Using this with TLS requires that "
+      + "OpenSSL be installed and configured as described in "
+      + "http://netty.io/wiki/forked-tomcat-native.html. Only supported on Linux.",
+      SocketAddressValidator.INET),
+  NETTY_UNIX_DOMAIN_SOCKET(false, "The Netty Unix Domain Socket transport. This currently "
+      + "does not support TLS.", SocketAddressValidator.UDS),
+  OK_HTTP(true, "The OkHttp transport.", SocketAddressValidator.INET);
+
+  public final boolean tlsSupported;
+  final String description;
+  final SocketAddressValidator socketAddressValidator;
+
+  Transport(boolean tlsSupported, String description,
+            SocketAddressValidator socketAddressValidator) {
+    this.tlsSupported = tlsSupported;
+    this.description = description;
+    this.socketAddressValidator = socketAddressValidator;
+  }
+
+  /**
+   * Validates the given address for this transport.
+   *
+   * @throws IllegalArgumentException if the given address is invalid for this transport.
+   */
+  public void validateSocketAddress(SocketAddress address) {
+    if (!socketAddressValidator.isValidSocketAddress(address)) {
+      throw new IllegalArgumentException(
+          "Invalid address " + address + " for transport " + this);
+    }
+  }
+
+  /**
+   * Describe the {@link Transport}.
+   */
+  public static String getDescriptionString() {
+    StringBuilder builder = new StringBuilder("Select the transport to use. Options:\n");
+    boolean first = true;
+    for (Transport transport : Transport.values()) {
+      if (!first) {
+        builder.append("\n");
+      }
+      builder.append(transport.name().toLowerCase());
+      builder.append(": ");
+      builder.append(transport.description);
+      first = false;
+    }
+    return builder.toString();
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/Utils.java b/benchmarks/src/main/java/io/grpc/benchmarks/Utils.java
new file mode 100644
index 0000000..9a3d1f6
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/Utils.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks;
+
+import static java.util.concurrent.ForkJoinPool.defaultForkJoinWorkerThreadFactory;
+
+import com.google.common.util.concurrent.UncaughtExceptionHandlers;
+import com.google.protobuf.ByteString;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.Status;
+import io.grpc.benchmarks.proto.Messages;
+import io.grpc.benchmarks.proto.Messages.Payload;
+import io.grpc.benchmarks.proto.Messages.SimpleRequest;
+import io.grpc.benchmarks.proto.Messages.SimpleResponse;
+import io.grpc.internal.testing.TestUtils;
+import io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.okhttp.OkHttpChannelBuilder;
+import io.grpc.okhttp.internal.Platform;
+import io.netty.channel.epoll.EpollDomainSocketChannel;
+import io.netty.channel.epoll.EpollEventLoopGroup;
+import io.netty.channel.epoll.EpollSocketChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.channel.unix.DomainSocketAddress;
+import io.netty.util.concurrent.DefaultThreadFactory;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.SocketAddress;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory;
+import java.util.concurrent.ForkJoinWorkerThread;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.annotation.Nullable;
+import org.HdrHistogram.Histogram;
+
+/**
+ * Utility methods to support benchmarking classes.
+ */
+public final class Utils {
+  private static final String UNIX_DOMAIN_SOCKET_PREFIX = "unix://";
+
+  // The histogram can record values between 1 microsecond and 1 min.
+  public static final long HISTOGRAM_MAX_VALUE = 60000000L;
+
+  // Value quantization will be no more than 1%. See the README of HdrHistogram for more details.
+  public static final int HISTOGRAM_PRECISION = 2;
+
+  public static final int DEFAULT_FLOW_CONTROL_WINDOW =
+      NettyChannelBuilder.DEFAULT_FLOW_CONTROL_WINDOW;
+
+  private Utils() {
+  }
+
+  public static boolean parseBoolean(String value) {
+    return value.isEmpty() || Boolean.parseBoolean(value);
+  }
+
+  /**
+   * Parse a {@link SocketAddress} from the given string.
+   */
+  public static SocketAddress parseSocketAddress(String value) {
+    if (value.startsWith(UNIX_DOMAIN_SOCKET_PREFIX)) {
+      // Unix Domain Socket address.
+      // Create the underlying file for the Unix Domain Socket.
+      String filePath = value.substring(UNIX_DOMAIN_SOCKET_PREFIX.length());
+      File file = new File(filePath);
+      if (!file.isAbsolute()) {
+        throw new IllegalArgumentException("File path must be absolute: " + filePath);
+      }
+      try {
+        if (file.createNewFile()) {
+          // If this application created the file, delete it when the application exits.
+          file.deleteOnExit();
+        }
+      } catch (IOException ex) {
+        throw new RuntimeException(ex);
+      }
+      // Create the SocketAddress referencing the file.
+      return new DomainSocketAddress(file);
+    } else {
+      // Standard TCP/IP address.
+      String[] parts = value.split(":", 2);
+      if (parts.length < 2) {
+        throw new IllegalArgumentException(
+            "Address must be a unix:// path or be in the form host:port. Got: " + value);
+      }
+      String host = parts[0];
+      int port = Integer.parseInt(parts[1]);
+      return new InetSocketAddress(host, port);
+    }
+  }
+
+  private static OkHttpChannelBuilder newOkHttpClientChannel(
+      SocketAddress address, boolean tls, boolean testca) {
+    InetSocketAddress addr = (InetSocketAddress) address;
+    OkHttpChannelBuilder builder =
+        OkHttpChannelBuilder.forAddress(addr.getHostName(), addr.getPort());
+    if (!tls) {
+      builder.usePlaintext();
+    } else if (testca) {
+      try {
+        builder.sslSocketFactory(TestUtils.newSslSocketFactoryForCa(
+            Platform.get().getProvider(),
+            TestUtils.loadCert("ca.pem")));
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+    return builder;
+  }
+
+  private static NettyChannelBuilder newNettyClientChannel(Transport transport,
+      SocketAddress address, boolean tls, boolean testca, int flowControlWindow)
+      throws IOException {
+    NettyChannelBuilder builder =
+        NettyChannelBuilder.forAddress(address).flowControlWindow(flowControlWindow);
+    if (!tls) {
+      builder.usePlaintext();
+    } else if (testca) {
+      File cert = TestUtils.loadCert("ca.pem");
+      builder.sslContext(GrpcSslContexts.forClient().trustManager(cert).build());
+    }
+
+    DefaultThreadFactory tf = new DefaultThreadFactory("client-elg-", true /*daemon */);
+    switch (transport) {
+      case NETTY_NIO:
+        builder
+            .eventLoopGroup(new NioEventLoopGroup(0, tf))
+            .channelType(NioSocketChannel.class);
+        break;
+
+      case NETTY_EPOLL:
+        // These classes only work on Linux.
+        builder
+            .eventLoopGroup(new EpollEventLoopGroup(0, tf))
+            .channelType(EpollSocketChannel.class);
+        break;
+
+      case NETTY_UNIX_DOMAIN_SOCKET:
+        // These classes only work on Linux.
+        builder
+            .eventLoopGroup(new EpollEventLoopGroup(0, tf))
+            .channelType(EpollDomainSocketChannel.class);
+        break;
+
+      default:
+        // Should never get here.
+        throw new IllegalArgumentException("Unsupported transport: " + transport);
+    }
+    return builder;
+  }
+
+  private static ExecutorService clientExecutor;
+
+  private static synchronized ExecutorService getExecutor() {
+    if (clientExecutor == null) {
+      clientExecutor = new ForkJoinPool(
+          Runtime.getRuntime().availableProcessors(),
+          new ForkJoinWorkerThreadFactory() {
+            final AtomicInteger num = new AtomicInteger();
+            @Override
+            public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
+              ForkJoinWorkerThread thread = defaultForkJoinWorkerThreadFactory.newThread(pool);
+              thread.setDaemon(true);
+              thread.setName("grpc-client-app-" + "-" + num.getAndIncrement());
+              return thread;
+            }
+          }, UncaughtExceptionHandlers.systemExit(), true /* async */);
+    }
+    return clientExecutor;
+  }
+
+  /**
+   * Create a {@link ManagedChannel} for the given parameters.
+   */
+  public static ManagedChannel newClientChannel(Transport transport, SocketAddress address,
+        boolean tls, boolean testca, @Nullable String authorityOverride,
+        int flowControlWindow, boolean directExecutor) {
+    ManagedChannelBuilder<?> builder;
+    if (transport == Transport.OK_HTTP) {
+      builder = newOkHttpClientChannel(address, tls, testca);
+    } else {
+      try {
+        builder = newNettyClientChannel(transport, address, tls, testca, flowControlWindow);
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+    if (authorityOverride != null) {
+      builder.overrideAuthority(authorityOverride);
+    }
+
+    if (directExecutor) {
+      builder.directExecutor();
+    } else {
+      // TODO(carl-mastrangelo): This should not be necessary.  I don't know where this should be
+      // put.  Move it somewhere else, or remove it if no longer necessary.
+      // See: https://github.com/grpc/grpc-java/issues/2119
+      builder.executor(getExecutor());
+    }
+
+    return builder.build();
+  }
+
+  /**
+   * Save a {@link Histogram} to a file.
+   */
+  public static void saveHistogram(Histogram histogram, String filename) throws IOException {
+    File file;
+    PrintStream log = null;
+    try {
+      file = new File(filename);
+      if (file.exists() && !file.delete()) {
+        System.err.println("Failed deleting previous histogram file: " + file.getAbsolutePath());
+      }
+      log = new PrintStream(new FileOutputStream(file), false);
+      histogram.outputPercentileDistribution(log, 1.0);
+    } finally {
+      if (log != null) {
+        log.close();
+      }
+    }
+  }
+
+  /**
+   * Construct a {@link SimpleResponse} for the given request.
+   */
+  public static SimpleResponse makeResponse(SimpleRequest request) {
+    if (request.getResponseSize() > 0) {
+      if (!Messages.PayloadType.COMPRESSABLE.equals(request.getResponseType())) {
+        throw Status.INTERNAL.augmentDescription("Error creating payload.").asRuntimeException();
+      }
+
+      ByteString body = ByteString.copyFrom(new byte[request.getResponseSize()]);
+      Messages.PayloadType type = request.getResponseType();
+
+      Payload payload = Payload.newBuilder().setType(type).setBody(body).build();
+      return SimpleResponse.newBuilder().setPayload(payload).build();
+    }
+    return SimpleResponse.getDefaultInstance();
+  }
+
+  /**
+   * Construct a {@link SimpleRequest} with the specified dimensions.
+   */
+  public static SimpleRequest makeRequest(Messages.PayloadType payloadType, int reqLength,
+                                          int respLength) {
+    ByteString body = ByteString.copyFrom(new byte[reqLength]);
+    Payload payload = Payload.newBuilder()
+        .setType(payloadType)
+        .setBody(body)
+        .build();
+
+    return SimpleRequest.newBuilder()
+        .setResponseType(payloadType)
+        .setResponseSize(respLength)
+        .setPayload(payload)
+        .build();
+  }
+
+  /**
+   * Picks a port that is not used right at this moment.
+   * Warning: Not thread safe. May see "BindException: Address already in use: bind" if using the
+   * returned port to create a new server socket when other threads/processes are concurrently
+   * creating new sockets without a specific port.
+   */
+  public static int pickUnusedPort() {
+    try {
+      ServerSocket serverSocket = new ServerSocket(0);
+      int port = serverSocket.getLocalPort();
+      serverSocket.close();
+      return port;
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/driver/LoadClient.java b/benchmarks/src/main/java/io/grpc/benchmarks/driver/LoadClient.java
new file mode 100644
index 0000000..f6779da
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/driver/LoadClient.java
@@ -0,0 +1,540 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.driver;
+
+import com.sun.management.OperatingSystemMXBean;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.benchmarks.Transport;
+import io.grpc.benchmarks.Utils;
+import io.grpc.benchmarks.proto.BenchmarkServiceGrpc;
+import io.grpc.benchmarks.proto.Control;
+import io.grpc.benchmarks.proto.Messages;
+import io.grpc.benchmarks.proto.Payloads;
+import io.grpc.benchmarks.proto.Stats;
+import io.grpc.stub.ClientCalls;
+import io.grpc.stub.StreamObserver;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.PooledByteBufAllocator;
+import io.netty.channel.epoll.Epoll;
+import io.netty.util.concurrent.DefaultThreadFactory;
+import java.lang.management.ManagementFactory;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.LockSupport;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.HdrHistogram.Histogram;
+import org.HdrHistogram.LogarithmicIterator;
+import org.HdrHistogram.Recorder;
+import org.apache.commons.math3.distribution.ExponentialDistribution;
+
+/**
+ * Implements the client-side contract for the load testing scenarios.
+ */
+class LoadClient {
+
+  private static final Logger log = Logger.getLogger(LoadClient.class.getName());
+  private ByteBuf genericRequest;
+
+  private final Control.ClientConfig config;
+  private final ExponentialDistribution distribution;
+  private volatile boolean shutdown;
+  private final int threadCount;
+
+  ManagedChannel[] channels;
+  BenchmarkServiceGrpc.BenchmarkServiceBlockingStub[] blockingStubs;
+  BenchmarkServiceGrpc.BenchmarkServiceStub[] asyncStubs;
+  Recorder recorder;
+  private ExecutorService fixedThreadPool;
+  private Messages.SimpleRequest simpleRequest;
+  private final OperatingSystemMXBean osBean;
+  private long lastMarkCpuTime;
+
+  LoadClient(Control.ClientConfig config) throws Exception {
+    log.log(Level.INFO, "Client Config \n" + config.toString());
+    this.config = config;
+    // Create the channels
+    channels = new ManagedChannel[config.getClientChannels()];
+    for (int i = 0; i < config.getClientChannels(); i++) {
+      channels[i] =
+          Utils.newClientChannel(
+              Epoll.isAvailable() ?  Transport.NETTY_EPOLL : Transport.NETTY_NIO,
+              Utils.parseSocketAddress(config.getServerTargets(i % config.getServerTargetsCount())),
+              config.hasSecurityParams(),
+              config.hasSecurityParams() && config.getSecurityParams().getUseTestCa(),
+              config.hasSecurityParams()
+                  ? config.getSecurityParams().getServerHostOverride()
+                  : null,
+              Utils.DEFAULT_FLOW_CONTROL_WINDOW,
+              false);
+    }
+
+    // Create a stub per channel
+    if (config.getClientType() == Control.ClientType.ASYNC_CLIENT) {
+      asyncStubs = new BenchmarkServiceGrpc.BenchmarkServiceStub[channels.length];
+      for (int i = 0; i < channels.length; i++) {
+        asyncStubs[i] = BenchmarkServiceGrpc.newStub(channels[i]);
+      }
+    } else {
+      blockingStubs = new BenchmarkServiceGrpc.BenchmarkServiceBlockingStub[channels.length];
+      for (int i = 0; i < channels.length; i++) {
+        blockingStubs[i] = BenchmarkServiceGrpc.newBlockingStub(channels[i]);
+      }
+    }
+
+    // Determine no of threads
+    if (config.getClientType() == Control.ClientType.SYNC_CLIENT) {
+      threadCount = config.getOutstandingRpcsPerChannel() * config.getClientChannels();
+    } else {
+      threadCount = config.getAsyncClientThreads() == 0
+          ? Runtime.getRuntime().availableProcessors()
+          : config.getAsyncClientThreads();
+    }
+    // Use a fixed sized pool of daemon threads.
+    fixedThreadPool = Executors.newFixedThreadPool(threadCount,
+        new DefaultThreadFactory("client-worker", true));
+
+    // Create the load distribution
+    switch (config.getLoadParams().getLoadCase()) {
+      case CLOSED_LOOP:
+        distribution = null;
+        break;
+      case LOAD_NOT_SET:
+        distribution = null;
+        break;
+      case POISSON:
+        // Mean of exp distribution per thread is <no threads> / <offered load per second>
+        distribution = new ExponentialDistribution(
+            threadCount / config.getLoadParams().getPoisson().getOfferedLoad());
+        break;
+      default:
+        throw new IllegalArgumentException("Scenario not implemented");
+    }
+
+    // Create payloads
+    switch (config.getPayloadConfig().getPayloadCase()) {
+      case SIMPLE_PARAMS: {
+        Payloads.SimpleProtoParams simpleParams = config.getPayloadConfig().getSimpleParams();
+        simpleRequest = Utils.makeRequest(Messages.PayloadType.COMPRESSABLE,
+            simpleParams.getReqSize(), simpleParams.getRespSize());
+        break;
+      }
+      case BYTEBUF_PARAMS: {
+        PooledByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;
+        genericRequest = alloc.buffer(config.getPayloadConfig().getBytebufParams().getRespSize());
+        if (genericRequest.capacity() > 0) {
+          genericRequest.writerIndex(genericRequest.capacity() - 1);
+        }
+        break;
+      }
+      default: {
+        // Not implemented yet
+        throw new IllegalArgumentException("Scenario not implemented");
+      }
+    }
+
+    List<OperatingSystemMXBean> beans =
+        ManagementFactory.getPlatformMXBeans(OperatingSystemMXBean.class);
+    if (!beans.isEmpty()) {
+      osBean = beans.get(0);
+    } else {
+      osBean = null;
+    }
+
+    // Create the histogram recorder
+    recorder = new Recorder((long) config.getHistogramParams().getMaxPossible(), 3);
+  }
+
+  /**
+   * Start the load scenario.
+   */
+  void start() {
+    Runnable r;
+    for (int i = 0; i < threadCount; i++) {
+      r = null;
+      switch (config.getPayloadConfig().getPayloadCase()) {
+        case SIMPLE_PARAMS: {
+          if (config.getClientType() == Control.ClientType.SYNC_CLIENT) {
+            if (config.getRpcType() == Control.RpcType.UNARY) {
+              r = new BlockingUnaryWorker(blockingStubs[i % blockingStubs.length]);
+            }
+          } else if (config.getClientType() == Control.ClientType.ASYNC_CLIENT) {
+            if (config.getRpcType() == Control.RpcType.UNARY) {
+              r = new AsyncUnaryWorker(asyncStubs[i % asyncStubs.length]);
+            } else if (config.getRpcType() == Control.RpcType.STREAMING) {
+              r = new AsyncPingPongWorker(asyncStubs[i % asyncStubs.length]);
+            }
+          }
+          break;
+        }
+        case BYTEBUF_PARAMS: {
+          if (config.getClientType() == Control.ClientType.SYNC_CLIENT) {
+            if (config.getRpcType() == Control.RpcType.UNARY) {
+              r = new GenericBlockingUnaryWorker(channels[i % channels.length]);
+            }
+          } else if (config.getClientType() == Control.ClientType.ASYNC_CLIENT) {
+            if (config.getRpcType() == Control.RpcType.UNARY) {
+              r = new GenericAsyncUnaryWorker(channels[i % channels.length]);
+            } else if (config.getRpcType() == Control.RpcType.STREAMING) {
+              r = new GenericAsyncPingPongWorker(channels[i % channels.length]);
+            }
+          }
+
+          break;
+        }
+        default: {
+          throw Status.UNIMPLEMENTED.withDescription(
+              "Unknown payload case " + config.getPayloadConfig().getPayloadCase().name())
+              .asRuntimeException();
+        }
+      }
+      if (r == null) {
+        throw new IllegalStateException(config.getRpcType().name()
+            + " not supported for client type "
+            + config.getClientType());
+      }
+      fixedThreadPool.execute(r);
+    }
+    if (osBean != null) {
+      lastMarkCpuTime = osBean.getProcessCpuTime();
+    }
+  }
+
+  /**
+   * Take a snapshot of the statistics which can be returned to the driver.
+   */
+  Stats.ClientStats getStats() {
+    Histogram intervalHistogram = recorder.getIntervalHistogram();
+
+    Stats.ClientStats.Builder statsBuilder = Stats.ClientStats.newBuilder();
+    Stats.HistogramData.Builder latenciesBuilder = statsBuilder.getLatenciesBuilder();
+    double resolution = 1.0 + Math.max(config.getHistogramParams().getResolution(), 0.01);
+    LogarithmicIterator logIterator = new LogarithmicIterator(intervalHistogram, 1,
+        resolution);
+    double base = 1;
+    while (logIterator.hasNext()) {
+      latenciesBuilder.addBucket((int) logIterator.next().getCountAddedInThisIterationStep());
+      base = base * resolution;
+    }
+    // Driver expects values for all buckets in the range, not just the range of buckets that
+    // have values.
+    while (base < config.getHistogramParams().getMaxPossible()) {
+      latenciesBuilder.addBucket(0);
+      base = base * resolution;
+    }
+    latenciesBuilder.setMaxSeen(intervalHistogram.getMaxValue());
+    latenciesBuilder.setMinSeen(intervalHistogram.getMinNonZeroValue());
+    latenciesBuilder.setCount(intervalHistogram.getTotalCount());
+    latenciesBuilder.setSum(intervalHistogram.getMean()
+        * intervalHistogram.getTotalCount());
+    // TODO: No support for sum of squares
+
+    statsBuilder.setTimeElapsed((intervalHistogram.getEndTimeStamp()
+        - intervalHistogram.getStartTimeStamp()) / 1000.0);
+    if (osBean != null) {
+      // Report all the CPU time as user-time  (which is intentionally incorrect)
+      long nowCpu = osBean.getProcessCpuTime();
+      statsBuilder.setTimeUser(((double) nowCpu - lastMarkCpuTime) / 1000000000.0);
+      lastMarkCpuTime = nowCpu;
+    }
+    return statsBuilder.build();
+  }
+
+  /**
+   * Shutdown the scenario as cleanly as possible.
+   */
+  void shutdownNow() {
+    shutdown = true;
+    for (int i = 0; i < channels.length; i++) {
+      // Initiate channel shutdown
+      channels[i].shutdown();
+    }
+    for (int i = 0; i < channels.length; i++) {
+      try {
+        // Wait for channel termination
+        channels[i].awaitTermination(1, TimeUnit.SECONDS);
+      } catch (InterruptedException ie) {
+        channels[i].shutdownNow();
+      }
+    }
+    fixedThreadPool.shutdownNow();
+  }
+
+  /**
+   * Record the event elapsed time to the histogram and delay initiation of the next event based
+   * on the load distribution.
+   */
+  void delay(long alreadyElapsed) {
+    recorder.recordValue(alreadyElapsed);
+    if (distribution != null) {
+      long nextPermitted = Math.round(distribution.sample() * 1000000000.0);
+      if (nextPermitted > alreadyElapsed) {
+        LockSupport.parkNanos(nextPermitted - alreadyElapsed);
+      }
+    }
+  }
+
+  /**
+   * Worker which executes blocking unary calls. Event timing is the duration between sending the
+   * request and receiving the response.
+   */
+  class BlockingUnaryWorker implements Runnable {
+    final BenchmarkServiceGrpc.BenchmarkServiceBlockingStub stub;
+
+    private BlockingUnaryWorker(BenchmarkServiceGrpc.BenchmarkServiceBlockingStub stub) {
+      this.stub = stub;
+    }
+
+    @Override
+    public void run() {
+      while (!shutdown) {
+        long now = System.nanoTime();
+        stub.unaryCall(simpleRequest);
+        delay(System.nanoTime() - now);
+      }
+    }
+  }
+
+  /**
+   * Worker which executes async unary calls. Event timing is the duration between sending the
+   * request and receiving the response.
+   */
+  private class AsyncUnaryWorker implements Runnable {
+    final BenchmarkServiceGrpc.BenchmarkServiceStub stub;
+    final Semaphore maxOutstanding = new Semaphore(config.getOutstandingRpcsPerChannel());
+
+    AsyncUnaryWorker(BenchmarkServiceGrpc.BenchmarkServiceStub stub) {
+      this.stub = stub;
+    }
+
+    @Override
+    public void run() {
+      while (true) {
+        maxOutstanding.acquireUninterruptibly();
+        if (shutdown) {
+          maxOutstanding.release();
+          return;
+        }
+        stub.unaryCall(simpleRequest, new StreamObserver<Messages.SimpleResponse>() {
+          long now = System.nanoTime();
+          @Override
+          public void onNext(Messages.SimpleResponse value) {
+
+          }
+
+          @Override
+          public void onError(Throwable t) {
+            maxOutstanding.release();
+            Level level = shutdown ? Level.FINE : Level.INFO;
+            log.log(level, "Error in AsyncUnary call", t);
+          }
+
+          @Override
+          public void onCompleted() {
+            delay(System.nanoTime() - now);
+            maxOutstanding.release();
+          }
+        });
+      }
+    }
+  }
+
+  /**
+   * Worker which executes a streaming ping-pong call. Event timing is the duration between
+   * sending the ping and receiving the pong.
+   */
+  private class AsyncPingPongWorker implements Runnable {
+    final BenchmarkServiceGrpc.BenchmarkServiceStub stub;
+    final Semaphore maxOutstanding = new Semaphore(config.getOutstandingRpcsPerChannel());
+
+    AsyncPingPongWorker(BenchmarkServiceGrpc.BenchmarkServiceStub stub) {
+      this.stub = stub;
+    }
+
+    @Override
+    public void run() {
+      while (!shutdown) {
+        maxOutstanding.acquireUninterruptibly();
+        final AtomicReference<StreamObserver<Messages.SimpleRequest>> requestObserver =
+            new AtomicReference<StreamObserver<Messages.SimpleRequest>>();
+        requestObserver.set(stub.streamingCall(
+            new StreamObserver<Messages.SimpleResponse>() {
+              long now = System.nanoTime();
+
+              @Override
+              public void onNext(Messages.SimpleResponse value) {
+                delay(System.nanoTime() - now);
+                if (shutdown) {
+                  requestObserver.get().onCompleted();
+                  // Must not send another request.
+                  return;
+                }
+                requestObserver.get().onNext(simpleRequest);
+                now = System.nanoTime();
+              }
+
+              @Override
+              public void onError(Throwable t) {
+                maxOutstanding.release();
+                Level level = shutdown ? Level.FINE : Level.INFO;
+                log.log(level, "Error in Async Ping-Pong call", t);
+
+              }
+
+              @Override
+              public void onCompleted() {
+                maxOutstanding.release();
+              }
+            }));
+        requestObserver.get().onNext(simpleRequest);
+      }
+    }
+  }
+
+  /**
+   * Worker which executes generic blocking unary calls. Event timing is the duration between
+   * sending the request and receiving the response.
+   */
+  private class GenericBlockingUnaryWorker implements Runnable {
+    final Channel channel;
+
+    GenericBlockingUnaryWorker(Channel channel) {
+      this.channel = channel;
+    }
+
+    @Override
+    public void run() {
+      long now;
+      while (!shutdown) {
+        now = System.nanoTime();
+        ClientCalls.blockingUnaryCall(channel, LoadServer.GENERIC_UNARY_METHOD,
+            CallOptions.DEFAULT,
+            genericRequest.slice());
+        delay(System.nanoTime() - now);
+      }
+    }
+  }
+
+  /**
+   * Worker which executes generic async unary calls. Event timing is the duration between
+   * sending the request and receiving the response.
+   */
+  private class GenericAsyncUnaryWorker implements Runnable {
+    final Channel channel;
+    final Semaphore maxOutstanding = new Semaphore(config.getOutstandingRpcsPerChannel());
+
+    GenericAsyncUnaryWorker(Channel channel) {
+      this.channel = channel;
+    }
+
+    @Override
+    public void run() {
+      while (true) {
+        maxOutstanding.acquireUninterruptibly();
+        if (shutdown) {
+          maxOutstanding.release();
+          return;
+        }
+        ClientCalls.asyncUnaryCall(
+            channel.newCall(LoadServer.GENERIC_UNARY_METHOD, CallOptions.DEFAULT),
+            genericRequest.slice(),
+            new StreamObserver<ByteBuf>() {
+              long now = System.nanoTime();
+
+              @Override
+              public void onNext(ByteBuf value) {
+
+              }
+
+              @Override
+              public void onError(Throwable t) {
+                maxOutstanding.release();
+                Level level = shutdown ? Level.FINE : Level.INFO;
+                log.log(level, "Error in Generic Async Unary call", t);
+              }
+
+              @Override
+              public void onCompleted() {
+                delay(System.nanoTime() - now);
+                maxOutstanding.release();
+              }
+            });
+      }
+    }
+  }
+
+  /**
+   * Worker which executes a streaming ping-pong call. Event timing is the duration between
+   * sending the ping and receiving the pong.
+   */
+  private class GenericAsyncPingPongWorker implements Runnable {
+    final Semaphore maxOutstanding = new Semaphore(config.getOutstandingRpcsPerChannel());
+    final Channel channel;
+
+    GenericAsyncPingPongWorker(Channel channel) {
+      this.channel = channel;
+    }
+
+    @Override
+    public void run() {
+      while (true) {
+        maxOutstanding.acquireUninterruptibly();
+        if (shutdown) {
+          maxOutstanding.release();
+          return;
+        }
+        final ClientCall<ByteBuf, ByteBuf> call =
+            channel.newCall(LoadServer.GENERIC_STREAMING_PING_PONG_METHOD, CallOptions.DEFAULT);
+        call.start(new ClientCall.Listener<ByteBuf>() {
+          long now = System.nanoTime();
+
+          @Override
+          public void onMessage(ByteBuf message) {
+            delay(System.nanoTime() - now);
+            if (shutdown) {
+              call.cancel("Shutting down", null);
+              return;
+            }
+            call.request(1);
+            call.sendMessage(genericRequest.slice());
+            now = System.nanoTime();
+          }
+
+          @Override
+          public void onClose(Status status, Metadata trailers) {
+            maxOutstanding.release();
+            Level level = shutdown ? Level.FINE : Level.INFO;
+            if (!status.isOk() && status.getCode() != Status.Code.CANCELLED) {
+              log.log(level, "Error in Generic Async Ping-Pong call", status.getCause());
+            }
+          }
+        }, new Metadata());
+        call.request(1);
+        call.sendMessage(genericRequest.slice());
+      }
+    }
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/driver/LoadServer.java b/benchmarks/src/main/java/io/grpc/benchmarks/driver/LoadServer.java
new file mode 100644
index 0000000..f07d640
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/driver/LoadServer.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.driver;
+
+import static java.util.concurrent.ForkJoinPool.defaultForkJoinWorkerThreadFactory;
+
+import com.google.common.util.concurrent.UncaughtExceptionHandlers;
+import com.sun.management.OperatingSystemMXBean;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.ServiceDescriptor;
+import io.grpc.Status;
+import io.grpc.benchmarks.ByteBufOutputMarshaller;
+import io.grpc.benchmarks.Utils;
+import io.grpc.benchmarks.proto.BenchmarkServiceGrpc;
+import io.grpc.benchmarks.proto.Control;
+import io.grpc.benchmarks.proto.Stats;
+import io.grpc.benchmarks.qps.AsyncServer;
+import io.grpc.internal.testing.TestUtils;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.PooledByteBufAllocator;
+import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory;
+import java.util.concurrent.ForkJoinWorkerThread;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Implements the server-side contract for the load testing scenarios.
+ */
+final class LoadServer {
+
+  private static final Marshaller<ByteBuf> marshaller = new ByteBufOutputMarshaller();
+  /**
+   * Generic version of the unary method call.
+   */
+  static final MethodDescriptor<ByteBuf, ByteBuf> GENERIC_UNARY_METHOD =
+      BenchmarkServiceGrpc.getUnaryCallMethod().toBuilder(marshaller, marshaller)
+          .build();
+
+  /**
+   * Generic version of the streaming ping-pong method call.
+   */
+  static final MethodDescriptor<ByteBuf, ByteBuf> GENERIC_STREAMING_PING_PONG_METHOD =
+      BenchmarkServiceGrpc.getStreamingCallMethod().toBuilder(marshaller, marshaller)
+          .build();
+
+  private static final Logger log = Logger.getLogger(LoadServer.class.getName());
+
+  private final Server server;
+  private final AsyncServer.BenchmarkServiceImpl benchmarkService;
+  private final OperatingSystemMXBean osBean;
+  private final int port;
+  private ByteBuf genericResponse;
+  private long lastStatTime;
+  private long lastMarkCpuTime;
+
+  LoadServer(Control.ServerConfig config) throws Exception {
+    log.log(Level.INFO, "Server Config \n" + config.toString());
+    port = config.getPort() ==  0 ? Utils.pickUnusedPort() : config.getPort();
+    ServerBuilder<?> serverBuilder = ServerBuilder.forPort(port);
+    int asyncThreads = config.getAsyncServerThreads() == 0
+        ? Runtime.getRuntime().availableProcessors()
+        : config.getAsyncServerThreads();
+    // The concepts of sync & async server are quite different in the C impl and the names
+    // chosen for the enum are based on that implementation. We use 'sync' to mean
+    // the direct executor case in Java even though the service implementations are always
+    // fully async.
+    switch (config.getServerType()) {
+      case ASYNC_SERVER: {
+        serverBuilder.executor(getExecutor(asyncThreads));
+        break;
+      }
+      case SYNC_SERVER: {
+        serverBuilder.directExecutor();
+        break;
+      }
+      case ASYNC_GENERIC_SERVER: {
+        serverBuilder.executor(getExecutor(asyncThreads));
+        // Create buffers for the generic service
+        PooledByteBufAllocator alloc = PooledByteBufAllocator.DEFAULT;
+        genericResponse = alloc.buffer(config.getPayloadConfig().getBytebufParams().getRespSize());
+        if (genericResponse.capacity() > 0) {
+          genericResponse.writerIndex(genericResponse.capacity() - 1);
+        }
+        break;
+      }
+      default: {
+        throw new IllegalArgumentException();
+      }
+    }
+    if (config.hasSecurityParams()) {
+      File cert = TestUtils.loadCert("server1.pem");
+      File key = TestUtils.loadCert("server1.key");
+      serverBuilder.useTransportSecurity(cert, key);
+    }
+    benchmarkService = new AsyncServer.BenchmarkServiceImpl();
+    if (config.getServerType() == Control.ServerType.ASYNC_GENERIC_SERVER) {
+      serverBuilder.addService(
+          ServerServiceDefinition
+              .builder(new ServiceDescriptor(BenchmarkServiceGrpc.SERVICE_NAME,
+                  GENERIC_STREAMING_PING_PONG_METHOD))
+              .addMethod(GENERIC_STREAMING_PING_PONG_METHOD, new GenericServiceCallHandler())
+              .build());
+    } else {
+      serverBuilder.addService(benchmarkService);
+    }
+    server = serverBuilder.build();
+
+    List<OperatingSystemMXBean> beans =
+        ManagementFactory.getPlatformMXBeans(OperatingSystemMXBean.class);
+    if (!beans.isEmpty()) {
+      osBean = beans.get(0);
+    } else {
+      osBean = null;
+    }
+  }
+
+  ExecutorService getExecutor(int asyncThreads) {
+    // TODO(carl-mastrangelo): This should not be necessary.  I don't know where this should be
+    // put.  Move it somewhere else, or remove it if no longer necessary.
+    // See: https://github.com/grpc/grpc-java/issues/2119
+    return new ForkJoinPool(asyncThreads,
+        new ForkJoinWorkerThreadFactory() {
+          final AtomicInteger num = new AtomicInteger();
+          @Override
+          public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
+            ForkJoinWorkerThread thread = defaultForkJoinWorkerThreadFactory.newThread(pool);
+            thread.setDaemon(true);
+            thread.setName("server-worker-" + "-" + num.getAndIncrement());
+            return thread;
+          }
+        }, UncaughtExceptionHandlers.systemExit(), true /* async */);
+  }
+
+  int getPort() {
+    return port;
+  }
+
+  int getCores() {
+    return Runtime.getRuntime().availableProcessors();
+  }
+
+  void start() throws Exception {
+    server.start();
+    lastStatTime = System.nanoTime();
+    if (osBean != null) {
+      lastMarkCpuTime = osBean.getProcessCpuTime();
+    }
+  }
+
+  Stats.ServerStats getStats() {
+    Stats.ServerStats.Builder builder = Stats.ServerStats.newBuilder();
+    long now = System.nanoTime();
+    double elapsed = ((double) now - lastStatTime) / 1000000000.0;
+    lastStatTime = now;
+    builder.setTimeElapsed(elapsed);
+    if (osBean != null) {
+      // Report all the CPU time as user-time  (which is intentionally incorrect)
+      long nowCpu = osBean.getProcessCpuTime();
+      builder.setTimeUser(((double) nowCpu - lastMarkCpuTime) / 1000000000.0);
+      lastMarkCpuTime = nowCpu;
+    }
+    return builder.build();
+  }
+
+  void shutdownNow() {
+    benchmarkService.shutdown();
+    server.shutdownNow();
+  }
+
+  private class GenericServiceCallHandler implements ServerCallHandler<ByteBuf, ByteBuf> {
+
+    @Override
+    public ServerCall.Listener<ByteBuf> startCall(
+        final ServerCall<ByteBuf, ByteBuf> call, Metadata headers) {
+      call.sendHeaders(new Metadata());
+      call.request(1);
+      return new ServerCall.Listener<ByteBuf>() {
+        @Override
+        public void onMessage(ByteBuf message) {
+          // no-op
+          message.release();
+          call.request(1);
+          call.sendMessage(genericResponse.slice());
+        }
+
+        @Override
+        public void onHalfClose() {
+          call.close(Status.OK, new Metadata());
+        }
+
+        @Override
+        public void onCancel() {
+        }
+
+        @Override
+        public void onComplete() {
+        }
+      };
+    }
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/driver/LoadWorker.java b/benchmarks/src/main/java/io/grpc/benchmarks/driver/LoadWorker.java
new file mode 100644
index 0000000..30968e5
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/driver/LoadWorker.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.driver;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import io.grpc.Server;
+import io.grpc.Status;
+import io.grpc.benchmarks.proto.Control;
+import io.grpc.benchmarks.proto.Control.ClientArgs;
+import io.grpc.benchmarks.proto.Control.ServerArgs;
+import io.grpc.benchmarks.proto.Control.ServerArgs.ArgtypeCase;
+import io.grpc.benchmarks.proto.WorkerServiceGrpc;
+import io.grpc.netty.NettyServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.netty.channel.nio.NioEventLoopGroup;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A load worker process which a driver can use to create clients and servers. The worker
+ * implements the contract defined in 'control.proto'.
+ */
+public class LoadWorker {
+
+  private static final Logger log = Logger.getLogger(LoadWorker.class.getName());
+
+  private final int serverPort;
+  private final Server driverServer;
+
+  LoadWorker(int driverPort, int serverPort) throws Exception {
+    this.serverPort = serverPort;
+    NioEventLoopGroup singleThreadGroup = new NioEventLoopGroup(1,
+        new ThreadFactoryBuilder()
+            .setDaemon(true)
+            .setNameFormat("load-worker-%d")
+            .build());
+    this.driverServer = NettyServerBuilder.forPort(driverPort)
+        .directExecutor()
+        .workerEventLoopGroup(singleThreadGroup)
+        .bossEventLoopGroup(singleThreadGroup)
+        .addService(new WorkerServiceImpl())
+        .build();
+  }
+
+  public void start() throws Exception {
+    driverServer.start();
+  }
+
+  /**
+   * Start the load worker process.
+   */
+  public static void main(String[] args) throws Exception {
+    boolean usage = false;
+    int serverPort = 0;
+    int driverPort = 0;
+    for (String arg : args) {
+      if (!arg.startsWith("--")) {
+        System.err.println("All arguments must start with '--': " + arg);
+        usage = true;
+        break;
+      }
+      String[] parts = arg.substring(2).split("=", 2);
+      String key = parts[0];
+      if ("help".equals(key)) {
+        usage = true;
+        break;
+      }
+      if (parts.length != 2) {
+        System.err.println("All arguments must be of the form --arg=value");
+        usage = true;
+        break;
+      }
+      String value = parts[1];
+      if ("server_port".equals(key)) {
+        serverPort = Integer.valueOf(value);
+      } else if ("driver_port".equals(key)) {
+        driverPort = Integer.valueOf(value);
+      } else {
+        System.err.println("Unknown argument: " + key);
+        usage = true;
+        break;
+      }
+    }
+    if (usage || driverPort == 0) {
+      System.err.println(
+          "Usage: [ARGS...]"
+              + "\n"
+              + "\n  --driver_port=<port>"
+              + "\n    Port to expose grpc.testing.WorkerService, used by driver to initiate work."
+              + "\n  --server_port=<port>"
+              + "\n    Port to start load servers on. Defaults to any available port");
+      System.exit(1);
+    }
+    LoadWorker loadWorker = new LoadWorker(driverPort, serverPort);
+    loadWorker.start();
+    loadWorker.driverServer.awaitTermination();
+    log.log(Level.INFO, "DriverServer has terminated.");
+
+    // Allow enough time for quitWorker to deliver OK status to the driver.
+    Thread.sleep(3000);
+  }
+
+  /**
+   * Implement the worker service contract which can launch clients and servers.
+   */
+  private class WorkerServiceImpl extends WorkerServiceGrpc.WorkerServiceImplBase {
+
+    private LoadServer workerServer;
+    private LoadClient workerClient;
+
+    @Override
+    public StreamObserver<ServerArgs> runServer(
+        final StreamObserver<Control.ServerStatus> responseObserver) {
+      return new StreamObserver<ServerArgs>() {
+        @Override
+        public void onNext(ServerArgs value) {
+          try {
+            ArgtypeCase argTypeCase = value.getArgtypeCase();
+            if (argTypeCase == ServerArgs.ArgtypeCase.SETUP && workerServer == null) {
+              if (serverPort != 0 && value.getSetup().getPort() == 0) {
+                Control.ServerArgs.Builder builder = value.toBuilder();
+                builder.getSetupBuilder().setPort(serverPort);
+                value = builder.build();
+              }
+              workerServer = new LoadServer(value.getSetup());
+              workerServer.start();
+              responseObserver.onNext(Control.ServerStatus.newBuilder()
+                  .setPort(workerServer.getPort())
+                  .setCores(workerServer.getCores())
+                  .build());
+            } else if (argTypeCase == ArgtypeCase.MARK && workerServer != null) {
+              responseObserver.onNext(Control.ServerStatus.newBuilder()
+                  .setStats(workerServer.getStats())
+                  .build());
+            } else {
+              responseObserver.onError(Status.ALREADY_EXISTS
+                  .withDescription("Server already started")
+                  .asRuntimeException());
+            }
+          } catch (Throwable t) {
+            log.log(Level.WARNING, "Error running server", t);
+            responseObserver.onError(Status.INTERNAL.withCause(t).asException());
+            // Shutdown server if we can
+            onCompleted();
+          }
+        }
+
+        @Override
+        public void onError(Throwable t) {
+          Status status = Status.fromThrowable(t);
+          if (status.getCode() != Status.Code.CANCELLED) {
+            log.log(Level.WARNING, "Error driving server", t);
+          }
+          onCompleted();
+        }
+
+        @Override
+        public void onCompleted() {
+          try {
+            if (workerServer != null) {
+              workerServer.shutdownNow();
+            }
+          } finally {
+            workerServer = null;
+            responseObserver.onCompleted();
+          }
+        }
+      };
+    }
+
+    @Override
+    public StreamObserver<ClientArgs> runClient(
+        final StreamObserver<Control.ClientStatus> responseObserver) {
+      return new StreamObserver<ClientArgs>() {
+        @Override
+        public void onNext(ClientArgs value) {
+          try {
+            ClientArgs.ArgtypeCase argTypeCase = value.getArgtypeCase();
+            if (argTypeCase == ClientArgs.ArgtypeCase.SETUP && workerClient == null) {
+              workerClient = new LoadClient(value.getSetup());
+              workerClient.start();
+              responseObserver.onNext(Control.ClientStatus.newBuilder().build());
+            } else if (argTypeCase == ClientArgs.ArgtypeCase.MARK && workerClient != null) {
+              responseObserver.onNext(Control.ClientStatus.newBuilder()
+                  .setStats(workerClient.getStats())
+                  .build());
+            } else {
+              responseObserver.onError(Status.ALREADY_EXISTS
+                  .withDescription("Client already started")
+                  .asRuntimeException());
+            }
+          } catch (Throwable t) {
+            log.log(Level.WARNING, "Error running client", t);
+            responseObserver.onError(Status.INTERNAL.withCause(t).asException());
+            // Shutdown the client if we can
+            onCompleted();
+          }
+        }
+
+        @Override
+        public void onError(Throwable t) {
+          Status status = Status.fromThrowable(t);
+          if (status.getCode() != Status.Code.CANCELLED) {
+            log.log(Level.WARNING, "Error driving client", t);
+          }
+          onCompleted();
+        }
+
+        @Override
+        public void onCompleted() {
+          try {
+            if (workerClient != null) {
+              workerClient.shutdownNow();
+            }
+          } finally {
+            workerClient = null;
+            responseObserver.onCompleted();
+          }
+        }
+      };
+    }
+
+    @Override
+    public void coreCount(Control.CoreRequest request,
+                          StreamObserver<Control.CoreResponse> responseObserver) {
+      responseObserver.onNext(
+          Control.CoreResponse.newBuilder()
+              .setCores(Runtime.getRuntime().availableProcessors())
+              .build());
+      responseObserver.onCompleted();
+    }
+
+    @Override
+    public void quitWorker(Control.Void request,
+                           StreamObserver<Control.Void> responseObserver) {
+      try {
+        log.log(Level.INFO, "Received quitWorker request.");
+        responseObserver.onNext(Control.Void.getDefaultInstance());
+        responseObserver.onCompleted();
+        driverServer.shutdownNow();
+      } catch (Throwable t) {
+        log.log(Level.WARNING, "Error during shutdown", t);
+      }
+    }
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/AbstractConfigurationBuilder.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/AbstractConfigurationBuilder.java
new file mode 100644
index 0000000..378c160
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/AbstractConfigurationBuilder.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.qps;
+
+import static java.lang.Math.max;
+import static java.lang.String.CASE_INSENSITIVE_ORDER;
+
+import com.google.common.base.Strings;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * Abstract base class for all {@link Configuration.Builder}s.
+ */
+public abstract class AbstractConfigurationBuilder<T extends Configuration>
+    implements Configuration.Builder<T> {
+
+  private static final Param HELP = new Param() {
+    @Override
+    public String getName() {
+      return "help";
+    }
+
+    @Override
+    public String getType() {
+      return "";
+    }
+
+    @Override
+    public String getDescription() {
+      return "Print this text.";
+    }
+
+    @Override
+    public boolean isRequired() {
+      return false;
+    }
+
+    @Override
+    public String getDefaultValue() {
+      return null;
+    }
+
+    @Override
+    public void setValue(Configuration config, String value) {
+      throw new UnsupportedOperationException();
+    }
+  };
+
+  /**
+   * A single application parameter supported by this builder.
+   */
+  protected interface Param {
+    /**
+     * The name of the parameter as it would appear on the command-line.
+     */
+    String getName();
+
+    /**
+     * A string representation of the parameter type. If not applicable, just returns an empty
+     * string.
+     */
+    String getType();
+
+    /**
+     * A description of this parameter used when printing usage.
+     */
+    String getDescription();
+
+    /**
+     * The default value used when not set explicitly. Ignored if {@link #isRequired()} is {@code
+     * true}.
+     */
+    String getDefaultValue();
+
+    /**
+     * Indicates whether or not this parameter is required and must therefore be set before the
+     * configuration can be successfully built.
+     */
+    boolean isRequired();
+
+    /**
+     * Sets this parameter on the given configuration instance.
+     */
+    void setValue(Configuration config, String value);
+  }
+
+  @Override
+  public final T build(String[] args) {
+    T config = newConfiguration();
+    Map<String, Param> paramMap = getParamMap();
+    Set<String> appliedParams = new TreeSet<String>(CASE_INSENSITIVE_ORDER);
+
+    for (String arg : args) {
+      if (!arg.startsWith("--")) {
+        throw new IllegalArgumentException("All arguments must start with '--': " + arg);
+      }
+      String[] pair = arg.substring(2).split("=", 2);
+      String key = pair[0];
+      String value = "";
+      if (pair.length == 2) {
+        value = pair[1];
+      }
+
+      // If help was requested, just throw now to print out the usage.
+      if (HELP.getName().equalsIgnoreCase(key)) {
+        throw new IllegalArgumentException("Help requested");
+      }
+
+      Param param = paramMap.get(key);
+      if (param == null) {
+        throw new IllegalArgumentException("Unsupported argument: " + key);
+      }
+      param.setValue(config, value);
+      appliedParams.add(key);
+    }
+
+    // Ensure that all required options have been provided.
+    for (Param param : getParams()) {
+      if (param.isRequired() && !appliedParams.contains(param.getName())) {
+        throw new IllegalArgumentException("Missing required option '--"
+            + param.getName() + "'.");
+      }
+    }
+
+    return build0(config);
+  }
+
+  @Override
+  public final void printUsage() {
+    System.out.println("Usage: [ARGS...]");
+    int column1Width = 0;
+    List<Param> params = new ArrayList<>();
+    params.add(HELP);
+    params.addAll(getParams());
+
+    for (Param param : params) {
+      column1Width = max(commandLineFlag(param).length(), column1Width);
+    }
+    int column1Start = 2;
+    int column2Start = column1Start + column1Width + 2;
+    for (Param param : params) {
+      StringBuilder sb = new StringBuilder();
+      sb.append(Strings.repeat(" ", column1Start));
+      sb.append(commandLineFlag(param));
+      sb.append(Strings.repeat(" ", column2Start - sb.length()));
+      String message = param.getDescription();
+      sb.append(wordWrap(message, column2Start, 80));
+      if (param.isRequired()) {
+        sb.append(Strings.repeat(" ", column2Start));
+        sb.append("[Required]\n");
+      } else if (param.getDefaultValue() != null && !param.getDefaultValue().isEmpty()) {
+        sb.append(Strings.repeat(" ", column2Start));
+        sb.append("[Default=" + param.getDefaultValue() + "]\n");
+      }
+      System.out.println(sb);
+    }
+    System.out.println();
+  }
+
+  /**
+   * Creates a new configuration instance which will be used as the target for command-line
+   * arguments.
+   */
+  protected abstract T newConfiguration();
+
+  /**
+   * Returns the valid parameters supported by the configuration.
+   */
+  protected abstract Collection<Param> getParams();
+
+  /**
+   * Called by {@link #build(String[])} after verifying that all required options have been set.
+   * Performs any final validation and modifications to the configuration. If successful, returns
+   * the fully built configuration.
+   */
+  protected abstract T build0(T config);
+
+  private Map<String, Param> getParamMap() {
+    Map<String, Param> map = new TreeMap<String, Param>(CASE_INSENSITIVE_ORDER);
+    for (Param param : getParams()) {
+      map.put(param.getName(), param);
+    }
+    return map;
+  }
+
+  private static String commandLineFlag(Param param) {
+    String name = param.getName().toLowerCase();
+    String type = (!param.getType().isEmpty() ? '=' + param.getType() : "");
+    return "--" + name + type;
+  }
+
+  private static String wordWrap(String text, int startPos, int maxPos) {
+    StringBuilder builder = new StringBuilder();
+    int pos = startPos;
+    String[] parts = text.split("\\n", -1);
+    boolean isBulleted = parts.length > 1;
+    for (String part : parts) {
+      int lineStart = startPos;
+      while (!part.isEmpty()) {
+        if (pos < lineStart) {
+          builder.append(Strings.repeat(" ", lineStart - pos));
+          pos = lineStart;
+        }
+        int maxLength = maxPos - pos;
+        int length = part.length();
+        if (length > maxLength) {
+          length = part.lastIndexOf(' ', maxPos - pos) + 1;
+          if (length == 0) {
+            length = part.length();
+          }
+        }
+        builder.append(part.substring(0, length));
+        part = part.substring(length);
+
+        // Wrap to the next line.
+        builder.append("\n");
+        pos = 0;
+        lineStart = isBulleted ? startPos + 2 : startPos;
+      }
+    }
+    return builder.toString();
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncClient.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncClient.java
new file mode 100644
index 0000000..e453dd8
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncClient.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.qps;
+
+import static io.grpc.benchmarks.Utils.HISTOGRAM_MAX_VALUE;
+import static io.grpc.benchmarks.Utils.HISTOGRAM_PRECISION;
+import static io.grpc.benchmarks.Utils.saveHistogram;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.ADDRESS;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.CHANNELS;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.CLIENT_PAYLOAD;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.DIRECTEXECUTOR;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.DURATION;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.FLOW_CONTROL_WINDOW;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.OUTSTANDING_RPCS;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.SAVE_HISTOGRAM;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.SERVER_PAYLOAD;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.STREAMING_RPCS;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TESTCA;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TLS;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TRANSPORT;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.WARMUP_DURATION;
+
+import com.google.common.base.Preconditions;
+import com.google.protobuf.ByteString;
+import io.grpc.Channel;
+import io.grpc.ManagedChannel;
+import io.grpc.Status;
+import io.grpc.benchmarks.proto.BenchmarkServiceGrpc;
+import io.grpc.benchmarks.proto.BenchmarkServiceGrpc.BenchmarkServiceStub;
+import io.grpc.benchmarks.proto.Messages.Payload;
+import io.grpc.benchmarks.proto.Messages.SimpleRequest;
+import io.grpc.benchmarks.proto.Messages.SimpleResponse;
+import io.grpc.stub.StreamObserver;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import org.HdrHistogram.Histogram;
+import org.HdrHistogram.HistogramIterationValue;
+
+/**
+ * QPS Client using the non-blocking API.
+ */
+public class AsyncClient {
+
+  private final ClientConfiguration config;
+
+  public AsyncClient(ClientConfiguration config) {
+    this.config = config;
+  }
+
+  /**
+   * Start the QPS Client.
+   */
+  public void run() throws Exception {
+    if (config == null) {
+      return;
+    }
+
+    SimpleRequest req = newRequest();
+
+    List<ManagedChannel> channels = new ArrayList<>(config.channels);
+    for (int i = 0; i < config.channels; i++) {
+      channels.add(config.newChannel());
+    }
+
+    // Do a warmup first. It's the same as the actual benchmark, except that
+    // we ignore the statistics.
+    warmup(req, channels);
+
+    long startTime = System.nanoTime();
+    long endTime = startTime + TimeUnit.SECONDS.toNanos(config.duration);
+    List<Histogram> histograms = doBenchmark(req, channels, endTime);
+    long elapsedTime = System.nanoTime() - startTime;
+
+    Histogram merged = merge(histograms);
+
+    printStats(merged, elapsedTime);
+    if (config.histogramFile != null) {
+      saveHistogram(merged, config.histogramFile);
+    }
+    shutdown(channels);
+  }
+
+  private SimpleRequest newRequest() {
+    ByteString body = ByteString.copyFrom(new byte[config.clientPayload]);
+    Payload payload = Payload.newBuilder().setType(config.payloadType).setBody(body).build();
+
+    return SimpleRequest.newBuilder()
+            .setResponseType(config.payloadType)
+            .setResponseSize(config.serverPayload)
+            .setPayload(payload)
+            .build();
+  }
+
+  private void warmup(SimpleRequest req, List<? extends Channel> channels) throws Exception {
+    long endTime = System.nanoTime() + TimeUnit.SECONDS.toNanos(config.warmupDuration);
+    doBenchmark(req, channels, endTime);
+    // I don't know if this helps, but it doesn't hurt trying. We sometimes run warmups
+    // of several minutes at full load and it would be nice to start the actual benchmark
+    // with a clean heap.
+    System.gc();
+  }
+
+  private List<Histogram> doBenchmark(SimpleRequest req,
+                                      List<? extends Channel> channels,
+                                      long endTime) throws Exception {
+    // Initiate the concurrent calls
+    List<Future<Histogram>> futures =
+        new ArrayList<Future<Histogram>>(config.outstandingRpcsPerChannel);
+    for (int i = 0; i < config.channels; i++) {
+      for (int j = 0; j < config.outstandingRpcsPerChannel; j++) {
+        Channel channel = channels.get(i);
+        futures.add(doRpcs(channel, req, endTime));
+      }
+    }
+    // Wait for completion
+    List<Histogram> histograms = new ArrayList<>(futures.size());
+    for (Future<Histogram> future : futures) {
+      histograms.add(future.get());
+    }
+    return histograms;
+  }
+
+  private Future<Histogram> doRpcs(Channel channel, SimpleRequest request, long endTime) {
+    switch (config.rpcType) {
+      case UNARY:
+        return doUnaryCalls(channel, request, endTime);
+      case STREAMING:
+        return doStreamingCalls(channel, request, endTime);
+      default:
+        throw new IllegalStateException("unsupported rpc type");
+    }
+  }
+
+  private Future<Histogram> doUnaryCalls(Channel channel, final SimpleRequest request,
+                                         final long endTime) {
+    final BenchmarkServiceStub stub = BenchmarkServiceGrpc.newStub(channel);
+    final Histogram histogram = new Histogram(HISTOGRAM_MAX_VALUE, HISTOGRAM_PRECISION);
+    final HistogramFuture future = new HistogramFuture(histogram);
+
+    stub.unaryCall(request, new StreamObserver<SimpleResponse>() {
+      long lastCall = System.nanoTime();
+
+      @Override
+      public void onNext(SimpleResponse value) {
+      }
+
+      @Override
+      public void onError(Throwable t) {
+        Status status = Status.fromThrowable(t);
+        System.err.println("Encountered an error in unaryCall. Status is " + status);
+        t.printStackTrace();
+
+        future.cancel(true);
+      }
+
+      @Override
+      public void onCompleted() {
+        long now = System.nanoTime();
+        // Record the latencies in microseconds
+        histogram.recordValue((now - lastCall) / 1000);
+        lastCall = now;
+
+        if (endTime - now > 0) {
+          stub.unaryCall(request, this);
+        } else {
+          future.done();
+        }
+      }
+    });
+
+    return future;
+  }
+
+  private static Future<Histogram> doStreamingCalls(Channel channel, final SimpleRequest request,
+                                             final long endTime) {
+    final BenchmarkServiceStub stub = BenchmarkServiceGrpc.newStub(channel);
+    final Histogram histogram = new Histogram(HISTOGRAM_MAX_VALUE, HISTOGRAM_PRECISION);
+    final HistogramFuture future = new HistogramFuture(histogram);
+
+    ThisIsAHackStreamObserver responseObserver =
+        new ThisIsAHackStreamObserver(request, histogram, future, endTime);
+
+    StreamObserver<SimpleRequest> requestObserver = stub.streamingCall(responseObserver);
+    responseObserver.requestObserver = requestObserver;
+    requestObserver.onNext(request);
+    return future;
+  }
+
+  /**
+   * This seems necessary as we need to reference the requestObserver in the responseObserver.
+   * The alternative would be to use the channel layer directly.
+   */
+  private static class ThisIsAHackStreamObserver implements StreamObserver<SimpleResponse> {
+
+    final SimpleRequest request;
+    final Histogram histogram;
+    final HistogramFuture future;
+    final long endTime;
+    long lastCall = System.nanoTime();
+
+    StreamObserver<SimpleRequest> requestObserver;
+
+    ThisIsAHackStreamObserver(SimpleRequest request,
+                              Histogram histogram,
+                              HistogramFuture future,
+                              long endTime) {
+      this.request = request;
+      this.histogram = histogram;
+      this.future = future;
+      this.endTime = endTime;
+    }
+
+    @Override
+    public void onNext(SimpleResponse value) {
+      long now = System.nanoTime();
+      // Record the latencies in microseconds
+      histogram.recordValue((now - lastCall) / 1000);
+      lastCall = now;
+
+      if (endTime - now > 0) {
+        requestObserver.onNext(request);
+      } else {
+        requestObserver.onCompleted();
+      }
+    }
+
+    @Override
+    public void onError(Throwable t) {
+      Status status = Status.fromThrowable(t);
+      System.err.println("Encountered an error in streamingCall. Status is " + status);
+      t.printStackTrace();
+
+      future.cancel(true);
+    }
+
+    @Override
+    public void onCompleted() {
+      future.done();
+    }
+  }
+
+  private static Histogram merge(List<Histogram> histograms) {
+    Histogram merged = new Histogram(HISTOGRAM_MAX_VALUE, HISTOGRAM_PRECISION);
+    for (Histogram histogram : histograms) {
+      for (HistogramIterationValue value : histogram.allValues()) {
+        long latency = value.getValueIteratedTo();
+        long count = value.getCountAtValueIteratedTo();
+        merged.recordValueWithCount(latency, count);
+      }
+    }
+    return merged;
+  }
+
+  private void printStats(Histogram histogram, long elapsedTime) {
+    long latency50 = histogram.getValueAtPercentile(50);
+    long latency90 = histogram.getValueAtPercentile(90);
+    long latency95 = histogram.getValueAtPercentile(95);
+    long latency99 = histogram.getValueAtPercentile(99);
+    long latency999 = histogram.getValueAtPercentile(99.9);
+    long latencyMax = histogram.getValueAtPercentile(100);
+    long queriesPerSecond = histogram.getTotalCount() * 1000000000L / elapsedTime;
+
+    StringBuilder values = new StringBuilder();
+    values.append("Channels:                       ").append(config.channels).append('\n')
+          .append("Outstanding RPCs per Channel:   ")
+          .append(config.outstandingRpcsPerChannel).append('\n')
+          .append("Server Payload Size:            ").append(config.serverPayload).append('\n')
+          .append("Client Payload Size:            ").append(config.clientPayload).append('\n')
+          .append("50%ile Latency (in micros):     ").append(latency50).append('\n')
+          .append("90%ile Latency (in micros):     ").append(latency90).append('\n')
+          .append("95%ile Latency (in micros):     ").append(latency95).append('\n')
+          .append("99%ile Latency (in micros):     ").append(latency99).append('\n')
+          .append("99.9%ile Latency (in micros):   ").append(latency999).append('\n')
+          .append("Maximum Latency (in micros):    ").append(latencyMax).append('\n')
+          .append("QPS:                            ").append(queriesPerSecond).append('\n');
+    System.out.println(values);
+  }
+
+  private static void shutdown(List<ManagedChannel> channels) {
+    for (ManagedChannel channel : channels) {
+      channel.shutdown();
+    }
+  }
+
+  /**
+   * checkstyle complains if there is no javadoc comment here.
+   */
+  public static void main(String... args) throws Exception {
+    ClientConfiguration.Builder configBuilder = ClientConfiguration.newBuilder(
+        ADDRESS, CHANNELS, OUTSTANDING_RPCS, CLIENT_PAYLOAD, SERVER_PAYLOAD,
+        TLS, TESTCA, TRANSPORT, DURATION, WARMUP_DURATION, DIRECTEXECUTOR,
+        SAVE_HISTOGRAM, STREAMING_RPCS, FLOW_CONTROL_WINDOW);
+    ClientConfiguration config;
+    try {
+      config = configBuilder.build(args);
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+      configBuilder.printUsage();
+      return;
+    }
+    AsyncClient client = new AsyncClient(config);
+    client.run();
+  }
+
+  private static class HistogramFuture implements Future<Histogram> {
+    private final Histogram histogram;
+    private boolean canceled;
+    private boolean done;
+
+    HistogramFuture(Histogram histogram) {
+      Preconditions.checkNotNull(histogram, "histogram");
+      this.histogram = histogram;
+    }
+
+    @Override
+    public synchronized boolean cancel(boolean mayInterruptIfRunning) {
+      if (!done && !canceled) {
+        canceled = true;
+        notifyAll();
+        return true;
+      }
+      return false;
+    }
+
+    @Override
+    public synchronized boolean isCancelled() {
+      return canceled;
+    }
+
+    @Override
+    public synchronized boolean isDone() {
+      return done || canceled;
+    }
+
+    @Override
+    public synchronized Histogram get() throws InterruptedException {
+      while (!isDone() && !isCancelled()) {
+        wait();
+      }
+
+      if (isCancelled()) {
+        throw new CancellationException();
+      }
+
+      return histogram;
+    }
+
+    @Override
+    public Histogram get(long timeout, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    private synchronized void done() {
+      done = true;
+      notifyAll();
+    }
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncServer.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncServer.java
new file mode 100644
index 0000000..d73ffdf
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/AsyncServer.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.qps;
+
+import com.google.common.util.concurrent.UncaughtExceptionHandlers;
+import com.google.protobuf.ByteString;
+import io.grpc.Server;
+import io.grpc.Status;
+import io.grpc.benchmarks.Utils;
+import io.grpc.benchmarks.proto.BenchmarkServiceGrpc;
+import io.grpc.benchmarks.proto.Messages;
+import io.grpc.internal.testing.TestUtils;
+import io.grpc.netty.NettyServerBuilder;
+import io.grpc.stub.ServerCallStreamObserver;
+import io.grpc.stub.StreamObserver;
+import io.grpc.stub.StreamObservers;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.util.concurrent.DefaultThreadFactory;
+import java.io.File;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory;
+import java.util.concurrent.ForkJoinWorkerThread;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Logger;
+
+/**
+ * QPS server using the non-blocking API.
+ */
+public class AsyncServer {
+  private static final Logger log = Logger.getLogger(AsyncServer.class.getName());
+
+  /**
+   * checkstyle complains if there is no javadoc comment here.
+   */
+  public static void main(String... args) throws Exception {
+    new AsyncServer().run(args);
+  }
+
+  /** Equivalent of "main", but non-static. */
+  public void run(String[] args) throws Exception {
+    ServerConfiguration.Builder configBuilder = ServerConfiguration.newBuilder();
+    ServerConfiguration config;
+    try {
+      config = configBuilder.build(args);
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+      configBuilder.printUsage();
+      return;
+    }
+
+    final Server server = newServer(config);
+    server.start();
+
+    System.out.println("QPS Server started on " + config.address);
+
+    Runtime.getRuntime().addShutdownHook(new Thread() {
+      @Override
+      public void run() {
+        try {
+          System.out.println("QPS Server shutting down");
+          server.shutdown();
+        } catch (Exception e) {
+          e.printStackTrace();
+        }
+      }
+    });
+    server.awaitTermination();
+  }
+
+  @SuppressWarnings("LiteralClassName") // Epoll is not available on windows
+  static Server newServer(ServerConfiguration config) throws IOException {
+    final EventLoopGroup boss;
+    final EventLoopGroup worker;
+    final Class<? extends ServerChannel> channelType;
+    ThreadFactory tf = new DefaultThreadFactory("server-elg-", true /*daemon */);
+    switch (config.transport) {
+      case NETTY_NIO: {
+        boss = new NioEventLoopGroup(1, tf);
+        worker = new NioEventLoopGroup(0, tf);
+        channelType = NioServerSocketChannel.class;
+        break;
+      }
+      case NETTY_EPOLL: {
+        try {
+          // These classes are only available on linux.
+          Class<?> groupClass = Class.forName("io.netty.channel.epoll.EpollEventLoopGroup");
+          @SuppressWarnings("unchecked")
+          Class<? extends ServerChannel> channelClass = (Class<? extends ServerChannel>)
+              Class.forName("io.netty.channel.epoll.EpollServerSocketChannel");
+          boss =
+              (EventLoopGroup)
+                  (groupClass
+                      .getConstructor(int.class, ThreadFactory.class)
+                      .newInstance(1, tf));
+          worker =
+              (EventLoopGroup)
+                  (groupClass
+                      .getConstructor(int.class, ThreadFactory.class)
+                      .newInstance(0, tf));
+          channelType = channelClass;
+          break;
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+      case NETTY_UNIX_DOMAIN_SOCKET: {
+        try {
+          // These classes are only available on linux.
+          Class<?> groupClass = Class.forName("io.netty.channel.epoll.EpollEventLoopGroup");
+          @SuppressWarnings("unchecked")
+          Class<? extends ServerChannel> channelClass = (Class<? extends ServerChannel>)
+              Class.forName("io.netty.channel.epoll.EpollServerDomainSocketChannel");
+          boss =
+              (EventLoopGroup)
+                  (groupClass
+                      .getConstructor(int.class, ThreadFactory.class)
+                      .newInstance(1, tf));
+          worker =
+              (EventLoopGroup)
+                  (groupClass
+                      .getConstructor(int.class, ThreadFactory.class)
+                      .newInstance(0, tf));
+          channelType = channelClass;
+          break;
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+      default: {
+        // Should never get here.
+        throw new IllegalArgumentException("Unsupported transport: " + config.transport);
+      }
+    }
+
+    NettyServerBuilder builder = NettyServerBuilder
+        .forAddress(config.address)
+        .bossEventLoopGroup(boss)
+        .workerEventLoopGroup(worker)
+        .channelType(channelType)
+        .addService(new BenchmarkServiceImpl())
+        .flowControlWindow(config.flowControlWindow);
+    if (config.tls) {
+      System.out.println("Using fake CA for TLS certificate.\n"
+          + "Run the Java client with --tls --testca");
+
+      File cert = TestUtils.loadCert("server1.pem");
+      File key = TestUtils.loadCert("server1.key");
+      builder.useTransportSecurity(cert, key);
+    }
+    if (config.directExecutor) {
+      builder.directExecutor();
+    } else {
+      // TODO(carl-mastrangelo): This should not be necessary.  I don't know where this should be
+      // put.  Move it somewhere else, or remove it if no longer necessary.
+      // See: https://github.com/grpc/grpc-java/issues/2119
+      builder.executor(new ForkJoinPool(Runtime.getRuntime().availableProcessors(),
+          new ForkJoinWorkerThreadFactory() {
+            final AtomicInteger num = new AtomicInteger();
+            @Override
+            public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
+              ForkJoinWorkerThread thread =
+                  ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
+              thread.setDaemon(true);
+              thread.setName("grpc-server-app-" + "-" + num.getAndIncrement());
+              return thread;
+            }
+          }, UncaughtExceptionHandlers.systemExit(), true /* async */));
+    }
+
+    return builder.build();
+  }
+
+  public static class BenchmarkServiceImpl extends BenchmarkServiceGrpc.BenchmarkServiceImplBase {
+    // Always use the same canned response for bidi. This is allowed by the spec.
+    private static final int BIDI_RESPONSE_BYTES = 100;
+    private static final Messages.SimpleResponse BIDI_RESPONSE = Messages.SimpleResponse
+        .newBuilder()
+        .setPayload(Messages.Payload.newBuilder()
+            .setBody(ByteString.copyFrom(new byte[BIDI_RESPONSE_BYTES])).build())
+        .build();
+
+    private final AtomicBoolean shutdown = new AtomicBoolean();
+
+    public BenchmarkServiceImpl() {
+    }
+
+    public void shutdown() {
+      shutdown.set(true);
+    }
+
+    @Override
+    public void unaryCall(Messages.SimpleRequest request,
+        StreamObserver<Messages.SimpleResponse> responseObserver) {
+      responseObserver.onNext(Utils.makeResponse(request));
+      responseObserver.onCompleted();
+    }
+
+    @Override
+    public StreamObserver<Messages.SimpleRequest> streamingCall(
+        final StreamObserver<Messages.SimpleResponse> observer) {
+      final ServerCallStreamObserver<Messages.SimpleResponse> responseObserver =
+          (ServerCallStreamObserver<Messages.SimpleResponse>) observer;
+      // TODO(spencerfang): flow control to stop reading when !responseObserver.isReady
+      return new StreamObserver<Messages.SimpleRequest>() {
+        @Override
+        public void onNext(Messages.SimpleRequest value) {
+          if (shutdown.get()) {
+            responseObserver.onCompleted();
+            return;
+          }
+          responseObserver.onNext(Utils.makeResponse(value));
+        }
+
+        @Override
+        public void onError(Throwable t) {
+          // other side closed with non OK
+          responseObserver.onError(t);
+        }
+
+        @Override
+        public void onCompleted() {
+          // other side closed with OK
+          responseObserver.onCompleted();
+        }
+      };
+    }
+
+    @Override
+    public StreamObserver<Messages.SimpleRequest> streamingFromClient(
+        final StreamObserver<Messages.SimpleResponse> responseObserver) {
+      return new StreamObserver<Messages.SimpleRequest>() {
+        Messages.SimpleRequest lastSeen = null;
+
+        @Override
+        public void onNext(Messages.SimpleRequest value) {
+          if (shutdown.get()) {
+            responseObserver.onCompleted();
+            return;
+          }
+          lastSeen = value;
+        }
+
+        @Override
+        public void onError(Throwable t) {
+          // other side closed with non OK
+          responseObserver.onError(t);
+        }
+
+        @Override
+        public void onCompleted() {
+          if (lastSeen != null) {
+            responseObserver.onNext(Utils.makeResponse(lastSeen));
+            responseObserver.onCompleted();
+          } else {
+            responseObserver.onError(
+                Status.FAILED_PRECONDITION
+                    .withDescription("never received any requests").asException());
+          }
+        }
+      };
+    }
+
+    @Override
+    public void streamingFromServer(
+        final Messages.SimpleRequest request,
+        final StreamObserver<Messages.SimpleResponse> observer) {
+      // send forever, until the client cancels or we shut down
+      final Messages.SimpleResponse response = Utils.makeResponse(request);
+      final ServerCallStreamObserver<Messages.SimpleResponse> responseObserver =
+          (ServerCallStreamObserver<Messages.SimpleResponse>) observer;
+      // If the client cancels, copyWithFlowControl takes care of calling
+      // responseObserver.onCompleted() for us
+      StreamObservers.copyWithFlowControl(
+          new Iterator<Messages.SimpleResponse>() {
+            @Override
+            public boolean hasNext() {
+              return !shutdown.get() && !responseObserver.isCancelled();
+            }
+
+            @Override
+            public Messages.SimpleResponse next() {
+              return response;
+            }
+
+            @Override
+            public void remove() {
+              throw new UnsupportedOperationException();
+            }
+          },
+          responseObserver);
+    }
+
+    @Override
+    public StreamObserver<Messages.SimpleRequest> streamingBothWays(
+        final StreamObserver<Messages.SimpleResponse> observer) {
+      // receive data forever and send data forever until client cancels or we shut down.
+      final ServerCallStreamObserver<Messages.SimpleResponse> responseObserver =
+          (ServerCallStreamObserver<Messages.SimpleResponse>) observer;
+      // If the client cancels, copyWithFlowControl takes care of calling
+      // responseObserver.onCompleted() for us
+      StreamObservers.copyWithFlowControl(
+          new Iterator<Messages.SimpleResponse>() {
+            @Override
+            public boolean hasNext() {
+              return !shutdown.get() && !responseObserver.isCancelled();
+            }
+
+            @Override
+            public Messages.SimpleResponse next() {
+              return BIDI_RESPONSE;
+            }
+
+            @Override
+            public void remove() {
+              throw new UnsupportedOperationException();
+            }
+          },
+          responseObserver
+      );
+
+      return new StreamObserver<Messages.SimpleRequest>() {
+        @Override
+        public void onNext(final Messages.SimpleRequest request) {
+          // noop
+        }
+
+        @Override
+        public void onError(Throwable t) {
+          // other side cancelled
+        }
+
+        @Override
+        public void onCompleted() {
+          // Should never happen, because clients should cancel this call in order to stop
+          // the operation. Also because copyWithFlowControl hogs the inbound network thread
+          // via the handler for onReady, we would never expect this callback to be able to
+          // run anyways.
+          log.severe("clients should CANCEL the call to stop bidi streaming");
+        }
+      };
+    }
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/ClientConfiguration.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/ClientConfiguration.java
new file mode 100644
index 0000000..f15779b
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/ClientConfiguration.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.qps;
+
+import static io.grpc.benchmarks.Utils.parseBoolean;
+import static java.lang.Integer.parseInt;
+import static java.util.Arrays.asList;
+
+import io.grpc.ManagedChannel;
+import io.grpc.benchmarks.Transport;
+import io.grpc.benchmarks.Utils;
+import io.grpc.benchmarks.proto.Control.RpcType;
+import io.grpc.benchmarks.proto.Messages;
+import io.grpc.benchmarks.proto.Messages.PayloadType;
+import io.grpc.internal.testing.TestUtils;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Configuration options for benchmark clients.
+ */
+public class ClientConfiguration implements Configuration {
+  private static final ClientConfiguration DEFAULT = new ClientConfiguration();
+
+  Transport transport = Transport.NETTY_NIO;
+  boolean tls;
+  boolean testca;
+  String authorityOverride = TestUtils.TEST_SERVER_HOST;
+  boolean useDefaultCiphers;
+  boolean directExecutor;
+  SocketAddress address;
+  int channels = 4;
+  int outstandingRpcsPerChannel = 10;
+  int serverPayload;
+  int clientPayload;
+  int flowControlWindow = Utils.DEFAULT_FLOW_CONTROL_WINDOW;
+  // seconds
+  int duration = 60;
+  // seconds
+  int warmupDuration = 10;
+  int targetQps;
+  String histogramFile;
+  RpcType rpcType = RpcType.UNARY;
+  PayloadType payloadType = PayloadType.COMPRESSABLE;
+
+  private ClientConfiguration() {
+  }
+
+  public ManagedChannel newChannel() throws IOException {
+    return Utils.newClientChannel(transport, address, tls, testca, authorityOverride,
+        flowControlWindow, directExecutor);
+  }
+
+  public Messages.SimpleRequest newRequest() {
+    return Utils.makeRequest(payloadType, clientPayload, serverPayload);
+  }
+
+  /**
+   * Constructs a builder for configuring a client application with supported parameters. If no
+   * parameters are provided, all parameters are assumed to be supported.
+   */
+  static Builder newBuilder(ClientParam... supportedParams) {
+    return new Builder(supportedParams);
+  }
+
+  static final class Builder extends AbstractConfigurationBuilder<ClientConfiguration> {
+    private final Collection<Param> supportedParams;
+
+    private Builder(ClientParam... supportedParams) {
+      this.supportedParams = supportedOptionsSet(supportedParams);
+    }
+
+    @Override
+    protected ClientConfiguration newConfiguration() {
+      return new ClientConfiguration();
+    }
+
+    @Override
+    protected Collection<Param> getParams() {
+      return supportedParams;
+    }
+
+    @Override
+    protected ClientConfiguration build0(ClientConfiguration config) {
+      if (config.tls) {
+        if (!config.transport.tlsSupported) {
+          throw new IllegalArgumentException(
+              "Transport " + config.transport.name().toLowerCase() + " does not support TLS.");
+        }
+
+        if (config.transport != Transport.OK_HTTP
+            && config.testca && config.address instanceof InetSocketAddress) {
+          // Override the socket address with the host from the testca.
+          InetSocketAddress address = (InetSocketAddress) config.address;
+          config.address = TestUtils.testServerAddress(address.getHostName(),
+                  address.getPort());
+        }
+      }
+
+      // Verify that the address type is correct for the transport type.
+      config.transport.validateSocketAddress(config.address);
+
+      return config;
+    }
+
+    private static Set<Param> supportedOptionsSet(ClientParam... supportedParams) {
+      if (supportedParams.length == 0) {
+        // If no options are supplied, default to including all options.
+        supportedParams = ClientParam.values();
+      }
+      return Collections.unmodifiableSet(new LinkedHashSet<Param>(asList(supportedParams)));
+    }
+  }
+
+  enum ClientParam implements AbstractConfigurationBuilder.Param {
+    ADDRESS("STR", "Socket address (host:port) or Unix Domain Socket file name "
+        + "(unix:///path/to/file), depending on the transport selected.", null, true) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.address = Utils.parseSocketAddress(value);
+      }
+    },
+    CHANNELS("INT", "Number of Channels.", "" + DEFAULT.channels) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.channels = parseInt(value);
+      }
+    },
+    OUTSTANDING_RPCS("INT", "Number of outstanding RPCs per Channel.",
+        "" + DEFAULT.outstandingRpcsPerChannel) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.outstandingRpcsPerChannel = parseInt(value);
+      }
+    },
+    CLIENT_PAYLOAD("BYTES", "Payload Size of the Request.", "" + DEFAULT.clientPayload) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.clientPayload = parseInt(value);
+      }
+    },
+    SERVER_PAYLOAD("BYTES", "Payload Size of the Response.", "" + DEFAULT.serverPayload) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.serverPayload = parseInt(value);
+      }
+    },
+    TLS("", "Enable TLS.", "" + DEFAULT.tls) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.tls = parseBoolean(value);
+      }
+    },
+    TESTCA("", "Use the provided Test Certificate for TLS.", "" + DEFAULT.testca) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.testca = parseBoolean(value);
+      }
+    },
+    TRANSPORT("STR", Transport.getDescriptionString(), DEFAULT.transport.name().toLowerCase()) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.transport = Transport.valueOf(value.toUpperCase());
+      }
+    },
+    DURATION("SECONDS", "Duration of the benchmark.", "" + DEFAULT.duration) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.duration = parseInt(value);
+      }
+    },
+    WARMUP_DURATION("SECONDS", "Warmup Duration of the benchmark.", "" + DEFAULT.warmupDuration) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.warmupDuration = parseInt(value);
+      }
+    },
+    DIRECTEXECUTOR("",
+        "Don't use a threadpool for RPC calls, instead execute calls directly "
+            + "in the transport thread.", "" + DEFAULT.directExecutor) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.directExecutor = parseBoolean(value);
+      }
+    },
+    SAVE_HISTOGRAM("FILE", "Write the histogram with the latency recordings to file.", null) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.histogramFile = value;
+      }
+    },
+    STREAMING_RPCS("", "Use Streaming RPCs.", "false") {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.rpcType = RpcType.STREAMING;
+      }
+    },
+    FLOW_CONTROL_WINDOW("BYTES", "The HTTP/2 flow control window.",
+        "" + DEFAULT.flowControlWindow) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.flowControlWindow = parseInt(value);
+      }
+    },
+    TARGET_QPS("INT", "Average number of QPS to shoot for.", "" + DEFAULT.targetQps, true) {
+      @Override
+      protected void setClientValue(ClientConfiguration config, String value) {
+        config.targetQps = parseInt(value);
+      }
+    };
+
+    private final String type;
+    private final String description;
+    private final String defaultValue;
+    private final boolean required;
+
+    ClientParam(String type, String description, String defaultValue) {
+      this(type, description, defaultValue, false);
+    }
+
+    ClientParam(String type, String description, String defaultValue, boolean required) {
+      this.type = type;
+      this.description = description;
+      this.defaultValue = defaultValue;
+      this.required = required;
+    }
+
+    @Override
+    public String getName() {
+      return name().toLowerCase();
+    }
+
+    @Override
+    public String getType() {
+      return type;
+    }
+
+    @Override
+    public String getDescription() {
+      return description;
+    }
+
+    @Override
+    public String getDefaultValue() {
+      return defaultValue;
+    }
+
+    @Override
+    public boolean isRequired() {
+      return required;
+    }
+
+    @Override
+    public void setValue(Configuration config, String value) {
+      setClientValue((ClientConfiguration) config, value);
+    }
+
+    protected abstract void setClientValue(ClientConfiguration config, String value);
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/Configuration.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/Configuration.java
new file mode 100644
index 0000000..ce2b643
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/Configuration.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.qps;
+
+/**
+ * Configuration for a benchmark application.
+ */
+public interface Configuration {
+  /**
+   * Builder for the {@link Configuration}.
+   * @param <T> The type of {@link Configuration} that this builder creates.
+   */
+  interface Builder<T extends Configuration> {
+    /**
+     * Builds the {@link Configuration} from the given command-line arguments.
+     * @throws IllegalArgumentException if unable to build the configuration for any reason.
+     */
+    T build(String[] args);
+
+    /**
+     * Prints the command-line usage for the application based on the options supported by this
+     * builder.
+     */
+    void printUsage();
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/OpenLoopClient.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/OpenLoopClient.java
new file mode 100644
index 0000000..f366bc3
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/OpenLoopClient.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.qps;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.benchmarks.Utils.HISTOGRAM_MAX_VALUE;
+import static io.grpc.benchmarks.Utils.HISTOGRAM_PRECISION;
+import static io.grpc.benchmarks.Utils.saveHistogram;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.ADDRESS;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.CLIENT_PAYLOAD;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.DURATION;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.FLOW_CONTROL_WINDOW;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.SAVE_HISTOGRAM;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.SERVER_PAYLOAD;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TARGET_QPS;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TESTCA;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TLS;
+import static io.grpc.benchmarks.qps.ClientConfiguration.ClientParam.TRANSPORT;
+
+import io.grpc.Channel;
+import io.grpc.ManagedChannel;
+import io.grpc.Status;
+import io.grpc.benchmarks.proto.BenchmarkServiceGrpc;
+import io.grpc.benchmarks.proto.Messages.SimpleRequest;
+import io.grpc.benchmarks.proto.Messages.SimpleResponse;
+import io.grpc.stub.StreamObserver;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import org.HdrHistogram.AtomicHistogram;
+import org.HdrHistogram.Histogram;
+
+/**
+ * Tries to generate traffic that closely resembles user-generated RPC traffic. This is done using
+ * a Poisson Process to average at a target QPS and the delays between calls are randomized using
+ * an exponential variate.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Poisson_process">Poisson Process</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Exponential_distribution">Exponential Distribution</a>
+ */
+public class OpenLoopClient {
+
+  private final ClientConfiguration config;
+
+  public OpenLoopClient(ClientConfiguration config) {
+    this.config = config;
+  }
+
+  /**
+   * Comment for checkstyle.
+   */
+  public static void main(String... args) throws Exception {
+    ClientConfiguration.Builder configBuilder = ClientConfiguration.newBuilder(
+        ADDRESS, TARGET_QPS, CLIENT_PAYLOAD, SERVER_PAYLOAD, TLS,
+        TESTCA, TRANSPORT, DURATION, SAVE_HISTOGRAM, FLOW_CONTROL_WINDOW);
+    ClientConfiguration config;
+    try {
+      config = configBuilder.build(args);
+    } catch (Exception e) {
+      System.out.println(e.getMessage());
+      configBuilder.printUsage();
+      return;
+    }
+    OpenLoopClient client = new OpenLoopClient(config);
+    client.run();
+  }
+
+  /**
+   * Start the open loop client.
+   */
+  public void run() throws Exception {
+    if (config == null) {
+      return;
+    }
+    config.channels = 1;
+    config.directExecutor = true;
+    ManagedChannel ch = config.newChannel();
+    SimpleRequest req = config.newRequest();
+    LoadGenerationWorker worker =
+        new LoadGenerationWorker(ch, req, config.targetQps, config.duration);
+    final long start = System.nanoTime();
+    Histogram histogram = worker.call();
+    final long end = System.nanoTime();
+    printStats(histogram, end - start);
+    if (config.histogramFile != null) {
+      saveHistogram(histogram, config.histogramFile);
+    }
+    ch.shutdown();
+  }
+
+  private void printStats(Histogram histogram, long elapsedTime) {
+    long latency50 = histogram.getValueAtPercentile(50);
+    long latency90 = histogram.getValueAtPercentile(90);
+    long latency95 = histogram.getValueAtPercentile(95);
+    long latency99 = histogram.getValueAtPercentile(99);
+    long latency999 = histogram.getValueAtPercentile(99.9);
+    long latencyMax = histogram.getValueAtPercentile(100);
+    long queriesPerSecond = histogram.getTotalCount() * 1000000000L / elapsedTime;
+
+    StringBuilder values = new StringBuilder();
+    values.append("Server Payload Size:            ").append(config.serverPayload).append('\n')
+          .append("Client Payload Size:            ").append(config.clientPayload).append('\n')
+          .append("50%ile Latency (in micros):     ").append(latency50).append('\n')
+          .append("90%ile Latency (in micros):     ").append(latency90).append('\n')
+          .append("95%ile Latency (in micros):     ").append(latency95).append('\n')
+          .append("99%ile Latency (in micros):     ").append(latency99).append('\n')
+          .append("99.9%ile Latency (in micros):   ").append(latency999).append('\n')
+          .append("Maximum Latency (in micros):    ").append(latencyMax).append('\n')
+          .append("Actual QPS:                     ").append(queriesPerSecond).append('\n')
+          .append("Target QPS:                     ").append(config.targetQps).append('\n');
+    System.out.println(values);
+  }
+
+  static class LoadGenerationWorker implements Callable<Histogram> {
+    final Histogram histogram = new AtomicHistogram(HISTOGRAM_MAX_VALUE, HISTOGRAM_PRECISION);
+    final BenchmarkServiceGrpc.BenchmarkServiceStub stub;
+    final SimpleRequest request;
+    final Random rnd;
+    final int targetQps;
+    final long numRpcs;
+
+    LoadGenerationWorker(Channel channel, SimpleRequest request, int targetQps, int duration) {
+      stub = BenchmarkServiceGrpc.newStub(checkNotNull(channel, "channel"));
+      this.request = checkNotNull(request, "request");
+      this.targetQps = targetQps;
+      numRpcs = (long) targetQps * duration;
+      rnd = new Random();
+    }
+
+    /**
+     * Discuss waiting strategy between calls. Sleeping seems to be very inaccurate
+     * (see below). On the other hand calling System.nanoTime() a lot (especially from
+     * different threads seems to impact its accuracy
+     * http://shipilev.net/blog/2014/nanotrusting-nanotime/
+     * On my system the overhead of LockSupport.park(long) seems to average at ~55 micros.
+     * // Try to sleep for 1 nanosecond and measure how long it actually takes.
+     * long start = System.nanoTime();
+     * int i = 0;
+     * while (i < 10000) {
+     *   LockSupport.parkNanos(1);
+     *   i++;
+     * }
+     * long end = System.nanoTime();
+     * System.out.println((end - start) / 10000);
+     */
+    @Override
+    public Histogram call() throws Exception {
+      long now = System.nanoTime();
+      long nextRpc = now;
+      long i = 0;
+      while (i < numRpcs) {
+        now = System.nanoTime();
+        if (nextRpc - now <= 0) {
+          // TODO: Add option to print how far we have been off from the target delay in micros.
+          nextRpc += nextDelay(targetQps);
+          newRpc(stub);
+          i++;
+        }
+      }
+
+      waitForRpcsToComplete(1);
+
+      return histogram;
+    }
+
+    private void newRpc(BenchmarkServiceGrpc.BenchmarkServiceStub stub) {
+      stub.unaryCall(request, new StreamObserver<SimpleResponse>() {
+
+        private final long start = System.nanoTime();
+
+        @Override
+        public void onNext(SimpleResponse value) {
+        }
+
+        @Override
+        public void onError(Throwable t) {
+          Status status = Status.fromThrowable(t);
+          System.err.println("Encountered an error in unaryCall. Status is " + status);
+          t.printStackTrace();
+        }
+
+        @Override
+        public void onCompleted() {
+          final long end = System.nanoTime();
+          histogram.recordValue((end - start) / 1000);
+        }
+      });
+    }
+
+    private void waitForRpcsToComplete(int duration) {
+      long now = System.nanoTime();
+      long end = now + duration * 1000 * 1000 * 1000;
+      while (histogram.getTotalCount() < numRpcs && end - now > 0) {
+        now = System.nanoTime();
+      }
+    }
+
+    private static final double DELAY_EPSILON = Math.nextUp(0d);
+
+    private long nextDelay(int targetQps) {
+      double seconds = -Math.log(Math.max(rnd.nextDouble(), DELAY_EPSILON)) / targetQps;
+      double nanos = seconds * 1000 * 1000 * 1000;
+      return Math.round(nanos);
+    }
+  }
+}
diff --git a/benchmarks/src/main/java/io/grpc/benchmarks/qps/ServerConfiguration.java b/benchmarks/src/main/java/io/grpc/benchmarks/qps/ServerConfiguration.java
new file mode 100644
index 0000000..a8a0973
--- /dev/null
+++ b/benchmarks/src/main/java/io/grpc/benchmarks/qps/ServerConfiguration.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.qps;
+
+import static io.grpc.benchmarks.Utils.parseBoolean;
+import static java.lang.Integer.parseInt;
+
+import io.grpc.benchmarks.SocketAddressValidator;
+import io.grpc.benchmarks.Utils;
+import io.grpc.netty.NettyChannelBuilder;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Configuration options for benchmark servers.
+ */
+class ServerConfiguration implements Configuration {
+  private static final ServerConfiguration DEFAULT = new ServerConfiguration();
+
+  Transport transport = Transport.NETTY_NIO;
+  boolean tls;
+  boolean directExecutor;
+  SocketAddress address;
+  int flowControlWindow = NettyChannelBuilder.DEFAULT_FLOW_CONTROL_WINDOW;
+
+  private ServerConfiguration() {
+  }
+
+  static Builder newBuilder() {
+    return new Builder();
+  }
+
+  static class Builder extends AbstractConfigurationBuilder<ServerConfiguration> {
+    private static final List<Param> PARAMS = supportedParams();
+
+    private Builder() {
+    }
+
+    @Override
+    protected ServerConfiguration newConfiguration() {
+      return new ServerConfiguration();
+    }
+
+    @Override
+    protected Collection<Param> getParams() {
+      return PARAMS;
+    }
+
+    @Override
+    protected ServerConfiguration build0(ServerConfiguration config) {
+      if (config.tls && !config.transport.tlsSupported) {
+        throw new IllegalArgumentException(
+            "TLS unsupported with the " + config.transport.name().toLowerCase() + " transport");
+      }
+
+      // Verify that the address type is correct for the transport type.
+      config.transport.validateSocketAddress(config.address);
+      return config;
+    }
+
+    private static List<Param> supportedParams() {
+      return Collections.unmodifiableList(new ArrayList<Param>(
+          Arrays.asList(ServerParam.values())));
+    }
+  }
+
+  /**
+   * All of the supported transports.
+   */
+  public enum Transport {
+    NETTY_NIO(true, "The Netty Java NIO transport. Using this with TLS requires "
+        + "that the Java bootclasspath be configured with Jetty ALPN boot.",
+        SocketAddressValidator.INET),
+    NETTY_EPOLL(true, "The Netty native EPOLL transport. Using this with TLS requires that "
+        + "OpenSSL be installed and configured as described in "
+        + "http://netty.io/wiki/forked-tomcat-native.html. Only supported on Linux.",
+        SocketAddressValidator.INET),
+    NETTY_UNIX_DOMAIN_SOCKET(false, "The Netty Unix Domain Socket transport. This currently "
+        + "does not support TLS.",
+        SocketAddressValidator.UDS);
+
+    private final boolean tlsSupported;
+    private final String description;
+    private final SocketAddressValidator socketAddressValidator;
+
+    Transport(boolean tlsSupported, String description,
+              SocketAddressValidator socketAddressValidator) {
+      this.tlsSupported = tlsSupported;
+      this.description = description;
+      this.socketAddressValidator = socketAddressValidator;
+    }
+
+    /**
+     * Validates the given address for this transport.
+     *
+     * @throws IllegalArgumentException if the given address is invalid for this transport.
+     */
+    void validateSocketAddress(SocketAddress address) {
+      if (!socketAddressValidator.isValidSocketAddress(address)) {
+        throw new IllegalArgumentException(
+            "Invalid address " + address + " for transport " + this);
+      }
+    }
+
+    static String getDescriptionString() {
+      StringBuilder builder = new StringBuilder("Select the transport to use. Options:\n");
+      boolean first = true;
+      for (Transport transport : Transport.values()) {
+        if (!first) {
+          builder.append("\n");
+        }
+        builder.append(transport.name().toLowerCase());
+        builder.append(": ");
+        builder.append(transport.description);
+        first = false;
+      }
+      return builder.toString();
+    }
+  }
+
+  enum ServerParam implements AbstractConfigurationBuilder.Param {
+    ADDRESS("STR", "Socket address (host:port) or Unix Domain Socket file name "
+        + "(unix:///path/to/file), depending on the transport selected.", null, true) {
+      @Override
+      protected void setServerValue(ServerConfiguration config, String value) {
+        SocketAddress address = Utils.parseSocketAddress(value);
+        if (address instanceof InetSocketAddress) {
+          InetSocketAddress addr = (InetSocketAddress) address;
+          int port = addr.getPort() == 0 ? Utils.pickUnusedPort() : addr.getPort();
+          // Re-create the address so that the server is available on all local addresses.
+          address = new InetSocketAddress(port);
+        }
+        config.address = address;
+      }
+    },
+    TLS("", "Enable TLS.", "" + DEFAULT.tls) {
+      @Override
+      protected void setServerValue(ServerConfiguration config, String value) {
+        config.tls = parseBoolean(value);
+      }
+    },
+    TRANSPORT("STR", Transport.getDescriptionString(), DEFAULT.transport.name().toLowerCase()) {
+      @Override
+      protected void setServerValue(ServerConfiguration config, String value) {
+        config.transport = Transport.valueOf(value.toUpperCase());
+      }
+    },
+    DIRECTEXECUTOR("", "Don't use a threadpool for RPC calls, instead execute calls directly "
+        + "in the transport thread.", "" + DEFAULT.directExecutor) {
+      @Override
+      protected void setServerValue(ServerConfiguration config, String value) {
+        config.directExecutor = parseBoolean(value);
+      }
+    },
+    FLOW_CONTROL_WINDOW("BYTES", "The HTTP/2 flow control window.",
+        "" + DEFAULT.flowControlWindow) {
+      @Override
+      protected void setServerValue(ServerConfiguration config, String value) {
+        config.flowControlWindow = parseInt(value);
+      }
+    };
+
+    private final String type;
+    private final String description;
+    private final String defaultValue;
+    private final boolean required;
+
+    ServerParam(String type, String description, String defaultValue) {
+      this(type, description, defaultValue, false);
+    }
+
+    ServerParam(String type, String description, String defaultValue, boolean required) {
+      this.type = type;
+      this.description = description;
+      this.defaultValue = defaultValue;
+      this.required = required;
+    }
+
+    @Override
+    public String getName() {
+      return name().toLowerCase();
+    }
+
+    @Override
+    public String getType() {
+      return type;
+    }
+
+    @Override
+    public String getDescription() {
+      return description;
+    }
+
+    @Override
+    public String getDefaultValue() {
+      return defaultValue;
+    }
+
+    @Override
+    public boolean isRequired() {
+      return required;
+    }
+
+    @Override
+    public void setValue(Configuration config, String value) {
+      setServerValue((ServerConfiguration) config, value);
+    }
+
+    protected abstract void setServerValue(ServerConfiguration config, String value);
+  }
+}
diff --git a/benchmarks/src/main/proto/grpc/testing/control.proto b/benchmarks/src/main/proto/grpc/testing/control.proto
new file mode 100644
index 0000000..403450c
--- /dev/null
+++ b/benchmarks/src/main/proto/grpc/testing/control.proto
@@ -0,0 +1,259 @@
+// Copyright 2015-2016 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto3";
+
+import "grpc/testing/payloads.proto";
+import "grpc/testing/stats.proto";
+
+package grpc.testing;
+
+option java_package = "io.grpc.benchmarks.proto";
+option java_outer_classname = "Control";
+
+enum ClientType {
+  // Many languages support a basic distinction between using
+  // sync or async client, and this allows the specification
+  SYNC_CLIENT = 0;
+  ASYNC_CLIENT = 1;
+  OTHER_CLIENT = 2; // used for some language-specific variants
+}
+
+enum ServerType {
+  SYNC_SERVER = 0;
+  ASYNC_SERVER = 1;
+  ASYNC_GENERIC_SERVER = 2;
+  OTHER_SERVER = 3; // used for some language-specific variants
+}
+
+enum RpcType {
+  UNARY = 0;
+  STREAMING = 1;
+  STREAMING_FROM_CLIENT = 2;
+  STREAMING_FROM_SERVER = 3;
+  STREAMING_BOTH_WAYS = 4;
+}
+
+// Parameters of poisson process distribution, which is a good representation
+// of activity coming in from independent identical stationary sources.
+message PoissonParams {
+  // The rate of arrivals (a.k.a. lambda parameter of the exp distribution).
+  double offered_load = 1;
+}
+
+// Once an RPC finishes, immediately start a new one.
+// No configuration parameters needed.
+message ClosedLoopParams {}
+
+message LoadParams {
+  oneof load {
+    ClosedLoopParams closed_loop = 1;
+    PoissonParams poisson = 2;
+  };
+}
+
+// presence of SecurityParams implies use of TLS
+message SecurityParams {
+  bool use_test_ca = 1;
+  string server_host_override = 2;
+}
+
+message ChannelArg {
+  string name = 1;
+  oneof value {
+    string str_value = 2;
+    int32 int_value = 3;
+  }
+}
+
+message ClientConfig {
+  // List of targets to connect to. At least one target needs to be specified.
+  repeated string server_targets = 1;
+  ClientType client_type = 2;
+  SecurityParams security_params = 3;
+  // How many concurrent RPCs to start for each channel.
+  // For synchronous client, use a separate thread for each outstanding RPC.
+  int32 outstanding_rpcs_per_channel = 4;
+  // Number of independent client channels to create.
+  // i-th channel will connect to server_target[i % server_targets.size()]
+  int32 client_channels = 5;
+  // Only for async client. Number of threads to use to start/manage RPCs.
+  int32 async_client_threads = 7;
+  RpcType rpc_type = 8;
+  // The requested load for the entire client (aggregated over all the threads).
+  LoadParams load_params = 10;
+  PayloadConfig payload_config = 11;
+  HistogramParams histogram_params = 12;
+
+  // Specify the cores we should run the client on, if desired
+  repeated int32 core_list = 13;
+  int32 core_limit = 14;
+
+  // If we use an OTHER_CLIENT client_type, this string gives more detail
+  string other_client_api = 15;
+
+  repeated ChannelArg channel_args = 16;
+
+  // Number of messages on a stream before it gets finished/restarted
+  int32 messages_per_stream = 18;
+}
+
+message ClientStatus { ClientStats stats = 1; }
+
+// Request current stats
+message Mark {
+  // if true, the stats will be reset after taking their snapshot.
+  bool reset = 1;
+}
+
+message ClientArgs {
+  oneof argtype {
+    ClientConfig setup = 1;
+    Mark mark = 2;
+  }
+}
+
+message ServerConfig {
+  ServerType server_type = 1;
+  SecurityParams security_params = 2;
+  // Port on which to listen. Zero means pick unused port.
+  int32 port = 4;
+  // Only for async server. Number of threads used to serve the requests.
+  int32 async_server_threads = 7;
+  // Specify the number of cores to limit server to, if desired
+  int32 core_limit = 8;
+  // payload config, used in generic server.
+  // Note this must NOT be used in proto (non-generic) servers. For proto servers,
+  // 'response sizes' must be configured from the 'response_size' field of the
+  // 'SimpleRequest' objects in RPC requests.
+  PayloadConfig payload_config = 9;
+
+  // Specify the cores we should run the server on, if desired
+  repeated int32 core_list = 10;
+
+  // If we use an OTHER_SERVER client_type, this string gives more detail
+  string other_server_api = 11;
+
+  // c++-only options (for now) --------------------------------
+
+  // Buffer pool size (no buffer pool specified if unset)
+  int32 resource_quota_size = 1001;
+}
+
+message ServerArgs {
+  oneof argtype {
+    ServerConfig setup = 1;
+    Mark mark = 2;
+  }
+}
+
+message ServerStatus {
+  ServerStats stats = 1;
+  // the port bound by the server
+  int32 port = 2;
+  // Number of cores available to the server
+  int32 cores = 3;
+}
+
+message CoreRequest {
+}
+
+message CoreResponse {
+  // Number of cores available on the server
+  int32 cores = 1;
+}
+
+message Void {
+}
+
+// A single performance scenario: input to qps_json_driver
+message Scenario {
+  // Human readable name for this scenario
+  string name = 1;
+  // Client configuration
+  ClientConfig client_config = 2;
+  // Number of clients to start for the test
+  int32 num_clients = 3;
+  // Server configuration
+  ServerConfig server_config = 4;
+  // Number of servers to start for the test
+  int32 num_servers = 5;
+  // Warmup period, in seconds
+  int32 warmup_seconds = 6;
+  // Benchmark time, in seconds
+  int32 benchmark_seconds = 7;
+  // Number of workers to spawn locally (usually zero)
+  int32 spawn_local_worker_count = 8;
+}
+
+// A set of scenarios to be run with qps_json_driver
+message Scenarios {
+  repeated Scenario scenarios = 1;
+}
+
+// Basic summary that can be computed from ClientStats and ServerStats
+// once the scenario has finished.
+message ScenarioResultSummary
+{
+  // Total number of operations per second over all clients.
+  double qps = 1;
+  // QPS per one server core.
+  double qps_per_server_core = 2;
+  // server load based on system_time (0.85 => 85%)
+  double server_system_time = 3;
+  // server load based on user_time (0.85 => 85%)
+  double server_user_time = 4;
+  // client load based on system_time (0.85 => 85%)
+  double client_system_time = 5;
+  // client load based on user_time (0.85 => 85%)
+  double client_user_time = 6;
+
+  // X% latency percentiles (in nanoseconds)
+  double latency_50 = 7;
+  double latency_90 = 8;
+  double latency_95 = 9;
+  double latency_99 = 10;
+  double latency_999 = 11;
+
+  // server cpu usage percentage
+  double server_cpu_usage = 12;
+
+  // Number of requests that succeeded/failed
+  double successful_requests_per_second = 13;
+  double failed_requests_per_second = 14;
+
+  // Number of polls called inside completion queue per request
+  double client_polls_per_request = 15;
+  double server_polls_per_request = 16;
+}
+
+// Results of a single benchmark scenario.
+message ScenarioResult {
+  // Inputs used to run the scenario.
+  Scenario scenario = 1;
+  // Histograms from all clients merged into one histogram.
+  HistogramData latencies = 2;
+  // Client stats for each client
+  repeated ClientStats client_stats = 3;
+  // Server stats for each server
+  repeated ServerStats server_stats = 4;
+  // Number of cores available to each server
+  repeated int32 server_cores = 5;
+  // An after-the-fact computed summary
+  ScenarioResultSummary summary = 6;
+  // Information on success or failure of each worker
+  repeated bool client_success = 7;
+  repeated bool server_success = 8;
+  // Number of failed requests (one row per status code seen)
+  repeated RequestResultCount request_results = 9;
+}
diff --git a/benchmarks/src/main/proto/grpc/testing/messages.proto b/benchmarks/src/main/proto/grpc/testing/messages.proto
new file mode 100644
index 0000000..ec8c4ff
--- /dev/null
+++ b/benchmarks/src/main/proto/grpc/testing/messages.proto
@@ -0,0 +1,170 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+// Message definitions to be used by integration test service definitions.
+
+syntax = "proto3";
+
+package grpc.testing;
+
+option java_package = "io.grpc.benchmarks.proto";
+option java_outer_classname = "Messages";
+
+// TODO(dgq): Go back to using well-known types once
+// https://github.com/grpc/grpc/issues/6980 has been fixed.
+// import "google/protobuf/wrappers.proto";
+message BoolValue {
+  // The bool value.
+  bool value = 1;
+}
+
+// DEPRECATED, don't use. To be removed shortly.
+// The type of payload that should be returned.
+enum PayloadType {
+  // Compressable text format.
+  COMPRESSABLE = 0;
+}
+
+// A block of data, to simply increase gRPC message size.
+message Payload {
+  // DEPRECATED, don't use. To be removed shortly.
+  // The type of data in body.
+  PayloadType type = 1;
+  // Primary contents of payload.
+  bytes body = 2;
+}
+
+// A protobuf representation for grpc status. This is used by test
+// clients to specify a status that the server should attempt to return.
+message EchoStatus {
+  int32 code = 1;
+  string message = 2;
+}
+
+// Unary request.
+message SimpleRequest {
+  // DEPRECATED, don't use. To be removed shortly.
+  // Desired payload type in the response from the server.
+  // If response_type is RANDOM, server randomly chooses one from other formats.
+  PayloadType response_type = 1;
+
+  // Desired payload size in the response from the server.
+  int32 response_size = 2;
+
+  // Optional input payload sent along with the request.
+  Payload payload = 3;
+
+  // Whether SimpleResponse should include username.
+  bool fill_username = 4;
+
+  // Whether SimpleResponse should include OAuth scope.
+  bool fill_oauth_scope = 5;
+
+  // Whether to request the server to compress the response. This field is
+  // "nullable" in order to interoperate seamlessly with clients not able to
+  // implement the full compression tests by introspecting the call to verify
+  // the response's compression status.
+  BoolValue response_compressed = 6;
+
+  // Whether server should return a given status
+  EchoStatus response_status = 7;
+
+  // Whether the server should expect this request to be compressed.
+  BoolValue expect_compressed = 8;
+}
+
+// Unary response, as configured by the request.
+message SimpleResponse {
+  // Payload to increase message size.
+  Payload payload = 1;
+  // The user the request came from, for verifying authentication was
+  // successful when the client expected it.
+  string username = 2;
+  // OAuth scope.
+  string oauth_scope = 3;
+}
+
+// Client-streaming request.
+message StreamingInputCallRequest {
+  // Optional input payload sent along with the request.
+  Payload payload = 1;
+
+  // Whether the server should expect this request to be compressed. This field
+  // is "nullable" in order to interoperate seamlessly with servers not able to
+  // implement the full compression tests by introspecting the call to verify
+  // the request's compression status.
+  BoolValue expect_compressed = 2;
+
+  // Not expecting any payload from the response.
+}
+
+// Client-streaming response.
+message StreamingInputCallResponse {
+  // Aggregated size of payloads received from the client.
+  int32 aggregated_payload_size = 1;
+}
+
+// Configuration for a particular response.
+message ResponseParameters {
+  // Desired payload sizes in responses from the server.
+  int32 size = 1;
+
+  // Desired interval between consecutive responses in the response stream in
+  // microseconds.
+  int32 interval_us = 2;
+
+  // Whether to request the server to compress the response. This field is
+  // "nullable" in order to interoperate seamlessly with clients not able to
+  // implement the full compression tests by introspecting the call to verify
+  // the response's compression status.
+  BoolValue compressed = 3;
+}
+
+// Server-streaming request.
+message StreamingOutputCallRequest {
+  // DEPRECATED, don't use. To be removed shortly.
+  // Desired payload type in the response from the server.
+  // If response_type is RANDOM, the payload from each response in the stream
+  // might be of different types. This is to simulate a mixed type of payload
+  // stream.
+  PayloadType response_type = 1;
+
+  // Configuration for each expected response message.
+  repeated ResponseParameters response_parameters = 2;
+
+  // Optional input payload sent along with the request.
+  Payload payload = 3;
+
+  // Whether server should return a given status
+  EchoStatus response_status = 7;
+}
+
+// Server-streaming response, as configured by the request and parameters.
+message StreamingOutputCallResponse {
+  // Payload to increase response size.
+  Payload payload = 1;
+}
+
+// For reconnect interop test only.
+// Client tells server what reconnection parameters it used.
+message ReconnectParams {
+  int32 max_reconnect_backoff_ms = 1;
+}
+
+// For reconnect interop test only.
+// Server tells client whether its reconnects are following the spec and the
+// reconnect backoffs it saw.
+message ReconnectInfo {
+  bool passed = 1;
+  repeated int32 backoff_ms = 2;
+}
diff --git a/benchmarks/src/main/proto/grpc/testing/payloads.proto b/benchmarks/src/main/proto/grpc/testing/payloads.proto
new file mode 100644
index 0000000..6529d11
--- /dev/null
+++ b/benchmarks/src/main/proto/grpc/testing/payloads.proto
@@ -0,0 +1,42 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto3";
+
+package grpc.testing;
+
+option java_package = "io.grpc.benchmarks.proto";
+option java_outer_classname = "Payloads";
+
+message ByteBufferParams {
+  int32 req_size = 1;
+  int32 resp_size = 2;
+}
+
+message SimpleProtoParams {
+  int32 req_size = 1;
+  int32 resp_size = 2;
+}
+
+message ComplexProtoParams {
+  // TODO (vpai): Fill this in once the details of complex, representative
+  //              protos are decided
+}
+
+message PayloadConfig {
+  oneof payload {
+    ByteBufferParams bytebuf_params = 1;
+    SimpleProtoParams simple_params = 2;
+    ComplexProtoParams complex_params = 3;
+  }
+}
diff --git a/benchmarks/src/main/proto/grpc/testing/services.proto b/benchmarks/src/main/proto/grpc/testing/services.proto
new file mode 100644
index 0000000..543d4e8
--- /dev/null
+++ b/benchmarks/src/main/proto/grpc/testing/services.proto
@@ -0,0 +1,76 @@
+// Copyright 2015-2016 The gRPC Authors
+//
+// Licensed 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.
+// An integration test service that covers all the method signature permutations
+// of unary/streaming requests/responses.
+syntax = "proto3";
+
+import "grpc/testing/messages.proto";
+import "grpc/testing/control.proto";
+
+package grpc.testing;
+
+option java_package = "io.grpc.benchmarks.proto";
+option java_outer_classname = "Services";
+
+service BenchmarkService {
+  // One request followed by one response.
+  // The server returns the client payload as-is.
+  rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
+
+  // Repeated sequence of one request followed by one response.
+  // Should be called streaming ping-pong
+  // The server returns the client payload as-is on each response
+  rpc StreamingCall(stream SimpleRequest) returns (stream SimpleResponse);
+
+  // Single-sided unbounded streaming from client to server
+  // The server returns the client payload as-is once the client does WritesDone
+  rpc StreamingFromClient(stream SimpleRequest) returns (SimpleResponse);
+
+  // Single-sided unbounded streaming from server to client
+  // The server repeatedly returns the client payload as-is
+  rpc StreamingFromServer(SimpleRequest) returns (stream SimpleResponse);
+
+  // Two-sided unbounded streaming between server to client
+  // Both sides send the content of their own choice to the other
+  rpc StreamingBothWays(stream SimpleRequest) returns (stream SimpleResponse);
+}
+
+service WorkerService {
+  // Start server with specified workload.
+  // First request sent specifies the ServerConfig followed by ServerStatus
+  // response. After that, a "Mark" can be sent anytime to request the latest
+  // stats. Closing the stream will initiate shutdown of the test server
+  // and once the shutdown has finished, the OK status is sent to terminate
+  // this RPC.
+  rpc RunServer(stream ServerArgs) returns (stream ServerStatus);
+
+  // Start client with specified workload.
+  // First request sent specifies the ClientConfig followed by ClientStatus
+  // response. After that, a "Mark" can be sent anytime to request the latest
+  // stats. Closing the stream will initiate shutdown of the test client
+  // and once the shutdown has finished, the OK status is sent to terminate
+  // this RPC.
+  rpc RunClient(stream ClientArgs) returns (stream ClientStatus);
+
+  // Just return the core count - unary call
+  rpc CoreCount(CoreRequest) returns (CoreResponse);
+
+  // Quit this worker
+  rpc QuitWorker(Void) returns (Void);
+}
+
+service ReportQpsScenarioService {
+  // Report results of a QPS test benchmark scenario.
+  rpc ReportScenario(ScenarioResult) returns (Void);
+}
diff --git a/benchmarks/src/main/proto/grpc/testing/stats.proto b/benchmarks/src/main/proto/grpc/testing/stats.proto
new file mode 100644
index 0000000..5a233aa
--- /dev/null
+++ b/benchmarks/src/main/proto/grpc/testing/stats.proto
@@ -0,0 +1,77 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto3";
+
+package grpc.testing;
+
+option java_package = "io.grpc.benchmarks.proto";
+option java_outer_classname = "Stats";
+
+message ServerStats {
+  // wall clock time change in seconds since last reset
+  double time_elapsed = 1;
+
+  // change in user time (in seconds) used by the server since last reset
+  double time_user = 2;
+
+  // change in server time (in seconds) used by the server process and all
+  // threads since last reset
+  double time_system = 3;
+
+  // change in total cpu time of the server (data from proc/stat)
+  uint64 total_cpu_time = 4;
+
+  // change in idle time of the server (data from proc/stat)
+  uint64 idle_cpu_time = 5;
+
+  // Number of polls called inside completion queue
+  uint64 cq_poll_count = 6;
+}
+
+// Histogram params based on grpc/support/histogram.c
+message HistogramParams {
+  double resolution = 1;   // first bucket is [0, 1 + resolution)
+  double max_possible = 2; // use enough buckets to allow this value
+}
+
+// Histogram data based on grpc/support/histogram.c
+message HistogramData {
+  repeated uint32 bucket = 1;
+  double min_seen = 2;
+  double max_seen = 3;
+  double sum = 4;
+  double sum_of_squares = 5;
+  double count = 6;
+}
+
+message RequestResultCount {
+  int32 status_code = 1;
+  int64 count = 2;
+}
+
+message ClientStats {
+  // Latency histogram. Data points are in nanoseconds.
+  HistogramData latencies = 1;
+
+  // See ServerStats for details.
+  double time_elapsed = 2;
+  double time_user = 3;
+  double time_system = 4;
+
+  // Number of failed requests (one row per status code seen)
+  repeated RequestResultCount request_results = 5;
+
+  // Number of polls called inside completion queue
+  uint64 cq_poll_count = 6;
+}
diff --git a/benchmarks/src/test/java/io/grpc/benchmarks/driver/LoadClientTest.java b/benchmarks/src/test/java/io/grpc/benchmarks/driver/LoadClientTest.java
new file mode 100644
index 0000000..e8c6b92
--- /dev/null
+++ b/benchmarks/src/test/java/io/grpc/benchmarks/driver/LoadClientTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.driver;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+
+import io.grpc.benchmarks.proto.Control;
+import io.grpc.benchmarks.proto.Stats;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link LoadClient}.
+ */
+@RunWith(JUnit4.class)
+public class LoadClientTest {
+
+  @Test
+  public void testHistogramToStatsConversion() throws Exception {
+    double resolution = 1.01;
+    double maxPossible = 10000.0;
+    Control.ClientConfig.Builder config = Control.ClientConfig.newBuilder();
+    config.getHistogramParamsBuilder().setMaxPossible(maxPossible)
+        .setResolution(resolution - 1.0);
+    config.getPayloadConfigBuilder().getSimpleParamsBuilder()
+        .setReqSize(1)
+        .setRespSize(1);
+    config.setRpcType(Control.RpcType.UNARY);
+    config.setClientType(Control.ClientType.SYNC_CLIENT);
+    config.setClientChannels(1);
+    config.setOutstandingRpcsPerChannel(1);
+    config.getLoadParamsBuilder().getClosedLoopBuilder();
+    config.addServerTargets("localhost:9999");
+
+    LoadClient loadClient = new LoadClient(config.build());
+    loadClient.delay(1);
+    loadClient.delay(10);
+    loadClient.delay(10);
+    loadClient.delay(100);
+    loadClient.delay(100);
+    loadClient.delay(100);
+    loadClient.delay(1000);
+    loadClient.delay(1000);
+    loadClient.delay(1000);
+    loadClient.delay(1000);
+
+    Stats.ClientStats stats = loadClient.getStats();
+
+    assertEquals(1.0, stats.getLatencies().getMinSeen(), 0.0);
+    assertEquals(1000.0, stats.getLatencies().getMaxSeen(), 0.0);
+    assertEquals(10.0, stats.getLatencies().getCount(), 0.0);
+
+    double base = 0;
+    double logBase = 1;
+
+    for (int i = 0; i < stats.getLatencies().getBucketCount(); i++) {
+      int bucketCount = stats.getLatencies().getBucket(i);
+      if (base > 1.0 && base / resolution < 1.0) {
+        assertEquals(1, bucketCount);
+      } else if (base > 10.0 && base / resolution < 10.0) {
+        assertEquals(2, bucketCount);
+      } else if (base > 100.0 && base / resolution < 100.0) {
+        assertEquals(3, bucketCount);
+      } else if (base > 1000.0 && base / resolution < 1000.0) {
+        assertEquals(4, bucketCount);
+      } else {
+        assertEquals(0, bucketCount);
+      }
+      logBase = logBase * resolution;
+      base = logBase - 1;
+    }
+    assertTrue(base > 10000);
+    assertTrue(base / resolution <= 10000);
+  }
+}
diff --git a/benchmarks/src/test/java/io/grpc/benchmarks/driver/LoadWorkerTest.java b/benchmarks/src/test/java/io/grpc/benchmarks/driver/LoadWorkerTest.java
new file mode 100644
index 0000000..a9de21f
--- /dev/null
+++ b/benchmarks/src/test/java/io/grpc/benchmarks/driver/LoadWorkerTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.benchmarks.driver;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import io.grpc.ManagedChannel;
+import io.grpc.benchmarks.Utils;
+import io.grpc.benchmarks.proto.Control;
+import io.grpc.benchmarks.proto.Stats;
+import io.grpc.benchmarks.proto.WorkerServiceGrpc;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.stub.StreamObserver;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Basic tests for {@link io.grpc.benchmarks.driver.LoadWorker}
+ */
+@RunWith(JUnit4.class)
+public class LoadWorkerTest {
+
+
+  private static final int TIMEOUT = 5;
+  private static final Control.ClientArgs MARK = Control.ClientArgs.newBuilder()
+      .setMark(Control.Mark.newBuilder().setReset(true).build())
+      .build();
+
+  private LoadWorker worker;
+  private ManagedChannel channel;
+  private WorkerServiceGrpc.WorkerServiceStub workerServiceStub;
+  private LinkedBlockingQueue<Stats.ClientStats> marksQueue;
+
+  @Before
+  public void setup() throws Exception {
+    int port = Utils.pickUnusedPort();
+    worker = new LoadWorker(port, 0);
+    worker.start();
+    channel = NettyChannelBuilder.forAddress("localhost", port).usePlaintext().build();
+    workerServiceStub = WorkerServiceGrpc.newStub(channel);
+    marksQueue = new LinkedBlockingQueue<Stats.ClientStats>();
+  }
+
+  @Test
+  public void runUnaryBlockingClosedLoop() throws Exception {
+    Control.ServerArgs.Builder serverArgsBuilder = Control.ServerArgs.newBuilder();
+    serverArgsBuilder.getSetupBuilder()
+        .setServerType(Control.ServerType.ASYNC_SERVER)
+        .setAsyncServerThreads(4)
+        .setPort(0)
+        .getPayloadConfigBuilder().getSimpleParamsBuilder().setRespSize(1000);
+    int serverPort = startServer(serverArgsBuilder.build());
+
+    Control.ClientArgs.Builder clientArgsBuilder = Control.ClientArgs.newBuilder();
+    String serverAddress = "localhost:" + serverPort;
+    clientArgsBuilder.getSetupBuilder()
+        .setClientType(Control.ClientType.SYNC_CLIENT)
+        .setRpcType(Control.RpcType.UNARY)
+        .setClientChannels(2)
+        .setOutstandingRpcsPerChannel(2)
+        .addServerTargets(serverAddress);
+    clientArgsBuilder.getSetupBuilder().getPayloadConfigBuilder().getSimpleParamsBuilder()
+        .setReqSize(1000)
+        .setRespSize(1000);
+    clientArgsBuilder.getSetupBuilder().getHistogramParamsBuilder()
+        .setResolution(0.01)
+        .setMaxPossible(60000000000.0);
+    StreamObserver<Control.ClientArgs> clientObserver = startClient(clientArgsBuilder.build());
+    assertWorkOccurred(clientObserver);
+  }
+
+  @Test
+  public void runUnaryAsyncClosedLoop() throws Exception {
+    Control.ServerArgs.Builder serverArgsBuilder = Control.ServerArgs.newBuilder();
+    serverArgsBuilder.getSetupBuilder()
+        .setServerType(Control.ServerType.ASYNC_SERVER)
+        .setAsyncServerThreads(4)
+        .setPort(0)
+        .getPayloadConfigBuilder().getSimpleParamsBuilder().setRespSize(1000);
+    int serverPort = startServer(serverArgsBuilder.build());
+
+    Control.ClientArgs.Builder clientArgsBuilder = Control.ClientArgs.newBuilder();
+    String serverAddress = "localhost:" + serverPort;
+    clientArgsBuilder.getSetupBuilder()
+        .setClientType(Control.ClientType.ASYNC_CLIENT)
+        .setClientChannels(2)
+        .setRpcType(Control.RpcType.UNARY)
+        .setOutstandingRpcsPerChannel(1)
+        .setAsyncClientThreads(4)
+        .addServerTargets(serverAddress);
+    clientArgsBuilder.getSetupBuilder().getPayloadConfigBuilder().getSimpleParamsBuilder()
+        .setReqSize(1000)
+        .setRespSize(1000);
+    clientArgsBuilder.getSetupBuilder().getHistogramParamsBuilder()
+        .setResolution(0.01)
+        .setMaxPossible(60000000000.0);
+    StreamObserver<Control.ClientArgs> clientObserver = startClient(clientArgsBuilder.build());
+    assertWorkOccurred(clientObserver);
+  }
+
+  @Test
+  public void runPingPongAsyncClosedLoop() throws Exception {
+    Control.ServerArgs.Builder serverArgsBuilder = Control.ServerArgs.newBuilder();
+    serverArgsBuilder.getSetupBuilder()
+        .setServerType(Control.ServerType.ASYNC_SERVER)
+        .setAsyncServerThreads(4)
+        .setPort(0)
+        .getPayloadConfigBuilder().getSimpleParamsBuilder().setRespSize(1000);
+    int serverPort = startServer(serverArgsBuilder.build());
+
+    Control.ClientArgs.Builder clientArgsBuilder = Control.ClientArgs.newBuilder();
+    String serverAddress = "localhost:" + serverPort;
+    clientArgsBuilder.getSetupBuilder()
+        .setClientType(Control.ClientType.ASYNC_CLIENT)
+        .setClientChannels(2)
+        .setRpcType(Control.RpcType.STREAMING)
+        .setOutstandingRpcsPerChannel(1)
+        .setAsyncClientThreads(4)
+        .addServerTargets(serverAddress);
+    clientArgsBuilder.getSetupBuilder().getPayloadConfigBuilder().getSimpleParamsBuilder()
+        .setReqSize(1000)
+        .setRespSize(1000);
+    clientArgsBuilder.getSetupBuilder().getHistogramParamsBuilder()
+        .setResolution(0.01)
+        .setMaxPossible(60000000000.0);
+    StreamObserver<Control.ClientArgs> clientObserver = startClient(clientArgsBuilder.build());
+    assertWorkOccurred(clientObserver);
+  }
+
+  @Test
+  public void runGenericPingPongAsyncClosedLoop() throws Exception {
+    Control.ServerArgs.Builder serverArgsBuilder = Control.ServerArgs.newBuilder();
+    serverArgsBuilder.getSetupBuilder()
+        .setServerType(Control.ServerType.ASYNC_GENERIC_SERVER)
+        .setAsyncServerThreads(4)
+        .setPort(0)
+        .getPayloadConfigBuilder().getBytebufParamsBuilder().setReqSize(1000).setRespSize(1000);
+    int serverPort = startServer(serverArgsBuilder.build());
+
+    Control.ClientArgs.Builder clientArgsBuilder = Control.ClientArgs.newBuilder();
+    String serverAddress = "localhost:" + serverPort;
+    clientArgsBuilder.getSetupBuilder()
+        .setClientType(Control.ClientType.ASYNC_CLIENT)
+        .setClientChannels(2)
+        .setRpcType(Control.RpcType.STREAMING)
+        .setOutstandingRpcsPerChannel(1)
+        .setAsyncClientThreads(4)
+        .addServerTargets(serverAddress);
+    clientArgsBuilder.getSetupBuilder().getPayloadConfigBuilder().getBytebufParamsBuilder()
+        .setReqSize(1000)
+        .setRespSize(1000);
+    clientArgsBuilder.getSetupBuilder().getHistogramParamsBuilder()
+        .setResolution(0.01)
+        .setMaxPossible(60000000000.0);
+    StreamObserver<Control.ClientArgs> clientObserver = startClient(clientArgsBuilder.build());
+    assertWorkOccurred(clientObserver);
+  }
+
+  private void assertWorkOccurred(StreamObserver<Control.ClientArgs> clientObserver)
+      throws InterruptedException {
+
+    Stats.ClientStats stat = null;
+    for (int i = 0; i < 3; i++) {
+      // Poll until we get some stats
+      Thread.sleep(300);
+      clientObserver.onNext(MARK);
+      stat = marksQueue.poll(TIMEOUT, TimeUnit.SECONDS);
+      if (stat == null) {
+        fail("Did not receive stats");
+      }
+      if (stat.getLatencies().getCount() > 10) {
+        break;
+      }
+    }
+    clientObserver.onCompleted();
+    assertTrue(stat.hasLatencies());
+    assertTrue(stat.getLatencies().getCount() < stat.getLatencies().getSum());
+    double mean = stat.getLatencies().getSum() / stat.getLatencies().getCount();
+    System.out.println("Mean " + mean + " us");
+    assertTrue(mean > stat.getLatencies().getMinSeen());
+    assertTrue(mean < stat.getLatencies().getMaxSeen());
+  }
+
+  private StreamObserver<Control.ClientArgs> startClient(Control.ClientArgs clientArgs)
+      throws InterruptedException {
+    final CountDownLatch clientReady = new CountDownLatch(1);
+    StreamObserver<Control.ClientArgs> clientObserver = workerServiceStub.runClient(
+        new StreamObserver<Control.ClientStatus>() {
+          @Override
+          public void onNext(Control.ClientStatus value) {
+            clientReady.countDown();
+            if (value.hasStats()) {
+              marksQueue.add(value.getStats());
+            }
+          }
+
+          @Override
+          public void onError(Throwable t) {
+          }
+
+          @Override
+          public void onCompleted() {
+          }
+        });
+
+    // Start the client
+    clientObserver.onNext(clientArgs);
+    if (!clientReady.await(TIMEOUT, TimeUnit.SECONDS)) {
+      fail("Client failed to start");
+    }
+    return clientObserver;
+  }
+
+  private int startServer(Control.ServerArgs serverArgs) throws InterruptedException {
+    final AtomicInteger serverPort = new AtomicInteger();
+    final CountDownLatch serverReady = new CountDownLatch(1);
+    StreamObserver<Control.ServerArgs> serverObserver =
+        workerServiceStub.runServer(new StreamObserver<Control.ServerStatus>() {
+          @Override
+          public void onNext(Control.ServerStatus value) {
+            serverPort.set(value.getPort());
+            serverReady.countDown();
+          }
+
+          @Override
+          public void onError(Throwable t) {
+          }
+
+          @Override
+          public void onCompleted() {
+          }
+        });
+    // trigger server startup
+    serverObserver.onNext(serverArgs);
+    if (!serverReady.await(TIMEOUT, TimeUnit.SECONDS)) {
+      fail("Server failed to start");
+    }
+    return serverPort.get();
+  }
+}
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..2297bed
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,509 @@
+buildscript {
+    repositories {
+        mavenLocal()
+        maven { url "https://plugins.gradle.org/m2/" }
+    }
+    dependencies {
+        classpath "com.diffplug.spotless:spotless-plugin-gradle:3.13.0"
+        classpath 'com.google.gradle:osdetector-gradle-plugin:1.4.0'
+        classpath 'ru.vyarus:gradle-animalsniffer-plugin:1.4.5'
+        classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.13'
+        classpath 'net.ltgt.gradle:gradle-apt-plugin:0.13'
+        classpath "me.champeau.gradle:jmh-gradle-plugin:0.4.5"
+        classpath 'me.champeau.gradle:japicmp-gradle-plugin:0.2.5'
+    }
+}
+
+subprojects {
+    apply plugin: "checkstyle"
+    apply plugin: "java"
+    apply plugin: "maven"
+    apply plugin: "idea"
+    apply plugin: "signing"
+    apply plugin: "jacoco"
+
+    apply plugin: "me.champeau.gradle.jmh"
+    apply plugin: "com.google.osdetector"
+    // The plugin only has an effect if a signature is specified
+    apply plugin: "ru.vyarus.animalsniffer"
+    // jdk10 not supported by errorprone: https://github.com/google/error-prone/issues/860
+    if (!JavaVersion.current().isJava10Compatible() &&
+        rootProject.properties.get('errorProne', true)) {
+        apply plugin: "net.ltgt.errorprone"
+        apply plugin: "net.ltgt.apt"
+
+        dependencies {
+            // The ErrorProne plugin defaults to the latest, which would break our
+            // build if error prone releases a new version with a new check
+            errorprone 'com.google.errorprone:error_prone_core:2.2.0'
+            apt 'com.google.guava:guava-beta-checker:1.0'
+        }
+    } else {
+        // Remove per-project error-prone checker config
+        allprojects {
+            afterEvaluate { project ->
+                project.tasks.withType(JavaCompile) {
+                    options.compilerArgs.removeAll { it.startsWith("-Xep") }
+                }
+            }
+        }
+    }
+    // TODO(zpencer): remove when intellij 2017.2 is released
+    // https://github.com/gradle/gradle/issues/2315
+    idea.module.inheritOutputDirs = true
+
+    group = "io.grpc"
+    version = "1.16.0-SNAPSHOT" // CURRENT_GRPC_VERSION
+
+    sourceCompatibility = 1.7
+    targetCompatibility = 1.7
+
+    repositories {
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+        mavenLocal()
+    }
+
+    [
+        compileJava,
+        compileTestJava,
+        compileJmhJava
+    ].each() {
+        it.options.compilerArgs += [
+            "-Xlint:all",
+            "-Xlint:-options",
+            "-Xlint:-path",
+            "-Xlint:-try"
+        ]
+        it.options.encoding = "UTF-8"
+        if (rootProject.hasProperty('failOnWarnings') && rootProject.failOnWarnings.toBoolean()) {
+            it.options.compilerArgs += ["-Werror"]
+        }
+    }
+
+    compileTestJava {
+        // serialVersionUID is basically guaranteed to be useless in our tests
+        // LinkedList doesn't hurt much in tests and has lots of usages
+        options.compilerArgs += [
+            "-Xlint:-serial",
+            "-Xep:JdkObsolete:OFF"
+        ]
+    }
+
+    jar.manifest {
+        attributes('Implementation-Title': name,
+        'Implementation-Version': version,
+        'Built-By': System.getProperty('user.name'),
+        'Built-JDK': System.getProperty('java.version'),
+        'Source-Compatibility': sourceCompatibility,
+        'Target-Compatibility': targetCompatibility)
+    }
+
+    javadoc.options {
+        encoding = 'UTF-8'
+        use = true
+        links 'https://docs.oracle.com/javase/8/docs/api/'
+    }
+
+    ext {
+        def exeSuffix = osdetector.os == 'windows' ? ".exe" : ""
+        protocPluginBaseName = 'protoc-gen-grpc-java'
+        javaPluginPath = "$rootDir/compiler/build/exe/java_plugin/$protocPluginBaseName$exeSuffix"
+
+        nettyVersion = '4.1.27.Final'
+        guavaVersion = '20.0'
+        protobufVersion = '3.5.1'
+        protocVersion = '3.5.1-1'
+        protobufNanoVersion = '3.0.0-alpha-5'
+        opencensusVersion = '0.12.3'
+
+        configureProtoCompilation = {
+            String generatedSourcePath = "${projectDir}/src/generated"
+            if (rootProject.childProjects.containsKey('grpc-compiler')) {
+                // Only when the codegen is built along with the project, will we be able to recompile
+                // the proto files.
+                project.apply plugin: 'com.google.protobuf'
+                project.protobuf {
+                    protoc {
+                        if (project.hasProperty('protoc')) {
+                            path = project.protoc
+                        } else {
+                            artifact = "com.google.protobuf:protoc:${protocVersion}"
+                        }
+                    }
+                    plugins { grpc { path = javaPluginPath } }
+                    generateProtoTasks {
+                        all().each { task ->
+                            task.dependsOn ':grpc-compiler:java_pluginExecutable'
+                            // Delete the generated sources first, so that we can be alerted if they are not re-compiled.
+                            task.dependsOn 'deleteGeneratedSource' + task.sourceSet.name
+                            // Recompile protos when the codegen has been changed
+                            task.inputs.file javaPluginPath
+                            // Recompile protos when build.gradle has been changed, because
+                            // it's possible the version of protoc has been changed.
+                            task.inputs.file "${rootProject.projectDir}/build.gradle"
+                            task.plugins { grpc { option 'noversion' } }
+                        }
+                    }
+                    generatedFilesBaseDir = generatedSourcePath
+                }
+
+                sourceSets.each { sourceSet ->
+                    task "deleteGeneratedSource${sourceSet.name}" {
+                        doLast {
+                            project.delete project.fileTree(dir: generatedSourcePath + '/' + sourceSet.name)
+                        }
+                    }
+                }
+            } else {
+                // Otherwise, we just use the checked-in generated code.
+                project.sourceSets {
+                    main {
+                        java {
+                            srcDir "${generatedSourcePath}/main/java"
+                            srcDir "${generatedSourcePath}/main/javanano"
+                            srcDir "${generatedSourcePath}/main/grpc"
+                        }
+                    }
+                    test {
+                        java {
+                            srcDir "${generatedSourcePath}/test/java"
+                            srcDir "${generatedSourcePath}/test/javanano"
+                            srcDir "${generatedSourcePath}/test/grpc"
+                        }
+                    }
+                }
+            }
+
+            [
+                compileJava,
+                compileTestJava,
+                compileJmhJava
+            ].each() {
+                // Protobuf-generated code produces some warnings.
+                // https://github.com/google/protobuf/issues/2718
+                it.options.compilerArgs += [
+                    "-Xlint:-cast",
+                    "-XepExcludedPaths:.*/src/generated/[^/]+/java/.*",
+                ]
+            }
+        }
+
+        def epoll_suffix = "";
+        if (osdetector.classifier in ["linux-x86_64"]) {
+            // The native code is only pre-compiled on certain platforms.
+            epoll_suffix = ":" + osdetector.classifier
+        }
+        libraries = [
+            animalsniffer_annotations: "org.codehaus.mojo:animal-sniffer-annotations:1.17",
+            errorprone: "com.google.errorprone:error_prone_annotations:2.2.0",
+            gson: "com.google.code.gson:gson:2.7",
+            guava: "com.google.guava:guava:${guavaVersion}",
+            hpack: 'com.twitter:hpack:0.10.1',
+            javax_annotation: 'javax.annotation:javax.annotation-api:1.2',
+            jsr305: 'com.google.code.findbugs:jsr305:3.0.0',
+            oauth_client: 'com.google.auth:google-auth-library-oauth2-http:0.9.0',
+            google_api_protos: 'com.google.api.grpc:proto-google-common-protos:1.0.0',
+            google_auth_credentials: 'com.google.auth:google-auth-library-credentials:0.9.0',
+            google_auth_oauth2_http: 'com.google.auth:google-auth-library-oauth2-http:0.9.0',
+            okhttp: 'com.squareup.okhttp:okhttp:2.5.0',
+            okio: 'com.squareup.okio:okio:1.13.0',
+            opencensus_api: "io.opencensus:opencensus-api:${opencensusVersion}",
+            opencensus_contrib_grpc_metrics: "io.opencensus:opencensus-contrib-grpc-metrics:${opencensusVersion}",
+            opencensus_impl: "io.opencensus:opencensus-impl:${opencensusVersion}",
+            opencensus_impl_lite: "io.opencensus:opencensus-impl-lite:${opencensusVersion}",
+            instrumentation_api: 'com.google.instrumentation:instrumentation-api:0.4.3',
+            protobuf: "com.google.protobuf:protobuf-java:${protobufVersion}",
+            protobuf_lite: "com.google.protobuf:protobuf-lite:3.0.1",
+            protoc_lite: "com.google.protobuf:protoc-gen-javalite:3.0.0",
+            protobuf_nano: "com.google.protobuf.nano:protobuf-javanano:${protobufNanoVersion}",
+            protobuf_plugin: 'com.google.protobuf:protobuf-gradle-plugin:0.8.5',
+            protobuf_util: "com.google.protobuf:protobuf-java-util:${protobufVersion}",
+            lang: "org.apache.commons:commons-lang3:3.5",
+
+            netty: "io.netty:netty-codec-http2:[${nettyVersion}]",
+            netty_epoll: "io.netty:netty-transport-native-epoll:${nettyVersion}" + epoll_suffix,
+            netty_proxy_handler: "io.netty:netty-handler-proxy:${nettyVersion}",
+            netty_tcnative: 'io.netty:netty-tcnative-boringssl-static:2.0.12.Final',
+
+            conscrypt: 'org.conscrypt:conscrypt-openjdk-uber:1.0.1',
+            re2j: 'com.google.re2j:re2j:1.2',
+
+            // Test dependencies.
+            junit: 'junit:junit:4.12',
+            mockito: 'org.mockito:mockito-core:1.9.5',
+            truth: 'com.google.truth:truth:0.42',
+            guava_testlib: 'com.google.guava:guava-testlib:20.0',
+
+            // Benchmark dependencies
+            hdrhistogram: 'org.hdrhistogram:HdrHistogram:2.1.10',
+            math: 'org.apache.commons:commons-math3:3.6',
+
+            // Jetty ALPN dependencies
+            jetty_alpn_agent: 'org.mortbay.jetty.alpn:jetty-alpn-agent:2.0.7'
+        ]
+    }
+
+    // Define a separate configuration for managing the dependency on Jetty ALPN agent.
+    configurations {
+        alpnagent
+
+        compile {
+            // Detect Maven Enforcer's dependencyConvergence failures. We only
+            // care for artifacts used as libraries by others.
+            if (!(project.name in [
+                'grpc-benchmarks',
+                'grpc-interop-testing',
+                'grpc-gae-interop-testing-jdk7',
+                'grpc-gae-interop-testing-jdk8',
+            ])) {
+                resolutionStrategy.failOnVersionConflict()
+            }
+        }
+    }
+
+    dependencies {
+        testCompile libraries.junit,
+                libraries.mockito,
+                libraries.truth
+
+        // Configuration for modules that use Jetty ALPN agent
+        alpnagent libraries.jetty_alpn_agent
+
+        jmh 'org.openjdk.jmh:jmh-core:1.19',
+                'org.openjdk.jmh:jmh-generator-bytecode:1.19'
+    }
+
+    signing {
+        required false
+        sign configurations.archives
+    }
+
+    // Disable JavaDoc doclint on Java 8. It's annoying.
+    if (JavaVersion.current().isJava8Compatible()) {
+        allprojects {
+            tasks.withType(Javadoc) {
+                options.addStringOption('Xdoclint:none', '-quiet')
+            }
+        }
+    }
+
+    // For jdk10 we must explicitly choose between html4 and html5, otherwise we get a warning
+    if (JavaVersion.current().isJava10Compatible()) {
+        allprojects {
+            tasks.withType(Javadoc) {
+                options.addBooleanOption('html4', true)
+            }
+        }
+    }
+
+    checkstyle {
+        configDir = file("$rootDir/buildscripts")
+        toolVersion = "6.17"
+        ignoreFailures = false
+        if (rootProject.hasProperty("checkstyle.ignoreFailures")) {
+            ignoreFailures = rootProject.properties["checkstyle.ignoreFailures"].toBoolean()
+        }
+    }
+
+    checkstyleMain {
+        source = fileTree(dir: "src/main", include: "**/*.java")
+    }
+
+    checkstyleTest {
+        source = fileTree(dir: "src/test", include: "**/*.java")
+    }
+
+    // invoke jmh on a single benchmark class like so:
+    //   ./gradlew -PjmhIncludeSingleClass=StatsTraceContextBenchmark clean :grpc-core:jmh
+    jmh {
+        warmupIterations = 10
+        iterations = 10
+        fork = 1
+        // None of our benchmarks need the tests, and we have pseudo-circular
+        // dependencies that break when including them. (context's testCompile
+        // depends on core; core's testCompile depends on testing)
+        includeTests = false
+        if (project.hasProperty('jmhIncludeSingleClass')) {
+            include = [
+                project.property('jmhIncludeSingleClass')
+            ]
+        }
+    }
+
+    task javadocJar(type: Jar) {
+        classifier = 'javadoc'
+        from javadoc
+    }
+
+    task sourcesJar(type: Jar) {
+        classifier = 'sources'
+        from sourceSets.main.allSource
+    }
+
+    artifacts { archives javadocJar, sourcesJar }
+
+    uploadArchives.repositories.mavenDeployer {
+        beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
+        if (rootProject.hasProperty('repositoryDir')) {
+            repository(url: new File(rootProject.repositoryDir).toURI())
+        } else {
+            String stagingUrl
+            if (rootProject.hasProperty('repositoryId')) {
+                stagingUrl = 'https://oss.sonatype.org/service/local/staging/deployByRepositoryId/' +
+                        rootProject.repositoryId
+            } else {
+                stagingUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
+            }
+            def configureAuth = {
+                if (rootProject.hasProperty('ossrhUsername') && rootProject.hasProperty('ossrhPassword')) {
+                    authentication(userName: rootProject.ossrhUsername, password: rootProject.ossrhPassword)
+                }
+            }
+            repository(url: stagingUrl, configureAuth)
+            snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots/', configureAuth)
+        }
+    }
+    uploadArchives.onlyIf { !name.contains("grpc-gae-interop-testing") }
+
+    [
+        install.repositories.mavenInstaller,
+        uploadArchives.repositories.mavenDeployer,
+    ]*.pom*.whenConfigured { pom ->
+        pom.project {
+            name "$project.group:$project.name"
+            description project.description
+            url 'https://github.com/grpc/grpc-java'
+
+            scm {
+                connection 'scm:git:https://github.com/grpc/grpc-java.git'
+                developerConnection 'scm:git:git@github.com:grpc/grpc-java.git'
+                url 'https://github.com/grpc/grpc-java'
+            }
+
+            licenses {
+                license {
+                    name 'Apache 2.0'
+                    url 'https://opensource.org/licenses/Apache-2.0'
+                }
+            }
+
+            developers {
+                developer {
+                    id "grpc.io"
+                    name "gRPC Contributors"
+                    email "grpc-io@googlegroups.com"
+                    url "https://grpc.io/"
+                    // https://issues.gradle.org/browse/GRADLE-2719
+                    organization = "gRPC Authors"
+                    organizationUrl "https://www.google.com"
+                }
+            }
+        }
+        if (!(project.name in
+        [
+            "grpc-stub",
+            "grpc-protobuf",
+            "grpc-protobuf-lite",
+            "grpc-protobuf-nano"
+        ])) {
+            def core = pom.dependencies.find {dep -> dep.artifactId == 'grpc-core'}
+            if (core != null) {
+                // Depend on specific version of grpc-core because internal package is unstable
+                core.version = "[" + core.version + "]"
+            }
+        }
+    }
+    // At a test failure, log the stack trace to the console so that we don't
+    // have to open the HTML in a browser.
+    test {
+        testLogging {
+            exceptionFormat = 'full'
+            showExceptions true
+            showCauses true
+            showStackTraces true
+        }
+        maxHeapSize = '1500m'
+    }
+}
+
+// Run with: ./gradlew japicmp --continue
+def baselineGrpcVersion = '1.6.1'
+def publicApiSubprojects = [
+    // TODO: uncomment after grpc-alts artifact is published.
+    // ':grpc-alts',
+    ':grpc-auth',
+    ':grpc-context',
+    ':grpc-core',
+    ':grpc-grpclb',
+    ':grpc-netty',
+    ':grpc-okhttp',
+    ':grpc-protobuf',
+    ':grpc-protobuf-lite',
+    ':grpc-protobuf-nano',
+    ':grpc-stub',
+    ':grpc-testing',
+]
+
+publicApiSubprojects.each { name ->
+    project(":$name") {
+        apply plugin: 'me.champeau.gradle.japicmp'
+
+        // Get the baseline version's jar for this subproject
+        File baselineArtifact = null
+        // Use a detached configuration, otherwise the current version's jar will take precedence
+        // over the baseline jar.
+        // A necessary hack, the intuitive thing does NOT work:
+        // https://discuss.gradle.org/t/is-the-default-configuration-leaking-into-independent-configurations/2088/6
+        def oldGroup = project.group
+        try {
+            project.group = 'virtual_group_for_japicmp'
+            String depModule = "io.grpc:${project.name}:${baselineGrpcVersion}@jar"
+            String depJar = "${project.name}-${baselineGrpcVersion}.jar"
+            Configuration configuration = configurations.detachedConfiguration(
+                    dependencies.create(depModule)
+                    )
+            baselineArtifact = files(configuration.files).filter {
+                it.name.equals(depJar)
+            }.singleFile
+        } finally {
+            project.group = oldGroup
+        }
+
+        // Add a japicmp task that compares the current .jar with baseline .jar
+        task japicmp(type: me.champeau.gradle.japicmp.JapicmpTask, dependsOn: jar) {
+            oldClasspath = files(baselineArtifact)
+            newClasspath = files(jar.archivePath)
+            onlyBinaryIncompatibleModified = false
+            // Be quiet about things that did not change
+            onlyModified = true
+            // This task should fail if there are incompatible changes
+            failOnModification = true
+            ignoreMissingClasses = true
+            htmlOutputFile = file("$buildDir/reports/japi.html")
+
+            packageExcludes = ['io.grpc.internal']
+
+            // Also break on source incompatible changes, not just binary.
+            // Eg adding abstract method to public class.
+            // TODO(zpencer): enable after japicmp-gradle-plugin/pull/14
+            // breakOnSourceIncompatibility = true
+
+            // Ignore any classes or methods marked @ExperimentalApi
+            // TODO(zpencer): enable after japicmp-gradle-plugin/pull/15
+            // annotationExcludes = ['@io.grpc.ExperimentalApi']
+        }
+    }
+}
+
+// format checkers
+apply plugin: "com.diffplug.gradle.spotless"
+apply plugin: 'groovy'
+spotless {
+    groovyGradle {
+        target '**/*.gradle'
+        greclipse()
+        indentWithSpaces()
+        paddedCell()
+    }
+}
diff --git a/buildscripts/build_artifacts_in_docker.sh b/buildscripts/build_artifacts_in_docker.sh
new file mode 100755
index 0000000..ca24f24
--- /dev/null
+++ b/buildscripts/build_artifacts_in_docker.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -exu -o pipefail
+
+# Runs all the tests and builds mvn artifacts.
+# mvn artifacts are stored in grpc-java/mvn-artifacts/
+ALL_ARTIFACTS=true ARCH=64 "$(dirname $0)"/kokoro/unix.sh
+# Already ran tests the first time, so skip tests this time
+SKIP_TESTS=true ARCH=32 "$(dirname $0)"/kokoro/unix.sh
diff --git a/buildscripts/build_docker.sh b/buildscripts/build_docker.sh
new file mode 100755
index 0000000..ca3edf8
--- /dev/null
+++ b/buildscripts/build_docker.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+set -eu -o pipefail
+
+readonly proto_dir="$(mktemp -d protobuf.XXXXXX)"
+# Download an unreleased SHA to include TLS 1.2 support:
+# https://github.com/google/protobuf/pull/4879
+wget -O - https://github.com/google/protobuf/archive/61476b8e74357ea875f71bb321874ca4530b7d50.tar.gz | tar xz -C "$proto_dir"
+
+docker build -t protoc-artifacts "$proto_dir"/protobuf-61476b8e74357ea875f71bb321874ca4530b7d50/protoc-artifacts
+rm -r "$proto_dir"
diff --git a/buildscripts/checkstyle.license b/buildscripts/checkstyle.license
new file mode 100644
index 0000000..cef19a3
--- /dev/null
+++ b/buildscripts/checkstyle.license
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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/buildscripts/checkstyle.xml b/buildscripts/checkstyle.xml
new file mode 100644
index 0000000..52b5642
--- /dev/null
+++ b/buildscripts/checkstyle.xml
@@ -0,0 +1,229 @@
+<?xml version="1.0"?>
+<!DOCTYPE module PUBLIC
+          "-//Puppy Crawl//DTD Check Configuration 1.3//EN"
+          "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
+
+<!--
+    Checkstyle configuration that checks the Google coding conventions from Google Java Style
+    that can be found at https://google.github.io/styleguide/javaguide.html.
+
+    Checkstyle is very configurable. Be sure to read the documentation at
+    http://checkstyle.sf.net (or in your downloaded distribution).
+
+    To completely disable a check, just comment it out or delete it from the file.
+
+    Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
+ -->
+
+<module name = "Checker">
+    <property name="charset" value="UTF-8"/>
+    
+    <property name="severity" value="error"/>
+
+    <module name="Header">
+      <property name="headerFile" value="${config_loc}/checkstyle.license"/>
+      <property name="ignoreLines" value="2"/>
+      <property name="fileExtensions" value="java"/>
+    </module>
+
+    <property name="fileExtensions" value="java, properties, xml"/>
+    <!-- Checks for whitespace                               -->
+    <!-- See http://checkstyle.sf.net/config_whitespace.html -->
+        <module name="FileTabCharacter">
+            <property name="eachLine" value="true"/>
+        </module>
+
+    <module name="TreeWalker">
+        <module name="OuterTypeFilename"/>
+        <module name="IllegalTokenText">
+            <property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
+            <property name="format" value="\\u00(08|09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
+            <property name="message" value="Avoid using corresponding octal or Unicode escape."/>
+        </module>
+        <module name="AvoidEscapedUnicodeCharacters">
+            <property name="allowEscapesForControlCharacters" value="true"/>
+            <property name="allowByTailComment" value="true"/>
+            <property name="allowNonPrintableEscapes" value="true"/>
+        </module>
+        <module name="LineLength">
+            <property name="max" value="100"/>
+            <property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
+        </module>
+        <module name="AvoidStarImport"/>
+        <!-- TODO(ejona): Upstream? -->
+        <module name="UnusedImports"/>
+        <module name="OneTopLevelClass"/>
+        <module name="NoLineWrap"/>
+        <module name="EmptyBlock">
+            <property name="option" value="TEXT"/>
+            <property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
+        </module>
+        <module name="NeedBraces"/>
+        <module name="LeftCurly">
+            <property name="maxLineLength" value="100"/>
+        </module>
+        <module name="RightCurly"/>
+        <module name="RightCurly">
+            <property name="option" value="alone"/>
+            <property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT"/>
+        </module>
+        <module name="WhitespaceAround">
+            <property name="allowEmptyConstructors" value="true"/>
+            <property name="allowEmptyMethods" value="true"/>
+            <property name="allowEmptyTypes" value="true"/>
+            <property name="allowEmptyLoops" value="true"/>
+            <message key="ws.notFollowed"
+             value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
+             <message key="ws.notPreceded"
+             value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
+        </module>
+        <module name="OneStatementPerLine"/>
+        <module name="MultipleVariableDeclarations"/>
+        <module name="ArrayTypeStyle"/>
+        <module name="MissingSwitchDefault"/>
+        <module name="FallThrough"/>
+        <module name="UpperEll"/>
+        <module name="ModifierOrder"/>
+        <module name="EmptyLineSeparator">
+            <property name="allowNoEmptyLineBetweenFields" value="true"/>
+        </module>
+        <module name="SeparatorWrap">
+            <property name="tokens" value="DOT"/>
+            <property name="option" value="nl"/>
+        </module>
+        <module name="SeparatorWrap">
+            <property name="tokens" value="COMMA"/>
+            <property name="option" value="EOL"/>
+        </module>
+        <module name="PackageName">
+            <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
+            <message key="name.invalidPattern"
+             value="Package name ''{0}'' must match pattern ''{1}''."/>
+        </module>
+        <module name="TypeName">
+            <message key="name.invalidPattern"
+             value="Type name ''{0}'' must match pattern ''{1}''."/>
+        </module>
+        <module name="MemberName">
+            <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
+            <message key="name.invalidPattern"
+             value="Member name ''{0}'' must match pattern ''{1}''."/>
+        </module>
+        <module name="ParameterName">
+            <!-- TODO(ejona): too strict, as one-character parameter names are permitted
+            <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/-->
+            <property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
+            <message key="name.invalidPattern"
+             value="Parameter name ''{0}'' must match pattern ''{1}''."/>
+        </module>
+        <module name="CatchParameterName">
+            <!-- TODO(ejona): too strict, as one-character parameter names are permitted.
+            <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/-->
+            <property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
+            <message key="name.invalidPattern"
+             value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
+        </module>
+        <module name="LocalVariableName">
+            <property name="tokens" value="VARIABLE_DEF"/>
+            <!-- TODO(ejona): too strict, as one-character parameter names are permitted.
+                 Add support for Unicode.
+            <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/-->
+            <property name="format" value="^[^A-Z]"/>
+            <property name="allowOneCharVarInForLoop" value="true"/>
+            <message key="name.invalidPattern"
+             value="Local variable name ''{0}'' must match pattern ''{1}''."/>
+        </module>
+        <module name="ClassTypeParameterName">
+            <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
+            <message key="name.invalidPattern"
+             value="Class type name ''{0}'' must match pattern ''{1}''."/>
+        </module>
+        <module name="MethodTypeParameterName">
+            <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
+            <message key="name.invalidPattern"
+             value="Method type name ''{0}'' must match pattern ''{1}''."/>
+        </module>
+        <module name="InterfaceTypeParameterName">
+            <property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
+            <message key="name.invalidPattern"
+             value="Interface type name ''{0}'' must match pattern ''{1}''."/>
+        </module>
+        <module name="NoFinalizer"/>
+        <module name="GenericWhitespace">
+            <message key="ws.followed"
+             value="GenericWhitespace ''{0}'' is followed by whitespace."/>
+             <message key="ws.preceded"
+             value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
+             <message key="ws.illegalFollow"
+             value="GenericWhitespace ''{0}'' should followed by whitespace."/>
+             <message key="ws.notPreceded"
+             value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
+        </module>
+        <module name="Indentation">
+            <property name="basicOffset" value="2"/>
+            <property name="braceAdjustment" value="0"/>
+            <property name="caseIndent" value="2"/>
+            <property name="throwsIndent" value="4"/>
+            <property name="lineWrappingIndentation" value="4"/>
+            <property name="arrayInitIndent" value="2"/>
+        </module>
+        <module name="AbbreviationAsWordInName">
+            <property name="ignoreFinal" value="false"/>
+            <property name="allowedAbbreviationLength" value="1"/>
+        </module>
+        <module name="OverloadMethodsDeclarationOrder"/>
+        <!-- TODO(ejona): Does not consider assignment during declaration as a usage.
+        <module name="VariableDeclarationUsageDistance"/-->
+        <module name="CustomImportOrder">
+            <property name="sortImportsInGroupAlphabetically" value="true"/>
+            <property name="separateLineBetweenGroups" value="true"/>
+            <property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
+        </module>
+        <module name="MethodParamPad"/>
+        <module name="OperatorWrap">
+            <property name="option" value="NL"/>
+            <property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR "/>
+        </module>
+        <module name="AnnotationLocation">
+            <property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
+        </module>
+        <module name="AnnotationLocation">
+            <property name="tokens" value="VARIABLE_DEF"/>
+            <property name="allowSamelineMultipleAnnotations" value="true"/>
+        </module>
+        <module name="NonEmptyAtclauseDescription"/>
+        <module name="JavadocTagContinuationIndentation"/>
+        <module name="SummaryJavadoc">
+            <property name="forbiddenSummaryFragments" value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
+        </module>
+        <module name="JavadocParagraph"/>
+        <module name="AtclauseOrder">
+            <property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
+            <property name="target" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
+        </module>
+        <module name="JavadocMethod">
+            <property name="scope" value="public"/>
+            <property name="allowMissingParamTags" value="true"/>
+            <property name="allowMissingThrowsTags" value="true"/>
+            <property name="allowMissingReturnTag" value="true"/>
+            <property name="minLineCount" value="2"/>
+            <!-- TOOD(ejona): Too restrictive for tests
+            <property name="allowedAnnotations" value="Override, Test"/-->
+            <property name="allowedAnnotations" value="Override, Test, Before, After, BeforeClass, AfterClass, Setup, TearDown"/>
+            <property name="allowThrowsTagsForSubclasses" value="true"/>
+        </module>
+        <module name="MethodName">
+            <property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
+            <message key="name.invalidPattern"
+             value="Method name ''{0}'' must match pattern ''{1}''."/>
+        </module>
+        <module name="SingleLineJavadoc">
+            <!-- TODO(ejona): Based on mis-reading of style; consider upstreaming
+            <property name="ignoreInlineTags" value="false"/-->
+        </module>
+        <module name="EmptyCatchBlock">
+            <property name="exceptionVariableName" value="expected"/>
+        </module>
+        <module name="CommentsIndentation"/>
+    </module>
+</module>
diff --git a/buildscripts/jenkins-pre.bat b/buildscripts/jenkins-pre.bat
new file mode 100644
index 0000000..836fdd6
--- /dev/null
+++ b/buildscripts/jenkins-pre.bat
@@ -0,0 +1,14 @@
+if NOT EXIST grpc-java-helper mkdir grpc-java-helper
+cd grpc-java-helper
+
+call "%VS120COMNTOOLS%\vsvars32.bat"
+call "%WORKSPACE%\buildscripts\make_dependencies.bat"
+
+cd "%WORKSPACE%"
+
+set ESCWORKSPACE=%WORKSPACE:\=\\%
+
+echo targetArch=x86_32> gradle.properties
+echo failOnWarnings=true>> gradle.properties
+echo vcProtobufLibs=%ESCWORKSPACE%\\grpc-java-helper\\protobuf-%PROTOBUF_VER%\\cmake\\build\\Release>> gradle.properties
+echo vcProtobufInclude=%ESCWORKSPACE%\\grpc-java-helper\\protobuf-%PROTOBUF_VER%\\cmake\\build\\include>> gradle.properties
diff --git a/buildscripts/kokoro/android-interop.cfg b/buildscripts/kokoro/android-interop.cfg
new file mode 100644
index 0000000..e399207
--- /dev/null
+++ b/buildscripts/kokoro/android-interop.cfg
@@ -0,0 +1,5 @@
+# Config file for internal CI
+
+# Location of the continuous shell script in repository.
+build_file: "grpc-java/buildscripts/kokoro/android-interop.sh"
+timeout_mins: 45
diff --git a/buildscripts/kokoro/android-interop.sh b/buildscripts/kokoro/android-interop.sh
new file mode 100755
index 0000000..a143926
--- /dev/null
+++ b/buildscripts/kokoro/android-interop.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+set -exu -o pipefail
+if [[ -f /VERSION ]]; then
+  cat /VERSION
+fi
+
+# Install gRPC and codegen for the Android interop app
+# (a composite gradle build can't find protoc-gen-grpc-java)
+
+cd github/grpc-java
+
+export GRADLE_OPTS=-Xmx512m
+export LDFLAGS=-L/tmp/protobuf/lib
+export CXXFLAGS=-I/tmp/protobuf/include
+export LD_LIBRARY_PATH=/tmp/protobuf/lib
+export OS_NAME=$(uname)
+
+# Proto deps
+buildscripts/make_dependencies.sh
+
+./gradlew install
+
+
+# Build and run interop instrumentation tests on Firebase Test Lab
+cd android-interop-testing
+../gradlew assembleDebug
+../gradlew assembleDebugAndroidTest
+gcloud firebase test android run \
+  --type instrumentation \
+  --app app/build/outputs/apk/debug/app-debug.apk \
+  --test app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk \
+  --environment-variables \
+      server_host=grpc-test.sandbox.googleapis.com,server_port=443,test_case=all \
+  --device model=Nexus6P,version=27,locale=en,orientation=portrait \
+  --device model=Nexus6P,version=26,locale=en,orientation=portrait \
+  --device model=Nexus6P,version=25,locale=en,orientation=portrait \
+  --device model=Nexus6P,version=24,locale=en,orientation=portrait \
+  --device model=Nexus6P,version=23,locale=en,orientation=portrait \
+  --device model=Nexus6,version=22,locale=en,orientation=portrait \
+  --device model=Nexus6,version=21,locale=en,orientation=portrait
diff --git a/buildscripts/kokoro/android.cfg b/buildscripts/kokoro/android.cfg
new file mode 100644
index 0000000..7730b90
--- /dev/null
+++ b/buildscripts/kokoro/android.cfg
@@ -0,0 +1,5 @@
+# Config file for internal CI
+
+# Location of the continuous shell script in repository.
+build_file: "grpc-java/buildscripts/kokoro/android.sh"
+timeout_mins: 45
diff --git a/buildscripts/kokoro/android.sh b/buildscripts/kokoro/android.sh
new file mode 100755
index 0000000..8c85071
--- /dev/null
+++ b/buildscripts/kokoro/android.sh
@@ -0,0 +1,122 @@
+#!/bin/bash
+
+set -exu -o pipefail
+cat /VERSION
+
+BASE_DIR="$(pwd)"
+
+# Install gRPC and codegen for the Android examples
+# (a composite gradle build can't find protoc-gen-grpc-java)
+
+cd "$BASE_DIR/github/grpc-java"
+
+export GRADLE_OPTS=-Xmx512m
+export LDFLAGS=-L/tmp/protobuf/lib
+export CXXFLAGS=-I/tmp/protobuf/include
+export LD_LIBRARY_PATH=/tmp/protobuf/lib
+export OS_NAME=$(uname)
+
+# Proto deps
+buildscripts/make_dependencies.sh
+
+./gradlew install
+
+# Build grpc-cronet
+
+pushd cronet
+../gradlew build
+popd
+
+# Build grpc-android
+
+pushd android
+../gradlew build
+popd
+
+# Build examples
+
+cd ./examples/android/clientcache
+./gradlew build
+cd ../routeguide
+./gradlew build
+cd ../helloworld
+./gradlew build
+
+cd "$BASE_DIR/github/grpc-java/examples/example-kotlin/android/helloworld/"
+./gradlew build
+
+# Skip APK size and dex count comparisons for non-PR builds
+
+if [[ -z "${KOKORO_GITHUB_PULL_REQUEST_COMMIT:-}" ]]; then
+    echo "Skipping APK size and dex count"
+    exit 0
+fi
+
+# Save a copy of set_github_status.py (it may differ from the base commit)
+
+SET_GITHUB_STATUS="$TMPDIR/set_github_status.py"
+cp "$BASE_DIR/github/grpc-java/buildscripts/set_github_status.py" "$SET_GITHUB_STATUS"
+
+
+# Collect APK size and dex count stats for the helloworld example
+
+HELLO_WORLD_OUTPUT_DIR="$BASE_DIR/github/grpc-java/examples/android/helloworld/app/build/outputs"
+
+read -r ignored new_dex_count < \
+  <("${ANDROID_HOME}/tools/bin/apkanalyzer" dex references \
+  "$HELLO_WORLD_OUTPUT_DIR/apk/release/app-release-unsigned.apk")
+
+set +x
+all_new_methods=`"${ANDROID_HOME}/tools/bin/apkanalyzer" dex packages \
+  --proguard-mapping "$HELLO_WORLD_OUTPUT_DIR/mapping/release/mapping.txt" \
+  "$HELLO_WORLD_OUTPUT_DIR/apk/release/app-release-unsigned.apk" | grep ^M | cut -f4 | sort`
+set -x
+
+new_apk_size="$(stat --printf=%s $HELLO_WORLD_OUTPUT_DIR/apk/release/app-release-unsigned.apk)"
+
+
+# Get the APK size and dex count stats using the pull request base commit
+
+cd $BASE_DIR/github/grpc-java
+git checkout HEAD^
+./gradlew install
+cd examples/android/helloworld/
+./gradlew build
+
+read -r ignored old_dex_count < \
+  <("${ANDROID_HOME}/tools/bin/apkanalyzer" dex references app/build/outputs/apk/release/app-release-unsigned.apk)
+
+set +x
+all_old_methods=`"${ANDROID_HOME}/tools/bin/apkanalyzer" dex packages --proguard-mapping app/build/outputs/mapping/release/mapping.txt app/build/outputs/apk/release/app-release-unsigned.apk | grep ^M | cut -f4 | sort`
+set -x
+
+old_apk_size="$(stat --printf=%s app/build/outputs/apk/release/app-release-unsigned.apk)"
+
+dex_count_delta="$((new_dex_count-old_dex_count))"
+
+apk_size_delta="$((new_apk_size-old_apk_size))"
+
+set +x
+dex_method_diff=`diff -u <(echo "$all_old_methods") <(echo "$all_new_methods") || true`
+set -x
+
+if [[ -n "$dex_method_diff" ]]
+then
+  echo "Method diff: ${dex_method_diff}"
+fi
+
+# Update the statuses with the deltas
+
+gsutil cp gs://grpc-testing-secrets/github_credentials/oauth_token.txt ~/
+
+"$SET_GITHUB_STATUS" \
+  --sha1 "$KOKORO_GITHUB_PULL_REQUEST_COMMIT" \
+  --state success \
+  --description "New DEX reference count: $(printf "%'d" "$new_dex_count") (delta: $(printf "%'d" "$dex_count_delta"))" \
+  --context android/dex_diff --oauth_file ~/oauth_token.txt
+
+"$SET_GITHUB_STATUS" \
+  --sha1 "$KOKORO_GITHUB_PULL_REQUEST_COMMIT" \
+  --state success \
+  --description "New APK size in bytes: $(printf "%'d" "$new_apk_size") (delta: $(printf "%'d" "$apk_size_delta"))" \
+  --context android/apk_diff --oauth_file ~/oauth_token.txt
diff --git a/buildscripts/kokoro/bazel.cfg b/buildscripts/kokoro/bazel.cfg
new file mode 100644
index 0000000..ae7cb26
--- /dev/null
+++ b/buildscripts/kokoro/bazel.cfg
@@ -0,0 +1,5 @@
+# Config file for internal CI
+
+# Location of the continuous shell script in repository.
+build_file: "grpc-java/buildscripts/kokoro/bazel.sh"
+timeout_mins: 45
diff --git a/buildscripts/kokoro/bazel.sh b/buildscripts/kokoro/bazel.sh
new file mode 100755
index 0000000..13bbaa4
--- /dev/null
+++ b/buildscripts/kokoro/bazel.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+set -exu -o pipefail
+cat /VERSION
+
+cd github/grpc-java
+bazel build ...
+
+cd examples
+bazel build ...
diff --git a/buildscripts/kokoro/gae-interop.cfg b/buildscripts/kokoro/gae-interop.cfg
new file mode 100644
index 0000000..78e14f9
--- /dev/null
+++ b/buildscripts/kokoro/gae-interop.cfg
@@ -0,0 +1,5 @@
+# Config file for internal CI
+
+# Location of the continuous shell script in repository.
+build_file: "grpc-java/buildscripts/kokoro/gae-interop.sh"
+timeout_mins: 45
diff --git a/buildscripts/kokoro/gae-interop.sh b/buildscripts/kokoro/gae-interop.sh
new file mode 100755
index 0000000..cbdff2d
--- /dev/null
+++ b/buildscripts/kokoro/gae-interop.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+
+set -exu -o pipefail
+if [[ -f /VERSION ]]; then
+  cat /VERSION
+fi
+
+KOKORO_GAE_SERVICE="java-gae-interop-test"
+
+# We deploy as different versions of a single service, this way any stale
+# lingering deploys can be easily cleaned up by purging all running versions
+# of this service.
+KOKORO_GAE_APP_VERSION=$(hostname)
+
+# A dummy version that can be the recipient of all traffic, so that the kokoro test version can be
+# set to 0 traffic. This is a requirement in order to delete it.
+DUMMY_DEFAULT_VERSION='dummy-default'
+
+function cleanup() {
+  echo "Performing cleanup now."
+  gcloud app services delete $KOKORO_GAE_SERVICE --version $KOKORO_GAE_APP_VERSION --quiet
+}
+trap cleanup SIGHUP SIGINT SIGTERM EXIT
+
+readonly GRPC_JAVA_DIR="$(cd "$(dirname "$0")"/../.. && pwd)"
+cd "$GRPC_JAVA_DIR"
+
+##
+## Deploy the dummy 'default' version of the service
+##
+GRADLE_FLAGS="--stacktrace -DgaeStopPreviousVersion=false -PskipCodegen=true"
+
+# Deploy the dummy 'default' version. We only require that it exists when cleanup() is called.
+# It ok if we race with another run and fail here, because the end result is idempotent.
+set +e
+if ! gcloud app versions describe "$DUMMY_DEFAULT_VERSION" --service="$KOKORO_GAE_SERVICE"; then
+  ./gradlew $GRADLE_FLAGS -DgaeDeployVersion="$DUMMY_DEFAULT_VERSION" -DgaePromote=true :grpc-gae-interop-testing-jdk8:appengineDeploy
+else
+  echo "default version already exists: $DUMMY_DEFAULT_VERSION"
+fi
+set -e
+
+# Deploy and test the real app (jdk8)
+./gradlew $GRADLE_FLAGS -DgaeDeployVersion="$KOKORO_GAE_APP_VERSION" :grpc-gae-interop-testing-jdk8:runInteropTestRemote
+
+set +e
+echo "Cleaning out stale deploys from previous runs, it is ok if this part fails"
+
+# Sometimes the trap based cleanup fails.
+# Delete all versions whose name is not 'dummy-default' and is older than 1 hour.
+# This expression is an ISO8601 relative date:
+# https://cloud.google.com/sdk/gcloud/reference/topic/datetimes
+gcloud app versions list --format="get(version.id)" --filter="service=$KOKORO_GAE_SERVICE AND NOT version : 'dummy-default' AND version.createTime<'-p1h'" | xargs -i gcloud app services delete "$KOKORO_GAE_SERVICE" --version {} --quiet
+exit 0
diff --git a/buildscripts/kokoro/linux_artifacts.cfg b/buildscripts/kokoro/linux_artifacts.cfg
new file mode 100644
index 0000000..46263cf
--- /dev/null
+++ b/buildscripts/kokoro/linux_artifacts.cfg
@@ -0,0 +1,11 @@
+# Config file for internal CI
+
+# Location of the continuous shell script in repository.
+build_file: "grpc-java/buildscripts/kokoro/linux_artifacts.sh"
+timeout_mins: 60
+
+action {
+  define_artifacts {
+    regex: ["**/mvn-artifacts/**"]
+  }
+}
diff --git a/buildscripts/kokoro/linux_artifacts.sh b/buildscripts/kokoro/linux_artifacts.sh
new file mode 100755
index 0000000..a442acc
--- /dev/null
+++ b/buildscripts/kokoro/linux_artifacts.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+set -veux -o pipefail
+
+if [[ -f /VERSION ]]; then
+  cat /VERSION
+fi
+
+readonly GRPC_JAVA_DIR="$(cd "$(dirname "$0")"/../.. && pwd)"
+
+"$GRPC_JAVA_DIR"/buildscripts/build_docker.sh
+"$GRPC_JAVA_DIR"/buildscripts/run_in_docker.sh /grpc-java/buildscripts/build_artifacts_in_docker.sh
+
+# grpc-android requires the Android SDK, so build outside of Docker and
+# use --include-build for its grpc-core dependency
+LOCAL_MVN_TEMP=$(mktemp -d)
+pushd "$GRPC_JAVA_DIR/android"
+../gradlew uploadArchives \
+  --include-build "$GRPC_JAVA_DIR" \
+  -Dorg.gradle.parallel=false \
+  -PskipCodegen=true \
+  -PrepositoryDir="$LOCAL_MVN_TEMP"
+popd
+
+readonly MVN_ARTIFACT_DIR="${MVN_ARTIFACT_DIR:-$GRPC_JAVA_DIR/mvn-artifacts}"
+mkdir -p "$MVN_ARTIFACT_DIR"
+cp -r "$LOCAL_MVN_TEMP"/* "$MVN_ARTIFACT_DIR"/
diff --git a/buildscripts/kokoro/macos.cfg b/buildscripts/kokoro/macos.cfg
new file mode 100644
index 0000000..b7d1dd7
--- /dev/null
+++ b/buildscripts/kokoro/macos.cfg
@@ -0,0 +1,12 @@
+# Config file for internal CI
+
+# Location of the continuous shell script in repository.
+build_file: "grpc-java/buildscripts/kokoro/unix.sh"
+timeout_mins: 45
+
+# We always build mvn artifacts.
+action {
+  define_artifacts {
+    regex: ["**/mvn-artifacts/**"]
+  }
+}
diff --git a/buildscripts/kokoro/release_artifacts.cfg b/buildscripts/kokoro/release_artifacts.cfg
new file mode 100644
index 0000000..518e8f1
--- /dev/null
+++ b/buildscripts/kokoro/release_artifacts.cfg
@@ -0,0 +1,5 @@
+# Config file for internal CI
+
+# Location of the continuous shell script in repository.
+build_file: "grpc-java/buildscripts/kokoro/release_artifacts.sh"
+timeout_mins: 60
diff --git a/buildscripts/kokoro/release_artifacts.sh b/buildscripts/kokoro/release_artifacts.sh
new file mode 100755
index 0000000..ebd72f6
--- /dev/null
+++ b/buildscripts/kokoro/release_artifacts.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+set -veux -o pipefail
+
+if [[ -f /VERSION ]]; then
+  cat /VERSION
+fi
+
+readonly GRPC_JAVA_DIR=$(cd $(dirname $0)/../.. && pwd)
+
+# A place holder at the moment
diff --git a/buildscripts/kokoro/unix.sh b/buildscripts/kokoro/unix.sh
new file mode 100755
index 0000000..6e2c913
--- /dev/null
+++ b/buildscripts/kokoro/unix.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+
+# This file is used for both Linux and MacOS builds.
+# For Linux, this script is called inside a docker container with
+# the correct environment for releases.
+# To run locally:
+#  ./buildscripts/kokoro/unix.sh
+# For 32 bit:
+#  ARCH=32 ./buildscripts/kokoro/unix.sh
+
+# This script assumes `set -e`. Removing it may lead to undefined behavior.
+set -exu -o pipefail
+
+# It would be nicer to use 'readlink -f' here but osx does not support it.
+readonly GRPC_JAVA_DIR="$(cd "$(dirname "$0")"/../.. && pwd)"
+
+if [[ -f /VERSION ]]; then
+  cat /VERSION
+fi
+
+# cd to the root dir of grpc-java
+cd $(dirname $0)/../..
+
+# TODO(zpencer): always make sure we are using Oracle jdk8
+
+# ARCH is 64 bit unless otherwise specified.
+ARCH="${ARCH:-64}"
+
+ARCH="$ARCH" buildscripts/make_dependencies.sh
+
+# Set properties via flags, do not pollute gradle.properties
+GRADLE_FLAGS="${GRADLE_FLAGS:-}"
+GRADLE_FLAGS+=" -PtargetArch=x86_$ARCH $GRADLE_FLAGS"
+GRADLE_FLAGS+=" -Pcheckstyle.ignoreFailures=false"
+GRADLE_FLAGS+=" -PfailOnWarnings=true"
+GRADLE_FLAGS+=" -PerrorProne=true"
+GRADLE_FLAGS+=" -Dorg.gradle.parallel=true"
+export GRADLE_OPTS="-Xmx512m"
+
+# Make protobuf discoverable by :grpc-compiler
+export LD_LIBRARY_PATH=/tmp/protobuf/lib
+export LDFLAGS=-L/tmp/protobuf/lib
+export CXXFLAGS="-I/tmp/protobuf/include"
+
+./gradlew clean $GRADLE_FLAGS
+
+if [[ -z "${SKIP_TESTS:-}" ]]; then
+  # Ensure all *.proto changes include *.java generated code
+  ./gradlew assemble generateTestProto install $GRADLE_FLAGS
+
+  if [[ -z "${SKIP_CLEAN_CHECK:-}" && ! -z $(git status --porcelain) ]]; then
+    git status
+    echo "Error Working directory is not clean. Forget to commit generated files?"
+    exit 1
+  fi
+  # Run tests
+  ./gradlew build $GRADLE_FLAGS
+  pushd examples
+  ./gradlew clean $GRADLE_FLAGS
+  ./gradlew build $GRADLE_FLAGS
+  # --batch-mode reduces log spam
+  mvn clean verify --batch-mode
+  popd
+  # TODO(zpencer): also build the GAE examples
+fi
+
+LOCAL_MVN_TEMP=$(mktemp -d)
+# Note that this disables parallel=true from GRADLE_FLAGS
+if [[ -z "${ALL_ARTIFACTS:-}" ]]; then
+  ./gradlew grpc-compiler:build grpc-compiler:uploadArchives $GRADLE_FLAGS \
+    -Dorg.gradle.parallel=false -PrepositoryDir=$LOCAL_MVN_TEMP
+else
+  ./gradlew uploadArchives $GRADLE_FLAGS \
+    -Dorg.gradle.parallel=false -PrepositoryDir=$LOCAL_MVN_TEMP
+fi
+
+readonly MVN_ARTIFACT_DIR="${MVN_ARTIFACT_DIR:-$GRPC_JAVA_DIR/mvn-artifacts}"
+
+mkdir -p "$MVN_ARTIFACT_DIR"
+cp -r "$LOCAL_MVN_TEMP"/* "$MVN_ARTIFACT_DIR"/
diff --git a/buildscripts/kokoro/upload_artifacts.cfg b/buildscripts/kokoro/upload_artifacts.cfg
new file mode 100644
index 0000000..38ee2f3
--- /dev/null
+++ b/buildscripts/kokoro/upload_artifacts.cfg
@@ -0,0 +1,5 @@
+# Config file for internal CI
+
+# Location of the continuous shell script in repository.
+build_file: "grpc-java/buildscripts/kokoro/upload_artifacts.sh"
+timeout_mins: 60
diff --git a/buildscripts/kokoro/upload_artifacts.sh b/buildscripts/kokoro/upload_artifacts.sh
new file mode 100644
index 0000000..fe49cdf
--- /dev/null
+++ b/buildscripts/kokoro/upload_artifacts.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+set -veux -o pipefail
+
+if [[ -f /VERSION ]]; then
+  cat /VERSION
+fi
+
+readonly GRPC_JAVA_DIR="$(cd "$(dirname "$0")"/../.. && pwd)"
+
+echo "Verifying that all artifacts are here..."
+find "$KOKORO_GFILE_DIR"
+
+# The output from all the jobs are coalesced into a single dir
+LOCAL_MVN_ARTIFACTS="$KOKORO_GFILE_DIR"/github/grpc-java/mvn-artifacts/
+
+# verify that files from all 3 grouped jobs are present.
+# platform independent artifacts, from linux job:
+[[ "$(find "$LOCAL_MVN_ARTIFACTS" -type f -iname 'grpc-core-*.jar' | wc -l)" != '0' ]]
+
+# android artifact from linux job:
+[[ "$(find "$LOCAL_MVN_ARTIFACTS" -type f -iname 'grpc-android-*.aar' | wc -l)" != '0' ]]
+
+# from linux job:
+[[ "$(find "$LOCAL_MVN_ARTIFACTS" -type f -iname 'protoc-gen-grpc-java-*-linux-x86_64.exe' | wc -l)" != '0' ]]
+[[ "$(find "$LOCAL_MVN_ARTIFACTS" -type f -iname 'protoc-gen-grpc-java-*-linux-x86_32.exe' | wc -l)" != '0' ]]
+
+# from macos job:
+[[ "$(find "$LOCAL_MVN_ARTIFACTS" -type f -iname 'protoc-gen-grpc-java-*-osx-x86_64.exe' | wc -l)" != '0' ]]
+
+# from windows job:
+[[ "$(find "$LOCAL_MVN_ARTIFACTS" -type f -iname 'protoc-gen-grpc-java-*-windows-x86_64.exe' | wc -l)" != '0' ]]
+[[ "$(find "$LOCAL_MVN_ARTIFACTS" -type f -iname 'protoc-gen-grpc-java-*-windows-x86_32.exe' | wc -l)" != '0' ]]
+
+
+mkdir -p ~/.config/
+gsutil cp gs://grpc-testing-secrets/sonatype_credentials/sonatype-upload ~/.config/sonatype-upload
+
+mkdir -p ~/java_signing/
+gsutil cp -r gs://grpc-testing-secrets/java_signing/ ~/
+gpg --batch  --import ~/java_signing/grpc-java-team-sonatype.asc
+
+# gpg commands changed between v1 and v2 are different.
+gpg --version
+
+# This is the version found on kokoro.
+if gpg --version | grep 'gpg (GnuPG) 1.'; then
+  # This command was tested on 1.4.16
+  find "$LOCAL_MVN_ARTIFACTS" -type f \
+    -not -name "maven-metadata.xml*" -not -name "*.sha1" -not -name "*.md5" -exec \
+    bash -c 'cat ~/java_signing/passphrase | gpg --batch --passphrase-fd 0 --detach-sign -a {}' \;
+fi
+
+# This is the version found on corp workstations. Maybe kokoro will be updated to gpg2 some day.
+if gpg --version | grep 'gpg (GnuPG) 2.'; then
+  # This command was tested on 2.2.2
+  find "$LOCAL_MVN_ARTIFACTS" -type f \
+    -not -name "maven-metadata.xml*" -not -name "*.sha1" -not -name "*.md5" -exec \
+    gpg --batch --passphrase-file ~/java_signing/passphrase --pinentry-mode loopback \
+    --detach-sign -a {} \;
+fi
+
+STAGING_REPO=a93898609ef848
+"$GRPC_JAVA_DIR"/buildscripts/sonatype-upload.sh "$STAGING_REPO" "$LOCAL_MVN_ARTIFACTS"
diff --git a/buildscripts/kokoro/windows.bat b/buildscripts/kokoro/windows.bat
new file mode 100644
index 0000000..709b61c
--- /dev/null
+++ b/buildscripts/kokoro/windows.bat
@@ -0,0 +1,24 @@
+@rem ##########################################################################
+@rem
+@rem Script to set up Kokoro worker and run Windows tests
+@rem
+@rem ##########################################################################
+
+type c:\VERSION
+
+@rem Enter repo root
+cd /d %~dp0\..\..
+
+set WORKSPACE=T:\src\github\grpc-java
+set ESCWORKSPACE=%WORKSPACE:\=\\%
+
+
+@rem Clear JAVA_HOME to prevent a different Java version from being used
+set JAVA_HOME=
+set PATH=C:\Program Files\java\jdk1.8.0_152\bin;%PATH%
+
+cmd.exe /C "%WORKSPACE%\buildscripts\kokoro\windows32.bat" || exit /b 1
+cmd.exe /C "%WORKSPACE%\buildscripts\kokoro\windows64.bat" || exit /b 1
+
+mkdir mvn-artifacts
+move artifacts\io mvn-artifacts
diff --git a/buildscripts/kokoro/windows.cfg b/buildscripts/kokoro/windows.cfg
new file mode 100644
index 0000000..0002fd1
--- /dev/null
+++ b/buildscripts/kokoro/windows.cfg
@@ -0,0 +1,12 @@
+# Config file for internal CI
+
+# Location of the continuous shell script in repository.
+build_file: "grpc-java/buildscripts/kokoro/windows.bat"
+timeout_mins: 45
+
+# We always build mvn artifacts.
+action {
+  define_artifacts {
+    regex: ["**/build/test-results/**/*.xml", "**/mvn-artifacts/**"]
+  }
+}
diff --git a/buildscripts/kokoro/windows32.bat b/buildscripts/kokoro/windows32.bat
new file mode 100644
index 0000000..dbe677d
--- /dev/null
+++ b/buildscripts/kokoro/windows32.bat
@@ -0,0 +1,51 @@
+@rem ##########################################################################
+@rem
+@rem Runs tests and then builds artifacts to %WORKSPACE%\artifacts\
+@rem
+@rem ##########################################################################
+
+type c:\VERSION
+
+@rem Enter repo root
+cd /d %~dp0\..\..
+
+set WORKSPACE=T:\src\github\grpc-java
+set ESCWORKSPACE=%WORKSPACE:\=\\%
+
+
+@rem Clear JAVA_HOME to prevent a different Java version from being used
+set JAVA_HOME=
+set PATH=C:\Program Files\java\jdk1.8.0_152\bin;%PATH%
+
+mkdir grpc-java-helper32
+cd grpc-java-helper32
+call "%VS120COMNTOOLS%\vsvars32.bat" || exit /b 1
+call "%WORKSPACE%\buildscripts\make_dependencies.bat" || exit /b 1
+
+cd "%WORKSPACE%"
+
+SET TARGET_ARCH=x86_32
+SET FAIL_ON_WARNINGS=true
+SET VC_PROTOBUF_LIBS=%ESCWORKSPACE%\\grpc-java-helper32\\protobuf-%PROTOBUF_VER%\\cmake\\build\\Release
+SET VC_PROTOBUF_INCLUDE=%ESCWORKSPACE%\\grpc-java-helper32\\protobuf-%PROTOBUF_VER%\\cmake\\build\\include
+SET GRADLE_FLAGS=-PtargetArch=%TARGET_ARCH% -PfailOnWarnings=%FAIL_ON_WARNINGS% -PvcProtobufLibs=%VC_PROTOBUF_LIBS% -PvcProtobufInclude=%VC_PROTOBUF_INCLUDE%
+
+cmd.exe /C "%WORKSPACE%\gradlew.bat %GRADLE_FLAGS% build"
+set GRADLEEXIT=%ERRORLEVEL%
+
+@rem Rename test results .xml files to format parsable by Kokoro
+@echo off
+for /r %%F in (TEST-*.xml) do (
+  mkdir "%%~dpnF"
+  move "%%F" "%%~dpnF\sponge_log.xml" >NUL
+)
+@echo on
+
+IF NOT %GRADLEEXIT% == 0 (
+  exit /b %GRADLEEXIT%
+)
+
+@rem make sure no daemons have any files open
+cmd.exe /C "%WORKSPACE%\gradlew.bat --stop"
+
+cmd.exe /C "%WORKSPACE%\gradlew.bat  %GRADLE_FLAGS% -Dorg.gradle.parallel=false -PrepositoryDir=%WORKSPACE%\artifacts clean grpc-compiler:build grpc-compiler:uploadArchives" || exit /b 1
diff --git a/buildscripts/kokoro/windows64.bat b/buildscripts/kokoro/windows64.bat
new file mode 100644
index 0000000..6346b44
--- /dev/null
+++ b/buildscripts/kokoro/windows64.bat
@@ -0,0 +1,35 @@
+@rem ##########################################################################
+@rem
+@rem Builds artifacts for x86_64 into %WORKSPACE%\artifacts\
+@rem
+@rem ##########################################################################
+
+type c:\VERSION
+
+@rem Enter repo root
+cd /d %~dp0\..\..
+
+set WORKSPACE=T:\src\github\grpc-java
+set ESCWORKSPACE=%WORKSPACE:\=\\%
+
+@rem Clear JAVA_HOME to prevent a different Java version from being used
+set JAVA_HOME=
+set PATH=C:\Program Files\java\jdk1.8.0_152\bin;%PATH%
+
+mkdir grpc-java-helper64
+cd grpc-java-helper64
+call "%VS120COMNTOOLS%\..\..\VC\bin\amd64\vcvars64.bat" || exit /b 1
+call "%WORKSPACE%\buildscripts\make_dependencies.bat" || exit /b 1
+
+cd "%WORKSPACE%"
+
+SET TARGET_ARCH=x86_64
+SET FAIL_ON_WARNINGS=true
+SET VC_PROTOBUF_LIBS=%ESCWORKSPACE%\\grpc-java-helper64\\protobuf-%PROTOBUF_VER%\\cmake\\build\\Release
+SET VC_PROTOBUF_INCLUDE=%ESCWORKSPACE%\\grpc-java-helper64\\protobuf-%PROTOBUF_VER%\\cmake\\build\\include
+SET GRADLE_FLAGS=-PtargetArch=%TARGET_ARCH% -PfailOnWarnings=%FAIL_ON_WARNINGS% -PvcProtobufLibs=%VC_PROTOBUF_LIBS% -PvcProtobufInclude=%VC_PROTOBUF_INCLUDE%
+
+@rem make sure no daemons have any files open
+cmd.exe /C "%WORKSPACE%\gradlew.bat --stop"
+
+cmd.exe /C "%WORKSPACE%\gradlew.bat  %GRADLE_FLAGS% -Dorg.gradle.parallel=false -PrepositoryDir=%WORKSPACE%\artifacts clean grpc-compiler:build grpc-compiler:uploadArchives" || exit /b 1
diff --git a/buildscripts/make_dependencies.bat b/buildscripts/make_dependencies.bat
new file mode 100644
index 0000000..691520e
--- /dev/null
+++ b/buildscripts/make_dependencies.bat
@@ -0,0 +1,50 @@
+set PROTOBUF_VER=3.5.1
+set CMAKE_NAME=cmake-3.3.2-win32-x86
+
+if not exist "protobuf-%PROTOBUF_VER%\cmake\build\Release\" (
+  call :installProto || exit /b 1
+)
+
+echo Compile gRPC-Java with something like:
+echo -PtargetArch=x86_32 -PvcProtobufLibs=%cd%\protobuf-%PROTOBUF_VER%\cmake\build\Release -PvcProtobufInclude=%cd%\protobuf-%PROTOBUF_VER%\cmake\build\include
+goto :eof
+
+
+:installProto
+
+where /q cmake
+if not ERRORLEVEL 1 goto :hasCmake
+if not exist "%CMAKE_NAME%" (
+  call :installCmake || exit /b 1
+)
+set PATH=%PATH%;%cd%\%CMAKE_NAME%\bin
+:hasCmake
+@rem GitHub requires TLSv1.2, and for whatever reason our powershell doesn't have it enabled
+powershell -command "$ErrorActionPreference = 'stop'; & { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 ; iwr https://github.com/google/protobuf/archive/v%PROTOBUF_VER%.zip -OutFile protobuf.zip }" || exit /b 1
+powershell -command "$ErrorActionPreference = 'stop'; & { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('protobuf.zip', '.') }" || exit /b 1
+del protobuf.zip
+pushd protobuf-%PROTOBUF_VER%\cmake
+mkdir build
+cd build
+
+@rem cmake does not detect x86_64 from the vcvars64.bat variables.
+@rem If vcvars64.bat has set PLATFORM to X64, then inform cmake to use the Win64 version of VS
+if "%PLATFORM%" == "X64" (
+  @rem Note the space
+  SET CMAKE_VSARCH= Win64
+) else (
+  SET CMAKE_VSARCH=
+)
+cmake -Dprotobuf_BUILD_TESTS=OFF -G "Visual Studio %VisualStudioVersion:~0,2%%CMAKE_VSARCH%" .. || exit /b 1
+msbuild /maxcpucount /p:Configuration=Release libprotoc.vcxproj || exit /b 1
+call extract_includes.bat || exit /b 1
+popd
+goto :eof
+
+
+:installCmake
+
+powershell -command "$ErrorActionPreference = 'stop'; & { iwr https://cmake.org/files/v3.3/%CMAKE_NAME%.zip -OutFile cmake.zip }" || exit /b 1
+powershell -command "$ErrorActionPreference = 'stop'; & { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('cmake.zip', '.') }" || exit /b 1
+del cmake.zip
+goto :eof
diff --git a/buildscripts/make_dependencies.sh b/buildscripts/make_dependencies.sh
new file mode 100755
index 0000000..e4aa541
--- /dev/null
+++ b/buildscripts/make_dependencies.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+#
+# Build protoc
+set -evux -o pipefail
+
+PROTOBUF_VERSION=3.5.1
+
+# ARCH is 64 bit unless otherwise specified.
+ARCH="${ARCH:-64}"
+DOWNLOAD_DIR=/tmp/source
+INSTALL_DIR="/tmp/protobuf-cache/$PROTOBUF_VERSION/$(uname -s)-$(uname -p)-x86_$ARCH"
+mkdir -p $DOWNLOAD_DIR
+
+# Start with a sane default
+NUM_CPU=4
+if [[ $(uname) == 'Linux' ]]; then
+    NUM_CPU=$(nproc)
+fi
+if [[ $(uname) == 'Darwin' ]]; then
+    NUM_CPU=$(sysctl -n hw.ncpu)
+fi
+
+# Make protoc
+# Can't check for presence of directory as cache auto-creates it.
+if [ -f ${INSTALL_DIR}/bin/protoc ]; then
+  echo "Not building protobuf. Already built"
+# TODO(ejona): swap to `brew install --devel protobuf` once it is up-to-date
+else
+  if [[ ! -d "$DOWNLOAD_DIR"/protobuf-"${PROTOBUF_VERSION}" ]]; then
+    wget -O - https://github.com/google/protobuf/releases/download/v${PROTOBUF_VERSION}/protobuf-all-${PROTOBUF_VERSION}.tar.gz | tar xz -C $DOWNLOAD_DIR
+  fi
+  pushd $DOWNLOAD_DIR/protobuf-${PROTOBUF_VERSION}
+  # install here so we don't need sudo
+  ./configure CFLAGS=-m"$ARCH" CXXFLAGS=-m"$ARCH" --disable-shared \
+    --prefix="$INSTALL_DIR"
+  # the same source dir is used for 32 and 64 bit builds, so we need to clean stale data first
+  make clean
+  make -j$NUM_CPU
+  make install
+  popd
+fi
+
+# If /tmp/protobuf exists then we just assume it's a symlink created by us.
+# It may be that it points to the wrong arch, so we idempotently set it now.
+if [[ -L /tmp/protobuf ]]; then
+  rm /tmp/protobuf
+fi
+ln -s "$INSTALL_DIR" /tmp/protobuf
+
+cat <<EOF
+To compile with the build dependencies:
+
+export LDFLAGS=-L/tmp/protobuf/lib
+export CXXFLAGS=-I/tmp/protobuf/include
+export LD_LIBRARY_PATH=/tmp/protobuf/lib
+EOF
diff --git a/buildscripts/run_in_docker.sh b/buildscripts/run_in_docker.sh
new file mode 100755
index 0000000..87dc915
--- /dev/null
+++ b/buildscripts/run_in_docker.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+set -eu -o pipefail
+
+quote() {
+  local arg
+  for arg in "$@"; do
+    printf "'"
+    printf "%s" "$arg" | sed -e "s/'/'\\\\''/g"
+    printf "' "
+  done
+}
+
+readonly grpc_java_dir="$(dirname "$(readlink -f "$0")")/.."
+if [[ -t 0 ]]; then
+  DOCKER_ARGS="-it"
+else
+  # The input device on kokoro is not a TTY, so -it does not work.
+  DOCKER_ARGS=
+fi
+# Use a trap function to fix file permissions upon exit, without affecting
+# the original exit code. $DOCKER_ARGS can not be quoted, otherwise it becomes a '' which confuses
+# docker.
+exec docker run $DOCKER_ARGS --rm=true -v "${grpc_java_dir}":/grpc-java -w /grpc-java \
+  protoc-artifacts \
+  bash -c "function fixFiles() { chown -R $(id -u):$(id -g) /grpc-java; }; trap fixFiles EXIT; $(quote "$@")"
diff --git a/buildscripts/set_github_status.py b/buildscripts/set_github_status.py
new file mode 100755
index 0000000..09b2ad2
--- /dev/null
+++ b/buildscripts/set_github_status.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python2.7
+#
+# Copyright 2018 The gRPC Authors
+#
+# Licensed 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.
+
+import argparse
+import json
+import urllib2
+
+
+def run():
+  argp = argparse.ArgumentParser(description='Set status on pull request')
+
+  argp.add_argument(
+      '--sha1', type=str, help='SHA1 of the commit', required=True)
+  argp.add_argument(
+      '--state',
+      type=str,
+      choices=('error', 'failure', 'pending', 'success'),
+      help='State to set',
+      required=True)
+  argp.add_argument(
+      '--description', type=str, help='Status description', required=True)
+  argp.add_argument('--context', type=str, help='Status context', required=True)
+  argp.add_argument(
+      '--oauth_file', type=str, help='File with OAuth token', required=True)
+
+  args = argp.parse_args()
+  sha1 = args.sha1
+  state = args.state
+  description = args.description
+  context = args.context
+  oauth_file = args.oauth_file
+
+  with open(oauth_file, 'r') as oauth_file_reader:
+    oauth_token = oauth_file_reader.read().replace('\n', '')
+
+  req = urllib2.Request(
+      url='https://api.github.com/repos/grpc/grpc-java/statuses/%s' % sha1,
+      data=json.dumps({
+          'state': state,
+          'description': description,
+          'context': context,
+      }),
+      headers={
+          'Authorization': 'token %s' % oauth_token,
+          'Content-Type': 'application/json',
+      })
+  print urllib2.urlopen(req).read()
+
+
+if __name__ == '__main__':
+  run()
diff --git a/buildscripts/sign-local-repo.sh b/buildscripts/sign-local-repo.sh
new file mode 100755
index 0000000..e2377c4
--- /dev/null
+++ b/buildscripts/sign-local-repo.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+# Copyright 2017 The gRPC Authors
+#
+# Licensed 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.
+
+set -e
+
+if [ $# -ne 1 ]; then
+  cat <<EOF
+Usage: $0 DIR
+  DIR Local repository with files needing to be signed
+EOF
+  exit 1
+fi
+
+find "$1" -type f | egrep -v '\.(md5|sha1)$' | grep -v maven-metadata.xml | xargs gpg -ab
diff --git a/buildscripts/sonatype-upload.sh b/buildscripts/sonatype-upload.sh
new file mode 100755
index 0000000..236212d
--- /dev/null
+++ b/buildscripts/sonatype-upload.sh
@@ -0,0 +1,110 @@
+#!/bin/bash
+# Copyright 2017 The gRPC Authors
+#
+# Licensed 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.
+
+set -eu -o pipefail
+
+if [ $# -ne 2 ]; then
+  cat <<EOF
+Usage: $0 PROFILEID DIR
+  PROFILEID The Sonatype profile to use for staging repository
+            Obtain profile ID from https://oss.sonatype.org:
+             * Build Promotion > Staging Profiles
+             * Select profile based on name (e.g., 'io.grpc')
+             * Copy hex identifier from URL after "#stagingProfiles;"
+  DIR       Directory to upload to Sonatype as a new staging repository
+
+~/.config/sonatype-upload: Configuration file for Sonatype username and password
+  USERNAME=yourusername
+  PASSWORD=yourpass
+
+Sonatype provides a "user token" that is a randomly generated username/password.
+It does allow access to your account, however. You can create one via:
+ * Log in to https://oss.sonatype.org
+ * Click your username at the top right and click to Profile
+ * Change the drop-down from "Summary" to "User Token"
+ * Click "Access User Token"
+EOF
+  exit 1
+fi
+
+PROFILE_ID="$1"
+DIR="$2"
+if [ -z "$DIR" ]; then
+  echo "Must specify non-empty directory name"
+  exit 1
+fi
+
+CONF="$HOME/.config/sonatype-upload"
+[ -f "$CONF" ] && . "$CONF"
+
+USERNAME="${USERNAME:-}"
+PASSWORD="${PASSWORD:-}"
+
+if [ -z "$USERNAME" -o -z "$PASSWORD" ]; then
+  # TODO(ejona86): if people would use it, could prompt for values to avoid
+  # having passwords in plain-text.
+  echo "You must create '$CONF' with keys USERNAME and PASSWORD" >&2
+  exit 1
+fi
+
+STAGING_URL="https://oss.sonatype.org/service/local/staging"
+
+# We go through the effort of using deloyByRepositoryId/ because it is
+# _substantially_ faster to upload files than deploy/maven2/. When using
+# deploy/maven2/ a repository is implicitly created, but the response doesn't
+# provide its name.
+
+USERPASS="$USERNAME:$PASSWORD"
+
+# https://oss.sonatype.org/nexus-staging-plugin/default/docs/index.html
+#
+# Example returned data:
+#   <promoteResponse>
+#     <data>
+#       <stagedRepositoryId>iogrpc-1082</stagedRepositoryId>
+#       <description>Release upload</description>
+#     </data>
+#   </promoteResponse>
+echo "Creating staging repo"
+REPOID="$(
+  XML="
+  <promoteRequest>
+    <data>
+      <description>Release upload</description>
+    </data>
+  </promoteRequest>"
+  curl -s -X POST -d "$XML" -u "$USERPASS" -H "Content-Type: application/xml" \
+    "$STAGING_URL/profiles/$PROFILE_ID/start" |
+  grep stagedRepositoryId |
+  sed 's/.*<stagedRepositoryId>\(.*\)<\/stagedRepositoryId>.*/\1/'
+  )"
+echo "Repository id: $REPOID"
+
+for X in $(cd "$DIR" && find -type f | cut -b 3-); do
+  echo "Uploading $X"
+  curl -T "$DIR/$X" -u "$USERPASS" -H "Content-Type: application/octet-stream" \
+    "$STAGING_URL/deployByRepositoryId/$REPOID/$X"
+done
+
+echo "Closing staging repo"
+XML="
+<promoteRequest>
+  <data>
+    <stagedRepositoryId>$REPOID</stagedRepositoryId>
+    <description>Auto-close via upload script</description>
+  </data>
+</promoteRequest>"
+curl -X POST -d "$XML" -u "$USERPASS" -H "Content-Type: application/xml" \
+  "$STAGING_URL/profiles/$PROFILE_ID/finish"
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..665b25e
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,14 @@
+coverage:
+  status:
+    patch:
+      default:
+        target: '80'
+    project:
+      default:
+        target: auto
+        threshold: 1
+    changes:
+      default:
+        branches:
+          - nonExistantBranchToDisableTheFeature
+comment: off
diff --git a/compiler/BUILD.bazel b/compiler/BUILD.bazel
new file mode 100644
index 0000000..1a2f2f3
--- /dev/null
+++ b/compiler/BUILD.bazel
@@ -0,0 +1,12 @@
+cc_binary(
+    name = "grpc_java_plugin",
+    srcs = [
+        "src/java_plugin/cpp/java_generator.cpp",
+        "src/java_plugin/cpp/java_generator.h",
+        "src/java_plugin/cpp/java_plugin.cpp",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        "@com_google_protobuf//:protoc_lib",
+    ],
+)
diff --git a/compiler/Dockerfile b/compiler/Dockerfile
new file mode 100644
index 0000000..d76cb78
--- /dev/null
+++ b/compiler/Dockerfile
@@ -0,0 +1,20 @@
+FROM protoc-artifacts:latest
+
+RUN scl enable devtoolset-1.1 'bash -c "cd /protobuf && \
+    git fetch && \
+    git checkout v3.5.1 && \
+    ./autogen.sh && \
+    CXXFLAGS=-m32 ./configure --disable-shared --prefix=/protobuf-32 && \
+    make clean && make -j$(nproc) && make -j$(nproc) install"'
+
+RUN scl enable devtoolset-1.1 'bash -c "cd /protobuf && \
+    CXXFLAGS=-m64 ./configure --disable-shared --prefix=/protobuf-64 && \
+    make clean && make -j$(nproc) && make -j$(nproc) install"'
+
+ENV CXXFLAGS=-I/protobuf-32/include \
+    LDFLAGS="-L/protobuf-32/lib -L/protobuf-64/lib"
+
+RUN git clone --depth 1 https://github.com/grpc/grpc-java.git
+
+# Start in devtoolset environment that uses GCC 4.7
+CMD ["scl", "enable", "devtoolset-1.1", "bash"]
diff --git a/compiler/README.md b/compiler/README.md
new file mode 100644
index 0000000..0d10207
--- /dev/null
+++ b/compiler/README.md
@@ -0,0 +1,61 @@
+gRPC Java Codegen Plugin for Protobuf Compiler
+==============================================
+
+This generates the Java interfaces out of the service definition from a
+`.proto` file. It works with the Protobuf Compiler (``protoc``).
+
+Normally you don't need to compile the codegen by yourself, since pre-compiled
+binaries for common platforms are available on Maven Central. However, if the
+pre-compiled binaries are not compatible with your system, you may want to
+build your own codegen.
+
+## System requirement
+
+* Linux, Mac OS X with Clang, or Windows with MSYS2
+* Java 7 or up
+* [Protobuf](https://github.com/google/protobuf) 3.0.0-beta-3 or up
+
+## Compiling and testing the codegen
+Change to the `compiler` directory:
+```
+$ cd $GRPC_JAVA_ROOT/compiler
+```
+
+To compile the plugin:
+```
+$ ../gradlew java_pluginExecutable
+```
+
+To test the plugin with the compiler:
+```
+$ ../gradlew test
+```
+You will see a `PASS` if the test succeeds.
+
+To compile a proto file and generate Java interfaces out of the service definitions:
+```
+$ protoc --plugin=protoc-gen-grpc-java=build/exe/java_plugin/protoc-gen-grpc-java \
+  --grpc-java_out="$OUTPUT_FILE" --proto_path="$DIR_OF_PROTO_FILE" "$PROTO_FILE"
+```
+To generate Java interfaces with protobuf lite:
+```
+$ protoc --plugin=protoc-gen-grpc-java=build/exe/java_plugin/protoc-gen-grpc-java \
+  --grpc-java_out=lite:"$OUTPUT_FILE" --proto_path="$DIR_OF_PROTO_FILE" "$PROTO_FILE"
+```
+To generate Java interfaces with protobuf nano:
+```
+$ protoc --plugin=protoc-gen-grpc-java=build/exe/java_plugin/protoc-gen-grpc-java \
+  --grpc-java_out=nano:"$OUTPUT_FILE" --proto_path="$DIR_OF_PROTO_FILE" "$PROTO_FILE"
+```
+
+## Installing the codegen to Maven local repository
+This will compile a codegen and put it under your ``~/.m2/repository``. This
+will make it available to any build tool that pulls codegens from Maven
+repostiories.
+```
+$ ../gradlew install
+```
+
+## Creating a release of GRPC Java
+Please follow the instructions in ``RELEASING.md`` under the root directory for
+details on how to create a new release.
diff --git a/compiler/build.gradle b/compiler/build.gradle
new file mode 100644
index 0000000..253392f
--- /dev/null
+++ b/compiler/build.gradle
@@ -0,0 +1,330 @@
+apply plugin: "cpp"
+apply plugin: "com.google.protobuf"
+
+description = 'The protoc plugin for gRPC Java'
+
+buildscript {
+    repositories {
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+        mavenLocal()
+    }
+    dependencies { classpath libraries.protobuf_plugin }
+}
+
+def artifactStagingPath = "$buildDir/artifacts" as File
+// Adds space-delimited arguments from the environment variable env to the
+// argList.
+def addEnvArgs = { env, argList ->
+    def value = System.getenv(env)
+    if (value != null) {
+        value.split(' +').each() { it -> argList.add(it) }
+    }
+}
+
+// Adds corresponding "-l" option to the argList if libName is not found in
+// LDFLAGS. This is only used for Mac because when building for uploadArchives
+// artifacts, we add the ".a" files directly to LDFLAGS and without "-l" in
+// order to get statically linked, otherwise we add the libraries through "-l"
+// so that they can be searched for in default search paths.
+def addLibraryIfNotLinked = { libName, argList ->
+    def ldflags = System.env.LDFLAGS
+    if (ldflags == null || !ldflags.contains('lib' + libName + '.a')) {
+        argList.add('-l' + libName)
+    }
+}
+
+def String arch = rootProject.hasProperty('targetArch') ? rootProject.targetArch : osdetector.arch
+def boolean vcDisable = rootProject.hasProperty('vcDisable') ? rootProject.vcDisable : false
+def boolean usingVisualCpp // Whether VisualCpp is actually available and selected
+
+model {
+    toolChains {
+        // If you have both VC and Gcc installed, VC will be selected, unless you
+        // set 'vcDisable=true'
+        if (!vcDisable) {
+            visualCpp(VisualCpp) {
+                // Prefer vcvars-provided environment over registry-discovered environment
+                def String vsDir = System.getenv("VSINSTALLDIR")
+                def String winDir = System.getenv("WindowsSdkDir")
+                if (vsDir != null && winDir != null) {
+                    installDir = vsDir
+                    windowsSdkDir = winDir
+                }
+            }
+        }
+        gcc(Gcc) {
+            target("ppcle_64")
+            target("aarch_64")
+        }
+        clang(Clang) {
+        }
+    }
+
+    platforms {
+        x86_32 { architecture "x86" }
+        x86_64 { architecture "x86_64" }
+        ppcle_64 { architecture "ppcle_64" }
+        aarch_64 { architecture "aarch_64" }
+    }
+
+    components {
+        java_plugin(NativeExecutableSpec) {
+            if (arch in [
+                'x86_32',
+                'x86_64',
+                'ppcle_64',
+                'aarch_64'
+            ]) {
+                // If arch is not within the defined platforms, we do not specify the
+                // targetPlatform so that Gradle will choose what is appropriate.
+                targetPlatform arch
+            }
+            baseName "$protocPluginBaseName"
+        }
+    }
+
+    binaries {
+        all {
+            if (toolChain in Gcc || toolChain in Clang) {
+                cppCompiler.define("GRPC_VERSION", version)
+                cppCompiler.args "--std=c++0x"
+                addEnvArgs("CXXFLAGS", cppCompiler.args)
+                addEnvArgs("CPPFLAGS", cppCompiler.args)
+                if (osdetector.os == "osx") {
+                    cppCompiler.args "-mmacosx-version-min=10.7", "-stdlib=libc++"
+                    addLibraryIfNotLinked('protoc', linker.args)
+                    addLibraryIfNotLinked('protobuf', linker.args)
+                } else if (osdetector.os == "windows") {
+                    linker.args "-static", "-lprotoc", "-lprotobuf", "-static-libgcc", "-static-libstdc++",
+                            "-s"
+                } else {
+                    // Link protoc, protobuf, libgcc and libstdc++ statically.
+                    // Link other (system) libraries dynamically.
+                    // Clang under OSX doesn't support these options.
+                    linker.args "-Wl,-Bstatic", "-lprotoc", "-lprotobuf", "-static-libgcc",
+                            "-static-libstdc++",
+                            "-Wl,-Bdynamic", "-lpthread", "-s"
+                }
+                addEnvArgs("LDFLAGS", linker.args)
+            } else if (toolChain in VisualCpp) {
+                usingVisualCpp = true
+                cppCompiler.define("GRPC_VERSION", version)
+                cppCompiler.args "/EHsc", "/MT"
+                if (rootProject.hasProperty('vcProtobufInclude')) {
+                    cppCompiler.args "/I${rootProject.vcProtobufInclude}"
+                }
+                linker.args "libprotobuf.lib", "libprotoc.lib"
+                if (rootProject.hasProperty('vcProtobufLibs')) {
+                    linker.args "/LIBPATH:${rootProject.vcProtobufLibs}"
+                }
+            }
+        }
+    }
+}
+
+configurations {
+    testLiteCompile
+    testNanoCompile
+}
+
+dependencies {
+    testCompile project(':grpc-protobuf'),
+            project(':grpc-stub')
+    testLiteCompile project(':grpc-protobuf-lite'),
+            project(':grpc-stub')
+    testNanoCompile project(':grpc-protobuf-nano'),
+            project(':grpc-stub')
+}
+
+sourceSets {
+    testLite {
+        proto { setSrcDirs(['src/test/proto']) }
+    }
+    testNano {
+        proto { setSrcDirs(['src/test/proto']) }
+    }
+}
+
+compileTestJava {
+    options.compilerArgs += [
+        "-Xlint:-cast",
+        "-XepExcludedPaths:.*/build/generated/source/proto/.*",
+    ]
+}
+
+compileTestLiteJava {
+    options.compilerArgs = compileTestJava.options.compilerArgs
+    // Protobuf-generated Lite produces quite a few warnings.
+    options.compilerArgs += [
+        "-Xlint:-rawtypes",
+        "-Xlint:-unchecked",
+        "-Xlint:-fallthrough"
+    ]
+}
+
+compileTestNanoJava {
+    options.compilerArgs = compileTestJava.options.compilerArgs
+}
+
+protobuf {
+    protoc {
+        if (project.hasProperty('protoc')) {
+            path = project.protoc
+        } else {
+            artifact = "com.google.protobuf:protoc:${protocVersion}"
+        }
+    }
+    plugins {
+        javalite {
+            if (project.hasProperty('protoc-gen-javalite')) {
+                path = project['protoc-gen-javalite']
+            } else {
+                artifact = libraries.protoc_lite
+            }
+        }
+        grpc { path = javaPluginPath }
+    }
+    generateProtoTasks {
+        all().each { task ->
+            task.dependsOn 'java_pluginExecutable'
+            task.inputs.file javaPluginPath
+        }
+        ofSourceSet('test')*.plugins { grpc {} }
+        ofSourceSet('testLite')*.each { task ->
+            task.builtins { remove java }
+            task.plugins {
+                javalite {}
+                grpc { option 'lite' }
+            }
+        }
+        ofSourceSet('testNano').each { task ->
+            task.builtins {
+                remove java
+                javanano { option 'ignore_services=true' }
+            }
+            task.plugins { grpc { option 'nano' } }
+        }
+    }
+}
+
+checkstyleTestNano {
+    source = fileTree(dir: "src/testNano", include: "**/*.java")
+}
+
+println "*** Building codegen requires Protobuf version ${protocVersion}"
+println "*** Please refer to https://github.com/grpc/grpc-java/blob/master/COMPILING.md#how-to-build-code-generation-plugin"
+
+task buildArtifacts(type: Copy) {
+    dependsOn 'java_pluginExecutable'
+    from("$buildDir/exe") {
+        if (osdetector.os != 'windows') {
+            rename 'protoc-gen-grpc-java', '$0.exe'
+        }
+    }
+    into artifactStagingPath
+}
+
+archivesBaseName = "$protocPluginBaseName"
+
+artifacts {
+    archives("$artifactStagingPath/java_plugin/${protocPluginBaseName}.exe" as File) {
+        classifier osdetector.os + "-" + arch
+        type "exe"
+        extension "exe"
+        builtBy buildArtifacts
+    }
+}
+
+// Exe files are skipped by Maven by default. Override it.
+// Also skip jar files that is generated by the java plugin.
+[
+    install.repositories.mavenInstaller,
+    uploadArchives.repositories.mavenDeployer,
+]*.setFilter {artifact, file ->
+    ! (file.getName().endsWith('jar') || file.getName().endsWith('jar.asc'))
+}
+
+[
+    uploadArchives.repositories.mavenDeployer,
+]*.beforeDeployment { it ->
+    if (!usingVisualCpp) {
+        def ret = exec {
+            executable 'bash'
+            args 'check-artifact.sh', osdetector.os, arch
+        }
+        if (ret.exitValue != 0) {
+            throw new GradleException("check-artifact.sh exited with " + ret.exitValue)
+        }
+    } else {
+        def exeName = "$artifactStagingPath/java_plugin/${protocPluginBaseName}.exe"
+        def os = new ByteArrayOutputStream()
+        def ret = exec {
+            executable 'dumpbin'
+            args '/nologo', '/dependents', exeName
+            standardOutput = os
+        }
+        if (ret.exitValue != 0) {
+            throw new GradleException("dumpbin exited with " + ret.exitValue)
+        }
+        def dlls = os.toString() =~ /Image has the following dependencies:\s+(.*)\s+Summary/
+        if (dlls[0][1] != "KERNEL32.dll") {
+            throw new Exception("unexpected dll deps: " + dlls[0][1]);
+        }
+        os.reset()
+        ret = exec {
+            executable 'dumpbin'
+            args '/nologo', '/headers', exeName
+            standardOutput = os
+        }
+        if (ret.exitValue != 0) {
+            throw new GradleException("dumpbin exited with " + ret.exitValue)
+        }
+        def machine = os.toString() =~ / machine \(([^)]+)\)/
+        def expectedArch = [x86_32: "x86", x86_64: "x64"][arch]
+        if (machine[0][1] != expectedArch) {
+            throw new Exception("unexpected architecture: " + machine[0][1]);
+        }
+    }
+}
+
+[
+    install.repositories.mavenInstaller,
+    uploadArchives.repositories.mavenDeployer,
+]*.pom*.whenConfigured { pom ->
+    pom.project {
+        // This isn't any sort of Java archive artifact, and OSSRH doesn't enforce
+        // javadoc for 'pom' packages. 'exe' would be a more appropriate packaging
+        // value, but it isn't clear how that will be interpreted. In addition,
+        // 'pom' is typically the value used when building an exe with Maven.
+        packaging = "pom"
+    }
+}
+
+def configureTestTask(Task task, String dep, String extraPackage, String serviceName) {
+    test.dependsOn task
+    task.dependsOn "generateTest${dep}Proto"
+    if (osdetector.os != 'windows') {
+        task.executable "diff"
+        task.args "-u"
+    } else {
+        task.executable "fc"
+    }
+    // File isn't found on Windows if last slash is forward-slash
+    def slash = System.getProperty("file.separator")
+    task.args "$buildDir/generated/source/proto/test${dep}/grpc/io/grpc/testing/compiler${extraPackage}${slash}${serviceName}Grpc.java",
+            "$projectDir/src/test${dep}/golden/${serviceName}.java.txt"
+}
+
+task testGolden(type: Exec)
+task testLiteGolden(type: Exec)
+task testNanoGolden(type: Exec)
+task testDeprecatedGolden(type: Exec)
+task testDeprecatedLiteGolden(type: Exec)
+task testDeprecatedNanoGolden(type: Exec)
+configureTestTask(testGolden, '', '', 'TestService')
+configureTestTask(testLiteGolden, 'Lite', '', 'TestService')
+configureTestTask(testNanoGolden, 'Nano', '/nano', 'TestService')
+configureTestTask(testDeprecatedGolden, '', '', 'TestDeprecatedService')
+configureTestTask(testDeprecatedLiteGolden, 'Lite', '', 'TestDeprecatedService')
+configureTestTask(testDeprecatedNanoGolden, 'Nano', '/nano', 'TestDeprecatedService')
diff --git a/compiler/check-artifact.sh b/compiler/check-artifact.sh
new file mode 100755
index 0000000..af31861
--- /dev/null
+++ b/compiler/check-artifact.sh
@@ -0,0 +1,129 @@
+#!/bin/bash
+
+# Check that the codegen artifacts are of correct architecture and don't have
+# unexpected dependencies.
+# To be run from Gradle.
+# Usage: check-artifact <OS> <ARCH>
+# <OS> and <ARCH> are ${os.detected.name} and ${os.detected.arch} from
+# osdetector-gradle-plugin
+OS=$1
+ARCH=$2
+
+if [[ $# < 2 ]]; then
+  echo "No arguments provided. This script is intended to be run from Gradle."
+  exit 1
+fi
+
+# Under Cygwin, bash doesn't have these in PATH when called from Gradle which
+# runs in Windows version of Java.
+export PATH="/bin:/usr/bin:$PATH"
+
+E_PARAM_ERR=98
+E_ASSERT_FAILED=99
+
+# Usage: fail ERROR_MSG
+fail()
+{
+  echo "ERROR: $1"
+  echo
+  exit $E_ASSERT_FAILED
+}
+
+# Usage: assertEq VAL1 VAL2 $LINENO
+assertEq ()
+{
+  lineno=$3
+  if [ -z "$lineno" ]; then
+    echo "lineno not given"
+    exit $E_PARAM_ERR
+  fi
+
+  if [[ "$1" != "$2" ]]; then
+    echo "Assertion failed:  \"$1\" == \"$2\""
+    echo "File \"$0\", line $lineno"    # Give name of file and line number.
+    exit $E_ASSERT_FAILED
+  fi
+}
+
+# Checks the artifact is for the expected architecture
+# Usage: checkArch <path-to-protoc>
+checkArch ()
+{
+  echo
+  echo "Checking format of $1"
+  if [[ "$OS" == windows || "$OS" == linux ]]; then
+    format="$(objdump -f "$1" | grep -o "file format .*$" | grep -o "[^ ]*$")"
+    echo Format=$format
+    if [[ "$OS" == linux ]]; then
+      if [[ "$ARCH" == x86_32 ]]; then
+        assertEq $format "elf32-i386" $LINENO
+      elif [[ "$ARCH" == x86_64 ]]; then
+        assertEq $format "elf64-x86-64" $LINENO
+      else
+        fail "Unsupported arch: $ARCH"
+      fi
+    else
+      # $OS == windows
+      if [[ "$ARCH" == x86_32 ]]; then
+        assertEq $format "pei-i386" $LINENO
+      elif [[ "$ARCH" == x86_64 ]]; then
+        assertEq $format "pei-x86-64" $LINENO
+      else
+        fail "Unsupported arch: $ARCH"
+      fi
+    fi
+  elif [[ "$OS" == osx ]]; then
+    format="$(file -b "$1" | grep -o "[^ ]*$")"
+    echo Format=$format
+    if [[ "$ARCH" == x86_32 ]]; then
+      assertEq $format "i386" $LINENO
+    elif [[ "$ARCH" == x86_64 ]]; then
+      assertEq $format "x86_64" $LINENO
+    else
+      fail "Unsupported arch: $ARCH"
+    fi
+  else
+    fail "Unsupported system: $OS"
+  fi
+  echo
+}
+
+# Checks the dependencies of the artifact. Artifacts should only depend on
+# system libraries.
+# Usage: checkDependencies <path-to-protoc>
+checkDependencies ()
+{
+  echo "Checking dependencies of $1"
+  if [[ "$OS" == windows ]]; then
+    dump_cmd='objdump -x '"$1"' | fgrep "DLL Name"'
+    white_list="KERNEL32\.dll\|msvcrt\.dll\|USER32\.dll"
+  elif [[ "$OS" == linux ]]; then
+    dump_cmd='ldd '"$1"
+    if [[ "$ARCH" == x86_32 ]]; then
+      white_list="linux-gate\.so\.1\|libpthread\.so\.0\|libm\.so\.6\|libc\.so\.6\|ld-linux\.so\.2"
+    elif [[ "$ARCH" == x86_64 ]]; then
+      white_list="linux-vdso\.so\.1\|libpthread\.so\.0\|libm\.so\.6\|libc\.so\.6\|ld-linux-x86-64\.so\.2"
+    fi
+  elif [[ "$OS" == osx ]]; then
+    dump_cmd='otool -L '"$1"' | fgrep dylib'
+    white_list="libz\.1\.dylib\|libc++.1.dylib\|libstdc++\.6\.dylib\|libSystem\.B\.dylib"
+  fi
+  if [[ -z "$white_list" || -z "$dump_cmd" ]]; then
+    fail "Unsupported platform $OS-$ARCH."
+  fi
+  echo "Checking for expected dependencies ..."
+  eval $dump_cmd | grep -i "$white_list" || fail "doesn't show any expected dependencies"
+  echo "Checking for unexpected dependencies ..."
+  eval $dump_cmd | grep -i -v "$white_list"
+  ret=$?
+  if [[ $ret == 0 ]]; then
+    fail "found unexpected dependencies (listed above)."
+  elif [[ $ret != 1 ]]; then
+    fail "Error when checking dependencies."
+  fi  # grep returns 1 when "not found", which is what we expect
+  echo "Dependencies look good."
+  echo
+}
+
+FILE="build/artifacts/java_plugin/protoc-gen-grpc-java.exe"
+checkArch "$FILE" && checkDependencies "$FILE"
diff --git a/compiler/src/java_plugin/cpp/java_generator.cpp b/compiler/src/java_plugin/cpp/java_generator.cpp
new file mode 100644
index 0000000..519a28a
--- /dev/null
+++ b/compiler/src/java_plugin/cpp/java_generator.cpp
@@ -0,0 +1,1349 @@
+#include "java_generator.h"
+
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <map>
+#include <set>
+#include <vector>
+#include <google/protobuf/compiler/java/java_names.h>
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/descriptor.pb.h>
+#include <google/protobuf/io/printer.h>
+#include <google/protobuf/io/zero_copy_stream.h>
+
+// Stringify helpers used solely to cast GRPC_VERSION
+#ifndef STR
+#define STR(s) #s
+#endif
+
+#ifndef XSTR
+#define XSTR(s) STR(s)
+#endif
+
+#ifndef FALLTHROUGH_INTENDED
+#define FALLTHROUGH_INTENDED
+#endif
+
+namespace java_grpc_generator {
+
+using google::protobuf::FileDescriptor;
+using google::protobuf::ServiceDescriptor;
+using google::protobuf::MethodDescriptor;
+using google::protobuf::Descriptor;
+using google::protobuf::io::Printer;
+using google::protobuf::SourceLocation;
+using std::to_string;
+
+// java keywords from: https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.9
+static std::set<string> java_keywords = {
+  "abstract",
+  "assert",
+  "boolean",
+  "break",
+  "byte",
+  "case",
+  "catch",
+  "char",
+  "class",
+  "const",
+  "continue",
+  "default",
+  "do",
+  "double",
+  "else",
+  "enum",
+  "extends",
+  "final",
+  "finally",
+  "float",
+  "for",
+  "goto",
+  "if",
+  "implements",
+  "import",
+  "instanceof",
+  "int",
+  "interface",
+  "long",
+  "native",
+  "new",
+  "package",
+  "private",
+  "protected",
+  "public",
+  "return",
+  "short",
+  "static",
+  "strictfp",
+  "super",
+  "switch",
+  "synchronized",
+  "this",
+  "throw",
+  "throws",
+  "transient",
+  "try",
+  "void",
+  "volatile",
+  "while",
+  // additional ones added by us
+  "true",
+  "false",
+};
+
+// Adjust a method name prefix identifier to follow the JavaBean spec:
+//   - decapitalize the first letter
+//   - remove embedded underscores & capitalize the following letter
+//  Finally, if the result is a reserved java keyword, append an underscore.
+static string MixedLower(const string& word) {
+  string w;
+  w += tolower(word[0]);
+  bool after_underscore = false;
+  for (size_t i = 1; i < word.length(); ++i) {
+    if (word[i] == '_') {
+      after_underscore = true;
+    } else {
+      w += after_underscore ? toupper(word[i]) : word[i];
+      after_underscore = false;
+    }
+  }
+  if (java_keywords.find(w) != java_keywords.end()) {
+    return w + "_";
+  }
+  return w;
+}
+
+// Converts to the identifier to the ALL_UPPER_CASE format.
+//   - An underscore is inserted where a lower case letter is followed by an
+//     upper case letter.
+//   - All letters are converted to upper case
+static string ToAllUpperCase(const string& word) {
+  string w;
+  for (size_t i = 0; i < word.length(); ++i) {
+    w += toupper(word[i]);
+    if ((i < word.length() - 1) && islower(word[i]) && isupper(word[i + 1])) {
+      w += '_';
+    }
+  }
+  return w;
+}
+
+static inline string LowerMethodName(const MethodDescriptor* method) {
+  return MixedLower(method->name());
+}
+
+static inline string MethodPropertiesFieldName(const MethodDescriptor* method) {
+  return "METHOD_" + ToAllUpperCase(method->name());
+}
+
+static inline string MethodPropertiesGetterName(const MethodDescriptor* method) {
+  return MixedLower("get_" + method->name() + "_method");
+}
+
+static inline string MethodIdFieldName(const MethodDescriptor* method) {
+  return "METHODID_" + ToAllUpperCase(method->name());
+}
+
+static inline bool ShouldGenerateAsLite(const Descriptor* desc) {
+  return false;
+}
+
+static inline string MessageFullJavaName(bool nano, const Descriptor* desc) {
+  string name = google::protobuf::compiler::java::ClassName(desc);
+  if (nano && !ShouldGenerateAsLite(desc)) {
+    // XXX: Add "nano" to the original package
+    // (https://github.com/grpc/grpc-java/issues/900)
+    if (isupper(name[0])) {
+      // No java package specified.
+      return "nano." + name;
+    }
+    for (size_t i = 0; i < name.size(); ++i) {
+      if ((name[i] == '.') && (i < (name.size() - 1)) && isupper(name[i + 1])) {
+        return name.substr(0, i + 1) + "nano." + name.substr(i + 1);
+      }
+    }
+  }
+  return name;
+}
+
+// TODO(nmittler): Remove once protobuf includes javadoc methods in distribution.
+template <typename ITR>
+static void GrpcSplitStringToIteratorUsing(const string& full,
+                                       const char* delim,
+                                       ITR& result) {
+  // Optimize the common case where delim is a single character.
+  if (delim[0] != '\0' && delim[1] == '\0') {
+    char c = delim[0];
+    const char* p = full.data();
+    const char* end = p + full.size();
+    while (p != end) {
+      if (*p == c) {
+        ++p;
+      } else {
+        const char* start = p;
+        while (++p != end && *p != c);
+        *result++ = string(start, p - start);
+      }
+    }
+    return;
+  }
+
+  string::size_type begin_index, end_index;
+  begin_index = full.find_first_not_of(delim);
+  while (begin_index != string::npos) {
+    end_index = full.find_first_of(delim, begin_index);
+    if (end_index == string::npos) {
+      *result++ = full.substr(begin_index);
+      return;
+    }
+    *result++ = full.substr(begin_index, (end_index - begin_index));
+    begin_index = full.find_first_not_of(delim, end_index);
+  }
+}
+
+// TODO(nmittler): Remove once protobuf includes javadoc methods in distribution.
+static void GrpcSplitStringUsing(const string& full,
+                             const char* delim,
+                             std::vector<string>* result) {
+  std::back_insert_iterator< std::vector<string> > it(*result);
+  GrpcSplitStringToIteratorUsing(full, delim, it);
+}
+
+// TODO(nmittler): Remove once protobuf includes javadoc methods in distribution.
+static std::vector<string> GrpcSplit(const string& full, const char* delim) {
+  std::vector<string> result;
+  GrpcSplitStringUsing(full, delim, &result);
+  return result;
+}
+
+// TODO(nmittler): Remove once protobuf includes javadoc methods in distribution.
+static string GrpcEscapeJavadoc(const string& input) {
+  string result;
+  result.reserve(input.size() * 2);
+
+  char prev = '*';
+
+  for (string::size_type i = 0; i < input.size(); i++) {
+    char c = input[i];
+    switch (c) {
+      case '*':
+        // Avoid "/*".
+        if (prev == '/') {
+          result.append("&#42;");
+        } else {
+          result.push_back(c);
+        }
+        break;
+      case '/':
+        // Avoid "*/".
+        if (prev == '*') {
+          result.append("&#47;");
+        } else {
+          result.push_back(c);
+        }
+        break;
+      case '@':
+        // '@' starts javadoc tags including the @deprecated tag, which will
+        // cause a compile-time error if inserted before a declaration that
+        // does not have a corresponding @Deprecated annotation.
+        result.append("&#64;");
+        break;
+      case '<':
+        // Avoid interpretation as HTML.
+        result.append("&lt;");
+        break;
+      case '>':
+        // Avoid interpretation as HTML.
+        result.append("&gt;");
+        break;
+      case '&':
+        // Avoid interpretation as HTML.
+        result.append("&amp;");
+        break;
+      case '\\':
+        // Java interprets Unicode escape sequences anywhere!
+        result.append("&#92;");
+        break;
+      default:
+        result.push_back(c);
+        break;
+    }
+
+    prev = c;
+  }
+
+  return result;
+}
+
+// TODO(nmittler): Remove once protobuf includes javadoc methods in distribution.
+template <typename DescriptorType>
+static string GrpcGetCommentsForDescriptor(const DescriptorType* descriptor) {
+  SourceLocation location;
+  if (descriptor->GetSourceLocation(&location)) {
+    return location.leading_comments.empty() ?
+      location.trailing_comments : location.leading_comments;
+  }
+  return string();
+}
+
+// TODO(nmittler): Remove once protobuf includes javadoc methods in distribution.
+static std::vector<string> GrpcGetDocLines(const string& comments) {
+  if (!comments.empty()) {
+    // TODO(kenton):  Ideally we should parse the comment text as Markdown and
+    //   write it back as HTML, but this requires a Markdown parser.  For now
+    //   we just use <pre> to get fixed-width text formatting.
+
+    // If the comment itself contains block comment start or end markers,
+    // HTML-escape them so that they don't accidentally close the doc comment.
+    string escapedComments = GrpcEscapeJavadoc(comments);
+
+    std::vector<string> lines = GrpcSplit(escapedComments, "\n");
+    while (!lines.empty() && lines.back().empty()) {
+      lines.pop_back();
+    }
+    return lines;
+  }
+  return std::vector<string>();
+}
+
+// TODO(nmittler): Remove once protobuf includes javadoc methods in distribution.
+template <typename DescriptorType>
+static std::vector<string> GrpcGetDocLinesForDescriptor(const DescriptorType* descriptor) {
+  return GrpcGetDocLines(GrpcGetCommentsForDescriptor(descriptor));
+}
+
+// TODO(nmittler): Remove once protobuf includes javadoc methods in distribution.
+static void GrpcWriteDocCommentBody(Printer* printer,
+                                    const std::vector<string>& lines,
+                                    bool surroundWithPreTag) {
+  if (!lines.empty()) {
+    if (surroundWithPreTag) {
+      printer->Print(" * <pre>\n");
+    }
+
+    for (size_t i = 0; i < lines.size(); i++) {
+      // Most lines should start with a space.  Watch out for lines that start
+      // with a /, since putting that right after the leading asterisk will
+      // close the comment.
+      if (!lines[i].empty() && lines[i][0] == '/') {
+        printer->Print(" * $line$\n", "line", lines[i]);
+      } else {
+        printer->Print(" *$line$\n", "line", lines[i]);
+      }
+    }
+
+    if (surroundWithPreTag) {
+      printer->Print(" * </pre>\n");
+    }
+  }
+}
+
+// TODO(nmittler): Remove once protobuf includes javadoc methods in distribution.
+static void GrpcWriteDocComment(Printer* printer, const string& comments) {
+  printer->Print("/**\n");
+  std::vector<string> lines = GrpcGetDocLines(comments);
+  GrpcWriteDocCommentBody(printer, lines, false);
+  printer->Print(" */\n");
+}
+
+// TODO(nmittler): Remove once protobuf includes javadoc methods in distribution.
+static void GrpcWriteServiceDocComment(Printer* printer,
+                                       const ServiceDescriptor* service) {
+  // Deviating from protobuf to avoid extraneous docs
+  // (see https://github.com/google/protobuf/issues/1406);
+  printer->Print("/**\n");
+  std::vector<string> lines = GrpcGetDocLinesForDescriptor(service);
+  GrpcWriteDocCommentBody(printer, lines, true);
+  printer->Print(" */\n");
+}
+
+// TODO(nmittler): Remove once protobuf includes javadoc methods in distribution.
+void GrpcWriteMethodDocComment(Printer* printer,
+                           const MethodDescriptor* method) {
+  // Deviating from protobuf to avoid extraneous docs
+  // (see https://github.com/google/protobuf/issues/1406);
+  printer->Print("/**\n");
+  std::vector<string> lines = GrpcGetDocLinesForDescriptor(method);
+  GrpcWriteDocCommentBody(printer, lines, true);
+  printer->Print(" */\n");
+}
+
+static void PrintMethodFields(
+    const ServiceDescriptor* service, std::map<string, string>* vars,
+    Printer* p, ProtoFlavor flavor) {
+  p->Print("// Static method descriptors that strictly reflect the proto.\n");
+  (*vars)["service_name"] = service->name();
+  for (int i = 0; i < service->method_count(); ++i) {
+    const MethodDescriptor* method = service->method(i);
+    (*vars)["arg_in_id"] = to_string(2 * i);
+    (*vars)["arg_out_id"] = to_string(2 * i + 1);
+    (*vars)["method_name"] = method->name();
+    (*vars)["input_type"] = MessageFullJavaName(flavor == ProtoFlavor::NANO,
+                                                method->input_type());
+    (*vars)["output_type"] = MessageFullJavaName(flavor == ProtoFlavor::NANO,
+                                                 method->output_type());
+    (*vars)["method_field_name"] = MethodPropertiesFieldName(method);
+    (*vars)["method_new_field_name"] = MethodPropertiesGetterName(method);
+    (*vars)["method_method_name"] = MethodPropertiesGetterName(method);
+    bool client_streaming = method->client_streaming();
+    bool server_streaming = method->server_streaming();
+    if (client_streaming) {
+      if (server_streaming) {
+        (*vars)["method_type"] = "BIDI_STREAMING";
+      } else {
+        (*vars)["method_type"] = "CLIENT_STREAMING";
+      }
+    } else {
+      if (server_streaming) {
+        (*vars)["method_type"] = "SERVER_STREAMING";
+      } else {
+        (*vars)["method_type"] = "UNARY";
+      }
+    }
+
+    if (flavor == ProtoFlavor::NANO) {
+      // TODO(zsurocking): we're creating two NanoFactories for each method right now.
+      // We could instead create static NanoFactories and reuse them if some methods
+      // share the same request or response messages.
+      if (!ShouldGenerateAsLite(method->input_type())) {
+        p->Print(
+            *vars,
+            "private static final int ARG_IN_$method_field_name$ = $arg_in_id$;\n");
+      }
+      if (!ShouldGenerateAsLite(method->output_type())) {
+        p->Print(
+            *vars,
+            "private static final int ARG_OUT_$method_field_name$ = $arg_out_id$;\n");
+      }
+      p->Print(
+          *vars,
+          "private static volatile $MethodDescriptor$<$input_type$,\n"
+          "    $output_type$> $method_new_field_name$;\n"
+          "\n"
+          "public static $MethodDescriptor$<$input_type$,\n"
+          "    $output_type$> $method_method_name$() {\n"
+          "  $MethodDescriptor$<$input_type$, $output_type$> $method_new_field_name$;\n"
+          "  if (($method_new_field_name$ = $service_class_name$.$method_new_field_name$) == null) {\n"
+          "    synchronized ($service_class_name$.class) {\n"
+          "      if (($method_new_field_name$ = $service_class_name$.$method_new_field_name$) == null) {\n"
+          "        $service_class_name$.$method_new_field_name$ = $method_new_field_name$ = \n"
+          "            $MethodDescriptor$.<$input_type$, $output_type$>newBuilder()\n"
+          "            .setType($MethodType$.$method_type$)\n"
+          "            .setFullMethodName(generateFullMethodName(\n"
+          "                \"$Package$$service_name$\", \"$method_name$\"))\n"
+          "            .setSampledToLocalTracing(true)\n");
+
+      (*vars)["ProtoLiteUtils"] = "io.grpc.protobuf.lite.ProtoLiteUtils";
+
+      if (ShouldGenerateAsLite(method->input_type())) {
+        p->Print(
+            *vars,
+            "            .setRequestMarshaller($ProtoLiteUtils$.marshaller(\n"
+            "                $input_type$.getDefaultInstance()))\n");
+      } else {
+        p->Print(
+            *vars,
+            "            .setRequestMarshaller($NanoUtils$.<$input_type$>marshaller(\n"
+            "                new NanoFactory<$input_type$>(ARG_IN_$method_field_name$)))\n");
+      }
+      if (ShouldGenerateAsLite(method->output_type())) {
+        p->Print(
+            *vars,
+            "            .setResponseMarshaller($ProtoLiteUtils$.marshaller(\n"
+            "                $output_type$.getDefaultInstance()))\n");
+      } else {
+        p->Print(
+            *vars,
+            "            .setResponseMarshaller($NanoUtils$.<$output_type$>marshaller(\n"
+            "                new NanoFactory<$output_type$>(ARG_OUT_$method_field_name$)))\n");
+      }
+      p->Print(
+          *vars,
+          "            .build();\n"
+          "      }\n"
+          "    }\n"
+          "  }\n"
+          "  return $method_new_field_name$;\n"
+          "}\n"
+          "\n");
+    } else {
+      if (flavor == ProtoFlavor::LITE) {
+        (*vars)["ProtoUtils"] = "io.grpc.protobuf.lite.ProtoLiteUtils";
+      } else {
+        (*vars)["ProtoUtils"] = "io.grpc.protobuf.ProtoUtils";
+      }
+      p->Print(
+          *vars,
+          "private static volatile $MethodDescriptor$<$input_type$,\n"
+          "    $output_type$> $method_new_field_name$;\n"
+          "\n"
+          "@$RpcMethod$(\n"
+          "    fullMethodName = SERVICE_NAME + '/' + \"$method_name$\",\n"
+          "    requestType = $input_type$.class,\n"
+          "    responseType = $output_type$.class,\n"
+          "    methodType = $MethodType$.$method_type$)\n"
+          "public static $MethodDescriptor$<$input_type$,\n"
+          "    $output_type$> $method_method_name$() {\n"
+          "  $MethodDescriptor$<$input_type$, $output_type$> $method_new_field_name$;\n"
+          "  if (($method_new_field_name$ = $service_class_name$.$method_new_field_name$) == null) {\n"
+          "    synchronized ($service_class_name$.class) {\n"
+          "      if (($method_new_field_name$ = $service_class_name$.$method_new_field_name$) == null) {\n"
+          "        $service_class_name$.$method_new_field_name$ = $method_new_field_name$ = \n"
+          "            $MethodDescriptor$.<$input_type$, $output_type$>newBuilder()\n"
+          "            .setType($MethodType$.$method_type$)\n"
+          "            .setFullMethodName(generateFullMethodName(\n"
+          "                \"$Package$$service_name$\", \"$method_name$\"))\n"
+          "            .setSampledToLocalTracing(true)\n"
+          "            .setRequestMarshaller($ProtoUtils$.marshaller(\n"
+          "                $input_type$.getDefaultInstance()))\n"
+          "            .setResponseMarshaller($ProtoUtils$.marshaller(\n"
+          "                $output_type$.getDefaultInstance()))\n");
+
+      (*vars)["proto_method_descriptor_supplier"] = service->name() + "MethodDescriptorSupplier";
+      if (flavor == ProtoFlavor::NORMAL) {
+        p->Print(
+            *vars,
+          "                .setSchemaDescriptor(new $proto_method_descriptor_supplier$(\"$method_name$\"))\n");
+      }
+
+      p->Print(
+          *vars,
+          "                .build();\n");
+      p->Print(*vars,
+          "        }\n"
+          "      }\n"
+          "   }\n"
+          "   return $method_new_field_name$;\n"
+          "}\n"
+          "\n");
+    }
+  }
+
+  if (flavor == ProtoFlavor::NANO) {
+    p->Print(
+        *vars,
+        "private static final class NanoFactory<T extends com.google.protobuf.nano.MessageNano>\n"
+        "    implements io.grpc.protobuf.nano.MessageNanoFactory<T> {\n"
+        "  private final int id;\n"
+        "\n"
+        "  NanoFactory(int id) {\n"
+        "    this.id = id;\n"
+        "  }\n"
+        "\n"
+        "  @$Override$\n"
+        "  public T newInstance() {\n"
+        "    Object o;\n"
+        "    switch (id) {\n");
+    bool generate_nano = true;
+    for (int i = 0; i < service->method_count(); ++i) {
+      const MethodDescriptor* method = service->method(i);
+      (*vars)["input_type"] = MessageFullJavaName(generate_nano,
+                                                  method->input_type());
+      (*vars)["output_type"] = MessageFullJavaName(generate_nano,
+                                                   method->output_type());
+      (*vars)["method_field_name"] = MethodPropertiesFieldName(method);
+      if (!ShouldGenerateAsLite(method->input_type())) {
+        p->Print(
+            *vars,
+            "    case ARG_IN_$method_field_name$:\n"
+            "      o = new $input_type$();\n"
+            "      break;\n");
+      }
+      if (!ShouldGenerateAsLite(method->output_type())) {
+        p->Print(
+            *vars,
+            "    case ARG_OUT_$method_field_name$:\n"
+            "      o = new $output_type$();\n"
+            "      break;\n");
+      }
+    }
+    p->Print(
+        "    default:\n"
+        "      throw new AssertionError();\n"
+        "    }\n"
+        "    @java.lang.SuppressWarnings(\"unchecked\")\n"
+        "    T t = (T) o;\n"
+        "    return t;\n"
+        "  }\n"
+        "}\n"
+        "\n");
+  }
+}
+
+enum StubType {
+  ASYNC_INTERFACE = 0,
+  BLOCKING_CLIENT_INTERFACE = 1,
+  FUTURE_CLIENT_INTERFACE = 2,
+  BLOCKING_SERVER_INTERFACE = 3,
+  ASYNC_CLIENT_IMPL = 4,
+  BLOCKING_CLIENT_IMPL = 5,
+  FUTURE_CLIENT_IMPL = 6,
+  ABSTRACT_CLASS = 7,
+};
+
+enum CallType {
+  ASYNC_CALL = 0,
+  BLOCKING_CALL = 1,
+  FUTURE_CALL = 2
+};
+
+static void PrintBindServiceMethodBody(const ServiceDescriptor* service,
+                                   std::map<string, string>* vars,
+                                   Printer* p,
+                                   bool generate_nano);
+
+// Prints a client interface or implementation class, or a server interface.
+static void PrintStub(
+    const ServiceDescriptor* service,
+    std::map<string, string>* vars,
+    Printer* p, StubType type, bool generate_nano) {
+  const string service_name = service->name();
+  (*vars)["service_name"] = service_name;
+  (*vars)["abstract_name"] = service_name + "ImplBase";
+  string stub_name = service_name;
+  string client_name = service_name;
+  CallType call_type;
+  bool impl_base = false;
+  bool interface = false;
+  switch (type) {
+    case ABSTRACT_CLASS:
+      call_type = ASYNC_CALL;
+      impl_base = true;
+      break;
+    case ASYNC_CLIENT_IMPL:
+      call_type = ASYNC_CALL;
+      stub_name += "Stub";
+      break;
+    case BLOCKING_CLIENT_INTERFACE:
+      interface = true;
+      FALLTHROUGH_INTENDED;
+    case BLOCKING_CLIENT_IMPL:
+      call_type = BLOCKING_CALL;
+      stub_name += "BlockingStub";
+      client_name += "BlockingClient";
+      break;
+    case FUTURE_CLIENT_INTERFACE:
+      interface = true;
+      FALLTHROUGH_INTENDED;
+    case FUTURE_CLIENT_IMPL:
+      call_type = FUTURE_CALL;
+      stub_name += "FutureStub";
+      client_name += "FutureClient";
+      break;
+    case ASYNC_INTERFACE:
+      call_type = ASYNC_CALL;
+      interface = true;
+      break;
+    default:
+      GRPC_CODEGEN_FAIL << "Cannot determine class name for StubType: " << type;
+  }
+  (*vars)["stub_name"] = stub_name;
+  (*vars)["client_name"] = client_name;
+
+  // Class head
+  if (!interface) {
+    GrpcWriteServiceDocComment(p, service);
+  }
+
+  if (service->options().deprecated()) {
+    p->Print(*vars, "@$Deprecated$\n");
+  }
+
+  if (impl_base) {
+    p->Print(
+        *vars,
+        "public static abstract class $abstract_name$ implements $BindableService$ {\n");
+  } else {
+    p->Print(
+        *vars,
+        "public static final class $stub_name$ extends $AbstractStub$<$stub_name$> {\n");
+  }
+  p->Indent();
+
+  // Constructor and build() method
+  if (!impl_base && !interface) {
+    p->Print(
+        *vars,
+        "private $stub_name$($Channel$ channel) {\n");
+    p->Indent();
+    p->Print("super(channel);\n");
+    p->Outdent();
+    p->Print("}\n\n");
+    p->Print(
+        *vars,
+        "private $stub_name$($Channel$ channel,\n"
+        "    $CallOptions$ callOptions) {\n");
+    p->Indent();
+    p->Print("super(channel, callOptions);\n");
+    p->Outdent();
+    p->Print("}\n\n");
+    p->Print(
+        *vars,
+        "@$Override$\n"
+        "protected $stub_name$ build($Channel$ channel,\n"
+        "    $CallOptions$ callOptions) {\n");
+    p->Indent();
+    p->Print(
+        *vars,
+        "return new $stub_name$(channel, callOptions);\n");
+    p->Outdent();
+    p->Print("}\n");
+  }
+
+  // RPC methods
+  for (int i = 0; i < service->method_count(); ++i) {
+    const MethodDescriptor* method = service->method(i);
+    (*vars)["input_type"] = MessageFullJavaName(generate_nano,
+                                                method->input_type());
+    (*vars)["output_type"] = MessageFullJavaName(generate_nano,
+                                                 method->output_type());
+    (*vars)["lower_method_name"] = LowerMethodName(method);
+    (*vars)["method_method_name"] = MethodPropertiesGetterName(method);
+    bool client_streaming = method->client_streaming();
+    bool server_streaming = method->server_streaming();
+
+    if (call_type == BLOCKING_CALL && client_streaming) {
+      // Blocking client interface with client streaming is not available
+      continue;
+    }
+
+    if (call_type == FUTURE_CALL && (client_streaming || server_streaming)) {
+      // Future interface doesn't support streaming.
+      continue;
+    }
+
+    // Method signature
+    p->Print("\n");
+    // TODO(nmittler): Replace with WriteMethodDocComment once included by the protobuf distro.
+    if (!interface) {
+      GrpcWriteMethodDocComment(p, method);
+    }
+
+    if (method->options().deprecated()) {
+      p->Print(*vars, "@$Deprecated$\n");
+    }
+
+    p->Print("public ");
+    switch (call_type) {
+      case BLOCKING_CALL:
+        GRPC_CODEGEN_CHECK(!client_streaming)
+            << "Blocking client interface with client streaming is unavailable";
+        if (server_streaming) {
+          // Server streaming
+          p->Print(
+              *vars,
+              "$Iterator$<$output_type$> $lower_method_name$(\n"
+              "    $input_type$ request)");
+        } else {
+          // Simple RPC
+          p->Print(
+              *vars,
+              "$output_type$ $lower_method_name$($input_type$ request)");
+        }
+        break;
+      case ASYNC_CALL:
+        if (client_streaming) {
+          // Bidirectional streaming or client streaming
+          p->Print(
+              *vars,
+              "$StreamObserver$<$input_type$> $lower_method_name$(\n"
+              "    $StreamObserver$<$output_type$> responseObserver)");
+        } else {
+          // Server streaming or simple RPC
+          p->Print(
+              *vars,
+              "void $lower_method_name$($input_type$ request,\n"
+              "    $StreamObserver$<$output_type$> responseObserver)");
+        }
+        break;
+      case FUTURE_CALL:
+        GRPC_CODEGEN_CHECK(!client_streaming && !server_streaming)
+            << "Future interface doesn't support streaming. "
+            << "client_streaming=" << client_streaming << ", "
+            << "server_streaming=" << server_streaming;
+        p->Print(
+            *vars,
+            "$ListenableFuture$<$output_type$> $lower_method_name$(\n"
+            "    $input_type$ request)");
+        break;
+    }
+
+    if (interface) {
+      p->Print(";\n");
+      continue;
+    }
+    // Method body.
+    p->Print(" {\n");
+    p->Indent();
+    if (impl_base) {
+      switch (call_type) {
+        // NB: Skipping validation of service methods. If something is wrong, we wouldn't get to
+        // this point as compiler would return errors when generating service interface.
+        case ASYNC_CALL:
+          if (client_streaming) {
+            p->Print(
+                *vars,
+                "return asyncUnimplementedStreamingCall($method_method_name$(), responseObserver);\n");
+          } else {
+            p->Print(
+                *vars,
+                "asyncUnimplementedUnaryCall($method_method_name$(), responseObserver);\n");
+          }
+          break;
+        default:
+          break;
+      }
+    } else if (!interface) {
+      switch (call_type) {
+        case BLOCKING_CALL:
+          GRPC_CODEGEN_CHECK(!client_streaming)
+              << "Blocking client streaming interface is not available";
+          if (server_streaming) {
+            (*vars)["calls_method"] = "blockingServerStreamingCall";
+            (*vars)["params"] = "request";
+          } else {
+            (*vars)["calls_method"] = "blockingUnaryCall";
+            (*vars)["params"] = "request";
+          }
+          p->Print(
+              *vars,
+              "return $calls_method$(\n"
+              "    getChannel(), $method_method_name$(), getCallOptions(), $params$);\n");
+          break;
+        case ASYNC_CALL:
+          if (server_streaming) {
+            if (client_streaming) {
+              (*vars)["calls_method"] = "asyncBidiStreamingCall";
+              (*vars)["params"] = "responseObserver";
+            } else {
+              (*vars)["calls_method"] = "asyncServerStreamingCall";
+              (*vars)["params"] = "request, responseObserver";
+            }
+          } else {
+            if (client_streaming) {
+              (*vars)["calls_method"] = "asyncClientStreamingCall";
+              (*vars)["params"] = "responseObserver";
+            } else {
+              (*vars)["calls_method"] = "asyncUnaryCall";
+              (*vars)["params"] = "request, responseObserver";
+            }
+          }
+          (*vars)["last_line_prefix"] = client_streaming ? "return " : "";
+          p->Print(
+              *vars,
+              "$last_line_prefix$$calls_method$(\n"
+              "    getChannel().newCall($method_method_name$(), getCallOptions()), $params$);\n");
+          break;
+        case FUTURE_CALL:
+          GRPC_CODEGEN_CHECK(!client_streaming && !server_streaming)
+              << "Future interface doesn't support streaming. "
+              << "client_streaming=" << client_streaming << ", "
+              << "server_streaming=" << server_streaming;
+          (*vars)["calls_method"] = "futureUnaryCall";
+          p->Print(
+              *vars,
+              "return $calls_method$(\n"
+              "    getChannel().newCall($method_method_name$(), getCallOptions()), request);\n");
+          break;
+      }
+    }
+    p->Outdent();
+    p->Print("}\n");
+  }
+
+  if (impl_base) {
+    p->Print("\n");
+    p->Print(
+        *vars,
+        "@$Override$ public final $ServerServiceDefinition$ bindService() {\n");
+    (*vars)["instance"] = "this";
+    PrintBindServiceMethodBody(service, vars, p, generate_nano);
+    p->Print("}\n");
+  }
+
+  p->Outdent();
+  p->Print("}\n\n");
+}
+
+static bool CompareMethodClientStreaming(const MethodDescriptor* method1,
+                                         const MethodDescriptor* method2)
+{
+  return method1->client_streaming() < method2->client_streaming();
+}
+
+// Place all method invocations into a single class to reduce memory footprint
+// on Android.
+static void PrintMethodHandlerClass(const ServiceDescriptor* service,
+                                   std::map<string, string>* vars,
+                                   Printer* p,
+                                   bool generate_nano) {
+  // Sort method ids based on client_streaming() so switch tables are compact.
+  std::vector<const MethodDescriptor*> sorted_methods(service->method_count());
+  for (int i = 0; i < service->method_count(); ++i) {
+    sorted_methods[i] = service->method(i);
+  }
+  stable_sort(sorted_methods.begin(), sorted_methods.end(),
+              CompareMethodClientStreaming);
+  for (size_t i = 0; i < sorted_methods.size(); i++) {
+    const MethodDescriptor* method = sorted_methods[i];
+    (*vars)["method_id"] = to_string(i);
+    (*vars)["method_id_name"] = MethodIdFieldName(method);
+    p->Print(
+        *vars,
+        "private static final int $method_id_name$ = $method_id$;\n");
+  }
+  p->Print("\n");
+  (*vars)["service_name"] = service->name() + "ImplBase";
+  p->Print(
+      *vars,
+      "private static final class MethodHandlers<Req, Resp> implements\n"
+      "    io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,\n"
+      "    io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,\n"
+      "    io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,\n"
+      "    io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {\n"
+      "  private final $service_name$ serviceImpl;\n"
+      "  private final int methodId;\n"
+      "\n"
+      "  MethodHandlers($service_name$ serviceImpl, int methodId) {\n"
+      "    this.serviceImpl = serviceImpl;\n"
+      "    this.methodId = methodId;\n"
+      "  }\n\n");
+  p->Indent();
+  p->Print(
+      *vars,
+      "@$Override$\n"
+      "@java.lang.SuppressWarnings(\"unchecked\")\n"
+      "public void invoke(Req request, $StreamObserver$<Resp> responseObserver) {\n"
+      "  switch (methodId) {\n");
+  p->Indent();
+  p->Indent();
+
+  for (int i = 0; i < service->method_count(); ++i) {
+    const MethodDescriptor* method = service->method(i);
+    if (method->client_streaming()) {
+      continue;
+    }
+    (*vars)["method_id_name"] = MethodIdFieldName(method);
+    (*vars)["lower_method_name"] = LowerMethodName(method);
+    (*vars)["input_type"] = MessageFullJavaName(generate_nano,
+                                                method->input_type());
+    (*vars)["output_type"] = MessageFullJavaName(generate_nano,
+                                                 method->output_type());
+    p->Print(
+        *vars,
+        "case $method_id_name$:\n"
+        "  serviceImpl.$lower_method_name$(($input_type$) request,\n"
+        "      ($StreamObserver$<$output_type$>) responseObserver);\n"
+        "  break;\n");
+  }
+  p->Print("default:\n"
+           "  throw new AssertionError();\n");
+
+  p->Outdent();
+  p->Outdent();
+  p->Print("  }\n"
+           "}\n\n");
+
+  p->Print(
+      *vars,
+      "@$Override$\n"
+      "@java.lang.SuppressWarnings(\"unchecked\")\n"
+      "public $StreamObserver$<Req> invoke(\n"
+      "    $StreamObserver$<Resp> responseObserver) {\n"
+      "  switch (methodId) {\n");
+  p->Indent();
+  p->Indent();
+
+  for (int i = 0; i < service->method_count(); ++i) {
+    const MethodDescriptor* method = service->method(i);
+    if (!method->client_streaming()) {
+      continue;
+    }
+    (*vars)["method_id_name"] = MethodIdFieldName(method);
+    (*vars)["lower_method_name"] = LowerMethodName(method);
+    (*vars)["input_type"] = MessageFullJavaName(generate_nano,
+                                                method->input_type());
+    (*vars)["output_type"] = MessageFullJavaName(generate_nano,
+                                                 method->output_type());
+    p->Print(
+        *vars,
+        "case $method_id_name$:\n"
+        "  return ($StreamObserver$<Req>) serviceImpl.$lower_method_name$(\n"
+        "      ($StreamObserver$<$output_type$>) responseObserver);\n");
+  }
+  p->Print("default:\n"
+           "  throw new AssertionError();\n");
+
+  p->Outdent();
+  p->Outdent();
+  p->Print("  }\n"
+           "}\n");
+
+
+  p->Outdent();
+  p->Print("}\n\n");
+}
+
+static void PrintGetServiceDescriptorMethod(const ServiceDescriptor* service,
+                                   std::map<string, string>* vars,
+                                   Printer* p,
+                                   ProtoFlavor flavor) {
+  (*vars)["service_name"] = service->name();
+
+
+  if (flavor == ProtoFlavor::NORMAL) {
+    (*vars)["proto_base_descriptor_supplier"] = service->name() + "BaseDescriptorSupplier";
+    (*vars)["proto_file_descriptor_supplier"] = service->name() + "FileDescriptorSupplier";
+    (*vars)["proto_method_descriptor_supplier"] = service->name() + "MethodDescriptorSupplier";
+    (*vars)["proto_class_name"] = google::protobuf::compiler::java::ClassName(service->file());
+    p->Print(
+        *vars,
+        "private static abstract class $proto_base_descriptor_supplier$\n"
+        "    implements $ProtoFileDescriptorSupplier$, $ProtoServiceDescriptorSupplier$ {\n"
+        "  $proto_base_descriptor_supplier$() {}\n"
+        "\n"
+        "  @$Override$\n"
+        "  public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {\n"
+        "    return $proto_class_name$.getDescriptor();\n"
+        "  }\n"
+        "\n"
+        "  @$Override$\n"
+        "  public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {\n"
+        "    return getFileDescriptor().findServiceByName(\"$service_name$\");\n"
+        "  }\n"
+        "}\n"
+        "\n"
+        "private static final class $proto_file_descriptor_supplier$\n"
+        "    extends $proto_base_descriptor_supplier$ {\n"
+        "  $proto_file_descriptor_supplier$() {}\n"
+        "}\n"
+        "\n"
+        "private static final class $proto_method_descriptor_supplier$\n"
+        "    extends $proto_base_descriptor_supplier$\n"
+        "    implements $ProtoMethodDescriptorSupplier$ {\n"
+        "  private final String methodName;\n"
+        "\n"
+        "  $proto_method_descriptor_supplier$(String methodName) {\n"
+        "    this.methodName = methodName;\n"
+        "  }\n"
+        "\n"
+        "  @$Override$\n"
+        "  public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {\n"
+        "    return getServiceDescriptor().findMethodByName(methodName);\n"
+        "  }\n"
+        "}\n\n");
+  }
+
+  p->Print(
+      *vars,
+      "private static volatile $ServiceDescriptor$ serviceDescriptor;\n\n");
+
+  p->Print(
+      *vars,
+      "public static $ServiceDescriptor$ getServiceDescriptor() {\n");
+  p->Indent();
+  p->Print(
+      *vars,
+      "$ServiceDescriptor$ result = serviceDescriptor;\n");
+  p->Print("if (result == null) {\n");
+  p->Indent();
+  p->Print(
+      *vars,
+      "synchronized ($service_class_name$.class) {\n");
+  p->Indent();
+  p->Print("result = serviceDescriptor;\n");
+  p->Print("if (result == null) {\n");
+  p->Indent();
+
+  p->Print(
+      *vars,
+      "serviceDescriptor = result = $ServiceDescriptor$.newBuilder(SERVICE_NAME)");
+  p->Indent();
+  p->Indent();
+  if (flavor == ProtoFlavor::NORMAL) {
+    p->Print(
+        *vars,
+        "\n.setSchemaDescriptor(new $proto_file_descriptor_supplier$())");
+  }
+  for (int i = 0; i < service->method_count(); ++i) {
+    const MethodDescriptor* method = service->method(i);
+    (*vars)["method_method_name"] = MethodPropertiesGetterName(method);
+    p->Print(*vars, "\n.addMethod($method_method_name$())");
+  }
+  p->Print("\n.build();\n");
+  p->Outdent();
+  p->Outdent();
+
+  p->Outdent();
+  p->Print("}\n");
+  p->Outdent();
+  p->Print("}\n");
+  p->Outdent();
+  p->Print("}\n");
+  p->Print("return result;\n");
+  p->Outdent();
+  p->Print("}\n");
+}
+
+static void PrintBindServiceMethodBody(const ServiceDescriptor* service,
+                                   std::map<string, string>* vars,
+                                   Printer* p,
+                                   bool generate_nano) {
+  (*vars)["service_name"] = service->name();
+  p->Indent();
+  p->Print(*vars,
+           "return "
+           "$ServerServiceDefinition$.builder(getServiceDescriptor())\n");
+  p->Indent();
+  p->Indent();
+  for (int i = 0; i < service->method_count(); ++i) {
+    const MethodDescriptor* method = service->method(i);
+    (*vars)["lower_method_name"] = LowerMethodName(method);
+    (*vars)["method_method_name"] = MethodPropertiesGetterName(method);
+    (*vars)["input_type"] = MessageFullJavaName(generate_nano,
+                                                method->input_type());
+    (*vars)["output_type"] = MessageFullJavaName(generate_nano,
+                                                 method->output_type());
+    (*vars)["method_id_name"] = MethodIdFieldName(method);
+    bool client_streaming = method->client_streaming();
+    bool server_streaming = method->server_streaming();
+    if (client_streaming) {
+      if (server_streaming) {
+        (*vars)["calls_method"] = "asyncBidiStreamingCall";
+      } else {
+        (*vars)["calls_method"] = "asyncClientStreamingCall";
+      }
+    } else {
+      if (server_streaming) {
+        (*vars)["calls_method"] = "asyncServerStreamingCall";
+      } else {
+        (*vars)["calls_method"] = "asyncUnaryCall";
+      }
+    }
+    p->Print(*vars, ".addMethod(\n");
+    p->Indent();
+    p->Print(
+        *vars,
+        "$method_method_name$(),\n"
+        "$calls_method$(\n");
+    p->Indent();
+    p->Print(
+        *vars,
+        "new MethodHandlers<\n"
+        "  $input_type$,\n"
+        "  $output_type$>(\n"
+        "    $instance$, $method_id_name$)))\n");
+    p->Outdent();
+    p->Outdent();
+  }
+  p->Print(".build();\n");
+  p->Outdent();
+  p->Outdent();
+  p->Outdent();
+}
+
+static void PrintService(const ServiceDescriptor* service,
+                         std::map<string, string>* vars,
+                         Printer* p,
+                         ProtoFlavor flavor,
+                         bool disable_version) {
+  (*vars)["service_name"] = service->name();
+  (*vars)["file_name"] = service->file()->name();
+  (*vars)["service_class_name"] = ServiceClassName(service);
+  (*vars)["grpc_version"] = "";
+  #ifdef GRPC_VERSION
+  if (!disable_version) {
+    (*vars)["grpc_version"] = " (version " XSTR(GRPC_VERSION) ")";
+  }
+  #endif
+  // TODO(nmittler): Replace with WriteServiceDocComment once included by protobuf distro.
+  GrpcWriteServiceDocComment(p, service);
+  p->Print(
+      *vars,
+      "@$Generated$(\n"
+      "    value = \"by gRPC proto compiler$grpc_version$\",\n"
+      "    comments = \"Source: $file_name$\")\n");
+
+  if (service->options().deprecated()) {
+    p->Print(*vars, "@$Deprecated$\n");
+  }
+
+  p->Print(
+      *vars,
+      "public final class $service_class_name$ {\n\n");
+  p->Indent();
+  p->Print(
+      *vars,
+      "private $service_class_name$() {}\n\n");
+
+  p->Print(
+      *vars,
+      "public static final String SERVICE_NAME = "
+      "\"$Package$$service_name$\";\n\n");
+
+  PrintMethodFields(service, vars, p, flavor);
+
+  // TODO(nmittler): Replace with WriteDocComment once included by protobuf distro.
+  GrpcWriteDocComment(p, " Creates a new async stub that supports all call types for the service");
+  p->Print(
+      *vars,
+      "public static $service_name$Stub newStub($Channel$ channel) {\n");
+  p->Indent();
+  p->Print(
+      *vars,
+      "return new $service_name$Stub(channel);\n");
+  p->Outdent();
+  p->Print("}\n\n");
+
+  // TODO(nmittler): Replace with WriteDocComment once included by protobuf distro.
+  GrpcWriteDocComment(p, " Creates a new blocking-style stub that supports unary and streaming "
+                         "output calls on the service");
+  p->Print(
+      *vars,
+      "public static $service_name$BlockingStub newBlockingStub(\n"
+      "    $Channel$ channel) {\n");
+  p->Indent();
+  p->Print(
+      *vars,
+      "return new $service_name$BlockingStub(channel);\n");
+  p->Outdent();
+  p->Print("}\n\n");
+
+  // TODO(nmittler): Replace with WriteDocComment once included by protobuf distro.
+  GrpcWriteDocComment(p, " Creates a new ListenableFuture-style stub that supports unary calls "
+                         "on the service");
+  p->Print(
+      *vars,
+      "public static $service_name$FutureStub newFutureStub(\n"
+      "    $Channel$ channel) {\n");
+  p->Indent();
+  p->Print(
+      *vars,
+      "return new $service_name$FutureStub(channel);\n");
+  p->Outdent();
+  p->Print("}\n\n");
+
+  bool generate_nano = flavor == ProtoFlavor::NANO;
+  PrintStub(service, vars, p, ABSTRACT_CLASS, generate_nano);
+  PrintStub(service, vars, p, ASYNC_CLIENT_IMPL, generate_nano);
+  PrintStub(service, vars, p, BLOCKING_CLIENT_IMPL, generate_nano);
+  PrintStub(service, vars, p, FUTURE_CLIENT_IMPL, generate_nano);
+
+  PrintMethodHandlerClass(service, vars, p, generate_nano);
+  PrintGetServiceDescriptorMethod(service, vars, p, flavor);
+  p->Outdent();
+  p->Print("}\n");
+}
+
+void PrintImports(Printer* p, bool generate_nano) {
+  p->Print(
+      "import static "
+      "io.grpc.MethodDescriptor.generateFullMethodName;\n"
+      "import static "
+      "io.grpc.stub.ClientCalls.asyncBidiStreamingCall;\n"
+      "import static "
+      "io.grpc.stub.ClientCalls.asyncClientStreamingCall;\n"
+      "import static "
+      "io.grpc.stub.ClientCalls.asyncServerStreamingCall;\n"
+      "import static "
+      "io.grpc.stub.ClientCalls.asyncUnaryCall;\n"
+      "import static "
+      "io.grpc.stub.ClientCalls.blockingServerStreamingCall;\n"
+      "import static "
+      "io.grpc.stub.ClientCalls.blockingUnaryCall;\n"
+      "import static "
+      "io.grpc.stub.ClientCalls.futureUnaryCall;\n"
+      "import static "
+      "io.grpc.stub.ServerCalls.asyncBidiStreamingCall;\n"
+      "import static "
+      "io.grpc.stub.ServerCalls.asyncClientStreamingCall;\n"
+      "import static "
+      "io.grpc.stub.ServerCalls.asyncServerStreamingCall;\n"
+      "import static "
+      "io.grpc.stub.ServerCalls.asyncUnaryCall;\n"
+      "import static "
+      "io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;\n"
+      "import static "
+      "io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;\n\n");
+  if (generate_nano) {
+    p->Print("import java.io.IOException;\n\n");
+  }
+}
+
+void GenerateService(const ServiceDescriptor* service,
+                     google::protobuf::io::ZeroCopyOutputStream* out,
+                     ProtoFlavor flavor,
+                     bool disable_version) {
+  // All non-generated classes must be referred by fully qualified names to
+  // avoid collision with generated classes.
+  std::map<string, string> vars;
+  vars["String"] = "java.lang.String";
+  vars["Deprecated"] = "java.lang.Deprecated";
+  vars["Override"] = "java.lang.Override";
+  vars["Channel"] = "io.grpc.Channel";
+  vars["CallOptions"] = "io.grpc.CallOptions";
+  vars["MethodType"] = "io.grpc.MethodDescriptor.MethodType";
+  vars["ServerMethodDefinition"] =
+      "io.grpc.ServerMethodDefinition";
+  vars["BindableService"] = "io.grpc.BindableService";
+  vars["ServerServiceDefinition"] =
+      "io.grpc.ServerServiceDefinition";
+  vars["ServiceDescriptor"] =
+      "io.grpc.ServiceDescriptor";
+  vars["ProtoFileDescriptorSupplier"] =
+      "io.grpc.protobuf.ProtoFileDescriptorSupplier";
+  vars["ProtoServiceDescriptorSupplier"] =
+      "io.grpc.protobuf.ProtoServiceDescriptorSupplier";
+  vars["ProtoMethodDescriptorSupplier"] =
+      "io.grpc.protobuf.ProtoMethodDescriptorSupplier";
+  vars["AbstractStub"] = "io.grpc.stub.AbstractStub";
+  vars["RpcMethod"] = "io.grpc.stub.annotations.RpcMethod";
+  vars["MethodDescriptor"] = "io.grpc.MethodDescriptor";
+  vars["NanoUtils"] = "io.grpc.protobuf.nano.NanoUtils";
+  vars["StreamObserver"] = "io.grpc.stub.StreamObserver";
+  vars["Iterator"] = "java.util.Iterator";
+  vars["Generated"] = "javax.annotation.Generated";
+  vars["ListenableFuture"] =
+      "com.google.common.util.concurrent.ListenableFuture";
+
+  Printer printer(out, '$');
+  string package_name = ServiceJavaPackage(service->file(),
+                                           flavor == ProtoFlavor::NANO);
+  if (!package_name.empty()) {
+    printer.Print(
+        "package $package_name$;\n\n",
+        "package_name", package_name);
+  }
+  PrintImports(&printer, flavor == ProtoFlavor::NANO);
+
+  // Package string is used to fully qualify method names.
+  vars["Package"] = service->file()->package();
+  if (!vars["Package"].empty()) {
+    vars["Package"].append(".");
+  }
+  PrintService(service, &vars, &printer, flavor, disable_version);
+}
+
+string ServiceJavaPackage(const FileDescriptor* file, bool nano) {
+  string result = google::protobuf::compiler::java::ClassName(file);
+  size_t last_dot_pos = result.find_last_of('.');
+  if (last_dot_pos != string::npos) {
+    result.resize(last_dot_pos);
+  } else {
+    result = "";
+  }
+  if (nano) {
+    if (!result.empty()) {
+      result += ".";
+    }
+    result += "nano";
+  }
+  return result;
+}
+
+string ServiceClassName(const google::protobuf::ServiceDescriptor* service) {
+  return service->name() + "Grpc";
+}
+
+}  // namespace java_grpc_generator
diff --git a/compiler/src/java_plugin/cpp/java_generator.h b/compiler/src/java_plugin/cpp/java_generator.h
new file mode 100644
index 0000000..ab265d0
--- /dev/null
+++ b/compiler/src/java_plugin/cpp/java_generator.h
@@ -0,0 +1,57 @@
+#ifndef NET_GRPC_COMPILER_JAVA_GENERATOR_H_
+#define NET_GRPC_COMPILER_JAVA_GENERATOR_H_
+
+#include <stdlib.h>  // for abort()
+#include <iostream>
+#include <string>
+
+#include <google/protobuf/io/zero_copy_stream.h>
+#include <google/protobuf/descriptor.h>
+
+class LogHelper {
+  std::ostream* os;
+
+ public:
+  LogHelper(std::ostream* os) : os(os) {}
+  ~LogHelper() {
+    *os << std::endl;
+    ::abort();
+  }
+  std::ostream& get_os() {
+    return *os;
+  }
+};
+
+// Abort the program after logging the mesage if the given condition is not
+// true. Otherwise, do nothing.
+#define GRPC_CODEGEN_CHECK(x) !(x) && LogHelper(&std::cerr).get_os() \
+                             << "CHECK FAILED: " << __FILE__ << ":" \
+                             << __LINE__ << ": "
+
+// Abort the program after logging the mesage.
+#define GRPC_CODEGEN_FAIL GRPC_CODEGEN_CHECK(false)
+
+using namespace std;
+
+namespace java_grpc_generator {
+
+enum ProtoFlavor {
+  NORMAL, LITE, NANO
+};
+
+// Returns the package name of the gRPC services defined in the given file.
+string ServiceJavaPackage(const google::protobuf::FileDescriptor* file, bool nano);
+
+// Returns the name of the outer class that wraps in all the generated code for
+// the given service.
+string ServiceClassName(const google::protobuf::ServiceDescriptor* service);
+
+// Writes the generated service interface into the given ZeroCopyOutputStream
+void GenerateService(const google::protobuf::ServiceDescriptor* service,
+                     google::protobuf::io::ZeroCopyOutputStream* out,
+                     ProtoFlavor flavor,
+                     bool disable_version);
+
+}  // namespace java_grpc_generator
+
+#endif  // NET_GRPC_COMPILER_JAVA_GENERATOR_H_
diff --git a/compiler/src/java_plugin/cpp/java_plugin.cpp b/compiler/src/java_plugin/cpp/java_plugin.cpp
new file mode 100644
index 0000000..b2cbd0b
--- /dev/null
+++ b/compiler/src/java_plugin/cpp/java_plugin.cpp
@@ -0,0 +1,70 @@
+// Generates Java gRPC service interface out of Protobuf IDL.
+//
+// This is a Proto2 compiler plugin.  See net/proto2/compiler/proto/plugin.proto
+// and net/proto2/compiler/public/plugin.h for more information on plugins.
+
+#include <memory>
+
+#include "java_generator.h"
+#include <google/protobuf/compiler/code_generator.h>
+#include <google/protobuf/compiler/plugin.h>
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/io/zero_copy_stream.h>
+
+static string JavaPackageToDir(const string& package_name) {
+  string package_dir = package_name;
+  for (size_t i = 0; i < package_dir.size(); ++i) {
+    if (package_dir[i] == '.') {
+      package_dir[i] = '/';
+    }
+  }
+  if (!package_dir.empty()) package_dir += "/";
+  return package_dir;
+}
+
+class JavaGrpcGenerator : public google::protobuf::compiler::CodeGenerator {
+ public:
+  JavaGrpcGenerator() {}
+  virtual ~JavaGrpcGenerator() {}
+
+  virtual bool Generate(const google::protobuf::FileDescriptor* file,
+                        const string& parameter,
+                        google::protobuf::compiler::GeneratorContext* context,
+                        string* error) const {
+    std::vector<std::pair<string, string> > options;
+    google::protobuf::compiler::ParseGeneratorParameter(parameter, &options);
+
+    java_grpc_generator::ProtoFlavor flavor =
+        java_grpc_generator::ProtoFlavor::NORMAL;
+
+    bool disable_version = false;
+    for (size_t i = 0; i < options.size(); i++) {
+      if (options[i].first == "nano") {
+        flavor = java_grpc_generator::ProtoFlavor::NANO;
+      } else if (options[i].first == "lite") {
+        flavor = java_grpc_generator::ProtoFlavor::LITE;
+      } else if (options[i].first == "noversion") {
+        disable_version = true;
+      }
+    }
+
+    string package_name = java_grpc_generator::ServiceJavaPackage(
+        file, flavor == java_grpc_generator::ProtoFlavor::NANO);
+    string package_filename = JavaPackageToDir(package_name);
+    for (int i = 0; i < file->service_count(); ++i) {
+      const google::protobuf::ServiceDescriptor* service = file->service(i);
+      string filename = package_filename
+          + java_grpc_generator::ServiceClassName(service) + ".java";
+      std::unique_ptr<google::protobuf::io::ZeroCopyOutputStream> output(
+          context->Open(filename));
+      java_grpc_generator::GenerateService(
+          service, output.get(), flavor, disable_version);
+    }
+    return true;
+  }
+};
+
+int main(int argc, char* argv[]) {
+  JavaGrpcGenerator generator;
+  return google::protobuf::compiler::PluginMain(argc, argv, &generator);
+}
diff --git a/compiler/src/test/golden/TestDeprecatedService.java.txt b/compiler/src/test/golden/TestDeprecatedService.java.txt
new file mode 100644
index 0000000..8cc441b
--- /dev/null
+++ b/compiler/src/test/golden/TestDeprecatedService.java.txt
@@ -0,0 +1,316 @@
+package io.grpc.testing.compiler;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ * <pre>
+ * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+ * </pre>
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler (version 1.16.0-SNAPSHOT)",
+    comments = "Source: grpc/testing/compiler/test.proto")
+@java.lang.Deprecated
+public final class TestDeprecatedServiceGrpc {
+
+  private TestDeprecatedServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.compiler.TestDeprecatedService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.SimpleRequest,
+      io.grpc.testing.compiler.Test.SimpleResponse> getDeprecatedMethodMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "DeprecatedMethod",
+      requestType = io.grpc.testing.compiler.Test.SimpleRequest.class,
+      responseType = io.grpc.testing.compiler.Test.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.SimpleRequest,
+      io.grpc.testing.compiler.Test.SimpleResponse> getDeprecatedMethodMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.SimpleRequest, io.grpc.testing.compiler.Test.SimpleResponse> getDeprecatedMethodMethod;
+    if ((getDeprecatedMethodMethod = TestDeprecatedServiceGrpc.getDeprecatedMethodMethod) == null) {
+      synchronized (TestDeprecatedServiceGrpc.class) {
+        if ((getDeprecatedMethodMethod = TestDeprecatedServiceGrpc.getDeprecatedMethodMethod) == null) {
+          TestDeprecatedServiceGrpc.getDeprecatedMethodMethod = getDeprecatedMethodMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.SimpleRequest, io.grpc.testing.compiler.Test.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestDeprecatedService", "DeprecatedMethod"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.SimpleResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestDeprecatedServiceMethodDescriptorSupplier("DeprecatedMethod"))
+                  .build();
+          }
+        }
+     }
+     return getDeprecatedMethodMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static TestDeprecatedServiceStub newStub(io.grpc.Channel channel) {
+    return new TestDeprecatedServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static TestDeprecatedServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new TestDeprecatedServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static TestDeprecatedServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new TestDeprecatedServiceFutureStub(channel);
+  }
+
+  /**
+   * <pre>
+   * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+   * </pre>
+   */
+  @java.lang.Deprecated
+  public static abstract class TestDeprecatedServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * An RPC method that has been deprecated and should generate with Java's &#64;Deprecated annotation
+     * </pre>
+     */
+    @java.lang.Deprecated
+    public void deprecatedMethod(io.grpc.testing.compiler.Test.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.SimpleResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getDeprecatedMethodMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getDeprecatedMethodMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.SimpleRequest,
+                io.grpc.testing.compiler.Test.SimpleResponse>(
+                  this, METHODID_DEPRECATED_METHOD)))
+          .build();
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+   * </pre>
+   */
+  @java.lang.Deprecated
+  public static final class TestDeprecatedServiceStub extends io.grpc.stub.AbstractStub<TestDeprecatedServiceStub> {
+    private TestDeprecatedServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestDeprecatedServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestDeprecatedServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestDeprecatedServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * An RPC method that has been deprecated and should generate with Java's &#64;Deprecated annotation
+     * </pre>
+     */
+    @java.lang.Deprecated
+    public void deprecatedMethod(io.grpc.testing.compiler.Test.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.SimpleResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getDeprecatedMethodMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+   * </pre>
+   */
+  @java.lang.Deprecated
+  public static final class TestDeprecatedServiceBlockingStub extends io.grpc.stub.AbstractStub<TestDeprecatedServiceBlockingStub> {
+    private TestDeprecatedServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestDeprecatedServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestDeprecatedServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestDeprecatedServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * An RPC method that has been deprecated and should generate with Java's &#64;Deprecated annotation
+     * </pre>
+     */
+    @java.lang.Deprecated
+    public io.grpc.testing.compiler.Test.SimpleResponse deprecatedMethod(io.grpc.testing.compiler.Test.SimpleRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getDeprecatedMethodMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+   * </pre>
+   */
+  @java.lang.Deprecated
+  public static final class TestDeprecatedServiceFutureStub extends io.grpc.stub.AbstractStub<TestDeprecatedServiceFutureStub> {
+    private TestDeprecatedServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestDeprecatedServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestDeprecatedServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestDeprecatedServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * An RPC method that has been deprecated and should generate with Java's &#64;Deprecated annotation
+     * </pre>
+     */
+    @java.lang.Deprecated
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.compiler.Test.SimpleResponse> deprecatedMethod(
+        io.grpc.testing.compiler.Test.SimpleRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getDeprecatedMethodMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_DEPRECATED_METHOD = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final TestDeprecatedServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(TestDeprecatedServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_DEPRECATED_METHOD:
+          serviceImpl.deprecatedMethod((io.grpc.testing.compiler.Test.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.SimpleResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class TestDeprecatedServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    TestDeprecatedServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.testing.compiler.Test.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("TestDeprecatedService");
+    }
+  }
+
+  private static final class TestDeprecatedServiceFileDescriptorSupplier
+      extends TestDeprecatedServiceBaseDescriptorSupplier {
+    TestDeprecatedServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class TestDeprecatedServiceMethodDescriptorSupplier
+      extends TestDeprecatedServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    TestDeprecatedServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (TestDeprecatedServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new TestDeprecatedServiceFileDescriptorSupplier())
+              .addMethod(getDeprecatedMethodMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/compiler/src/test/golden/TestService.java.txt b/compiler/src/test/golden/TestService.java.txt
new file mode 100644
index 0000000..c9e10cf
--- /dev/null
+++ b/compiler/src/test/golden/TestService.java.txt
@@ -0,0 +1,665 @@
+package io.grpc.testing.compiler;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ * <pre>
+ * Test service that supports all call types.
+ * </pre>
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler (version 1.16.0-SNAPSHOT)",
+    comments = "Source: grpc/testing/compiler/test.proto")
+public final class TestServiceGrpc {
+
+  private TestServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.compiler.TestService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.SimpleRequest,
+      io.grpc.testing.compiler.Test.SimpleResponse> getUnaryCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "UnaryCall",
+      requestType = io.grpc.testing.compiler.Test.SimpleRequest.class,
+      responseType = io.grpc.testing.compiler.Test.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.SimpleRequest,
+      io.grpc.testing.compiler.Test.SimpleResponse> getUnaryCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.SimpleRequest, io.grpc.testing.compiler.Test.SimpleResponse> getUnaryCallMethod;
+    if ((getUnaryCallMethod = TestServiceGrpc.getUnaryCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getUnaryCallMethod = TestServiceGrpc.getUnaryCallMethod) == null) {
+          TestServiceGrpc.getUnaryCallMethod = getUnaryCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.SimpleRequest, io.grpc.testing.compiler.Test.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "UnaryCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.SimpleResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("UnaryCall"))
+                  .build();
+          }
+        }
+     }
+     return getUnaryCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getStreamingOutputCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "StreamingOutputCall",
+      requestType = io.grpc.testing.compiler.Test.StreamingOutputCallRequest.class,
+      responseType = io.grpc.testing.compiler.Test.StreamingOutputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getStreamingOutputCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getStreamingOutputCallMethod;
+    if ((getStreamingOutputCallMethod = TestServiceGrpc.getStreamingOutputCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getStreamingOutputCallMethod = TestServiceGrpc.getStreamingOutputCallMethod) == null) {
+          TestServiceGrpc.getStreamingOutputCallMethod = getStreamingOutputCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.Test.StreamingOutputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "StreamingOutputCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingOutputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingOutputCallResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("StreamingOutputCall"))
+                  .build();
+          }
+        }
+     }
+     return getStreamingOutputCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingInputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingInputCallResponse> getStreamingInputCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "StreamingInputCall",
+      requestType = io.grpc.testing.compiler.Test.StreamingInputCallRequest.class,
+      responseType = io.grpc.testing.compiler.Test.StreamingInputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingInputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingInputCallResponse> getStreamingInputCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingInputCallRequest, io.grpc.testing.compiler.Test.StreamingInputCallResponse> getStreamingInputCallMethod;
+    if ((getStreamingInputCallMethod = TestServiceGrpc.getStreamingInputCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getStreamingInputCallMethod = TestServiceGrpc.getStreamingInputCallMethod) == null) {
+          TestServiceGrpc.getStreamingInputCallMethod = getStreamingInputCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.StreamingInputCallRequest, io.grpc.testing.compiler.Test.StreamingInputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "StreamingInputCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingInputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingInputCallResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("StreamingInputCall"))
+                  .build();
+          }
+        }
+     }
+     return getStreamingInputCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getFullBidiCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "FullBidiCall",
+      requestType = io.grpc.testing.compiler.Test.StreamingOutputCallRequest.class,
+      responseType = io.grpc.testing.compiler.Test.StreamingOutputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getFullBidiCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getFullBidiCallMethod;
+    if ((getFullBidiCallMethod = TestServiceGrpc.getFullBidiCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getFullBidiCallMethod = TestServiceGrpc.getFullBidiCallMethod) == null) {
+          TestServiceGrpc.getFullBidiCallMethod = getFullBidiCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.Test.StreamingOutputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "FullBidiCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingOutputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingOutputCallResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("FullBidiCall"))
+                  .build();
+          }
+        }
+     }
+     return getFullBidiCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getHalfBidiCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "HalfBidiCall",
+      requestType = io.grpc.testing.compiler.Test.StreamingOutputCallRequest.class,
+      responseType = io.grpc.testing.compiler.Test.StreamingOutputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getHalfBidiCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getHalfBidiCallMethod;
+    if ((getHalfBidiCallMethod = TestServiceGrpc.getHalfBidiCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getHalfBidiCallMethod = TestServiceGrpc.getHalfBidiCallMethod) == null) {
+          TestServiceGrpc.getHalfBidiCallMethod = getHalfBidiCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.Test.StreamingOutputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "HalfBidiCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingOutputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingOutputCallResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("HalfBidiCall"))
+                  .build();
+          }
+        }
+     }
+     return getHalfBidiCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingInputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingInputCallResponse> getImportMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "Import",
+      requestType = io.grpc.testing.compiler.Test.StreamingInputCallRequest.class,
+      responseType = io.grpc.testing.compiler.Test.StreamingInputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingInputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingInputCallResponse> getImportMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingInputCallRequest, io.grpc.testing.compiler.Test.StreamingInputCallResponse> getImportMethod;
+    if ((getImportMethod = TestServiceGrpc.getImportMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getImportMethod = TestServiceGrpc.getImportMethod) == null) {
+          TestServiceGrpc.getImportMethod = getImportMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.StreamingInputCallRequest, io.grpc.testing.compiler.Test.StreamingInputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "Import"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingInputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingInputCallResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("Import"))
+                  .build();
+          }
+        }
+     }
+     return getImportMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static TestServiceStub newStub(io.grpc.Channel channel) {
+    return new TestServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static TestServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new TestServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static TestServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new TestServiceFutureStub(channel);
+  }
+
+  /**
+   * <pre>
+   * Test service that supports all call types.
+   * </pre>
+   */
+  public static abstract class TestServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public void unaryCall(io.grpc.testing.compiler.Test.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.SimpleResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getUnaryCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * One request followed by a sequence of responses (streamed download).
+     * The server returns the payload with client desired type and sizes.
+     * </pre>
+     */
+    public void streamingOutputCall(io.grpc.testing.compiler.Test.StreamingOutputCallRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getStreamingOutputCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by one response (streamed upload).
+     * The server returns the aggregated size of client payload as the result.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallRequest> streamingInputCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getStreamingInputCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests with each request served by the server immediately.
+     * As one request could lead to multiple responses, this interface
+     * demonstrates the idea of full bidirectionality.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallRequest> fullBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getFullBidiCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by a sequence of responses.
+     * The server buffers all the client requests and then serves them in order. A
+     * stream of responses are returned to the client when the server starts with
+     * first request.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallRequest> halfBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getHalfBidiCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * An RPC method whose Java name collides with a keyword, and whose generated
+     * method should have a '_' appended.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallRequest> import_(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getImportMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getUnaryCallMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.SimpleRequest,
+                io.grpc.testing.compiler.Test.SimpleResponse>(
+                  this, METHODID_UNARY_CALL)))
+          .addMethod(
+            getStreamingOutputCallMethod(),
+            asyncServerStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+                io.grpc.testing.compiler.Test.StreamingOutputCallResponse>(
+                  this, METHODID_STREAMING_OUTPUT_CALL)))
+          .addMethod(
+            getStreamingInputCallMethod(),
+            asyncClientStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.StreamingInputCallRequest,
+                io.grpc.testing.compiler.Test.StreamingInputCallResponse>(
+                  this, METHODID_STREAMING_INPUT_CALL)))
+          .addMethod(
+            getFullBidiCallMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+                io.grpc.testing.compiler.Test.StreamingOutputCallResponse>(
+                  this, METHODID_FULL_BIDI_CALL)))
+          .addMethod(
+            getHalfBidiCallMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+                io.grpc.testing.compiler.Test.StreamingOutputCallResponse>(
+                  this, METHODID_HALF_BIDI_CALL)))
+          .addMethod(
+            getImportMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.StreamingInputCallRequest,
+                io.grpc.testing.compiler.Test.StreamingInputCallResponse>(
+                  this, METHODID_IMPORT)))
+          .build();
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that supports all call types.
+   * </pre>
+   */
+  public static final class TestServiceStub extends io.grpc.stub.AbstractStub<TestServiceStub> {
+    private TestServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public void unaryCall(io.grpc.testing.compiler.Test.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.SimpleResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getUnaryCallMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * One request followed by a sequence of responses (streamed download).
+     * The server returns the payload with client desired type and sizes.
+     * </pre>
+     */
+    public void streamingOutputCall(io.grpc.testing.compiler.Test.StreamingOutputCallRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> responseObserver) {
+      asyncServerStreamingCall(
+          getChannel().newCall(getStreamingOutputCallMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by one response (streamed upload).
+     * The server returns the aggregated size of client payload as the result.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallRequest> streamingInputCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallResponse> responseObserver) {
+      return asyncClientStreamingCall(
+          getChannel().newCall(getStreamingInputCallMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests with each request served by the server immediately.
+     * As one request could lead to multiple responses, this interface
+     * demonstrates the idea of full bidirectionality.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallRequest> fullBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getFullBidiCallMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by a sequence of responses.
+     * The server buffers all the client requests and then serves them in order. A
+     * stream of responses are returned to the client when the server starts with
+     * first request.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallRequest> halfBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getHalfBidiCallMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * An RPC method whose Java name collides with a keyword, and whose generated
+     * method should have a '_' appended.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallRequest> import_(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getImportMethod(), getCallOptions()), responseObserver);
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that supports all call types.
+   * </pre>
+   */
+  public static final class TestServiceBlockingStub extends io.grpc.stub.AbstractStub<TestServiceBlockingStub> {
+    private TestServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public io.grpc.testing.compiler.Test.SimpleResponse unaryCall(io.grpc.testing.compiler.Test.SimpleRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getUnaryCallMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * One request followed by a sequence of responses (streamed download).
+     * The server returns the payload with client desired type and sizes.
+     * </pre>
+     */
+    public java.util.Iterator<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> streamingOutputCall(
+        io.grpc.testing.compiler.Test.StreamingOutputCallRequest request) {
+      return blockingServerStreamingCall(
+          getChannel(), getStreamingOutputCallMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that supports all call types.
+   * </pre>
+   */
+  public static final class TestServiceFutureStub extends io.grpc.stub.AbstractStub<TestServiceFutureStub> {
+    private TestServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.compiler.Test.SimpleResponse> unaryCall(
+        io.grpc.testing.compiler.Test.SimpleRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getUnaryCallMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_UNARY_CALL = 0;
+  private static final int METHODID_STREAMING_OUTPUT_CALL = 1;
+  private static final int METHODID_STREAMING_INPUT_CALL = 2;
+  private static final int METHODID_FULL_BIDI_CALL = 3;
+  private static final int METHODID_HALF_BIDI_CALL = 4;
+  private static final int METHODID_IMPORT = 5;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final TestServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(TestServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_UNARY_CALL:
+          serviceImpl.unaryCall((io.grpc.testing.compiler.Test.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.SimpleResponse>) responseObserver);
+          break;
+        case METHODID_STREAMING_OUTPUT_CALL:
+          serviceImpl.streamingOutputCall((io.grpc.testing.compiler.Test.StreamingOutputCallRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_STREAMING_INPUT_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.streamingInputCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallResponse>) responseObserver);
+        case METHODID_FULL_BIDI_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.fullBidiCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse>) responseObserver);
+        case METHODID_HALF_BIDI_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.halfBidiCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse>) responseObserver);
+        case METHODID_IMPORT:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.import_(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallResponse>) responseObserver);
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class TestServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    TestServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.testing.compiler.Test.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("TestService");
+    }
+  }
+
+  private static final class TestServiceFileDescriptorSupplier
+      extends TestServiceBaseDescriptorSupplier {
+    TestServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class TestServiceMethodDescriptorSupplier
+      extends TestServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    TestServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (TestServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new TestServiceFileDescriptorSupplier())
+              .addMethod(getUnaryCallMethod())
+              .addMethod(getStreamingOutputCallMethod())
+              .addMethod(getStreamingInputCallMethod())
+              .addMethod(getFullBidiCallMethod())
+              .addMethod(getHalfBidiCallMethod())
+              .addMethod(getImportMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/compiler/src/test/proto/grpc/testing/compiler/test.proto b/compiler/src/test/proto/grpc/testing/compiler/test.proto
new file mode 100644
index 0000000..62cf6c8
--- /dev/null
+++ b/compiler/src/test/proto/grpc/testing/compiler/test.proto
@@ -0,0 +1,83 @@
+// Copyright 2015 The gRPC Authors
+// All rights reserved.
+//
+// Licensed 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.
+
+// A simple service definition for testing the protoc plugin.
+syntax = "proto3";
+
+package grpc.testing.compiler;
+
+option java_package = "io.grpc.testing.compiler";
+
+message SimpleRequest {
+}
+
+message SimpleResponse {
+}
+
+message StreamingInputCallRequest {
+}
+
+message StreamingInputCallResponse {
+}
+
+message StreamingOutputCallRequest {
+}
+
+message StreamingOutputCallResponse {
+}
+
+// Test service that supports all call types.
+service TestService {
+  // One request followed by one response.
+  // The server returns the client payload as-is.
+  rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
+
+  // One request followed by a sequence of responses (streamed download).
+  // The server returns the payload with client desired type and sizes.
+  rpc StreamingOutputCall(StreamingOutputCallRequest)
+      returns (stream StreamingOutputCallResponse);
+
+  // A sequence of requests followed by one response (streamed upload).
+  // The server returns the aggregated size of client payload as the result.
+  rpc StreamingInputCall(stream StreamingInputCallRequest)
+      returns (StreamingInputCallResponse);
+
+  // A sequence of requests with each request served by the server immediately.
+  // As one request could lead to multiple responses, this interface
+  // demonstrates the idea of full bidirectionality.
+  rpc FullBidiCall(stream StreamingOutputCallRequest)
+      returns (stream StreamingOutputCallResponse);
+
+  // A sequence of requests followed by a sequence of responses.
+  // The server buffers all the client requests and then serves them in order. A
+  // stream of responses are returned to the client when the server starts with
+  // first request.
+  rpc HalfBidiCall(stream StreamingOutputCallRequest)
+      returns (stream StreamingOutputCallResponse);
+
+  // An RPC method whose Java name collides with a keyword, and whose generated
+  // method should have a '_' appended.
+  rpc Import(stream StreamingInputCallRequest) returns (stream StreamingInputCallResponse);
+}
+
+// Test service that has been deprecated and should generate with Java's @Deprecated annotation
+service TestDeprecatedService {
+  option deprecated = true;
+
+  // An RPC method that has been deprecated and should generate with Java's @Deprecated annotation
+  rpc DeprecatedMethod(SimpleRequest) returns (SimpleResponse) {
+    option deprecated = true;
+  }
+}
\ No newline at end of file
diff --git a/compiler/src/testLite/golden/TestDeprecatedService.java.txt b/compiler/src/testLite/golden/TestDeprecatedService.java.txt
new file mode 100644
index 0000000..9601483
--- /dev/null
+++ b/compiler/src/testLite/golden/TestDeprecatedService.java.txt
@@ -0,0 +1,279 @@
+package io.grpc.testing.compiler;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ * <pre>
+ * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+ * </pre>
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler (version 1.16.0-SNAPSHOT)",
+    comments = "Source: grpc/testing/compiler/test.proto")
+@java.lang.Deprecated
+public final class TestDeprecatedServiceGrpc {
+
+  private TestDeprecatedServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.compiler.TestDeprecatedService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.SimpleRequest,
+      io.grpc.testing.compiler.Test.SimpleResponse> getDeprecatedMethodMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "DeprecatedMethod",
+      requestType = io.grpc.testing.compiler.Test.SimpleRequest.class,
+      responseType = io.grpc.testing.compiler.Test.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.SimpleRequest,
+      io.grpc.testing.compiler.Test.SimpleResponse> getDeprecatedMethodMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.SimpleRequest, io.grpc.testing.compiler.Test.SimpleResponse> getDeprecatedMethodMethod;
+    if ((getDeprecatedMethodMethod = TestDeprecatedServiceGrpc.getDeprecatedMethodMethod) == null) {
+      synchronized (TestDeprecatedServiceGrpc.class) {
+        if ((getDeprecatedMethodMethod = TestDeprecatedServiceGrpc.getDeprecatedMethodMethod) == null) {
+          TestDeprecatedServiceGrpc.getDeprecatedMethodMethod = getDeprecatedMethodMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.SimpleRequest, io.grpc.testing.compiler.Test.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestDeprecatedService", "DeprecatedMethod"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.SimpleResponse.getDefaultInstance()))
+                  .build();
+          }
+        }
+     }
+     return getDeprecatedMethodMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static TestDeprecatedServiceStub newStub(io.grpc.Channel channel) {
+    return new TestDeprecatedServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static TestDeprecatedServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new TestDeprecatedServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static TestDeprecatedServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new TestDeprecatedServiceFutureStub(channel);
+  }
+
+  /**
+   * <pre>
+   * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+   * </pre>
+   */
+  @java.lang.Deprecated
+  public static abstract class TestDeprecatedServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * An RPC method that has been deprecated and should generate with Java's &#64;Deprecated annotation
+     * </pre>
+     */
+    @java.lang.Deprecated
+    public void deprecatedMethod(io.grpc.testing.compiler.Test.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.SimpleResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getDeprecatedMethodMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getDeprecatedMethodMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.SimpleRequest,
+                io.grpc.testing.compiler.Test.SimpleResponse>(
+                  this, METHODID_DEPRECATED_METHOD)))
+          .build();
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+   * </pre>
+   */
+  @java.lang.Deprecated
+  public static final class TestDeprecatedServiceStub extends io.grpc.stub.AbstractStub<TestDeprecatedServiceStub> {
+    private TestDeprecatedServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestDeprecatedServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestDeprecatedServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestDeprecatedServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * An RPC method that has been deprecated and should generate with Java's &#64;Deprecated annotation
+     * </pre>
+     */
+    @java.lang.Deprecated
+    public void deprecatedMethod(io.grpc.testing.compiler.Test.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.SimpleResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getDeprecatedMethodMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+   * </pre>
+   */
+  @java.lang.Deprecated
+  public static final class TestDeprecatedServiceBlockingStub extends io.grpc.stub.AbstractStub<TestDeprecatedServiceBlockingStub> {
+    private TestDeprecatedServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestDeprecatedServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestDeprecatedServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestDeprecatedServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * An RPC method that has been deprecated and should generate with Java's &#64;Deprecated annotation
+     * </pre>
+     */
+    @java.lang.Deprecated
+    public io.grpc.testing.compiler.Test.SimpleResponse deprecatedMethod(io.grpc.testing.compiler.Test.SimpleRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getDeprecatedMethodMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+   * </pre>
+   */
+  @java.lang.Deprecated
+  public static final class TestDeprecatedServiceFutureStub extends io.grpc.stub.AbstractStub<TestDeprecatedServiceFutureStub> {
+    private TestDeprecatedServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestDeprecatedServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestDeprecatedServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestDeprecatedServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * An RPC method that has been deprecated and should generate with Java's &#64;Deprecated annotation
+     * </pre>
+     */
+    @java.lang.Deprecated
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.compiler.Test.SimpleResponse> deprecatedMethod(
+        io.grpc.testing.compiler.Test.SimpleRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getDeprecatedMethodMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_DEPRECATED_METHOD = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final TestDeprecatedServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(TestDeprecatedServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_DEPRECATED_METHOD:
+          serviceImpl.deprecatedMethod((io.grpc.testing.compiler.Test.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.SimpleResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (TestDeprecatedServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .addMethod(getDeprecatedMethodMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/compiler/src/testLite/golden/TestService.java.txt b/compiler/src/testLite/golden/TestService.java.txt
new file mode 100644
index 0000000..2adcbdd
--- /dev/null
+++ b/compiler/src/testLite/golden/TestService.java.txt
@@ -0,0 +1,623 @@
+package io.grpc.testing.compiler;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ * <pre>
+ * Test service that supports all call types.
+ * </pre>
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler (version 1.16.0-SNAPSHOT)",
+    comments = "Source: grpc/testing/compiler/test.proto")
+public final class TestServiceGrpc {
+
+  private TestServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.compiler.TestService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.SimpleRequest,
+      io.grpc.testing.compiler.Test.SimpleResponse> getUnaryCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "UnaryCall",
+      requestType = io.grpc.testing.compiler.Test.SimpleRequest.class,
+      responseType = io.grpc.testing.compiler.Test.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.SimpleRequest,
+      io.grpc.testing.compiler.Test.SimpleResponse> getUnaryCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.SimpleRequest, io.grpc.testing.compiler.Test.SimpleResponse> getUnaryCallMethod;
+    if ((getUnaryCallMethod = TestServiceGrpc.getUnaryCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getUnaryCallMethod = TestServiceGrpc.getUnaryCallMethod) == null) {
+          TestServiceGrpc.getUnaryCallMethod = getUnaryCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.SimpleRequest, io.grpc.testing.compiler.Test.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "UnaryCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.SimpleResponse.getDefaultInstance()))
+                  .build();
+          }
+        }
+     }
+     return getUnaryCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getStreamingOutputCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "StreamingOutputCall",
+      requestType = io.grpc.testing.compiler.Test.StreamingOutputCallRequest.class,
+      responseType = io.grpc.testing.compiler.Test.StreamingOutputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getStreamingOutputCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getStreamingOutputCallMethod;
+    if ((getStreamingOutputCallMethod = TestServiceGrpc.getStreamingOutputCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getStreamingOutputCallMethod = TestServiceGrpc.getStreamingOutputCallMethod) == null) {
+          TestServiceGrpc.getStreamingOutputCallMethod = getStreamingOutputCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.Test.StreamingOutputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "StreamingOutputCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingOutputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingOutputCallResponse.getDefaultInstance()))
+                  .build();
+          }
+        }
+     }
+     return getStreamingOutputCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingInputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingInputCallResponse> getStreamingInputCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "StreamingInputCall",
+      requestType = io.grpc.testing.compiler.Test.StreamingInputCallRequest.class,
+      responseType = io.grpc.testing.compiler.Test.StreamingInputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingInputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingInputCallResponse> getStreamingInputCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingInputCallRequest, io.grpc.testing.compiler.Test.StreamingInputCallResponse> getStreamingInputCallMethod;
+    if ((getStreamingInputCallMethod = TestServiceGrpc.getStreamingInputCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getStreamingInputCallMethod = TestServiceGrpc.getStreamingInputCallMethod) == null) {
+          TestServiceGrpc.getStreamingInputCallMethod = getStreamingInputCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.StreamingInputCallRequest, io.grpc.testing.compiler.Test.StreamingInputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "StreamingInputCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingInputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingInputCallResponse.getDefaultInstance()))
+                  .build();
+          }
+        }
+     }
+     return getStreamingInputCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getFullBidiCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "FullBidiCall",
+      requestType = io.grpc.testing.compiler.Test.StreamingOutputCallRequest.class,
+      responseType = io.grpc.testing.compiler.Test.StreamingOutputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getFullBidiCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getFullBidiCallMethod;
+    if ((getFullBidiCallMethod = TestServiceGrpc.getFullBidiCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getFullBidiCallMethod = TestServiceGrpc.getFullBidiCallMethod) == null) {
+          TestServiceGrpc.getFullBidiCallMethod = getFullBidiCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.Test.StreamingOutputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "FullBidiCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingOutputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingOutputCallResponse.getDefaultInstance()))
+                  .build();
+          }
+        }
+     }
+     return getFullBidiCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getHalfBidiCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "HalfBidiCall",
+      requestType = io.grpc.testing.compiler.Test.StreamingOutputCallRequest.class,
+      responseType = io.grpc.testing.compiler.Test.StreamingOutputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getHalfBidiCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.Test.StreamingOutputCallResponse> getHalfBidiCallMethod;
+    if ((getHalfBidiCallMethod = TestServiceGrpc.getHalfBidiCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getHalfBidiCallMethod = TestServiceGrpc.getHalfBidiCallMethod) == null) {
+          TestServiceGrpc.getHalfBidiCallMethod = getHalfBidiCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.Test.StreamingOutputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "HalfBidiCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingOutputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingOutputCallResponse.getDefaultInstance()))
+                  .build();
+          }
+        }
+     }
+     return getHalfBidiCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingInputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingInputCallResponse> getImportMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "Import",
+      requestType = io.grpc.testing.compiler.Test.StreamingInputCallRequest.class,
+      responseType = io.grpc.testing.compiler.Test.StreamingInputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingInputCallRequest,
+      io.grpc.testing.compiler.Test.StreamingInputCallResponse> getImportMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.Test.StreamingInputCallRequest, io.grpc.testing.compiler.Test.StreamingInputCallResponse> getImportMethod;
+    if ((getImportMethod = TestServiceGrpc.getImportMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getImportMethod = TestServiceGrpc.getImportMethod) == null) {
+          TestServiceGrpc.getImportMethod = getImportMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.Test.StreamingInputCallRequest, io.grpc.testing.compiler.Test.StreamingInputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "Import"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingInputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.lite.ProtoLiteUtils.marshaller(
+                  io.grpc.testing.compiler.Test.StreamingInputCallResponse.getDefaultInstance()))
+                  .build();
+          }
+        }
+     }
+     return getImportMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static TestServiceStub newStub(io.grpc.Channel channel) {
+    return new TestServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static TestServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new TestServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static TestServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new TestServiceFutureStub(channel);
+  }
+
+  /**
+   * <pre>
+   * Test service that supports all call types.
+   * </pre>
+   */
+  public static abstract class TestServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public void unaryCall(io.grpc.testing.compiler.Test.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.SimpleResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getUnaryCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * One request followed by a sequence of responses (streamed download).
+     * The server returns the payload with client desired type and sizes.
+     * </pre>
+     */
+    public void streamingOutputCall(io.grpc.testing.compiler.Test.StreamingOutputCallRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getStreamingOutputCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by one response (streamed upload).
+     * The server returns the aggregated size of client payload as the result.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallRequest> streamingInputCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getStreamingInputCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests with each request served by the server immediately.
+     * As one request could lead to multiple responses, this interface
+     * demonstrates the idea of full bidirectionality.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallRequest> fullBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getFullBidiCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by a sequence of responses.
+     * The server buffers all the client requests and then serves them in order. A
+     * stream of responses are returned to the client when the server starts with
+     * first request.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallRequest> halfBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getHalfBidiCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * An RPC method whose Java name collides with a keyword, and whose generated
+     * method should have a '_' appended.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallRequest> import_(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getImportMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getUnaryCallMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.SimpleRequest,
+                io.grpc.testing.compiler.Test.SimpleResponse>(
+                  this, METHODID_UNARY_CALL)))
+          .addMethod(
+            getStreamingOutputCallMethod(),
+            asyncServerStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+                io.grpc.testing.compiler.Test.StreamingOutputCallResponse>(
+                  this, METHODID_STREAMING_OUTPUT_CALL)))
+          .addMethod(
+            getStreamingInputCallMethod(),
+            asyncClientStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.StreamingInputCallRequest,
+                io.grpc.testing.compiler.Test.StreamingInputCallResponse>(
+                  this, METHODID_STREAMING_INPUT_CALL)))
+          .addMethod(
+            getFullBidiCallMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+                io.grpc.testing.compiler.Test.StreamingOutputCallResponse>(
+                  this, METHODID_FULL_BIDI_CALL)))
+          .addMethod(
+            getHalfBidiCallMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.StreamingOutputCallRequest,
+                io.grpc.testing.compiler.Test.StreamingOutputCallResponse>(
+                  this, METHODID_HALF_BIDI_CALL)))
+          .addMethod(
+            getImportMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.Test.StreamingInputCallRequest,
+                io.grpc.testing.compiler.Test.StreamingInputCallResponse>(
+                  this, METHODID_IMPORT)))
+          .build();
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that supports all call types.
+   * </pre>
+   */
+  public static final class TestServiceStub extends io.grpc.stub.AbstractStub<TestServiceStub> {
+    private TestServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public void unaryCall(io.grpc.testing.compiler.Test.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.SimpleResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getUnaryCallMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * One request followed by a sequence of responses (streamed download).
+     * The server returns the payload with client desired type and sizes.
+     * </pre>
+     */
+    public void streamingOutputCall(io.grpc.testing.compiler.Test.StreamingOutputCallRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> responseObserver) {
+      asyncServerStreamingCall(
+          getChannel().newCall(getStreamingOutputCallMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by one response (streamed upload).
+     * The server returns the aggregated size of client payload as the result.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallRequest> streamingInputCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallResponse> responseObserver) {
+      return asyncClientStreamingCall(
+          getChannel().newCall(getStreamingInputCallMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests with each request served by the server immediately.
+     * As one request could lead to multiple responses, this interface
+     * demonstrates the idea of full bidirectionality.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallRequest> fullBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getFullBidiCallMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by a sequence of responses.
+     * The server buffers all the client requests and then serves them in order. A
+     * stream of responses are returned to the client when the server starts with
+     * first request.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallRequest> halfBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getHalfBidiCallMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * An RPC method whose Java name collides with a keyword, and whose generated
+     * method should have a '_' appended.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallRequest> import_(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getImportMethod(), getCallOptions()), responseObserver);
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that supports all call types.
+   * </pre>
+   */
+  public static final class TestServiceBlockingStub extends io.grpc.stub.AbstractStub<TestServiceBlockingStub> {
+    private TestServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public io.grpc.testing.compiler.Test.SimpleResponse unaryCall(io.grpc.testing.compiler.Test.SimpleRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getUnaryCallMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * One request followed by a sequence of responses (streamed download).
+     * The server returns the payload with client desired type and sizes.
+     * </pre>
+     */
+    public java.util.Iterator<io.grpc.testing.compiler.Test.StreamingOutputCallResponse> streamingOutputCall(
+        io.grpc.testing.compiler.Test.StreamingOutputCallRequest request) {
+      return blockingServerStreamingCall(
+          getChannel(), getStreamingOutputCallMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that supports all call types.
+   * </pre>
+   */
+  public static final class TestServiceFutureStub extends io.grpc.stub.AbstractStub<TestServiceFutureStub> {
+    private TestServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.compiler.Test.SimpleResponse> unaryCall(
+        io.grpc.testing.compiler.Test.SimpleRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getUnaryCallMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_UNARY_CALL = 0;
+  private static final int METHODID_STREAMING_OUTPUT_CALL = 1;
+  private static final int METHODID_STREAMING_INPUT_CALL = 2;
+  private static final int METHODID_FULL_BIDI_CALL = 3;
+  private static final int METHODID_HALF_BIDI_CALL = 4;
+  private static final int METHODID_IMPORT = 5;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final TestServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(TestServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_UNARY_CALL:
+          serviceImpl.unaryCall((io.grpc.testing.compiler.Test.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.SimpleResponse>) responseObserver);
+          break;
+        case METHODID_STREAMING_OUTPUT_CALL:
+          serviceImpl.streamingOutputCall((io.grpc.testing.compiler.Test.StreamingOutputCallRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_STREAMING_INPUT_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.streamingInputCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallResponse>) responseObserver);
+        case METHODID_FULL_BIDI_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.fullBidiCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse>) responseObserver);
+        case METHODID_HALF_BIDI_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.halfBidiCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingOutputCallResponse>) responseObserver);
+        case METHODID_IMPORT:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.import_(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.Test.StreamingInputCallResponse>) responseObserver);
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (TestServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .addMethod(getUnaryCallMethod())
+              .addMethod(getStreamingOutputCallMethod())
+              .addMethod(getStreamingInputCallMethod())
+              .addMethod(getFullBidiCallMethod())
+              .addMethod(getHalfBidiCallMethod())
+              .addMethod(getImportMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/compiler/src/testNano/golden/TestDeprecatedService.java.txt b/compiler/src/testNano/golden/TestDeprecatedService.java.txt
new file mode 100644
index 0000000..6e5bba9
--- /dev/null
+++ b/compiler/src/testNano/golden/TestDeprecatedService.java.txt
@@ -0,0 +1,305 @@
+package io.grpc.testing.compiler.nano;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+import java.io.IOException;
+
+/**
+ * <pre>
+ * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+ * </pre>
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler (version 1.16.0-SNAPSHOT)",
+    comments = "Source: grpc/testing/compiler/test.proto")
+@java.lang.Deprecated
+public final class TestDeprecatedServiceGrpc {
+
+  private TestDeprecatedServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.compiler.TestDeprecatedService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static final int ARG_IN_METHOD_DEPRECATED_METHOD = 0;
+  private static final int ARG_OUT_METHOD_DEPRECATED_METHOD = 1;
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.SimpleRequest,
+      io.grpc.testing.compiler.nano.Test.SimpleResponse> getDeprecatedMethodMethod;
+
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.SimpleRequest,
+      io.grpc.testing.compiler.nano.Test.SimpleResponse> getDeprecatedMethodMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.SimpleRequest, io.grpc.testing.compiler.nano.Test.SimpleResponse> getDeprecatedMethodMethod;
+    if ((getDeprecatedMethodMethod = TestDeprecatedServiceGrpc.getDeprecatedMethodMethod) == null) {
+      synchronized (TestDeprecatedServiceGrpc.class) {
+        if ((getDeprecatedMethodMethod = TestDeprecatedServiceGrpc.getDeprecatedMethodMethod) == null) {
+          TestDeprecatedServiceGrpc.getDeprecatedMethodMethod = getDeprecatedMethodMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.nano.Test.SimpleRequest, io.grpc.testing.compiler.nano.Test.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestDeprecatedService", "DeprecatedMethod"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.SimpleRequest>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.SimpleRequest>(ARG_IN_METHOD_DEPRECATED_METHOD)))
+              .setResponseMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.SimpleResponse>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.SimpleResponse>(ARG_OUT_METHOD_DEPRECATED_METHOD)))
+              .build();
+        }
+      }
+    }
+    return getDeprecatedMethodMethod;
+  }
+
+  private static final class NanoFactory<T extends com.google.protobuf.nano.MessageNano>
+      implements io.grpc.protobuf.nano.MessageNanoFactory<T> {
+    private final int id;
+
+    NanoFactory(int id) {
+      this.id = id;
+    }
+
+    @java.lang.Override
+    public T newInstance() {
+      Object o;
+      switch (id) {
+      case ARG_IN_METHOD_DEPRECATED_METHOD:
+        o = new io.grpc.testing.compiler.nano.Test.SimpleRequest();
+        break;
+      case ARG_OUT_METHOD_DEPRECATED_METHOD:
+        o = new io.grpc.testing.compiler.nano.Test.SimpleResponse();
+        break;
+      default:
+        throw new AssertionError();
+      }
+      @java.lang.SuppressWarnings("unchecked")
+      T t = (T) o;
+      return t;
+    }
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static TestDeprecatedServiceStub newStub(io.grpc.Channel channel) {
+    return new TestDeprecatedServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static TestDeprecatedServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new TestDeprecatedServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static TestDeprecatedServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new TestDeprecatedServiceFutureStub(channel);
+  }
+
+  /**
+   * <pre>
+   * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+   * </pre>
+   */
+  @java.lang.Deprecated
+  public static abstract class TestDeprecatedServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * An RPC method that has been deprecated and should generate with Java's &#64;Deprecated annotation
+     * </pre>
+     */
+    @java.lang.Deprecated
+    public void deprecatedMethod(io.grpc.testing.compiler.nano.Test.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.SimpleResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getDeprecatedMethodMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getDeprecatedMethodMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.nano.Test.SimpleRequest,
+                io.grpc.testing.compiler.nano.Test.SimpleResponse>(
+                  this, METHODID_DEPRECATED_METHOD)))
+          .build();
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+   * </pre>
+   */
+  @java.lang.Deprecated
+  public static final class TestDeprecatedServiceStub extends io.grpc.stub.AbstractStub<TestDeprecatedServiceStub> {
+    private TestDeprecatedServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestDeprecatedServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestDeprecatedServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestDeprecatedServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * An RPC method that has been deprecated and should generate with Java's &#64;Deprecated annotation
+     * </pre>
+     */
+    @java.lang.Deprecated
+    public void deprecatedMethod(io.grpc.testing.compiler.nano.Test.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.SimpleResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getDeprecatedMethodMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+   * </pre>
+   */
+  @java.lang.Deprecated
+  public static final class TestDeprecatedServiceBlockingStub extends io.grpc.stub.AbstractStub<TestDeprecatedServiceBlockingStub> {
+    private TestDeprecatedServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestDeprecatedServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestDeprecatedServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestDeprecatedServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * An RPC method that has been deprecated and should generate with Java's &#64;Deprecated annotation
+     * </pre>
+     */
+    @java.lang.Deprecated
+    public io.grpc.testing.compiler.nano.Test.SimpleResponse deprecatedMethod(io.grpc.testing.compiler.nano.Test.SimpleRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getDeprecatedMethodMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that has been deprecated and should generate with Java's &#64;Deprecated annotation
+   * </pre>
+   */
+  @java.lang.Deprecated
+  public static final class TestDeprecatedServiceFutureStub extends io.grpc.stub.AbstractStub<TestDeprecatedServiceFutureStub> {
+    private TestDeprecatedServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestDeprecatedServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestDeprecatedServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestDeprecatedServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * An RPC method that has been deprecated and should generate with Java's &#64;Deprecated annotation
+     * </pre>
+     */
+    @java.lang.Deprecated
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.compiler.nano.Test.SimpleResponse> deprecatedMethod(
+        io.grpc.testing.compiler.nano.Test.SimpleRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getDeprecatedMethodMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_DEPRECATED_METHOD = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final TestDeprecatedServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(TestDeprecatedServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_DEPRECATED_METHOD:
+          serviceImpl.deprecatedMethod((io.grpc.testing.compiler.nano.Test.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.SimpleResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (TestDeprecatedServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .addMethod(getDeprecatedMethodMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/compiler/src/testNano/golden/TestService.java.txt b/compiler/src/testNano/golden/TestService.java.txt
new file mode 100644
index 0000000..35c3332
--- /dev/null
+++ b/compiler/src/testNano/golden/TestService.java.txt
@@ -0,0 +1,664 @@
+package io.grpc.testing.compiler.nano;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+import java.io.IOException;
+
+/**
+ * <pre>
+ * Test service that supports all call types.
+ * </pre>
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler (version 1.16.0-SNAPSHOT)",
+    comments = "Source: grpc/testing/compiler/test.proto")
+public final class TestServiceGrpc {
+
+  private TestServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.compiler.TestService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static final int ARG_IN_METHOD_UNARY_CALL = 0;
+  private static final int ARG_OUT_METHOD_UNARY_CALL = 1;
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.SimpleRequest,
+      io.grpc.testing.compiler.nano.Test.SimpleResponse> getUnaryCallMethod;
+
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.SimpleRequest,
+      io.grpc.testing.compiler.nano.Test.SimpleResponse> getUnaryCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.SimpleRequest, io.grpc.testing.compiler.nano.Test.SimpleResponse> getUnaryCallMethod;
+    if ((getUnaryCallMethod = TestServiceGrpc.getUnaryCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getUnaryCallMethod = TestServiceGrpc.getUnaryCallMethod) == null) {
+          TestServiceGrpc.getUnaryCallMethod = getUnaryCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.nano.Test.SimpleRequest, io.grpc.testing.compiler.nano.Test.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "UnaryCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.SimpleRequest>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.SimpleRequest>(ARG_IN_METHOD_UNARY_CALL)))
+              .setResponseMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.SimpleResponse>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.SimpleResponse>(ARG_OUT_METHOD_UNARY_CALL)))
+              .build();
+        }
+      }
+    }
+    return getUnaryCallMethod;
+  }
+
+  private static final int ARG_IN_METHOD_STREAMING_OUTPUT_CALL = 2;
+  private static final int ARG_OUT_METHOD_STREAMING_OUTPUT_CALL = 3;
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> getStreamingOutputCallMethod;
+
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> getStreamingOutputCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> getStreamingOutputCallMethod;
+    if ((getStreamingOutputCallMethod = TestServiceGrpc.getStreamingOutputCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getStreamingOutputCallMethod = TestServiceGrpc.getStreamingOutputCallMethod) == null) {
+          TestServiceGrpc.getStreamingOutputCallMethod = getStreamingOutputCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "StreamingOutputCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest>(ARG_IN_METHOD_STREAMING_OUTPUT_CALL)))
+              .setResponseMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>(ARG_OUT_METHOD_STREAMING_OUTPUT_CALL)))
+              .build();
+        }
+      }
+    }
+    return getStreamingOutputCallMethod;
+  }
+
+  private static final int ARG_IN_METHOD_STREAMING_INPUT_CALL = 4;
+  private static final int ARG_OUT_METHOD_STREAMING_INPUT_CALL = 5;
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest,
+      io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse> getStreamingInputCallMethod;
+
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest,
+      io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse> getStreamingInputCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest, io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse> getStreamingInputCallMethod;
+    if ((getStreamingInputCallMethod = TestServiceGrpc.getStreamingInputCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getStreamingInputCallMethod = TestServiceGrpc.getStreamingInputCallMethod) == null) {
+          TestServiceGrpc.getStreamingInputCallMethod = getStreamingInputCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest, io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "StreamingInputCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest>(ARG_IN_METHOD_STREAMING_INPUT_CALL)))
+              .setResponseMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse>(ARG_OUT_METHOD_STREAMING_INPUT_CALL)))
+              .build();
+        }
+      }
+    }
+    return getStreamingInputCallMethod;
+  }
+
+  private static final int ARG_IN_METHOD_FULL_BIDI_CALL = 6;
+  private static final int ARG_OUT_METHOD_FULL_BIDI_CALL = 7;
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> getFullBidiCallMethod;
+
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> getFullBidiCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> getFullBidiCallMethod;
+    if ((getFullBidiCallMethod = TestServiceGrpc.getFullBidiCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getFullBidiCallMethod = TestServiceGrpc.getFullBidiCallMethod) == null) {
+          TestServiceGrpc.getFullBidiCallMethod = getFullBidiCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "FullBidiCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest>(ARG_IN_METHOD_FULL_BIDI_CALL)))
+              .setResponseMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>(ARG_OUT_METHOD_FULL_BIDI_CALL)))
+              .build();
+        }
+      }
+    }
+    return getFullBidiCallMethod;
+  }
+
+  private static final int ARG_IN_METHOD_HALF_BIDI_CALL = 8;
+  private static final int ARG_OUT_METHOD_HALF_BIDI_CALL = 9;
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> getHalfBidiCallMethod;
+
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest,
+      io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> getHalfBidiCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> getHalfBidiCallMethod;
+    if ((getHalfBidiCallMethod = TestServiceGrpc.getHalfBidiCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getHalfBidiCallMethod = TestServiceGrpc.getHalfBidiCallMethod) == null) {
+          TestServiceGrpc.getHalfBidiCallMethod = getHalfBidiCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest, io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "HalfBidiCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest>(ARG_IN_METHOD_HALF_BIDI_CALL)))
+              .setResponseMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>(ARG_OUT_METHOD_HALF_BIDI_CALL)))
+              .build();
+        }
+      }
+    }
+    return getHalfBidiCallMethod;
+  }
+
+  private static final int ARG_IN_METHOD_IMPORT = 10;
+  private static final int ARG_OUT_METHOD_IMPORT = 11;
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest,
+      io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse> getImportMethod;
+
+  public static io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest,
+      io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse> getImportMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest, io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse> getImportMethod;
+    if ((getImportMethod = TestServiceGrpc.getImportMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getImportMethod = TestServiceGrpc.getImportMethod) == null) {
+          TestServiceGrpc.getImportMethod = getImportMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest, io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.compiler.TestService", "Import"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest>(ARG_IN_METHOD_IMPORT)))
+              .setResponseMarshaller(io.grpc.protobuf.nano.NanoUtils.<io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse>marshaller(
+                  new NanoFactory<io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse>(ARG_OUT_METHOD_IMPORT)))
+              .build();
+        }
+      }
+    }
+    return getImportMethod;
+  }
+
+  private static final class NanoFactory<T extends com.google.protobuf.nano.MessageNano>
+      implements io.grpc.protobuf.nano.MessageNanoFactory<T> {
+    private final int id;
+
+    NanoFactory(int id) {
+      this.id = id;
+    }
+
+    @java.lang.Override
+    public T newInstance() {
+      Object o;
+      switch (id) {
+      case ARG_IN_METHOD_UNARY_CALL:
+        o = new io.grpc.testing.compiler.nano.Test.SimpleRequest();
+        break;
+      case ARG_OUT_METHOD_UNARY_CALL:
+        o = new io.grpc.testing.compiler.nano.Test.SimpleResponse();
+        break;
+      case ARG_IN_METHOD_STREAMING_OUTPUT_CALL:
+        o = new io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest();
+        break;
+      case ARG_OUT_METHOD_STREAMING_OUTPUT_CALL:
+        o = new io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse();
+        break;
+      case ARG_IN_METHOD_STREAMING_INPUT_CALL:
+        o = new io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest();
+        break;
+      case ARG_OUT_METHOD_STREAMING_INPUT_CALL:
+        o = new io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse();
+        break;
+      case ARG_IN_METHOD_FULL_BIDI_CALL:
+        o = new io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest();
+        break;
+      case ARG_OUT_METHOD_FULL_BIDI_CALL:
+        o = new io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse();
+        break;
+      case ARG_IN_METHOD_HALF_BIDI_CALL:
+        o = new io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest();
+        break;
+      case ARG_OUT_METHOD_HALF_BIDI_CALL:
+        o = new io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse();
+        break;
+      case ARG_IN_METHOD_IMPORT:
+        o = new io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest();
+        break;
+      case ARG_OUT_METHOD_IMPORT:
+        o = new io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse();
+        break;
+      default:
+        throw new AssertionError();
+      }
+      @java.lang.SuppressWarnings("unchecked")
+      T t = (T) o;
+      return t;
+    }
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static TestServiceStub newStub(io.grpc.Channel channel) {
+    return new TestServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static TestServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new TestServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static TestServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new TestServiceFutureStub(channel);
+  }
+
+  /**
+   * <pre>
+   * Test service that supports all call types.
+   * </pre>
+   */
+  public static abstract class TestServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public void unaryCall(io.grpc.testing.compiler.nano.Test.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.SimpleResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getUnaryCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * One request followed by a sequence of responses (streamed download).
+     * The server returns the payload with client desired type and sizes.
+     * </pre>
+     */
+    public void streamingOutputCall(io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getStreamingOutputCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by one response (streamed upload).
+     * The server returns the aggregated size of client payload as the result.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest> streamingInputCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getStreamingInputCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests with each request served by the server immediately.
+     * As one request could lead to multiple responses, this interface
+     * demonstrates the idea of full bidirectionality.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest> fullBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getFullBidiCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by a sequence of responses.
+     * The server buffers all the client requests and then serves them in order. A
+     * stream of responses are returned to the client when the server starts with
+     * first request.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest> halfBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getHalfBidiCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * An RPC method whose Java name collides with a keyword, and whose generated
+     * method should have a '_' appended.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest> import_(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getImportMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getUnaryCallMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.nano.Test.SimpleRequest,
+                io.grpc.testing.compiler.nano.Test.SimpleResponse>(
+                  this, METHODID_UNARY_CALL)))
+          .addMethod(
+            getStreamingOutputCallMethod(),
+            asyncServerStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest,
+                io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>(
+                  this, METHODID_STREAMING_OUTPUT_CALL)))
+          .addMethod(
+            getStreamingInputCallMethod(),
+            asyncClientStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest,
+                io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse>(
+                  this, METHODID_STREAMING_INPUT_CALL)))
+          .addMethod(
+            getFullBidiCallMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest,
+                io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>(
+                  this, METHODID_FULL_BIDI_CALL)))
+          .addMethod(
+            getHalfBidiCallMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest,
+                io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>(
+                  this, METHODID_HALF_BIDI_CALL)))
+          .addMethod(
+            getImportMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest,
+                io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse>(
+                  this, METHODID_IMPORT)))
+          .build();
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that supports all call types.
+   * </pre>
+   */
+  public static final class TestServiceStub extends io.grpc.stub.AbstractStub<TestServiceStub> {
+    private TestServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public void unaryCall(io.grpc.testing.compiler.nano.Test.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.SimpleResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getUnaryCallMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * One request followed by a sequence of responses (streamed download).
+     * The server returns the payload with client desired type and sizes.
+     * </pre>
+     */
+    public void streamingOutputCall(io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> responseObserver) {
+      asyncServerStreamingCall(
+          getChannel().newCall(getStreamingOutputCallMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by one response (streamed upload).
+     * The server returns the aggregated size of client payload as the result.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest> streamingInputCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse> responseObserver) {
+      return asyncClientStreamingCall(
+          getChannel().newCall(getStreamingInputCallMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests with each request served by the server immediately.
+     * As one request could lead to multiple responses, this interface
+     * demonstrates the idea of full bidirectionality.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest> fullBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getFullBidiCallMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by a sequence of responses.
+     * The server buffers all the client requests and then serves them in order. A
+     * stream of responses are returned to the client when the server starts with
+     * first request.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest> halfBidiCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getHalfBidiCallMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * An RPC method whose Java name collides with a keyword, and whose generated
+     * method should have a '_' appended.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingInputCallRequest> import_(
+        io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getImportMethod(), getCallOptions()), responseObserver);
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that supports all call types.
+   * </pre>
+   */
+  public static final class TestServiceBlockingStub extends io.grpc.stub.AbstractStub<TestServiceBlockingStub> {
+    private TestServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public io.grpc.testing.compiler.nano.Test.SimpleResponse unaryCall(io.grpc.testing.compiler.nano.Test.SimpleRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getUnaryCallMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * One request followed by a sequence of responses (streamed download).
+     * The server returns the payload with client desired type and sizes.
+     * </pre>
+     */
+    public java.util.Iterator<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse> streamingOutputCall(
+        io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest request) {
+      return blockingServerStreamingCall(
+          getChannel(), getStreamingOutputCallMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   * <pre>
+   * Test service that supports all call types.
+   * </pre>
+   */
+  public static final class TestServiceFutureStub extends io.grpc.stub.AbstractStub<TestServiceFutureStub> {
+    private TestServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * The server returns the client payload as-is.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.compiler.nano.Test.SimpleResponse> unaryCall(
+        io.grpc.testing.compiler.nano.Test.SimpleRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getUnaryCallMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_UNARY_CALL = 0;
+  private static final int METHODID_STREAMING_OUTPUT_CALL = 1;
+  private static final int METHODID_STREAMING_INPUT_CALL = 2;
+  private static final int METHODID_FULL_BIDI_CALL = 3;
+  private static final int METHODID_HALF_BIDI_CALL = 4;
+  private static final int METHODID_IMPORT = 5;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final TestServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(TestServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_UNARY_CALL:
+          serviceImpl.unaryCall((io.grpc.testing.compiler.nano.Test.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.SimpleResponse>) responseObserver);
+          break;
+        case METHODID_STREAMING_OUTPUT_CALL:
+          serviceImpl.streamingOutputCall((io.grpc.testing.compiler.nano.Test.StreamingOutputCallRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_STREAMING_INPUT_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.streamingInputCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse>) responseObserver);
+        case METHODID_FULL_BIDI_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.fullBidiCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>) responseObserver);
+        case METHODID_HALF_BIDI_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.halfBidiCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingOutputCallResponse>) responseObserver);
+        case METHODID_IMPORT:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.import_(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.compiler.nano.Test.StreamingInputCallResponse>) responseObserver);
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (TestServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .addMethod(getUnaryCallMethod())
+              .addMethod(getStreamingOutputCallMethod())
+              .addMethod(getStreamingInputCallMethod())
+              .addMethod(getFullBidiCallMethod())
+              .addMethod(getHalfBidiCallMethod())
+              .addMethod(getImportMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/context/BUILD.bazel b/context/BUILD.bazel
new file mode 100644
index 0000000..caa80f2
--- /dev/null
+++ b/context/BUILD.bazel
@@ -0,0 +1,12 @@
+java_library(
+    name = "context",
+    srcs = glob([
+        "src/main/java/**/*.java",
+    ]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_errorprone_error_prone_annotations//jar",
+        "@com_google_guava_guava//jar",
+    ],
+)
diff --git a/context/build.gradle b/context/build.gradle
new file mode 100644
index 0000000..c38132a
--- /dev/null
+++ b/context/build.gradle
@@ -0,0 +1,7 @@
+description = 'gRPC: Context'
+
+dependencies {
+    testCompile libraries.jsr305
+    signature "org.codehaus.mojo.signature:java17:1.0@signature"
+    signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
+}
diff --git a/context/src/jmh/java/io/grpc/AttachDetachBenchmark.java b/context/src/jmh/java/io/grpc/AttachDetachBenchmark.java
new file mode 100644
index 0000000..8a9321e
--- /dev/null
+++ b/context/src/jmh/java/io/grpc/AttachDetachBenchmark.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import io.grpc.Context.Key;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.GroupThreads;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+/** StatusBenchmark. */
+@State(Scope.Benchmark)
+public class AttachDetachBenchmark {
+
+  private final Key<Integer> key = Context.keyWithDefault("key", 9999);
+  private final Context cu = Context.current().withValue(key, 8888);
+
+  /**
+   * Javadoc comment.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  @GroupThreads(6)
+  public int attachDetach() {
+    Context old = cu.attach();
+    try {
+      return key.get();
+    } finally {
+      Context.current().detach(old);
+    }
+  }
+}
diff --git a/context/src/jmh/java/io/grpc/ReadBenchmark.java b/context/src/jmh/java/io/grpc/ReadBenchmark.java
new file mode 100644
index 0000000..fa078ce
--- /dev/null
+++ b/context/src/jmh/java/io/grpc/ReadBenchmark.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+
+/** Read benchmark. */
+public class ReadBenchmark {
+
+  @State(Scope.Benchmark)
+  public static class ContextState {
+    List<Context.Key<Object>> keys = new ArrayList<Context.Key<Object>>();
+    List<Context> contexts = new ArrayList<>();
+
+    @Setup
+    public void setup() {
+      for (int i = 0; i < 8; i++) {
+        keys.add(Context.key("Key" + i));
+      }
+      contexts.add(Context.ROOT.withValue(keys.get(0), new Object()));
+      contexts.add(Context.ROOT.withValues(keys.get(0), new Object(), keys.get(1), new Object()));
+      contexts.add(
+          Context.ROOT.withValues(
+              keys.get(0), new Object(), keys.get(1), new Object(), keys.get(2), new Object()));
+      contexts.add(
+          Context.ROOT.withValues(
+              keys.get(0),
+              new Object(),
+              keys.get(1),
+              new Object(),
+              keys.get(2),
+              new Object(),
+              keys.get(3),
+              new Object()));
+      contexts.add(contexts.get(0).withValue(keys.get(1), new Object()));
+      contexts.add(
+          contexts.get(1).withValues(keys.get(2), new Object(), keys.get(3), new Object()));
+      contexts.add(
+          contexts
+              .get(2)
+              .withValues(
+                  keys.get(3), new Object(), keys.get(4), new Object(), keys.get(5), new Object()));
+      contexts.add(
+          contexts
+              .get(3)
+              .withValues(
+                  keys.get(4),
+                  new Object(),
+                  keys.get(5),
+                  new Object(),
+                  keys.get(6),
+                  new Object(),
+                  keys.get(7),
+                  new Object()));
+    }
+  }
+
+  /** Perform the read operation. */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public void testContextLookup(ContextState state, Blackhole bh) {
+    for (Context.Key<?> key : state.keys) {
+      for (Context ctx : state.contexts) {
+        bh.consume(key.get(ctx));
+      }
+    }
+  }
+}
diff --git a/context/src/jmh/java/io/grpc/WriteBenchmark.java b/context/src/jmh/java/io/grpc/WriteBenchmark.java
new file mode 100644
index 0000000..d4c2705
--- /dev/null
+++ b/context/src/jmh/java/io/grpc/WriteBenchmark.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+
+/** Write benchmark. */
+public class WriteBenchmark {
+  @State(Scope.Thread)
+  public static class ContextState {
+    @Param({"0", "10", "25", "100"})
+    public int preexistingKeys;
+
+    Context.Key<Object> key1 = Context.key("key1");
+    Context.Key<Object> key2 = Context.key("key2");
+    Context.Key<Object> key3 = Context.key("key3");
+    Context.Key<Object> key4 = Context.key("key4");
+    Context context;
+    Object val = new Object();
+
+    @Setup
+    public void setup() {
+      context = Context.ROOT;
+      for (int i = 0; i < preexistingKeys; i++) {
+        context = context.withValue(Context.key("preexisting_key" + i), val);
+      }
+    }
+  }
+
+  /** Perform the write operation. */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public Context doWrite(ContextState state, Blackhole bh) {
+    return state.context.withValues(
+        state.key1, state.val,
+        state.key2, state.val,
+        state.key3, state.val,
+        state.key4, state.val);
+  }
+}
diff --git a/context/src/main/java/io/grpc/Context.java b/context/src/main/java/io/grpc/Context.java
new file mode 100644
index 0000000..4df0e0f
--- /dev/null
+++ b/context/src/main/java/io/grpc/Context.java
@@ -0,0 +1,1073 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import io.grpc.Context.CheckReturnValue;
+import java.io.Closeable;
+import java.util.ArrayList;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A context propagation mechanism which can carry scoped-values across API boundaries and between
+ * threads. Examples of state propagated via context include:
+ * <ul>
+ *   <li>Security principals and credentials.</li>
+ *   <li>Local and distributed tracing information.</li>
+ * </ul>
+ *
+ * <p>A Context object can be {@link #attach attached} to the {@link Storage}, which effectively
+ * forms a <b>scope</b> for the context.  The scope is bound to the current thread.  Within a scope,
+ * its Context is accessible even across API boundaries, through {@link #current}.  The scope is
+ * later exited by {@link #detach detaching} the Context.
+ *
+ * <p>Context objects are immutable and inherit state from their parent. To add or overwrite the
+ * current state a new context object must be created and then attached, replacing the previously
+ * bound context. For example:
+ *
+ * <pre>
+ *   Context withCredential = Context.current().withValue(CRED_KEY, cred);
+ *   withCredential.run(new Runnable() {
+ *     public void run() {
+ *        readUserRecords(userId, CRED_KEY.get());
+ *     }
+ *   });
+ * </pre>
+ *
+ * <p>Contexts are also used to represent a scoped unit of work. When the unit of work is done the
+ * context can be cancelled. This cancellation will also cascade to all descendant contexts. You can
+ * add a {@link CancellationListener} to a context to be notified when it or one of its ancestors
+ * has been cancelled. Cancellation does not release the state stored by a context and it's
+ * perfectly valid to {@link #attach()} an already cancelled context to make it current. To cancel a
+ * context (and its descendants) you first create a {@link CancellableContext} and when you need to
+ * signal cancellation call {@link CancellableContext#cancel} or {@link
+ * CancellableContext#detachAndCancel}. For example:
+ * <pre>
+ *   CancellableContext withCancellation = Context.current().withCancellation();
+ *   try {
+ *     withCancellation.run(new Runnable() {
+ *       public void run() {
+ *         while (waitingForData() &amp;&amp; !Context.current().isCancelled()) {}
+ *       }
+ *     });
+ *     doSomeWork();
+ *   } catch (Throwable t) {
+ *      withCancellation.cancel(t);
+ *   }
+ * </pre>
+ *
+ * <p>Contexts can also be created with a timeout relative to the system nano clock which will
+ * cause it to automatically cancel at the desired time.
+ *
+ *
+ * <p>Notes and cautions on use:
+ * <ul>
+ *    <li>Every {@code attach()} should have a {@code detach()} in the same method. And every
+ * CancellableContext should be cancelled at some point. Breaking these rules may lead to memory
+ * leaks.
+ *    <li>While Context objects are immutable they do not place such a restriction on the state
+ * they store.</li>
+ *    <li>Context is not intended for passing optional parameters to an API and developers should
+ * take care to avoid excessive dependence on context when designing an API.</li>
+ *    <li>Do not mock this class.  Use {@link #ROOT} for a non-null instance.
+ * </ul>
+ */
+/* @DoNotMock("Use ROOT for a non-null Context") // commented out to avoid dependencies  */
+@CheckReturnValue
+public class Context {
+
+  private static final Logger log = Logger.getLogger(Context.class.getName());
+
+  private static final PersistentHashArrayMappedTrie<Key<?>, Object> EMPTY_ENTRIES =
+      new PersistentHashArrayMappedTrie<Key<?>, Object>();
+
+  // Long chains of contexts are suspicious and usually indicate a misuse of Context.
+  // The threshold is arbitrarily chosen.
+  // VisibleForTesting
+  static final int CONTEXT_DEPTH_WARN_THRESH = 1000;
+
+  /**
+   * The logical root context which is the ultimate ancestor of all contexts. This context
+   * is not cancellable and so will not cascade cancellation or retain listeners.
+   *
+   * <p>Never assume this is the default context for new threads, because {@link Storage} may define
+   * a default context that is different from ROOT.
+   */
+  public static final Context ROOT = new Context(null, EMPTY_ENTRIES);
+
+  // Lazy-loaded storage. Delaying storage initialization until after class initialization makes it
+  // much easier to avoid circular loading since there can still be references to Context as long as
+  // they don't depend on storage, like key() and currentContextExecutor(). It also makes it easier
+  // to handle exceptions.
+  private static final AtomicReference<Storage> storage = new AtomicReference<Storage>();
+
+  // For testing
+  static Storage storage() {
+    Storage tmp = storage.get();
+    if (tmp == null) {
+      tmp = createStorage();
+    }
+    return tmp;
+  }
+
+  private static Storage createStorage() {
+    // Note that this method may be run more than once
+    try {
+      Class<?> clazz = Class.forName("io.grpc.override.ContextStorageOverride");
+      // The override's constructor is prohibited from triggering any code that can loop back to
+      // Context
+      Storage newStorage = (Storage) clazz.getConstructor().newInstance();
+      storage.compareAndSet(null, newStorage);
+    } catch (ClassNotFoundException e) {
+      Storage newStorage = new ThreadLocalContextStorage();
+      // Must set storage before logging, since logging may call Context.current().
+      if (storage.compareAndSet(null, newStorage)) {
+        // Avoid logging if this thread lost the race, to avoid confusion
+        log.log(Level.FINE, "Storage override doesn't exist. Using default", e);
+      }
+    } catch (Exception e) {
+      throw new RuntimeException("Storage override failed to initialize", e);
+    }
+    // Re-retreive from storage since compareAndSet may have failed (returned false) in case of
+    // race.
+    return storage.get();
+  }
+
+  /**
+   * Create a {@link Key} with the given debug name. Multiple different keys may have the same name;
+   * the name is intended for debugging purposes and does not impact behavior.
+   */
+  public static <T> Key<T> key(String name) {
+    return new Key<T>(name);
+  }
+
+  /**
+   * Create a {@link Key} with the given debug name and default value. Multiple different keys may
+   * have the same name; the name is intended for debugging purposes and does not impact behavior.
+   */
+  public static <T> Key<T> keyWithDefault(String name, T defaultValue) {
+    return new Key<T>(name, defaultValue);
+  }
+
+  /**
+   * Return the context associated with the current scope, will never return {@code null}.
+   *
+   * <p>Will never return {@link CancellableContext} even if one is attached, instead a
+   * {@link Context} is returned with the same properties and lifetime. This is to avoid
+   * code stealing the ability to cancel arbitrarily.
+   */
+  public static Context current() {
+    Context current = storage().current();
+    if (current == null) {
+      return ROOT;
+    }
+    return current;
+  }
+
+  private ArrayList<ExecutableListener> listeners;
+  private CancellationListener parentListener = new ParentListener();
+  final CancellableContext cancellableAncestor;
+  final PersistentHashArrayMappedTrie<Key<?>, Object> keyValueEntries;
+  // The number parents between this context and the root context.
+  final int generation;
+
+  /**
+   * Construct a context that cannot be cancelled and will not cascade cancellation from its parent.
+   */
+  private Context(PersistentHashArrayMappedTrie<Key<?>, Object> keyValueEntries, int generation) {
+    cancellableAncestor = null;
+    this.keyValueEntries = keyValueEntries;
+    this.generation = generation;
+    validateGeneration(generation);
+  }
+
+  /**
+   * Construct a context that cannot be cancelled but will cascade cancellation from its parent if
+   * it is cancellable.
+   */
+  private Context(Context parent, PersistentHashArrayMappedTrie<Key<?>, Object> keyValueEntries) {
+    cancellableAncestor = cancellableAncestor(parent);
+    this.keyValueEntries = keyValueEntries;
+    this.generation = parent == null ? 0 : parent.generation + 1;
+    validateGeneration(generation);
+  }
+
+  /**
+   * Create a new context which is independently cancellable and also cascades cancellation from
+   * its parent. Callers <em>must</em> ensure that either {@link
+   * CancellableContext#cancel(Throwable)} or {@link CancellableContext#detachAndCancel(Context,
+   * Throwable)} are called at a later point, in order to allow this context to be garbage
+   * collected.
+   *
+   * <p>Sample usage:
+   * <pre>
+   *   Context.CancellableContext withCancellation = Context.current().withCancellation();
+   *   try {
+   *     withCancellation.run(new Runnable() {
+   *       public void run() {
+   *         Context current = Context.current();
+   *         while (!current.isCancelled()) {
+   *           keepWorking();
+   *         }
+   *       }
+   *     });
+   *   } finally {
+   *     withCancellation.cancel(null);
+   *   }
+   * </pre>
+   */
+  public CancellableContext withCancellation() {
+    return new CancellableContext(this);
+  }
+
+  /**
+   * Create a new context which will cancel itself after the given {@code duration} from now.
+   * The returned context will cascade cancellation of its parent. Callers may explicitly cancel
+   * the returned context prior to the deadline just as for {@link #withCancellation()}. If the unit
+   * of work completes before the deadline, the context should be explicitly cancelled to allow
+   * it to be garbage collected.
+   *
+   * <p>Sample usage:
+   * <pre>
+   *   Context.CancellableContext withDeadline = Context.current()
+   *       .withDeadlineAfter(5, TimeUnit.SECONDS, scheduler);
+   *   try {
+   *     withDeadline.run(new Runnable() {
+   *       public void run() {
+   *         Context current = Context.current();
+   *         while (!current.isCancelled()) {
+   *           keepWorking();
+   *         }
+   *       }
+   *     });
+   *   } finally {
+   *     withDeadline.cancel(null);
+   *   }
+   * </pre>
+   */
+  public CancellableContext withDeadlineAfter(long duration, TimeUnit unit,
+                                              ScheduledExecutorService scheduler) {
+    return withDeadline(Deadline.after(duration, unit), scheduler);
+  }
+
+  /**
+   * Create a new context which will cancel itself at the given {@link Deadline}.
+   * The returned context will cascade cancellation of its parent. Callers may explicitly cancel
+   * the returned context prior to the deadline just as for {@link #withCancellation()}. If the unit
+   * of work completes before the deadline, the context should be explicitly cancelled to allow
+   * it to be garbage collected.
+   *
+   * <p>Sample usage:
+   * <pre>
+   *   Context.CancellableContext withDeadline = Context.current()
+   *      .withDeadline(someReceivedDeadline, scheduler);
+   *   try {
+   *     withDeadline.run(new Runnable() {
+   *       public void run() {
+   *         Context current = Context.current();
+   *         while (!current.isCancelled() &amp;&amp; moreWorkToDo()) {
+   *           keepWorking();
+   *         }
+   *       }
+   *     });
+   *   } finally {
+   *     withDeadline.cancel(null);
+   *   }
+   * </pre>
+   */
+  public CancellableContext withDeadline(Deadline deadline,
+      ScheduledExecutorService scheduler) {
+    checkNotNull(deadline, "deadline");
+    checkNotNull(scheduler, "scheduler");
+    return new CancellableContext(this, deadline, scheduler);
+  }
+
+  /**
+   * Create a new context with the given key value set. The new context will cascade cancellation
+   * from its parent.
+   *
+   <pre>
+   *   Context withCredential = Context.current().withValue(CRED_KEY, cred);
+   *   withCredential.run(new Runnable() {
+   *     public void run() {
+   *        readUserRecords(userId, CRED_KEY.get());
+   *     }
+   *   });
+   * </pre>
+   *
+   */
+  public <V> Context withValue(Key<V> k1, V v1) {
+    PersistentHashArrayMappedTrie<Key<?>, Object> newKeyValueEntries = keyValueEntries.put(k1, v1);
+    return new Context(this, newKeyValueEntries);
+  }
+
+  /**
+   * Create a new context with the given key value set. The new context will cascade cancellation
+   * from its parent.
+   */
+  public <V1, V2> Context withValues(Key<V1> k1, V1 v1, Key<V2> k2, V2 v2) {
+    PersistentHashArrayMappedTrie<Key<?>, Object> newKeyValueEntries =
+        keyValueEntries.put(k1, v1).put(k2, v2);
+    return new Context(this, newKeyValueEntries);
+  }
+
+  /**
+   * Create a new context with the given key value set. The new context will cascade cancellation
+   * from its parent.
+   */
+  public <V1, V2, V3> Context withValues(Key<V1> k1, V1 v1, Key<V2> k2, V2 v2, Key<V3> k3, V3 v3) {
+    PersistentHashArrayMappedTrie<Key<?>, Object> newKeyValueEntries =
+        keyValueEntries.put(k1, v1).put(k2, v2).put(k3, v3);
+    return new Context(this, newKeyValueEntries);
+  }
+
+  /**
+   * Create a new context with the given key value set. The new context will cascade cancellation
+   * from its parent.
+   */
+  public <V1, V2, V3, V4> Context withValues(Key<V1> k1, V1 v1, Key<V2> k2, V2 v2,
+      Key<V3> k3, V3 v3, Key<V4> k4, V4 v4) {
+    PersistentHashArrayMappedTrie<Key<?>, Object> newKeyValueEntries =
+        keyValueEntries.put(k1, v1).put(k2, v2).put(k3, v3).put(k4, v4);
+    return new Context(this, newKeyValueEntries);
+  }
+
+  /**
+   * Create a new context which propagates the values of this context but does not cascade its
+   * cancellation.
+   */
+  public Context fork() {
+    return new Context(keyValueEntries, generation + 1);
+  }
+
+  boolean canBeCancelled() {
+    return cancellableAncestor != null;
+  }
+
+  /**
+   * Attach this context, thus enter a new scope within which this context is {@link #current}.  The
+   * previously current context is returned. It is allowed to attach contexts where {@link
+   * #isCancelled()} is {@code true}.
+   *
+   * <p>Instead of using {@code attach()} and {@link #detach(Context)} most use-cases are better
+   * served by using the {@link #run(Runnable)} or {@link #call(java.util.concurrent.Callable)} to
+   * execute work immediately within a context's scope. If work needs to be done in other threads it
+   * is recommended to use the 'wrap' methods or to use a propagating executor.
+   *
+   * <p>All calls to {@code attach()} should have a corresponding {@link #detach(Context)} within
+   * the same method:
+   * <pre>{@code Context previous = someContext.attach();
+   * try {
+   *   // Do work
+   * } finally {
+   *   someContext.detach(previous);
+   * }}</pre>
+   */
+  public Context attach() {
+    Context prev = storage().doAttach(this);
+    if (prev == null) {
+      return ROOT;
+    }
+    return prev;
+  }
+
+  /**
+   * Reverse an {@code attach()}, restoring the previous context and exiting the current scope.
+   *
+   * <p>This context should be the same context that was previously {@link #attach attached}.  The
+   * provided replacement should be what was returned by the same {@link #attach attach()} call.  If
+   * an {@code attach()} and a {@code detach()} meet above requirements, they match.
+   *
+   * <p>It is expected that between any pair of matching {@code attach()} and {@code detach()}, all
+   * {@code attach()}es and {@code detach()}es are called in matching pairs.  If this method finds
+   * that this context is not {@link #current current}, either you or some code in-between are not
+   * detaching correctly, and a SEVERE message will be logged but the context to attach will still
+   * be bound.  <strong>Never</strong> use {@code Context.current().detach()}, as this will
+   * compromise this error-detecting mechanism.
+   */
+  public void detach(Context toAttach) {
+    checkNotNull(toAttach, "toAttach");
+    storage().detach(this, toAttach);
+  }
+
+  // Visible for testing
+  boolean isCurrent() {
+    return current() == this;
+  }
+
+  /**
+   * Is this context cancelled.
+   */
+  public boolean isCancelled() {
+    if (cancellableAncestor == null) {
+      return false;
+    } else {
+      return cancellableAncestor.isCancelled();
+    }
+  }
+
+  /**
+   * If a context {@link #isCancelled()} then return the cause of the cancellation or
+   * {@code null} if context was cancelled without a cause. If the context is not yet cancelled
+   * will always return {@code null}.
+   *
+   * <p>The cancellation cause is provided for informational purposes only and implementations
+   * should generally assume that it has already been handled and logged properly.
+   */
+  public Throwable cancellationCause() {
+    if (cancellableAncestor == null) {
+      return null;
+    } else {
+      return cancellableAncestor.cancellationCause();
+    }
+  }
+
+  /**
+   * A context may have an associated {@link Deadline} at which it will be automatically cancelled.
+   * @return A {@link io.grpc.Deadline} or {@code null} if no deadline is set.
+   */
+  public Deadline getDeadline() {
+    if (cancellableAncestor == null) {
+      return null;
+    }
+    return cancellableAncestor.getDeadline();
+  }
+
+  /**
+   * Add a listener that will be notified when the context becomes cancelled.
+   */
+  public void addListener(final CancellationListener cancellationListener,
+                          final Executor executor) {
+    checkNotNull(cancellationListener, "cancellationListener");
+    checkNotNull(executor, "executor");
+    if (canBeCancelled()) {
+      ExecutableListener executableListener =
+          new ExecutableListener(executor, cancellationListener);
+      synchronized (this) {
+        if (isCancelled()) {
+          executableListener.deliver();
+        } else {
+          if (listeners == null) {
+            // Now that we have a listener we need to listen to our parent so
+            // we can cascade listener notification.
+            listeners = new ArrayList<>();
+            listeners.add(executableListener);
+            if (cancellableAncestor != null) {
+              cancellableAncestor.addListener(parentListener, DirectExecutor.INSTANCE);
+            }
+          } else {
+            listeners.add(executableListener);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Remove a {@link CancellationListener}.
+   */
+  public void removeListener(CancellationListener cancellationListener) {
+    if (!canBeCancelled()) {
+      return;
+    }
+    synchronized (this) {
+      if (listeners != null) {
+        for (int i = listeners.size() - 1; i >= 0; i--) {
+          if (listeners.get(i).listener == cancellationListener) {
+            listeners.remove(i);
+            // Just remove the first matching listener, given that we allow duplicate
+            // adds we should allow for duplicates after remove.
+            break;
+          }
+        }
+        // We have no listeners so no need to listen to our parent
+        if (listeners.isEmpty()) {
+          if (cancellableAncestor != null) {
+            cancellableAncestor.removeListener(parentListener);
+          }
+          listeners = null;
+        }
+      }
+    }
+  }
+
+  /**
+   * Notify all listeners that this context has been cancelled and immediately release
+   * any reference to them so that they may be garbage collected.
+   */
+  void notifyAndClearListeners() {
+    if (!canBeCancelled()) {
+      return;
+    }
+    ArrayList<ExecutableListener> tmpListeners;
+    synchronized (this) {
+      if (listeners == null) {
+        return;
+      }
+      tmpListeners = listeners;
+      listeners = null;
+    }
+    // Deliver events to non-child context listeners before we notify child contexts. We do this
+    // to cancel higher level units of work before child units. This allows for a better error
+    // handling paradigm where the higher level unit of work knows it is cancelled and so can
+    // ignore errors that bubble up as a result of cancellation of lower level units.
+    for (int i = 0; i < tmpListeners.size(); i++) {
+      if (!(tmpListeners.get(i).listener instanceof ParentListener)) {
+        tmpListeners.get(i).deliver();
+      }
+    }
+    for (int i = 0; i < tmpListeners.size(); i++) {
+      if (tmpListeners.get(i).listener instanceof ParentListener) {
+        tmpListeners.get(i).deliver();
+      }
+    }
+    if (cancellableAncestor != null) {
+      cancellableAncestor.removeListener(parentListener);
+    }
+  }
+
+  // Used in tests to ensure that listeners are defined and released when cancellation cascades.
+  // It's very important to ensure that we do not accidentally retain listeners.
+  int listenerCount() {
+    synchronized (this) {
+      return listeners == null ? 0 : listeners.size();
+    }
+  }
+
+  /**
+   * Immediately run a {@link Runnable} with this context as the {@link #current} context.
+   * @param r {@link Runnable} to run.
+   */
+  public void run(Runnable r) {
+    Context previous = attach();
+    try {
+      r.run();
+    } finally {
+      detach(previous);
+    }
+  }
+
+  /**
+   * Immediately call a {@link Callable} with this context as the {@link #current} context.
+   * @param c {@link Callable} to call.
+   * @return result of call.
+   */
+  @CanIgnoreReturnValue
+  public <V> V call(Callable<V> c) throws Exception {
+    Context previous = attach();
+    try {
+      return c.call();
+    } finally {
+      detach(previous);
+    }
+  }
+
+  /**
+   * Wrap a {@link Runnable} so that it executes with this context as the {@link #current} context.
+   */
+  public Runnable wrap(final Runnable r) {
+    return new Runnable() {
+      @Override
+      public void run() {
+        Context previous = attach();
+        try {
+          r.run();
+        } finally {
+          detach(previous);
+        }
+      }
+    };
+  }
+
+  /**
+   * Wrap a {@link Callable} so that it executes with this context as the {@link #current} context.
+   */
+  public <C> Callable<C> wrap(final Callable<C> c) {
+    return new Callable<C>() {
+      @Override
+      public C call() throws Exception {
+        Context previous = attach();
+        try {
+          return c.call();
+        } finally {
+          detach(previous);
+        }
+      }
+    };
+  }
+
+  /**
+   * Wrap an {@link Executor} so that it always executes with this context as the {@link #current}
+   * context. It is generally expected that {@link #currentContextExecutor(Executor)} would be
+   * used more commonly than this method.
+   *
+   * <p>One scenario in which this executor may be useful is when a single thread is sharding work
+   * to multiple threads.
+   *
+   * @see #currentContextExecutor(Executor)
+   */
+  public Executor fixedContextExecutor(final Executor e) {
+    class FixedContextExecutor implements Executor {
+      @Override
+      public void execute(Runnable r) {
+        e.execute(wrap(r));
+      }
+    }
+
+    return new FixedContextExecutor();
+  }
+
+  /**
+   * Create an executor that propagates the {@link #current} context when {@link Executor#execute}
+   * is called as the {@link #current} context of the {@code Runnable} scheduled. <em>Note that this
+   * is a static method.</em>
+   *
+   * @see #fixedContextExecutor(Executor)
+   */
+  public static Executor currentContextExecutor(final Executor e) {
+    class CurrentContextExecutor implements Executor {
+      @Override
+      public void execute(Runnable r) {
+        e.execute(Context.current().wrap(r));
+      }
+    }
+
+    return new CurrentContextExecutor();
+  }
+
+  /**
+   * Lookup the value for a key in the context inheritance chain.
+   */
+  private Object lookup(Key<?> key) {
+    return keyValueEntries.get(key);
+  }
+
+  /**
+   * A context which inherits cancellation from its parent but which can also be independently
+   * cancelled and which will propagate cancellation to its descendants. To avoid leaking memory,
+   * every CancellableContext must have a defined lifetime, after which it is guaranteed to be
+   * cancelled.
+   *
+   * <p>This class must be cancelled by either calling {@link #close} or {@link #cancel}.
+   * {@link #close} is equivalent to calling {@code cancel(null)}. It is safe to call the methods
+   * more than once, but only the first call will have any effect. Because it's safe to call the
+   * methods multiple times, users are encouraged to always call {@link #close} at the end of
+   * the operation, and disregard whether {@link #cancel} was already called somewhere else.
+   *
+   * <p>Blocking code can use the try-with-resources idiom:
+   * <pre>
+   * try (CancellableContext c = Context.current()
+   *     .withDeadlineAfter(100, TimeUnit.MILLISECONDS, executor)) {
+   *   Context toRestore = c.attach();
+   *   try {
+   *     // do some blocking work
+   *   } finally {
+   *     c.detach(toRestore);
+   *   }
+   * }</pre>
+   *
+   * <p>Asynchronous code will have to manually track the end of the CancellableContext's lifetime,
+   * and cancel the context at the appropriate time.
+   */
+  public static final class CancellableContext extends Context implements Closeable {
+
+    private final Deadline deadline;
+    private final Context uncancellableSurrogate;
+
+    private boolean cancelled;
+    private Throwable cancellationCause;
+    private ScheduledFuture<?> pendingDeadline;
+
+    /**
+     * Create a cancellable context that does not have a deadline.
+     */
+    private CancellableContext(Context parent) {
+      super(parent, parent.keyValueEntries);
+      deadline = parent.getDeadline();
+      // Create a surrogate that inherits from this to attach so that you cannot retrieve a
+      // cancellable context from Context.current()
+      uncancellableSurrogate = new Context(this, keyValueEntries);
+    }
+
+    /**
+     * Create a cancellable context that has a deadline.
+     */
+    private CancellableContext(Context parent, Deadline deadline,
+        ScheduledExecutorService scheduler) {
+      super(parent, parent.keyValueEntries);
+      Deadline parentDeadline = parent.getDeadline();
+      if (parentDeadline != null && parentDeadline.compareTo(deadline) <= 0) {
+        // The new deadline won't have an effect, so ignore it
+        deadline = parentDeadline;
+      } else {
+        // The new deadline has an effect
+        if (!deadline.isExpired()) {
+          // The parent deadline was after the new deadline so we need to install a listener
+          // on the new earlier deadline to trigger expiration for this context.
+          pendingDeadline = deadline.runOnExpiration(new Runnable() {
+            @Override
+            public void run() {
+              try {
+                cancel(new TimeoutException("context timed out"));
+              } catch (Throwable t) {
+                log.log(Level.SEVERE, "Cancel threw an exception, which should not happen", t);
+              }
+            }
+          }, scheduler);
+        } else {
+          // Cancel immediately if the deadline is already expired.
+          cancel(new TimeoutException("context timed out"));
+        }
+      }
+      this.deadline = deadline;
+      uncancellableSurrogate = new Context(this, keyValueEntries);
+    }
+
+
+    @Override
+    public Context attach() {
+      return uncancellableSurrogate.attach();
+    }
+
+    @Override
+    public void detach(Context toAttach) {
+      uncancellableSurrogate.detach(toAttach);
+    }
+
+    /**
+     * Returns true if the Context is the current context.
+     *
+     * @deprecated This method violates some GRPC class encapsulation and should not be used.
+     *     If you must know whether a Context is the current context, check whether it is the same
+     *     object returned by {@link Context#current()}.
+     */
+    //TODO(spencerfang): The superclass's method is package-private, so this should really match.
+    @Override
+    @Deprecated
+    public boolean isCurrent() {
+      return uncancellableSurrogate.isCurrent();
+    }
+
+    /**
+     * Cancel this context and optionally provide a cause (can be {@code null}) for the
+     * cancellation. This will trigger notification of listeners. It is safe to call this method
+     * multiple times. Only the first call will have any effect.
+     *
+     * <p>Calling {@code cancel(null)} is the same as calling {@link #close}.
+     *
+     * @return {@code true} if this context cancelled the context and notified listeners,
+     *    {@code false} if the context was already cancelled.
+     */
+    @CanIgnoreReturnValue
+    public boolean cancel(Throwable cause) {
+      boolean triggeredCancel = false;
+      synchronized (this) {
+        if (!cancelled) {
+          cancelled = true;
+          if (pendingDeadline != null) {
+            // If we have a scheduled cancellation pending attempt to cancel it.
+            pendingDeadline.cancel(false);
+            pendingDeadline = null;
+          }
+          this.cancellationCause = cause;
+          triggeredCancel = true;
+        }
+      }
+      if (triggeredCancel) {
+        notifyAndClearListeners();
+      }
+      return triggeredCancel;
+    }
+
+    /**
+     * Cancel this context and detach it as the current context.
+     *
+     * @param toAttach context to make current.
+     * @param cause of cancellation, can be {@code null}.
+     */
+    public void detachAndCancel(Context toAttach, Throwable cause) {
+      try {
+        detach(toAttach);
+      } finally {
+        cancel(cause);
+      }
+    }
+
+    @Override
+    public boolean isCancelled() {
+      synchronized (this) {
+        if (cancelled) {
+          return true;
+        }
+      }
+      // Detect cancellation of parent in the case where we have no listeners and
+      // record it.
+      if (super.isCancelled()) {
+        cancel(super.cancellationCause());
+        return true;
+      }
+      return false;
+    }
+
+    @Override
+    public Throwable cancellationCause() {
+      if (isCancelled()) {
+        return cancellationCause;
+      }
+      return null;
+    }
+
+    @Override
+    public Deadline getDeadline() {
+      return deadline;
+    }
+
+    @Override
+    boolean canBeCancelled() {
+      return true;
+    }
+
+    /**
+     * Cleans up this object by calling {@code cancel(null)}.
+     */
+    @Override
+    public void close() {
+      cancel(null);
+    }
+  }
+
+  /**
+   * A listener notified on context cancellation.
+   */
+  public interface CancellationListener {
+    /**
+     * @param context the newly cancelled context.
+     */
+    public void cancelled(Context context);
+  }
+
+  /**
+   * Key for indexing values stored in a context.
+   */
+  public static final class Key<T> {
+    private final String name;
+    private final T defaultValue;
+
+    Key(String name) {
+      this(name, null);
+    }
+
+    Key(String name, T defaultValue) {
+      this.name = checkNotNull(name, "name");
+      this.defaultValue = defaultValue;
+    }
+
+    /**
+     * Get the value from the {@link #current()} context for this key.
+     */
+    @SuppressWarnings("unchecked")
+    public T get() {
+      return get(Context.current());
+    }
+
+    /**
+     * Get the value from the specified context for this key.
+     */
+    @SuppressWarnings("unchecked")
+    public T get(Context context) {
+      T value = (T) context.lookup(this);
+      return value == null ? defaultValue : value;
+    }
+
+    @Override
+    public String toString() {
+      return name;
+    }
+  }
+
+  /**
+   * Defines the mechanisms for attaching and detaching the "current" context. The constructor for
+   * extending classes <em>must not</em> trigger any activity that can use Context, which includes
+   * logging, otherwise it can trigger an infinite initialization loop. Extending classes must not
+   * assume that only one instance will be created; Context guarantees it will only use one
+   * instance, but it may create multiple and then throw away all but one.
+   *
+   * <p>The default implementation will put the current context in a {@link ThreadLocal}.  If an
+   * alternative implementation named {@code io.grpc.override.ContextStorageOverride} exists in the
+   * classpath, it will be used instead of the default implementation.
+   *
+   * <p>This API is <a href="https://github.com/grpc/grpc-java/issues/2462">experimental</a> and
+   * subject to change.
+   */
+  public abstract static class Storage {
+    /**
+     * @deprecated This is an old API that is no longer used.
+     */
+    @Deprecated
+    public void attach(Context toAttach) {
+      throw new UnsupportedOperationException("Deprecated. Do not call.");
+    }
+
+    /**
+     * Implements {@link io.grpc.Context#attach}.
+     *
+     * <p>Caution: {@link Context#attach()} interprets a return value of {@code null} to mean
+     * the same thing as {@link Context#ROOT}.
+     *
+     * <p>See also: {@link #current()}.
+
+     * @param toAttach the context to be attached
+     * @return A {@link Context} that should be passed back into {@link #detach(Context, Context)}
+     *        as the {@code toRestore} parameter. {@code null} is a valid return value, but see
+     *        caution note.
+     */
+    public Context doAttach(Context toAttach) {
+      // This is a default implementation to help migrate existing Storage implementations that
+      // have an attach() method but no doAttach() method.
+      Context current = current();
+      attach(toAttach);
+      return current;
+    }
+
+    /**
+     * Implements {@link io.grpc.Context#detach}
+     *
+     * @param toDetach the context to be detached. Should be, or be equivalent to, the current
+     *        context of the current scope
+     * @param toRestore the context to be the current.  Should be, or be equivalent to, the context
+     *        of the outer scope
+     */
+    public abstract void detach(Context toDetach, Context toRestore);
+
+    /**
+     * Implements {@link io.grpc.Context#current}.
+     *
+     * <p>Caution: {@link Context} interprets a return value of {@code null} to mean the same
+     * thing as {@code Context{@link #ROOT}}.
+     *
+     * <p>See also {@link #doAttach(Context)}.
+     *
+     * @return The context of the current scope. {@code null} is a valid return value, but see
+     *        caution note.
+     */
+    public abstract Context current();
+  }
+
+  /**
+   * Stores listener and executor pair.
+   */
+  private class ExecutableListener implements Runnable {
+    private final Executor executor;
+    private final CancellationListener listener;
+
+    private ExecutableListener(Executor executor, CancellationListener listener) {
+      this.executor = executor;
+      this.listener = listener;
+    }
+
+    private void deliver() {
+      try {
+        executor.execute(this);
+      } catch (Throwable t) {
+        log.log(Level.INFO, "Exception notifying context listener", t);
+      }
+    }
+
+    @Override
+    public void run() {
+      listener.cancelled(Context.this);
+    }
+  }
+
+  private class ParentListener implements CancellationListener {
+    @Override
+    public void cancelled(Context context) {
+      if (Context.this instanceof CancellableContext) {
+        // Record cancellation with its cancellationCause.
+        ((CancellableContext) Context.this).cancel(context.cancellationCause());
+      } else {
+        notifyAndClearListeners();
+      }
+    }
+  }
+
+  @CanIgnoreReturnValue
+  private static <T> T checkNotNull(T reference, Object errorMessage) {
+    if (reference == null) {
+      throw new NullPointerException(String.valueOf(errorMessage));
+    }
+    return reference;
+  }
+
+  private enum DirectExecutor implements Executor {
+    INSTANCE;
+
+    @Override
+    public void execute(Runnable command) {
+      command.run();
+    }
+
+    @Override
+    public String toString() {
+      return "Context.DirectExecutor";
+    }
+  }
+
+  /**
+   * Returns {@code parent} if it is a {@link CancellableContext}, otherwise returns the parent's
+   * {@link #cancellableAncestor}.
+   */
+  static CancellableContext cancellableAncestor(Context parent) {
+    if (parent == null) {
+      return null;
+    }
+    if (parent instanceof CancellableContext) {
+      return (CancellableContext) parent;
+    }
+    // The parent simply cascades cancellations.
+    // Bypass the parent and reference the ancestor directly (may be null).
+    return parent.cancellableAncestor;
+  }
+
+  /**
+   * If the ancestry chain length is unreasonably long, then print an error to the log and record
+   * the stack trace.
+   */
+  private static void validateGeneration(int generation) {
+    if (generation == CONTEXT_DEPTH_WARN_THRESH) {
+      log.log(
+          Level.SEVERE,
+          "Context ancestry chain length is abnormally long. "
+              + "This suggests an error in application code. "
+              + "Length exceeded: " + CONTEXT_DEPTH_WARN_THRESH,
+          new Exception());
+    }
+  }
+
+  // Not using the standard com.google.errorprone.annotations.CheckReturnValue because that will
+  // introduce dependencies that some io.grpc.Context API consumers may not want.
+  @interface CheckReturnValue {}
+
+  @interface CanIgnoreReturnValue {}
+}
diff --git a/context/src/main/java/io/grpc/Deadline.java b/context/src/main/java/io/grpc/Deadline.java
new file mode 100644
index 0000000..1152ee3
--- /dev/null
+++ b/context/src/main/java/io/grpc/Deadline.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An absolute point in time, generally for tracking when a task should be completed. A deadline is
+ * immutable except for the passage of time causing it to expire.
+ *
+ * <p>Many systems use timeouts, which are relative to the start of the operation. However, being
+ * relative causes them to be poorly suited for managing higher-level tasks where there are many
+ * components and sub-operations that may not know the time of the initial "start of the operation."
+ * However, a timeout can be converted to a {@code Deadline} at the start of the operation and then
+ * passed to the various components unambiguously.
+ */
+public final class Deadline implements Comparable<Deadline> {
+  private static final SystemTicker SYSTEM_TICKER = new SystemTicker();
+  // nanoTime has a range of just under 300 years. Only allow up to 100 years in the past or future
+  // to prevent wraparound as long as process runs for less than ~100 years.
+  private static final long MAX_OFFSET = TimeUnit.DAYS.toNanos(100 * 365);
+  private static final long MIN_OFFSET = -MAX_OFFSET;
+
+  /**
+   * Create a deadline that will expire at the specified offset from the current system clock.
+   * @param duration A non-negative duration.
+   * @param units The time unit for the duration.
+   * @return A new deadline.
+   */
+  public static Deadline after(long duration, TimeUnit units) {
+    return after(duration, units, SYSTEM_TICKER);
+  }
+
+  // For testing
+  static Deadline after(long duration, TimeUnit units, Ticker ticker) {
+    checkNotNull(units, "units");
+    return new Deadline(ticker, units.toNanos(duration), true);
+  }
+
+  private final Ticker ticker;
+  private final long deadlineNanos;
+  private volatile boolean expired;
+
+  private Deadline(Ticker ticker, long offset, boolean baseInstantAlreadyExpired) {
+    this(ticker, ticker.read(), offset, baseInstantAlreadyExpired);
+  }
+
+  private Deadline(Ticker ticker, long baseInstant, long offset,
+      boolean baseInstantAlreadyExpired) {
+    this.ticker = ticker;
+    // Clamp to range [MIN_OFFSET, MAX_OFFSET]
+    offset = Math.min(MAX_OFFSET, Math.max(MIN_OFFSET, offset));
+    deadlineNanos = baseInstant + offset;
+    expired = baseInstantAlreadyExpired && offset <= 0;
+  }
+
+  /**
+   * Has this deadline expired
+   * @return {@code true} if it has, otherwise {@code false}.
+   */
+  public boolean isExpired() {
+    if (!expired) {
+      if (deadlineNanos - ticker.read() <= 0) {
+        expired = true;
+      } else {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Is {@code this} deadline before another.
+   */
+  public boolean isBefore(Deadline other) {
+    return this.deadlineNanos - other.deadlineNanos < 0;
+  }
+
+  /**
+   * Return the minimum deadline of {@code this} or an other deadline.
+   * @param other deadline to compare with {@code this}.
+   */
+  public Deadline minimum(Deadline other) {
+    return isBefore(other) ? this : other;
+  }
+
+  /**
+   * Create a new deadline that is offset from {@code this}.
+   */
+  // TODO(ejona): This method can cause deadlines to grow too far apart. For example:
+  // Deadline.after(100 * 365, DAYS).offset(100 * 365, DAYS) would be less than
+  // Deadline.after(-100 * 365, DAYS)
+  public Deadline offset(long offset, TimeUnit units) {
+    // May already be expired
+    if (offset == 0) {
+      return this;
+    }
+    return new Deadline(ticker, deadlineNanos, units.toNanos(offset), isExpired());
+  }
+
+  /**
+   * How much time is remaining in the specified time unit. Internal units are maintained as
+   * nanoseconds and conversions are subject to the constraints documented for
+   * {@link TimeUnit#convert}. If there is no time remaining, the returned duration is how
+   * long ago the deadline expired.
+   */
+  public long timeRemaining(TimeUnit unit) {
+    final long nowNanos = ticker.read();
+    if (!expired && deadlineNanos - nowNanos <= 0) {
+      expired = true;
+    }
+    return unit.convert(deadlineNanos - nowNanos, TimeUnit.NANOSECONDS);
+  }
+
+  /**
+   * Schedule a task to be run when the deadline expires.
+   * @param task to run on expiration
+   * @param scheduler used to execute the task
+   * @return {@link ScheduledFuture} which can be used to cancel execution of the task
+   */
+  public ScheduledFuture<?> runOnExpiration(Runnable task, ScheduledExecutorService scheduler) {
+    checkNotNull(task, "task");
+    checkNotNull(scheduler, "scheduler");
+    return scheduler.schedule(task, deadlineNanos - ticker.read(), TimeUnit.NANOSECONDS);
+  }
+
+  @Override
+  public String toString() {
+    return timeRemaining(TimeUnit.NANOSECONDS) + " ns from now";
+  }
+
+  @Override
+  public int compareTo(Deadline that) {
+    long diff = this.deadlineNanos - that.deadlineNanos;
+    if (diff < 0) {
+      return -1;
+    } else if (diff > 0) {
+      return 1;
+    }
+    return 0;
+  }
+
+  /** Time source representing nanoseconds since fixed but arbitrary point in time. */
+  abstract static class Ticker {
+    /** Returns the number of nanoseconds since this source's epoch. */
+    public abstract long read();
+  }
+
+  private static class SystemTicker extends Ticker {
+    @Override
+    public long read() {
+      return System.nanoTime();
+    }
+  }
+
+  private static <T> T checkNotNull(T reference, Object errorMessage) {
+    if (reference == null) {
+      throw new NullPointerException(String.valueOf(errorMessage));
+    }
+    return reference;
+  }
+}
diff --git a/context/src/main/java/io/grpc/PersistentHashArrayMappedTrie.java b/context/src/main/java/io/grpc/PersistentHashArrayMappedTrie.java
new file mode 100644
index 0000000..8c5b053
--- /dev/null
+++ b/context/src/main/java/io/grpc/PersistentHashArrayMappedTrie.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.util.Arrays;
+
+/**
+ * A persistent (copy-on-write) hash tree/trie. Collisions are handled
+ * linearly. Delete is not supported, but replacement is. The implementation
+ * favors simplicity and low memory allocation during insertion. Although the
+ * asymptotics are good, it is optimized for small sizes like less than 20;
+ * "unbelievably large" would be 100.
+ *
+ * <p>Inspired by popcnt-based compression seen in Ideal Hash Trees, Phil
+ * Bagwell (2000). The rest of the implementation is ignorant of/ignores the
+ * paper.
+ */
+final class PersistentHashArrayMappedTrie<K,V> {
+  private final Node<K,V> root;
+
+  PersistentHashArrayMappedTrie() {
+    this(null);
+  }
+
+  private PersistentHashArrayMappedTrie(Node<K,V> root) {
+    this.root = root;
+  }
+
+  public int size() {
+    if (root == null) {
+      return 0;
+    }
+    return root.size();
+  }
+
+  /**
+   * Returns the value with the specified key, or {@code null} if it does not exist.
+   */
+  public V get(K key) {
+    if (root == null) {
+      return null;
+    }
+    return root.get(key, key.hashCode(), 0);
+  }
+
+  /**
+   * Returns a new trie where the key is set to the specified value.
+   */
+  public PersistentHashArrayMappedTrie<K,V> put(K key, V value) {
+    if (root == null) {
+      return new PersistentHashArrayMappedTrie<K,V>(new Leaf<K,V>(key, value));
+    } else {
+      return new PersistentHashArrayMappedTrie<K,V>(root.put(key, value, key.hashCode(), 0));
+    }
+  }
+
+  // Not actually annotated to avoid depending on guava
+  // @VisibleForTesting
+  static final class Leaf<K,V> implements Node<K,V> {
+    private final K key;
+    private final V value;
+
+    public Leaf(K key, V value) {
+      this.key = key;
+      this.value = value;
+    }
+
+    @Override
+    public int size() {
+      return 1;
+    }
+
+    @Override
+    public V get(K key, int hash, int bitsConsumed) {
+      if (this.key == key) {
+        return value;
+      } else {
+        return null;
+      }
+    }
+
+    @Override
+    public Node<K,V> put(K key, V value, int hash, int bitsConsumed) {
+      int thisHash = this.key.hashCode();
+      if (thisHash != hash) {
+        // Insert
+        return CompressedIndex.combine(
+            new Leaf<K,V>(key, value), hash, this, thisHash, bitsConsumed);
+      } else if (this.key == key) {
+        // Replace
+        return new Leaf<K,V>(key, value);
+      } else {
+        // Hash collision
+        return new CollisionLeaf<K,V>(this.key, this.value, key, value);
+      }
+    }
+
+    @Override
+    public String toString() {
+      return String.format("Leaf(key=%s value=%s)", key, value);
+    }
+  }
+
+  // Not actually annotated to avoid depending on guava
+  // @VisibleForTesting
+  static final class CollisionLeaf<K,V> implements Node<K,V> {
+    // All keys must have same hash, but not have the same reference
+    private final K[] keys;
+    private final V[] values;
+
+    // Not actually annotated to avoid depending on guava
+    // @VisibleForTesting
+    @SuppressWarnings("unchecked")
+    CollisionLeaf(K key1, V value1, K key2, V value2) {
+      this((K[]) new Object[] {key1, key2}, (V[]) new Object[] {value1, value2});
+      assert key1 != key2;
+      assert key1.hashCode() == key2.hashCode();
+    }
+
+    private CollisionLeaf(K[] keys, V[] values) {
+      this.keys = keys;
+      this.values = values;
+    }
+
+    @Override
+    public int size() {
+      return values.length;
+    }
+
+    @Override
+    public V get(K key, int hash, int bitsConsumed) {
+      for (int i = 0; i < keys.length; i++) {
+        if (keys[i] == key) {
+          return values[i];
+        }
+      }
+      return null;
+    }
+
+    @Override
+    public Node<K,V> put(K key, V value, int hash, int bitsConsumed) {
+      int thisHash = keys[0].hashCode();
+      int keyIndex;
+      if (thisHash != hash) {
+        // Insert
+        return CompressedIndex.combine(
+            new Leaf<K,V>(key, value), hash, this, thisHash, bitsConsumed);
+      } else if ((keyIndex = indexOfKey(key)) != -1) {
+        // Replace
+        K[] newKeys = Arrays.copyOf(keys, keys.length);
+        V[] newValues = Arrays.copyOf(values, keys.length);
+        newKeys[keyIndex] = key;
+        newValues[keyIndex] = value;
+        return new CollisionLeaf<K,V>(newKeys, newValues);
+      } else {
+        // Yet another hash collision
+        K[] newKeys = Arrays.copyOf(keys, keys.length + 1);
+        V[] newValues = Arrays.copyOf(values, keys.length + 1);
+        newKeys[keys.length] = key;
+        newValues[keys.length] = value;
+        return new CollisionLeaf<K,V>(newKeys, newValues);
+      }
+    }
+
+    // -1 if not found
+    private int indexOfKey(K key) {
+      for (int i = 0; i < keys.length; i++) {
+        if (keys[i] == key) {
+          return i;
+        }
+      }
+      return -1;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder valuesSb = new StringBuilder();
+      valuesSb.append("CollisionLeaf(");
+      for (int i = 0; i < values.length; i++) {
+        valuesSb.append("(key=").append(keys[i]).append(" value=").append(values[i]).append(") ");
+      }
+      return valuesSb.append(")").toString();
+    }
+  }
+
+  // Not actually annotated to avoid depending on guava
+  // @VisibleForTesting
+  static final class CompressedIndex<K,V> implements Node<K,V> {
+    private static final int BITS = 5;
+    private static final int BITS_MASK = 0x1F;
+
+    final int bitmap;
+    final Node<K,V>[] values;
+    private final int size;
+
+    private CompressedIndex(int bitmap, Node<K,V>[] values, int size) {
+      this.bitmap = bitmap;
+      this.values = values;
+      this.size = size;
+    }
+
+    @Override
+    public int size() {
+      return size;
+    }
+
+    @Override
+    public V get(K key, int hash, int bitsConsumed) {
+      int indexBit = indexBit(hash, bitsConsumed);
+      if ((bitmap & indexBit) == 0) {
+        return null;
+      }
+      int compressedIndex = compressedIndex(indexBit);
+      return values[compressedIndex].get(key, hash, bitsConsumed + BITS);
+    }
+
+    @Override
+    public Node<K,V> put(K key, V value, int hash, int bitsConsumed) {
+      int indexBit = indexBit(hash, bitsConsumed);
+      int compressedIndex = compressedIndex(indexBit);
+      if ((bitmap & indexBit) == 0) {
+        // Insert
+        int newBitmap = bitmap | indexBit;
+        @SuppressWarnings("unchecked")
+        Node<K,V>[] newValues = (Node<K,V>[]) new Node<?,?>[values.length + 1];
+        System.arraycopy(values, 0, newValues, 0, compressedIndex);
+        newValues[compressedIndex] = new Leaf<K,V>(key, value);
+        System.arraycopy(
+            values,
+            compressedIndex,
+            newValues,
+            compressedIndex + 1,
+            values.length - compressedIndex);
+        return new CompressedIndex<K,V>(newBitmap, newValues, size() + 1);
+      } else {
+        // Replace
+        Node<K,V>[] newValues = Arrays.copyOf(values, values.length);
+        newValues[compressedIndex] =
+            values[compressedIndex].put(key, value, hash, bitsConsumed + BITS);
+        int newSize = size();
+        newSize += newValues[compressedIndex].size();
+        newSize -= values[compressedIndex].size();
+        return new CompressedIndex<K,V>(bitmap, newValues, newSize);
+      }
+    }
+
+    static <K,V> Node<K,V> combine(
+        Node<K,V> node1, int hash1, Node<K,V> node2, int hash2, int bitsConsumed) {
+      assert hash1 != hash2;
+      int indexBit1 = indexBit(hash1, bitsConsumed);
+      int indexBit2 = indexBit(hash2, bitsConsumed);
+      if (indexBit1 == indexBit2) {
+        Node<K,V> node = combine(node1, hash1, node2, hash2, bitsConsumed + BITS);
+        @SuppressWarnings("unchecked")
+        Node<K,V>[] values = (Node<K,V>[]) new Node<?,?>[] {node};
+        return new CompressedIndex<K,V>(indexBit1, values, node.size());
+      } else {
+        // Make node1 the smallest
+        if (uncompressedIndex(hash1, bitsConsumed) > uncompressedIndex(hash2, bitsConsumed)) {
+          Node<K,V> nodeCopy = node1;
+          node1 = node2;
+          node2 = nodeCopy;
+        }
+        @SuppressWarnings("unchecked")
+        Node<K,V>[] values = (Node<K,V>[]) new Node<?,?>[] {node1, node2};
+        return new CompressedIndex<K,V>(indexBit1 | indexBit2, values, node1.size() + node2.size());
+      }
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder valuesSb = new StringBuilder();
+      valuesSb.append("CompressedIndex(")
+          .append(String.format("bitmap=%s ", Integer.toBinaryString(bitmap)));
+      for (Node<K, V> value : values) {
+        valuesSb.append(value).append(" ");
+      }
+      return valuesSb.append(")").toString();
+    }
+
+    private int compressedIndex(int indexBit) {
+      return Integer.bitCount(bitmap & (indexBit - 1));
+    }
+
+    private static int uncompressedIndex(int hash, int bitsConsumed) {
+      return (hash >>> bitsConsumed) & BITS_MASK;
+    }
+
+    private static int indexBit(int hash, int bitsConsumed) {
+      int uncompressedIndex = uncompressedIndex(hash, bitsConsumed);
+      return 1 << uncompressedIndex;
+    }
+  }
+
+  interface Node<K,V> {
+    V get(K key, int hash, int bitsConsumed);
+
+    Node<K,V> put(K key, V value, int hash, int bitsConsumed);
+
+    int size();
+  }
+}
diff --git a/context/src/main/java/io/grpc/ThreadLocalContextStorage.java b/context/src/main/java/io/grpc/ThreadLocalContextStorage.java
new file mode 100644
index 0000000..ab734e2
--- /dev/null
+++ b/context/src/main/java/io/grpc/ThreadLocalContextStorage.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A {@link ThreadLocal}-based context storage implementation.  Used by default.
+ */
+final class ThreadLocalContextStorage extends Context.Storage {
+  private static final Logger log = Logger.getLogger(ThreadLocalContextStorage.class.getName());
+
+  /**
+   * Currently bound context.
+   */
+  private static final ThreadLocal<Context> localContext = new ThreadLocal<Context>();
+
+  @Override
+  public Context doAttach(Context toAttach) {
+    Context current = current();
+    localContext.set(toAttach);
+    return current;
+  }
+
+  @Override
+  public void detach(Context toDetach, Context toRestore) {
+    if (current() != toDetach) {
+      // Log a severe message instead of throwing an exception as the context to attach is assumed
+      // to be the correct one and the unbalanced state represents a coding mistake in a lower
+      // layer in the stack that cannot be recovered from here.
+      log.log(Level.SEVERE, "Context was not attached when detaching",
+          new Throwable().fillInStackTrace());
+    }
+    doAttach(toRestore);
+  }
+
+  @Override
+  public Context current() {
+    return localContext.get();
+  }
+}
diff --git a/context/src/test/java/io/grpc/ContextTest.java b/context/src/test/java/io/grpc/ContextTest.java
new file mode 100644
index 0000000..c0b8f96
--- /dev/null
+++ b/context/src/test/java/io/grpc/ContextTest.java
@@ -0,0 +1,1008 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static io.grpc.Context.cancellableAncestor;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link Context}.
+ */
+@RunWith(JUnit4.class)
+@SuppressWarnings("CheckReturnValue") // false-positive in test for current ver errorprone plugin
+public class ContextTest {
+
+  private static final Context.Key<String> PET = Context.key("pet");
+  private static final Context.Key<String> FOOD = Context.keyWithDefault("food", "lasagna");
+  private static final Context.Key<String> COLOR = Context.key("color");
+  private static final Context.Key<Object> FAVORITE = Context.key("favorite");
+  private static final Context.Key<Integer> LUCKY = Context.key("lucky");
+
+  private Context listenerNotifedContext;
+  private CountDownLatch deadlineLatch = new CountDownLatch(1);
+  private Context.CancellationListener cancellationListener = new Context.CancellationListener() {
+    @Override
+    public void cancelled(Context context) {
+      listenerNotifedContext = context;
+      deadlineLatch.countDown();
+    }
+  };
+
+  private Context observed;
+  private Runnable runner = new Runnable() {
+    @Override
+    public void run() {
+      observed = Context.current();
+    }
+  };
+  private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
+
+  @Before
+  public void setUp() throws Exception {
+    Context.ROOT.attach();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    scheduler.shutdown();
+    assertEquals(Context.ROOT, Context.current());
+  }
+
+  @Test
+  public void defaultContext() throws Exception {
+    final SettableFuture<Context> contextOfNewThread = SettableFuture.create();
+    Context contextOfThisThread = Context.ROOT.withValue(PET, "dog");
+    Context toRestore = contextOfThisThread.attach();
+    new Thread(new Runnable() {
+      @Override
+      public void run() {
+        contextOfNewThread.set(Context.current());
+      }
+      }).start();
+    assertNotNull(contextOfNewThread.get(5, TimeUnit.SECONDS));
+    assertNotSame(contextOfThisThread, contextOfNewThread.get());
+    assertSame(contextOfThisThread, Context.current());
+    contextOfThisThread.detach(toRestore);
+  }
+
+  @Test
+  public void rootCanBeAttached() {
+    Context fork = Context.ROOT.fork();
+    Context toRestore1 = fork.attach();
+    Context toRestore2 = Context.ROOT.attach();
+    assertTrue(Context.ROOT.isCurrent());
+
+    Context toRestore3 = fork.attach();
+    assertTrue(fork.isCurrent());
+
+    fork.detach(toRestore3);
+    Context.ROOT.detach(toRestore2);
+    fork.detach(toRestore1);
+  }
+
+  @Test
+  public void rootCanNeverHaveAListener() {
+    Context root = Context.current();
+    root.addListener(cancellationListener, MoreExecutors.directExecutor());
+    assertEquals(0, root.listenerCount());
+  }
+
+  @Test
+  public void rootIsNotCancelled() {
+    assertFalse(Context.ROOT.isCancelled());
+    assertNull(Context.ROOT.cancellationCause());
+  }
+
+  @Test
+  public void attachedCancellableContextCannotBeCastFromCurrent() {
+    Context initial = Context.current();
+    Context.CancellableContext base = initial.withCancellation();
+    base.attach();
+    assertFalse(Context.current() instanceof Context.CancellableContext);
+    assertNotSame(base, Context.current());
+    assertNotSame(initial, Context.current());
+    base.detachAndCancel(initial, null);
+    assertSame(initial, Context.current());
+  }
+
+  @Test
+  public void attachingNonCurrentReturnsCurrent() {
+    Context initial = Context.current();
+    Context base = initial.withValue(PET, "dog");
+    assertSame(initial, base.attach());
+    assertSame(base, initial.attach());
+  }
+
+  @Test
+  public void detachingNonCurrentLogsSevereMessage() {
+    final AtomicReference<LogRecord> logRef = new AtomicReference<LogRecord>();
+    Handler handler = new Handler() {
+      @Override
+      public void publish(LogRecord record) {
+        logRef.set(record);
+      }
+
+      @Override
+      public void flush() {
+      }
+
+      @Override
+      public void close() throws SecurityException {
+      }
+    };
+    Logger logger = Logger.getLogger(Context.storage().getClass().getName());
+    try {
+      logger.addHandler(handler);
+      Context initial = Context.current();
+      Context base = initial.withValue(PET, "dog");
+      // Base is not attached
+      base.detach(initial);
+      assertSame(initial, Context.current());
+      assertNotNull(logRef.get());
+      assertEquals(Level.SEVERE, logRef.get().getLevel());
+    } finally {
+      logger.removeHandler(handler);
+    }
+  }
+
+  @Test
+  public void valuesAndOverrides() {
+    Context base = Context.current().withValue(PET, "dog");
+    Context child = base.withValues(PET, null, FOOD, "cheese");
+
+    base.attach();
+
+    assertEquals("dog", PET.get());
+    assertEquals("lasagna", FOOD.get());
+    assertNull(COLOR.get());
+
+    child.attach();
+
+    assertNull(PET.get());
+    assertEquals("cheese", FOOD.get());
+    assertNull(COLOR.get());
+
+    child.detach(base);
+
+    // Should have values from base
+    assertEquals("dog", PET.get());
+    assertEquals("lasagna", FOOD.get());
+    assertNull(COLOR.get());
+
+    base.detach(Context.ROOT);
+
+    assertNull(PET.get());
+    assertEquals("lasagna", FOOD.get());
+    assertNull(COLOR.get());
+  }
+
+  @Test
+  public void withValuesThree() {
+    Object fav = new Object();
+    Context base = Context.current().withValues(PET, "dog", COLOR, "blue");
+    Context child = base.withValues(PET, "cat", FOOD, "cheese", FAVORITE, fav);
+
+    Context toRestore = child.attach();
+
+    assertEquals("cat", PET.get());
+    assertEquals("cheese", FOOD.get());
+    assertEquals("blue", COLOR.get());
+    assertEquals(fav, FAVORITE.get());
+
+    child.detach(toRestore);
+  }
+
+  @Test
+  public void withValuesFour() {
+    Object fav = new Object();
+    Context base = Context.current().withValues(PET, "dog", COLOR, "blue");
+    Context child = base.withValues(PET, "cat", FOOD, "cheese", FAVORITE, fav, LUCKY, 7);
+
+    Context toRestore = child.attach();
+
+    assertEquals("cat", PET.get());
+    assertEquals("cheese", FOOD.get());
+    assertEquals("blue", COLOR.get());
+    assertEquals(fav, FAVORITE.get());
+    assertEquals(7, (int) LUCKY.get());
+
+    child.detach(toRestore);
+  }
+
+  @Test
+  public void cancelReturnsFalseIfAlreadyCancelled() {
+    Context.CancellableContext base = Context.current().withCancellation();
+    assertTrue(base.cancel(null));
+    assertTrue(base.isCancelled());
+    assertFalse(base.cancel(null));
+  }
+
+  @Test
+  public void notifyListenersOnCancel() {
+    class SetContextCancellationListener implements Context.CancellationListener {
+      private final AtomicReference<Context> observed;
+
+      public SetContextCancellationListener(AtomicReference<Context> observed) {
+        this.observed = observed;
+      }
+
+      @Override
+      public void cancelled(Context context) {
+        observed.set(context);
+      }
+    }
+
+    Context.CancellableContext base = Context.current().withCancellation();
+    final AtomicReference<Context> observed1 = new AtomicReference<Context>();
+    base.addListener(new SetContextCancellationListener(observed1), MoreExecutors.directExecutor());
+    final AtomicReference<Context> observed2 = new AtomicReference<Context>();
+    base.addListener(new SetContextCancellationListener(observed2), MoreExecutors.directExecutor());
+    assertNull(observed1.get());
+    assertNull(observed2.get());
+    base.cancel(null);
+    assertSame(base, observed1.get());
+    assertSame(base, observed2.get());
+
+    final AtomicReference<Context> observed3 = new AtomicReference<Context>();
+    base.addListener(new SetContextCancellationListener(observed3), MoreExecutors.directExecutor());
+    assertSame(base, observed3.get());
+  }
+
+  @Test
+  public void exceptionOfExecutorDoesntThrow() {
+    final AtomicReference<Throwable> loggedThrowable = new AtomicReference<Throwable>();
+    Handler logHandler = new Handler() {
+      @Override
+      public void publish(LogRecord record) {
+        Throwable thrown = record.getThrown();
+        if (thrown != null) {
+          if (loggedThrowable.get() == null) {
+            loggedThrowable.set(thrown);
+          } else {
+            loggedThrowable.set(new RuntimeException("Too many exceptions", thrown));
+          }
+        }
+      }
+
+      @Override
+      public void close() {}
+
+      @Override
+      public void flush() {}
+    };
+    Logger logger = Logger.getLogger(Context.class.getName());
+    logger.addHandler(logHandler);
+    try {
+      Context.CancellableContext base = Context.current().withCancellation();
+      final AtomicReference<Runnable> observed1 = new AtomicReference<Runnable>();
+      final Error err = new Error();
+      base.addListener(cancellationListener, new Executor() {
+        @Override
+        public void execute(Runnable runnable) {
+          observed1.set(runnable);
+          throw err;
+        }
+      });
+      assertNull(observed1.get());
+      assertNull(loggedThrowable.get());
+      base.cancel(null);
+      assertNotNull(observed1.get());
+      assertSame(err, loggedThrowable.get());
+
+      final Error err2 = new Error();
+      loggedThrowable.set(null);
+      final AtomicReference<Runnable> observed2 = new AtomicReference<Runnable>();
+      base.addListener(cancellationListener, new Executor() {
+        @Override
+        public void execute(Runnable runnable) {
+          observed2.set(runnable);
+          throw err2;
+        }
+      });
+      assertNotNull(observed2.get());
+      assertSame(err2, loggedThrowable.get());
+    } finally {
+      logger.removeHandler(logHandler);
+    }
+  }
+
+  @Test
+  public void cascadingCancellationNotifiesChild() {
+    // Root is not cancellable so we can't cascade from it
+    Context.CancellableContext base = Context.current().withCancellation();
+    assertEquals(0, base.listenerCount());
+    Context child = base.withValue(FOOD, "lasagna");
+    assertEquals(0, child.listenerCount());
+    child.addListener(cancellationListener, MoreExecutors.directExecutor());
+    assertEquals(1, child.listenerCount());
+    assertEquals(1, base.listenerCount()); // child is now listening to base
+    assertFalse(base.isCancelled());
+    assertFalse(child.isCancelled());
+    IllegalStateException cause = new IllegalStateException();
+    base.cancel(cause);
+    assertTrue(base.isCancelled());
+    assertSame(cause, base.cancellationCause());
+    assertSame(child, listenerNotifedContext);
+    assertTrue(child.isCancelled());
+    assertSame(cause, child.cancellationCause());
+    assertEquals(0, base.listenerCount());
+    assertEquals(0, child.listenerCount());
+  }
+
+  @Test
+  public void cascadingCancellationWithoutListener() {
+    Context.CancellableContext base = Context.current().withCancellation();
+    Context child = base.withCancellation();
+    Throwable t = new Throwable();
+    base.cancel(t);
+    assertTrue(child.isCancelled());
+    assertSame(t, child.cancellationCause());
+  }
+
+  // Context#isCurrent() and Context.CancellableContext#isCurrent() are intended
+  // to be visible only for testing. The deprecation is meant for users.
+  @SuppressWarnings("deprecation")
+  @Test
+  public void cancellableContextIsAttached() {
+    Context.CancellableContext base = Context.current().withValue(FOOD, "fish").withCancellation();
+    assertFalse(base.isCurrent());
+    Context toRestore = base.attach();
+
+    Context attached = Context.current();
+    assertSame("fish", FOOD.get());
+    assertFalse(attached.isCancelled());
+    assertNull(attached.cancellationCause());
+    assertTrue(attached.canBeCancelled());
+    assertTrue(attached.isCurrent());
+    assertTrue(base.isCurrent());
+
+    attached.addListener(cancellationListener, MoreExecutors.directExecutor());
+    Throwable t = new Throwable();
+    base.cancel(t);
+    assertTrue(attached.isCancelled());
+    assertSame(t, attached.cancellationCause());
+    assertSame(attached, listenerNotifedContext);
+
+    base.detach(toRestore);
+  }
+
+  @Test
+  public void cancellableContextCascadesFromCancellableParent() {
+    // Root is not cancellable so we can't cascade from it
+    Context.CancellableContext base = Context.current().withCancellation();
+    Context child = base.withCancellation();
+    child.addListener(cancellationListener, MoreExecutors.directExecutor());
+    assertFalse(base.isCancelled());
+    assertFalse(child.isCancelled());
+    IllegalStateException cause = new IllegalStateException();
+    base.cancel(cause);
+    assertTrue(base.isCancelled());
+    assertSame(cause, base.cancellationCause());
+    assertSame(child, listenerNotifedContext);
+    assertTrue(child.isCancelled());
+    assertSame(cause, child.cancellationCause());
+    assertEquals(0, base.listenerCount());
+    assertEquals(0, child.listenerCount());
+  }
+
+  @Test
+  public void nonCascadingCancellationDoesNotNotifyForked() {
+    Context.CancellableContext base = Context.current().withCancellation();
+    Context fork = base.fork();
+    fork.addListener(cancellationListener, MoreExecutors.directExecutor());
+    assertEquals(0, base.listenerCount());
+    assertEquals(0, fork.listenerCount());
+    assertTrue(base.cancel(new Throwable()));
+    assertNull(listenerNotifedContext);
+    assertFalse(fork.isCancelled());
+    assertNull(fork.cancellationCause());
+  }
+
+  @Test
+  public void testWrapRunnable() throws Exception {
+    Context base = Context.current().withValue(PET, "cat");
+    Context current = Context.current().withValue(PET, "fish");
+    current.attach();
+
+    base.wrap(runner).run();
+    assertSame(base, observed);
+    assertSame(current, Context.current());
+
+    current.wrap(runner).run();
+    assertSame(current, observed);
+    assertSame(current, Context.current());
+
+    final TestError err = new TestError();
+    try {
+      base.wrap(new Runnable() {
+        @Override
+        public void run() {
+          throw err;
+        }
+      }).run();
+      fail("Expected exception");
+    } catch (TestError ex) {
+      assertSame(err, ex);
+    }
+    assertSame(current, Context.current());
+
+    current.detach(Context.ROOT);
+  }
+
+  @Test
+  public void testWrapCallable() throws Exception {
+    Context base = Context.current().withValue(PET, "cat");
+    Context current = Context.current().withValue(PET, "fish");
+    current.attach();
+
+    final Object ret = new Object();
+    Callable<Object> callable = new Callable<Object>() {
+      @Override
+      public Object call() {
+        runner.run();
+        return ret;
+      }
+    };
+
+    assertSame(ret, base.wrap(callable).call());
+    assertSame(base, observed);
+    assertSame(current, Context.current());
+
+    assertSame(ret, current.wrap(callable).call());
+    assertSame(current, observed);
+    assertSame(current, Context.current());
+
+    final TestError err = new TestError();
+    try {
+      base.wrap(new Callable<Object>() {
+        @Override
+        public Object call() {
+          throw err;
+        }
+      }).call();
+      fail("Excepted exception");
+    } catch (TestError ex) {
+      assertSame(err, ex);
+    }
+    assertSame(current, Context.current());
+
+    current.detach(Context.ROOT);
+  }
+
+  @Test
+  public void currentContextExecutor() throws Exception {
+    QueuedExecutor queuedExecutor = new QueuedExecutor();
+    Executor executor = Context.currentContextExecutor(queuedExecutor);
+    Context base = Context.current().withValue(PET, "cat");
+    Context previous = base.attach();
+    try {
+      executor.execute(runner);
+    } finally {
+      base.detach(previous);
+    }
+    assertEquals(1, queuedExecutor.runnables.size());
+    queuedExecutor.runnables.remove().run();
+    assertSame(base, observed);
+  }
+
+  @Test
+  public void fixedContextExecutor() throws Exception {
+    Context base = Context.current().withValue(PET, "cat");
+    QueuedExecutor queuedExecutor = new QueuedExecutor();
+    base.fixedContextExecutor(queuedExecutor).execute(runner);
+    assertEquals(1, queuedExecutor.runnables.size());
+    queuedExecutor.runnables.remove().run();
+    assertSame(base, observed);
+  }
+
+  @Test
+  public void typicalTryFinallyHandling() throws Exception {
+    Context base = Context.current().withValue(COLOR, "blue");
+    Context previous = base.attach();
+    try {
+      assertTrue(base.isCurrent());
+      // Do something
+    } finally {
+      base.detach(previous);
+    }
+    assertFalse(base.isCurrent());
+  }
+
+  @Test
+  public void typicalCancellableTryCatchFinallyHandling() throws Exception {
+    Context.CancellableContext base = Context.current().withCancellation();
+    Context previous = base.attach();
+    try {
+      // Do something
+      throw new IllegalStateException("Argh");
+    } catch (IllegalStateException ise) {
+      base.cancel(ise);
+    } finally {
+      base.detachAndCancel(previous, null);
+    }
+    assertTrue(base.isCancelled());
+    assertNotNull(base.cancellationCause());
+  }
+
+  @Test
+  public void rootHasNoDeadline() {
+    assertNull(Context.ROOT.getDeadline());
+  }
+
+  @Test
+  public void contextWithDeadlineHasDeadline() {
+    Context.CancellableContext cancellableContext =
+        Context.ROOT.withDeadlineAfter(1, TimeUnit.SECONDS, scheduler);
+    assertNotNull(cancellableContext.getDeadline());
+  }
+
+  @Test
+  public void earlierParentDeadlineTakesPrecedenceOverLaterChildDeadline() throws Exception {
+    final Deadline sooner = Deadline.after(100, TimeUnit.MILLISECONDS);
+    final Deadline later = Deadline.after(1, TimeUnit.MINUTES);
+    Context.CancellableContext parent = Context.ROOT.withDeadline(sooner, scheduler);
+    Context.CancellableContext child = parent.withDeadline(later, scheduler);
+    assertSame(parent.getDeadline(), sooner);
+    assertSame(child.getDeadline(), sooner);
+    final CountDownLatch latch = new CountDownLatch(1);
+    final AtomicReference<Exception> error = new AtomicReference<Exception>();
+    child.addListener(new Context.CancellationListener() {
+      @Override
+      public void cancelled(Context context) {
+        try {
+          assertTrue(sooner.isExpired());
+          assertFalse(later.isExpired());
+        } catch (Exception e) {
+          error.set(e);
+        }
+        latch.countDown();
+      }
+    }, MoreExecutors.directExecutor());
+    latch.await(3, TimeUnit.SECONDS);
+    if (error.get() != null) {
+      throw error.get();
+    }
+  }
+
+  @Test
+  public void earlierChldDeadlineTakesPrecedenceOverLaterParentDeadline() {
+    Deadline sooner = Deadline.after(1, TimeUnit.HOURS);
+    Deadline later = Deadline.after(1, TimeUnit.DAYS);
+    Context.CancellableContext parent = Context.ROOT.withDeadline(later, scheduler);
+    Context.CancellableContext child = parent.withDeadline(sooner, scheduler);
+    assertSame(parent.getDeadline(), later);
+    assertSame(child.getDeadline(), sooner);
+  }
+
+  @Test
+  public void forkingContextDoesNotCarryDeadline() {
+    Deadline deadline = Deadline.after(1, TimeUnit.HOURS);
+    Context.CancellableContext parent = Context.ROOT.withDeadline(deadline, scheduler);
+    Context fork = parent.fork();
+    assertNull(fork.getDeadline());
+  }
+
+  @Test
+  public void cancellationDoesNotExpireDeadline() {
+    Deadline deadline = Deadline.after(1, TimeUnit.HOURS);
+    Context.CancellableContext parent = Context.ROOT.withDeadline(deadline, scheduler);
+    parent.cancel(null);
+    assertFalse(deadline.isExpired());
+  }
+
+  @Test
+  public void absoluteDeadlineTriggersAndPropagates() throws Exception {
+    Context base = Context.current().withDeadline(Deadline.after(1, TimeUnit.SECONDS), scheduler);
+    Context child = base.withValue(FOOD, "lasagna");
+    child.addListener(cancellationListener, MoreExecutors.directExecutor());
+    assertFalse(base.isCancelled());
+    assertFalse(child.isCancelled());
+    assertTrue(deadlineLatch.await(2, TimeUnit.SECONDS));
+    assertTrue(base.isCancelled());
+    assertTrue(base.cancellationCause() instanceof TimeoutException);
+    assertSame(child, listenerNotifedContext);
+    assertTrue(child.isCancelled());
+    assertSame(base.cancellationCause(), child.cancellationCause());
+  }
+
+  @Test
+  public void relativeDeadlineTriggersAndPropagates() throws Exception {
+    Context base = Context.current().withDeadline(Deadline.after(1, TimeUnit.SECONDS), scheduler);
+    Context child = base.withValue(FOOD, "lasagna");
+    child.addListener(cancellationListener, MoreExecutors.directExecutor());
+    assertFalse(base.isCancelled());
+    assertFalse(child.isCancelled());
+    assertTrue(deadlineLatch.await(2, TimeUnit.SECONDS));
+    assertTrue(base.isCancelled());
+    assertTrue(base.cancellationCause() instanceof TimeoutException);
+    assertSame(child, listenerNotifedContext);
+    assertTrue(child.isCancelled());
+    assertSame(base.cancellationCause(), child.cancellationCause());
+  }
+
+  @Test
+  public void innerDeadlineCompletesBeforeOuter() throws Exception {
+    Context base = Context.current().withDeadline(Deadline.after(2, TimeUnit.SECONDS), scheduler);
+    Context child = base.withDeadline(Deadline.after(1, TimeUnit.SECONDS), scheduler);
+    child.addListener(cancellationListener, MoreExecutors.directExecutor());
+    assertFalse(base.isCancelled());
+    assertFalse(child.isCancelled());
+    assertTrue(deadlineLatch.await(2, TimeUnit.SECONDS));
+    assertFalse(base.isCancelled());
+    assertSame(child, listenerNotifedContext);
+    assertTrue(child.isCancelled());
+    assertTrue(child.cancellationCause() instanceof TimeoutException);
+
+    deadlineLatch = new CountDownLatch(1);
+    base.addListener(cancellationListener, MoreExecutors.directExecutor());
+    assertTrue(deadlineLatch.await(2, TimeUnit.SECONDS));
+    assertTrue(base.isCancelled());
+    assertTrue(base.cancellationCause() instanceof TimeoutException);
+    assertNotSame(base.cancellationCause(), child.cancellationCause());
+  }
+
+  @Test
+  public void cancellationCancelsScheduledTask() {
+    ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
+    try {
+      assertEquals(0, executor.getQueue().size());
+      Context.CancellableContext base
+          = Context.current().withDeadline(Deadline.after(1, TimeUnit.DAYS), executor);
+      assertEquals(1, executor.getQueue().size());
+      base.cancel(null);
+      executor.purge();
+      assertEquals(0, executor.getQueue().size());
+    } finally {
+      executor.shutdown();
+    }
+  }
+
+  private static class QueuedExecutor implements Executor {
+    private final Queue<Runnable> runnables = new ArrayDeque<Runnable>();
+
+    @Override
+    public void execute(Runnable r) {
+      runnables.add(r);
+    }
+  }
+
+  @Test
+  public void childContextListenerNotifiedAfterParentListener() {
+    Context.CancellableContext parent = Context.current().withCancellation();
+    Context child = parent.withValue(COLOR, "red");
+    final AtomicBoolean childAfterParent = new AtomicBoolean();
+    final AtomicBoolean parentCalled = new AtomicBoolean();
+    child.addListener(new Context.CancellationListener() {
+      @Override
+      public void cancelled(Context context) {
+        if (parentCalled.get()) {
+          childAfterParent.set(true);
+        }
+      }
+    }, MoreExecutors.directExecutor());
+    parent.addListener(new Context.CancellationListener() {
+      @Override
+      public void cancelled(Context context) {
+        parentCalled.set(true);
+      }
+    }, MoreExecutors.directExecutor());
+    parent.cancel(null);
+    assertTrue(parentCalled.get());
+    assertTrue(childAfterParent.get());
+  }
+
+  @Test
+  public void expiredDeadlineShouldCancelContextImmediately() {
+    Context parent = Context.current();
+    assertFalse(parent.isCancelled());
+
+    Context.CancellableContext context = parent.withDeadlineAfter(0, TimeUnit.SECONDS, scheduler);
+    assertTrue(context.isCancelled());
+    assertThat(context.cancellationCause(), instanceOf(TimeoutException.class));
+
+    assertFalse(parent.isCancelled());
+    Deadline deadline = Deadline.after(-10, TimeUnit.SECONDS);
+    assertTrue(deadline.isExpired());
+    context = parent.withDeadline(deadline, scheduler);
+    assertTrue(context.isCancelled());
+    assertThat(context.cancellationCause(), instanceOf(TimeoutException.class));
+  }
+
+  /**
+   * Tests initializing the {@link Context} class with a custom logger which uses Context's storage
+   * when logging.
+   */
+  @Test
+  public void initContextWithCustomClassLoaderWithCustomLogger() throws Exception {
+    StaticTestingClassLoader classLoader =
+        new StaticTestingClassLoader(
+            getClass().getClassLoader(), Pattern.compile("io\\.grpc\\.[^.]+"));
+    Class<?> runnable =
+        classLoader.loadClass(LoadMeWithStaticTestingClassLoader.class.getName());
+
+    ((Runnable) runnable.getDeclaredConstructor().newInstance()).run();
+  }
+
+  /**
+   * Ensure that newly created threads can attach/detach a context.
+   * The current test thread already has a context manually attached in {@link #setUp()}.
+   */
+  @Test
+  public void newThreadAttachContext() throws Exception {
+    Context parent = Context.current().withValue(COLOR, "blue");
+    parent.call(new Callable<Object>() {
+      @Override
+      public Object call() throws Exception {
+        assertEquals("blue", COLOR.get());
+
+        final Context child = Context.current().withValue(COLOR, "red");
+        Future<String> workerThreadVal = scheduler
+            .submit(new Callable<String>() {
+              @Override
+              public String call() {
+                Context initial = Context.current();
+                assertNotNull(initial);
+                Context toRestore = child.attach();
+                try {
+                  assertNotNull(toRestore);
+                  return COLOR.get();
+                } finally {
+                  child.detach(toRestore);
+                  assertEquals(initial, Context.current());
+                }
+              }
+            });
+        assertEquals("red", workerThreadVal.get());
+
+        assertEquals("blue", COLOR.get());
+        return null;
+      }
+    });
+  }
+
+  /**
+   * Similar to {@link #newThreadAttachContext()} but without giving the new thread a specific ctx.
+   */
+  @Test
+  public void newThreadWithoutContext() throws Exception {
+    Context parent = Context.current().withValue(COLOR, "blue");
+    parent.call(new Callable<Object>() {
+      @Override
+      public Object call() throws Exception {
+        assertEquals("blue", COLOR.get());
+
+        Future<String> workerThreadVal = scheduler
+            .submit(new Callable<String>() {
+              @Override
+              public String call() {
+                assertNotNull(Context.current());
+                return COLOR.get();
+              }
+            });
+        assertEquals(null, workerThreadVal.get());
+
+        assertEquals("blue", COLOR.get());
+        return null;
+      }
+    });
+  }
+
+  @Test
+  public void storageReturnsNullTest() throws Exception {
+    Field storage = Context.class.getDeclaredField("storage");
+    assertTrue(Modifier.isFinal(storage.getModifiers()));
+    // use reflection to forcibly change the storage object to a test object
+    storage.setAccessible(true);
+    Object o = storage.get(null);
+    @SuppressWarnings("unchecked")
+    AtomicReference<Context.Storage> storageRef = (AtomicReference<Context.Storage>) o;
+    Context.Storage originalStorage = storageRef.get();
+    try {
+      storageRef.set(new Context.Storage() {
+        @Override
+        public Context doAttach(Context toAttach) {
+          return null;
+        }
+
+        @Override
+        public void detach(Context toDetach, Context toRestore) {
+          // noop
+        }
+
+        @Override
+        public Context current() {
+          return null;
+        }
+      });
+      // current() returning null gets transformed into ROOT
+      assertEquals(Context.ROOT, Context.current());
+
+      // doAttach() returning null gets transformed into ROOT
+      Context blueContext = Context.current().withValue(COLOR, "blue");
+      Context toRestore = blueContext.attach();
+      assertEquals(Context.ROOT, toRestore);
+
+      // final sanity check
+      blueContext.detach(toRestore);
+      assertEquals(Context.ROOT, Context.current());
+    } finally {
+      // undo the changes
+      storageRef.set(originalStorage);
+      storage.setAccessible(false);
+    }
+  }
+
+  @Test
+  public void cancellableAncestorTest() {
+    assertEquals(null, cancellableAncestor(null));
+
+    Context c = Context.current();
+    assertFalse(c.canBeCancelled());
+    assertEquals(null, cancellableAncestor(c));
+
+    Context.CancellableContext withCancellation = c.withCancellation();
+    assertEquals(withCancellation, cancellableAncestor(withCancellation));
+
+    Context child = withCancellation.withValue(COLOR, "blue");
+    assertFalse(child instanceof Context.CancellableContext);
+    assertEquals(withCancellation, cancellableAncestor(child));
+
+    Context grandChild = child.withValue(COLOR, "red");
+    assertFalse(grandChild instanceof Context.CancellableContext);
+    assertEquals(withCancellation, cancellableAncestor(grandChild));
+  }
+
+  @Test
+  public void cancellableAncestorIntegrationTest() {
+    Context base = Context.current();
+
+    Context blue = base.withValue(COLOR, "blue");
+    assertNull(blue.cancellableAncestor);
+    Context.CancellableContext cancellable = blue.withCancellation();
+    assertNull(cancellable.cancellableAncestor);
+    Context childOfCancel = cancellable.withValue(PET, "cat");
+    assertSame(cancellable, childOfCancel.cancellableAncestor);
+    Context grandChildOfCancel = childOfCancel.withValue(FOOD, "lasagna");
+    assertSame(cancellable, grandChildOfCancel.cancellableAncestor);
+
+    Context.CancellableContext cancellable2 = childOfCancel.withCancellation();
+    assertSame(cancellable, cancellable2.cancellableAncestor);
+    Context childOfCancellable2 = cancellable2.withValue(PET, "dog");
+    assertSame(cancellable2, childOfCancellable2.cancellableAncestor);
+  }
+
+  @Test
+  public void cancellableAncestorFork() {
+    Context.CancellableContext cancellable = Context.current().withCancellation();
+    Context fork = cancellable.fork();
+    assertNull(fork.cancellableAncestor);
+  }
+
+  @Test
+  public void cancellableContext_closeCancelsWithNullCause() throws Exception {
+    Context.CancellableContext cancellable = Context.current().withCancellation();
+    cancellable.close();
+    assertTrue(cancellable.isCancelled());
+    assertNull(cancellable.cancellationCause());
+  }
+
+  @Test
+  public void errorWhenAncestryLengthLong() {
+    final AtomicReference<LogRecord> logRef = new AtomicReference<LogRecord>();
+    Handler handler = new Handler() {
+      @Override
+      public void publish(LogRecord record) {
+        logRef.set(record);
+      }
+
+      @Override
+      public void flush() {
+      }
+
+      @Override
+      public void close() throws SecurityException {
+      }
+    };
+    Logger logger = Logger.getLogger(Context.class.getName());
+    try {
+      logger.addHandler(handler);
+      Context ctx = Context.current();
+      for (int i = 0; i < Context.CONTEXT_DEPTH_WARN_THRESH ; i++) {
+        assertNull(logRef.get());
+        ctx = ctx.fork();
+      }
+      ctx = ctx.fork();
+      assertNotNull(logRef.get());
+      assertNotNull(logRef.get().getThrown());
+      assertEquals(Level.SEVERE, logRef.get().getLevel());
+    } finally {
+      logger.removeHandler(handler);
+    }
+  }
+
+  // UsedReflectively
+  public static final class LoadMeWithStaticTestingClassLoader implements Runnable {
+    @Override
+    public void run() {
+      Logger logger = Logger.getLogger(Context.class.getName());
+      logger.setLevel(Level.ALL);
+      Handler handler = new Handler() {
+        @Override
+        public void publish(LogRecord record) {
+          Context ctx = Context.current();
+          Context previous = ctx.attach();
+          ctx.detach(previous);
+        }
+
+        @Override
+        public void flush() {
+        }
+
+        @Override
+        public void close() throws SecurityException {
+        }
+      };
+      logger.addHandler(handler);
+
+      try {
+        assertNotNull(Context.ROOT);
+      } finally {
+        logger.removeHandler(handler);
+      }
+    }
+  }
+
+  /** Allows more precise catch blocks than plain Error to avoid catching AssertionError. */
+  private static final class TestError extends Error {}
+}
diff --git a/context/src/test/java/io/grpc/DeadlineTest.java b/context/src/test/java/io/grpc/DeadlineTest.java
new file mode 100644
index 0000000..548ebe5
--- /dev/null
+++ b/context/src/test/java/io/grpc/DeadlineTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static io.grpc.testing.DeadlineSubject.deadline;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.truth.Truth;
+import java.util.Arrays;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Tests for {@link Context}.
+ */
+@RunWith(Parameterized.class)
+public class DeadlineTest {
+  /** Ticker epochs to vary testing. */
+  @Parameters
+  public static Iterable<Object[]> data() {
+    return Arrays.asList(new Object[][] {
+      // MAX_VALUE / 2 is important because the signs are generally the same for past and future
+      // deadlines.
+      {Long.MAX_VALUE / 2}, {0}, {Long.MAX_VALUE}, {Long.MIN_VALUE}
+    });
+  }
+
+  private FakeTicker ticker = new FakeTicker();
+
+  public DeadlineTest(long epoch) {
+    ticker.reset(epoch);
+  }
+
+  @Test
+  public void defaultTickerIsSystemTicker() {
+    Deadline d = Deadline.after(0, TimeUnit.SECONDS);
+    ticker.reset(System.nanoTime());
+    Deadline reference = Deadline.after(0, TimeUnit.SECONDS, ticker);
+    // Allow inaccuracy to account for system time advancing during test.
+    assertAbout(deadline()).that(d).isWithin(1, TimeUnit.SECONDS).of(reference);
+  }
+
+  @Test
+  public void timeCanOverflow() {
+    ticker.reset(Long.MAX_VALUE);
+    Deadline d = Deadline.after(10, TimeUnit.DAYS, ticker);
+    assertEquals(10, d.timeRemaining(TimeUnit.DAYS));
+    assertTrue(Deadline.after(0, TimeUnit.DAYS, ticker).isBefore(d));
+    assertFalse(d.isExpired());
+
+    ticker.increment(10, TimeUnit.DAYS);
+    assertTrue(d.isExpired());
+  }
+
+  @Test
+  public void timeCanUnderflow() {
+    ticker.reset(Long.MIN_VALUE);
+    Deadline d = Deadline.after(-10, TimeUnit.DAYS, ticker);
+    assertEquals(-10, d.timeRemaining(TimeUnit.DAYS));
+    assertTrue(d.isBefore(Deadline.after(0, TimeUnit.DAYS, ticker)));
+    assertTrue(d.isExpired());
+  }
+
+  @Test
+  public void deadlineClamps() {
+    Deadline d = Deadline.after(-300 * 365, TimeUnit.DAYS, ticker);
+    Deadline d2 = Deadline.after(300 * 365, TimeUnit.DAYS, ticker);
+    assertTrue(d.isBefore(d2));
+
+    Deadline d3 = Deadline.after(-200 * 365, TimeUnit.DAYS, ticker);
+    // d and d3 are equal
+    assertFalse(d.isBefore(d3));
+    assertFalse(d3.isBefore(d));
+  }
+
+  @Test
+  public void immediateDeadlineIsExpired() {
+    Deadline deadline = Deadline.after(0, TimeUnit.SECONDS, ticker);
+    assertTrue(deadline.isExpired());
+  }
+
+  @Test
+  public void shortDeadlineEventuallyExpires() throws Exception {
+    Deadline d = Deadline.after(100, TimeUnit.MILLISECONDS, ticker);
+    assertTrue(d.timeRemaining(TimeUnit.NANOSECONDS) > 0);
+    assertFalse(d.isExpired());
+    ticker.increment(101, TimeUnit.MILLISECONDS);
+
+    assertTrue(d.isExpired());
+    assertEquals(-1, d.timeRemaining(TimeUnit.MILLISECONDS));
+  }
+
+  @Test
+  public void deadlineMatchesLongValue() {
+    assertEquals(10, Deadline.after(10, TimeUnit.MINUTES, ticker).timeRemaining(TimeUnit.MINUTES));
+  }
+
+  @Test
+  public void pastDeadlineIsExpired() {
+    Deadline d = Deadline.after(-1, TimeUnit.SECONDS, ticker);
+    assertTrue(d.isExpired());
+    assertEquals(-1000, d.timeRemaining(TimeUnit.MILLISECONDS));
+  }
+
+  @Test
+  public void deadlineDoesNotOverflowOrUnderflow() {
+    Deadline after = Deadline.after(Long.MAX_VALUE, TimeUnit.NANOSECONDS, ticker);
+    assertFalse(after.isExpired());
+
+    Deadline before = Deadline.after(-Long.MAX_VALUE, TimeUnit.NANOSECONDS, ticker);
+    assertTrue(before.isExpired());
+
+    assertTrue(before.isBefore(after));
+  }
+
+  @Test
+  public void beforeExpiredDeadlineIsExpired() {
+    Deadline base = Deadline.after(0, TimeUnit.SECONDS, ticker);
+    assertTrue(base.isExpired());
+    assertTrue(base.offset(-1, TimeUnit.SECONDS).isExpired());
+  }
+
+  @Test
+  public void beforeNotExpiredDeadlineMayBeExpired() {
+    Deadline base = Deadline.after(10, TimeUnit.SECONDS, ticker);
+    assertFalse(base.isExpired());
+    assertFalse(base.offset(-1, TimeUnit.SECONDS).isExpired());
+    assertTrue(base.offset(-11, TimeUnit.SECONDS).isExpired());
+  }
+
+  @Test
+  public void afterExpiredDeadlineMayBeExpired() {
+    Deadline base = Deadline.after(-10, TimeUnit.SECONDS, ticker);
+    assertTrue(base.isExpired());
+    assertTrue(base.offset(1, TimeUnit.SECONDS).isExpired());
+    assertFalse(base.offset(11, TimeUnit.SECONDS).isExpired());
+  }
+
+  @Test
+  public void zeroOffsetIsSameDeadline() {
+    Deadline base = Deadline.after(0, TimeUnit.SECONDS, ticker);
+    assertSame(base, base.offset(0, TimeUnit.SECONDS));
+  }
+
+  @Test
+  public void runOnEventualExpirationIsExecuted() throws Exception {
+    Deadline base = Deadline.after(50, TimeUnit.MICROSECONDS, ticker);
+    ScheduledExecutorService mockScheduler = mock(ScheduledExecutorService.class);
+    final AtomicBoolean executed = new AtomicBoolean();
+    Future<?> unused = base.runOnExpiration(
+        new Runnable() {
+          @Override
+          public void run() {
+            executed.set(true);
+          }
+        }, mockScheduler);
+    assertFalse(executed.get());
+    ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+    verify(mockScheduler).schedule(runnableCaptor.capture(), eq(50000L), eq(TimeUnit.NANOSECONDS));
+    runnableCaptor.getValue().run();
+    assertTrue(executed.get());
+  }
+
+  @Test
+  public void runOnAlreadyExpiredIsExecutedOnExecutor() throws Exception {
+    Deadline base = Deadline.after(0, TimeUnit.MICROSECONDS, ticker);
+    ScheduledExecutorService mockScheduler = mock(ScheduledExecutorService.class);
+    final AtomicBoolean executed = new AtomicBoolean();
+    Future<?> unused = base.runOnExpiration(
+        new Runnable() {
+          @Override
+          public void run() {
+            executed.set(true);
+          }
+        }, mockScheduler);
+    assertFalse(executed.get());
+    ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+    verify(mockScheduler).schedule(runnableCaptor.capture(), eq(0L), eq(TimeUnit.NANOSECONDS));
+    runnableCaptor.getValue().run();
+    assertTrue(executed.get());
+  }
+
+  @Test
+  public void toString_exact() {
+    Deadline d = Deadline.after(0, TimeUnit.MILLISECONDS, ticker);
+    assertEquals("0 ns from now", d.toString());
+  }
+
+  @Test
+  public void toString_after() {
+    Deadline d = Deadline.after(-1, TimeUnit.MINUTES, ticker);
+    assertEquals("-60000000000 ns from now", d.toString());
+  }
+
+  @Test
+  public void compareTo_greater() {
+    Deadline d1 = Deadline.after(10, TimeUnit.SECONDS, ticker);
+    ticker.increment(1, TimeUnit.NANOSECONDS);
+    Deadline d2 = Deadline.after(10, TimeUnit.SECONDS, ticker);
+    Truth.assertThat(d2).isGreaterThan(d1);
+  }
+
+  @Test
+  public void compareTo_less() {
+    Deadline d1 = Deadline.after(10, TimeUnit.SECONDS, ticker);
+    ticker.increment(1, TimeUnit.NANOSECONDS);
+    Deadline d2 = Deadline.after(10, TimeUnit.SECONDS, ticker);
+    Truth.assertThat(d1).isLessThan(d2);
+  }
+
+  @Test
+  public void compareTo_same() {
+    Deadline d1 = Deadline.after(10, TimeUnit.SECONDS, ticker);
+    Deadline d2 = Deadline.after(10, TimeUnit.SECONDS, ticker);
+    Truth.assertThat(d1).isEquivalentAccordingToCompareTo(d2);
+  }
+
+  @Test
+  public void toString_before() {
+    Deadline d = Deadline.after(12, TimeUnit.MICROSECONDS, ticker);
+    assertEquals("12000 ns from now", d.toString());
+  }
+
+  private static class FakeTicker extends Deadline.Ticker {
+    private long time;
+
+    @Override
+    public long read() {
+      return time;
+    }
+
+    public void reset(long time) {
+      this.time = time;
+    }
+
+    public void increment(long period, TimeUnit unit) {
+      if (period < 0) {
+        throw new IllegalArgumentException();
+      }
+      this.time += unit.toNanos(period);
+    }
+  }
+}
diff --git a/context/src/test/java/io/grpc/PersistentHashArrayMappedTrieTest.java b/context/src/test/java/io/grpc/PersistentHashArrayMappedTrieTest.java
new file mode 100644
index 0000000..c918005
--- /dev/null
+++ b/context/src/test/java/io/grpc/PersistentHashArrayMappedTrieTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import io.grpc.PersistentHashArrayMappedTrie.CollisionLeaf;
+import io.grpc.PersistentHashArrayMappedTrie.CompressedIndex;
+import io.grpc.PersistentHashArrayMappedTrie.Leaf;
+import io.grpc.PersistentHashArrayMappedTrie.Node;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PersistentHashArrayMappedTrieTest {
+  @Test
+  public void leaf_replace() {
+    Key key = new Key(0);
+    Object value1 = new Object();
+    Object value2 = new Object();
+    Leaf<Key, Object> leaf = new Leaf<Key, Object>(key, value1);
+    Node<Key, Object> ret = leaf.put(key, value2, key.hashCode(), 0);
+    assertTrue(ret instanceof Leaf);
+    assertSame(value2, ret.get(key, key.hashCode(), 0));
+
+    assertSame(value1, leaf.get(key, key.hashCode(), 0));
+
+    assertEquals(1, leaf.size());
+    assertEquals(1, ret.size());
+  }
+
+  @Test
+  public void leaf_collision() {
+    Key key1 = new Key(0);
+    Key key2 = new Key(0);
+    Object value1 = new Object();
+    Object value2 = new Object();
+    Leaf<Key, Object> leaf = new Leaf<Key, Object>(key1, value1);
+    Node<Key, Object> ret = leaf.put(key2, value2, key2.hashCode(), 0);
+    assertTrue(ret instanceof CollisionLeaf);
+    assertSame(value1, ret.get(key1, key1.hashCode(), 0));
+    assertSame(value2, ret.get(key2, key2.hashCode(), 0));
+
+    assertSame(value1, leaf.get(key1, key1.hashCode(), 0));
+    assertSame(null, leaf.get(key2, key2.hashCode(), 0));
+
+    assertEquals(1, leaf.size());
+    assertEquals(2, ret.size());
+  }
+
+  @Test
+  public void leaf_insert() {
+    Key key1 = new Key(0);
+    Key key2 = new Key(1);
+    Object value1 = new Object();
+    Object value2 = new Object();
+    Leaf<Key, Object> leaf = new Leaf<Key, Object>(key1, value1);
+    Node<Key, Object> ret = leaf.put(key2, value2, key2.hashCode(), 0);
+    assertTrue(ret instanceof CompressedIndex);
+    assertSame(value1, ret.get(key1, key1.hashCode(), 0));
+    assertSame(value2, ret.get(key2, key2.hashCode(), 0));
+
+    assertSame(value1, leaf.get(key1, key1.hashCode(), 0));
+    assertSame(null, leaf.get(key2, key2.hashCode(), 0));
+
+    assertEquals(1, leaf.size());
+    assertEquals(2, ret.size());
+  }
+
+  @Test(expected = AssertionError.class)
+  public void collisionLeaf_assertKeysDifferent() {
+    Key key1 = new Key(0);
+    new CollisionLeaf<Key, Object>(key1, new Object(), key1, new Object());
+  }
+
+  @Test(expected = AssertionError.class)
+  public void collisionLeaf_assertHashesSame() {
+    new CollisionLeaf<Key, Object>(new Key(0), new Object(), new Key(1), new Object());
+  }
+
+  @Test
+  public void collisionLeaf_insert() {
+    Key key1 = new Key(0);
+    Key key2 = new Key(key1.hashCode());
+    Key insertKey = new Key(1);
+    Object value1 = new Object();
+    Object value2 = new Object();
+    Object insertValue = new Object();
+    CollisionLeaf<Key, Object> leaf =
+        new CollisionLeaf<Key, Object>(key1, value1, key2, value2);
+
+    Node<Key, Object> ret = leaf.put(insertKey, insertValue, insertKey.hashCode(), 0);
+    assertTrue(ret instanceof CompressedIndex);
+    assertSame(value1, ret.get(key1, key1.hashCode(), 0));
+    assertSame(value2, ret.get(key2, key2.hashCode(), 0));
+    assertSame(insertValue, ret.get(insertKey, insertKey.hashCode(), 0));
+
+    assertSame(value1, leaf.get(key1, key1.hashCode(), 0));
+    assertSame(value2, leaf.get(key2, key2.hashCode(), 0));
+    assertSame(null, leaf.get(insertKey, insertKey.hashCode(), 0));
+
+    assertEquals(2, leaf.size());
+    assertEquals(3, ret.size());
+  }
+
+  @Test
+  public void collisionLeaf_replace() {
+    Key replaceKey = new Key(0);
+    Object originalValue = new Object();
+    Key key = new Key(replaceKey.hashCode());
+    Object value = new Object();
+    CollisionLeaf<Key, Object> leaf =
+        new CollisionLeaf<Key, Object>(replaceKey, originalValue, key, value);
+    Object replaceValue = new Object();
+    Node<Key, Object> ret = leaf.put(replaceKey, replaceValue, replaceKey.hashCode(), 0);
+    assertTrue(ret instanceof CollisionLeaf);
+    assertSame(replaceValue, ret.get(replaceKey, replaceKey.hashCode(), 0));
+    assertSame(value, ret.get(key, key.hashCode(), 0));
+
+    assertSame(value, leaf.get(key, key.hashCode(), 0));
+    assertSame(originalValue, leaf.get(replaceKey, replaceKey.hashCode(), 0));
+
+    assertEquals(2, leaf.size());
+    assertEquals(2, ret.size());
+  }
+
+  @Test
+  public void collisionLeaf_collision() {
+    Key key1 = new Key(0);
+    Key key2 = new Key(key1.hashCode());
+    Key key3 = new Key(key1.hashCode());
+    Object value1 = new Object();
+    Object value2 = new Object();
+    Object value3 = new Object();
+    CollisionLeaf<Key, Object> leaf =
+        new CollisionLeaf<Key, Object>(key1, value1, key2, value2);
+
+    Node<Key, Object> ret = leaf.put(key3, value3, key3.hashCode(), 0);
+    assertTrue(ret instanceof CollisionLeaf);
+    assertSame(value1, ret.get(key1, key1.hashCode(), 0));
+    assertSame(value2, ret.get(key2, key2.hashCode(), 0));
+    assertSame(value3, ret.get(key3, key3.hashCode(), 0));
+
+    assertSame(value1, leaf.get(key1, key1.hashCode(), 0));
+    assertSame(value2, leaf.get(key2, key2.hashCode(), 0));
+    assertSame(null, leaf.get(key3, key3.hashCode(), 0));
+
+    assertEquals(2, leaf.size());
+    assertEquals(3, ret.size());
+  }
+
+  @Test
+  public void compressedIndex_combine_differentIndexBit() {
+    final Key key1 = new Key(7);
+    final Key key2 = new Key(19);
+    final Object value1 = new Object();
+    final Object value2 = new Object();
+    Leaf<Key, Object> leaf1 = new Leaf<Key, Object>(key1, value1);
+    Leaf<Key, Object> leaf2 = new Leaf<Key, Object>(key2, value2);
+    class Verifier {
+      private void verify(Node<Key, Object> ret) {
+        CompressedIndex<Key, Object> collisionLeaf = (CompressedIndex<Key, Object>) ret;
+        assertEquals((1 << 7) | (1 << 19), collisionLeaf.bitmap);
+        assertEquals(2, collisionLeaf.values.length);
+        assertSame(value1, collisionLeaf.values[0].get(key1, key1.hashCode(), 0));
+        assertSame(value2, collisionLeaf.values[1].get(key2, key2.hashCode(), 0));
+
+        assertSame(value1, ret.get(key1, key1.hashCode(), 0));
+        assertSame(value2, ret.get(key2, key2.hashCode(), 0));
+
+        assertEquals(2, ret.size());
+      }
+    }
+
+    Verifier verifier = new Verifier();
+    verifier.verify(CompressedIndex.combine(leaf1, key1.hashCode(), leaf2, key2.hashCode(), 0));
+    verifier.verify(CompressedIndex.combine(leaf2, key2.hashCode(), leaf1, key1.hashCode(), 0));
+
+    assertEquals(1, leaf1.size());
+    assertEquals(1, leaf2.size());
+  }
+
+  @Test
+  public void compressedIndex_combine_sameIndexBit() {
+    final Key key1 = new Key(17 << 5 | 1); // 5 bit regions: (17, 1)
+    final Key key2 = new Key(31 << 5 | 1); // 5 bit regions: (31, 1)
+    final Object value1 = new Object();
+    final Object value2 = new Object();
+    Leaf<Key, Object> leaf1 = new Leaf<Key, Object>(key1, value1);
+    Leaf<Key, Object> leaf2 = new Leaf<Key, Object>(key2, value2);
+    class Verifier {
+      private void verify(Node<Key, Object> ret) {
+        CompressedIndex<Key, Object> collisionInternal = (CompressedIndex<Key, Object>) ret;
+        assertEquals(1 << 1, collisionInternal.bitmap);
+        assertEquals(1, collisionInternal.values.length);
+        CompressedIndex<Key, Object> collisionLeaf =
+            (CompressedIndex<Key, Object>) collisionInternal.values[0];
+        assertEquals((1 << 31) | (1 << 17), collisionLeaf.bitmap);
+        assertSame(value1, ret.get(key1, key1.hashCode(), 0));
+        assertSame(value2, ret.get(key2, key2.hashCode(), 0));
+
+        assertEquals(2, ret.size());
+      }
+    }
+
+    Verifier verifier = new Verifier();
+    verifier.verify(CompressedIndex.combine(leaf1, key1.hashCode(), leaf2, key2.hashCode, 0));
+    verifier.verify(CompressedIndex.combine(leaf2, key2.hashCode(), leaf1, key1.hashCode, 0));
+
+    assertEquals(1, leaf1.size());
+    assertEquals(1, leaf2.size());
+  }
+
+  /**
+   * A key with a settable hashcode.
+   */
+  static final class Key {
+    private final int hashCode;
+
+    Key(int hashCode) {
+      this.hashCode = hashCode;
+    }
+
+    @Override
+    public int hashCode() {
+      return hashCode;
+    }
+
+    @Override
+    public String toString() {
+      return String.format("Key(hashCode=%x)", hashCode);
+    }
+  }
+}
diff --git a/context/src/test/java/io/grpc/StaticTestingClassLoader.java b/context/src/test/java/io/grpc/StaticTestingClassLoader.java
new file mode 100644
index 0000000..716a887
--- /dev/null
+++ b/context/src/test/java/io/grpc/StaticTestingClassLoader.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.base.Preconditions;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.regex.Pattern;
+
+/**
+ * A class loader that can be used to repeatedly trigger static initialization of a class. A new
+ * instance is required per test.
+ */
+public final class StaticTestingClassLoader extends ClassLoader {
+  private final Pattern classesToDefine;
+
+  public StaticTestingClassLoader(ClassLoader parent, Pattern classesToDefine) {
+    super(parent);
+    this.classesToDefine = Preconditions.checkNotNull(classesToDefine, "classesToDefine");
+  }
+
+  @Override
+  protected Class<?> findClass(String name) throws ClassNotFoundException {
+    if (!classesToDefine.matcher(name).matches()) {
+      throw new ClassNotFoundException(name);
+    }
+    InputStream is = getResourceAsStream(name.replace('.', '/') + ".class");
+    if (is == null) {
+      throw new ClassNotFoundException(name);
+    }
+    byte[] b;
+    try {
+      b = ByteStreams.toByteArray(is);
+    } catch (IOException ex) {
+      throw new ClassNotFoundException(name, ex);
+    }
+    return defineClass(name, b, 0, b.length);
+  }
+
+  @Override
+  protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+    // Reverse normal loading order; check this class loader before its parent
+    synchronized (getClassLoadingLock(name)) {
+      Class<?> klass = findLoadedClass(name);
+      if (klass == null) {
+        try {
+          klass = findClass(name);
+        } catch (ClassNotFoundException e) {
+          // This ClassLoader doesn't know a class with that name; that's part of normal operation
+        }
+      }
+      if (klass == null) {
+        klass = super.loadClass(name, false);
+      }
+      if (resolve) {
+        resolveClass(klass);
+      }
+      return klass;
+    }
+  }
+}
diff --git a/context/src/test/java/io/grpc/testing/DeadlineSubject.java b/context/src/test/java/io/grpc/testing/DeadlineSubject.java
new file mode 100644
index 0000000..4a19b35
--- /dev/null
+++ b/context/src/test/java/io/grpc/testing/DeadlineSubject.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Strings.lenientFormat;
+import static com.google.common.truth.Fact.simpleFact;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import com.google.common.truth.ComparableSubject;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import io.grpc.Deadline;
+import java.math.BigInteger;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nullable;
+
+/**
+ * Propositions for {@link Deadline} subjects.
+ */
+public final class DeadlineSubject extends ComparableSubject<DeadlineSubject, Deadline> {
+  private static final Subject.Factory<DeadlineSubject, Deadline> deadlineFactory =
+      new Factory();
+
+  public static Subject.Factory<DeadlineSubject, Deadline> deadline() {
+    return deadlineFactory;
+  }
+
+  private DeadlineSubject(FailureMetadata metadata, Deadline subject) {
+    super(metadata, subject);
+  }
+
+  /**
+   * Prepares for a check that the subject is deadline within the given tolerance of an
+   * expected value that will be provided in the next call in the fluent chain.
+   */
+  @CheckReturnValue
+  public TolerantDeadlineComparison isWithin(final long delta, final TimeUnit timeUnit) {
+    return new TolerantDeadlineComparison() {
+      @Override
+      public void of(Deadline expected) {
+        Deadline actual = actual();
+        checkNotNull(actual, "actual value cannot be null. expected=%s", expected);
+
+        // This is probably overkill, but easier than thinking about overflow.
+        BigInteger actualTimeRemaining = BigInteger.valueOf(actual.timeRemaining(NANOSECONDS));
+        BigInteger expectedTimeRemaining = BigInteger.valueOf(expected.timeRemaining(NANOSECONDS));
+        BigInteger deltaNanos = BigInteger.valueOf(timeUnit.toNanos(delta));
+        if (actualTimeRemaining.subtract(expectedTimeRemaining).abs().compareTo(deltaNanos) > 0) {
+          failWithoutActual(
+              simpleFact(
+                  lenientFormat(
+                      "%s and <%s> should have been within <%sns> of each other",
+                      actualAsString(), expected, deltaNanos)));
+        }
+      }
+    };
+  }
+
+  // TODO(carl-mastrangelo):  Add a isNotWithin method once there is need for one.  Currently there
+  // is no such method since there is no code that uses it, and would lower our coverage numbers.
+
+  /**
+   * A partially specified proposition about an approximate relationship to a {@code deadline}
+   * subject using a tolerance.
+   */
+  public abstract static class TolerantDeadlineComparison {
+
+    private TolerantDeadlineComparison() {}
+
+    /**
+     * Fails if the subject was expected to be within the tolerance of the given value but was not
+     * <i>or</i> if it was expected <i>not</i> to be within the tolerance but was. The expectation,
+     * subject, and tolerance are all specified earlier in the fluent call chain.
+     */
+    public abstract void of(Deadline expectedDeadline);
+
+    /**
+     * Do not call this method.
+     *
+     * @throws UnsupportedOperationException always
+     * @deprecated {@link Object#equals(Object)} is not supported on TolerantDeadlineComparison
+     *     If you meant to compare deadlines, use {@link #of(Deadline)} instead.
+     */
+    // Deprecation used to signal visual warning in IDE for the unaware users.
+    // This method is created as a precaution and won't be removed as part of deprecation policy.
+    @Deprecated
+    @Override
+    public boolean equals(@Nullable Object o) {
+      throw new UnsupportedOperationException(
+          "If you meant to compare deadlines, use .of(Deadline) instead.");
+    }
+
+    /**
+     * Do not call this method.
+     *
+     * @throws UnsupportedOperationException always
+     * @deprecated {@link Object#hashCode()} is not supported on TolerantDeadlineComparison
+     */
+    // Deprecation used to signal visual warning in IDE for the unaware users.
+    // This method is created as a precaution and won't be removed as part of deprecation policy.
+    @Deprecated
+    @Override
+    public int hashCode() {
+      throw new UnsupportedOperationException("Subject.hashCode() is not supported.");
+    }
+  }
+
+  private static final class Factory implements Subject.Factory<DeadlineSubject, Deadline>  {
+    @Override
+    public DeadlineSubject createSubject(FailureMetadata metadata, Deadline that) {
+      return new DeadlineSubject(metadata, that);
+    }
+  }
+}
diff --git a/core/BUILD.bazel b/core/BUILD.bazel
new file mode 100644
index 0000000..2cdb351
--- /dev/null
+++ b/core/BUILD.bazel
@@ -0,0 +1,63 @@
+java_library(
+    name = "core",
+    srcs = glob([
+        "src/main/java/io/grpc/*.java",
+    ]),
+    resources = glob([
+        "src/main/resources/**",
+    ]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//context",
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_guava_guava//jar",
+    ],
+)
+
+java_library(
+    name = "inprocess",
+    srcs = glob([
+        "src/main/java/io/grpc/inprocess/*.java",
+    ]),
+    visibility = ["//visibility:public"],
+    deps = [
+        ":core",
+        ":internal",
+        "//context",
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_guava_guava//jar",
+    ],
+)
+
+java_library(
+    name = "internal",
+    srcs = glob([
+        "src/main/java/io/grpc/internal/*.java",
+    ]),
+    visibility = ["//:__subpackages__"],
+    deps = [
+        ":core",
+        "//context",
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_code_gson_gson//jar",
+        "@com_google_errorprone_error_prone_annotations//jar",
+        "@com_google_guava_guava//jar",
+        "@io_opencensus_opencensus_api//jar",
+        "@io_opencensus_opencensus_contrib_grpc_metrics//jar",
+        "@org_codehaus_mojo_animal_sniffer_annotations//jar",
+    ],
+)
+
+java_library(
+    name = "util",
+    srcs = glob([
+        "src/main/java/io/grpc/util/*.java",
+    ]),
+    visibility = ["//visibility:public"],
+    deps = [
+        ":core",
+        ":internal",
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_guava_guava//jar",
+    ],
+)
diff --git a/core/build.gradle b/core/build.gradle
new file mode 100644
index 0000000..700592f
--- /dev/null
+++ b/core/build.gradle
@@ -0,0 +1,48 @@
+description = 'gRPC: Core'
+
+dependencies {
+    compile project(':grpc-context'),
+            libraries.gson,
+            libraries.guava,
+            libraries.errorprone,
+            libraries.jsr305,
+            libraries.animalsniffer_annotations
+    compile (libraries.opencensus_api) {
+        // prefer 3.0.0 from libraries instead of 3.0.1
+        exclude group: 'com.google.code.findbugs', module: 'jsr305'
+        // prefer 20.0 from libraries instead of 19.0
+        exclude group: 'com.google.guava', module: 'guava'
+        // we'll always be more up-to-date
+        exclude group: 'io.grpc', module: 'grpc-context'
+    }
+    compile (libraries.opencensus_contrib_grpc_metrics) {
+        // prefer 3.0.0 from libraries instead of 3.0.1
+        exclude group: 'com.google.code.findbugs', module: 'jsr305'
+        // we'll always be more up-to-date
+        exclude group: 'io.grpc', module: 'grpc-context'
+        // prefer 20.0 from libraries instead of 19.0
+        exclude group: 'com.google.guava', module: 'guava'
+    }
+
+    testCompile project(':grpc-context').sourceSets.test.output,
+            project(':grpc-testing'),
+            project(':grpc-grpclb'),
+            libraries.guava_testlib
+
+    signature "org.codehaus.mojo.signature:java17:1.0@signature"
+    signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
+}
+
+javadoc {
+    // We want io.grpc.Internal, but not io.grpc.Internal*
+    exclude 'io/grpc/Internal?*.java'
+    exclude 'io/grpc/internal/**'
+}
+
+animalsniffer {
+    // Don't check sourceSets.jmh
+    sourceSets = [
+        sourceSets.main,
+        sourceSets.test
+    ]
+}
diff --git a/core/src/jmh/java/io/grpc/AttributesBenchmark.java b/core/src/jmh/java/io/grpc/AttributesBenchmark.java
new file mode 100644
index 0000000..f387b81
--- /dev/null
+++ b/core/src/jmh/java/io/grpc/AttributesBenchmark.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Javadoc.
+ */
+@State(Scope.Benchmark)
+public class AttributesBenchmark {
+
+  public Attributes base = Attributes.EMPTY;
+
+  public Attributes.Key<Object>[] keys;
+  public Attributes withValue = base;
+
+  /**
+   * Javadoc.
+   */
+  @Setup
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  public void setUp() {
+    keys = new Attributes.Key[iterations];
+    for (int i = 0; i < iterations; i++) {
+      keys[i] = Attributes.Key.create("any");
+      withValue = withValue.toBuilder().set(keys[i], "yes").build();
+    }
+  }
+
+  @Param({"1", "2", "10"})
+  public int iterations;
+
+  /**
+   * Javadoc.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public Attributes chain() {
+    Attributes attr = base;
+    for (int i = 0; i < iterations; i++) {
+      attr = attr.toBuilder().set(keys[i], new Object()).build();
+    }
+    return attr;
+  }
+
+  /**
+   * Javadoc.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public Object lookup() {
+    return withValue.get(keys[0]);
+  }
+}
diff --git a/core/src/jmh/java/io/grpc/CallOptionsBenchmark.java b/core/src/jmh/java/io/grpc/CallOptionsBenchmark.java
new file mode 100644
index 0000000..cc426e5
--- /dev/null
+++ b/core/src/jmh/java/io/grpc/CallOptionsBenchmark.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Call options benchmark.
+ */
+@State(Scope.Benchmark)
+public class CallOptionsBenchmark {
+
+  @Param({"1", "2", "4", "8"})
+  public int customOptionsCount;
+
+  private List<CallOptions.Key<String>> customOptions;
+
+  private CallOptions allOpts;
+  private List<CallOptions.Key<String>> shuffledCustomOptions;
+
+  /**
+   * Setup.
+   */
+  @Setup
+  public void setUp() throws Exception {
+    customOptions = new ArrayList<CallOptions.Key<String>>(customOptionsCount);
+    for (int i = 0; i < customOptionsCount; i++) {
+      customOptions.add(CallOptions.Key.createWithDefault("name " + i, "defaultvalue"));
+    }
+
+    allOpts = CallOptions.DEFAULT;
+    for (int i = 0; i < customOptionsCount; i++) {
+      allOpts = allOpts.withOption(customOptions.get(i), "value");
+    }
+
+    shuffledCustomOptions = new ArrayList<CallOptions.Key<String>>(customOptions);
+    // Make the shuffling deterministic
+    Collections.shuffle(shuffledCustomOptions, new Random(1));
+  }
+
+  /**
+   * Adding custom call options without duplicate keys.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public CallOptions withOption() {
+    CallOptions opts = CallOptions.DEFAULT;
+    for (int i = 0; i < customOptions.size(); i++) {
+      opts = opts.withOption(customOptions.get(i), "value");
+    }
+    return opts;
+  }
+
+  /**
+   * Adding custom call options, overwritting existing keys.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public CallOptions withOptionDuplicates() {
+    CallOptions opts = allOpts;
+    for (int i = 1; i < shuffledCustomOptions.size(); i++) {
+      opts = opts.withOption(shuffledCustomOptions.get(i), "value2");
+    }
+    return opts;
+  }
+}
diff --git a/core/src/jmh/java/io/grpc/DecompressorRegistryBenchmark.java b/core/src/jmh/java/io/grpc/DecompressorRegistryBenchmark.java
new file mode 100644
index 0000000..65af12e
--- /dev/null
+++ b/core/src/jmh/java/io/grpc/DecompressorRegistryBenchmark.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.TransportFrameUtil;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Decomressor Registry encoding benchmark.
+ */
+@State(Scope.Benchmark)
+public class DecompressorRegistryBenchmark {
+
+  @Param({"0", "1", "2"})
+  public int extraEncodings;
+
+  private DecompressorRegistry reg = DecompressorRegistry.getDefaultInstance();
+
+  @Setup
+  public void setUp() throws Exception {
+    reg = DecompressorRegistry.getDefaultInstance();
+    for (int i = 0; i < extraEncodings; i++) {
+      reg = reg.with(new Decompressor() {
+
+        @Override
+        public String getMessageEncoding() {
+          return UUID.randomUUID().toString();
+
+        }
+
+        @Override
+        public InputStream decompress(InputStream is) throws IOException {
+          return null;
+
+        }
+      }, true);
+    }
+  }
+
+  /**
+   * Javadoc comment.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public byte[][] marshalOld() {
+    Metadata m = new Metadata();
+    m.put(
+        GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY,
+        InternalDecompressorRegistry.getRawAdvertisedMessageEncodings(reg));
+    return TransportFrameUtil.toHttp2Headers(m);
+  }
+}
+
diff --git a/core/src/jmh/java/io/grpc/StatusBenchmark.java b/core/src/jmh/java/io/grpc/StatusBenchmark.java
new file mode 100644
index 0000000..e3f087c
--- /dev/null
+++ b/core/src/jmh/java/io/grpc/StatusBenchmark.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.nio.charset.Charset;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+/** StatusBenchmark. */
+@State(Scope.Benchmark)
+public class StatusBenchmark {
+
+  /**
+   * Javadoc comment.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public byte[] messageEncodePlain() {
+    return Status.MESSAGE_KEY.toBytes("Unexpected RST in stream");
+  }
+
+  /**
+   * Javadoc comment.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public byte[] messageEncodeEscape() {
+    return Status.MESSAGE_KEY.toBytes("Some Error\nWasabi and Horseradish are the same");
+  }
+
+  /**
+   * Javadoc comment.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public String messageDecodePlain() {
+    return Status.MESSAGE_KEY.parseBytes(
+        "Unexpected RST in stream".getBytes(Charset.forName("US-ASCII")));
+  }
+
+  /**
+   * Javadoc comment.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public String messageDecodeEscape() {
+    return Status.MESSAGE_KEY.parseBytes(
+        "Some Error%10Wasabi and Horseradish are the same".getBytes(Charset.forName("US-ASCII")));
+  }
+
+  /**
+   * Javadoc comment.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public byte[] codeEncode() {
+    return Status.CODE_KEY.toBytes(Status.DATA_LOSS);
+  }
+
+  /**
+   * Javadoc comment.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public Status codeDecode() {
+    return Status.CODE_KEY.parseBytes("15".getBytes(Charset.forName("US-ASCII")));
+  }
+}
+
diff --git a/core/src/jmh/java/io/grpc/internal/ChannelzBenchmark.java b/core/src/jmh/java/io/grpc/internal/ChannelzBenchmark.java
new file mode 100644
index 0000000..6c30cd3
--- /dev/null
+++ b/core/src/jmh/java/io/grpc/internal/ChannelzBenchmark.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.ServerStats;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.InternalLogId;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Javadoc.
+ */
+@State(Scope.Benchmark)
+public class ChannelzBenchmark {
+  // Number of items already present
+  @Param({"10", "100", "1000", "10000"})
+  public int preexisting;
+
+  public InternalChannelz channelz = new InternalChannelz();
+
+  public InternalInstrumented<ServerStats> serverToRemove;
+
+  public InternalInstrumented<ServerStats> serverToAdd;
+
+  public InternalInstrumented<ServerStats> serverForServerSocket;
+  public InternalInstrumented<SocketStats> serverSocketToAdd;
+  public InternalInstrumented<SocketStats> serverSocketToRemove;
+
+  /**
+   * Javadoc.
+   */
+  @Setup
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  public void setUp() {
+    serverToRemove = create();
+    channelz.addServer(serverToRemove);
+
+    serverForServerSocket = create();
+    channelz.addServer(serverForServerSocket);
+
+    serverSocketToRemove = create();
+    channelz.addClientSocket(serverSocketToRemove);
+    channelz.addServerSocket(serverForServerSocket, serverSocketToRemove);
+
+    populate(preexisting);
+
+    serverToAdd = create();
+    serverSocketToAdd = create();
+  }
+
+  private void populate(int count) {
+    for (int i = 0; i < count; i++) {
+      // for addNavigable / removeNavigable
+      InternalInstrumented<ServerStats> srv = create();
+      channelz.addServer(srv);
+
+      // for add / remove
+      InternalInstrumented<SocketStats> sock = create();
+      channelz.addClientSocket(sock);
+
+      // for addServerSocket / removeServerSocket
+      channelz.addServerSocket(serverForServerSocket, sock);
+    }
+  }
+
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public void addNavigable() {
+    channelz.addServer(serverToAdd);
+  }
+
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public void add() {
+    channelz.addClientSocket(serverSocketToAdd);
+  }
+
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public void addServerSocket() {
+    channelz.addServerSocket(serverForServerSocket, serverSocketToAdd);
+  }
+
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public void removeNavigable() {
+    channelz.removeServer(serverToRemove);
+  }
+
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public void remove() {
+    channelz.removeClientSocket(serverSocketToRemove);
+  }
+
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public void removeServerSocket() {
+    channelz.removeServerSocket(serverForServerSocket, serverSocketToRemove);
+  }
+
+  private static <T> InternalInstrumented<T> create() {
+    return new InternalInstrumented<T>() {
+      final InternalLogId id = InternalLogId.allocate("fake-tag");
+
+      @Override
+      public ListenableFuture<T> getStats() {
+        throw new UnsupportedOperationException();
+      }
+
+      @Override
+      public InternalLogId getLogId() {
+        return id;
+      }
+    };
+  }
+}
diff --git a/core/src/jmh/java/io/grpc/internal/SerializingExecutorBenchmark.java b/core/src/jmh/java/io/grpc/internal/SerializingExecutorBenchmark.java
new file mode 100644
index 0000000..d37c634
--- /dev/null
+++ b/core/src/jmh/java/io/grpc/internal/SerializingExecutorBenchmark.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Phaser;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+
+/**
+ * SerializingExecutor benchmark.
+ *
+ * <p>Since this is a microbenchmark, don't actually believe the numbers in a strict sense. Instead,
+ * it is a gauge that the code is behaving roughly as expected, to increase confidence that our
+ * understanding of the code is correct (and will behave as expected in other cases). Even more
+ * helpfully it pushes the implementation, which should weed out many multithreading bugs.
+ */
+@State(Scope.Thread)
+public class SerializingExecutorBenchmark {
+
+  private ExecutorService executorService = Executors.newSingleThreadExecutor();
+  private Executor executor = new SerializingExecutor(executorService);
+
+  private static class IncrRunnable implements Runnable {
+    int val;
+
+    @Override
+    public void run() {
+      val++;
+    }
+  }
+
+  private final IncrRunnable incrRunnable = new IncrRunnable();
+
+  private final Phaser phaser = new Phaser(2);
+  private final Runnable phaserRunnable = new Runnable() {
+    @Override
+    public void run() {
+      phaser.arrive();
+    }
+  };
+
+  @TearDown
+  public void tearDown() throws Exception {
+    executorService.shutdownNow();
+    if (!executorService.awaitTermination(1, TimeUnit.SECONDS)) {
+      throw new RuntimeException("executor failed to shut down in a timely fashion");
+    }
+  }
+
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public void oneRunnableLatency() throws Exception {
+    executor.execute(phaserRunnable);
+    phaser.arriveAndAwaitAdvance();
+  }
+
+  /**
+   * Queue many runnables, to better see queuing/consumption cost instead of just context switch.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public void manyRunnables() throws Exception {
+    incrRunnable.val = 0;
+    for (int i = 0; i < 500; i++) {
+      executor.execute(incrRunnable);
+    }
+    executor.execute(phaserRunnable);
+    phaser.arriveAndAwaitAdvance();
+    if (incrRunnable.val != 500) {
+      throw new AssertionError();
+    }
+  }
+}
diff --git a/core/src/jmh/java/io/grpc/internal/StatsTraceContextBenchmark.java b/core/src/jmh/java/io/grpc/internal/StatsTraceContextBenchmark.java
new file mode 100644
index 0000000..80ed9fc
--- /dev/null
+++ b/core/src/jmh/java/io/grpc/internal/StatsTraceContextBenchmark.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.CallOptions;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerStreamTracer;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Benchmark for {@link StatsTraceContext}.
+ */
+@State(Scope.Benchmark)
+public class StatsTraceContextBenchmark {
+
+  private final String methodName = MethodDescriptor.generateFullMethodName("service", "method");
+
+  private final Metadata emptyMetadata = new Metadata();
+  private final List<ServerStreamTracer.Factory> serverStreamTracerFactories =
+      Collections.<ServerStreamTracer.Factory>emptyList();
+
+  /**
+   * Javadoc comment.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public StatsTraceContext newClientContext() {
+    return StatsTraceContext.newClientContext(CallOptions.DEFAULT, emptyMetadata);
+  }
+
+  /**
+   * Javadoc comment.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public StatsTraceContext newServerContext_empty() {
+    return StatsTraceContext.newServerContext(
+        serverStreamTracerFactories, methodName, emptyMetadata);
+  }
+}
diff --git a/core/src/main/java/io/grpc/Attributes.java b/core/src/main/java/io/grpc/Attributes.java
new file mode 100644
index 0000000..9a13f51
--- /dev/null
+++ b/core/src/main/java/io/grpc/Attributes.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Objects;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * An immutable type-safe container of attributes.
+ * @since 1.13.0
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1764")
+@Immutable
+public final class Attributes {
+
+  private final Map<Key<?>, Object> data;
+
+  public static final Attributes EMPTY = new Attributes(Collections.<Key<?>, Object>emptyMap());
+
+  private Attributes(Map<Key<?>, Object> data) {
+    assert data != null;
+    this.data = data;
+  }
+
+  /**
+   * Gets the value for the key, or {@code null} if it's not present.
+   */
+  @SuppressWarnings("unchecked")
+  @Nullable
+  public <T> T get(Key<T> key) {
+    return (T) data.get(key);
+  }
+
+  /**
+   * Returns set of keys stored in container.
+   *
+   * @return Set of Key objects.
+   * @deprecated This method is being considered for removal, if you feel this method is needed
+   *     please reach out on this Github issue:
+   *     <a href="https://github.com/grpc/grpc-java/issues/1764">grpc-java/issues/1764</a>.
+   */
+  @Deprecated
+  public Set<Key<?>> keys() {
+    return Collections.unmodifiableSet(data.keySet());
+  }
+
+  Set<Key<?>> keysForTest() {
+    return Collections.unmodifiableSet(data.keySet());
+  }
+
+  /**
+   * Create a new builder that is pre-populated with the content from a given container.
+   * @deprecated Use {@link Attributes#toBuilder()} on the {@link Attributes} instance instead.
+   *     This method will be removed in the future.
+   */
+  @Deprecated
+  public static Builder newBuilder(Attributes base) {
+    checkNotNull(base, "base");
+    return new Builder(base);
+  }
+
+  /**
+   * Create a new builder.
+   */
+  public static Builder newBuilder() {
+    return new Builder(EMPTY);
+  }
+
+  /**
+   * Creates a new builder that is pre-populated with the content of this container.
+   * @return a new builder.
+   */
+  public Builder toBuilder() {
+    return new Builder(this);
+  }
+
+  /**
+   * Key for an key-value pair.
+   * @param <T> type of the value in the key-value pair
+   */
+  @Immutable
+  public static final class Key<T> {
+    private final String debugString;
+
+    private Key(String debugString) {
+      this.debugString = debugString;
+    }
+
+    @Override
+    public String toString() {
+      return debugString;
+    }
+
+    /**
+     * Factory method for creating instances of {@link Key}.
+     *
+     * @param debugString a string used to describe the key, used for debugging.
+     * @param <T> Key type
+     * @return Key object
+     * @deprecated use {@link #create} instead. This method will be removed in the future.
+     */
+    @Deprecated
+    public static <T> Key<T> of(String debugString) {
+      return new Key<T>(debugString);
+    }
+
+    /**
+     * Factory method for creating instances of {@link Key}.
+     *
+     * @param debugString a string used to describe the key, used for debugging.
+     * @param <T> Key type
+     * @return Key object
+     */
+    public static <T> Key<T> create(String debugString) {
+      return new Key<T>(debugString);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return data.toString();
+  }
+
+  /**
+   * Returns true if the given object is also a {@link Attributes} with an equal attribute values.
+   *
+   * <p>Note that if a stored values are mutable, it is possible for two objects to be considered
+   * equal at one point in time and not equal at another (due to concurrent mutation of attribute
+   * values).
+   *
+   * <p>This method is not implemented efficiently and is meant for testing.
+   *
+   * @param o an object.
+   * @return true if the given object is a {@link Attributes} equal attributes.
+   */
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Attributes that = (Attributes) o;
+    if (data.size() != that.data.size()) {
+      return false;
+    }
+    for (Entry<Key<?>, Object> e : data.entrySet()) {
+      if (!that.data.containsKey(e.getKey())) {
+        return false;
+      }
+      if (!Objects.equal(e.getValue(), that.data.get(e.getKey()))) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns a hash code for the attributes.
+   *
+   * <p>Note that if a stored values are mutable, it is possible for two objects to be considered
+   * equal at one point in time and not equal at another (due to concurrent mutation of attribute
+   * values).
+   *
+   * @return a hash code for the attributes map.
+   */
+  @Override
+  public int hashCode() {
+    int hashCode = 0;
+    for (Entry<Key<?>, Object> e : data.entrySet()) {
+      hashCode += Objects.hashCode(e.getKey(), e.getValue());
+    }
+    return hashCode;
+  }
+
+  /**
+   * The helper class to build an Attributes instance.
+   */
+  public static final class Builder {
+    private Attributes base;
+    private Map<Key<?>, Object> newdata;
+
+    private Builder(Attributes base) {
+      assert base != null;
+      this.base = base;
+    }
+
+    private Map<Key<?>, Object> data(int size) {
+      if (newdata == null) {
+        newdata = new IdentityHashMap<Key<?>, Object>(size);
+      }
+      return newdata;
+    }
+
+    public <T> Builder set(Key<T> key, T value) {
+      data(1).put(key, value);
+      return this;
+    }
+
+    public <T> Builder setAll(Attributes other) {
+      data(other.data.size()).putAll(other.data);
+      return this;
+    }
+
+    /**
+     * Build the attributes.
+     */
+    public Attributes build() {
+      if (newdata != null) {
+        for (Entry<Key<?>, Object> entry : base.data.entrySet()) {
+          if (!newdata.containsKey(entry.getKey())) {
+            newdata.put(entry.getKey(), entry.getValue());
+          }
+        }
+        base = new Attributes(newdata);
+        newdata = null;
+      }
+      return base;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/BinaryLog.java b/core/src/main/java/io/grpc/BinaryLog.java
new file mode 100644
index 0000000..5c2d4ab
--- /dev/null
+++ b/core/src/main/java/io/grpc/BinaryLog.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2018, gRPC Authors All rights reserved.
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.io.Closeable;
+
+/**
+ * A binary log that can be installed on a channel or server. {@link #close} must be called after
+ * all the servers and channels associated with the binary log are terminated.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4017")
+public abstract class BinaryLog implements Closeable {
+
+  public abstract <ReqT, RespT> ServerMethodDefinition<?, ?> wrapMethodDefinition(
+      ServerMethodDefinition<ReqT, RespT> oMethodDef);
+
+  public abstract Channel wrapChannel(Channel channel);
+
+  /**
+   * A CallId is two byte[] arrays both of size 8 that uniquely identifies the RPC. Users are
+   * free to use the byte arrays however they see fit.
+   */
+  public static final class CallId {
+    public final long hi;
+    public final long lo;
+
+    /**
+     * Creates an instance.
+     */
+    public CallId(long hi, long lo) {
+      this.hi = hi;
+      this.lo = lo;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/BindableService.java b/core/src/main/java/io/grpc/BindableService.java
new file mode 100644
index 0000000..682d959
--- /dev/null
+++ b/core/src/main/java/io/grpc/BindableService.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * Provides a way to bind instance of service implementation to server.
+ *
+ * <p>It is used by service's abstract class generated by compiler (eg.:
+ * RouteGuideGrpc.RouteGuideImplBase for RouteGuide service) and lets implementation classes to be
+ * bind to server.
+ *
+ * <pre><code>
+ * class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
+ *   // ...
+ * }
+ *
+ * Server server = ServerBuilder.forPort(1234).addService(new RouteGuideService()).build();
+ * </code></pre></p>
+ */
+public interface BindableService {
+  /**
+   * Creates {@link ServerServiceDefinition} object for current instance of service implementation.
+   *
+   * @return ServerServiceDefinition object.
+   */
+  ServerServiceDefinition bindService();
+}
diff --git a/core/src/main/java/io/grpc/CallCredentials.java b/core/src/main/java/io/grpc/CallCredentials.java
new file mode 100644
index 0000000..7ce6219
--- /dev/null
+++ b/core/src/main/java/io/grpc/CallCredentials.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import io.grpc.Attributes.Key;
+import java.util.concurrent.Executor;
+
+/**
+ * Carries credential data that will be propagated to the server via request metadata for each RPC.
+ *
+ * <p>This is used by {@link CallOptions#withCallCredentials} and {@code withCallCredentials()} on
+ * the generated stub, for example:
+ * <pre>
+ * FooGrpc.FooStub stub = FooGrpc.newStub(channel);
+ * response = stub.withCallCredentials(creds).bar(request);
+ * </pre>
+ *
+ * <p>The contents and nature of this interface (and whether it remains an interface) is
+ * experimental, in that it can change. However, we are guaranteeing stability for the
+ * <em>name</em>. That is, we are guaranteeing stability for code to be returned a reference and
+ * pass that reference to gRPC for usage. However, code may not call or implement the {@code
+ * CallCredentials} itself if it wishes to only use stable APIs.
+ */
+public interface CallCredentials {
+  /**
+   * The security level of the transport. It is guaranteed to be present in the {@code attrs} passed
+   * to {@link #applyRequestMetadata}. It is by default {@link SecurityLevel#NONE} but can be
+   * overridden by the transport.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914")
+  public static final Key<SecurityLevel> ATTR_SECURITY_LEVEL =
+      Key.create("io.grpc.CallCredentials.securityLevel");
+
+  /**
+   * The authority string used to authenticate the server. Usually it's the server's host name. It
+   * is guaranteed to be present in the {@code attrs} passed to {@link #applyRequestMetadata}. It is
+   * by default from the channel, but can be overridden by the transport and {@link
+   * io.grpc.CallOptions} with increasing precedence.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914")
+  public static final Key<String> ATTR_AUTHORITY = Key.create("io.grpc.CallCredentials.authority");
+
+  /**
+   * Pass the credential data to the given {@link MetadataApplier}, which will propagate it to
+   * the request metadata.
+   *
+   * <p>It is called for each individual RPC, within the {@link Context} of the call, before the
+   * stream is about to be created on a transport. Implementations should not block in this
+   * method. If metadata is not immediately available, e.g., needs to be fetched from network, the
+   * implementation may give the {@code applier} to an asynchronous task which will eventually call
+   * the {@code applier}. The RPC proceeds only after the {@code applier} is called.
+   *
+   * @param method The method descriptor of this RPC
+   * @param attrs Additional attributes from the transport, along with the keys defined in this
+   *        interface (i.e. the {@code ATTR_*} fields) which are guaranteed to be present.
+   * @param appExecutor The application thread-pool. It is provided to the implementation in case it
+   *        needs to perform blocking operations.
+   * @param applier The outlet of the produced headers. It can be called either before or after this
+   *        method returns.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914")
+  void applyRequestMetadata(
+      MethodDescriptor<?, ?> method, Attributes attrs,
+      Executor appExecutor, MetadataApplier applier);
+
+  /**
+   * Should be a noop but never called; tries to make it clearer to implementors that they may break
+   * in the future.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914")
+  void thisUsesUnstableApi();
+
+  /**
+   * The outlet of the produced headers. Not thread-safe.
+   *
+   * <p>Exactly one of its methods must be called to make the RPC proceed.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1914")
+  public interface MetadataApplier {
+    /**
+     * Called when headers are successfully generated. They will be merged into the original
+     * headers.
+     */
+    void apply(Metadata headers);
+
+    /**
+     * Called when there has been an error when preparing the headers. This will fail the RPC.
+     */
+    void fail(Status status);
+  }
+}
diff --git a/core/src/main/java/io/grpc/CallOptions.java b/core/src/main/java/io/grpc/CallOptions.java
new file mode 100644
index 0000000..1b8efb2
--- /dev/null
+++ b/core/src/main/java/io/grpc/CallOptions.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * The collection of runtime options for a new RPC call.
+ *
+ * <p>A field that is not set is {@code null}.
+ */
+@Immutable
+@CheckReturnValue
+public final class CallOptions {
+  /**
+   * A blank {@code CallOptions} that all fields are not set.
+   */
+  public static final CallOptions DEFAULT = new CallOptions();
+
+  // Although {@code CallOptions} is immutable, its fields are not final, so that we can initialize
+  // them outside of constructor. Otherwise the constructor will have a potentially long list of
+  // unnamed arguments, which is undesirable.
+  private Deadline deadline;
+  private Executor executor;
+
+  @Nullable
+  private String authority;
+
+  @Nullable
+  private CallCredentials credentials;
+
+  @Nullable
+  private String compressorName;
+
+  private Object[][] customOptions = new Object[0][2];
+
+  // Unmodifiable list
+  private List<ClientStreamTracer.Factory> streamTracerFactories = Collections.emptyList();
+
+  /**
+   * Opposite to fail fast.
+   */
+  private boolean waitForReady;
+
+  @Nullable
+  private Integer maxInboundMessageSize;
+  @Nullable
+  private Integer maxOutboundMessageSize;
+
+
+  /**
+   * Override the HTTP/2 authority the channel claims to be connecting to. <em>This is not
+   * generally safe.</em> Overriding allows advanced users to re-use a single Channel for multiple
+   * services, even if those services are hosted on different domain names. That assumes the
+   * server is virtually hosting multiple domains and is guaranteed to continue doing so. It is
+   * rare for a service provider to make such a guarantee. <em>At this time, there is no security
+   * verification of the overridden value, such as making sure the authority matches the server's
+   * TLS certificate.</em>
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1767")
+  public CallOptions withAuthority(@Nullable String authority) {
+    CallOptions newOptions = new CallOptions(this);
+    newOptions.authority = authority;
+    return newOptions;
+  }
+
+  /**
+   * Returns a new {@code CallOptions} with the given call credentials.
+   */
+  public CallOptions withCallCredentials(@Nullable CallCredentials credentials) {
+    CallOptions newOptions = new CallOptions(this);
+    newOptions.credentials = credentials;
+    return newOptions;
+  }
+
+  /**
+   * Sets the compression to use for the call.  The compressor must be a valid name known in the
+   * {@link CompressorRegistry}.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+  public CallOptions withCompression(@Nullable String compressorName) {
+    CallOptions newOptions = new CallOptions(this);
+    newOptions.compressorName = compressorName;
+    return newOptions;
+  }
+
+  /**
+   * Returns a new {@code CallOptions} with the given absolute deadline.
+   *
+   * <p>This is mostly used for propagating an existing deadline. {@link #withDeadlineAfter} is the
+   * recommended way of setting a new deadline,
+   *
+   * @param deadline the deadline or {@code null} for unsetting the deadline.
+   */
+  public CallOptions withDeadline(@Nullable Deadline deadline) {
+    CallOptions newOptions = new CallOptions(this);
+    newOptions.deadline = deadline;
+    return newOptions;
+  }
+
+  /**
+   * Returns a new {@code CallOptions} with a deadline that is after the given {@code duration} from
+   * now.
+   */
+  public CallOptions withDeadlineAfter(long duration, TimeUnit unit) {
+    return withDeadline(Deadline.after(duration, unit));
+  }
+
+  /**
+   * Returns the deadline or {@code null} if the deadline is not set.
+   */
+  @Nullable
+  public Deadline getDeadline() {
+    return deadline;
+  }
+
+  /**
+   * Enables <a href="https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md">
+   * 'wait for ready'</a> feature for the call. 'Fail fast' is the default option for gRPC calls
+   * and 'wait for ready' is the opposite to it.
+   */
+  public CallOptions withWaitForReady() {
+    CallOptions newOptions = new CallOptions(this);
+    newOptions.waitForReady = true;
+    return newOptions;
+  }
+
+  /**
+   * Disables 'wait for ready' feature for the call.
+   * This method should be rarely used because the default is without 'wait for ready'.
+   */
+  public CallOptions withoutWaitForReady() {
+    CallOptions newOptions = new CallOptions(this);
+    newOptions.waitForReady = false;
+    return newOptions;
+  }
+
+  /**
+   * Returns the compressor's name.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+  @Nullable
+  public String getCompressor() {
+    return compressorName;
+  }
+
+  /**
+   * Override the HTTP/2 authority the channel claims to be connecting to. <em>This is not
+   * generally safe.</em> Overriding allows advanced users to re-use a single Channel for multiple
+   * services, even if those services are hosted on different domain names. That assumes the
+   * server is virtually hosting multiple domains and is guaranteed to continue doing so. It is
+   * rare for a service provider to make such a guarantee. <em>At this time, there is no security
+   * verification of the overridden value, such as making sure the authority matches the server's
+   * TLS certificate.</em>
+   */
+  @Nullable
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1767")
+  public String getAuthority() {
+    return authority;
+  }
+
+  /**
+   * Returns the call credentials.
+   */
+  @Nullable
+  public CallCredentials getCredentials() {
+    return credentials;
+  }
+
+  /**
+   * Returns a new {@code CallOptions} with {@code executor} to be used instead of the default
+   * executor specified with {@link ManagedChannelBuilder#executor}.
+   */
+  public CallOptions withExecutor(Executor executor) {
+    CallOptions newOptions = new CallOptions(this);
+    newOptions.executor = executor;
+    return newOptions;
+  }
+
+  /**
+   * Returns a new {@code CallOptions} with a {@code ClientStreamTracerFactory} in addition to
+   * the existing factories.
+   *
+   * <p>This method doesn't replace existing factories, or try to de-duplicate factories.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
+  public CallOptions withStreamTracerFactory(ClientStreamTracer.Factory factory) {
+    CallOptions newOptions = new CallOptions(this);
+    ArrayList<ClientStreamTracer.Factory> newList =
+        new ArrayList<ClientStreamTracer.Factory>(streamTracerFactories.size() + 1);
+    newList.addAll(streamTracerFactories);
+    newList.add(factory);
+    newOptions.streamTracerFactories = Collections.unmodifiableList(newList);
+    return newOptions;
+  }
+
+  /**
+   * Returns an immutable list of {@code ClientStreamTracerFactory}s.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
+  public List<ClientStreamTracer.Factory> getStreamTracerFactories() {
+    return streamTracerFactories;
+  }
+
+  /**
+   * Key for a key-value pair. Uses reference equality.
+   */
+  public static final class Key<T> {
+    private final String debugString;
+    private final T defaultValue;
+
+    private Key(String debugString, T defaultValue) {
+      this.debugString = debugString;
+      this.defaultValue = defaultValue;
+    }
+
+    /**
+     * Returns the user supplied default value for this key.
+     */
+    public T getDefault() {
+      return defaultValue;
+    }
+
+    @Override
+    public String toString() {
+      return debugString;
+    }
+
+    /**
+     * Factory method for creating instances of {@link Key}.
+     *
+     * @param debugString a string used to describe this key, used for debugging.
+     * @param defaultValue default value to return when value for key not set
+     * @param <T> Key type
+     * @return Key object
+     * @deprecated Use {@link #create} or {@link #createWithDefault} instead. This method will
+     *     be removed.
+     */
+    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1869")
+    @Deprecated
+    public static <T> Key<T> of(String debugString, T defaultValue) {
+      Preconditions.checkNotNull(debugString, "debugString");
+      return new Key<T>(debugString, defaultValue);
+    }
+
+    /**
+     * Factory method for creating instances of {@link Key}. The default value of the
+     * key is {@code null}.
+     *
+     * @param debugString a debug string that describes this key.
+     * @param <T> Key type
+     * @return Key object
+     * @since 1.13.0
+     */
+    public static <T> Key<T> create(String debugString) {
+      Preconditions.checkNotNull(debugString, "debugString");
+      return new Key<T>(debugString, /*defaultValue=*/ null);
+    }
+
+    /**
+     * Factory method for creating instances of {@link Key}.
+     *
+     * @param debugString a debug string that describes this key.
+     * @param defaultValue default value to return when value for key not set
+     * @param <T> Key type
+     * @return Key object
+     * @since 1.13.0
+     */
+    public static <T> Key<T> createWithDefault(String debugString, T defaultValue) {
+      Preconditions.checkNotNull(debugString, "debugString");
+      return new Key<T>(debugString, defaultValue);
+    }
+  }
+
+  /**
+   * Sets a custom option. Any existing value for the key is overwritten.
+   *
+   * @param key The option key
+   * @param value The option value.
+   * @since 1.13.0
+   */
+  public <T> CallOptions withOption(Key<T> key, T value) {
+    Preconditions.checkNotNull(key, "key");
+    Preconditions.checkNotNull(value, "value");
+
+    CallOptions newOptions = new CallOptions(this);
+    int existingIdx = -1;
+    for (int i = 0; i < customOptions.length; i++) {
+      if (key.equals(customOptions[i][0])) {
+        existingIdx = i;
+        break;
+      }
+    }
+
+    newOptions.customOptions = new Object[customOptions.length + (existingIdx == -1 ? 1 : 0)][2];
+    System.arraycopy(customOptions, 0, newOptions.customOptions, 0, customOptions.length);
+
+    if (existingIdx == -1) {
+      // Add a new option
+      newOptions.customOptions[customOptions.length] = new Object[] {key, value};
+    } else {
+      // Replace an existing option
+      newOptions.customOptions[existingIdx][1] = value;
+    }
+
+    return newOptions;
+  }
+
+  /**
+   * Get the value for a custom option or its inherent default.
+   * @param key Key identifying option
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1869")
+  @SuppressWarnings("unchecked")
+  public <T> T getOption(Key<T> key) {
+    Preconditions.checkNotNull(key, "key");
+    for (int i = 0; i < customOptions.length; i++) {
+      if (key.equals(customOptions[i][0])) {
+        return (T) customOptions[i][1];
+      }
+    }
+    return key.defaultValue;
+  }
+
+  @Nullable
+  public Executor getExecutor() {
+    return executor;
+  }
+
+  private CallOptions() {
+  }
+
+  /**
+   * Returns whether <a href="https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md">
+   * 'wait for ready'</a> option is enabled for the call. 'Fail fast' is the default option for gRPC
+   * calls and 'wait for ready' is the opposite to it.
+   */
+  public boolean isWaitForReady() {
+    return waitForReady;
+  }
+
+  /**
+   * Sets the maximum allowed message size acceptable from the remote peer.  If unset, this will
+   * default to the value set on the {@link ManagedChannelBuilder#maxInboundMessageSize(int)}.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2563")
+  public CallOptions withMaxInboundMessageSize(int maxSize) {
+    checkArgument(maxSize >= 0, "invalid maxsize %s", maxSize);
+    CallOptions newOptions = new CallOptions(this);
+    newOptions.maxInboundMessageSize = maxSize;
+    return newOptions;
+  }
+
+  /**
+   * Sets the maximum allowed message size acceptable sent to the remote peer.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2563")
+  public CallOptions withMaxOutboundMessageSize(int maxSize) {
+    checkArgument(maxSize >= 0, "invalid maxsize %s", maxSize);
+    CallOptions newOptions = new CallOptions(this);
+    newOptions.maxOutboundMessageSize = maxSize;
+    return newOptions;
+  }
+
+  /**
+   * Gets the maximum allowed message size acceptable from the remote peer.
+   */
+  @Nullable
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2563")
+  public Integer getMaxInboundMessageSize() {
+    return maxInboundMessageSize;
+  }
+
+  /**
+   * Gets the maximum allowed message size acceptable to send the remote peer.
+   */
+  @Nullable
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2563")
+  public Integer getMaxOutboundMessageSize() {
+    return maxOutboundMessageSize;
+  }
+
+  /**
+   * Copy constructor.
+   */
+  private CallOptions(CallOptions other) {
+    deadline = other.deadline;
+    authority = other.authority;
+    credentials = other.credentials;
+    executor = other.executor;
+    compressorName = other.compressorName;
+    customOptions = other.customOptions;
+    waitForReady = other.waitForReady;
+    maxInboundMessageSize = other.maxInboundMessageSize;
+    maxOutboundMessageSize = other.maxOutboundMessageSize;
+    streamTracerFactories = other.streamTracerFactories;
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("deadline", deadline)
+        .add("authority", authority)
+        .add("callCredentials", credentials)
+        .add("executor", executor != null ? executor.getClass() : null)
+        .add("compressorName", compressorName)
+        .add("customOptions", Arrays.deepToString(customOptions))
+        .add("waitForReady", isWaitForReady())
+        .add("maxInboundMessageSize", maxInboundMessageSize)
+        .add("maxOutboundMessageSize", maxOutboundMessageSize)
+        .add("streamTracerFactories", streamTracerFactories)
+        .toString();
+  }
+}
diff --git a/core/src/main/java/io/grpc/Channel.java b/core/src/main/java/io/grpc/Channel.java
new file mode 100644
index 0000000..60ff76f
--- /dev/null
+++ b/core/src/main/java/io/grpc/Channel.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * A virtual connection to a conceptual endpoint, to perform RPCs. A channel is free to have zero or
+ * many actual connections to the endpoint based on configuration, load, etc. A channel is also free
+ * to determine which actual endpoints to use and may change it every RPC, permitting client-side
+ * load balancing. Applications are generally expected to use stubs instead of calling this class
+ * directly.
+ *
+ * <p>Applications can add common cross-cutting behaviors to stubs by decorating Channel
+ * implementations using {@link ClientInterceptor}. It is expected that most application
+ * code will not use this class directly but rather work with stubs that have been bound to a
+ * Channel that was decorated during application initialization.
+ */
+@ThreadSafe
+public abstract class Channel {
+  /**
+   * Create a {@link ClientCall} to the remote operation specified by the given
+   * {@link MethodDescriptor}. The returned {@link ClientCall} does not trigger any remote
+   * behavior until {@link ClientCall#start(ClientCall.Listener, Metadata)} is
+   * invoked.
+   *
+   * @param methodDescriptor describes the name and parameter types of the operation to call.
+   * @param callOptions runtime options to be applied to this call.
+   * @return a {@link ClientCall} bound to the specified method.
+   * @since 1.0.0
+   */
+  public abstract <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
+      MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions);
+
+  /**
+   * The authority of the destination this channel connects to. Typically this is in the format
+   * {@code host:port}.
+   *
+   * @since 1.0.0
+   */
+  public abstract String authority();
+}
diff --git a/core/src/main/java/io/grpc/ClientCall.java b/core/src/main/java/io/grpc/ClientCall.java
new file mode 100644
index 0000000..936cb32
--- /dev/null
+++ b/core/src/main/java/io/grpc/ClientCall.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import javax.annotation.Nullable;
+
+/**
+ * An instance of a call to a remote method. A call will send zero or more
+ * request messages to the server and receive zero or more response messages back.
+ *
+ * <p>Instances are created
+ * by a {@link Channel} and used by stubs to invoke their remote behavior.
+ *
+ * <p>More advanced usages may consume this interface directly as opposed to using a stub. Common
+ * reasons for doing so would be the need to interact with flow-control or when acting as a generic
+ * proxy for arbitrary operations.
+ *
+ * <p>{@link #start} must be called prior to calling any other methods, with the exception of
+ * {@link #cancel}. Whereas {@link #cancel} must not be followed by any other methods,
+ * but can be called more than once, while only the first one has effect.
+ *
+ * <p>No generic method for determining message receipt or providing acknowledgement is provided.
+ * Applications are expected to utilize normal payload messages for such signals, as a response
+ * naturally acknowledges its request.
+ *
+ * <p>Methods are guaranteed to be non-blocking. Not thread-safe except for {@link #request}, which
+ * may be called from any thread.
+ *
+ * <p>There is no interaction between the states on the {@link Listener Listener} and {@link
+ * ClientCall}, i.e., if {@link Listener#onClose Listener.onClose()} is called, it has no bearing on
+ * the permitted operations on {@code ClientCall} (but it may impact whether they do anything).
+ *
+ * <p>There is a race between {@link #cancel} and the completion/failure of the RPC in other ways.
+ * If {@link #cancel} won the race, {@link Listener#onClose Listener.onClose()} is called with
+ * {@link Status#CANCELLED CANCELLED}. Otherwise, {@link Listener#onClose Listener.onClose()} is
+ * called with whatever status the RPC was finished. We ensure that at most one is called.
+ *
+ * <h3>Usage examples</h3>
+ * <h4>Simple Unary (1 request, 1 response) RPC</h4>
+ * <pre>
+ *   call = channel.newCall(unaryMethod, callOptions);
+ *   call.start(listener, headers);
+ *   call.sendMessage(message);
+ *   call.halfClose();
+ *   call.request(1);
+ *   // wait for listener.onMessage()
+ * </pre>
+ *
+ * <h4>Flow-control in Streaming RPC</h4>
+ *
+ * <p>The following snippet demonstrates a bi-directional streaming case, where the client sends
+ * requests produced by a fictional <code>makeNextRequest()</code> in a flow-control-compliant
+ * manner, and notifies gRPC library to receive additional response after one is consumed by
+ * a fictional <code>processResponse()</code>.
+ *
+ * <p><pre>
+ *   call = channel.newCall(bidiStreamingMethod, callOptions);
+ *   listener = new ClientCall.Listener&lt;FooResponse&gt;() {
+ *     &#64;Override
+ *     public void onMessage(FooResponse response) {
+ *       processResponse(response);
+ *       // Notify gRPC to receive one additional response.
+ *       call.request(1);
+ *     }
+ *
+ *     &#64;Override
+ *     public void onReady() {
+ *       while (call.isReady()) {
+ *         FooRequest nextRequest = makeNextRequest();
+ *         if (nextRequest == null) {  // No more requests to send
+ *           call.halfClose();
+ *           return;
+ *         }
+ *         call.sendMessage(nextRequest);
+ *       }
+ *     }
+ *   }
+ *   call.start(listener, headers);
+ *   // Notify gRPC to receive one response. Without this line, onMessage() would never be called.
+ *   call.request(1);
+ * </pre>
+ *
+ * <p>DO NOT MOCK: Use InProcessServerBuilder and make a test server instead.
+ *
+ * @param <ReqT> type of message sent one or more times to the server.
+ * @param <RespT> type of message received one or more times from the server.
+ */
+public abstract class ClientCall<ReqT, RespT> {
+  /**
+   * Callbacks for receiving metadata, response messages and completion status from the server.
+   *
+   * <p>Implementations are free to block for extended periods of time. Implementations are not
+   * required to be thread-safe.
+   */
+  public abstract static class Listener<T> {
+
+    /**
+     * The response headers have been received. Headers always precede messages.
+     *
+     * <p>Since {@link Metadata} is not thread-safe, the caller must not access (read or write)
+     * {@code headers} after this point.
+     *
+     * @param headers containing metadata sent by the server at the start of the response.
+     */
+    public void onHeaders(Metadata headers) {}
+
+    /**
+     * A response message has been received. May be called zero or more times depending on whether
+     * the call response is empty, a single message or a stream of messages.
+     *
+     * @param message returned by the server
+     */
+    public void onMessage(T message) {}
+
+    /**
+     * The ClientCall has been closed. Any additional calls to the {@code ClientCall} will not be
+     * processed by the server. No further receiving will occur and no further notifications will be
+     * made.
+     *
+     * <p>Since {@link Metadata} is not thread-safe, the caller must not access (read or write)
+     * {@code trailers} after this point.
+     *
+     * <p>If {@code status} returns false for {@link Status#isOk()}, then the call failed.
+     * An additional block of trailer metadata may be received at the end of the call from the
+     * server. An empty {@link Metadata} object is passed if no trailers are received.
+     *
+     * @param status the result of the remote call.
+     * @param trailers metadata provided at call completion.
+     */
+    public void onClose(Status status, Metadata trailers) {}
+
+    /**
+     * This indicates that the ClientCall is now capable of sending additional messages (via
+     * {@link #sendMessage}) without requiring excessive buffering internally. This event is
+     * just a suggestion and the application is free to ignore it, however doing so may
+     * result in excessive buffering within the ClientCall.
+     *
+     * <p>If the type of a call is either {@link MethodDescriptor.MethodType#UNARY} or
+     * {@link MethodDescriptor.MethodType#SERVER_STREAMING}, this callback may not be fired. Calls
+     * that send exactly one message should not await this callback.
+     */
+    public void onReady() {}
+  }
+
+  /**
+   * Start a call, using {@code responseListener} for processing response messages.
+   *
+   * <p>It must be called prior to any other method on this class, except for {@link #cancel} which
+   * may be called at any time.
+   *
+   * <p>Since {@link Metadata} is not thread-safe, the caller must not access (read or write) {@code
+   * headers} after this point.
+   *
+   * @param responseListener receives response messages
+   * @param headers which can contain extra call metadata, e.g. authentication credentials.
+   * @throws IllegalStateException if a method (including {@code start()}) on this class has been
+   *                               called.
+   */
+  public abstract void start(Listener<RespT> responseListener, Metadata headers);
+
+  /**
+   * Requests up to the given number of messages from the call to be delivered to
+   * {@link Listener#onMessage(Object)}. No additional messages will be delivered.
+   *
+   * <p>Message delivery is guaranteed to be sequential in the order received. In addition, the
+   * listener methods will not be accessed concurrently. While it is not guaranteed that the same
+   * thread will always be used, it is guaranteed that only a single thread will access the listener
+   * at a time.
+   *
+   * <p>If it is desired to bypass inbound flow control, a very large number of messages can be
+   * specified (e.g. {@link Integer#MAX_VALUE}).
+   *
+   * <p>If called multiple times, the number of messages able to delivered will be the sum of the
+   * calls.
+   *
+   * <p>This method is safe to call from multiple threads without external synchronization.
+   *
+   * @param numMessages the requested number of messages to be delivered to the listener. Must be
+   *                    non-negative.
+   * @throws IllegalStateException if call is not {@code start()}ed
+   * @throws IllegalArgumentException if numMessages is negative
+   */
+  public abstract void request(int numMessages);
+
+  /**
+   * Prevent any further processing for this {@code ClientCall}. No further messages may be sent or
+   * will be received. The server is informed of cancellations, but may not stop processing the
+   * call. Cancellation is permitted if previously {@link #halfClose}d. Cancelling an already {@code
+   * cancel()}ed {@code ClientCall} has no effect.
+   *
+   * <p>No other methods on this class can be called after this method has been called.
+   *
+   * <p>It is recommended that at least one of the arguments to be non-{@code null}, to provide
+   * useful debug information. Both argument being null may log warnings and result in suboptimal
+   * performance. Also note that the provided information will not be sent to the server.
+   *
+   * @param message if not {@code null}, will appear as the description of the CANCELLED status
+   * @param cause if not {@code null}, will appear as the cause of the CANCELLED status
+   */
+  public abstract void cancel(@Nullable String message, @Nullable Throwable cause);
+
+  /**
+   * Close the call for request message sending. Incoming response messages are unaffected.  This
+   * should be called when no more messages will be sent from the client.
+   *
+   * @throws IllegalStateException if call is already {@code halfClose()}d or {@link #cancel}ed
+   */
+  public abstract void halfClose();
+
+  /**
+   * Send a request message to the server. May be called zero or more times depending on how many
+   * messages the server is willing to accept for the operation.
+   *
+   * @param message message to be sent to the server.
+   * @throws IllegalStateException if call is {@link #halfClose}d or explicitly {@link #cancel}ed
+   */
+  public abstract void sendMessage(ReqT message);
+
+  /**
+   * If {@code true}, indicates that the call is capable of sending additional messages
+   * without requiring excessive buffering internally. This event is
+   * just a suggestion and the application is free to ignore it, however doing so may
+   * result in excessive buffering within the call.
+   *
+   * <p>This abstract class's implementation always returns {@code true}. Implementations generally
+   * override the method.
+   *
+   * <p>If the type of the call is either {@link MethodDescriptor.MethodType#UNARY} or
+   * {@link MethodDescriptor.MethodType#SERVER_STREAMING}, this method may persistently return
+   * false. Calls that send exactly one message should not check this method.
+   */
+  public boolean isReady() {
+    return true;
+  }
+
+  /**
+   * Enables per-message compression, if an encoding type has been negotiated.  If no message
+   * encoding has been negotiated, this is a no-op. By default per-message compression is enabled,
+   * but may not have any effect if compression is not enabled on the call.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1703")
+  public void setMessageCompression(boolean enabled) {
+    // noop
+  }
+
+  /**
+   * Returns additional properties of the call. May only be called after {@link Listener#onHeaders}
+   * or {@link Listener#onClose}. If called prematurely, the implementation may throw {@code
+   * IllegalStateException} or return arbitrary {@code Attributes}.
+   *
+   * <p>{@link Grpc} defines commonly used attributes, but they are not guaranteed to be present.
+   *
+   * @return non-{@code null} attributes
+   * @throws IllegalStateException (optional) if called before permitted
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2607")
+  public Attributes getAttributes() {
+    return Attributes.EMPTY;
+  }
+}
diff --git a/core/src/main/java/io/grpc/ClientInterceptor.java b/core/src/main/java/io/grpc/ClientInterceptor.java
new file mode 100644
index 0000000..b01133a
--- /dev/null
+++ b/core/src/main/java/io/grpc/ClientInterceptor.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Interface for intercepting outgoing calls before they are dispatched by a {@link Channel}.
+ *
+ * <p>Implementers use this mechanism to add cross-cutting behavior to {@link Channel} and
+ * stub implementations. Common examples of such behavior include:
+ * <ul>
+ * <li>Adding credentials to header metadata</li>
+ * <li>Logging and monitoring call behavior</li>
+ * <li>Request and response rewriting</li>
+ * </ul>
+ */
+@ThreadSafe
+public interface ClientInterceptor {
+  /**
+   * Intercept {@link ClientCall} creation by the {@code next} {@link Channel}.
+   *
+   * <p>Many variations of interception are possible. Complex implementations may return a wrapper
+   * around the result of {@code next.newCall()}, whereas a simpler implementation may just modify
+   * the header metadata prior to returning the result of {@code next.newCall()}.
+   *
+   * <p>{@code next.newCall()} <strong>must not</strong> be called under a different {@link Context}
+   * other than the current {@code Context}. The outcome of such usage is undefined and may cause
+   * memory leak due to unbounded chain of {@code Context}s.
+   *
+   * @param method the remote method to be called.
+   * @param callOptions the runtime options to be applied to this call.
+   * @param next the channel which is being intercepted.
+   * @return the call object for the remote operation, never {@code null}.
+   */
+  <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+      MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next);
+}
diff --git a/core/src/main/java/io/grpc/ClientInterceptors.java b/core/src/main/java/io/grpc/ClientInterceptors.java
new file mode 100644
index 0000000..b6fe077
--- /dev/null
+++ b/core/src/main/java/io/grpc/ClientInterceptors.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.base.Preconditions;
+import io.grpc.MethodDescriptor.Marshaller;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility methods for working with {@link ClientInterceptor}s.
+ */
+public class ClientInterceptors {
+
+  // Prevent instantiation
+  private ClientInterceptors() {}
+
+  /**
+   * Create a new {@link Channel} that will call {@code interceptors} before starting a call on the
+   * given channel. The first interceptor will have its {@link ClientInterceptor#interceptCall}
+   * called first.
+   *
+   * @param channel the underlying channel to intercept.
+   * @param interceptors array of interceptors to bind to {@code channel}.
+   * @return a new channel instance with the interceptors applied.
+   */
+  public static Channel interceptForward(Channel channel, ClientInterceptor... interceptors) {
+    return interceptForward(channel, Arrays.asList(interceptors));
+  }
+
+  /**
+   * Create a new {@link Channel} that will call {@code interceptors} before starting a call on the
+   * given channel. The first interceptor will have its {@link ClientInterceptor#interceptCall}
+   * called first.
+   *
+   * @param channel the underlying channel to intercept.
+   * @param interceptors a list of interceptors to bind to {@code channel}.
+   * @return a new channel instance with the interceptors applied.
+   */
+  public static Channel interceptForward(Channel channel,
+                                         List<? extends ClientInterceptor> interceptors) {
+    List<? extends ClientInterceptor> copy = new ArrayList<>(interceptors);
+    Collections.reverse(copy);
+    return intercept(channel, copy);
+  }
+
+  /**
+   * Create a new {@link Channel} that will call {@code interceptors} before starting a call on the
+   * given channel. The last interceptor will have its {@link ClientInterceptor#interceptCall}
+   * called first.
+   *
+   * @param channel the underlying channel to intercept.
+   * @param interceptors array of interceptors to bind to {@code channel}.
+   * @return a new channel instance with the interceptors applied.
+   */
+  public static Channel intercept(Channel channel, ClientInterceptor... interceptors) {
+    return intercept(channel, Arrays.asList(interceptors));
+  }
+
+  /**
+   * Create a new {@link Channel} that will call {@code interceptors} before starting a call on the
+   * given channel. The last interceptor will have its {@link ClientInterceptor#interceptCall}
+   * called first.
+   *
+   * @param channel the underlying channel to intercept.
+   * @param interceptors a list of interceptors to bind to {@code channel}.
+   * @return a new channel instance with the interceptors applied.
+   */
+  public static Channel intercept(Channel channel, List<? extends ClientInterceptor> interceptors) {
+    Preconditions.checkNotNull(channel, "channel");
+    for (ClientInterceptor interceptor : interceptors) {
+      channel = new InterceptorChannel(channel, interceptor);
+    }
+    return channel;
+  }
+
+  /**
+   * Creates a new ClientInterceptor that transforms requests into {@code WReqT} and responses into
+   * {@code WRespT} before passing them into the {@code interceptor}.
+   */
+  static <WReqT, WRespT> ClientInterceptor wrapClientInterceptor(
+      final ClientInterceptor interceptor,
+      final Marshaller<WReqT> reqMarshaller,
+      final Marshaller<WRespT> respMarshaller) {
+    return new ClientInterceptor() {
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+          final MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+        final MethodDescriptor<WReqT, WRespT> wrappedMethod =
+            method.toBuilder(reqMarshaller, respMarshaller).build();
+        final ClientCall<WReqT, WRespT> wrappedCall =
+            interceptor.interceptCall(wrappedMethod, callOptions, next);
+        return new PartialForwardingClientCall<ReqT, RespT>() {
+          @Override
+          public void start(final Listener<RespT> responseListener, Metadata headers) {
+            wrappedCall.start(new PartialForwardingClientCallListener<WRespT>() {
+              @Override
+              public void onMessage(WRespT wMessage) {
+                InputStream bytes = respMarshaller.stream(wMessage);
+                RespT message = method.getResponseMarshaller().parse(bytes);
+                responseListener.onMessage(message);
+              }
+
+              @Override
+              protected Listener<?> delegate() {
+                return responseListener;
+              }
+            }, headers);
+          }
+
+          @Override
+          public void sendMessage(ReqT message) {
+            InputStream bytes = method.getRequestMarshaller().stream(message);
+            WReqT wReq = reqMarshaller.parse(bytes);
+            wrappedCall.sendMessage(wReq);
+          }
+
+          @Override
+          protected ClientCall<?, ?> delegate() {
+            return wrappedCall;
+          }
+        };
+      }
+    };
+  }
+
+  private static class InterceptorChannel extends Channel {
+    private final Channel channel;
+    private final ClientInterceptor interceptor;
+
+    private InterceptorChannel(Channel channel, ClientInterceptor interceptor) {
+      this.channel = channel;
+      this.interceptor = Preconditions.checkNotNull(interceptor, "interceptor");
+    }
+
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> newCall(
+        MethodDescriptor<ReqT, RespT> method, CallOptions callOptions) {
+      return interceptor.interceptCall(method, callOptions, channel);
+    }
+
+    @Override
+    public String authority() {
+      return channel.authority();
+    }
+  }
+
+  private static final ClientCall<Object, Object> NOOP_CALL = new ClientCall<Object, Object>() {
+    @Override
+    public void start(Listener<Object> responseListener, Metadata headers) {}
+
+    @Override
+    public void request(int numMessages) {}
+
+    @Override
+    public void cancel(String message, Throwable cause) {}
+
+    @Override
+    public void halfClose() {}
+
+    @Override
+    public void sendMessage(Object message) {}
+
+    /**
+     * Always returns {@code false}, since this is only used when the startup of the {@link
+     * ClientCall} fails (i.e. the {@link ClientCall} is closed).
+     */
+    @Override
+    public boolean isReady() {
+      return false;
+    }
+  };
+
+  /**
+   * A {@link io.grpc.ForwardingClientCall} that delivers exceptions from its start logic to the
+   * call listener.
+   *
+   * <p>{@link ClientCall#start(ClientCall.Listener, Metadata)} should not throw any
+   * exception other than those caused by misuse, e.g., {@link IllegalStateException}.  {@code
+   * CheckedForwardingClientCall} provides {@code checkedStart()} in which throwing exceptions is
+   * allowed.
+   */
+  public abstract static class CheckedForwardingClientCall<ReqT, RespT>
+      extends io.grpc.ForwardingClientCall<ReqT, RespT> {
+
+    private ClientCall<ReqT, RespT> delegate;
+
+    /**
+     * Subclasses implement the start logic here that would normally belong to {@code start()}.
+     *
+     * <p>Implementation should call {@code this.delegate().start()} in the normal path. Exceptions
+     * may safely be thrown prior to calling {@code this.delegate().start()}. Such exceptions will
+     * be handled by {@code CheckedForwardingClientCall} and be delivered to {@code
+     * responseListener}.  Exceptions <em>must not</em> be thrown after calling {@code
+     * this.delegate().start()}, as this can result in {@link ClientCall.Listener#onClose} being
+     * called multiple times.
+     */
+    protected abstract void checkedStart(Listener<RespT> responseListener, Metadata headers)
+        throws Exception;
+
+    protected CheckedForwardingClientCall(ClientCall<ReqT, RespT> delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    protected final ClientCall<ReqT, RespT> delegate() {
+      return delegate;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public final void start(Listener<RespT> responseListener, Metadata headers) {
+      try {
+        checkedStart(responseListener, headers);
+      } catch (Exception e) {
+        // Because start() doesn't throw, the caller may still try to call other methods on this
+        // call object. Passing these invocations to the original delegate will cause
+        // IllegalStateException because delegate().start() was not called. We switch the delegate
+        // to a NO-OP one to prevent the IllegalStateException. The user will finally get notified
+        // about the error through the listener.
+        delegate = (ClientCall<ReqT, RespT>) NOOP_CALL;
+        responseListener.onClose(Status.fromThrowable(e), new Metadata());
+      }
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/ClientStreamTracer.java b/core/src/main/java/io/grpc/ClientStreamTracer.java
new file mode 100644
index 0000000..5d419c2
--- /dev/null
+++ b/core/src/main/java/io/grpc/ClientStreamTracer.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * {@link StreamTracer} for the client-side.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
+@ThreadSafe
+public abstract class ClientStreamTracer extends StreamTracer {
+  /**
+   * Headers has been sent to the socket.
+   */
+  public void outboundHeaders() {
+  }
+
+  /**
+   * Headers has been received from the server.
+   */
+  public void inboundHeaders() {
+  }
+
+  /**
+   * Factory class for {@link ClientStreamTracer}.
+   */
+  public abstract static class Factory {
+    /**
+     * Creates a {@link ClientStreamTracer} for a new client stream.
+     *
+     * @deprecated Override/call {@link #newClientStreamTracer(CallOptions, Metadata)} instead. 
+     */
+    @Deprecated
+    public ClientStreamTracer newClientStreamTracer(Metadata headers) {
+      throw new UnsupportedOperationException("This method will be deleted. Do not call.");
+    }
+
+    /**
+     * Creates a {@link ClientStreamTracer} for a new client stream.
+     *
+     * @param callOptions the effective CallOptions of the call
+     * @param headers the mutable headers of the stream. It can be safely mutated within this
+     *        method.  It should not be saved because it is not safe for read or write after the
+     *        method returns.
+     */
+    @SuppressWarnings("deprecation")
+    public ClientStreamTracer newClientStreamTracer(CallOptions callOptions, Metadata headers) {
+      return newClientStreamTracer(headers);
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/Codec.java b/core/src/main/java/io/grpc/Codec.java
new file mode 100644
index 0000000..040dae0
--- /dev/null
+++ b/core/src/main/java/io/grpc/Codec.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Encloses classes related to the compression and decompression of messages.
+ *
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+public interface Codec extends Compressor, Decompressor {
+  /**
+   * A gzip compressor and decompressor.  In the future this will likely support other
+   * compression methods, such as compression level.
+   */
+  public static final class Gzip implements Codec {
+    @Override
+    public String getMessageEncoding() {
+      return "gzip";
+    }
+
+    @Override
+    public OutputStream compress(OutputStream os) throws IOException {
+      return new GZIPOutputStream(os);
+    }
+
+    @Override
+    public InputStream decompress(InputStream is) throws IOException {
+      return new GZIPInputStream(is);
+    }
+  }
+
+  /**
+   * The "identity", or "none" codec.  This codec is special in that it can be used to explicitly
+   * disable Call compression on a Channel that by default compresses.
+   */
+  public static final class Identity implements Codec {
+    /**
+     * Special sentinel codec indicating that no compression should be used.  Users should use
+     * reference equality to see if compression is disabled.
+     */
+    public static final Codec NONE = new Identity();
+
+    @Override
+    public InputStream decompress(InputStream is) throws IOException {
+      return is;
+    }
+
+    @Override
+    public String getMessageEncoding() {
+      return "identity";
+    }
+
+    @Override
+    public OutputStream compress(OutputStream os) throws IOException {
+      return os;
+    }
+
+    private Identity() {}
+  }
+}
diff --git a/core/src/main/java/io/grpc/Compressor.java b/core/src/main/java/io/grpc/Compressor.java
new file mode 100644
index 0000000..a933faf
--- /dev/null
+++ b/core/src/main/java/io/grpc/Compressor.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Represents a message compressor.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+public interface Compressor {
+  /**
+   * Returns the message encoding that this compressor uses.
+   *
+   * <p>This can be values such as "gzip", "deflate", "snappy", etc.
+   */
+  String getMessageEncoding();
+
+  /**
+   * Wraps an existing output stream with a compressing output stream.
+   * @param os The output stream of uncompressed data
+   * @return An output stream that compresses
+   */
+  OutputStream compress(OutputStream os) throws IOException;
+}
+
diff --git a/core/src/main/java/io/grpc/CompressorRegistry.java b/core/src/main/java/io/grpc/CompressorRegistry.java
new file mode 100644
index 0000000..e0124af
--- /dev/null
+++ b/core/src/main/java/io/grpc/CompressorRegistry.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Encloses classes related to the compression and decompression of messages.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+@ThreadSafe
+public final class CompressorRegistry {
+  private static final CompressorRegistry DEFAULT_INSTANCE = new CompressorRegistry(
+      new Codec.Gzip(),
+      Codec.Identity.NONE);
+
+  /**
+   * Returns the default instance used by gRPC when the registry is not specified.
+   * Currently the registry just contains support for gzip.
+   */
+  public static CompressorRegistry getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  /**
+   * Returns a new instance with no registered compressors.
+   */
+  public static CompressorRegistry newEmptyInstance() {
+    return new CompressorRegistry();
+  }
+
+  private final ConcurrentMap<String, Compressor> compressors;
+
+  @VisibleForTesting
+  CompressorRegistry(Compressor ...cs) {
+    compressors = new ConcurrentHashMap<String, Compressor>();
+    for (Compressor c : cs) {
+      compressors.put(c.getMessageEncoding(), c);
+    }
+  }
+
+  @Nullable
+  public Compressor lookupCompressor(String compressorName) {
+    return compressors.get(compressorName);
+  }
+
+  /**
+   * Registers a compressor for both decompression and message encoding negotiation.
+   *
+   * @param c The compressor to register
+   */
+  public void register(Compressor c) {
+    String encoding = c.getMessageEncoding();
+    checkArgument(!encoding.contains(","), "Comma is currently not allowed in message encoding");
+    compressors.put(encoding, c);
+  }
+}
diff --git a/core/src/main/java/io/grpc/ConnectivityState.java b/core/src/main/java/io/grpc/ConnectivityState.java
new file mode 100644
index 0000000..677039b
--- /dev/null
+++ b/core/src/main/java/io/grpc/ConnectivityState.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * The connectivity states.
+ *
+ * @see <a href="https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md">
+ * more information</a>
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4359")
+public enum ConnectivityState {
+  /**
+   * The channel is trying to establish a connection and is waiting to make progress on one of the
+   * steps involved in name resolution, TCP connection establishment or TLS handshake. This may be
+   * used as the initial state for channels upon creation.
+   */
+  CONNECTING,
+
+  /**
+   * The channel has successfully established a connection all the way through TLS handshake (or
+   * equivalent) and all subsequent attempt to communicate have succeeded (or are pending without
+   * any known failure ).
+   */
+  READY,
+
+  /**
+   * There has been some transient failure (such as a TCP 3-way handshake timing out or a socket
+   * error). Channels in this state will eventually switch to the CONNECTING state and try to
+   * establish a connection again. Since retries are done with exponential backoff, channels that
+   * fail to connect will start out spending very little time in this state but as the attempts
+   * fail repeatedly, the channel will spend increasingly large amounts of time in this state. For
+   * many non-fatal failures (e.g., TCP connection attempts timing out because the server is not
+   * yet available), the channel may spend increasingly large amounts of time in this state.
+   */
+  TRANSIENT_FAILURE,
+
+  /**
+   * This is the state where the channel is not even trying to create a connection because of a
+   * lack of new or pending RPCs. New RPCs MAY be created in this state. Any attempt to start an
+   * RPC on the channel will push the channel out of this state to connecting. When there has been
+   * no RPC activity on a channel for a configurable IDLE_TIMEOUT, i.e., no new or pending (active)
+   * RPCs for this period, channels that are READY or CONNECTING switch to IDLE. Additionaly,
+   * channels that receive a GOAWAY when there are no active or pending RPCs should also switch to
+   * IDLE to avoid connection overload at servers that are attempting to shed connections.
+   */
+  IDLE,
+
+  /**
+   * This channel has started shutting down. Any new RPCs should fail immediately. Pending RPCs
+   * may continue running till the application cancels them. Channels may enter this state either
+   * because the application explicitly requested a shutdown or if a non-recoverable error has
+   * happened during attempts to connect communicate . (As of 6/12/2015, there are no known errors
+   * (while connecting or communicating) that are classified as non-recoverable) Channels that
+   * enter this state never leave this state.
+   */
+  SHUTDOWN
+}
diff --git a/core/src/main/java/io/grpc/ConnectivityStateInfo.java b/core/src/main/java/io/grpc/ConnectivityStateInfo.java
new file mode 100644
index 0000000..e2c1624
--- /dev/null
+++ b/core/src/main/java/io/grpc/ConnectivityStateInfo.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * A tuple of a {@link ConnectivityState} and its associated {@link Status}.
+ *
+ * <p>If the state is {@code TRANSIENT_FAILURE}, the status is never {@code OK}.  For other states,
+ * the status is always {@code OK}.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
+public final class ConnectivityStateInfo {
+  private final ConnectivityState state;
+  private final Status status;
+
+  /**
+   * Returns an instance for a state that is not {@code TRANSIENT_FAILURE}.
+   *
+   * @throws IllegalArgumentException if {@code state} is {@code TRANSIENT_FAILURE}.
+   */
+  public static ConnectivityStateInfo forNonError(ConnectivityState state) {
+    Preconditions.checkArgument(
+        state != TRANSIENT_FAILURE,
+        "state is TRANSIENT_ERROR. Use forError() instead");
+    return new ConnectivityStateInfo(state, Status.OK);
+  }
+
+  /**
+   * Returns an instance for {@code TRANSIENT_FAILURE}, associated with an error status.
+   */
+  public static ConnectivityStateInfo forTransientFailure(Status error) {
+    Preconditions.checkArgument(!error.isOk(), "The error status must not be OK");
+    return new ConnectivityStateInfo(TRANSIENT_FAILURE, error);
+  }
+
+  /**
+   * Returns the state.
+   */
+  public ConnectivityState getState() {
+    return state;
+  }
+
+  /**
+   * Returns the status associated with the state.
+   *
+   * <p>If the state is {@code TRANSIENT_FAILURE}, the status is never {@code OK}.  For other
+   * states, the status is always {@code OK}.
+   */
+  public Status getStatus() {
+    return status;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (!(other instanceof ConnectivityStateInfo)) {
+      return false;
+    }
+    ConnectivityStateInfo o = (ConnectivityStateInfo) other;
+    return state.equals(o.state) && status.equals(o.status);
+  }
+
+  @Override
+  public int hashCode() {
+    return state.hashCode() ^ status.hashCode();
+  }
+
+  @Override
+  public String toString() {
+    if (status.isOk()) {
+      return state.toString();
+    }
+    return state + "(" + status + ")";
+  }
+
+  private ConnectivityStateInfo(ConnectivityState state, Status status) {
+    this.state = Preconditions.checkNotNull(state, "state is null");
+    this.status = Preconditions.checkNotNull(status, "status is null");
+  }
+}
diff --git a/core/src/main/java/io/grpc/Contexts.java b/core/src/main/java/io/grpc/Contexts.java
new file mode 100644
index 0000000..04f2fd0
--- /dev/null
+++ b/core/src/main/java/io/grpc/Contexts.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.base.Preconditions;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Utility methods for working with {@link Context}s in GRPC.
+ */
+public final class Contexts {
+
+  private Contexts() {
+  }
+
+  /**
+   * Make the provided {@link Context} {@link Context#current()} for the creation of a listener
+   * to a received call and for all events received by that listener.
+   *
+   * <p>This utility is expected to be used by {@link ServerInterceptor} implementations that need
+   * to augment the {@link Context} in which the application does work when receiving events from
+   * the client.
+   *
+   * @param context to make {@link Context#current()}.
+   * @param call used to send responses to client.
+   * @param headers received from client.
+   * @param next handler used to create the listener to be wrapped.
+   * @return listener that will receive events in the scope of the provided context.
+   */
+  public static <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+        Context context,
+        ServerCall<ReqT, RespT> call,
+        Metadata headers,
+        ServerCallHandler<ReqT, RespT> next) {
+    Context previous = context.attach();
+    try {
+      return new ContextualizedServerCallListener<ReqT>(
+          next.startCall(call, headers),
+          context);
+    } finally {
+      context.detach(previous);
+    }
+  }
+
+  /**
+   * Implementation of {@link io.grpc.ForwardingServerCallListener} that attaches a context before
+   * dispatching calls to the delegate and detaches them after the call completes.
+   */
+  private static class ContextualizedServerCallListener<ReqT> extends
+      ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT> {
+    private final Context context;
+
+    public ContextualizedServerCallListener(ServerCall.Listener<ReqT> delegate, Context context) {
+      super(delegate);
+      this.context = context;
+    }
+
+    @Override
+    public void onMessage(ReqT message) {
+      Context previous = context.attach();
+      try {
+        super.onMessage(message);
+      } finally {
+        context.detach(previous);
+      }
+    }
+
+    @Override
+    public void onHalfClose() {
+      Context previous = context.attach();
+      try {
+        super.onHalfClose();
+      } finally {
+        context.detach(previous);
+      }
+    }
+
+    @Override
+    public void onCancel() {
+      Context previous = context.attach();
+      try {
+        super.onCancel();
+      } finally {
+        context.detach(previous);
+      }
+    }
+
+    @Override
+    public void onComplete() {
+      Context previous = context.attach();
+      try {
+        super.onComplete();
+      } finally {
+        context.detach(previous);
+      }
+    }
+
+    @Override
+    public void onReady() {
+      Context previous = context.attach();
+      try {
+        super.onReady();
+      } finally {
+        context.detach(previous);
+      }
+    }
+  }
+
+  /**
+   * Returns the {@link Status} of a cancelled context or {@code null} if the context
+   * is not cancelled.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1975")
+  public static Status statusFromCancelled(Context context) {
+    Preconditions.checkNotNull(context, "context must not be null");
+    if (!context.isCancelled()) {
+      return null;
+    }
+
+    Throwable cancellationCause = context.cancellationCause();
+    if (cancellationCause == null) {
+      return Status.CANCELLED.withDescription("io.grpc.Context was cancelled without error");
+    }
+    if (cancellationCause instanceof TimeoutException) {
+      return Status.DEADLINE_EXCEEDED
+          .withDescription(cancellationCause.getMessage())
+          .withCause(cancellationCause);
+    }
+    Status status = Status.fromThrowable(cancellationCause);
+    if (Status.Code.UNKNOWN.equals(status.getCode())
+        && status.getCause() == cancellationCause) {
+      // If fromThrowable could not determine a status, then
+      // just return CANCELLED.
+      return Status.CANCELLED.withDescription("Context cancelled").withCause(cancellationCause);
+    }
+    return status.withCause(cancellationCause);
+  }
+}
diff --git a/core/src/main/java/io/grpc/Decompressor.java b/core/src/main/java/io/grpc/Decompressor.java
new file mode 100644
index 0000000..bdced21
--- /dev/null
+++ b/core/src/main/java/io/grpc/Decompressor.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Represents a message decompressor.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+public interface Decompressor {
+  /**
+   * Returns the message encoding that this compressor uses.
+   *
+   * <p>This can be values such as "gzip", "deflate", "snappy", etc.
+   */
+  String getMessageEncoding();
+
+  /**
+   * Wraps an existing input stream with a decompressing input stream.
+   * @param is The input stream of uncompressed data
+   * @return An input stream that decompresses
+   */
+  InputStream decompress(InputStream is) throws IOException;
+}
+
diff --git a/core/src/main/java/io/grpc/DecompressorRegistry.java b/core/src/main/java/io/grpc/DecompressorRegistry.java
new file mode 100644
index 0000000..72926d2
--- /dev/null
+++ b/core/src/main/java/io/grpc/DecompressorRegistry.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Joiner;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Encloses classes related to the compression and decompression of messages.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+@ThreadSafe
+public final class DecompressorRegistry {
+  static final Joiner ACCEPT_ENCODING_JOINER = Joiner.on(',');
+
+  public static DecompressorRegistry emptyInstance() {
+    return new DecompressorRegistry();
+  }
+
+  private static final DecompressorRegistry DEFAULT_INSTANCE =
+      emptyInstance()
+      .with(new Codec.Gzip(), true)
+      .with(Codec.Identity.NONE, false);
+
+  public static DecompressorRegistry getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private final Map<String, DecompressorInfo> decompressors;
+  private final byte[] advertisedDecompressors;
+
+  /**
+   * Registers a decompressor for both decompression and message encoding negotiation.  Returns a
+   * new registry.
+   *
+   * @param d The decompressor to register
+   * @param advertised If true, the message encoding will be listed in the Accept-Encoding header.
+   */
+  public DecompressorRegistry with(Decompressor d, boolean advertised) {
+    return new DecompressorRegistry(d, advertised, this);
+  }
+
+  private DecompressorRegistry(Decompressor d, boolean advertised, DecompressorRegistry parent) {
+    String encoding = d.getMessageEncoding();
+    checkArgument(!encoding.contains(","), "Comma is currently not allowed in message encoding");
+
+    int newSize = parent.decompressors.size();
+    if (!parent.decompressors.containsKey(d.getMessageEncoding())) {
+      newSize++;
+    }
+    Map<String, DecompressorInfo> newDecompressors =
+        new LinkedHashMap<String, DecompressorInfo>(newSize);
+    for (DecompressorInfo di : parent.decompressors.values()) {
+      String previousEncoding = di.decompressor.getMessageEncoding();
+      if (!previousEncoding.equals(encoding)) {
+        newDecompressors.put(
+            previousEncoding, new DecompressorInfo(di.decompressor, di.advertised));
+      }
+    }
+    newDecompressors.put(encoding, new DecompressorInfo(d, advertised));
+
+    decompressors = Collections.unmodifiableMap(newDecompressors);
+    advertisedDecompressors = ACCEPT_ENCODING_JOINER.join(getAdvertisedMessageEncodings())
+        .getBytes(Charset.forName("US-ASCII"));
+  }
+
+  private DecompressorRegistry() {
+    decompressors = new LinkedHashMap<String, DecompressorInfo>(0);
+    advertisedDecompressors = new byte[0];
+  }
+
+  /**
+   * Provides a list of all message encodings that have decompressors available.
+   */
+  public Set<String> getKnownMessageEncodings() {
+    return decompressors.keySet();
+  }
+
+
+  byte[] getRawAdvertisedMessageEncodings() {
+    return advertisedDecompressors;
+  }
+
+  /**
+   * Provides a list of all message encodings that have decompressors available and should be
+   * advertised.
+   *
+   * <p>The specification doesn't say anything about ordering, or preference, so the returned codes
+   * can be arbitrary.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+  public Set<String> getAdvertisedMessageEncodings() {
+    Set<String> advertisedDecompressors = new HashSet<String>(decompressors.size());
+    for (Entry<String, DecompressorInfo> entry : decompressors.entrySet()) {
+      if (entry.getValue().advertised) {
+        advertisedDecompressors.add(entry.getKey());
+      }
+    }
+    return Collections.unmodifiableSet(advertisedDecompressors);
+  }
+
+  /**
+   * Returns a decompressor for the given message encoding, or {@code null} if none has been
+   * registered.
+   *
+   * <p>This ignores whether the compressor is advertised.  According to the spec, if we know how
+   * to process this encoding, we attempt to, regardless of whether or not it is part of the
+   * encodings sent to the remote host.
+   */
+  @Nullable
+  public Decompressor lookupDecompressor(String messageEncoding) {
+    DecompressorInfo info = decompressors.get(messageEncoding);
+    return info != null ? info.decompressor : null;
+  }
+
+  /**
+   * Information about a decompressor.
+   */
+  private static final class DecompressorInfo {
+    final Decompressor decompressor;
+    final boolean advertised;
+
+    DecompressorInfo(Decompressor decompressor, boolean advertised) {
+      this.decompressor = checkNotNull(decompressor, "decompressor");
+      this.advertised = advertised;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/Drainable.java b/core/src/main/java/io/grpc/Drainable.java
new file mode 100644
index 0000000..7b7cf5c
--- /dev/null
+++ b/core/src/main/java/io/grpc/Drainable.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Extension to an {@link java.io.InputStream} or alike by adding a method that transfers all
+ * content to an {@link OutputStream}.
+ *
+ * <p>This can be used for optimizing for the case where the content of the input stream will be
+ * written to an {@link OutputStream} eventually. Instead of copying the content to a byte array
+ * through {@code read()}, then writing the {@code OutputStream}, the implementation can write
+ * the content directly to the {@code OutputStream}.
+ */
+public interface Drainable {
+
+  /**
+   * Transfers the entire contents of this stream to the specified target.
+   *
+   * @param target to write to.
+   * @return number of bytes written.
+   */
+  int drainTo(OutputStream target) throws IOException;
+}
diff --git a/core/src/main/java/io/grpc/EquivalentAddressGroup.java b/core/src/main/java/io/grpc/EquivalentAddressGroup.java
new file mode 100644
index 0000000..2218a7f
--- /dev/null
+++ b/core/src/main/java/io/grpc/EquivalentAddressGroup.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.base.Preconditions;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A group of {@link SocketAddress}es that are considered equivalent when channel makes connections.
+ *
+ * <p>Usually the addresses are addresses resolved from the same host name, and connecting to any of
+ * them is equally sufficient. They do have order. An address appears earlier on the list is likely
+ * to be tried earlier.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
+public final class EquivalentAddressGroup {
+
+  private final List<SocketAddress> addrs;
+  private final Attributes attrs;
+
+  /**
+   * {@link SocketAddress} docs say that the addresses are immutable, so we cache the hashCode.
+   */
+  private final int hashCode;
+
+  /**
+   * List constructor without {@link Attributes}.
+   */
+  public EquivalentAddressGroup(List<SocketAddress> addrs) {
+    this(addrs, Attributes.EMPTY);
+  }
+
+  /**
+   * List constructor with {@link Attributes}.
+   */
+  public EquivalentAddressGroup(List<SocketAddress> addrs, Attributes attrs) {
+    Preconditions.checkArgument(!addrs.isEmpty(), "addrs is empty");
+    this.addrs = Collections.unmodifiableList(new ArrayList<>(addrs));
+    this.attrs = Preconditions.checkNotNull(attrs, "attrs");
+    // Attributes may contain mutable objects, which means Attributes' hashCode may change over
+    // time, thus we don't cache Attributes' hashCode.
+    hashCode = this.addrs.hashCode();
+  }
+
+  /**
+   * Singleton constructor without Attributes.
+   */
+  public EquivalentAddressGroup(SocketAddress addr) {
+    this(addr, Attributes.EMPTY);
+  }
+
+  /**
+   * Singleton constructor with Attributes.
+   */
+  public EquivalentAddressGroup(SocketAddress addr, Attributes attrs) {
+    this(Collections.singletonList(addr), attrs);
+  }
+
+  /**
+   * Returns an immutable list of the addresses.
+   */
+  public List<SocketAddress> getAddresses() {
+    return addrs;
+  }
+
+  /**
+   * Returns the attributes.
+   */
+  public Attributes getAttributes() {
+    return attrs;
+  }
+
+  @Override
+  public String toString() {
+    // TODO(zpencer): Summarize return value if addr is very large
+    return "[addrs=" + addrs + ", attrs=" + attrs + "]";
+  }
+
+  @Override
+  public int hashCode() {
+    // Avoids creating an iterator on the underlying array list.
+    return hashCode;
+  }
+
+  /**
+   * Returns true if the given object is also an {@link EquivalentAddressGroup} with an equal
+   * address list and equal attribute values.
+   *
+   * <p>Note that if the attributes include mutable values, it is possible for two objects to be
+   * considered equal at one point in time and not equal at another (due to concurrent mutation of
+   * attribute values).
+   */
+  @Override
+  public boolean equals(Object other) {
+    if (!(other instanceof EquivalentAddressGroup)) {
+      return false;
+    }
+    EquivalentAddressGroup that = (EquivalentAddressGroup) other;
+    if (addrs.size() != that.addrs.size()) {
+      return false;
+    }
+    // Avoids creating an iterator on the underlying array list.
+    for (int i = 0; i < addrs.size(); i++) {
+      if (!addrs.get(i).equals(that.addrs.get(i))) {
+        return false;
+      }
+    }
+    if (!attrs.equals(that.attrs)) {
+      return false;
+    }
+    return true;
+  }
+}
diff --git a/core/src/main/java/io/grpc/ExperimentalApi.java b/core/src/main/java/io/grpc/ExperimentalApi.java
new file mode 100644
index 0000000..3ad54b2
--- /dev/null
+++ b/core/src/main/java/io/grpc/ExperimentalApi.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates a public API that can change at any time, and has no guarantee of API stability and
+ * backward-compatibility.
+ *
+ * <p>Usage guidelines:
+ * <ol>
+ * <li>This annotation is used only on public API. Internal interfaces should not use it.</li>
+ * <li>After gRPC has gained API stability, this annotation can only be added to new API. Adding it
+ * to an existing API is considered API-breaking.</li>
+ * <li>Removing this annotation from an API gives it stable status.</li>
+ * </ol>
+ *
+ * <p>Note: This annotation is intended only for gRPC library code. Users should not attach this
+ * annotation to their own code.
+ *
+ * <p>See: <a href="https://github.com/grpc/grpc-java-api-checker">grpc-java-api-checker</a>, an
+ * Error Prone plugin to automatically check for usages of this API.
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target({
+    ElementType.ANNOTATION_TYPE,
+    ElementType.CONSTRUCTOR,
+    ElementType.FIELD,
+    ElementType.METHOD,
+    ElementType.PACKAGE,
+    ElementType.TYPE})
+@Documented
+public @interface ExperimentalApi {
+  /**
+   * Context information such as links to discussion thread, tracking issue etc.
+   */
+  String value();
+}
diff --git a/core/src/main/java/io/grpc/ForwardingChannelBuilder.java b/core/src/main/java/io/grpc/ForwardingChannelBuilder.java
new file mode 100644
index 0000000..1528926
--- /dev/null
+++ b/core/src/main/java/io/grpc/ForwardingChannelBuilder.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.base.MoreObjects;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link ManagedChannelBuilder} that delegates all its builder method to another builder by
+ * default.
+ *
+ * @param <T> The type of the subclass extending this abstract class.
+ *
+ * @since 1.7.0
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/3363")
+public abstract class ForwardingChannelBuilder<T extends ForwardingChannelBuilder<T>>
+    extends ManagedChannelBuilder<T> {
+
+  /**
+   * The default constructor.
+   */
+  protected ForwardingChannelBuilder() {}
+
+  /**
+   * This method serves to force sub classes to "hide" this static factory.
+   */
+  public static ManagedChannelBuilder<?> forAddress(String name, int port) {
+    throw new UnsupportedOperationException("Subclass failed to hide static factory");
+  }
+
+  /**
+   * This method serves to force sub classes to "hide" this static factory.
+   */
+  public static ManagedChannelBuilder<?> forTarget(String target) {
+    throw new UnsupportedOperationException("Subclass failed to hide static factory");
+  }
+
+  /**
+   * Returns the delegated {@code ManagedChannelBuilder}.
+   */
+  protected abstract ManagedChannelBuilder<?> delegate();
+
+  @Override
+  public T directExecutor() {
+    delegate().directExecutor();
+    return thisT();
+  }
+
+  @Override
+  public T executor(Executor executor) {
+    delegate().executor(executor);
+    return thisT();
+  }
+
+  @Override
+  public T intercept(List<ClientInterceptor> interceptors) {
+    delegate().intercept(interceptors);
+    return thisT();
+  }
+
+  @Override
+  public T intercept(ClientInterceptor... interceptors) {
+    delegate().intercept(interceptors);
+    return thisT();
+  }
+
+  @Override
+  public T userAgent(String userAgent) {
+    delegate().userAgent(userAgent);
+    return thisT();
+  }
+
+  @Override
+  public T overrideAuthority(String authority) {
+    delegate().overrideAuthority(authority);
+    return thisT();
+  }
+
+  /**
+   * @deprecated use {@link #usePlaintext()} instead.
+   */
+  @Override
+  @Deprecated
+  public T usePlaintext(boolean skipNegotiation) {
+    delegate().usePlaintext(skipNegotiation);
+    return thisT();
+  }
+
+  @Override
+  public T usePlaintext() {
+    delegate().usePlaintext();
+    return thisT();
+  }
+
+  @Override
+  public T useTransportSecurity() {
+    delegate().useTransportSecurity();
+    return thisT();
+  }
+
+  @Override
+  public T nameResolverFactory(NameResolver.Factory resolverFactory) {
+    delegate().nameResolverFactory(resolverFactory);
+    return thisT();
+  }
+
+  @Override
+  public T loadBalancerFactory(LoadBalancer.Factory loadBalancerFactory) {
+    delegate().loadBalancerFactory(loadBalancerFactory);
+    return thisT();
+  }
+
+  @Override
+  public T enableFullStreamDecompression() {
+    delegate().enableFullStreamDecompression();
+    return thisT();
+  }
+
+  @Override
+  public T decompressorRegistry(DecompressorRegistry registry) {
+    delegate().decompressorRegistry(registry);
+    return thisT();
+  }
+
+  @Override
+  public T compressorRegistry(CompressorRegistry registry) {
+    delegate().compressorRegistry(registry);
+    return thisT();
+  }
+
+  @Override
+  public T idleTimeout(long value, TimeUnit unit) {
+    delegate().idleTimeout(value, unit);
+    return thisT();
+  }
+
+  @Override
+  public T maxInboundMessageSize(int max) {
+    delegate().maxInboundMessageSize(max);
+    return thisT();
+  }
+
+  @Override
+  public T keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
+    delegate().keepAliveTime(keepAliveTime, timeUnit);
+    return thisT();
+  }
+
+  @Override
+  public T keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
+    delegate().keepAliveTimeout(keepAliveTimeout, timeUnit);
+    return thisT();
+  }
+
+  @Override
+  public T keepAliveWithoutCalls(boolean enable) {
+    delegate().keepAliveWithoutCalls(enable);
+    return thisT();
+  }
+
+  @Override
+  public T maxRetryAttempts(int maxRetryAttempts) {
+    delegate().maxRetryAttempts(maxRetryAttempts);
+    return thisT();
+  }
+
+  @Override
+  public T maxHedgedAttempts(int maxHedgedAttempts) {
+    delegate().maxHedgedAttempts(maxHedgedAttempts);
+    return thisT();
+  }
+
+  @Override
+  public T retryBufferSize(long bytes) {
+    delegate().retryBufferSize(bytes);
+    return thisT();
+  }
+
+  @Override
+  public T perRpcBufferLimit(long bytes) {
+    delegate().perRpcBufferLimit(bytes);
+    return thisT();
+  }
+
+  @Override
+  public T disableRetry() {
+    delegate().disableRetry();
+    return thisT();
+  }
+
+  @Override
+  public T enableRetry() {
+    delegate().enableRetry();
+    return thisT();
+  }
+
+  @Override
+  public T setBinaryLog(BinaryLog binaryLog) {
+    delegate().setBinaryLog(binaryLog);
+    return thisT();
+  }
+
+  @Override
+  public T maxTraceEvents(int maxTraceEvents) {
+    delegate().maxTraceEvents(maxTraceEvents);
+    return thisT();
+  }
+
+  /**
+   * Returns the {@link ManagedChannel} built by the delegate by default. Overriding method can
+   * return different value.
+   */
+  @Override
+  public ManagedChannel build() {
+    return delegate().build();
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();
+  }
+
+  /**
+   * Returns the correctly typed version of the builder.
+   */
+  protected final T thisT() {
+    @SuppressWarnings("unchecked")
+    T thisT = (T) this;
+    return thisT;
+  }
+}
diff --git a/core/src/main/java/io/grpc/ForwardingClientCall.java b/core/src/main/java/io/grpc/ForwardingClientCall.java
new file mode 100644
index 0000000..6aa2712
--- /dev/null
+++ b/core/src/main/java/io/grpc/ForwardingClientCall.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * A {@link ClientCall} which forwards all of it's methods to another {@link ClientCall}.
+ */
+public abstract class ForwardingClientCall<ReqT, RespT>
+    extends PartialForwardingClientCall<ReqT, RespT> {
+  /**
+   * Returns the delegated {@code ClientCall}.
+   */
+  @Override
+  protected abstract ClientCall<ReqT, RespT> delegate();
+
+  @Override
+  public void start(Listener<RespT> responseListener, Metadata headers) {
+    delegate().start(responseListener, headers);
+  }
+
+  @Override
+  public void sendMessage(ReqT message) {
+    delegate().sendMessage(message);
+  }
+
+  /**
+   * A simplified version of {@link ForwardingClientCall} where subclasses can pass in a {@link
+   * ClientCall} as the delegate.
+   */
+  public abstract static class SimpleForwardingClientCall<ReqT, RespT>
+      extends ForwardingClientCall<ReqT, RespT> {
+    private final ClientCall<ReqT, RespT> delegate;
+
+    protected SimpleForwardingClientCall(ClientCall<ReqT, RespT> delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    protected ClientCall<ReqT, RespT> delegate() {
+      return delegate;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/ForwardingClientCallListener.java b/core/src/main/java/io/grpc/ForwardingClientCallListener.java
new file mode 100644
index 0000000..01961b5
--- /dev/null
+++ b/core/src/main/java/io/grpc/ForwardingClientCallListener.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * A {@link ClientCall.Listener} which forwards all of its methods to another {@link
+ * ClientCall.Listener}.
+ */
+public abstract class ForwardingClientCallListener<RespT>
+    extends PartialForwardingClientCallListener<RespT> {
+  /**
+   * Returns the delegated {@code ClientCall.Listener}.
+   */
+  @Override
+  protected abstract ClientCall.Listener<RespT> delegate();
+
+  @Override
+  public void onMessage(RespT message) {
+    delegate().onMessage(message);
+  }
+
+  /**
+   * A simplified version of {@link ForwardingClientCallListener} where subclasses can pass in a
+   * {@link ClientCall.Listener} as the delegate.
+   */
+  public abstract static class SimpleForwardingClientCallListener<RespT>
+      extends ForwardingClientCallListener<RespT> {
+
+    private final ClientCall.Listener<RespT> delegate;
+
+    protected SimpleForwardingClientCallListener(ClientCall.Listener<RespT> delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    protected ClientCall.Listener<RespT> delegate() {
+      return delegate;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/ForwardingServerCall.java b/core/src/main/java/io/grpc/ForwardingServerCall.java
new file mode 100644
index 0000000..a953d3d
--- /dev/null
+++ b/core/src/main/java/io/grpc/ForwardingServerCall.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * A {@link ServerCall} which forwards all of it's methods to another {@link ServerCall}.
+ */
+public abstract class ForwardingServerCall<ReqT, RespT>
+    extends PartialForwardingServerCall<ReqT, RespT> {
+  /**
+   * Returns the delegated {@code ServerCall}.
+   */
+  @Override
+  protected abstract ServerCall<ReqT, RespT> delegate();
+
+  @Override
+  public void sendMessage(RespT message) {
+    delegate().sendMessage(message);
+  }
+
+  /**
+   * A simplified version of {@link ForwardingServerCall} where subclasses can pass in a {@link
+   * ServerCall} as the delegate.
+   */
+  public abstract static class SimpleForwardingServerCall<ReqT, RespT>
+      extends ForwardingServerCall<ReqT, RespT> {
+
+    private final ServerCall<ReqT, RespT> delegate;
+
+    protected SimpleForwardingServerCall(ServerCall<ReqT, RespT> delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    protected ServerCall<ReqT, RespT> delegate() {
+      return delegate;
+    }
+
+    @Override
+    public MethodDescriptor<ReqT, RespT> getMethodDescriptor() {
+      return delegate.getMethodDescriptor();
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/ForwardingServerCallListener.java b/core/src/main/java/io/grpc/ForwardingServerCallListener.java
new file mode 100644
index 0000000..e978e4c
--- /dev/null
+++ b/core/src/main/java/io/grpc/ForwardingServerCallListener.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * A {@link ServerCall.Listener} which forwards all of its methods to another {@link
+ * ServerCall.Listener} of matching parameterized types.
+ */
+public abstract class ForwardingServerCallListener<ReqT>
+    extends PartialForwardingServerCallListener<ReqT> {
+  /**
+   * Returns the delegated {@code ServerCall.Listener}.
+   */
+  @Override
+  protected abstract ServerCall.Listener<ReqT> delegate();
+
+  @Override
+  public void onMessage(ReqT message) {
+    delegate().onMessage(message);
+  }
+
+  /**
+   * A simplified version of {@link ForwardingServerCallListener} where subclasses can pass in a
+   * {@link ServerCall.Listener} as the delegate.
+   */
+  public abstract static class SimpleForwardingServerCallListener<ReqT>
+      extends ForwardingServerCallListener<ReqT> {
+
+    private final ServerCall.Listener<ReqT> delegate;
+
+    protected SimpleForwardingServerCallListener(ServerCall.Listener<ReqT> delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    protected ServerCall.Listener<ReqT> delegate() {
+      return delegate;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/Grpc.java b/core/src/main/java/io/grpc/Grpc.java
new file mode 100644
index 0000000..aef0726
--- /dev/null
+++ b/core/src/main/java/io/grpc/Grpc.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.net.SocketAddress;
+import javax.net.ssl.SSLSession;
+
+/**
+ * Stuff that are part of the public API but are not bound to particular classes, e.g., static
+ * methods, constants, attribute and context keys.
+ */
+public final class Grpc {
+  private Grpc() {
+  }
+
+  /**
+   * Attribute key for the remote address of a transport.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1710")
+  public static final Attributes.Key<SocketAddress> TRANSPORT_ATTR_REMOTE_ADDR =
+          Attributes.Key.create("remote-addr");
+
+  /**
+   * Attribute key for SSL session of a transport.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1710")
+  public static final Attributes.Key<SSLSession> TRANSPORT_ATTR_SSL_SESSION =
+          Attributes.Key.create("ssl-session");
+}
diff --git a/core/src/main/java/io/grpc/HandlerRegistry.java b/core/src/main/java/io/grpc/HandlerRegistry.java
new file mode 100644
index 0000000..4aaf011
--- /dev/null
+++ b/core/src/main/java/io/grpc/HandlerRegistry.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Registry of services and their methods used by servers to dispatching incoming calls.
+ */
+@ThreadSafe
+public abstract class HandlerRegistry {
+
+  /**
+   * Returns the {@link ServerServiceDefinition}s provided by the registry, or an empty list if not
+   * supported by the implementation.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2222")
+  public List<ServerServiceDefinition> getServices() {
+    return Collections.emptyList();
+  }
+
+  /**
+   * Lookup a {@link ServerMethodDefinition} by its fully-qualified name.
+   *
+   * @param methodName to lookup {@link ServerMethodDefinition} for.
+   * @param authority the authority for the desired method (to do virtual hosting). If {@code null}
+   *        the first matching method will be returned.
+   * @return the resolved method or {@code null} if no method for that name exists.
+   */
+  @Nullable
+  public abstract ServerMethodDefinition<?, ?> lookupMethod(
+      String methodName, @Nullable String authority);
+
+  /**
+   * Lookup a {@link ServerMethodDefinition} by its fully-qualified name.
+   *
+   * @param methodName to lookup {@link ServerMethodDefinition} for.
+   * @return the resolved method or {@code null} if no method for that name exists.
+   */
+  @Nullable
+  public final ServerMethodDefinition<?, ?> lookupMethod(String methodName) {
+    return lookupMethod(methodName, null);
+  }
+
+}
diff --git a/core/src/main/java/io/grpc/Internal.java b/core/src/main/java/io/grpc/Internal.java
new file mode 100644
index 0000000..394fe9e
--- /dev/null
+++ b/core/src/main/java/io/grpc/Internal.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates a program element (class, method, package, etc) which is internal to gRPC, not part of
+ * the public API, and should not be used by users of gRPC.
+ *
+ * <p>However, if you want to implement an alternative transport you may use the internal parts.
+ * Please consult the gRPC team first, because internal APIs don't have the same API stability
+ * guarantee as the public APIs do.
+ *
+ * <p>Note: This annotation is intended only for gRPC library code. Users should not attach this
+ * annotation to their own code.
+ *
+ * <p>See: <a href="https://github.com/grpc/grpc-java-api-checker">grpc-java-api-checker</a>, an
+ * Error Prone plugin to automatically check for usages of this API.
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target({
+    ElementType.ANNOTATION_TYPE,
+    ElementType.CONSTRUCTOR,
+    ElementType.FIELD,
+    ElementType.METHOD,
+    ElementType.PACKAGE,
+    ElementType.TYPE})
+@Documented
+public @interface Internal {
+}
diff --git a/core/src/main/java/io/grpc/InternalChannelz.java b/core/src/main/java/io/grpc/InternalChannelz.java
new file mode 100644
index 0000000..3902a7b
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalChannelz.java
@@ -0,0 +1,1098 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import java.net.SocketAddress;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentNavigableMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+
+/**
+ * This is an internal API. Do NOT use.
+ */
+@Internal
+public final class InternalChannelz {
+  private static final Logger log = Logger.getLogger(InternalChannelz.class.getName());
+  private static final InternalChannelz INSTANCE = new InternalChannelz();
+
+  private final ConcurrentNavigableMap<Long, InternalInstrumented<ServerStats>> servers
+      = new ConcurrentSkipListMap<Long, InternalInstrumented<ServerStats>>();
+  private final ConcurrentNavigableMap<Long, InternalInstrumented<ChannelStats>> rootChannels
+      = new ConcurrentSkipListMap<Long, InternalInstrumented<ChannelStats>>();
+  private final ConcurrentMap<Long, InternalInstrumented<ChannelStats>> subchannels
+      = new ConcurrentHashMap<Long, InternalInstrumented<ChannelStats>>();
+  // An InProcessTransport can appear in both otherSockets and perServerSockets simultaneously
+  private final ConcurrentMap<Long, InternalInstrumented<SocketStats>> otherSockets
+      = new ConcurrentHashMap<Long, InternalInstrumented<SocketStats>>();
+  private final ConcurrentMap<Long, ServerSocketMap> perServerSockets
+      = new ConcurrentHashMap<Long, ServerSocketMap>();
+
+  // A convenience class to avoid deeply nested types.
+  private static final class ServerSocketMap
+      extends ConcurrentSkipListMap<Long, InternalInstrumented<SocketStats>> {
+    private static final long serialVersionUID = -7883772124944661414L;
+  }
+
+  @VisibleForTesting
+  public InternalChannelz() {
+  }
+
+  public static InternalChannelz instance() {
+    return INSTANCE;
+  }
+
+  /** Adds a server. */
+  public void addServer(InternalInstrumented<ServerStats> server) {
+    ServerSocketMap prev = perServerSockets.put(id(server), new ServerSocketMap());
+    assert prev == null;
+    add(servers, server);
+  }
+
+  /** Adds a subchannel. */
+  public void addSubchannel(InternalInstrumented<ChannelStats> subchannel) {
+    add(subchannels, subchannel);
+  }
+
+  /** Adds a root channel. */
+  public void addRootChannel(InternalInstrumented<ChannelStats> rootChannel) {
+    add(rootChannels, rootChannel);
+  }
+
+  /** Adds a socket. */
+  public void addClientSocket(InternalInstrumented<SocketStats> socket) {
+    add(otherSockets, socket);
+  }
+
+  public void addListenSocket(InternalInstrumented<SocketStats> socket) {
+    add(otherSockets, socket);
+  }
+
+  /** Adds a server socket. */
+  public void addServerSocket(
+      InternalInstrumented<ServerStats> server, InternalInstrumented<SocketStats> socket) {
+    ServerSocketMap serverSockets = perServerSockets.get(id(server));
+    assert serverSockets != null;
+    add(serverSockets, socket);
+  }
+
+  /** Removes a server. */
+  public void removeServer(InternalInstrumented<ServerStats> server) {
+    remove(servers, server);
+    ServerSocketMap prev = perServerSockets.remove(id(server));
+    assert prev != null;
+    assert prev.isEmpty();
+  }
+
+  public void removeSubchannel(InternalInstrumented<ChannelStats> subchannel) {
+    remove(subchannels, subchannel);
+  }
+
+  public void removeRootChannel(InternalInstrumented<ChannelStats> channel) {
+    remove(rootChannels, channel);
+  }
+
+  public void removeClientSocket(InternalInstrumented<SocketStats> socket) {
+    remove(otherSockets, socket);
+  }
+
+  public void removeListenSocket(InternalInstrumented<SocketStats> socket) {
+    remove(otherSockets, socket);
+  }
+
+  /** Removes a server socket. */
+  public void removeServerSocket(
+      InternalInstrumented<ServerStats> server, InternalInstrumented<SocketStats> socket) {
+    ServerSocketMap socketsOfServer = perServerSockets.get(id(server));
+    assert socketsOfServer != null;
+    remove(socketsOfServer, socket);
+  }
+
+  /** Returns a {@link RootChannelList}. */
+  public RootChannelList getRootChannels(long fromId, int maxPageSize) {
+    List<InternalInstrumented<ChannelStats>> channelList
+        = new ArrayList<InternalInstrumented<ChannelStats>>();
+    Iterator<InternalInstrumented<ChannelStats>> iterator
+        = rootChannels.tailMap(fromId).values().iterator();
+
+    while (iterator.hasNext() && channelList.size() < maxPageSize) {
+      channelList.add(iterator.next());
+    }
+    return new RootChannelList(channelList, !iterator.hasNext());
+  }
+
+  /** Returns a channel. */
+  @Nullable
+  public InternalInstrumented<ChannelStats> getChannel(long id) {
+    return rootChannels.get(id);
+  }
+
+  /** Returns a subchannel. */
+  @Nullable
+  public InternalInstrumented<ChannelStats> getSubchannel(long id) {
+    return subchannels.get(id);
+  }
+
+  /** Returns a server list. */
+  public ServerList getServers(long fromId, int maxPageSize) {
+    List<InternalInstrumented<ServerStats>> serverList
+        = new ArrayList<InternalInstrumented<ServerStats>>(maxPageSize);
+    Iterator<InternalInstrumented<ServerStats>> iterator
+        = servers.tailMap(fromId).values().iterator();
+
+    while (iterator.hasNext() && serverList.size() < maxPageSize) {
+      serverList.add(iterator.next());
+    }
+    return new ServerList(serverList, !iterator.hasNext());
+  }
+
+  /** Returns socket refs for a server. */
+  @Nullable
+  public ServerSocketsList getServerSockets(long serverId, long fromId, int maxPageSize) {
+    ServerSocketMap serverSockets = perServerSockets.get(serverId);
+    if (serverSockets == null) {
+      return null;
+    }
+    List<InternalWithLogId> socketList = new ArrayList<>(maxPageSize);
+    Iterator<InternalInstrumented<SocketStats>> iterator
+        = serverSockets.tailMap(fromId).values().iterator();
+    while (socketList.size() < maxPageSize && iterator.hasNext()) {
+      socketList.add(iterator.next());
+    }
+    return new ServerSocketsList(socketList, !iterator.hasNext());
+  }
+
+  /** Returns a socket. */
+  @Nullable
+  public InternalInstrumented<SocketStats> getSocket(long id) {
+    InternalInstrumented<SocketStats> clientSocket = otherSockets.get(id);
+    if (clientSocket != null) {
+      return clientSocket;
+    }
+    return getServerSocket(id);
+  }
+
+  private InternalInstrumented<SocketStats> getServerSocket(long id) {
+    for (ServerSocketMap perServerSockets : perServerSockets.values()) {
+      InternalInstrumented<SocketStats> serverSocket = perServerSockets.get(id);
+      if (serverSocket != null) {
+        return serverSocket;
+      }
+    }
+    return null;
+  }
+
+  @VisibleForTesting
+  public boolean containsServer(InternalLogId serverRef) {
+    return contains(servers, serverRef);
+  }
+
+  @VisibleForTesting
+  public boolean containsSubchannel(InternalLogId subchannelRef) {
+    return contains(subchannels, subchannelRef);
+  }
+
+  public InternalInstrumented<ChannelStats> getRootChannel(long id) {
+    return rootChannels.get(id);
+  }
+
+  @VisibleForTesting
+  public boolean containsClientSocket(InternalLogId transportRef) {
+    return contains(otherSockets, transportRef);
+  }
+
+  private static <T extends InternalInstrumented<?>> void add(Map<Long, T> map, T object) {
+    T prev = map.put(object.getLogId().getId(), object);
+    assert prev == null;
+  }
+
+  private static <T extends InternalInstrumented<?>> void remove(Map<Long, T> map, T object) {
+    T prev = map.remove(id(object));
+    assert prev != null;
+  }
+
+  private static <T extends InternalInstrumented<?>> boolean contains(
+      Map<Long, T> map, InternalLogId id) {
+    return map.containsKey(id.getId());
+  }
+
+  public static final class RootChannelList {
+    public final List<InternalInstrumented<ChannelStats>> channels;
+    public final boolean end;
+
+    /** Creates an instance. */
+    public RootChannelList(List<InternalInstrumented<ChannelStats>> channels, boolean end) {
+      this.channels = checkNotNull(channels);
+      this.end = end;
+    }
+  }
+
+  public static final class ServerList {
+    public final List<InternalInstrumented<ServerStats>> servers;
+    public final boolean end;
+
+    /** Creates an instance. */
+    public ServerList(List<InternalInstrumented<ServerStats>> servers, boolean end) {
+      this.servers = checkNotNull(servers);
+      this.end = end;
+    }
+  }
+
+  public static final class ServerSocketsList {
+    public final List<InternalWithLogId> sockets;
+    public final boolean end;
+
+    /** Creates an instance. */
+    public ServerSocketsList(List<InternalWithLogId> sockets, boolean end) {
+      this.sockets = sockets;
+      this.end = end;
+    }
+  }
+
+  @Immutable
+  public static final class ServerStats {
+    public final long callsStarted;
+    public final long callsSucceeded;
+    public final long callsFailed;
+    public final long lastCallStartedNanos;
+    public final List<InternalInstrumented<SocketStats>> listenSockets;
+
+    /**
+     * Creates an instance.
+     */
+    public ServerStats(
+        long callsStarted,
+        long callsSucceeded,
+        long callsFailed,
+        long lastCallStartedNanos,
+        List<InternalInstrumented<SocketStats>> listenSockets) {
+      this.callsStarted = callsStarted;
+      this.callsSucceeded = callsSucceeded;
+      this.callsFailed = callsFailed;
+      this.lastCallStartedNanos = lastCallStartedNanos;
+      this.listenSockets = checkNotNull(listenSockets);
+    }
+
+    public static final class Builder {
+      private long callsStarted;
+      private long callsSucceeded;
+      private long callsFailed;
+      private long lastCallStartedNanos;
+      public List<InternalInstrumented<SocketStats>> listenSockets = Collections.emptyList();
+
+      public Builder setCallsStarted(long callsStarted) {
+        this.callsStarted = callsStarted;
+        return this;
+      }
+
+      public Builder setCallsSucceeded(long callsSucceeded) {
+        this.callsSucceeded = callsSucceeded;
+        return this;
+      }
+
+      public Builder setCallsFailed(long callsFailed) {
+        this.callsFailed = callsFailed;
+        return this;
+      }
+
+      public Builder setLastCallStartedNanos(long lastCallStartedNanos) {
+        this.lastCallStartedNanos = lastCallStartedNanos;
+        return this;
+      }
+
+      /** Sets the listen sockets. */
+      public Builder setListenSockets(List<InternalInstrumented<SocketStats>> listenSockets) {
+        checkNotNull(listenSockets);
+        this.listenSockets = Collections.unmodifiableList(
+            new ArrayList<InternalInstrumented<SocketStats>>(listenSockets));
+        return this;
+      }
+
+      /**
+       * Builds an instance.
+       */
+      public ServerStats build() {
+        return new ServerStats(
+            callsStarted,
+            callsSucceeded,
+            callsFailed,
+            lastCallStartedNanos,
+            listenSockets);
+      }
+    }
+  }
+
+  /**
+   * A data class to represent a channel's stats.
+   */
+  @Immutable
+  public static final class ChannelStats {
+    public final String target;
+    public final ConnectivityState state;
+    @Nullable public final ChannelTrace channelTrace;
+    public final long callsStarted;
+    public final long callsSucceeded;
+    public final long callsFailed;
+    public final long lastCallStartedNanos;
+    public final List<InternalWithLogId> subchannels;
+    public final List<InternalWithLogId> sockets;
+
+    /**
+     * Creates an instance.
+     */
+    private ChannelStats(
+        String target,
+        ConnectivityState state,
+        @Nullable ChannelTrace channelTrace,
+        long callsStarted,
+        long callsSucceeded,
+        long callsFailed,
+        long lastCallStartedNanos,
+        List<InternalWithLogId> subchannels,
+        List<InternalWithLogId> sockets) {
+      checkState(
+          subchannels.isEmpty() || sockets.isEmpty(),
+          "channels can have subchannels only, subchannels can have either sockets OR subchannels, "
+              + "neither can have both");
+      this.target = target;
+      this.state = state;
+      this.channelTrace = channelTrace;
+      this.callsStarted = callsStarted;
+      this.callsSucceeded = callsSucceeded;
+      this.callsFailed = callsFailed;
+      this.lastCallStartedNanos = lastCallStartedNanos;
+      this.subchannels = checkNotNull(subchannels);
+      this.sockets = checkNotNull(sockets);
+    }
+
+    public static final class Builder {
+      private String target;
+      private ConnectivityState state;
+      private ChannelTrace channelTrace;
+      private long callsStarted;
+      private long callsSucceeded;
+      private long callsFailed;
+      private long lastCallStartedNanos;
+      private List<InternalWithLogId> subchannels = Collections.emptyList();
+      private List<InternalWithLogId> sockets = Collections.emptyList();
+
+      public Builder setTarget(String target) {
+        this.target = target;
+        return this;
+      }
+
+      public Builder setState(ConnectivityState state) {
+        this.state = state;
+        return this;
+      }
+
+      public Builder setChannelTrace(ChannelTrace channelTrace) {
+        this.channelTrace = channelTrace;
+        return this;
+      }
+
+      public Builder setCallsStarted(long callsStarted) {
+        this.callsStarted = callsStarted;
+        return this;
+      }
+
+      public Builder setCallsSucceeded(long callsSucceeded) {
+        this.callsSucceeded = callsSucceeded;
+        return this;
+      }
+
+      public Builder setCallsFailed(long callsFailed) {
+        this.callsFailed = callsFailed;
+        return this;
+      }
+
+      public Builder setLastCallStartedNanos(long lastCallStartedNanos) {
+        this.lastCallStartedNanos = lastCallStartedNanos;
+        return this;
+      }
+
+      /** Sets the subchannels. */
+      public Builder setSubchannels(List<InternalWithLogId> subchannels) {
+        checkState(sockets.isEmpty());
+        this.subchannels = Collections.unmodifiableList(checkNotNull(subchannels));
+        return this;
+      }
+
+      /** Sets the sockets. */
+      public Builder setSockets(List<InternalWithLogId> sockets) {
+        checkState(subchannels.isEmpty());
+        this.sockets = Collections.unmodifiableList(checkNotNull(sockets));
+        return this;
+      }
+
+      /**
+       * Builds an instance.
+       */
+      public ChannelStats build() {
+        return new ChannelStats(
+            target,
+            state,
+            channelTrace,
+            callsStarted,
+            callsSucceeded,
+            callsFailed,
+            lastCallStartedNanos,
+            subchannels,
+            sockets);
+      }
+    }
+  }
+
+  @Immutable
+  public static final class ChannelTrace {
+    public final long numEventsLogged;
+    public final long creationTimeNanos;
+    public final List<Event> events;
+
+    private ChannelTrace(long numEventsLogged, long creationTimeNanos, List<Event> events) {
+      this.numEventsLogged = numEventsLogged;
+      this.creationTimeNanos = creationTimeNanos;
+      this.events = events;
+    }
+
+    public static final class Builder {
+      private Long numEventsLogged;
+      private Long creationTimeNanos;
+      private List<Event> events = Collections.emptyList();
+
+      public Builder setNumEventsLogged(long numEventsLogged) {
+        this.numEventsLogged = numEventsLogged;
+        return this;
+      }
+
+      public Builder setCreationTimeNanos(long creationTimeNanos) {
+        this.creationTimeNanos = creationTimeNanos;
+        return this;
+      }
+
+      public Builder setEvents(List<Event> events) {
+        this.events = Collections.unmodifiableList(new ArrayList<>(events));
+        return this;
+      }
+
+      /** Builds a new ChannelTrace instance. */
+      public ChannelTrace build() {
+        checkNotNull(numEventsLogged, "numEventsLogged");
+        checkNotNull(creationTimeNanos, "creationTimeNanos");
+        return new ChannelTrace(numEventsLogged, creationTimeNanos, events);
+      }
+    }
+
+    @Immutable
+    public static final class Event {
+      public final String description;
+      public final Severity severity;
+      public final long timestampNanos;
+
+      // the oneof child_ref field in proto: one of channelRef and channelRef
+      @Nullable public final InternalWithLogId channelRef;
+      @Nullable public final InternalWithLogId subchannelRef;
+
+      public enum Severity {
+        CT_UNKNOWN, CT_INFO, CT_WARNING, CT_ERROR
+      }
+
+      private Event(
+          String description, Severity severity, long timestampNanos,
+          @Nullable InternalWithLogId channelRef, @Nullable InternalWithLogId subchannelRef) {
+        this.description = description;
+        this.severity = checkNotNull(severity, "severity");
+        this.timestampNanos = timestampNanos;
+        this.channelRef = channelRef;
+        this.subchannelRef = subchannelRef;
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hashCode(description, severity, timestampNanos, channelRef, subchannelRef);
+      }
+
+      @Override
+      public boolean equals(Object o) {
+        if (o instanceof Event) {
+          Event that = (Event) o;
+          return Objects.equal(description, that.description)
+              && Objects.equal(severity, that.severity)
+              && timestampNanos == that.timestampNanos
+              && Objects.equal(channelRef, that.channelRef)
+              && Objects.equal(subchannelRef, that.subchannelRef);
+        }
+        return false;
+      }
+
+      @Override
+      public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("description", description)
+            .add("severity", severity)
+            .add("timestampNanos", timestampNanos)
+            .add("channelRef", channelRef)
+            .add("subchannelRef", subchannelRef)
+            .toString();
+      }
+
+      public static final class Builder {
+        private String description;
+        private Severity severity;
+        private Long timestampNanos;
+        private InternalWithLogId channelRef;
+        private InternalWithLogId subchannelRef;
+
+        public Builder setDescription(String description) {
+          this.description = description;
+          return this;
+        }
+
+        public Builder setTimestampNanos(long timestampNanos) {
+          this.timestampNanos = timestampNanos;
+          return this;
+        }
+
+        public Builder setSeverity(Severity severity) {
+          this.severity = severity;
+          return this;
+        }
+
+        public Builder setChannelRef(InternalWithLogId channelRef) {
+          this.channelRef = channelRef;
+          return this;
+        }
+
+        public Builder setSubchannelRef(InternalWithLogId subchannelRef) {
+          this.subchannelRef = subchannelRef;
+          return this;
+        }
+
+        /** Builds a new Event instance. */
+        public Event build() {
+          checkNotNull(description, "description");
+          checkNotNull(severity, "severity");
+          checkNotNull(timestampNanos, "timestampNanos");
+          checkState(
+              channelRef == null || subchannelRef == null,
+              "at least one of channelRef and subchannelRef must be null");
+          return new Event(description, severity, timestampNanos, channelRef, subchannelRef);
+        }
+      }
+    }
+  }
+
+  public static final class Security {
+    @Nullable
+    public final Tls tls;
+    @Nullable
+    public final OtherSecurity other;
+
+    public Security(Tls tls) {
+      this.tls = checkNotNull(tls);
+      this.other = null;
+    }
+
+    public Security(OtherSecurity other) {
+      this.tls = null;
+      this.other = checkNotNull(other);
+    }
+  }
+
+  public static final class OtherSecurity {
+    public final String name;
+    @Nullable
+    public final Object any;
+
+    /**
+     * Creates an instance.
+     * @param name the name.
+     * @param any a com.google.protobuf.Any object
+     */
+    public OtherSecurity(String name, @Nullable Object any) {
+      this.name = checkNotNull(name);
+      checkState(
+          any == null || any.getClass().getName().endsWith("com.google.protobuf.Any"),
+          "the 'any' object must be of type com.google.protobuf.Any");
+      this.any = any;
+    }
+  }
+
+  @Immutable
+  public static final class Tls {
+    public final String cipherSuiteStandardName;
+    @Nullable public final Certificate localCert;
+    @Nullable public final Certificate remoteCert;
+
+    /**
+     * A constructor only for testing.
+     */
+    public Tls(String cipherSuiteName, Certificate localCert, Certificate remoteCert) {
+      this.cipherSuiteStandardName = cipherSuiteName;
+      this.localCert = localCert;
+      this.remoteCert = remoteCert;
+    }
+
+    /**
+     * Creates an instance.
+     */
+    public Tls(SSLSession session) {
+      String cipherSuiteStandardName = session.getCipherSuite();
+      Certificate localCert = null;
+      Certificate remoteCert = null;
+      Certificate[] localCerts = session.getLocalCertificates();
+      if (localCerts != null) {
+        localCert = localCerts[0];
+      }
+      try {
+        Certificate[] peerCerts = session.getPeerCertificates();
+        if (peerCerts != null) {
+          // The javadoc of getPeerCertificate states that the peer's own certificate is the first
+          // element of the list.
+          remoteCert = peerCerts[0];
+        }
+      } catch (SSLPeerUnverifiedException e) {
+        // peer cert is not available
+        log.log(
+            Level.FINE,
+            String.format("Peer cert not available for peerHost=%s", session.getPeerHost()),
+            e);
+      }
+      this.cipherSuiteStandardName = cipherSuiteStandardName;
+      this.localCert = localCert;
+      this.remoteCert = remoteCert;
+    }
+  }
+
+  public static final class SocketStats {
+    @Nullable public final TransportStats data;
+    @Nullable public final SocketAddress local;
+    @Nullable public final SocketAddress remote;
+    public final SocketOptions socketOptions;
+    // Can be null if plaintext
+    @Nullable public final Security security;
+
+    /** Creates an instance. */
+    public SocketStats(
+        TransportStats data,
+        @Nullable SocketAddress local,
+        @Nullable SocketAddress remote,
+        SocketOptions socketOptions,
+        Security security) {
+      this.data = data;
+      this.local = checkNotNull(local, "local socket");
+      this.remote = remote;
+      this.socketOptions = checkNotNull(socketOptions);
+      this.security = security;
+    }
+  }
+
+  public static final class TcpInfo {
+    public final int state;
+    public final int caState;
+    public final int retransmits;
+    public final int probes;
+    public final int backoff;
+    public final int options;
+    public final int sndWscale;
+    public final int rcvWscale;
+    public final int rto;
+    public final int ato;
+    public final int sndMss;
+    public final int rcvMss;
+    public final int unacked;
+    public final int sacked;
+    public final int lost;
+    public final int retrans;
+    public final int fackets;
+    public final int lastDataSent;
+    public final int lastAckSent;
+    public final int lastDataRecv;
+    public final int lastAckRecv;
+    public final int pmtu;
+    public final int rcvSsthresh;
+    public final int rtt;
+    public final int rttvar;
+    public final int sndSsthresh;
+    public final int sndCwnd;
+    public final int advmss;
+    public final int reordering;
+
+    TcpInfo(int state, int caState, int retransmits, int probes, int backoff, int options,
+        int sndWscale, int rcvWscale, int rto, int ato, int sndMss, int rcvMss, int unacked,
+        int sacked, int lost, int retrans, int fackets, int lastDataSent, int lastAckSent,
+        int lastDataRecv, int lastAckRecv, int pmtu, int rcvSsthresh, int rtt, int rttvar,
+        int sndSsthresh, int sndCwnd, int advmss, int reordering) {
+      this.state = state;
+      this.caState = caState;
+      this.retransmits = retransmits;
+      this.probes = probes;
+      this.backoff = backoff;
+      this.options = options;
+      this.sndWscale = sndWscale;
+      this.rcvWscale = rcvWscale;
+      this.rto = rto;
+      this.ato = ato;
+      this.sndMss = sndMss;
+      this.rcvMss = rcvMss;
+      this.unacked = unacked;
+      this.sacked = sacked;
+      this.lost = lost;
+      this.retrans = retrans;
+      this.fackets = fackets;
+      this.lastDataSent = lastDataSent;
+      this.lastAckSent = lastAckSent;
+      this.lastDataRecv = lastDataRecv;
+      this.lastAckRecv = lastAckRecv;
+      this.pmtu = pmtu;
+      this.rcvSsthresh = rcvSsthresh;
+      this.rtt = rtt;
+      this.rttvar = rttvar;
+      this.sndSsthresh = sndSsthresh;
+      this.sndCwnd = sndCwnd;
+      this.advmss = advmss;
+      this.reordering = reordering;
+    }
+
+    public static final class Builder {
+      private int state;
+      private int caState;
+      private int retransmits;
+      private int probes;
+      private int backoff;
+      private int options;
+      private int sndWscale;
+      private int rcvWscale;
+      private int rto;
+      private int ato;
+      private int sndMss;
+      private int rcvMss;
+      private int unacked;
+      private int sacked;
+      private int lost;
+      private int retrans;
+      private int fackets;
+      private int lastDataSent;
+      private int lastAckSent;
+      private int lastDataRecv;
+      private int lastAckRecv;
+      private int pmtu;
+      private int rcvSsthresh;
+      private int rtt;
+      private int rttvar;
+      private int sndSsthresh;
+      private int sndCwnd;
+      private int advmss;
+      private int reordering;
+
+      public Builder setState(int state) {
+        this.state = state;
+        return this;
+      }
+
+      public Builder setCaState(int caState) {
+        this.caState = caState;
+        return this;
+      }
+
+      public Builder setRetransmits(int retransmits) {
+        this.retransmits = retransmits;
+        return this;
+      }
+
+      public Builder setProbes(int probes) {
+        this.probes = probes;
+        return this;
+      }
+
+      public Builder setBackoff(int backoff) {
+        this.backoff = backoff;
+        return this;
+      }
+
+      public Builder setOptions(int options) {
+        this.options = options;
+        return this;
+      }
+
+      public Builder setSndWscale(int sndWscale) {
+        this.sndWscale = sndWscale;
+        return this;
+      }
+
+      public Builder setRcvWscale(int rcvWscale) {
+        this.rcvWscale = rcvWscale;
+        return this;
+      }
+
+      public Builder setRto(int rto) {
+        this.rto = rto;
+        return this;
+      }
+
+      public Builder setAto(int ato) {
+        this.ato = ato;
+        return this;
+      }
+
+      public Builder setSndMss(int sndMss) {
+        this.sndMss = sndMss;
+        return this;
+      }
+
+      public Builder setRcvMss(int rcvMss) {
+        this.rcvMss = rcvMss;
+        return this;
+      }
+
+      public Builder setUnacked(int unacked) {
+        this.unacked = unacked;
+        return this;
+      }
+
+      public Builder setSacked(int sacked) {
+        this.sacked = sacked;
+        return this;
+      }
+
+      public Builder setLost(int lost) {
+        this.lost = lost;
+        return this;
+      }
+
+      public Builder setRetrans(int retrans) {
+        this.retrans = retrans;
+        return this;
+      }
+
+      public Builder setFackets(int fackets) {
+        this.fackets = fackets;
+        return this;
+      }
+
+      public Builder setLastDataSent(int lastDataSent) {
+        this.lastDataSent = lastDataSent;
+        return this;
+      }
+
+      public Builder setLastAckSent(int lastAckSent) {
+        this.lastAckSent = lastAckSent;
+        return this;
+      }
+
+      public Builder setLastDataRecv(int lastDataRecv) {
+        this.lastDataRecv = lastDataRecv;
+        return this;
+      }
+
+      public Builder setLastAckRecv(int lastAckRecv) {
+        this.lastAckRecv = lastAckRecv;
+        return this;
+      }
+
+      public Builder setPmtu(int pmtu) {
+        this.pmtu = pmtu;
+        return this;
+      }
+
+      public Builder setRcvSsthresh(int rcvSsthresh) {
+        this.rcvSsthresh = rcvSsthresh;
+        return this;
+      }
+
+      public Builder setRtt(int rtt) {
+        this.rtt = rtt;
+        return this;
+      }
+
+      public Builder setRttvar(int rttvar) {
+        this.rttvar = rttvar;
+        return this;
+      }
+
+      public Builder setSndSsthresh(int sndSsthresh) {
+        this.sndSsthresh = sndSsthresh;
+        return this;
+      }
+
+      public Builder setSndCwnd(int sndCwnd) {
+        this.sndCwnd = sndCwnd;
+        return this;
+      }
+
+      public Builder setAdvmss(int advmss) {
+        this.advmss = advmss;
+        return this;
+      }
+
+      public Builder setReordering(int reordering) {
+        this.reordering = reordering;
+        return this;
+      }
+
+      /** Builds an instance. */
+      public TcpInfo build() {
+        return new TcpInfo(
+            state, caState, retransmits, probes, backoff, options, sndWscale, rcvWscale,
+            rto, ato, sndMss, rcvMss, unacked, sacked, lost, retrans, fackets, lastDataSent,
+            lastAckSent, lastDataRecv, lastAckRecv, pmtu, rcvSsthresh, rtt, rttvar, sndSsthresh,
+            sndCwnd, advmss, reordering);
+      }
+    }
+  }
+
+  public static final class SocketOptions {
+    public final Map<String, String> others;
+    // In netty, the value of a channel option may be null.
+    @Nullable public final Integer soTimeoutMillis;
+    @Nullable public final Integer lingerSeconds;
+    @Nullable public final TcpInfo tcpInfo;
+
+    /** Creates an instance. */
+    public SocketOptions(
+        @Nullable Integer timeoutMillis,
+        @Nullable Integer lingerSeconds,
+        @Nullable TcpInfo tcpInfo,
+        Map<String, String> others) {
+      checkNotNull(others);
+      this.soTimeoutMillis = timeoutMillis;
+      this.lingerSeconds = lingerSeconds;
+      this.tcpInfo = tcpInfo;
+      this.others = Collections.unmodifiableMap(new HashMap<String, String>(others));
+    }
+
+    public static final class Builder {
+      private final Map<String, String> others = new HashMap<String, String>();
+
+      private TcpInfo tcpInfo;
+      private Integer timeoutMillis;
+      private Integer lingerSeconds;
+
+      /** The value of {@link java.net.Socket#getSoTimeout()}. */
+      public Builder setSocketOptionTimeoutMillis(Integer timeoutMillis) {
+        this.timeoutMillis = timeoutMillis;
+        return this;
+      }
+
+      /** The value of {@link java.net.Socket#getSoLinger()}.
+       * Note: SO_LINGER is typically expressed in seconds.
+       */
+      public Builder setSocketOptionLingerSeconds(Integer lingerSeconds) {
+        this.lingerSeconds = lingerSeconds;
+        return this;
+      }
+
+      public Builder setTcpInfo(TcpInfo tcpInfo) {
+        this.tcpInfo = tcpInfo;
+        return this;
+      }
+
+      public Builder addOption(String name, String value) {
+        others.put(name, checkNotNull(value));
+        return this;
+      }
+
+      public Builder addOption(String name, int value) {
+        others.put(name, Integer.toString(value));
+        return this;
+      }
+
+      public Builder addOption(String name, boolean value) {
+        others.put(name, Boolean.toString(value));
+        return this;
+      }
+
+      public SocketOptions build() {
+        return new SocketOptions(timeoutMillis, lingerSeconds, tcpInfo, others);
+      }
+    }
+  }
+
+  /**
+   * A data class to represent transport stats.
+   */
+  @Immutable
+  public static final class TransportStats {
+    public final long streamsStarted;
+    public final long lastLocalStreamCreatedTimeNanos;
+    public final long lastRemoteStreamCreatedTimeNanos;
+    public final long streamsSucceeded;
+    public final long streamsFailed;
+    public final long messagesSent;
+    public final long messagesReceived;
+    public final long keepAlivesSent;
+    public final long lastMessageSentTimeNanos;
+    public final long lastMessageReceivedTimeNanos;
+    public final long localFlowControlWindow;
+    public final long remoteFlowControlWindow;
+    // TODO(zpencer): report socket flags and other info
+
+    /**
+     * Creates an instance.
+     */
+    public TransportStats(
+        long streamsStarted,
+        long lastLocalStreamCreatedTimeNanos,
+        long lastRemoteStreamCreatedTimeNanos,
+        long streamsSucceeded,
+        long streamsFailed,
+        long messagesSent,
+        long messagesReceived,
+        long keepAlivesSent,
+        long lastMessageSentTimeNanos,
+        long lastMessageReceivedTimeNanos,
+        long localFlowControlWindow,
+        long remoteFlowControlWindow) {
+      this.streamsStarted = streamsStarted;
+      this.lastLocalStreamCreatedTimeNanos = lastLocalStreamCreatedTimeNanos;
+      this.lastRemoteStreamCreatedTimeNanos = lastRemoteStreamCreatedTimeNanos;
+      this.streamsSucceeded = streamsSucceeded;
+      this.streamsFailed = streamsFailed;
+      this.messagesSent = messagesSent;
+      this.messagesReceived = messagesReceived;
+      this.keepAlivesSent = keepAlivesSent;
+      this.lastMessageSentTimeNanos = lastMessageSentTimeNanos;
+      this.lastMessageReceivedTimeNanos = lastMessageReceivedTimeNanos;
+      this.localFlowControlWindow = localFlowControlWindow;
+      this.remoteFlowControlWindow = remoteFlowControlWindow;
+    }
+  }
+
+  /** Unwraps a {@link InternalLogId} to return a {@code long}. */
+  public static long id(InternalWithLogId withLogId) {
+    return withLogId.getLogId().getId();
+  }
+}
diff --git a/core/src/main/java/io/grpc/InternalClientInterceptors.java b/core/src/main/java/io/grpc/InternalClientInterceptors.java
new file mode 100644
index 0000000..e6c8acd
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalClientInterceptors.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import io.grpc.MethodDescriptor.Marshaller;
+
+/**
+ * Internal accessor. Do not use.
+ */
+@Internal
+public final class InternalClientInterceptors {
+  public static <ReqT, RespT> ClientInterceptor wrapClientInterceptor(
+      final ClientInterceptor interceptor,
+      final Marshaller<ReqT> reqMarshaller,
+      final Marshaller<RespT> respMarshaller) {
+    return ClientInterceptors.wrapClientInterceptor(interceptor, reqMarshaller, respMarshaller);
+  }
+}
diff --git a/core/src/main/java/io/grpc/InternalDecompressorRegistry.java b/core/src/main/java/io/grpc/InternalDecompressorRegistry.java
new file mode 100644
index 0000000..c0563a4
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalDecompressorRegistry.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * Private accessor for decompressor registries.  Don't use this.
+ */
+@Internal
+public final class InternalDecompressorRegistry {
+  private InternalDecompressorRegistry() {}
+
+  @Internal
+  public static byte[] getRawAdvertisedMessageEncodings(DecompressorRegistry reg) {
+    return reg.getRawAdvertisedMessageEncodings();
+  }
+}
diff --git a/core/src/main/java/io/grpc/InternalInstrumented.java b/core/src/main/java/io/grpc/InternalInstrumented.java
new file mode 100644
index 0000000..44366d4
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalInstrumented.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * An internal class. Do not use.
+ *
+ * <p>An interface for types that <b>may</b> support instrumentation. If the actual type does not
+ * support instrumentation, then the future will return a {@code null}.
+ */
+@Internal
+public interface InternalInstrumented<T> extends InternalWithLogId {
+
+  /**
+   * Returns the stats object.
+   */
+  ListenableFuture<T> getStats();
+}
diff --git a/core/src/main/java/io/grpc/InternalKnownTransport.java b/core/src/main/java/io/grpc/InternalKnownTransport.java
new file mode 100644
index 0000000..3aca25f
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalKnownTransport.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * All known transports.
+ *
+ * <p>Make sure to update MethodDescriptor.rawMethodNames if this is changed.
+ */
+@Internal
+public enum InternalKnownTransport {
+  NETTY,
+  ;
+}
+
diff --git a/core/src/main/java/io/grpc/InternalLogId.java b/core/src/main/java/io/grpc/InternalLogId.java
new file mode 100644
index 0000000..709a133
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalLogId.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * An internal class. Do not use.
+ *
+ *<p>An object that has an ID that is unique within the JVM, primarily for debug logging.
+ */
+@Internal
+public final class InternalLogId {
+  private static final AtomicLong idAlloc = new AtomicLong();
+
+  /**
+   * @param tag a loggable tag associated with this tag. The ID that is allocated is guaranteed
+   *            to be unique and increasing, irrespective of the tag.
+   */
+  public static InternalLogId allocate(String tag) {
+    return new InternalLogId(tag, getNextId());
+  }
+
+  static long getNextId() {
+    return idAlloc.incrementAndGet();
+  }
+
+  private final String tag;
+  private final long id;
+
+  protected InternalLogId(String tag, long id) {
+    this.tag = tag;
+    this.id = id;
+  }
+
+  public long getId() {
+    return id;
+  }
+
+  public String getTag() {
+    return tag;
+  }
+
+  @Override
+  public String toString() {
+    return tag + "-" + id;
+  }
+}
diff --git a/core/src/main/java/io/grpc/InternalManagedChannelProvider.java b/core/src/main/java/io/grpc/InternalManagedChannelProvider.java
new file mode 100644
index 0000000..37fd52a
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalManagedChannelProvider.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+public final class InternalManagedChannelProvider {
+  public static final Iterable<Class<?>> HARDCODED_CLASSES =
+      ManagedChannelProvider.HARDCODED_CLASSES;
+}
diff --git a/core/src/main/java/io/grpc/InternalMetadata.java b/core/src/main/java/io/grpc/InternalMetadata.java
new file mode 100644
index 0000000..3e19702
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalMetadata.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import io.grpc.Metadata.AsciiMarshaller;
+import io.grpc.Metadata.Key;
+import java.nio.charset.Charset;
+
+/**
+ * Internal {@link Metadata} accessor. This is intended for use by io.grpc.internal, and the
+ * specifically supported transport packages. If you *really* think you need to use this, contact
+ * the gRPC team first.
+ */
+@Internal
+public final class InternalMetadata {
+
+  /**
+   * A specialized plain ASCII marshaller. Both input and output are assumed to be valid header
+   * ASCII.
+   *
+   * <p>Extended here to break the dependency.
+   */
+  @Internal
+  public interface TrustedAsciiMarshaller<T> extends Metadata.TrustedAsciiMarshaller<T> {}
+
+  /**
+   * Copy of StandardCharsets, which is only available on Java 1.7 and above.
+   */
+  @Internal
+  public static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+  @Internal
+  public static <T> Key<T> keyOf(String name, TrustedAsciiMarshaller<T> marshaller) {
+    boolean isPseudo = name != null && !name.isEmpty() && name.charAt(0) == ':';
+    return Metadata.Key.of(name, isPseudo, marshaller);
+  }
+
+  @Internal
+  public static <T> Key<T> keyOf(String name, AsciiMarshaller<T> marshaller) {
+    boolean isPseudo = name != null && !name.isEmpty() && name.charAt(0) == ':';
+    return Metadata.Key.of(name, isPseudo, marshaller);
+  }
+
+  @Internal
+  public static Metadata newMetadata(byte[]... binaryValues) {
+    return new Metadata(binaryValues);
+  }
+
+  @Internal
+  public static Metadata newMetadata(int usedNames, byte[]... binaryValues) {
+    return new Metadata(usedNames, binaryValues);
+  }
+
+  @Internal
+  public static byte[][] serialize(Metadata md) {
+    return md.serialize();
+  }
+
+  @Internal
+  public static int headerCount(Metadata md) {
+    return md.headerCount();
+  }
+}
diff --git a/core/src/main/java/io/grpc/InternalMethodDescriptor.java b/core/src/main/java/io/grpc/InternalMethodDescriptor.java
new file mode 100644
index 0000000..9ffc188
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalMethodDescriptor.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Accesses internal data and methods.  Do not use this.
+ */
+@Internal
+public final class InternalMethodDescriptor {
+  private final InternalKnownTransport transport;
+
+  public InternalMethodDescriptor(InternalKnownTransport transport) {
+    // TODO(carl-mastrangelo): maybe restrict access to this.
+    this.transport = checkNotNull(transport, "transport");
+  }
+
+  public Object geRawMethodName(MethodDescriptor<?, ?> md) {
+    return md.getRawMethodName(transport.ordinal());
+  }
+
+  public void setRawMethodName(MethodDescriptor<?, ?> md, Object o) {
+    md.setRawMethodName(transport.ordinal(), o);
+  }
+}
diff --git a/core/src/main/java/io/grpc/InternalNotifyOnServerBuild.java b/core/src/main/java/io/grpc/InternalNotifyOnServerBuild.java
new file mode 100644
index 0000000..b52acfa
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalNotifyOnServerBuild.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * Provides a callback method for a service to receive a reference to its server. The contract with
+ * {@link ServerBuilder} is that this method will be called on all registered services implementing
+ * the interface after build() has been called and before the {@link Server} instance is returned.
+ */
+@Internal
+public interface InternalNotifyOnServerBuild {
+  /** Notifies the service that the server has been built. */
+  void notifyOnBuild(Server server);
+}
diff --git a/core/src/main/java/io/grpc/InternalServerInterceptors.java b/core/src/main/java/io/grpc/InternalServerInterceptors.java
new file mode 100644
index 0000000..26c0b35
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalServerInterceptors.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * Accessor to internal methods of {@link ServerInterceptors}.
+ */
+@Internal
+public final class InternalServerInterceptors {
+  public static <ReqT, RespT> ServerCallHandler<ReqT, RespT> interceptCallHandler(
+      ServerInterceptor interceptor, ServerCallHandler<ReqT, RespT> callHandler) {
+    return ServerInterceptors.InterceptCallHandler.create(interceptor, callHandler);
+  }
+
+  public static <OrigReqT, OrigRespT, WrapReqT, WrapRespT>
+      ServerMethodDefinition<WrapReqT, WrapRespT> wrapMethod(
+      final ServerMethodDefinition<OrigReqT, OrigRespT> definition,
+      final MethodDescriptor<WrapReqT, WrapRespT> wrappedMethod) {
+    return ServerInterceptors.wrapMethod(definition, wrappedMethod);
+  }
+
+  public static <ReqT, RespT> ServerCallHandler<ReqT, RespT> interceptCallHandlerCreate(
+      ServerInterceptor interceptor, ServerCallHandler<ReqT, RespT> callHandler) {
+    return ServerInterceptors.InterceptCallHandler.create(interceptor, callHandler);
+  }
+
+  private InternalServerInterceptors() {
+  }
+}
diff --git a/core/src/main/java/io/grpc/InternalServiceProviders.java b/core/src/main/java/io/grpc/InternalServiceProviders.java
new file mode 100644
index 0000000..c72e01d
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalServiceProviders.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.List;
+
+@Internal
+public final class InternalServiceProviders {
+  private InternalServiceProviders() {
+  }
+
+  /**
+   * Accessor for method.
+   */
+  public static <T> T load(
+      Class<T> klass,
+      Iterable<Class<?>> hardcoded,
+      ClassLoader classLoader,
+      PriorityAccessor<T> priorityAccessor) {
+    return ServiceProviders.load(klass, hardcoded, classLoader, priorityAccessor);
+  }
+
+  /**
+   * Accessor for method.
+   */
+  public static <T> List<T> loadAll(
+      Class<T> klass,
+      Iterable<Class<?>> hardCodedClasses,
+      ClassLoader classLoader,
+      PriorityAccessor<T> priorityAccessor) {
+    return ServiceProviders.loadAll(klass, hardCodedClasses, classLoader, priorityAccessor);
+  }
+
+  /**
+   * Accessor for method.
+   */
+  @VisibleForTesting
+  public static <T> Iterable<T> getCandidatesViaServiceLoader(Class<T> klass, ClassLoader cl) {
+    return ServiceProviders.getCandidatesViaServiceLoader(klass, cl);
+  }
+
+  /**
+   * Accessor for method.
+   */
+  @VisibleForTesting
+  public static <T> Iterable<T> getCandidatesViaHardCoded(
+      Class<T> klass, Iterable<Class<?>> hardcoded) {
+    return ServiceProviders.getCandidatesViaHardCoded(klass, hardcoded);
+  }
+
+  /**
+   * Accessor for {@link ServiceProviders#isAndroid}.
+   */
+  public static boolean isAndroid(ClassLoader cl) {
+    return ServiceProviders.isAndroid(cl);
+  }
+
+  public interface PriorityAccessor<T> extends ServiceProviders.PriorityAccessor<T> {}
+}
diff --git a/core/src/main/java/io/grpc/InternalStatus.java b/core/src/main/java/io/grpc/InternalStatus.java
new file mode 100644
index 0000000..9f6854a
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalStatus.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * Accesses internal data.  Do not use this.
+ */
+@Internal
+public final class InternalStatus {
+  private InternalStatus() {}
+
+  /**
+   * Key to bind status message to trailing metadata.
+   */
+  @Internal
+  public static final Metadata.Key<String> MESSAGE_KEY = Status.MESSAGE_KEY;
+
+  /**
+   * Key to bind status code to trailing metadata.
+   */
+  @Internal
+  public static final Metadata.Key<Status> CODE_KEY = Status.CODE_KEY;
+}
diff --git a/core/src/main/java/io/grpc/InternalWithLogId.java b/core/src/main/java/io/grpc/InternalWithLogId.java
new file mode 100644
index 0000000..930c345
--- /dev/null
+++ b/core/src/main/java/io/grpc/InternalWithLogId.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * An internal class. Do not use.
+ *
+ * <p>A loggable ID, unique for the duration of the program.
+ */
+@Internal
+public interface InternalWithLogId {
+  /**
+   * Returns an ID that is primarily used in debug logs. It usually contains the class name and a
+   * numeric ID that is unique among the instances.
+   *
+   * <p>The subclasses of this interface usually want to include the log ID in their {@link
+   * #toString} results.
+   */
+  InternalLogId getLogId();
+}
diff --git a/core/src/main/java/io/grpc/KnownLength.java b/core/src/main/java/io/grpc/KnownLength.java
new file mode 100644
index 0000000..f31c338
--- /dev/null
+++ b/core/src/main/java/io/grpc/KnownLength.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.io.IOException;
+
+/**
+ * An {@link java.io.InputStream} or alike whose total number of bytes that can be read is known
+ * upfront.
+ *
+ * <p>Usually it's a {@link java.io.InputStream} that also implements this interface, in which case
+ * {@link java.io.InputStream#available()} has a stronger semantic by returning an accurate number
+ * instead of an estimation.
+ */
+public interface KnownLength {
+  /**
+   * Returns the total number of bytes that can be read (or skipped over) from this object until all
+   * bytes have been read out.
+   */
+  int available() throws IOException;
+}
diff --git a/core/src/main/java/io/grpc/LoadBalancer.java b/core/src/main/java/io/grpc/LoadBalancer.java
new file mode 100644
index 0000000..633bf52
--- /dev/null
+++ b/core/src/main/java/io/grpc/LoadBalancer.java
@@ -0,0 +1,668 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import java.util.Collections;
+import java.util.List;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import javax.annotation.concurrent.NotThreadSafe;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * A pluggable component that receives resolved addresses from {@link NameResolver} and provides the
+ * channel a usable subchannel when asked.
+ *
+ * <h3>Overview</h3>
+ *
+ * <p>A LoadBalancer typically implements three interfaces:
+ * <ol>
+ *   <li>{@link LoadBalancer} is the main interface.  All methods on it are invoked sequentially
+ *       from the Channel Executor.  It receives the results from the {@link NameResolver}, updates
+ *       of subchannels' connectivity states, and the channel's request for the LoadBalancer to
+ *       shutdown.</li>
+ *   <li>{@link SubchannelPicker SubchannelPicker} does the actual load-balancing work.  It selects
+ *       a {@link Subchannel Subchannel} for each new RPC.</li>
+ *   <li>{@link Factory Factory} creates a new {@link LoadBalancer} instance.
+ * </ol>
+ *
+ * <p>{@link Helper Helper} is implemented by gRPC library and provided to {@link Factory
+ * Factory}. It provides functionalities that a {@code LoadBalancer} implementation would typically
+ * need.
+ *
+ * <h3>Channel Executor</h3>
+ *
+ * <p>Channel Executor is an internal executor of the channel, which is used to serialize all the
+ * callback methods on the {@link LoadBalancer} interface, thus the balancer implementation doesn't
+ * need to worry about synchronization among them.  However, the actual thread of the Channel
+ * Executor is typically the network thread, thus the following rules must be followed to prevent
+ * blocking or even dead-locking in a network
+ *
+ * <ol>
+ *
+ *   <li><strong>Never block in Channel Executor</strong>.  The callback methods must return
+ *   quickly.  Examples or work that must be avoided: CPU-intensive calculation, waiting on
+ *   synchronization primitives, blocking I/O, blocking RPCs, etc.</li>
+ *
+ *   <li><strong>Avoid calling into other components with lock held</strong>.  Channel Executor may
+ *   run callbacks under a lock, e.g., the transport lock of OkHttp.  If your LoadBalancer has a
+ *   lock, holds the lock in a callback method (e.g., {@link #handleSubchannelState
+ *   handleSubchannelState()}) while calling into another class that may involve locks, be cautious
+ *   of deadlock.  Generally you wouldn't need any locking in the LoadBalancer.</li>
+ *
+ * </ol>
+ *
+ * <p>{@link Helper#runSerialized Helper.runSerialized()} allows you to schedule a task to be run in
+ * the Channel Executor.
+ *
+ * <h3>The canonical implementation pattern</h3>
+ *
+ * <p>A {@link LoadBalancer} keeps states like the latest addresses from NameResolver, the
+ * Subchannel(s) and their latest connectivity states.  These states are mutated within the Channel
+ * Executor.
+ *
+ * <p>A typical {@link SubchannelPicker SubchannelPicker} holds a snapshot of these states.  It may
+ * have its own states, e.g., a picker from a round-robin load-balancer may keep a pointer to the
+ * next Subchannel, which are typically mutated by multiple threads.  The picker should only mutate
+ * its own state, and should not mutate or re-acquire the states of the LoadBalancer.  This way the
+ * picker only needs to synchronize its own states, which is typically trivial to implement.
+ *
+ * <p>When the LoadBalancer states changes, e.g., Subchannels has become or stopped being READY, and
+ * we want subsequent RPCs to use the latest list of READY Subchannels, LoadBalancer would create
+ * a new picker, which holds a snapshot of the latest Subchannel list.  Refer to the javadoc of
+ * {@link #handleSubchannelState handleSubchannelState()} how to do this properly.
+ *
+ * <p>No synchronization should be necessary between LoadBalancer and its pickers if you follow
+ * the pattern above.  It may be possible to implement in a different way, but that would usually
+ * result in more complicated threading.
+ *
+ * @since 1.2.0
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
+@NotThreadSafe
+public abstract class LoadBalancer {
+  /**
+   * Handles newly resolved server groups and metadata attributes from name resolution system.
+   * {@code servers} contained in {@link EquivalentAddressGroup} should be considered equivalent
+   * but may be flattened into a single list if needed.
+   *
+   * <p>Implementations should not modify the given {@code servers}.
+   *
+   * @param servers the resolved server addresses, never empty.
+   * @param attributes extra metadata from naming system.
+   * @since 1.2.0
+   */
+  public abstract void handleResolvedAddressGroups(
+      List<EquivalentAddressGroup> servers, Attributes attributes);
+
+  /**
+   * Handles an error from the name resolution system.
+   *
+   * @param error a non-OK status
+   * @since 1.2.0
+   */
+  public abstract void handleNameResolutionError(Status error);
+
+  /**
+   * Handles a state change on a Subchannel.
+   *
+   * <p>The initial state of a Subchannel is IDLE. You won't get a notification for the initial IDLE
+   * state.
+   *
+   * <p>If the new state is not SHUTDOWN, this method should create a new picker and call {@link
+   * Helper#updateBalancingState Helper.updateBalancingState()}.  Failing to do so may result in
+   * unnecessary delays of RPCs. Please refer to {@link PickResult#withSubchannel
+   * PickResult.withSubchannel()}'s javadoc for more information.
+   *
+   * <p>SHUTDOWN can only happen in two cases.  One is that LoadBalancer called {@link
+   * Subchannel#shutdown} earlier, thus it should have already discarded this Subchannel.  The other
+   * is that Channel is doing a {@link ManagedChannel#shutdownNow forced shutdown} or has already
+   * terminated, thus there won't be further requests to LoadBalancer.  Therefore, SHUTDOWN can be
+   * safely ignored.
+   *
+   * @param subchannel the involved Subchannel
+   * @param stateInfo the new state
+   * @since 1.2.0
+   */
+  public abstract void handleSubchannelState(
+      Subchannel subchannel, ConnectivityStateInfo stateInfo);
+
+  /**
+   * The channel asks the load-balancer to shutdown.  No more callbacks will be called after this
+   * method.  The implementation should shutdown all Subchannels and OOB channels, and do any other
+   * cleanup as necessary.
+   *
+   * @since 1.2.0
+   */
+  public abstract void shutdown();
+
+  /**
+   * The main balancing logic.  It <strong>must be thread-safe</strong>. Typically it should only
+   * synchronize on its own state, and avoid synchronizing with the LoadBalancer's state.
+   *
+   * @since 1.2.0
+   */
+  @ThreadSafe
+  public abstract static class SubchannelPicker {
+    /**
+     * Make a balancing decision for a new RPC.
+     *
+     * @param args the pick arguments
+     * @since 1.3.0
+     */
+    public abstract PickResult pickSubchannel(PickSubchannelArgs args);
+
+    /**
+     * Tries to establish connections now so that the upcoming RPC may then just pick a ready
+     * connection without having to connect first.
+     *
+     * <p>No-op if unsupported.
+     *
+     * @since 1.11.0
+     */
+    public void requestConnection() {}
+  }
+
+  /**
+   * Provides arguments for a {@link SubchannelPicker#pickSubchannel(
+   * LoadBalancer.PickSubchannelArgs)}.
+   *
+   * @since 1.2.0
+   */
+  public abstract static class PickSubchannelArgs {
+
+    /**
+     * Call options.
+     *
+     * @since 1.2.0
+     */
+    public abstract CallOptions getCallOptions();
+
+    /**
+     * Headers of the call. {@link SubchannelPicker#pickSubchannel} may mutate it before before
+     * returning.
+     *
+     * @since 1.2.0
+     */
+    public abstract Metadata getHeaders();
+
+    /**
+     * Call method.
+     *
+     * @since 1.2.0
+     */
+    public abstract MethodDescriptor<?, ?> getMethodDescriptor();
+  }
+
+  /**
+   * A balancing decision made by {@link SubchannelPicker SubchannelPicker} for an RPC.
+   *
+   * <p>The outcome of the decision will be one of the following:
+   * <ul>
+   *   <li>Proceed: if a Subchannel is provided via {@link #withSubchannel withSubchannel()}, and is
+   *       in READY state when the RPC tries to start on it, the RPC will proceed on that
+   *       Subchannel.</li>
+   *   <li>Error: if an error is provided via {@link #withError withError()}, and the RPC is not
+   *       wait-for-ready (i.e., {@link CallOptions#withWaitForReady} was not called), the RPC will
+   *       fail immediately with the given error.</li>
+   *   <li>Buffer: in all other cases, the RPC will be buffered in the Channel, until the next
+   *       picker is provided via {@link Helper#updateBalancingState Helper.updateBalancingState()},
+   *       when the RPC will go through the same picking process again.</li>
+   * </ul>
+   *
+   * @since 1.2.0
+   */
+  @Immutable
+  public static final class PickResult {
+    private static final PickResult NO_RESULT = new PickResult(null, null, Status.OK, false);
+
+    @Nullable private final Subchannel subchannel;
+    @Nullable private final ClientStreamTracer.Factory streamTracerFactory;
+    // An error to be propagated to the application if subchannel == null
+    // Or OK if there is no error.
+    // subchannel being null and error being OK means RPC needs to wait
+    private final Status status;
+    // True if the result is created by withDrop()
+    private final boolean drop;
+
+    private PickResult(
+        @Nullable Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory,
+        Status status, boolean drop) {
+      this.subchannel = subchannel;
+      this.streamTracerFactory = streamTracerFactory;
+      this.status = Preconditions.checkNotNull(status, "status");
+      this.drop = drop;
+    }
+
+    /**
+     * A decision to proceed the RPC on a Subchannel.
+     *
+     * <p>Only Subchannels returned by {@link Helper#createSubchannel Helper.createSubchannel()}
+     * will work.  DO NOT try to use your own implementations of Subchannels, as they won't work.
+     *
+     * <p>When the RPC tries to use the return Subchannel, which is briefly after this method
+     * returns, the state of the Subchannel will decide where the RPC would go:
+     *
+     * <ul>
+     *   <li>READY: the RPC will proceed on this Subchannel.</li>
+     *   <li>IDLE: the RPC will be buffered.  Subchannel will attempt to create connection.</li>
+     *   <li>All other states: the RPC will be buffered.</li>
+     * </ul>
+     *
+     * <p><strong>All buffered RPCs will stay buffered</strong> until the next call of {@link
+     * Helper#updateBalancingState Helper.updateBalancingState()}, which will trigger a new picking
+     * process.
+     *
+     * <p>Note that Subchannel's state may change at the same time the picker is making the
+     * decision, which means the decision may be made with (to-be) outdated information.  For
+     * example, a picker may return a Subchannel known to be READY, but it has become IDLE when is
+     * about to be used by the RPC, which makes the RPC to be buffered.  The LoadBalancer will soon
+     * learn about the Subchannels' transition from READY to IDLE, create a new picker and allow the
+     * RPC to use another READY transport if there is any.
+     *
+     * <p>You will want to avoid running into a situation where there are READY Subchannels out
+     * there but some RPCs are still buffered for longer than a brief time.
+     * <ul>
+     *   <li>This can happen if you return Subchannels with states other than READY and IDLE.  For
+     *       example, suppose you round-robin on 2 Subchannels, in READY and CONNECTING states
+     *       respectively.  If the picker ignores the state and pick them equally, 50% of RPCs will
+     *       be stuck in buffered state until both Subchannels are READY.</li>
+     *   <li>This can also happen if you don't create a new picker at key state changes of
+     *       Subchannels.  Take the above round-robin example again.  Suppose you do pick only READY
+     *       and IDLE Subchannels, and initially both Subchannels are READY.  Now one becomes IDLE,
+     *       then CONNECTING and stays CONNECTING for a long time.  If you don't create a new picker
+     *       in response to the CONNECTING state to exclude that Subchannel, 50% of RPCs will hit it
+     *       and be buffered even though the other Subchannel is READY.</li>
+     * </ul>
+     *
+     * <p>In order to prevent unnecessary delay of RPCs, the rules of thumb are:
+     * <ol>
+     *   <li>The picker should only pick Subchannels that are known as READY or IDLE.  Whether to
+     *       pick IDLE Subchannels depends on whether you want Subchannels to connect on-demand or
+     *       actively:
+     *       <ul>
+     *         <li>If you want connect-on-demand, include IDLE Subchannels in your pick results,
+     *             because when an RPC tries to use an IDLE Subchannel, the Subchannel will try to
+     *             connect.</li>
+     *         <li>If you want Subchannels to be always connected even when there is no RPC, you
+     *             would call {@link Subchannel#requestConnection Subchannel.requestConnection()}
+     *             whenever the Subchannel has transitioned to IDLE, then you don't need to include
+     *             IDLE Subchannels in your pick results.</li>
+     *       </ul></li>
+     *   <li>Always create a new picker and call {@link Helper#updateBalancingState
+     *       Helper.updateBalancingState()} whenever {@link #handleSubchannelState
+     *       handleSubchannelState()} is called, unless the new state is SHUTDOWN. See
+     *       {@code handleSubchannelState}'s javadoc for more details.</li>
+     * </ol>
+     *
+     * @param subchannel the picked Subchannel
+     * @param streamTracerFactory if not null, will be used to trace the activities of the stream
+     *                            created as a result of this pick. Note it's possible that no
+     *                            stream is created at all in some cases.
+     * @since 1.3.0
+     */
+    public static PickResult withSubchannel(
+        Subchannel subchannel, @Nullable ClientStreamTracer.Factory streamTracerFactory) {
+      return new PickResult(
+          Preconditions.checkNotNull(subchannel, "subchannel"), streamTracerFactory, Status.OK,
+          false);
+    }
+
+    /**
+     * Equivalent to {@code withSubchannel(subchannel, null)}.
+     *
+     * @since 1.2.0
+     */
+    public static PickResult withSubchannel(Subchannel subchannel) {
+      return withSubchannel(subchannel, null);
+    }
+
+    /**
+     * A decision to report a connectivity error to the RPC.  If the RPC is {@link
+     * CallOptions#withWaitForReady wait-for-ready}, it will stay buffered.  Otherwise, it will fail
+     * with the given error.
+     *
+     * @param error the error status.  Must not be OK.
+     * @since 1.2.0
+     */
+    public static PickResult withError(Status error) {
+      Preconditions.checkArgument(!error.isOk(), "error status shouldn't be OK");
+      return new PickResult(null, null, error, false);
+    }
+
+    /**
+     * A decision to fail an RPC immediately.  This is a final decision and will ignore retry
+     * policy.
+     *
+     * @param status the status with which the RPC will fail.  Must not be OK.
+     * @since 1.8.0
+     */
+    public static PickResult withDrop(Status status) {
+      Preconditions.checkArgument(!status.isOk(), "drop status shouldn't be OK");
+      return new PickResult(null, null, status, true);
+    }
+
+    /**
+     * No decision could be made.  The RPC will stay buffered.
+     *
+     * @since 1.2.0
+     */
+    public static PickResult withNoResult() {
+      return NO_RESULT;
+    }
+
+    /**
+     * The Subchannel if this result was created by {@link #withSubchannel withSubchannel()}, or
+     * null otherwise.
+     *
+     * @since 1.2.0
+     */
+    @Nullable
+    public Subchannel getSubchannel() {
+      return subchannel;
+    }
+
+    /**
+     * The stream tracer factory this result was created with.
+     *
+     * @since 1.3.0
+     */
+    @Nullable
+    public ClientStreamTracer.Factory getStreamTracerFactory() {
+      return streamTracerFactory;
+    }
+
+    /**
+     * The status associated with this result.  Non-{@code OK} if created with {@link #withError
+     * withError}, or {@code OK} otherwise.
+     *
+     * @since 1.2.0
+     */
+    public Status getStatus() {
+      return status;
+    }
+
+    /**
+     * Returns {@code true} if this result was created by {@link #withDrop withDrop()}.
+     *
+     * @since 1.8.0
+     */
+    public boolean isDrop() {
+      return drop;
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("subchannel", subchannel)
+          .add("streamTracerFactory", streamTracerFactory)
+          .add("status", status)
+          .add("drop", drop)
+          .toString();
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(subchannel, status, streamTracerFactory, drop);
+    }
+
+    /**
+     * Returns true if the {@link Subchannel}, {@link Status}, and
+     * {@link ClientStreamTracer.Factory} all match.
+     */
+    @Override
+    public boolean equals(Object other) {
+      if (!(other instanceof PickResult)) {
+        return false;
+      }
+      PickResult that = (PickResult) other;
+      return Objects.equal(subchannel, that.subchannel) && Objects.equal(status, that.status)
+          && Objects.equal(streamTracerFactory, that.streamTracerFactory)
+          && drop == that.drop;
+    }
+  }
+
+  /**
+   * Provides essentials for LoadBalancer implementations.
+   *
+   * @since 1.2.0
+   */
+  @ThreadSafe
+  public abstract static class Helper {
+    /**
+     * Creates a Subchannel, which is a logical connection to the given group of addresses which are
+     * considered equivalent.  The {@code attrs} are custom attributes associated with this
+     * Subchannel, and can be accessed later through {@link Subchannel#getAttributes
+     * Subchannel.getAttributes()}.
+     *
+     * <p>The LoadBalancer is responsible for closing unused Subchannels, and closing all
+     * Subchannels within {@link #shutdown}.
+     *
+     * <p>The default implementation calls {@link #createSubchannel(List, Attributes)}.
+     * Implementations should not override this method.
+     *
+     * @since 1.2.0
+     */
+    public Subchannel createSubchannel(EquivalentAddressGroup addrs, Attributes attrs) {
+      Preconditions.checkNotNull(addrs, "addrs");
+      return createSubchannel(Collections.singletonList(addrs), attrs);
+    }
+
+    /**
+     * Creates a Subchannel, which is a logical connection to the given group of addresses which are
+     * considered equivalent.  The {@code attrs} are custom attributes associated with this
+     * Subchannel, and can be accessed later through {@link Subchannel#getAttributes
+     * Subchannel.getAttributes()}.
+     *
+     * <p>The LoadBalancer is responsible for closing unused Subchannels, and closing all
+     * Subchannels within {@link #shutdown}.
+     *
+     * @throws IllegalArgumentException if {@code addrs} is empty
+     * @since 1.14.0
+     */
+    public Subchannel createSubchannel(List<EquivalentAddressGroup> addrs, Attributes attrs) {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Replaces the existing addresses used with {@code subchannel}. This method is superior to
+     * {@link #createSubchannel} when the new and old addresses overlap, since the subchannel can
+     * continue using an existing connection.
+     *
+     * <p>The default implementation calls {@link #updateSubchannelAddresses(
+     * LoadBalancer.Subchannel, List)}. Implementations should not override this method.
+     *
+     * @throws IllegalArgumentException if {@code subchannel} was not returned from {@link
+     *     #createSubchannel}
+     * @since 1.4.0
+     */
+    public void updateSubchannelAddresses(
+        Subchannel subchannel, EquivalentAddressGroup addrs) {
+      Preconditions.checkNotNull(addrs, "addrs");
+      updateSubchannelAddresses(subchannel, Collections.singletonList(addrs));
+    }
+
+    /**
+     * Replaces the existing addresses used with {@code subchannel}. This method is superior to
+     * {@link #createSubchannel} when the new and old addresses overlap, since the subchannel can
+     * continue using an existing connection.
+     *
+     * @throws IllegalArgumentException if {@code subchannel} was not returned from {@link
+     *     #createSubchannel} or {@code addrs} is empty
+     * @since 1.14.0
+     */
+    public void updateSubchannelAddresses(
+        Subchannel subchannel, List<EquivalentAddressGroup> addrs) {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Out-of-band channel for LoadBalancer’s own RPC needs, e.g., talking to an external
+     * load-balancer service.
+     *
+     * <p>The LoadBalancer is responsible for closing unused OOB channels, and closing all OOB
+     * channels within {@link #shutdown}.
+     *
+     * @since 1.4.0
+     */
+    // TODO(ejona): Allow passing a List<EAG> here and to updateOobChannelAddresses, but want to
+    // wait until https://github.com/grpc/grpc-java/issues/4469 is done.
+    // https://github.com/grpc/grpc-java/issues/4618
+    public abstract ManagedChannel createOobChannel(EquivalentAddressGroup eag, String authority);
+
+    /**
+     * Updates the addresses used for connections in the {@code Channel}. This is supperior to
+     * {@link #createOobChannel} when the old and new addresses overlap, since the channel can
+     * continue using an existing connection.
+     *
+     * @throws IllegalArgumentException if {@code channel} was not returned from {@link
+     *     #createOobChannel}
+     * @since 1.4.0
+     */
+    public void updateOobChannelAddresses(ManagedChannel channel, EquivalentAddressGroup eag) {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Set a new state with a new picker to the channel.
+     *
+     * <p>When a new picker is provided via {@code updateBalancingState()}, the channel will apply
+     * the picker on all buffered RPCs, by calling {@link SubchannelPicker#pickSubchannel(
+     * LoadBalancer.PickSubchannelArgs)}.
+     *
+     * <p>The channel will hold the picker and use it for all RPCs, until {@code
+     * updateBalancingState()} is called again and a new picker replaces the old one.  If {@code
+     * updateBalancingState()} has never been called, the channel will buffer all RPCs until a
+     * picker is provided.
+     *
+     * <p>The passed state will be the channel's new state. The SHUTDOWN state should not be passed
+     * and its behavior is undefined.
+     *
+     * @since 1.6.0
+     */
+    public abstract void updateBalancingState(
+        @Nonnull ConnectivityState newState, @Nonnull SubchannelPicker newPicker);
+
+    /**
+     * Schedule a task to be run in the Channel Executor, which serializes the task with the
+     * callback methods on the {@link LoadBalancer} interface.
+     *
+     * @since 1.2.0
+     */
+    public abstract void runSerialized(Runnable task);
+
+    /**
+     * Returns the NameResolver of the channel.
+     *
+     * @since 1.2.0
+     */
+    public abstract NameResolver.Factory getNameResolverFactory();
+
+    /**
+     * Returns the authority string of the channel, which is derived from the DNS-style target name.
+     *
+     * @since 1.2.0
+     */
+    public abstract String getAuthority();
+  }
+
+  /**
+   * A logical connection to a server, or a group of equivalent servers represented by an {@link 
+   * EquivalentAddressGroup}.
+   *
+   * <p>It maintains at most one physical connection (aka transport) for sending new RPCs, while
+   * also keeps track of previous transports that has been shut down but not terminated yet.
+   *
+   * <p>If there isn't an active transport yet, and an RPC is assigned to the Subchannel, it will
+   * create a new transport.  It won't actively create transports otherwise.  {@link
+   * #requestConnection requestConnection()} can be used to ask Subchannel to create a transport if
+   * there isn't any.
+   *
+   * @since 1.2.0
+   */
+  @ThreadSafe
+  public abstract static class Subchannel {
+    /**
+     * Shuts down the Subchannel.  After this method is called, this Subchannel should no longer
+     * be returned by the latest {@link SubchannelPicker picker}, and can be safely discarded.
+     *
+     * @since 1.2.0
+     */
+    public abstract void shutdown();
+
+    /**
+     * Asks the Subchannel to create a connection (aka transport), if there isn't an active one.
+     *
+     * @since 1.2.0
+     */
+    public abstract void requestConnection();
+
+    /**
+     * Returns the addresses that this Subchannel is bound to. The default implementation calls
+     * getAllAddresses().
+     *
+     * <p>The default implementation calls {@link #getAllAddresses()}. Implementations should not
+     * override this method.
+     *
+     * @throws IllegalStateException if this subchannel has more than one EquivalentAddressGroup.
+     *     Use getAllAddresses() instead
+     * @since 1.2.0
+     */
+    public EquivalentAddressGroup getAddresses() {
+      List<EquivalentAddressGroup> groups = getAllAddresses();
+      Preconditions.checkState(groups.size() == 1, "Does not have exactly one group");
+      return groups.get(0);
+    }
+
+    /**
+     * Returns the addresses that this Subchannel is bound to. The returned list will not be empty.
+     *
+     * @since 1.14.0
+     */
+    public List<EquivalentAddressGroup> getAllAddresses() {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * The same attributes passed to {@link Helper#createSubchannel Helper.createSubchannel()}.
+     * LoadBalancer can use it to attach additional information here, e.g., the shard this
+     * Subchannel belongs to.
+     *
+     * @since 1.2.0
+     */
+    public abstract Attributes getAttributes();
+  }
+
+  /**
+   * Factory to create {@link LoadBalancer} instance.
+   *
+   * @since 1.2.0
+   */
+  @ThreadSafe
+  public abstract static class Factory {
+    /**
+     * Creates a {@link LoadBalancer} that will be used inside a channel.
+     *
+     * @since 1.2.0
+     */
+    public abstract LoadBalancer newLoadBalancer(Helper helper);
+  }
+}
diff --git a/core/src/main/java/io/grpc/ManagedChannel.java b/core/src/main/java/io/grpc/ManagedChannel.java
new file mode 100644
index 0000000..7875fdb
--- /dev/null
+++ b/core/src/main/java/io/grpc/ManagedChannel.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.util.concurrent.TimeUnit;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * A {@link Channel} that provides lifecycle management.
+ */
+@ThreadSafe
+public abstract class ManagedChannel extends Channel {
+  /**
+   * Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately
+   * cancelled.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  public abstract ManagedChannel shutdown();
+
+  /**
+   * Returns whether the channel is shutdown. Shutdown channels immediately cancel any new calls,
+   * but may still have some calls being processed.
+   *
+   * @see #shutdown()
+   * @see #isTerminated()
+   * @since 1.0.0
+   */
+  public abstract boolean isShutdown();
+
+  /**
+   * Returns whether the channel is terminated. Terminated channels have no running calls and
+   * relevant resources released (like TCP connections).
+   *
+   * @see #isShutdown()
+   * @since 1.0.0
+   */
+  public abstract boolean isTerminated();
+
+  /**
+   * Initiates a forceful shutdown in which preexisting and new calls are cancelled. Although
+   * forceful, the shutdown process is still not instantaneous; {@link #isTerminated()} will likely
+   * return {@code false} immediately after this method returns.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  public abstract ManagedChannel shutdownNow();
+
+  /**
+   * Waits for the channel to become terminated, giving up if the timeout is reached.
+   *
+   * @return whether the channel is terminated, as would be done by {@link #isTerminated()}.
+   * @since 1.0.0
+   */
+  public abstract boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
+
+  /**
+   * Gets the current connectivity state. Note the result may soon become outdated.
+   *
+   * <p>Note that the core library did not provide an implementation of this method until v1.6.1.
+   *
+   * @param requestConnection if {@code true}, the channel will try to make a connection if it is
+   *        currently IDLE
+   * @throws UnsupportedOperationException if not supported by implementation
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4359")
+  public ConnectivityState getState(boolean requestConnection) {
+    throw new UnsupportedOperationException("Not implemented");
+  }
+
+  /**
+   * Registers a one-off callback that will be run if the connectivity state of the channel diverges
+   * from the given {@code source}, which is typically what has just been returned by {@link
+   * #getState}.  If the states are already different, the callback will be called immediately.  The
+   * callback is run in the same executor that runs Call listeners.
+   *
+   * <p>There is an inherent race between the notification to {@code callback} and any call to
+   * {@code getState()}. There is a similar race between {@code getState()} and a call to {@code
+   * notifyWhenStateChanged()}. The state can change during those races, so there is not a way to
+   * see every state transition. "Transitions" to the same state are possible, because intermediate
+   * states may not have been observed. The API is only reliable in tracking the <em>current</em>
+   * state.
+   *
+   * <p>Note that the core library did not provide an implementation of this method until v1.6.1.
+   *
+   * @param source the assumed current state, typically just returned by {@link #getState}
+   * @param callback the one-off callback
+   * @throws UnsupportedOperationException if not supported by implementation
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4359")
+  public void notifyWhenStateChanged(ConnectivityState source, Runnable callback) {
+    throw new UnsupportedOperationException("Not implemented");
+  }
+
+  /**
+   * For subchannels that are in TRANSIENT_FAILURE state, short-circuit the backoff timer and make
+   * them reconnect immediately. May also attempt to invoke {@link NameResolver#refresh}.
+   *
+   * <p>This is primarily intended for Android users, where the network may experience frequent
+   * temporary drops. Rather than waiting for gRPC's name resolution and reconnect timers to elapse
+   * before reconnecting, the app may use this method as a mechanism to notify gRPC that the network
+   * is now available and a reconnection attempt may occur immediately.
+   *
+   * <p>No-op if not supported by the implementation.
+   *
+   * @since 1.8.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4056")
+  public void resetConnectBackoff() {}
+
+  /**
+   * Invoking this method moves the channel into the IDLE state and triggers tear-down of the
+   * channel's name resolver and load balancer, while still allowing on-going RPCs on the channel to
+   * continue. New RPCs on the channel will trigger creation of a new connection.
+   *
+   * <p>This is primarily intended for Android users when a device is transitioning from a cellular
+   * to a wifi connection. The OS will issue a notification that a new network (wifi) has been made
+   * the default, but for approximately 30 seconds the device will maintain both the cellular
+   * and wifi connections. Apps may invoke this method to ensure that new RPCs are created using the
+   * new default wifi network, rather than the soon-to-be-disconnected cellular network.
+   *
+   * <p>No-op if not supported by implementation.
+   *
+   * @since 1.11.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4056")
+  public void enterIdle() {}
+}
diff --git a/core/src/main/java/io/grpc/ManagedChannelBuilder.java b/core/src/main/java/io/grpc/ManagedChannelBuilder.java
new file mode 100644
index 0000000..7fc48c5
--- /dev/null
+++ b/core/src/main/java/io/grpc/ManagedChannelBuilder.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.base.Preconditions;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A builder for {@link ManagedChannel} instances.
+ *
+ * @param <T> The concrete type of this builder.
+ */
+public abstract class ManagedChannelBuilder<T extends ManagedChannelBuilder<T>> {
+  /**
+   * Creates a channel with the target's address and port number.
+   *
+   * @see #forTarget(String)
+   * @since 1.0.0
+   */
+  public static ManagedChannelBuilder<?> forAddress(String name, int port) {
+    return ManagedChannelProvider.provider().builderForAddress(name, port);
+  }
+
+  /**
+   * Creates a channel with a target string, which can be either a valid {@link
+   * NameResolver}-compliant URI, or an authority string.
+   *
+   * <p>A {@code NameResolver}-compliant URI is an absolute hierarchical URI as defined by {@link
+   * java.net.URI}. Example URIs:
+   * <ul>
+   *   <li>{@code "dns:///foo.googleapis.com:8080"}</li>
+   *   <li>{@code "dns:///foo.googleapis.com"}</li>
+   *   <li>{@code "dns:///%5B2001:db8:85a3:8d3:1319:8a2e:370:7348%5D:443"}</li>
+   *   <li>{@code "dns://8.8.8.8/foo.googleapis.com:8080"}</li>
+   *   <li>{@code "dns://8.8.8.8/foo.googleapis.com"}</li>
+   *   <li>{@code "zookeeper://zk.example.com:9900/example_service"}</li>
+   * </ul>
+   *
+   * <p>An authority string will be converted to a {@code NameResolver}-compliant URI, which has
+   * {@code "dns"} as the scheme, no authority, and the original authority string as its path after
+   * properly escaped. Example authority strings:
+   * <ul>
+   *   <li>{@code "localhost"}</li>
+   *   <li>{@code "127.0.0.1"}</li>
+   *   <li>{@code "localhost:8080"}</li>
+   *   <li>{@code "foo.googleapis.com:8080"}</li>
+   *   <li>{@code "127.0.0.1:8080"}</li>
+   *   <li>{@code "[2001:db8:85a3:8d3:1319:8a2e:370:7348]"}</li>
+   *   <li>{@code "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443"}</li>
+   * </ul>
+   *
+   * @since 1.0.0
+   */
+  public static ManagedChannelBuilder<?> forTarget(String target) {
+    return ManagedChannelProvider.provider().builderForTarget(target);
+  }
+
+  /**
+   * Execute application code directly in the transport thread.
+   *
+   * <p>Depending on the underlying transport, using a direct executor may lead to substantial
+   * performance improvements. However, it also requires the application to not block under
+   * any circumstances.
+   *
+   * <p>Calling this method is semantically equivalent to calling {@link #executor(Executor)} and
+   * passing in a direct executor. However, this is the preferred way as it may allow the transport
+   * to perform special optimizations.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  public abstract T directExecutor();
+
+  /**
+   * Provides a custom executor.
+   *
+   * <p>It's an optional parameter. If the user has not provided an executor when the channel is
+   * built, the builder will use a static cached thread pool.
+   *
+   * <p>The channel won't take ownership of the given executor. It's caller's responsibility to
+   * shut down the executor when it's desired.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  public abstract T executor(Executor executor);
+
+  /**
+   * Adds interceptors that will be called before the channel performs its real work. This is
+   * functionally equivalent to using {@link ClientInterceptors#intercept(Channel, List)}, but while
+   * still having access to the original {@code ManagedChannel}.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  public abstract T intercept(List<ClientInterceptor> interceptors);
+
+  /**
+   * Adds interceptors that will be called before the channel performs its real work. This is
+   * functionally equivalent to using {@link ClientInterceptors#intercept(Channel,
+   * ClientInterceptor...)}, but while still having access to the original {@code ManagedChannel}.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  public abstract T intercept(ClientInterceptor... interceptors);
+
+  /**
+   * Provides a custom {@code User-Agent} for the application.
+   *
+   * <p>It's an optional parameter. The library will provide a user agent independent of this
+   * option. If provided, the given agent will prepend the library's user agent information.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  public abstract T userAgent(String userAgent);
+
+  /**
+   * Overrides the authority used with TLS and HTTP virtual hosting. It does not change what host is
+   * actually connected to. Is commonly in the form {@code host:port}.
+   *
+   * <p>This method is intended for testing, but may safely be used outside of tests as an
+   * alternative to DNS overrides.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  public abstract T overrideAuthority(String authority);
+
+  /**
+   * Use of a plaintext connection to the server. By default a secure connection mechanism
+   * such as TLS will be used.
+   *
+   * <p>Should only be used for testing or for APIs where the use of such API or the data
+   * exchanged is not sensitive.
+   *
+   * @param skipNegotiation @{code true} if there is a priori knowledge that the endpoint supports
+   *                        plaintext, {@code false} if plaintext use must be negotiated.
+   * @deprecated Use {@link #usePlaintext()} instead.
+   *
+   * @throws UnsupportedOperationException if plaintext mode is not supported.
+   * @return this
+   * @since 1.0.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1772")
+  @Deprecated
+  public T usePlaintext(boolean skipNegotiation) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Use of a plaintext connection to the server. By default a secure connection mechanism
+   * such as TLS will be used.
+   *
+   * <p>Should only be used for testing or for APIs where the use of such API or the data
+   * exchanged is not sensitive.
+   *
+   * <p>This assumes prior knowledge that the target of this channel is using plaintext.  It will
+   * not perform HTTP/1.1 upgrades.
+   *
+   *
+   * @throws UnsupportedOperationException if plaintext mode is not supported.
+   * @return this
+   * @since 1.11.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1772")
+  @SuppressWarnings("deprecation")
+  public T usePlaintext() {
+    return usePlaintext(true);
+  }
+
+  /**
+   * Makes the client use TLS.
+   *
+   * @return this
+   * @throws UnsupportedOperationException if transport security is not supported.
+   * @since 1.9.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/3713")
+  public T useTransportSecurity() {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Provides a custom {@link NameResolver.Factory} for the channel. If this method is not called,
+   * the builder will try the providers listed by {@link NameResolverProvider#providers()} for the
+   * given target.
+   *
+   * <p>This method should rarely be used, as name resolvers should provide a {@code
+   * NameResolverProvider} and users rely on service loading to find implementations in the class
+   * path. That allows application's configuration to easily choose the name resolver via the
+   * 'target' string passed to {@link ManagedChannelBuilder#forTarget(String)}.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
+  public abstract T nameResolverFactory(NameResolver.Factory resolverFactory);
+
+  /**
+   * Provides a custom {@link LoadBalancer.Factory} for the channel.
+   *
+   * <p>If this method is not called, the builder will use {@link PickFirstBalancerFactory}
+   * for the channel.
+   *
+   * <p>This method is implemented by all stock channel builders that
+   * are shipped with gRPC, but may not be implemented by custom channel builders, in which case
+   * this method will throw.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
+  public abstract T loadBalancerFactory(LoadBalancer.Factory loadBalancerFactory);
+
+  /**
+   * Enables full-stream decompression of inbound streams. This will cause the channel's outbound
+   * headers to advertise support for GZIP compressed streams, and gRPC servers which support the
+   * feature may respond with a GZIP compressed stream.
+   *
+   * <p>EXPERIMENTAL: This method is here to enable an experimental feature, and may be changed or
+   * removed once the feature is stable.
+   *
+   * @throws UnsupportedOperationException if unsupported
+   * @since 1.7.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/3399")
+  public T enableFullStreamDecompression() {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Set the decompression registry for use in the channel. This is an advanced API call and
+   * shouldn't be used unless you are using custom message encoding. The default supported
+   * decompressors are in {@link DecompressorRegistry#getDefaultInstance}.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+  public abstract T decompressorRegistry(DecompressorRegistry registry);
+
+  /**
+   * Set the compression registry for use in the channel.  This is an advanced API call and
+   * shouldn't be used unless you are using custom message encoding.   The default supported
+   * compressors are in {@link CompressorRegistry#getDefaultInstance}.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+  public abstract T compressorRegistry(CompressorRegistry registry);
+
+  /**
+   * Set the duration without ongoing RPCs before going to idle mode.
+   *
+   * <p>In idle mode the channel shuts down all connections, the NameResolver and the
+   * LoadBalancer. A new RPC would take the channel out of idle mode. A channel starts in idle mode.
+   *
+   * <p>By default the channel will never go to idle mode after it leaves the initial idle
+   * mode.
+   *
+   * <p>This is an advisory option. Do not rely on any specific behavior related to this option.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2022")
+  public abstract T idleTimeout(long value, TimeUnit unit);
+
+  /**
+   * Sets the maximum message size allowed to be received on the channel. If not called,
+   * defaults to 4 MiB. The default provides protection to clients who haven't considered the
+   * possibility of receiving large messages while trying to be large enough to not be hit in normal
+   * usage.
+   *
+   * <p>This method is advisory, and implementations may decide to not enforce this.  Currently,
+   * the only known transport to not enforce this is {@code InProcessTransport}.
+   *
+   * @param bytes the maximum number of bytes a single message can be.
+   * @return this
+   * @throws IllegalArgumentException if bytes is negative.
+   * @since 1.1.0
+   */
+  public T maxInboundMessageSize(int bytes) {
+    // intentional noop rather than throw, this method is only advisory.
+    Preconditions.checkArgument(bytes >= 0, "bytes must be >= 0");
+    return thisT();
+  }
+
+  /**
+   * Sets the time without read activity before sending a keepalive ping. An unreasonably small
+   * value might be increased, and {@code Long.MAX_VALUE} nano seconds or an unreasonably large
+   * value will disable keepalive. Defaults to infinite.
+   *
+   * <p>Clients must receive permission from the service owner before enabling this option.
+   * Keepalives can increase the load on services and are commonly "invisible" making it hard to
+   * notice when they are causing excessive load. Clients are strongly encouraged to use only as
+   * small of a value as necessary.
+   *
+   * @throws UnsupportedOperationException if unsupported
+   * @since 1.7.0
+   */
+  public T keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Sets the time waiting for read activity after sending a keepalive ping. If the time expires
+   * without any read activity on the connection, the connection is considered dead. An unreasonably
+   * small value might be increased. Defaults to 20 seconds.
+   *
+   * <p>This value should be at least multiple times the RTT to allow for lost packets.
+   *
+   * @throws UnsupportedOperationException if unsupported
+   * @since 1.7.0
+   */
+  public T keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Sets whether keepalive will be performed when there are no outstanding RPC on a connection.
+   * Defaults to {@code false}.
+   *
+   * <p>Clients must receive permission from the service owner before enabling this option.
+   * Keepalives on unused connections can easilly accidentally consume a considerable amount of
+   * bandwidth and CPU. {@link ManagedChannelBuilder#idleTimeout idleTimeout()} should generally be
+   * used instead of this option.
+   *
+   * @throws UnsupportedOperationException if unsupported
+   * @see #keepAliveTime(long, TimeUnit)
+   * @since 1.7.0
+   */
+  public T keepAliveWithoutCalls(boolean enable) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Sets max number of retry attempts. The total number of retry attempts for each RPC will not
+   * exceed this number even if service config may allow a higher number. Setting this number to
+   * zero is not effectively the same as {@code disableRetry()} because the former does not disable
+   * <a
+   * href="https://github.com/grpc/proposal/blob/master/A6-client-retries.md#transparent-retries">
+   * transparent retry</a>.
+   *
+   * <p>This method may not work as expected for the current release because retry is not fully
+   * implemented yet.
+   *
+   * @return this
+   * @since 1.11.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/3982")
+  public T maxRetryAttempts(int maxRetryAttempts) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Sets max number of hedged attempts. The total number of hedged attempts for each RPC will not
+   * exceed this number even if service config may allow a higher number.
+   *
+   * <p>This method may not work as expected for the current release because retry is not fully
+   * implemented yet.
+   *
+   * @return this
+   * @since 1.11.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/3982")
+  public T maxHedgedAttempts(int maxHedgedAttempts) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Sets the retry buffer size in bytes. If the buffer limit is exceeded, no RPC
+   * could retry at the moment, and in hedging case all hedges but one of the same RPC will cancel.
+   * The implementation may only estimate the buffer size being used rather than count the
+   * exact physical memory allocated. The method does not have any effect if retry is disabled by
+   * the client.
+   *
+   * <p>This method may not work as expected for the current release because retry is not fully
+   * implemented yet.
+   *
+   * @return this
+   * @since 1.10.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/3982")
+  public T retryBufferSize(long bytes) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Sets the per RPC buffer limit in bytes used for retry. The RPC is not retriable if its buffer
+   * limit is exceeded. The implementation may only estimate the buffer size being used rather than
+   * count the exact physical memory allocated. It does not have any effect if retry is disabled by
+   * the client.
+   *
+   * <p>This method may not work as expected for the current release because retry is not fully
+   * implemented yet.
+   *
+   * @return this
+   * @since 1.10.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/3982")
+  public T perRpcBufferLimit(long bytes) {
+    throw new UnsupportedOperationException();
+  }
+
+
+  /**
+   * Disables the retry and hedging mechanism provided by the gRPC library. This is designed for the
+   * case when users have their own retry implementation and want to avoid their own retry taking
+   * place simultaneously with the gRPC library layer retry.
+   *
+   * @return this
+   * @since 1.11.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/3982")
+  public T disableRetry() {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Enables the retry and hedging mechanism provided by the gRPC library.
+   *
+   * <p>This method may not work as expected for the current release because retry is not fully
+   * implemented yet.
+   *
+   * @return this
+   * @since 1.11.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/3982")
+  public T enableRetry() {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Sets the BinaryLog object that this channel should log to. The channel does not take
+   * ownership of the object, and users are responsible for calling {@link BinaryLog#close()}.
+   *
+   * @param binaryLog the object to provide logging.
+   * @return this
+   * @since 1.13.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4017")
+  public T setBinaryLog(BinaryLog binaryLog) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Sets the maximum number of channel trace events to keep in the tracer for each channel or
+   * subchannel. If set to 0, channel tracing is effectively disabled.
+   *
+   * @return this
+   * @since 1.13.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4471")
+  public T maxTraceEvents(int maxTraceEvents) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Builds a channel using the given parameters.
+   *
+   * @since 1.0.0
+   */
+  public abstract ManagedChannel build();
+
+  /**
+   * Returns the correctly typed version of the builder.
+   */
+  private T thisT() {
+    @SuppressWarnings("unchecked")
+    T thisT = (T) this;
+    return thisT;
+  }
+}
diff --git a/core/src/main/java/io/grpc/ManagedChannelProvider.java b/core/src/main/java/io/grpc/ManagedChannelProvider.java
new file mode 100644
index 0000000..3455440
--- /dev/null
+++ b/core/src/main/java/io/grpc/ManagedChannelProvider.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.ServiceProviders.PriorityAccessor;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Provider of managed channels for transport agnostic consumption.
+ *
+ * <p>Implementations <em>should not</em> throw. If they do, it may interrupt class loading. If
+ * exceptions may reasonably occur for implementation-specific reasons, implementations should
+ * generally handle the exception gracefully and return {@code false} from {@link #isAvailable()}.
+ */
+@Internal
+public abstract class ManagedChannelProvider {
+  @VisibleForTesting
+  static final Iterable<Class<?>> HARDCODED_CLASSES = new HardcodedClasses();
+
+  private static final ManagedChannelProvider provider = ServiceProviders.load(
+      ManagedChannelProvider.class,
+      HARDCODED_CLASSES,
+      ManagedChannelProvider.class.getClassLoader(),
+      new PriorityAccessor<ManagedChannelProvider>() {
+        @Override
+        public boolean isAvailable(ManagedChannelProvider provider) {
+          return provider.isAvailable();
+        }
+
+        @Override
+        public int getPriority(ManagedChannelProvider provider) {
+          return provider.priority();
+        }
+      });
+
+  /**
+   * Returns the ClassLoader-wide default channel.
+   *
+   * @throws ProviderNotFoundException if no provider is available
+   */
+  public static ManagedChannelProvider provider() {
+    if (provider == null) {
+      throw new ProviderNotFoundException("No functional channel service provider found. "
+          + "Try adding a dependency on the grpc-okhttp, grpc-netty, or grpc-netty-shaded "
+          + "artifact");
+    }
+    return provider;
+  }
+
+  /**
+   * Whether this provider is available for use, taking the current environment into consideration.
+   * If {@code false}, no other methods are safe to be called.
+   */
+  protected abstract boolean isAvailable();
+
+  /**
+   * A priority, from 0 to 10 that this provider should be used, taking the current environment into
+   * consideration. 5 should be considered the default, and then tweaked based on environment
+   * detection. A priority of 0 does not imply that the provider wouldn't work; just that it should
+   * be last in line.
+   */
+  protected abstract int priority();
+
+  /**
+   * Creates a new builder with the given host and port.
+   */
+  protected abstract ManagedChannelBuilder<?> builderForAddress(String name, int port);
+
+  /**
+   * Creates a new builder with the given target URI.
+   */
+  protected abstract ManagedChannelBuilder<?> builderForTarget(String target);
+
+  /**
+   * Thrown when no suitable {@link ManagedChannelProvider} objects can be found.
+   */
+  public static final class ProviderNotFoundException extends RuntimeException {
+    private static final long serialVersionUID = 1;
+
+    public ProviderNotFoundException(String msg) {
+      super(msg);
+    }
+  }
+
+  private static final class HardcodedClasses implements Iterable<Class<?>> {
+    @Override
+    public Iterator<Class<?>> iterator() {
+      List<Class<?>> list = new ArrayList<Class<?>>();
+      try {
+        list.add(Class.forName("io.grpc.okhttp.OkHttpChannelProvider"));
+      } catch (ClassNotFoundException ex) {
+        // ignore
+      }
+      try {
+        list.add(Class.forName("io.grpc.netty.NettyChannelProvider"));
+      } catch (ClassNotFoundException ex) {
+        // ignore
+      }
+      return list.iterator();
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/Metadata.java b/core/src/main/java/io/grpc/Metadata.java
new file mode 100644
index 0000000..93f0347
--- /dev/null
+++ b/core/src/main/java/io/grpc/Metadata.java
@@ -0,0 +1,809 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Charsets.US_ASCII;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.io.BaseEncoding;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * Provides access to read and write metadata values to be exchanged during a call.
+ *
+ * <p>Keys are allowed to be associated with more than one value.
+ *
+ * <p>This class is not thread safe, implementations should ensure that header reads and writes do
+ * not occur in multiple threads concurrently.
+ */
+@NotThreadSafe
+public final class Metadata {
+
+  /**
+   * All binary headers should have this suffix in their names. Vice versa.
+   *
+   * <p>Its value is {@code "-bin"}. An ASCII header's name must not end with this.
+   */
+  public static final String BINARY_HEADER_SUFFIX = "-bin";
+
+  /**
+   * Simple metadata marshaller that encodes bytes as is.
+   *
+   * <p>This should be used when raw bytes are favored over un-serialized version of object. Can be
+   * helpful in situations where more processing to bytes is needed on application side, avoids
+   * double encoding/decoding.
+   *
+   * <p>Both {@link BinaryMarshaller#toBytes} and {@link BinaryMarshaller#parseBytes} methods do not
+   * return a copy of the byte array. Do _not_ modify the byte arrays of either the arguments or
+   * return values.
+   */
+  public static final BinaryMarshaller<byte[]> BINARY_BYTE_MARSHALLER =
+      new BinaryMarshaller<byte[]>() {
+
+        @Override
+        public byte[] toBytes(byte[] value) {
+          return value;
+        }
+
+        @Override
+        public byte[] parseBytes(byte[] serialized) {
+          return serialized;
+        }
+      };
+
+  /**
+   * Simple metadata marshaller that encodes strings as is.
+   *
+   * <p>This should be used with ASCII strings that only contain the characters listed in the class
+   * comment of {@link AsciiMarshaller}. Otherwise the output may be considered invalid and
+   * discarded by the transport, or the call may fail.
+   */
+  public static final AsciiMarshaller<String> ASCII_STRING_MARSHALLER =
+      new AsciiMarshaller<String>() {
+
+        @Override
+        public String toAsciiString(String value) {
+          return value;
+        }
+
+        @Override
+        public String parseAsciiString(String serialized) {
+          return serialized;
+        }
+      };
+
+  /**
+   * Constructor called by the transport layer when it receives binary metadata. Metadata will
+   * mutate the passed in array.
+   */
+  Metadata(byte[]... binaryValues) {
+    this(binaryValues.length / 2, binaryValues);
+  }
+
+  /**
+   * Constructor called by the transport layer when it receives binary metadata. Metadata will
+   * mutate the passed in array.
+   *
+   * @param usedNames the number of
+   */
+  Metadata(int usedNames, byte[]... binaryValues) {
+    assert (binaryValues.length & 1) == 0 : "Odd number of key-value pairs " + binaryValues.length;
+    size = usedNames;
+    namesAndValues = binaryValues;
+  }
+
+  private byte[][] namesAndValues;
+  // The unscaled number of headers present.
+  private int size;
+
+  private byte[] name(int i) {
+    return namesAndValues[i * 2];
+  }
+
+  private void name(int i, byte[] name) {
+    namesAndValues[i * 2] = name;
+  }
+
+  private byte[] value(int i) {
+    return namesAndValues[i * 2 + 1];
+  }
+
+  private void value(int i, byte[] value) {
+    namesAndValues[i * 2 + 1] = value;
+  }
+
+  private int cap() {
+    return namesAndValues != null ? namesAndValues.length : 0;
+  }
+
+  // The scaled version of size.
+  private int len() {
+    return size * 2;
+  }
+
+  private boolean isEmpty() {
+    /** checks when {@link #namesAndValues} is null or has no elements */
+    return size == 0;
+  }
+
+  /** Constructor called by the application layer when it wants to send metadata. */
+  public Metadata() {}
+
+  /** Returns the total number of key-value headers in this metadata, including duplicates. */
+  int headerCount() {
+    return size;
+  }
+
+  /**
+   * Returns true if a value is defined for the given key.
+   *
+   * <p>This is done by linear search, so if it is followed by {@link #get} or {@link #getAll},
+   * prefer calling them directly and checking the return value against {@code null}.
+   */
+  public boolean containsKey(Key<?> key) {
+    for (int i = 0; i < size; i++) {
+      if (bytesEqual(key.asciiName(), name(i))) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns the last metadata entry added with the name 'name' parsed as T.
+   *
+   * @return the parsed metadata entry or null if there are none.
+   */
+  @Nullable
+  public <T> T get(Key<T> key) {
+    for (int i = size - 1; i >= 0; i--) {
+      if (bytesEqual(key.asciiName(), name(i))) {
+        return key.parseBytes(value(i));
+      }
+    }
+    return null;
+  }
+
+  private final class IterableAt<T> implements Iterable<T> {
+    private final Key<T> key;
+    private int startIdx;
+
+    private IterableAt(Key<T> key, int startIdx) {
+      this.key = key;
+      this.startIdx = startIdx;
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+      return new Iterator<T>() {
+        private boolean hasNext = true;
+        private int idx = startIdx;
+
+        @Override
+        public boolean hasNext() {
+          if (hasNext) {
+            return true;
+          }
+          for (; idx < size; idx++) {
+            if (bytesEqual(key.asciiName(), name(idx))) {
+              hasNext = true;
+              return hasNext;
+            }
+          }
+          return false;
+        }
+
+        @Override
+        public T next() {
+          if (hasNext()) {
+            hasNext = false;
+            return key.parseBytes(value(idx++));
+          }
+          throw new NoSuchElementException();
+        }
+
+        @Override
+        public void remove() {
+          throw new UnsupportedOperationException();
+        }
+      };
+    }
+  }
+
+  /**
+   * Returns all the metadata entries named 'name', in the order they were received, parsed as T, or
+   * null if there are none. The iterator is not guaranteed to be "live." It may or may not be
+   * accurate if Metadata is mutated.
+   */
+  @Nullable
+  public <T> Iterable<T> getAll(final Key<T> key) {
+    for (int i = 0; i < size; i++) {
+      if (bytesEqual(key.asciiName(), name(i))) {
+        return new IterableAt<T>(key, i);
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns set of all keys in store.
+   *
+   * @return unmodifiable Set of keys
+   */
+  @SuppressWarnings("deprecation") // The String ctor is deprecated, but fast.
+  public Set<String> keys() {
+    if (isEmpty()) {
+      return Collections.emptySet();
+    }
+    Set<String> ks = new HashSet<String>(size);
+    for (int i = 0; i < size; i++) {
+      ks.add(new String(name(i), 0 /* hibyte */));
+    }
+    // immutable in case we decide to change the implementation later.
+    return Collections.unmodifiableSet(ks);
+  }
+
+  /**
+   * Adds the {@code key, value} pair. If {@code key} already has values, {@code value} is added to
+   * the end. Duplicate values for the same key are permitted.
+   *
+   * @throws NullPointerException if key or value is null
+   */
+  public <T> void put(Key<T> key, T value) {
+    Preconditions.checkNotNull(key, "key");
+    Preconditions.checkNotNull(value, "value");
+    maybeExpand();
+    name(size, key.asciiName());
+    value(size, key.toBytes(value));
+    size++;
+  }
+
+  private void maybeExpand() {
+    if (len() == 0 || len() == cap()) {
+      expand(Math.max(len() * 2, 8));
+    }
+  }
+
+  // Expands to exactly the desired capacity.
+  private void expand(int newCapacity) {
+    byte[][] newNamesAndValues = new byte[newCapacity][];
+    if (!isEmpty()) {
+      System.arraycopy(namesAndValues, 0, newNamesAndValues, 0, len());
+    }
+    namesAndValues = newNamesAndValues;
+  }
+
+  /**
+   * Removes the first occurrence of {@code value} for {@code key}.
+   *
+   * @param key key for value
+   * @param value value
+   * @return {@code true} if {@code value} removed; {@code false} if {@code value} was not present
+   * @throws NullPointerException if {@code key} or {@code value} is null
+   */
+  public <T> boolean remove(Key<T> key, T value) {
+    Preconditions.checkNotNull(key, "key");
+    Preconditions.checkNotNull(value, "value");
+    for (int i = 0; i < size; i++) {
+      if (!bytesEqual(key.asciiName(), name(i))) {
+        continue;
+      }
+      @SuppressWarnings("unchecked")
+      T stored = key.parseBytes(value(i));
+      if (!value.equals(stored)) {
+        continue;
+      }
+      int writeIdx = i * 2;
+      int readIdx = (i + 1) * 2;
+      int readLen = len() - readIdx;
+      System.arraycopy(namesAndValues, readIdx, namesAndValues, writeIdx, readLen);
+      size -= 1;
+      name(size, null);
+      value(size, null);
+      return true;
+    }
+    return false;
+  }
+
+  /** Remove all values for the given key. If there were no values, {@code null} is returned. */
+  public <T> Iterable<T> removeAll(Key<T> key) {
+    if (isEmpty()) {
+      return null;
+    }
+    int writeIdx = 0;
+    int readIdx = 0;
+    List<T> ret = null;
+    for (; readIdx < size; readIdx++) {
+      if (bytesEqual(key.asciiName(), name(readIdx))) {
+        ret = ret != null ? ret : new ArrayList<T>();
+        ret.add(key.parseBytes(value(readIdx)));
+        continue;
+      }
+      name(writeIdx, name(readIdx));
+      value(writeIdx, value(readIdx));
+      writeIdx++;
+    }
+    int newSize = writeIdx;
+    // Multiply by two since namesAndValues is interleaved.
+    Arrays.fill(namesAndValues, writeIdx * 2, len(), null);
+    size = newSize;
+    return ret;
+  }
+
+  /**
+   * Remove all values for the given key without returning them. This is a minor performance
+   * optimization if you do not need the previous values.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4691")
+  public <T> void discardAll(Key<T> key) {
+    if (isEmpty()) {
+      return;
+    }
+    int writeIdx = 0;
+    int readIdx = 0;
+    for (; readIdx < size; readIdx++) {
+      if (bytesEqual(key.asciiName(), name(readIdx))) {
+        continue;
+      }
+      name(writeIdx, name(readIdx));
+      value(writeIdx, value(readIdx));
+      writeIdx++;
+    }
+    int newSize = writeIdx;
+    // Multiply by two since namesAndValues is interleaved.
+    Arrays.fill(namesAndValues, writeIdx * 2, len(), null);
+    size = newSize;
+  }
+
+  /**
+   * Serialize all the metadata entries.
+   *
+   * <p>It produces serialized names and values interleaved. result[i*2] are names, while
+   * result[i*2+1] are values.
+   *
+   * <p>Names are ASCII string bytes that contains only the characters listed in the class comment
+   * of {@link Key}. If the name ends with {@code "-bin"}, the value can be raw binary. Otherwise,
+   * the value must contain only characters listed in the class comments of {@link AsciiMarshaller}
+   *
+   * <p>The returned individual byte arrays <em>must not</em> be modified. However, the top level
+   * array may be modified.
+   *
+   * <p>This method is intended for transport use only.
+   */
+  @Nullable
+  byte[][] serialize() {
+    if (len() == cap()) {
+      return namesAndValues;
+    }
+    byte[][] serialized = new byte[len()][];
+    System.arraycopy(namesAndValues, 0, serialized, 0, len());
+    return serialized;
+  }
+
+  /**
+   * Perform a simple merge of two sets of metadata.
+   *
+   * <p>This is a purely additive operation, because a single key can be associated with multiple
+   * values.
+   */
+  public void merge(Metadata other) {
+    if (other.isEmpty()) {
+      return;
+    }
+    int remaining = cap() - len();
+    if (isEmpty() || remaining < other.len()) {
+      expand(len() + other.len());
+    }
+    System.arraycopy(other.namesAndValues, 0, namesAndValues, len(), other.len());
+    size += other.size;
+  }
+
+  /**
+   * Merge values from the given set of keys into this set of metadata. If a key is present in keys,
+   * then all of the associated values will be copied over.
+   *
+   * @param other The source of the new key values.
+   * @param keys The subset of matching key we want to copy, if they exist in the source.
+   */
+  public void merge(Metadata other, Set<Key<?>> keys) {
+    Preconditions.checkNotNull(other, "other");
+    // Use ByteBuffer for equals and hashCode.
+    Map<ByteBuffer, Key<?>> asciiKeys = new HashMap<ByteBuffer, Key<?>>(keys.size());
+    for (Key<?> key : keys) {
+      asciiKeys.put(ByteBuffer.wrap(key.asciiName()), key);
+    }
+    for (int i = 0; i < other.size; i++) {
+      ByteBuffer wrappedNamed = ByteBuffer.wrap(other.name(i));
+      if (asciiKeys.containsKey(wrappedNamed)) {
+        maybeExpand();
+        name(size, other.name(i));
+        value(size, other.value(i));
+        size++;
+      }
+    }
+  }
+
+  @SuppressWarnings("BetaApi") // BaseEncoding is stable in Guava 20.0
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("Metadata(");
+    for (int i = 0; i < size; i++) {
+      if (i != 0) {
+        sb.append(',');
+      }
+      String headerName = new String(name(i), US_ASCII);
+      sb.append(headerName).append('=');
+      if (headerName.endsWith(BINARY_HEADER_SUFFIX)) {
+        sb.append(BaseEncoding.base64().encode(value(i)));
+      } else {
+        String headerValue = new String(value(i), US_ASCII);
+        sb.append(headerValue);
+      }
+    }
+    return sb.append(')').toString();
+  }
+
+  private boolean bytesEqual(byte[] left, byte[] right) {
+    return Arrays.equals(left, right);
+  }
+
+  /** Marshaller for metadata values that are serialized into raw binary. */
+  public interface BinaryMarshaller<T> {
+    /**
+     * Serialize a metadata value to bytes.
+     *
+     * @param value to serialize
+     * @return serialized version of value
+     */
+    byte[] toBytes(T value);
+
+    /**
+     * Parse a serialized metadata value from bytes.
+     *
+     * @param serialized value of metadata to parse
+     * @return a parsed instance of type T
+     */
+    T parseBytes(byte[] serialized);
+  }
+
+  /**
+   * Marshaller for metadata values that are serialized into ASCII strings. The strings contain only
+   * following characters:
+   *
+   * <ul>
+   * <li>Space: {@code 0x20}, but must not be at the beginning or at the end of the value. Leading
+   *     or trailing whitespace may not be preserved.
+   * <li>ASCII visible characters ({@code 0x21-0x7E}).
+   * </ul>
+   *
+   * <p>Note this has to be the subset of valid characters in {@code field-content} from RFC 7230
+   * Section 3.2.
+   */
+  public interface AsciiMarshaller<T> {
+    /**
+     * Serialize a metadata value to a ASCII string that contains only the characters listed in the
+     * class comment of {@link AsciiMarshaller}. Otherwise the output may be considered invalid and
+     * discarded by the transport, or the call may fail.
+     *
+     * @param value to serialize
+     * @return serialized version of value, or null if value cannot be transmitted.
+     */
+    String toAsciiString(T value);
+
+    /**
+     * Parse a serialized metadata value from an ASCII string.
+     *
+     * @param serialized value of metadata to parse
+     * @return a parsed instance of type T
+     */
+    T parseAsciiString(String serialized);
+  }
+
+  /**
+   * Key for metadata entries. Allows for parsing and serialization of metadata.
+   *
+   * <h3>Valid characters in key names</h3>
+   *
+   * <p>Only the following ASCII characters are allowed in the names of keys:
+   *
+   * <ul>
+   * <li>digits: {@code 0-9}
+   * <li>uppercase letters: {@code A-Z} (normalized to lower)
+   * <li>lowercase letters: {@code a-z}
+   * <li>special characters: {@code -_.}
+   * </ul>
+   *
+   * <p>This is a strict subset of the HTTP field-name rules. Applications may not send or receive
+   * metadata with invalid key names. However, the gRPC library may preserve any metadata received
+   * even if it does not conform to the above limitations. Additionally, if metadata contains non
+   * conforming field names, they will still be sent. In this way, unknown metadata fields are
+   * parsed, serialized and preserved, but never interpreted. They are similar to protobuf unknown
+   * fields.
+   *
+   * <p>Note this has to be the subset of valid HTTP/2 token characters as defined in RFC7230
+   * Section 3.2.6 and RFC5234 Section B.1
+   *
+   * <p>Note that a key is immutable but it may not be deeply immutable, because the key depends on
+   * its marshaller, and the marshaller can be mutable though not recommended.
+   *
+   * @see <a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md">Wire Spec</a>
+   * @see <a href="https://tools.ietf.org/html/rfc7230#section-3.2.6">RFC7230</a>
+   * @see <a href="https://tools.ietf.org/html/rfc5234#appendix-B.1">RFC5234</a>
+   */
+  @Immutable
+  public abstract static class Key<T> {
+
+    /** Valid characters for field names as defined in RFC7230 and RFC5234. */
+    private static final BitSet VALID_T_CHARS = generateValidTChars();
+
+    /**
+     * Creates a key for a binary header.
+     *
+     * @param name Must contain only the valid key characters as defined in the class comment. Must
+     *     end with {@link #BINARY_HEADER_SUFFIX}.
+     */
+    public static <T> Key<T> of(String name, BinaryMarshaller<T> marshaller) {
+      return new BinaryKey<T>(name, marshaller);
+    }
+
+    /**
+     * Creates a key for an ASCII header.
+     *
+     * @param name Must contain only the valid key characters as defined in the class comment. Must
+     *     <b>not</b> end with {@link #BINARY_HEADER_SUFFIX}
+     */
+    public static <T> Key<T> of(String name, AsciiMarshaller<T> marshaller) {
+      return of(name, false, marshaller);
+    }
+
+    static <T> Key<T> of(String name, boolean pseudo, AsciiMarshaller<T> marshaller) {
+      return new AsciiKey<T>(name, pseudo, marshaller);
+    }
+
+    static <T> Key<T> of(String name, boolean pseudo, TrustedAsciiMarshaller<T> marshaller) {
+      return new TrustedAsciiKey<T>(name, pseudo, marshaller);
+    }
+
+    private final String originalName;
+
+    private final String name;
+    private final byte[] nameBytes;
+
+    private static BitSet generateValidTChars() {
+      BitSet valid = new BitSet(0x7f);
+      valid.set('-');
+      valid.set('_');
+      valid.set('.');
+      for (char c = '0'; c <= '9'; c++) {
+        valid.set(c);
+      }
+      // Only validates after normalization, so we exclude uppercase.
+      for (char c = 'a'; c <= 'z'; c++) {
+        valid.set(c);
+      }
+      return valid;
+    }
+
+    private static String validateName(String n, boolean pseudo) {
+      checkNotNull(n, "name");
+      checkArgument(!n.isEmpty(), "token must have at least 1 tchar");
+      for (int i = 0; i < n.length(); i++) {
+        char tChar = n.charAt(i);
+        if (pseudo && tChar == ':' && i == 0) {
+          continue;
+        }
+
+        checkArgument(
+            VALID_T_CHARS.get(tChar), "Invalid character '%s' in key name '%s'", tChar, n);
+      }
+      return n;
+    }
+
+    private Key(String name, boolean pseudo) {
+      this.originalName = checkNotNull(name, "name");
+      this.name = validateName(this.originalName.toLowerCase(Locale.ROOT), pseudo);
+      this.nameBytes = this.name.getBytes(US_ASCII);
+    }
+
+    /**
+     * @return The original name used to create this key.
+     */
+    public final String originalName() {
+      return originalName;
+    }
+
+    /**
+     * @return The normalized name for this key.
+     */
+    public final String name() {
+      return name;
+    }
+
+    /**
+     * Get the name as bytes using ASCII-encoding.
+     *
+     * <p>The returned byte arrays <em>must not</em> be modified.
+     *
+     * <p>This method is intended for transport use only.
+     */
+    // TODO (louiscryan): Migrate to ByteString
+    @VisibleForTesting
+    byte[] asciiName() {
+      return nameBytes;
+    }
+
+    /**
+     * Returns true if the two objects are both Keys, and their names match (case insensitive).
+     */
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      Key<?> key = (Key<?>) o;
+      return name.equals(key.name);
+    }
+
+    @Override
+    public int hashCode() {
+      return name.hashCode();
+    }
+
+    @Override
+    public String toString() {
+      return "Key{name='" + name + "'}";
+    }
+
+    /**
+     * Serialize a metadata value to bytes.
+     *
+     * @param value to serialize
+     * @return serialized version of value
+     */
+    abstract byte[] toBytes(T value);
+
+    /**
+     * Parse a serialized metadata value from bytes.
+     *
+     * @param serialized value of metadata to parse
+     * @return a parsed instance of type T
+     */
+    abstract T parseBytes(byte[] serialized);
+  }
+
+  private static class BinaryKey<T> extends Key<T> {
+    private final BinaryMarshaller<T> marshaller;
+
+    /** Keys have a name and a binary marshaller used for serialization. */
+    private BinaryKey(String name, BinaryMarshaller<T> marshaller) {
+      super(name, false /* not pseudo */);
+      checkArgument(
+          name.endsWith(BINARY_HEADER_SUFFIX),
+          "Binary header is named %s. It must end with %s",
+          name,
+          BINARY_HEADER_SUFFIX);
+      checkArgument(name.length() > BINARY_HEADER_SUFFIX.length(), "empty key name");
+      this.marshaller = checkNotNull(marshaller, "marshaller is null");
+    }
+
+    @Override
+    byte[] toBytes(T value) {
+      return marshaller.toBytes(value);
+    }
+
+    @Override
+    T parseBytes(byte[] serialized) {
+      return marshaller.parseBytes(serialized);
+    }
+  }
+
+  private static class AsciiKey<T> extends Key<T> {
+    private final AsciiMarshaller<T> marshaller;
+
+    /** Keys have a name and an ASCII marshaller used for serialization. */
+    private AsciiKey(String name, boolean pseudo, AsciiMarshaller<T> marshaller) {
+      super(name, pseudo);
+      Preconditions.checkArgument(
+          !name.endsWith(BINARY_HEADER_SUFFIX),
+          "ASCII header is named %s.  Only binary headers may end with %s",
+          name,
+          BINARY_HEADER_SUFFIX);
+      this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
+    }
+
+    @Override
+    byte[] toBytes(T value) {
+      return marshaller.toAsciiString(value).getBytes(US_ASCII);
+    }
+
+    @Override
+    T parseBytes(byte[] serialized) {
+      return marshaller.parseAsciiString(new String(serialized, US_ASCII));
+    }
+  }
+
+  private static final class TrustedAsciiKey<T> extends Key<T> {
+    private final TrustedAsciiMarshaller<T> marshaller;
+
+    /** Keys have a name and an ASCII marshaller used for serialization. */
+    private TrustedAsciiKey(String name, boolean pseudo, TrustedAsciiMarshaller<T> marshaller) {
+      super(name, pseudo);
+      Preconditions.checkArgument(
+          !name.endsWith(BINARY_HEADER_SUFFIX),
+          "ASCII header is named %s.  Only binary headers may end with %s",
+          name,
+          BINARY_HEADER_SUFFIX);
+      this.marshaller = Preconditions.checkNotNull(marshaller, "marshaller");
+    }
+
+    @Override
+    byte[] toBytes(T value) {
+      return marshaller.toAsciiString(value);
+    }
+
+    @Override
+    T parseBytes(byte[] serialized) {
+      return marshaller.parseAsciiString(serialized);
+    }
+  }
+
+  /**
+   * A specialized plain ASCII marshaller. Both input and output are assumed to be valid header
+   * ASCII.
+   */
+  @Immutable
+  interface TrustedAsciiMarshaller<T> {
+    /**
+     * Serialize a metadata value to a ASCII string that contains only the characters listed in the
+     * class comment of {@link io.grpc.Metadata.AsciiMarshaller}. Otherwise the output may be
+     * considered invalid and discarded by the transport, or the call may fail.
+     *
+     * @param value to serialize
+     * @return serialized version of value, or null if value cannot be transmitted.
+     */
+    byte[] toAsciiString(T value);
+
+    /**
+     * Parse a serialized metadata value from an ASCII string.
+     *
+     * @param serialized value of metadata to parse
+     * @return a parsed instance of type T
+     */
+    T parseAsciiString(byte[] serialized);
+  }
+}
diff --git a/core/src/main/java/io/grpc/MethodDescriptor.java b/core/src/main/java/io/grpc/MethodDescriptor.java
new file mode 100644
index 0000000..7d006b7
--- /dev/null
+++ b/core/src/main/java/io/grpc/MethodDescriptor.java
@@ -0,0 +1,593 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicReferenceArray;
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Description of a remote method used by {@link Channel} to initiate a call.
+ *
+ * <p>Provides the name of the operation to execute as well as {@link Marshaller} instances
+ * used to parse and serialize request and response messages.
+ *
+ * <p>Can be constructed manually but will often be generated by stub code generators.
+ *
+ * @since 1.0.0
+ */
+@Immutable
+public final class MethodDescriptor<ReqT, RespT> {
+
+  private final MethodType type;
+  private final String fullMethodName;
+  private final Marshaller<ReqT> requestMarshaller;
+  private final Marshaller<RespT> responseMarshaller;
+  private final @Nullable Object schemaDescriptor;
+  private final boolean idempotent;
+  private final boolean safe;
+  private final boolean sampledToLocalTracing;
+
+  // Must be set to InternalKnownTransport.values().length
+  // Not referenced to break the dependency.
+  private final AtomicReferenceArray<Object> rawMethodNames = new AtomicReferenceArray<Object>(1);
+
+
+  /**
+   * Gets the cached "raw" method name for this Method Descriptor.  The raw name is transport
+   * specific, and should be set using {@link #setRawMethodName} by the transport.
+   *
+   * @param transportOrdinal the unique ID of the transport, given by
+   *        {@link InternalKnownTransport#ordinal}.
+   * @return a transport specific representation of the method name.
+   */
+  final Object getRawMethodName(int transportOrdinal) {
+    return rawMethodNames.get(transportOrdinal);
+  }
+
+  /**
+   * Safely, but weakly, sets the raw method name for this Method Descriptor.  This should only be
+   * called by the transport.  See {@link #getRawMethodName} for more detail.
+   */
+  final void setRawMethodName(int transportOrdinal, Object o) {
+    rawMethodNames.lazySet(transportOrdinal, o);
+  }
+
+  /**
+   * The call type of a method.
+   *
+   * @since 1.0.0
+   */
+  public enum MethodType {
+    /**
+     * One request message followed by one response message.
+     */
+    UNARY,
+
+    /**
+     * Zero or more request messages followed by one response message.
+     */
+    CLIENT_STREAMING,
+
+    /**
+     * One request message followed by zero or more response messages.
+     */
+    SERVER_STREAMING,
+
+    /**
+     * Zero or more request and response messages arbitrarily interleaved in time.
+     */
+    BIDI_STREAMING,
+
+    /**
+     * Cardinality and temporal relationships are not known. Implementations should not make
+     * buffering assumptions and should largely treat the same as {@link #BIDI_STREAMING}.
+     */
+    UNKNOWN;
+
+    /**
+     * Returns {@code true} if the client will immediately send one request message to the server
+     * after calling {@link ClientCall#start(io.grpc.ClientCall.Listener, io.grpc.Metadata)}
+     * and then immediately half-close the stream by calling {@link io.grpc.ClientCall#halfClose()}.
+     *
+     * @since 1.0.0
+     */
+    public final boolean clientSendsOneMessage() {
+      return this == UNARY || this == SERVER_STREAMING;
+    }
+
+    /**
+     * Returns {@code true} if the server will immediately send one response message to the client
+     * upon receipt of {@link io.grpc.ServerCall.Listener#onHalfClose()} and then immediately
+     * close the stream by calling {@link ServerCall#close(Status, io.grpc.Metadata)}.
+     *
+     * @since 1.0.0
+     */
+    public final boolean serverSendsOneMessage() {
+      return this == UNARY || this == CLIENT_STREAMING;
+    }
+  }
+
+  /**
+   * A typed abstraction over message serialization and deserialization, a.k.a. marshalling and
+   * unmarshalling.
+   *
+   * <p>Stub implementations will define implementations of this interface for each of the request
+   * and response messages provided by a service.
+   *
+   * @param <T> type of serializable message
+   * @since 1.0.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1777")
+  public interface Marshaller<T> {
+    /**
+     * Given a message, produce an {@link InputStream} for it so that it can be written to the wire.
+     * Where possible implementations should produce streams that are {@link io.grpc.KnownLength}
+     * to improve transport efficiency.
+     *
+     * @param value to serialize.
+     * @return serialized value as stream of bytes.
+     */
+    public InputStream stream(T value);
+
+    /**
+     * Given an {@link InputStream} parse it into an instance of the declared type so that it can be
+     * passed to application code.
+     *
+     * @param stream of bytes for serialized value
+     * @return parsed value
+     */
+    public T parse(InputStream stream);
+  }
+
+  /**
+   * A marshaller that supports retrieving it's type parameter {@code T} at runtime.
+   *
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2222")
+  public interface ReflectableMarshaller<T> extends Marshaller<T> {
+    /**
+     * Returns the {@code Class} that this marshaller serializes and deserializes. If inheritance is
+     * allowed, this is the base class or interface for all supported classes.
+     *
+     * @return non-{@code null} base class for all objects produced and consumed by this marshaller
+     */
+    public Class<T> getMessageClass();
+  }
+
+  /**
+   * A marshaller that uses a fixed instance of the type it produces.
+   *
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2222")
+  public interface PrototypeMarshaller<T> extends ReflectableMarshaller<T> {
+    /**
+     * An instance of the expected message type, typically used as a schema and helper for producing
+     * other message instances. The {@code null} value may be a special value for the marshaller
+     * (like the equivalent of {@link Void}), so it is a valid return value. {@code null} does
+     * <em>not</em> mean "unsupported" or "unknown".
+     *
+     * <p>It is generally expected this would return the same instance each invocation, but it is
+     * not a requirement.
+     */
+    @Nullable
+    public T getMessagePrototype();
+  }
+
+  /**
+   * Creates a new {@code MethodDescriptor}.
+   *
+   * @param type the call type of this method
+   * @param fullMethodName the fully qualified name of this method
+   * @param requestMarshaller the marshaller used to encode and decode requests
+   * @param responseMarshaller the marshaller used to encode and decode responses
+   * @since 1.0.0
+   * @deprecated use {@link #newBuilder()}.
+   */
+  @Deprecated
+  public static <RequestT, ResponseT> MethodDescriptor<RequestT, ResponseT> create(
+      MethodType type, String fullMethodName,
+      Marshaller<RequestT> requestMarshaller,
+      Marshaller<ResponseT> responseMarshaller) {
+    return new MethodDescriptor<RequestT, ResponseT>(
+        type, fullMethodName, requestMarshaller, responseMarshaller, null, false, false, false);
+  }
+
+  private MethodDescriptor(
+      MethodType type,
+      String fullMethodName,
+      Marshaller<ReqT> requestMarshaller,
+      Marshaller<RespT> responseMarshaller,
+      Object schemaDescriptor,
+      boolean idempotent,
+      boolean safe,
+      boolean sampledToLocalTracing) {
+
+    this.type = Preconditions.checkNotNull(type, "type");
+    this.fullMethodName = Preconditions.checkNotNull(fullMethodName, "fullMethodName");
+    this.requestMarshaller = Preconditions.checkNotNull(requestMarshaller, "requestMarshaller");
+    this.responseMarshaller = Preconditions.checkNotNull(responseMarshaller, "responseMarshaller");
+    this.schemaDescriptor = schemaDescriptor;
+    this.idempotent = idempotent;
+    this.safe = safe;
+    this.sampledToLocalTracing = sampledToLocalTracing;
+    Preconditions.checkArgument(!safe || type == MethodType.UNARY,
+        "Only unary methods can be specified safe");
+  }
+
+  /**
+   * The call type of the method.
+   *
+   * @since 1.0.0
+   */
+  public MethodType getType() {
+    return type;
+  }
+
+  /**
+   * The fully qualified name of the method.
+   *
+   * @since 1.0.0
+   */
+  public String getFullMethodName() {
+    return fullMethodName;
+  }
+
+  /**
+   * Parse a response payload from the given {@link InputStream}.
+   *
+   * @param input stream containing response message to parse.
+   * @return parsed response message object.
+   * @since 1.0.0
+   */
+  public RespT parseResponse(InputStream input) {
+    return responseMarshaller.parse(input);
+  }
+
+  /**
+   * Convert a request message to an {@link InputStream}.
+   * The returned InputStream should be closed by the caller.
+   *
+   * @param requestMessage to serialize using the request {@link Marshaller}.
+   * @return serialized request message.
+   * @since 1.0.0
+   */
+  public InputStream streamRequest(ReqT requestMessage) {
+    return requestMarshaller.stream(requestMessage);
+  }
+
+  /**
+   * Parse an incoming request message.
+   *
+   * @param input the serialized message as a byte stream.
+   * @return a parsed instance of the message.
+   * @since 1.0.0
+   */
+  public ReqT parseRequest(InputStream input) {
+    return requestMarshaller.parse(input);
+  }
+
+  /**
+   * Serialize an outgoing response message.
+   * The returned InputStream should be closed by the caller.
+   *
+   * @param response the response message to serialize.
+   * @return the serialized message as a byte stream.
+   * @since 1.0.0
+   */
+  public InputStream streamResponse(RespT response) {
+    return responseMarshaller.stream(response);
+  }
+
+  /**
+   * Returns the marshaller for the request type. Allows introspection of the request marshaller.
+   *
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2592")
+  public Marshaller<ReqT> getRequestMarshaller() {
+    return requestMarshaller;
+  }
+
+  /**
+   * Returns the marshaller for the response type. Allows introspection of the response marshaller.
+   *
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2592")
+  public Marshaller<RespT> getResponseMarshaller() {
+    return responseMarshaller;
+  }
+
+  /**
+   * Returns the schema descriptor for this method. A schema descriptor is an object that is not
+   * used by gRPC core but includes information related to the service method. The type of the
+   * object is specific to the consumer, so both the code setting the schema descriptor and the code
+   * calling {@link #getSchemaDescriptor()} must coordinate.  For example, protobuf generated code
+   * sets this value, in order to be consumed by the server reflection service.  See also:
+   * {@code io.grpc.protobuf.ProtoMethodDescriptorSupplier}.
+   *
+   * @since 1.7.0
+   */
+  public @Nullable Object getSchemaDescriptor() {
+    return schemaDescriptor;
+  }
+
+  /**
+   * Returns whether this method is idempotent.
+   *
+   * @since 1.0.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1775")
+  public boolean isIdempotent() {
+    return idempotent;
+  }
+
+  /**
+   * Returns whether this method is safe.
+   *
+   * <p>A safe request does nothing except retrieval so it has no side effects on the server side.
+   *
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1775")
+  public boolean isSafe() {
+    return safe;
+  }
+
+  /**
+   * Returns whether RPCs for this method may be sampled into the local tracing store.
+   */
+  public boolean isSampledToLocalTracing() {
+    return sampledToLocalTracing;
+  }
+
+  /**
+   * Generate the fully qualified method name.  This matches the the name
+   *
+   * @param fullServiceName the fully qualified service name that is prefixed with the package name
+   * @param methodName the short method name
+   * @since 1.0.0
+   */
+  public static String generateFullMethodName(String fullServiceName, String methodName) {
+    return checkNotNull(fullServiceName, "fullServiceName")
+        + "/"
+        + checkNotNull(methodName, "methodName");
+  }
+
+  /**
+   * Extract the fully qualified service name out of a fully qualified method name. May return
+   * {@code null} if the input is malformed, but you cannot rely on it for the validity of the
+   * input.
+   *
+   * @since 1.0.0
+   */
+  @Nullable
+  public static String extractFullServiceName(String fullMethodName) {
+    int index = checkNotNull(fullMethodName, "fullMethodName").lastIndexOf('/');
+    if (index == -1) {
+      return null;
+    }
+    return fullMethodName.substring(0, index);
+  }
+
+  /**
+   * Creates a new builder for a {@link MethodDescriptor}.
+   *
+   * @since 1.1.0
+   */
+  @CheckReturnValue
+  public static <ReqT, RespT> Builder<ReqT, RespT> newBuilder() {
+    return newBuilder(null, null);
+  }
+
+  /**
+   * Creates a new builder for a {@link MethodDescriptor}.
+   *
+   * @since 1.1.0
+   */
+  @CheckReturnValue
+  public static <ReqT, RespT> Builder<ReqT, RespT> newBuilder(
+      Marshaller<ReqT> requestMarshaller, Marshaller<RespT> responseMarshaller) {
+    return new Builder<ReqT, RespT>()
+        .setRequestMarshaller(requestMarshaller)
+        .setResponseMarshaller(responseMarshaller);
+  }
+
+  /**
+   * Turns this descriptor into a builder.
+   *
+   * @since 1.1.0
+   */
+  @CheckReturnValue
+  public Builder<ReqT, RespT> toBuilder() {
+    return toBuilder(requestMarshaller, responseMarshaller);
+  }
+
+  /**
+   * Turns this descriptor into a builder, replacing the request and response marshallers.
+   *
+   * @since 1.1.0
+   */
+  @CheckReturnValue
+  public <NewReqT, NewRespT> Builder<NewReqT, NewRespT> toBuilder(
+      Marshaller<NewReqT> requestMarshaller, Marshaller<NewRespT> responseMarshaller) {
+    return MethodDescriptor.<NewReqT, NewRespT>newBuilder()
+        .setRequestMarshaller(requestMarshaller)
+        .setResponseMarshaller(responseMarshaller)
+        .setType(type)
+        .setFullMethodName(fullMethodName)
+        .setIdempotent(idempotent)
+        .setSafe(safe)
+        .setSampledToLocalTracing(sampledToLocalTracing)
+        .setSchemaDescriptor(schemaDescriptor);
+  }
+
+  /**
+   * A builder for a {@link MethodDescriptor}.
+   *
+   * @since 1.1.0
+   */
+  public static final class Builder<ReqT, RespT> {
+
+    private Marshaller<ReqT> requestMarshaller;
+    private Marshaller<RespT> responseMarshaller;
+    private MethodType type;
+    private String fullMethodName;
+    private boolean idempotent;
+    private boolean safe;
+    private Object schemaDescriptor;
+    private boolean sampledToLocalTracing;
+
+    private Builder() {}
+
+    /**
+     * Sets the request marshaller.
+     *
+     * @param requestMarshaller the marshaller to use.
+     * @since 1.1.0
+     */
+    public Builder<ReqT, RespT> setRequestMarshaller(Marshaller<ReqT> requestMarshaller) {
+      this.requestMarshaller = requestMarshaller;
+      return this;
+    }
+
+    /**
+     * Sets the response marshaller.
+     *
+     * @param responseMarshaller the marshaller to use.
+     * @since 1.1.0
+     */
+    @SuppressWarnings("unchecked")
+    public Builder<ReqT, RespT> setResponseMarshaller(Marshaller<RespT> responseMarshaller) {
+      this.responseMarshaller = responseMarshaller;
+      return this;
+    }
+
+    /**
+     * Sets the method type.
+     *
+     * @param type the type of the method.
+     * @since 1.1.0
+     */
+    public Builder<ReqT, RespT> setType(MethodType type) {
+      this.type = type;
+      return this;
+    }
+
+    /**
+     * Sets the fully qualified (service and method) method name.
+     *
+     * @see MethodDescriptor#generateFullMethodName
+     * @since 1.1.0
+     */
+    public Builder<ReqT, RespT> setFullMethodName(String fullMethodName) {
+      this.fullMethodName = fullMethodName;
+      return this;
+    }
+
+    /**
+     * Sets the schema descriptor for this builder.  A schema descriptor is an object that is not
+     * used by gRPC core but includes information related to the methods. The type of the object
+     * is specific to the consumer, so both the code calling this and the code calling
+     * {@link MethodDescriptor#getSchemaDescriptor()} must coordinate.
+     *
+     * @param schemaDescriptor an object that describes the service structure.  Should be immutable.
+     * @since 1.7.0
+     */
+    public Builder<ReqT, RespT> setSchemaDescriptor(@Nullable Object schemaDescriptor) {
+      this.schemaDescriptor = schemaDescriptor;
+      return this;
+    }
+
+    /**
+     * Sets whether the method is idempotent.  If true, calling this method more than once doesn't
+     * have additional side effects.
+     *
+     * @since 1.1.0
+     */
+    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1775")
+    public Builder<ReqT, RespT> setIdempotent(boolean idempotent) {
+      this.idempotent = idempotent;
+      return this;
+    }
+
+    /**
+     * Sets whether this method is safe.  If true, calling this method any number of times doesn't
+     * have side effects.
+     *
+     * @since 1.1.0
+     */
+    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1775")
+    public Builder<ReqT, RespT> setSafe(boolean safe) {
+      this.safe = safe;
+      return this;
+    }
+
+    /**
+     * Sets whether RPCs for this method may be sampled into the local tracing store.  If true,
+     * sampled traces of this method may be kept in memory by tracing libraries.
+     *
+     * @since 1.8.0
+     */
+    public Builder<ReqT, RespT> setSampledToLocalTracing(boolean value) {
+      this.sampledToLocalTracing = value;
+      return this;
+    }
+
+    /**
+     * Builds the method descriptor.
+     *
+     * @since 1.1.0
+     */
+    @CheckReturnValue
+    public MethodDescriptor<ReqT, RespT> build() {
+      return new MethodDescriptor<ReqT, RespT>(
+          type,
+          fullMethodName,
+          requestMarshaller,
+          responseMarshaller,
+          schemaDescriptor,
+          idempotent,
+          safe,
+          sampledToLocalTracing);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+      .add("fullMethodName", fullMethodName)
+      .add("type", type)
+      .add("idempotent", idempotent)
+      .add("safe", safe)
+      .add("sampledToLocalTracing", sampledToLocalTracing)
+      .add("requestMarshaller", requestMarshaller)
+      .add("responseMarshaller", responseMarshaller)
+      .add("schemaDescriptor", schemaDescriptor)
+      .omitNullValues()
+      .toString();
+  }
+}
diff --git a/core/src/main/java/io/grpc/NameResolver.java b/core/src/main/java/io/grpc/NameResolver.java
new file mode 100644
index 0000000..60f819e
--- /dev/null
+++ b/core/src/main/java/io/grpc/NameResolver.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.net.URI;
+import java.util.List;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * A pluggable component that resolves a target {@link URI} and return addresses to the caller.
+ *
+ * <p>A {@code NameResolver} uses the URI's scheme to determine whether it can resolve it, and uses
+ * the components after the scheme for actual resolution.
+ *
+ * <p>The addresses and attributes of a target may be changed over time, thus the caller registers a
+ * {@link Listener} to receive continuous updates.
+ *
+ * <p>A {@code NameResolver} does not need to automatically re-resolve on failure. Instead, the
+ * {@link Listener} is responsible for eventually (after an appropriate backoff period) invoking
+ * {@link #refresh()}.
+ *
+ * @since 1.0.0
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1770")
+@ThreadSafe
+public abstract class NameResolver {
+  /**
+   * Returns the authority used to authenticate connections to servers.  It <strong>must</strong> be
+   * from a trusted source, because if the authority is tampered with, RPCs may be sent to the
+   * attackers which may leak sensitive user data.
+   *
+   * <p>An implementation must generate it without blocking, typically in line, and
+   * <strong>must</strong> keep it unchanged. {@code NameResolver}s created from the same factory
+   * with the same argument must return the same authority.
+   *
+   * @since 1.0.0
+   */
+  public abstract String getServiceAuthority();
+
+  /**
+   * Starts the resolution.
+   *
+   * @param listener used to receive updates on the target
+   * @since 1.0.0
+   */
+  public abstract void start(Listener listener);
+
+  /**
+   * Stops the resolution. Updates to the Listener will stop.
+   *
+   * @since 1.0.0
+   */
+  public abstract void shutdown();
+
+  /**
+   * Re-resolve the name.
+   *
+   * <p>Can only be called after {@link #start} has been called.
+   *
+   * <p>This is only a hint. Implementation takes it as a signal but may not start resolution
+   * immediately. It should never throw.
+   *
+   * <p>The default implementation is no-op.
+   *
+   * @since 1.0.0
+   */
+  public void refresh() {}
+
+  /**
+   * Factory that creates {@link NameResolver} instances.
+   *
+   * @since 1.0.0
+   */
+  public abstract static class Factory {
+    /**
+     * The port number used in case the target or the underlying naming system doesn't provide a
+     * port number.
+     *
+     * @since 1.0.0
+     */
+    public static final Attributes.Key<Integer> PARAMS_DEFAULT_PORT =
+        Attributes.Key.create("params-default-port");
+
+    /**
+     * Creates a {@link NameResolver} for the given target URI, or {@code null} if the given URI
+     * cannot be resolved by this factory. The decision should be solely based on the scheme of the
+     * URI.
+     *
+     * @param targetUri the target URI to be resolved, whose scheme must not be {@code null}
+     * @param params optional parameters. Canonical keys are defined as {@code PARAMS_*} fields in
+     *               {@link Factory}.
+     * @since 1.0.0
+     */
+    @Nullable
+    public abstract NameResolver newNameResolver(URI targetUri, Attributes params);
+
+    /**
+     * Returns the default scheme, which will be used to construct a URI when {@link
+     * ManagedChannelBuilder#forTarget(String)} is given an authority string instead of a compliant
+     * URI.
+     *
+     * @since 1.0.0
+     */
+    public abstract String getDefaultScheme();
+  }
+
+  /**
+   * Receives address updates.
+   *
+   * <p>All methods are expected to return quickly.
+   *
+   * @since 1.0.0
+   */
+  @ThreadSafe
+  public interface Listener {
+    /**
+     * Handles updates on resolved addresses and attributes.
+     *
+     * <p>Implementations will not modify the given {@code servers}.
+     *
+     * @param servers the resolved server addresses. An empty list will trigger {@link #onError}
+     * @param attributes extra metadata from naming system
+     * @since 1.3.0
+     */
+    void onAddresses(List<EquivalentAddressGroup> servers, Attributes attributes);
+
+    /**
+     * Handles an error from the resolver. The listener is responsible for eventually invoking
+     * {@link #refresh()} to re-attempt resolution.
+     *
+     * @param error a non-OK status
+     * @since 1.0.0
+     */
+    void onError(Status error);
+  }
+}
diff --git a/core/src/main/java/io/grpc/NameResolverProvider.java b/core/src/main/java/io/grpc/NameResolverProvider.java
new file mode 100644
index 0000000..f0b76ea
--- /dev/null
+++ b/core/src/main/java/io/grpc/NameResolverProvider.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Provider of name resolvers for name agnostic consumption.
+ *
+ * <p>Implementations <em>should not</em> throw. If they do, it may interrupt class loading. If
+ * exceptions may reasonably occur for implementation-specific reasons, implementations should
+ * generally handle the exception gracefully and return {@code false} from {@link #isAvailable()}.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4159")
+public abstract class NameResolverProvider extends NameResolver.Factory {
+
+  private static final Logger logger = Logger.getLogger(NameResolverProvider.class.getName());
+
+  /**
+   * The port number used in case the target or the underlying naming system doesn't provide a
+   * port number.
+   *
+   * @since 1.0.0
+   */
+  @SuppressWarnings("unused") // Avoids outside callers accidentally depending on the super class.
+  public static final Attributes.Key<Integer> PARAMS_DEFAULT_PORT =
+      NameResolver.Factory.PARAMS_DEFAULT_PORT;
+
+  @VisibleForTesting
+  static final Iterable<Class<?>> HARDCODED_CLASSES = getHardCodedClasses();
+
+  private static final List<NameResolverProvider> providers = ServiceProviders.loadAll(
+      NameResolverProvider.class,
+      HARDCODED_CLASSES,
+      NameResolverProvider.class.getClassLoader(),
+      new NameResolverPriorityAccessor());
+
+  private static final NameResolver.Factory factory = new NameResolverFactory(providers);
+
+  /**
+   * Returns non-{@code null} ClassLoader-wide providers, in preference order.
+   *
+   * @since 1.0.0
+   */
+  public static List<NameResolverProvider> providers() {
+    return providers;
+  }
+
+  /**
+   * @since 1.0.0
+   */
+  public static NameResolver.Factory asFactory() {
+    return factory;
+  }
+
+  @VisibleForTesting
+  static NameResolver.Factory asFactory(List<NameResolverProvider> providers) {
+    return new NameResolverFactory(providers);
+  }
+
+  /**
+   * Whether this provider is available for use, taking the current environment into consideration.
+   * If {@code false}, no other methods are safe to be called.
+   *
+   * @since 1.0.0
+   */
+  protected abstract boolean isAvailable();
+
+  /**
+   * A priority, from 0 to 10 that this provider should be used, taking the current environment into
+   * consideration. 5 should be considered the default, and then tweaked based on environment
+   * detection. A priority of 0 does not imply that the provider wouldn't work; just that it should
+   * be last in line.
+   *
+   * @since 1.0.0
+   */
+  protected abstract int priority();
+
+  private static final class NameResolverFactory extends NameResolver.Factory {
+    private final List<NameResolverProvider> providers;
+
+    NameResolverFactory(List<NameResolverProvider> providers) {
+      this.providers = Collections.unmodifiableList(new ArrayList<>(providers));
+    }
+
+    @Override
+    @Nullable
+    public NameResolver newNameResolver(URI targetUri, Attributes params) {
+      checkForProviders();
+      for (NameResolverProvider provider : providers) {
+        NameResolver resolver = provider.newNameResolver(targetUri, params);
+        if (resolver != null) {
+          return resolver;
+        }
+      }
+      return null;
+    }
+
+    @Override
+    public String getDefaultScheme() {
+      checkForProviders();
+      return providers.get(0).getDefaultScheme();
+    }
+
+    private void checkForProviders() {
+      if (providers.isEmpty()) {
+        String msg = "No NameResolverProviders found via ServiceLoader, including for DNS. "
+            + "This is probably due to a broken build. If using ProGuard, check your configuration";
+        throw new RuntimeException(msg);
+      }
+    }
+  }
+
+  @VisibleForTesting
+  static final List<Class<?>> getHardCodedClasses() {
+    // Class.forName(String) is used to remove the need for ProGuard configuration. Note that
+    // ProGuard does not detect usages of Class.forName(String, boolean, ClassLoader):
+    // https://sourceforge.net/p/proguard/bugs/418/
+    try {
+      return Collections.<Class<?>>singletonList(
+          Class.forName("io.grpc.internal.DnsNameResolverProvider"));
+    } catch (ClassNotFoundException e) {
+      logger.log(Level.FINE, "Unable to find DNS NameResolver", e);
+    }
+    return Collections.emptyList();
+  }
+
+  private static final class NameResolverPriorityAccessor
+      implements ServiceProviders.PriorityAccessor<NameResolverProvider> {
+
+    NameResolverPriorityAccessor() {}
+
+    @Override
+    public boolean isAvailable(NameResolverProvider provider) {
+      return provider.isAvailable();
+    }
+
+    @Override
+    public int getPriority(NameResolverProvider provider) {
+      return provider.priority();
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/PartialForwardingClientCall.java b/core/src/main/java/io/grpc/PartialForwardingClientCall.java
new file mode 100644
index 0000000..b49a143
--- /dev/null
+++ b/core/src/main/java/io/grpc/PartialForwardingClientCall.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.base.MoreObjects;
+import javax.annotation.Nullable;
+
+/**
+ * A {@link ClientCall} which forwards all of its methods to another {@link ClientCall} which
+ * may have a different sendMessage() message type.
+ */
+abstract class PartialForwardingClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> {
+  /**
+   * Returns the delegated {@code ClientCall}.
+   */
+  protected abstract ClientCall<?, ?> delegate();
+
+  @Override
+  public void request(int numMessages) {
+    delegate().request(numMessages);
+  }
+
+  @Override
+  public void cancel(@Nullable String message, @Nullable Throwable cause) {
+    delegate().cancel(message, cause);
+  }
+
+  @Override
+  public void halfClose() {
+    delegate().halfClose();
+  }
+
+  @Override
+  public void setMessageCompression(boolean enabled) {
+    delegate().setMessageCompression(enabled);
+  }
+
+  @Override
+  public boolean isReady() {
+    return delegate().isReady();
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    return delegate().getAttributes();
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();
+  }
+}
diff --git a/core/src/main/java/io/grpc/PartialForwardingClientCallListener.java b/core/src/main/java/io/grpc/PartialForwardingClientCallListener.java
new file mode 100644
index 0000000..632e6a1
--- /dev/null
+++ b/core/src/main/java/io/grpc/PartialForwardingClientCallListener.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * A {@link ClientCall.Listener} which forwards all of its methods to another {@link
+ * ClientCall.Listener} which may have a different parameterized type than the
+ * onMessage() message type.
+ */
+abstract class PartialForwardingClientCallListener<RespT> extends ClientCall.Listener<RespT> {
+  /**
+   * Returns the delegated {@code ClientCall.Listener}.
+   */
+  protected abstract ClientCall.Listener<?> delegate();
+
+  @Override
+  public void onHeaders(Metadata headers) {
+    delegate().onHeaders(headers);
+  }
+
+  @Override
+  public void onClose(Status status, Metadata trailers) {
+    delegate().onClose(status, trailers);
+  }
+
+  @Override
+  public void onReady() {
+    delegate().onReady();
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();
+  }
+}
diff --git a/core/src/main/java/io/grpc/PartialForwardingServerCall.java b/core/src/main/java/io/grpc/PartialForwardingServerCall.java
new file mode 100644
index 0000000..8b95d7a
--- /dev/null
+++ b/core/src/main/java/io/grpc/PartialForwardingServerCall.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * A {@link ServerCall} which forwards all of it's methods to another {@link ServerCall} which
+ * may have a different onMessage() message type.
+ */
+abstract class PartialForwardingServerCall<ReqT, RespT> extends ServerCall<ReqT, RespT> {
+  /**
+   * Returns the delegated {@code ServerCall}.
+   */
+  protected abstract ServerCall<?, ?> delegate();
+
+  @Override
+  public void request(int numMessages) {
+    delegate().request(numMessages);
+  }
+
+  @Override
+  public void sendHeaders(Metadata headers) {
+    delegate().sendHeaders(headers);
+  }
+
+  @Override
+  public boolean isReady() {
+    return delegate().isReady();
+  }
+
+  @Override
+  public void close(Status status, Metadata trailers) {
+    delegate().close(status, trailers);
+  }
+
+  @Override
+  public boolean isCancelled() {
+    return delegate().isCancelled();
+  }
+
+  @Override
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1703")
+  public void setMessageCompression(boolean enabled) {
+    delegate().setMessageCompression(enabled);
+  }
+
+  @Override
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+  public void setCompression(String compressor) {
+    delegate().setCompression(compressor);
+  }
+
+  @Override
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1779")
+  public Attributes getAttributes() {
+    return delegate().getAttributes();
+  }
+
+  @Override
+  public String getAuthority() {
+    return delegate().getAuthority();
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();
+  }
+}
diff --git a/core/src/main/java/io/grpc/PartialForwardingServerCallListener.java b/core/src/main/java/io/grpc/PartialForwardingServerCallListener.java
new file mode 100644
index 0000000..ca2fd00
--- /dev/null
+++ b/core/src/main/java/io/grpc/PartialForwardingServerCallListener.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.base.MoreObjects;
+
+/**
+ * A {@link ServerCall.Listener} which forwards all of its methods to another {@link
+ * ServerCall.Listener} which may have a different parameterized type than the
+ * onMessage() message type.
+ */
+abstract class PartialForwardingServerCallListener<ReqT>
+    extends ServerCall.Listener<ReqT> {
+  /**
+   * Returns the delegated {@code ServerCall.Listener}.
+   */
+  protected abstract ServerCall.Listener<?> delegate();
+
+  @Override
+  public void onHalfClose() {
+    delegate().onHalfClose();
+  }
+
+  @Override
+  public void onCancel() {
+    delegate().onCancel();
+  }
+
+  @Override
+  public void onComplete() {
+    delegate().onComplete();
+  }
+
+  @Override
+  public void onReady() {
+    delegate().onReady();
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();
+  }
+}
diff --git a/core/src/main/java/io/grpc/PickFirstBalancerFactory.java b/core/src/main/java/io/grpc/PickFirstBalancerFactory.java
new file mode 100644
index 0000000..3c75556
--- /dev/null
+++ b/core/src/main/java/io/grpc/PickFirstBalancerFactory.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.ConnectivityState.CONNECTING;
+import static io.grpc.ConnectivityState.SHUTDOWN;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import java.util.List;
+
+/**
+ * A {@link LoadBalancer} that provides no load balancing mechanism over the
+ * addresses from the {@link NameResolver}.  The channel's default behavior
+ * (currently pick-first) is used for all addresses found.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
+public final class PickFirstBalancerFactory extends LoadBalancer.Factory {
+
+  private static final PickFirstBalancerFactory INSTANCE = new PickFirstBalancerFactory();
+
+  private PickFirstBalancerFactory() {
+  }
+
+  /**
+   * Gets an instance of this factory.
+   */
+  public static PickFirstBalancerFactory getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public LoadBalancer newLoadBalancer(LoadBalancer.Helper helper) {
+    return new PickFirstBalancer(helper);
+  }
+
+  @VisibleForTesting
+  static final class PickFirstBalancer extends LoadBalancer {
+    private final Helper helper;
+    private Subchannel subchannel;
+
+    PickFirstBalancer(Helper helper) {
+      this.helper = checkNotNull(helper, "helper");
+    }
+
+    @Override
+    public void handleResolvedAddressGroups(
+        List<EquivalentAddressGroup> servers, Attributes attributes) {
+      if (subchannel == null) {
+        subchannel = helper.createSubchannel(servers, Attributes.EMPTY);
+
+        // The channel state does not get updated when doing name resolving today, so for the moment
+        // let LB report CONNECTION and call subchannel.requestConnection() immediately.
+        helper.updateBalancingState(CONNECTING, new Picker(PickResult.withSubchannel(subchannel)));
+        subchannel.requestConnection();
+      } else {
+        helper.updateSubchannelAddresses(subchannel, servers);
+      }
+    }
+
+    @Override
+    public void handleNameResolutionError(Status error) {
+      if (subchannel != null) {
+        subchannel.shutdown();
+        subchannel = null;
+      }
+      // NB(lukaszx0) Whether we should propagate the error unconditionally is arguable. It's fine
+      // for time being.
+      helper.updateBalancingState(TRANSIENT_FAILURE, new Picker(PickResult.withError(error)));
+    }
+
+    @Override
+    public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
+      ConnectivityState currentState = stateInfo.getState();
+      if (subchannel != this.subchannel || currentState == SHUTDOWN) {
+        return;
+      }
+
+      SubchannelPicker picker;
+      switch (currentState) {
+        case IDLE:
+          picker = new RequestConnectionPicker(subchannel);
+          break;
+        case CONNECTING:
+          // It's safe to use RequestConnectionPicker here, so when coming from IDLE we could leave
+          // the current picker in-place. But ignoring the potential optimization is simpler.
+          picker = new Picker(PickResult.withNoResult());
+          break;
+        case READY:
+          picker = new Picker(PickResult.withSubchannel(subchannel));
+          break;
+        case TRANSIENT_FAILURE:
+          picker = new Picker(PickResult.withError(stateInfo.getStatus()));
+          break;
+        default:
+          throw new IllegalArgumentException("Unsupported state:" + currentState);
+      }
+
+      helper.updateBalancingState(currentState, picker);
+    }
+
+    @Override
+    public void shutdown() {
+      if (subchannel != null) {
+        subchannel.shutdown();
+      }
+    }
+  }
+
+  /**
+   * No-op picker which doesn't add any custom picking logic. It just passes already known result
+   * received in constructor.
+   */
+  private static final class Picker extends SubchannelPicker {
+    private final PickResult result;
+
+    Picker(PickResult result) {
+      this.result = checkNotNull(result, "result");
+    }
+
+    @Override
+    public PickResult pickSubchannel(PickSubchannelArgs args) {
+      return result;
+    }
+  }
+
+  /** Picker that requests connection during pick, and returns noResult. */
+  private static final class RequestConnectionPicker extends SubchannelPicker {
+    private final Subchannel subchannel;
+
+    RequestConnectionPicker(Subchannel subchannel) {
+      this.subchannel = checkNotNull(subchannel, "subchannel");
+    }
+
+    @Override
+    public PickResult pickSubchannel(PickSubchannelArgs args) {
+      subchannel.requestConnection();
+      return PickResult.withNoResult();
+    }
+
+    @Override
+    public void requestConnection() {
+      subchannel.requestConnection();
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/SecurityLevel.java b/core/src/main/java/io/grpc/SecurityLevel.java
new file mode 100644
index 0000000..7d834aa
--- /dev/null
+++ b/core/src/main/java/io/grpc/SecurityLevel.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * The level of security guarantee in communications.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4692")
+public enum SecurityLevel {
+  /**
+   * No security guarantee.
+   */
+  NONE,
+
+  /**
+   * The other party is authenticated and the data is not tampered with.
+   */
+  INTEGRITY,
+
+  /**
+   * In addition to {@code INTEGRITY}, the data is only visible to the intended communication
+   * parties.
+   */
+  PRIVACY_AND_INTEGRITY
+}
diff --git a/core/src/main/java/io/grpc/Server.java b/core/src/main/java/io/grpc/Server.java
new file mode 100644
index 0000000..a9f0f40
--- /dev/null
+++ b/core/src/main/java/io/grpc/Server.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Server for listening for and dispatching incoming calls. It is not expected to be implemented by
+ * application code or interceptors.
+ */
+@ThreadSafe
+public abstract class Server {
+  /**
+   * Bind and start the server.
+   *
+   * @return {@code this} object
+   * @throws IllegalStateException if already started
+   * @throws IOException if unable to bind
+   * @since 1.0.0
+   */
+  public abstract Server start() throws IOException;
+
+  /**
+   * Returns the port number the server is listening on.  This can return -1 if there is no actual
+   * port or the result otherwise does not make sense.  Result is undefined after the server is
+   * terminated.
+   *
+   * @throws IllegalStateException if the server has not yet been started.
+   * @since 1.0.0
+   */
+  public int getPort() {
+    return -1;
+  }
+
+  /**
+   * Returns all services registered with the server, or an empty list if not supported by the
+   * implementation.
+   *
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2222")
+  public List<ServerServiceDefinition> getServices() {
+    return Collections.emptyList();
+  }
+
+  /**
+   * Returns immutable services registered with the server, or an empty list if not supported by the
+   * implementation.
+   *
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2222")
+  public List<ServerServiceDefinition> getImmutableServices() {
+    return Collections.emptyList();
+  }
+
+
+  /**
+   * Returns mutable services registered with the server, or an empty list if not supported by the
+   * implementation.
+   *
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2222")
+  public List<ServerServiceDefinition> getMutableServices() {
+    return Collections.emptyList();
+  }
+
+  /**
+   * Initiates an orderly shutdown in which preexisting calls continue but new calls are rejected.
+   *
+   * @return {@code this} object
+   * @since 1.0.0
+   */
+  public abstract Server shutdown();
+
+  /**
+   * Initiates a forceful shutdown in which preexisting and new calls are rejected. Although
+   * forceful, the shutdown process is still not instantaneous; {@link #isTerminated()} will likely
+   * return {@code false} immediately after this method returns.
+   *
+   * @return {@code this} object
+   * @since 1.0.0
+   */
+  public abstract Server shutdownNow();
+
+  /**
+   * Returns whether the server is shutdown. Shutdown servers reject any new calls, but may still
+   * have some calls being processed.
+   *
+   * @see #shutdown()
+   * @see #isTerminated()
+   * @since 1.0.0
+   */
+  public abstract boolean isShutdown();
+
+  /**
+   * Returns whether the server is terminated. Terminated servers have no running calls and
+   * relevant resources released (like TCP connections).
+   *
+   * @see #isShutdown()
+   * @since 1.0.0
+   */
+  public abstract boolean isTerminated();
+
+  /**
+   * Waits for the server to become terminated, giving up if the timeout is reached.
+   *
+   * @return whether the server is terminated, as would be done by {@link #isTerminated()}.
+   */
+  public abstract boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
+
+  /**
+   * Waits for the server to become terminated.
+   *
+   * @since 1.0.0
+   */
+  public abstract void awaitTermination() throws InterruptedException;
+}
diff --git a/core/src/main/java/io/grpc/ServerBuilder.java b/core/src/main/java/io/grpc/ServerBuilder.java
new file mode 100644
index 0000000..25f5234
--- /dev/null
+++ b/core/src/main/java/io/grpc/ServerBuilder.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.base.Preconditions;
+import java.io.File;
+import java.io.InputStream;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+/**
+ * A builder for {@link Server} instances.
+ *
+ * @param <T> The concrete type of this builder.
+ * @since 1.0.0
+ */
+public abstract class ServerBuilder<T extends ServerBuilder<T>> {
+
+  /**
+   * Static factory for creating a new ServerBuilder.
+   *
+   * @param port the port to listen on
+   * @since 1.0.0
+   */
+  public static ServerBuilder<?> forPort(int port) {
+    return ServerProvider.provider().builderForPort(port);
+  }
+
+  /**
+   * Execute application code directly in the transport thread.
+   *
+   * <p>Depending on the underlying transport, using a direct executor may lead to substantial
+   * performance improvements. However, it also requires the application to not block under
+   * any circumstances.
+   *
+   * <p>Calling this method is semantically equivalent to calling {@link #executor(Executor)} and
+   * passing in a direct executor. However, this is the preferred way as it may allow the transport
+   * to perform special optimizations.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  public abstract T directExecutor();
+
+  /**
+   * Provides a custom executor.
+   *
+   * <p>It's an optional parameter. If the user has not provided an executor when the server is
+   * built, the builder will use a static cached thread pool.
+   *
+   * <p>The server won't take ownership of the given executor. It's caller's responsibility to
+   * shut down the executor when it's desired.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  public abstract T executor(@Nullable Executor executor);
+
+  /**
+   * Adds a service implementation to the handler registry.
+   *
+   * @param service ServerServiceDefinition object
+   * @return this
+   * @since 1.0.0
+   */
+  public abstract T addService(ServerServiceDefinition service);
+
+  /**
+   * Adds a service implementation to the handler registry. If bindableService implements
+   * {@link InternalNotifyOnServerBuild}, the service will receive a reference to the generated
+   * server instance upon build().
+   *
+   * @param bindableService BindableService object
+   * @return this
+   * @since 1.0.0
+   */
+  public abstract T addService(BindableService bindableService);
+
+  /**
+   * Adds a {@link ServerInterceptor} that is run for all services on the server.  Interceptors
+   * added through this method always run before per-service interceptors added through {@link
+   * ServerInterceptors}.  Interceptors run in the reverse order in which they are added.
+   *
+   * @param interceptor the all-service interceptor
+   * @return this
+   * @since 1.5.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/3117")
+  public T intercept(ServerInterceptor interceptor) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Adds a {@link ServerTransportFilter}. The order of filters being added is the order they will
+   * be executed.
+   *
+   * @return this
+   * @since 1.2.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2132")
+  public T addTransportFilter(ServerTransportFilter filter) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Adds a {@link ServerStreamTracer.Factory} to measure server-side traffic.  The order of
+   * factories being added is the order they will be executed.
+   *
+   * @return this
+   * @since 1.3.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
+  public T addStreamTracerFactory(ServerStreamTracer.Factory factory) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Sets a fallback handler registry that will be looked up in if a method is not found in the
+   * primary registry. The primary registry (configured via {@code addService()}) is faster but
+   * immutable. The fallback registry is more flexible and allows implementations to mutate over
+   * time and load services on-demand.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  public abstract T fallbackHandlerRegistry(@Nullable HandlerRegistry fallbackRegistry);
+
+  /**
+   * Makes the server use TLS.
+   *
+   * @param certChain file containing the full certificate chain
+   * @param privateKey file containing the private key
+   *
+   * @return this
+   * @throws UnsupportedOperationException if the server does not support TLS.
+   * @since 1.0.0
+   */
+  public abstract T useTransportSecurity(File certChain, File privateKey);
+
+  /**
+   * Makes the server use TLS.
+   *
+   * @param certChain InputStream containing the full certificate chain
+   * @param privateKey InputStream containing the private key
+   *
+   * @return this
+   * @throws UnsupportedOperationException if the server does not support TLS, or does not support
+   *         reading these files from an InputStream.
+   * @since 1.12.0
+   */
+  public T useTransportSecurity(InputStream certChain, InputStream privateKey) {
+    throw new UnsupportedOperationException();
+  }
+
+
+  /**
+   * Set the decompression registry for use in the channel.  This is an advanced API call and
+   * shouldn't be used unless you are using custom message encoding.   The default supported
+   * decompressors are in {@code DecompressorRegistry.getDefaultInstance}.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+  public abstract T decompressorRegistry(@Nullable DecompressorRegistry registry);
+
+  /**
+   * Set the compression registry for use in the channel.  This is an advanced API call and
+   * shouldn't be used unless you are using custom message encoding.   The default supported
+   * compressors are in {@code CompressorRegistry.getDefaultInstance}.
+   *
+   * @return this
+   * @since 1.0.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+  public abstract T compressorRegistry(@Nullable CompressorRegistry registry);
+
+  /**
+   * Sets the permitted time for new connections to complete negotiation handshakes before being
+   * killed.
+   *
+   * @return this
+   * @throws IllegalArgumentException if timeout is negative
+   * @throws UnsupportedOperationException if unsupported
+   * @since 1.8.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/3706")
+  public T handshakeTimeout(long timeout, TimeUnit unit) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Sets the maximum message size allowed to be received on the server. If not called,
+   * defaults to 4 MiB. The default provides protection to servers who haven't considered the
+   * possibility of receiving large messages while trying to be large enough to not be hit in normal
+   * usage.
+   *
+   * <p>This method is advisory, and implementations may decide to not enforce this.  Currently,
+   * the only known transport to not enforce this is {@code InProcessServer}.
+   *
+   * @param bytes the maximum number of bytes a single message can be.
+   * @return this
+   * @throws IllegalArgumentException if bytes is negative.
+   * @throws UnsupportedOperationException if unsupported.
+   * @since 1.13.0
+   */
+  public T maxInboundMessageSize(int bytes) {
+    // intentional noop rather than throw, this method is only advisory.
+    Preconditions.checkArgument(bytes >= 0, "bytes must be >= 0");
+    return thisT();
+  }
+
+  /**
+   * Sets the BinaryLog object that this server should log to. The server does not take
+   * ownership of the object, and users are responsible for calling {@link BinaryLog#close()}.
+   *
+   * @param binaryLog the object to provide logging.
+   * @return this
+   * @since 1.13.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4017")
+  public T setBinaryLog(BinaryLog binaryLog) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Builds a server using the given parameters.
+   *
+   * <p>The returned service will not been started or be bound a port. You will need to start it
+   * with {@link Server#start()}.
+   *
+   * @return a new Server
+   * @since 1.0.0
+   */
+  public abstract Server build();
+
+  /**
+   * Returns the correctly typed version of the builder.
+   */
+  private T thisT() {
+    @SuppressWarnings("unchecked")
+    T thisT = (T) this;
+    return thisT;
+  }
+}
diff --git a/core/src/main/java/io/grpc/ServerCall.java b/core/src/main/java/io/grpc/ServerCall.java
new file mode 100644
index 0000000..106676f
--- /dev/null
+++ b/core/src/main/java/io/grpc/ServerCall.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import javax.annotation.Nullable;
+
+/**
+ * Encapsulates a single call received from a remote client. Calls may not simply be unary
+ * request-response even though this is the most common pattern. Calls may stream any number of
+ * requests and responses. This API is generally intended for use by generated handlers,
+ * but applications may use it directly if they need to.
+ *
+ * <p>Headers must be sent before any messages, which must be sent before closing.
+ *
+ * <p>No generic method for determining message receipt or providing acknowledgement is provided.
+ * Applications are expected to utilize normal messages for such signals, as a response
+ * naturally acknowledges its request.
+ *
+ * <p>Methods are guaranteed to be non-blocking. Implementations are not required to be thread-safe.
+ *
+ * <p>DO NOT MOCK: Use InProcessTransport and make a fake server instead.
+ *
+ * @param <ReqT> parsed type of request message.
+ * @param <RespT> parsed type of response message.
+ */
+public abstract class ServerCall<ReqT, RespT> {
+
+  /**
+   * Callbacks for consuming incoming RPC messages.
+   *
+   * <p>Any contexts are guaranteed to arrive before any messages, which are guaranteed before half
+   * close, which is guaranteed before completion.
+   *
+   * <p>Implementations are free to block for extended periods of time. Implementations are not
+   * required to be thread-safe.
+   */
+  // TODO(ejona86): We need to decide what to do in the case of server closing with non-cancellation
+  // before client half closes. It may be that we treat such a case as an error. If we permit such
+  // a case then we either get to generate a half close or purposefully omit it.
+  public abstract static class Listener<ReqT> {
+    /**
+     * A request message has been received. For streaming calls, there may be zero or more request
+     * messages.
+     *
+     * @param message a received request message.
+     */
+    public void onMessage(ReqT message) {}
+
+    /**
+     * The client completed all message sending. However, the call may still be cancelled.
+     */
+    public void onHalfClose() {}
+
+    /**
+     * The call was cancelled and the server is encouraged to abort processing to save resources,
+     * since the client will not process any further messages. Cancellations can be caused by
+     * timeouts, explicit cancellation by the client, network errors, etc.
+     *
+     * <p>There will be no further callbacks for the call.
+     */
+    public void onCancel() {}
+
+    /**
+     * The call is considered complete and {@link #onCancel} is guaranteed not to be called.
+     * However, the client is not guaranteed to have received all messages.
+     *
+     * <p>There will be no further callbacks for the call.
+     */
+    public void onComplete() {}
+
+    /**
+     * This indicates that the call is now capable of sending additional messages (via
+     * {@link #sendMessage}) without requiring excessive buffering internally. This event is
+     * just a suggestion and the application is free to ignore it, however doing so may
+     * result in excessive buffering within the call.
+     */
+    public void onReady() {}
+  }
+
+  /**
+   * Requests up to the given number of messages from the call to be delivered to
+   * {@link Listener#onMessage(Object)}. Once {@code numMessages} have been delivered
+   * no further request messages will be delivered until more messages are requested by
+   * calling this method again.
+   *
+   * <p>Servers use this mechanism to provide back-pressure to the client for flow-control.
+   *
+   * <p>This method is safe to call from multiple threads without external synchronizaton.
+   *
+   * @param numMessages the requested number of messages to be delivered to the listener.
+   */
+  public abstract void request(int numMessages);
+
+  /**
+   * Send response header metadata prior to sending a response message. This method may
+   * only be called once and cannot be called after calls to {@link #sendMessage} or {@link #close}.
+   *
+   * <p>Since {@link Metadata} is not thread-safe, the caller must not access (read or write) {@code
+   * headers} after this point.
+   *
+   * @param headers metadata to send prior to any response body.
+   * @throws IllegalStateException if {@code close} has been called, a message has been sent, or
+   *     headers have already been sent
+   */
+  public abstract void sendHeaders(Metadata headers);
+
+  /**
+   * Send a response message. Messages are the primary form of communication associated with
+   * RPCs. Multiple response messages may exist for streaming calls.
+   *
+   * @param message response message.
+   * @throws IllegalStateException if headers not sent or call is {@link #close}d
+   */
+  public abstract void sendMessage(RespT message);
+
+  /**
+   * If {@code true}, indicates that the call is capable of sending additional messages
+   * without requiring excessive buffering internally. This event is
+   * just a suggestion and the application is free to ignore it, however doing so may
+   * result in excessive buffering within the call.
+   *
+   * <p>This implementation always returns {@code true}.
+   */
+  public boolean isReady() {
+    return true;
+  }
+
+  /**
+   * Close the call with the provided status. No further sending or receiving will occur. If {@link
+   * Status#isOk} is {@code false}, then the call is said to have failed.
+   *
+   * <p>If no errors or cancellations are known to have occurred, then a {@link Listener#onComplete}
+   * notification should be expected, independent of {@code status}. Otherwise {@link
+   * Listener#onCancel} has been or will be called.
+   *
+   * <p>Since {@link Metadata} is not thread-safe, the caller must not access (read or write) {@code
+   * trailers} after this point.
+   *
+   * @throws IllegalStateException if call is already {@code close}d
+   */
+  public abstract void close(Status status, Metadata trailers);
+
+  /**
+   * Returns {@code true} when the call is cancelled and the server is encouraged to abort
+   * processing to save resources, since the client will not be processing any further methods.
+   * Cancellations can be caused by timeouts, explicit cancel by client, network errors, and
+   * similar.
+   *
+   * <p>This method may safely be called concurrently from multiple threads.
+   */
+  public abstract boolean isCancelled();
+
+  /**
+   * Enables per-message compression, if an encoding type has been negotiated.  If no message
+   * encoding has been negotiated, this is a no-op. By default per-message compression is enabled,
+   * but may not have any effect if compression is not enabled on the call.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+  public void setMessageCompression(boolean enabled) {
+    // noop
+  }
+
+  /**
+   * Sets the compression algorithm for this call.  If the server does not support the compression
+   * algorithm, the call will fail.  This method may only be called before {@link #sendHeaders}.
+   * The compressor to use will be looked up in the {@link CompressorRegistry}.  Default gRPC
+   * servers support the "gzip" compressor.
+   *
+   * @param compressor the name of the compressor to use.
+   * @throws IllegalArgumentException if the compressor name can not be found.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+  public void setCompression(String compressor) {
+    // noop
+  }
+
+  /**
+   * Returns properties of a single call.
+   *
+   * <p>Attributes originate from the transport and can be altered by {@link ServerTransportFilter}.
+   * {@link Grpc} defines commonly used attributes, but they are not guaranteed to be present.
+   *
+   * @return non-{@code null} Attributes container
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1779")
+  public Attributes getAttributes() {
+    return Attributes.EMPTY;
+  }
+
+  /**
+   * Gets the authority this call is addressed to.
+   *
+   * @return the authority string. {@code null} if not available.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2924")
+  @Nullable
+  public String getAuthority() {
+    return null;
+  }
+
+  /**
+   * The {@link MethodDescriptor} for the call.
+   */
+  public abstract MethodDescriptor<ReqT, RespT> getMethodDescriptor();
+}
diff --git a/core/src/main/java/io/grpc/ServerCallHandler.java b/core/src/main/java/io/grpc/ServerCallHandler.java
new file mode 100644
index 0000000..13d0b41
--- /dev/null
+++ b/core/src/main/java/io/grpc/ServerCallHandler.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Interface to initiate processing of incoming remote calls. Advanced applications and generated
+ * code will implement this interface to allows {@link Server}s to invoke service methods.
+ */
+@ThreadSafe
+public interface ServerCallHandler<RequestT, ResponseT> {
+  /**
+   * Produce a non-{@code null} listener for the incoming call. Implementations are free to call
+   * methods on {@code call} before this method has returned.
+   *
+   * <p>Since {@link Metadata} is not thread-safe, the caller must not access (read or write) {@code
+   * headers} after this point.
+   *
+   * <p>If the implementation throws an exception, {@code call} will be closed with an error.
+   * Implementations must not throw an exception if they started processing that may use {@code
+   * call} on another thread.
+   *
+   * @param call object for responding to the remote client.
+   * @return listener for processing incoming request messages for {@code call}
+   */
+  ServerCall.Listener<RequestT> startCall(
+      ServerCall<RequestT, ResponseT> call,
+      Metadata headers);
+}
diff --git a/core/src/main/java/io/grpc/ServerInterceptor.java b/core/src/main/java/io/grpc/ServerInterceptor.java
new file mode 100644
index 0000000..6a4a1e5
--- /dev/null
+++ b/core/src/main/java/io/grpc/ServerInterceptor.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Interface for intercepting incoming calls before that are dispatched by
+ * {@link ServerCallHandler}.
+ *
+ * <p>Implementers use this mechanism to add cross-cutting behavior to server-side calls. Common
+ * example of such behavior include:
+ * <ul>
+ * <li>Enforcing valid authentication credentials</li>
+ * <li>Logging and monitoring call behavior</li>
+ * <li>Delegating calls to other servers</li>
+ * </ul>
+ */
+@ThreadSafe
+public interface ServerInterceptor {
+  /**
+   * Intercept {@link ServerCall} dispatch by the {@code next} {@link ServerCallHandler}. General
+   * semantics of {@link ServerCallHandler#startCall} apply and the returned
+   * {@link io.grpc.ServerCall.Listener} must not be {@code null}.
+   *
+   * <p>If the implementation throws an exception, {@code call} will be closed with an error.
+   * Implementations must not throw an exception if they started processing that may use {@code
+   * call} on another thread.
+   *
+   * @param call object to receive response messages
+   * @param headers which can contain extra call metadata from {@link ClientCall#start},
+   *                e.g. authentication credentials.
+   * @param next next processor in the interceptor chain
+   * @return listener for processing incoming messages for {@code call}, never {@code null}.
+   */
+  <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+      ServerCall<ReqT, RespT> call,
+      Metadata headers,
+      ServerCallHandler<ReqT, RespT> next);
+}
diff --git a/core/src/main/java/io/grpc/ServerInterceptors.java b/core/src/main/java/io/grpc/ServerInterceptors.java
new file mode 100644
index 0000000..8f45755
--- /dev/null
+++ b/core/src/main/java/io/grpc/ServerInterceptors.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.base.Preconditions;
+import java.io.BufferedInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility methods for working with {@link ServerInterceptor}s.
+ */
+public final class ServerInterceptors {
+  // Prevent instantiation
+  private ServerInterceptors() {}
+
+  /**
+   * Create a new {@code ServerServiceDefinition} whose {@link ServerCallHandler}s will call
+   * {@code interceptors} before calling the pre-existing {@code ServerCallHandler}. The first
+   * interceptor will have its {@link ServerInterceptor#interceptCall} called first.
+   *
+   * @param serviceDef   the service definition for which to intercept all its methods.
+   * @param interceptors array of interceptors to apply to the service.
+   * @return a wrapped version of {@code serviceDef} with the interceptors applied.
+   */
+  public static ServerServiceDefinition interceptForward(ServerServiceDefinition serviceDef,
+                                                         ServerInterceptor... interceptors) {
+    return interceptForward(serviceDef, Arrays.asList(interceptors));
+  }
+
+  public static ServerServiceDefinition interceptForward(BindableService bindableService,
+      ServerInterceptor... interceptors) {
+    return interceptForward(bindableService.bindService(), Arrays.asList(interceptors));
+  }
+
+  /**
+   * Create a new {@code ServerServiceDefinition} whose {@link ServerCallHandler}s will call
+   * {@code interceptors} before calling the pre-existing {@code ServerCallHandler}. The first
+   * interceptor will have its {@link ServerInterceptor#interceptCall} called first.
+   *
+   * @param serviceDef   the service definition for which to intercept all its methods.
+   * @param interceptors list of interceptors to apply to the service.
+   * @return a wrapped version of {@code serviceDef} with the interceptors applied.
+   */
+  public static ServerServiceDefinition interceptForward(
+      ServerServiceDefinition serviceDef,
+      List<? extends ServerInterceptor> interceptors) {
+    List<? extends ServerInterceptor> copy = new ArrayList<>(interceptors);
+    Collections.reverse(copy);
+    return intercept(serviceDef, copy);
+  }
+
+  public static ServerServiceDefinition interceptForward(
+      BindableService bindableService,
+      List<? extends ServerInterceptor> interceptors) {
+    return interceptForward(bindableService.bindService(), interceptors);
+  }
+
+  /**
+   * Create a new {@code ServerServiceDefinition} whose {@link ServerCallHandler}s will call
+   * {@code interceptors} before calling the pre-existing {@code ServerCallHandler}. The last
+   * interceptor will have its {@link ServerInterceptor#interceptCall} called first.
+   *
+   * @param serviceDef   the service definition for which to intercept all its methods.
+   * @param interceptors array of interceptors to apply to the service.
+   * @return a wrapped version of {@code serviceDef} with the interceptors applied.
+   */
+  public static ServerServiceDefinition intercept(ServerServiceDefinition serviceDef,
+                                                  ServerInterceptor... interceptors) {
+    return intercept(serviceDef, Arrays.asList(interceptors));
+  }
+
+  public static ServerServiceDefinition intercept(BindableService bindableService,
+      ServerInterceptor... interceptors) {
+    Preconditions.checkNotNull(bindableService, "bindableService");
+    return intercept(bindableService.bindService(), Arrays.asList(interceptors));
+  }
+
+  /**
+   * Create a new {@code ServerServiceDefinition} whose {@link ServerCallHandler}s will call
+   * {@code interceptors} before calling the pre-existing {@code ServerCallHandler}. The last
+   * interceptor will have its {@link ServerInterceptor#interceptCall} called first.
+   *
+   * @param serviceDef   the service definition for which to intercept all its methods.
+   * @param interceptors list of interceptors to apply to the service.
+   * @return a wrapped version of {@code serviceDef} with the interceptors applied.
+   */
+  public static ServerServiceDefinition intercept(ServerServiceDefinition serviceDef,
+                                                  List<? extends ServerInterceptor> interceptors) {
+    Preconditions.checkNotNull(serviceDef, "serviceDef");
+    if (interceptors.isEmpty()) {
+      return serviceDef;
+    }
+    ServerServiceDefinition.Builder serviceDefBuilder
+        = ServerServiceDefinition.builder(serviceDef.getServiceDescriptor());
+    for (ServerMethodDefinition<?, ?> method : serviceDef.getMethods()) {
+      wrapAndAddMethod(serviceDefBuilder, method, interceptors);
+    }
+    return serviceDefBuilder.build();
+  }
+
+  public static ServerServiceDefinition intercept(BindableService bindableService,
+      List<? extends ServerInterceptor> interceptors) {
+    Preconditions.checkNotNull(bindableService, "bindableService");
+    return intercept(bindableService.bindService(), interceptors);
+  }
+
+  /**
+   * Create a new {@code ServerServiceDefinition} whose {@link MethodDescriptor} serializes to
+   * and from InputStream for all methods.  The InputStream is guaranteed return true for
+   * markSupported().  The {@code ServerCallHandler} created will automatically
+   * convert back to the original types for request and response before calling the existing
+   * {@code ServerCallHandler}.  Calling this method combined with the intercept methods will
+   * allow the developer to choose whether to intercept messages of InputStream, or the modeled
+   * types of their application.
+   *
+   * @param serviceDef the service definition to convert messages to InputStream
+   * @return a wrapped version of {@code serviceDef} with the InputStream conversion applied.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1712")
+  public static ServerServiceDefinition useInputStreamMessages(
+      final ServerServiceDefinition serviceDef) {
+    final MethodDescriptor.Marshaller<InputStream> marshaller =
+        new MethodDescriptor.Marshaller<InputStream>() {
+      @Override
+      public InputStream stream(final InputStream value) {
+        return value;
+      }
+
+      @Override
+      public InputStream parse(final InputStream stream) {
+        if (stream.markSupported()) {
+          return stream;
+        } else {
+          return new BufferedInputStream(stream);
+        }
+      }
+    };
+
+    return useMarshalledMessages(serviceDef, marshaller);
+  }
+
+  /**
+   * Create a new {@code ServerServiceDefinition} whose {@link MethodDescriptor} serializes to
+   * and from T for all methods.  The {@code ServerCallHandler} created will automatically
+   * convert back to the original types for request and response before calling the existing
+   * {@code ServerCallHandler}.  Calling this method combined with the intercept methods will
+   * allow the developer to choose whether to intercept messages of T, or the modeled types
+   * of their application.  This can also be chained to allow for interceptors to handle messages
+   * as multiple different T types within the chain if the added cost of serialization is not
+   * a concern.
+   *
+   * @param serviceDef the service definition to convert messages to T
+   * @return a wrapped version of {@code serviceDef} with the T conversion applied.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1712")
+  public static <T> ServerServiceDefinition useMarshalledMessages(
+      final ServerServiceDefinition serviceDef,
+      final MethodDescriptor.Marshaller<T> marshaller) {
+    List<ServerMethodDefinition<?, ?>> wrappedMethods =
+        new ArrayList<ServerMethodDefinition<?, ?>>();
+    List<MethodDescriptor<?, ?>> wrappedDescriptors =
+        new ArrayList<MethodDescriptor<?, ?>>();
+    // Wrap the descriptors
+    for (final ServerMethodDefinition<?, ?> definition : serviceDef.getMethods()) {
+      final MethodDescriptor<?, ?> originalMethodDescriptor = definition.getMethodDescriptor();
+      final MethodDescriptor<T, T> wrappedMethodDescriptor =
+          originalMethodDescriptor.toBuilder(marshaller, marshaller).build();
+      wrappedDescriptors.add(wrappedMethodDescriptor);
+      wrappedMethods.add(wrapMethod(definition, wrappedMethodDescriptor));
+    }
+    // Build the new service descriptor
+    final ServerServiceDefinition.Builder serviceBuilder = ServerServiceDefinition
+        .builder(new ServiceDescriptor(serviceDef.getServiceDescriptor().getName(),
+            wrappedDescriptors));
+    // Create the new service definiton.
+    for (ServerMethodDefinition<?, ?> definition : wrappedMethods) {
+      serviceBuilder.addMethod(definition);
+    }
+    return serviceBuilder.build();
+  }
+
+  private static <ReqT, RespT> void wrapAndAddMethod(
+      ServerServiceDefinition.Builder serviceDefBuilder, ServerMethodDefinition<ReqT, RespT> method,
+      List<? extends ServerInterceptor> interceptors) {
+    ServerCallHandler<ReqT, RespT> callHandler = method.getServerCallHandler();
+    for (ServerInterceptor interceptor : interceptors) {
+      callHandler = InterceptCallHandler.create(interceptor, callHandler);
+    }
+    serviceDefBuilder.addMethod(method.withServerCallHandler(callHandler));
+  }
+
+  static final class InterceptCallHandler<ReqT, RespT> implements ServerCallHandler<ReqT, RespT> {
+    public static <ReqT, RespT> InterceptCallHandler<ReqT, RespT> create(
+        ServerInterceptor interceptor, ServerCallHandler<ReqT, RespT> callHandler) {
+      return new InterceptCallHandler<ReqT, RespT>(interceptor, callHandler);
+    }
+
+    private final ServerInterceptor interceptor;
+    private final ServerCallHandler<ReqT, RespT> callHandler;
+
+    private InterceptCallHandler(
+        ServerInterceptor interceptor, ServerCallHandler<ReqT, RespT> callHandler) {
+      this.interceptor = Preconditions.checkNotNull(interceptor, "interceptor");
+      this.callHandler = callHandler;
+    }
+
+    @Override
+    public ServerCall.Listener<ReqT> startCall(
+        ServerCall<ReqT, RespT> call,
+        Metadata headers) {
+      return interceptor.interceptCall(call, headers, callHandler);
+    }
+  }
+
+  static <OReqT, ORespT, WReqT, WRespT> ServerMethodDefinition<WReqT, WRespT> wrapMethod(
+      final ServerMethodDefinition<OReqT, ORespT> definition,
+      final MethodDescriptor<WReqT, WRespT> wrappedMethod) {
+    final ServerCallHandler<WReqT, WRespT> wrappedHandler = wrapHandler(
+        definition.getServerCallHandler(),
+        definition.getMethodDescriptor(),
+        wrappedMethod);
+    return ServerMethodDefinition.create(wrappedMethod, wrappedHandler);
+  }
+
+  private static <OReqT, ORespT, WReqT, WRespT> ServerCallHandler<WReqT, WRespT> wrapHandler(
+      final ServerCallHandler<OReqT, ORespT> originalHandler,
+      final MethodDescriptor<OReqT, ORespT> originalMethod,
+      final MethodDescriptor<WReqT, WRespT> wrappedMethod) {
+    return new ServerCallHandler<WReqT, WRespT>() {
+      @Override
+      public ServerCall.Listener<WReqT> startCall(
+          final ServerCall<WReqT, WRespT> call,
+          final Metadata headers) {
+        final ServerCall<OReqT, ORespT> unwrappedCall =
+            new PartialForwardingServerCall<OReqT, ORespT>() {
+          @Override
+          protected ServerCall<WReqT, WRespT> delegate() {
+            return call;
+          }
+
+          @Override
+          public void sendMessage(ORespT message) {
+            final InputStream is = originalMethod.streamResponse(message);
+            final WRespT wrappedMessage = wrappedMethod.parseResponse(is);
+            delegate().sendMessage(wrappedMessage);
+          }
+
+          @Override
+          public MethodDescriptor<OReqT, ORespT> getMethodDescriptor() {
+            return originalMethod;
+          }
+        };
+
+        final ServerCall.Listener<OReqT> originalListener = originalHandler
+            .startCall(unwrappedCall, headers);
+
+        return new PartialForwardingServerCallListener<WReqT>() {
+          @Override
+          protected ServerCall.Listener<OReqT> delegate() {
+            return originalListener;
+          }
+
+          @Override
+          public void onMessage(WReqT message) {
+            final InputStream is = wrappedMethod.streamRequest(message);
+            final OReqT originalMessage = originalMethod.parseRequest(is);
+            delegate().onMessage(originalMessage);
+          }
+        };
+      }
+    };
+  }
+}
diff --git a/core/src/main/java/io/grpc/ServerMethodDefinition.java b/core/src/main/java/io/grpc/ServerMethodDefinition.java
new file mode 100644
index 0000000..5163172
--- /dev/null
+++ b/core/src/main/java/io/grpc/ServerMethodDefinition.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * Definition of a method exposed by a {@link Server}.
+ *
+ * @see ServerServiceDefinition
+ */
+public final class ServerMethodDefinition<ReqT, RespT> {
+  private final MethodDescriptor<ReqT, RespT> method;
+  private final ServerCallHandler<ReqT, RespT> handler;
+
+  private ServerMethodDefinition(MethodDescriptor<ReqT, RespT> method,
+      ServerCallHandler<ReqT, RespT> handler) {
+    this.method = method;
+    this.handler = handler;
+  }
+
+  /**
+   * Create a new instance.
+   *
+   * @param method the {@link MethodDescriptor} for this method.
+   * @param handler to dispatch calls to.
+   * @return a new instance.
+   */
+  public static <ReqT, RespT> ServerMethodDefinition<ReqT, RespT> create(
+      MethodDescriptor<ReqT, RespT> method,
+      ServerCallHandler<ReqT, RespT> handler) {
+    return new ServerMethodDefinition<ReqT, RespT>(method, handler);
+  }
+
+  /** The {@code MethodDescriptor} for this method. */
+  public MethodDescriptor<ReqT, RespT> getMethodDescriptor() {
+    return method;
+  }
+
+  /** Handler for incoming calls. */
+  public ServerCallHandler<ReqT, RespT> getServerCallHandler() {
+    return handler;
+  }
+
+  /**
+   * Create a new method definition with a different call handler.
+   *
+   * @param handler to bind to a cloned instance of this.
+   * @return a cloned instance of this with the new handler bound.
+   */
+  public ServerMethodDefinition<ReqT, RespT> withServerCallHandler(
+      ServerCallHandler<ReqT, RespT> handler) {
+    return new ServerMethodDefinition<ReqT, RespT>(method, handler);
+  }
+}
diff --git a/core/src/main/java/io/grpc/ServerProvider.java b/core/src/main/java/io/grpc/ServerProvider.java
new file mode 100644
index 0000000..301d05b
--- /dev/null
+++ b/core/src/main/java/io/grpc/ServerProvider.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import io.grpc.ManagedChannelProvider.ProviderNotFoundException;
+import io.grpc.ServiceProviders.PriorityAccessor;
+import java.util.Collections;
+
+/**
+ * Provider of servers for transport agnostic consumption.
+ *
+ * <p>Implementations <em>should not</em> throw. If they do, it may interrupt class loading. If
+ * exceptions may reasonably occur for implementation-specific reasons, implementations should
+ * generally handle the exception gracefully and return {@code false} from {@link #isAvailable()}.
+ */
+@Internal
+public abstract class ServerProvider {
+  private static final ServerProvider provider = ServiceProviders.load(
+      ServerProvider.class,
+      Collections.<Class<?>>emptyList(),
+      ServerProvider.class.getClassLoader(),
+      new PriorityAccessor<ServerProvider>() {
+        @Override
+        public boolean isAvailable(ServerProvider provider) {
+          return provider.isAvailable();
+        }
+
+        @Override
+        public int getPriority(ServerProvider provider) {
+          return provider.priority();
+        }
+      });
+
+  /**
+   * Returns the ClassLoader-wide default server.
+   *
+   * @throws ProviderNotFoundException if no provider is available
+   */
+  public static ServerProvider provider() {
+    if (provider == null) {
+      throw new ProviderNotFoundException("No functional server found. "
+          + "Try adding a dependency on the grpc-netty or grpc-netty-shaded artifact");
+    }
+    return provider;
+  }
+
+  /**
+   * Whether this provider is available for use, taking the current environment into consideration.
+   * If {@code false}, no other methods are safe to be called.
+   */
+  protected abstract boolean isAvailable();
+
+  /**
+   * A priority, from 0 to 10 that this provider should be used, taking the current environment into
+   * consideration. 5 should be considered the default, and then tweaked based on environment
+   * detection. A priority of 0 does not imply that the provider wouldn't work; just that it should
+   * be last in line.
+   */
+  protected abstract int priority();
+
+  /**
+   * Creates a new builder with the given port.
+   */
+  protected abstract ServerBuilder<?> builderForPort(int port);
+}
diff --git a/core/src/main/java/io/grpc/ServerServiceDefinition.java b/core/src/main/java/io/grpc/ServerServiceDefinition.java
new file mode 100644
index 0000000..d0faa10
--- /dev/null
+++ b/core/src/main/java/io/grpc/ServerServiceDefinition.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Definition of a service to be exposed via a Server. */
+public final class ServerServiceDefinition {
+  /** Convenience that constructs a {@link ServiceDescriptor} simultaneously. */
+  public static Builder builder(String serviceName) {
+    return new Builder(serviceName);
+  }
+
+  public static Builder builder(ServiceDescriptor serviceDescriptor) {
+    return new Builder(serviceDescriptor);
+  }
+
+  private final ServiceDescriptor serviceDescriptor;
+  private final Map<String, ServerMethodDefinition<?, ?>> methods;
+
+  private ServerServiceDefinition(
+      ServiceDescriptor serviceDescriptor, Map<String, ServerMethodDefinition<?, ?>> methods) {
+    this.serviceDescriptor = checkNotNull(serviceDescriptor, "serviceDescriptor");
+    this.methods =
+        Collections.unmodifiableMap(new HashMap<String, ServerMethodDefinition<?, ?>>(methods));
+  }
+
+  /**
+   * The descriptor for the service.
+   */
+  public ServiceDescriptor getServiceDescriptor() {
+    return serviceDescriptor;
+  }
+
+  /**
+   * Gets all the methods of service.
+   */
+  public Collection<ServerMethodDefinition<?, ?>> getMethods() {
+    return methods.values();
+  }
+
+  /**
+   * Look up a method by its fully qualified name.
+   *
+   * @param methodName the fully qualified name without leading slash. E.g., "com.foo.Foo/Bar"
+   */
+  @Internal
+  public ServerMethodDefinition<?, ?> getMethod(String methodName) {
+    return methods.get(methodName);
+  }
+
+  /**
+   * Builder for constructing Service instances.
+   */
+  public static final class Builder {
+    private final String serviceName;
+    private final ServiceDescriptor serviceDescriptor;
+    private final Map<String, ServerMethodDefinition<?, ?>> methods =
+        new HashMap<String, ServerMethodDefinition<?, ?>>();
+
+    private Builder(String serviceName) {
+      this.serviceName = checkNotNull(serviceName, "serviceName");
+      this.serviceDescriptor = null;
+    }
+
+    private Builder(ServiceDescriptor serviceDescriptor) {
+      this.serviceDescriptor = checkNotNull(serviceDescriptor, "serviceDescriptor");
+      this.serviceName = serviceDescriptor.getName();
+    }
+
+    /**
+     * Add a method to be supported by the service.
+     *
+     * @param method the {@link MethodDescriptor} of this method.
+     * @param handler handler for incoming calls
+     */
+    public <ReqT, RespT> Builder addMethod(
+        MethodDescriptor<ReqT, RespT> method, ServerCallHandler<ReqT, RespT> handler) {
+      return addMethod(ServerMethodDefinition.create(
+          checkNotNull(method, "method must not be null"),
+          checkNotNull(handler, "handler must not be null")));
+    }
+
+    /** Add a method to be supported by the service. */
+    public <ReqT, RespT> Builder addMethod(ServerMethodDefinition<ReqT, RespT> def) {
+      MethodDescriptor<ReqT, RespT> method = def.getMethodDescriptor();
+      checkArgument(
+          serviceName.equals(MethodDescriptor.extractFullServiceName(method.getFullMethodName())),
+          "Method name should be prefixed with service name and separated with '/'. "
+                  + "Expected service name: '%s'. Actual fully qualifed method name: '%s'.",
+          serviceName, method.getFullMethodName());
+      String name = method.getFullMethodName();
+      checkState(!methods.containsKey(name), "Method by same name already registered: %s", name);
+      methods.put(name, def);
+      return this;
+    }
+
+    /**
+     * Construct new ServerServiceDefinition.
+     */
+    public ServerServiceDefinition build() {
+      ServiceDescriptor serviceDescriptor = this.serviceDescriptor;
+      if (serviceDescriptor == null) {
+        List<MethodDescriptor<?, ?>> methodDescriptors
+            = new ArrayList<MethodDescriptor<?, ?>>(methods.size());
+        for (ServerMethodDefinition<?, ?> serverMethod : methods.values()) {
+          methodDescriptors.add(serverMethod.getMethodDescriptor());
+        }
+        serviceDescriptor = new ServiceDescriptor(serviceName, methodDescriptors);
+      }
+      Map<String, ServerMethodDefinition<?, ?>> tmpMethods =
+          new HashMap<String, ServerMethodDefinition<?, ?>>(methods);
+      for (MethodDescriptor<?, ?> descriptorMethod : serviceDescriptor.getMethods()) {
+        ServerMethodDefinition<?, ?> removed = tmpMethods.remove(
+            descriptorMethod.getFullMethodName());
+        if (removed == null) {
+          throw new IllegalStateException(
+              "No method bound for descriptor entry " + descriptorMethod.getFullMethodName());
+        }
+        if (removed.getMethodDescriptor() != descriptorMethod) {
+          throw new IllegalStateException(
+              "Bound method for " + descriptorMethod.getFullMethodName()
+                  + " not same instance as method in service descriptor");
+        }
+      }
+      if (tmpMethods.size() > 0) {
+        throw new IllegalStateException(
+            "No entry in descriptor matching bound method "
+                + tmpMethods.values().iterator().next().getMethodDescriptor().getFullMethodName());
+      }
+      return new ServerServiceDefinition(serviceDescriptor, methods);
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/ServerStreamTracer.java b/core/src/main/java/io/grpc/ServerStreamTracer.java
new file mode 100644
index 0000000..d004352
--- /dev/null
+++ b/core/src/main/java/io/grpc/ServerStreamTracer.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Listens to events on a stream to collect metrics.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
+@ThreadSafe
+public abstract class ServerStreamTracer extends StreamTracer {
+  /**
+   * Called before the interceptors and the call handlers and make changes to the Context object
+   * if needed.
+   */
+  public Context filterContext(Context context) {
+    return context;
+  }
+
+  /**
+   * Called when {@link ServerCall} is created.  This is for the tracer to access information about
+   * the {@code ServerCall}.  Called after {@link #filterContext} and before the application call
+   * handler.
+   */
+  @SuppressWarnings("deprecation")
+  public void serverCallStarted(ServerCallInfo<?, ?> callInfo) {
+    serverCallStarted(ReadOnlyServerCall.create(callInfo));
+  }
+
+  /**
+   * Called when {@link ServerCall} is created.  This is for the tracer to access information about
+   * the {@code ServerCall}.  Called after {@link #filterContext} and before the application call
+   * handler.
+   *
+   * @deprecated Implement {@link #serverCallStarted(ServerCallInfo)} instead. This method will be
+   *     removed in a future release of gRPC.
+   */
+  @Deprecated
+  public void serverCallStarted(ServerCall<?, ?> call) {
+  }
+
+  public abstract static class Factory {
+    /**
+     * Creates a {@link ServerStreamTracer} for a new server stream.
+     *
+     * <p>Called right before the stream is created
+     *
+     * @param fullMethodName the fully qualified method name
+     * @param headers the received request headers.  It can be safely mutated within this method.
+     *        It should not be saved because it is not safe for read or write after the method
+     *        returns.
+     */
+    public abstract ServerStreamTracer newServerStreamTracer(
+        String fullMethodName, Metadata headers);
+  }
+
+  /**
+   * A data class with info about the started {@link ServerCall}.
+   */
+  public abstract static class ServerCallInfo<ReqT, RespT> {
+    public abstract MethodDescriptor<ReqT, RespT> getMethodDescriptor();
+
+    public abstract Attributes getAttributes();
+
+    @Nullable
+    public abstract String getAuthority();
+  }
+
+  /**
+   * This class exists solely to help transition to the {@link ServerCallInfo} based API.
+   *
+   * @deprecated Will be deleted when {@link #serverCallStarted(ServerCall)} is removed.
+   */
+  @Deprecated
+  private static final class ReadOnlyServerCall<ReqT, RespT>
+      extends ForwardingServerCall<ReqT, RespT> {
+    private final ServerCallInfo<ReqT, RespT> callInfo;
+
+    private static <ReqT, RespT> ReadOnlyServerCall<ReqT, RespT> create(
+        ServerCallInfo<ReqT, RespT> callInfo) {
+      return new ReadOnlyServerCall<ReqT, RespT>(callInfo);
+    }
+
+    private ReadOnlyServerCall(ServerCallInfo<ReqT, RespT> callInfo) {
+      this.callInfo = callInfo;
+    }
+
+    @Override
+    public MethodDescriptor<ReqT, RespT> getMethodDescriptor() {
+      return callInfo.getMethodDescriptor();
+    }
+
+    @Override
+    public Attributes getAttributes() {
+      return callInfo.getAttributes();
+    }
+
+    @Override
+    public boolean isReady() {
+      // a dummy value
+      return false;
+    }
+
+    @Override
+    public boolean isCancelled() {
+      // a dummy value
+      return false;
+    }
+
+    @Override
+    public String getAuthority() {
+      return callInfo.getAuthority();
+    }
+
+    @Override
+    protected ServerCall<ReqT, RespT> delegate() {
+      throw new UnsupportedOperationException();
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/ServerTransportFilter.java b/core/src/main/java/io/grpc/ServerTransportFilter.java
new file mode 100644
index 0000000..834a83b
--- /dev/null
+++ b/core/src/main/java/io/grpc/ServerTransportFilter.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * Listens on server transport life-cycle events, with the capability to read and/or change
+ * transport attributes.  Attributes returned by this filter will be merged into {@link
+ * ServerCall#getAttributes}.
+ *
+ * <p>Multiple filters maybe registered to a server, in which case the output of a filter is the
+ * input of the next filter.  For example, what returned by {@link #transportReady} of a filter is
+ * passed to the same method of the next filter, and the last filter's return value is the effective
+ * transport attributes.
+ *
+ * <p>{@link Grpc} defines commonly used attributes.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2132")
+public abstract class ServerTransportFilter {
+  /**
+   * Called when a transport is ready to process streams.  All necessary handshakes, e.g., TLS
+   * handshake, are done at this point.
+   *
+   * <p>Note the implementation should always inherit the passed-in attributes using {@code
+   * Attributes.newBuilder(transportAttrs)}, instead of creating one from scratch.
+   *
+   * @param transportAttrs current transport attributes
+   *
+   * @return new transport attributes. Default implementation returns the passed-in attributes
+   *         intact.
+   */
+  public Attributes transportReady(Attributes transportAttrs) {
+    return transportAttrs;
+  }
+
+  /**
+   * Called when a transport is terminated.  Default implementation is no-op.
+   *
+   * @param transportAttrs the effective transport attributes, which is what returned by {@link
+   * #transportReady} of the last executed filter.
+   */
+  public void transportTerminated(Attributes transportAttrs) {
+  }
+}
diff --git a/core/src/main/java/io/grpc/ServiceDescriptor.java b/core/src/main/java/io/grpc/ServiceDescriptor.java
new file mode 100644
index 0000000..8bb762e
--- /dev/null
+++ b/core/src/main/java/io/grpc/ServiceDescriptor.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.annotation.Nullable;
+
+/**
+ * Descriptor for a service.
+ *
+ * @since 1.0.0
+ */
+public final class ServiceDescriptor {
+
+  private final String name;
+  private final Collection<MethodDescriptor<?, ?>> methods;
+  private final Object schemaDescriptor;
+
+  /**
+   * Constructs a new Service Descriptor.  Users are encouraged to use {@link #newBuilder}
+   * instead.
+   *
+   * @param name The name of the service
+   * @param methods The methods that are part of the service
+   * @since 1.0.0
+   */
+  public ServiceDescriptor(String name, MethodDescriptor<?, ?>... methods) {
+    this(name, Arrays.asList(methods));
+  }
+
+  /**
+   * Constructs a new Service Descriptor.  Users are encouraged to use {@link #newBuilder}
+   * instead.
+   *
+   * @param name The name of the service
+   * @param methods The methods that are part of the service
+   * @since 1.0.0
+   */
+  public ServiceDescriptor(String name, Collection<MethodDescriptor<?, ?>> methods) {
+    this(newBuilder(name).addAllMethods(checkNotNull(methods, "methods")));
+  }
+
+  private ServiceDescriptor(Builder b) {
+    this.name = b.name;
+    validateMethodNames(name, b.methods);
+    this.methods = Collections.unmodifiableList(new ArrayList<MethodDescriptor<?, ?>>(b.methods));
+    this.schemaDescriptor = b.schemaDescriptor;
+  }
+
+  /**
+   * Simple name of the service. It is not an absolute path.
+   *
+   * @since 1.0.0
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * A collection of {@link MethodDescriptor} instances describing the methods exposed by the
+   * service.
+   *
+   * @since 1.0.0
+   */
+  public Collection<MethodDescriptor<?, ?>> getMethods() {
+    return methods;
+  }
+
+  /**
+   * Returns the schema descriptor for this service.  A schema descriptor is an object that is not
+   * used by gRPC core but includes information related to the service.  The type of the object
+   * is specific to the consumer, so both the code setting the schema descriptor and the code
+   * calling {@link #getSchemaDescriptor()} must coordinate.  For example, protobuf generated code
+   * sets this value, in order to be consumed by the server reflection service.  See also:
+   * {@code io.grpc.protobuf.ProtoFileDescriptorSupplier}.
+   *
+   * @since 1.1.0
+   */
+  @Nullable
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2222")
+  public Object getSchemaDescriptor() {
+    return schemaDescriptor;
+  }
+
+  static void validateMethodNames(String serviceName, Collection<MethodDescriptor<?, ?>> methods) {
+    Set<String> allNames = new HashSet<String>(methods.size());
+    for (MethodDescriptor<?, ?> method : methods) {
+      checkNotNull(method, "method");
+      String methodServiceName =
+          MethodDescriptor.extractFullServiceName(method.getFullMethodName());
+      checkArgument(serviceName.equals(methodServiceName),
+          "service names %s != %s", methodServiceName, serviceName);
+      checkArgument(allNames.add(method.getFullMethodName()),
+          "duplicate name %s", method.getFullMethodName());
+    }
+  }
+
+  /**
+   * Creates a new builder for a {@link ServiceDescriptor}.
+   *
+   * @since 1.1.0
+   */
+  public static Builder newBuilder(String name) {
+    return new Builder(name);
+  }
+
+  /**
+   * A builder for a {@link ServiceDescriptor}.
+   *
+   * @since 1.1.0
+   */
+  public static final class Builder {
+    private Builder(String name) {
+      setName(name);
+    }
+
+    private String name;
+    private List<MethodDescriptor<?, ?>> methods = new ArrayList<MethodDescriptor<?, ?>>();
+    private Object schemaDescriptor;
+
+    /**
+     * Sets the name.  This should be non-{@code null}.
+     *
+     * @param name The name of the service.
+     * @return this builder.
+     * @since 1.1.0
+     */
+    @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2666")
+    public Builder setName(String name) {
+      this.name = checkNotNull(name, "name");
+      return this;
+    }
+
+    /**
+     * Adds a method to this service.  This should be non-{@code null}.
+     *
+     * @param method the method to add to the descriptor.
+     * @return this builder.
+     * @since 1.1.0
+     */
+    public Builder addMethod(MethodDescriptor<?, ?> method) {
+      methods.add(checkNotNull(method, "method"));
+      return this;
+    }
+
+    /**
+     * Currently not exposed.  Bulk adds methods to this builder.
+     *
+     * @param methods the methods to add.
+     * @return this builder.
+     */
+    private Builder addAllMethods(Collection<MethodDescriptor<?, ?>> methods) {
+      this.methods.addAll(methods);
+      return this;
+    }
+
+    /**
+     * Sets the schema descriptor for this builder.  A schema descriptor is an object that is not
+     * used by gRPC core but includes information related to the service.  The type of the object
+     * is specific to the consumer, so both the code calling this and the code calling
+     * {@link ServiceDescriptor#getSchemaDescriptor()} must coordinate.  For example, protobuf
+     * generated code sets this value, in order to be consumed by the server reflection service.
+     *
+     * @param schemaDescriptor an object that describes the service structure.  Should be immutable.
+     * @return this builder.
+     * @since 1.1.0
+     */
+    public Builder setSchemaDescriptor(@Nullable Object schemaDescriptor) {
+      this.schemaDescriptor = schemaDescriptor;
+      return this;
+    }
+
+    /**
+     * Constructs a new {@link ServiceDescriptor}.  {@link #setName} should have been called with a
+     * non-{@code null} value before calling this.
+     *
+     * @return a new ServiceDescriptor
+     * @since 1.1.0
+     */
+    public ServiceDescriptor build() {
+      return new ServiceDescriptor(this);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("name", name)
+        .add("schemaDescriptor", schemaDescriptor)
+        .add("methods", methods)
+        .omitNullValues()
+        .toString();
+  }
+}
\ No newline at end of file
diff --git a/core/src/main/java/io/grpc/ServiceProviders.java b/core/src/main/java/io/grpc/ServiceProviders.java
new file mode 100644
index 0000000..f776c49
--- /dev/null
+++ b/core/src/main/java/io/grpc/ServiceProviders.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+
+final class ServiceProviders {
+  private ServiceProviders() {
+    // do not instantiate
+  }
+
+  /**
+   * If this is not Android, returns the highest priority implementation of the class via
+   * {@link ServiceLoader}.
+   * If this is Android, returns an instance of the highest priority class in {@code hardcoded}.
+   */
+  public static <T> T load(
+      Class<T> klass,
+      Iterable<Class<?>> hardcoded,
+      ClassLoader cl,
+      PriorityAccessor<T> priorityAccessor) {
+    List<T> candidates = loadAll(klass, hardcoded, cl, priorityAccessor);
+    if (candidates.isEmpty()) {
+      return null;
+    }
+    return candidates.get(0);
+  }
+
+  /**
+   * If this is not Android, returns all available implementations discovered via
+   * {@link ServiceLoader}.
+   * If this is Android, returns all available implementations in {@code hardcoded}.
+   * The list is sorted in descending priority order.
+   */
+  public static <T> List<T> loadAll(
+      Class<T> klass,
+      Iterable<Class<?>> hardcoded,
+      ClassLoader cl,
+      final PriorityAccessor<T> priorityAccessor) {
+    Iterable<T> candidates;
+    if (isAndroid(cl)) {
+      candidates = getCandidatesViaHardCoded(klass, hardcoded);
+    } else {
+      candidates = getCandidatesViaServiceLoader(klass, cl);
+    }
+    List<T> list = new ArrayList<>();
+    for (T current: candidates) {
+      if (!priorityAccessor.isAvailable(current)) {
+        continue;
+      }
+      list.add(current);
+    }
+
+    // Sort descending based on priority.
+    Collections.sort(list, Collections.reverseOrder(new Comparator<T>() {
+      @Override
+      public int compare(T f1, T f2) {
+        return priorityAccessor.getPriority(f1) - priorityAccessor.getPriority(f2);
+      }
+    }));
+    return Collections.unmodifiableList(list);
+  }
+
+  /**
+   * Returns true if the {@link ClassLoader} is for android.
+   */
+  static boolean isAndroid(ClassLoader cl) {
+    try {
+      // Specify a class loader instead of null because we may be running under Robolectric
+      Class.forName("android.app.Application", /*initialize=*/ false, cl);
+      return true;
+    } catch (Exception e) {
+      // If Application isn't loaded, it might as well not be Android.
+      return false;
+    }
+  }
+
+  /**
+   * Loads service providers for the {@code klass} service using {@link ServiceLoader}.
+   */
+  @VisibleForTesting
+  public static <T> Iterable<T> getCandidatesViaServiceLoader(Class<T> klass, ClassLoader cl) {
+    Iterable<T> i = ServiceLoader.load(klass, cl);
+    // Attempt to load using the context class loader and ServiceLoader.
+    // This allows frameworks like http://aries.apache.org/modules/spi-fly.html to plug in.
+    if (!i.iterator().hasNext()) {
+      i = ServiceLoader.load(klass);
+    }
+    return i;
+  }
+
+  /**
+   * Load providers from a hard-coded list. This avoids using getResource(), which has performance
+   * problems on Android (see https://github.com/grpc/grpc-java/issues/2037).
+   */
+  @VisibleForTesting
+  static <T> Iterable<T> getCandidatesViaHardCoded(Class<T> klass, Iterable<Class<?>> hardcoded) {
+    List<T> list = new ArrayList<>();
+    for (Class<?> candidate : hardcoded) {
+      list.add(create(klass, candidate));
+    }
+    return list;
+  }
+
+  @VisibleForTesting
+  static <T> T create(Class<T> klass, Class<?> rawClass) {
+    try {
+      return rawClass.asSubclass(klass).getConstructor().newInstance();
+    } catch (Throwable t) {
+      throw new ServiceConfigurationError(
+          String.format("Provider %s could not be instantiated %s", rawClass.getName(), t), t);
+    }
+  }
+
+  /**
+   * An interface that allows us to get priority information about a provider.
+   */
+  public interface PriorityAccessor<T> {
+    /**
+     * Checks this provider is available for use, taking the current environment into consideration.
+     * If {@code false}, no other methods are safe to be called.
+     */
+    boolean isAvailable(T provider);
+
+    /**
+     * A priority, from 0 to 10 that this provider should be used, taking the current environment
+     * into consideration. 5 should be considered the default, and then tweaked based on environment
+     * detection. A priority of 0 does not imply that the provider wouldn't work; just that it
+     * should be last in line.
+     */
+    int getPriority(T provider);
+  }
+}
diff --git a/core/src/main/java/io/grpc/Status.java b/core/src/main/java/io/grpc/Status.java
new file mode 100644
index 0000000..27e13f0
--- /dev/null
+++ b/core/src/main/java/io/grpc/Status.java
@@ -0,0 +1,668 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Charsets.US_ASCII;
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Throwables.getStackTraceAsString;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import io.grpc.Metadata.TrustedAsciiMarshaller;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.TreeMap;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+
+/**
+ * Defines the status of an operation by providing a standard {@link Code} in conjunction with an
+ * optional descriptive message. Instances of {@code Status} are created by starting with the
+ * template for the appropriate {@link Status.Code} and supplementing it with additional
+ * information: {@code Status.NOT_FOUND.withDescription("Could not find 'important_file.txt'");}
+ *
+ * <p>For clients, every remote call will return a status on completion. In the case of errors this
+ * status may be propagated to blocking stubs as a {@link RuntimeException} or to a listener as an
+ * explicit parameter.
+ *
+ * <p>Similarly servers can report a status by throwing {@link StatusRuntimeException}
+ * or by passing the status to a callback.
+ *
+ * <p>Utility functions are provided to convert a status to an exception and to extract them
+ * back out.
+ */
+@Immutable
+public final class Status {
+
+  /**
+   * The set of canonical status codes. If new codes are added over time they must choose
+   * a numerical value that does not collide with any previously used value.
+   */
+  public enum Code {
+    /**
+     * The operation completed successfully.
+     */
+    OK(0),
+
+    /**
+     * The operation was cancelled (typically by the caller).
+     */
+    CANCELLED(1),
+
+    /**
+     * Unknown error.  An example of where this error may be returned is
+     * if a Status value received from another address space belongs to
+     * an error-space that is not known in this address space.  Also
+     * errors raised by APIs that do not return enough error information
+     * may be converted to this error.
+     */
+    UNKNOWN(2),
+
+    /**
+     * Client specified an invalid argument.  Note that this differs
+     * from FAILED_PRECONDITION.  INVALID_ARGUMENT indicates arguments
+     * that are problematic regardless of the state of the system
+     * (e.g., a malformed file name).
+     */
+    INVALID_ARGUMENT(3),
+
+    /**
+     * Deadline expired before operation could complete.  For operations
+     * that change the state of the system, this error may be returned
+     * even if the operation has completed successfully.  For example, a
+     * successful response from a server could have been delayed long
+     * enough for the deadline to expire.
+     */
+    DEADLINE_EXCEEDED(4),
+
+    /**
+     * Some requested entity (e.g., file or directory) was not found.
+     */
+    NOT_FOUND(5),
+
+    /**
+     * Some entity that we attempted to create (e.g., file or directory) already exists.
+     */
+    ALREADY_EXISTS(6),
+
+    /**
+     * The caller does not have permission to execute the specified
+     * operation.  PERMISSION_DENIED must not be used for rejections
+     * caused by exhausting some resource (use RESOURCE_EXHAUSTED
+     * instead for those errors).  PERMISSION_DENIED must not be
+     * used if the caller cannot be identified (use UNAUTHENTICATED
+     * instead for those errors).
+     */
+    PERMISSION_DENIED(7),
+
+    /**
+     * Some resource has been exhausted, perhaps a per-user quota, or
+     * perhaps the entire file system is out of space.
+     */
+    RESOURCE_EXHAUSTED(8),
+
+    /**
+     * Operation was rejected because the system is not in a state
+     * required for the operation's execution.  For example, directory
+     * to be deleted may be non-empty, an rmdir operation is applied to
+     * a non-directory, etc.
+     *
+     * <p>A litmus test that may help a service implementor in deciding
+     * between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
+     * (a) Use UNAVAILABLE if the client can retry just the failing call.
+     * (b) Use ABORTED if the client should retry at a higher-level
+     * (e.g., restarting a read-modify-write sequence).
+     * (c) Use FAILED_PRECONDITION if the client should not retry until
+     * the system state has been explicitly fixed.  E.g., if an "rmdir"
+     * fails because the directory is non-empty, FAILED_PRECONDITION
+     * should be returned since the client should not retry unless
+     * they have first fixed up the directory by deleting files from it.
+     */
+    FAILED_PRECONDITION(9),
+
+    /**
+     * The operation was aborted, typically due to a concurrency issue
+     * like sequencer check failures, transaction aborts, etc.
+     *
+     * <p>See litmus test above for deciding between FAILED_PRECONDITION,
+     * ABORTED, and UNAVAILABLE.
+     */
+    ABORTED(10),
+
+    /**
+     * Operation was attempted past the valid range.  E.g., seeking or
+     * reading past end of file.
+     *
+     * <p>Unlike INVALID_ARGUMENT, this error indicates a problem that may
+     * be fixed if the system state changes. For example, a 32-bit file
+     * system will generate INVALID_ARGUMENT if asked to read at an
+     * offset that is not in the range [0,2^32-1], but it will generate
+     * OUT_OF_RANGE if asked to read from an offset past the current
+     * file size.
+     *
+     * <p>There is a fair bit of overlap between FAILED_PRECONDITION and OUT_OF_RANGE.
+     * We recommend using OUT_OF_RANGE (the more specific error) when it applies
+     * so that callers who are iterating through
+     * a space can easily look for an OUT_OF_RANGE error to detect when they are done.
+     */
+    OUT_OF_RANGE(11),
+
+    /**
+     * Operation is not implemented or not supported/enabled in this service.
+     */
+    UNIMPLEMENTED(12),
+
+    /**
+     * Internal errors.  Means some invariants expected by underlying
+     * system has been broken.  If you see one of these errors,
+     * something is very broken.
+     */
+    INTERNAL(13),
+
+    /**
+     * The service is currently unavailable.  This is a most likely a
+     * transient condition and may be corrected by retrying with
+     * a backoff.
+     *
+     * <p>See litmus test above for deciding between FAILED_PRECONDITION,
+     * ABORTED, and UNAVAILABLE.
+     */
+    UNAVAILABLE(14),
+
+    /**
+     * Unrecoverable data loss or corruption.
+     */
+    DATA_LOSS(15),
+
+    /**
+     * The request does not have valid authentication credentials for the
+     * operation.
+     */
+    UNAUTHENTICATED(16);
+
+    private final int value;
+    @SuppressWarnings("ImmutableEnumChecker") // we make sure the byte[] can't be modified
+    private final byte[] valueAscii;
+
+    private Code(int value) {
+      this.value = value;
+      this.valueAscii = Integer.toString(value).getBytes(US_ASCII);
+    }
+
+    /**
+     * The numerical value of the code.
+     */
+    public int value() {
+      return value;
+    }
+
+    /**
+     * Returns a {@link Status} object corresponding to this status code.
+     */
+    public Status toStatus() {
+      return STATUS_LIST.get(value);
+    }
+
+    private byte[] valueAscii() {
+      return valueAscii;
+    }
+  }
+
+  // Create the canonical list of Status instances indexed by their code values.
+  private static final List<Status> STATUS_LIST = buildStatusList();
+
+  private static List<Status> buildStatusList() {
+    TreeMap<Integer, Status> canonicalizer = new TreeMap<Integer, Status>();
+    for (Code code : Code.values()) {
+      Status replaced = canonicalizer.put(code.value(), new Status(code));
+      if (replaced != null) {
+        throw new IllegalStateException("Code value duplication between "
+            + replaced.getCode().name() + " & " + code.name());
+      }
+    }
+    return Collections.unmodifiableList(new ArrayList<>(canonicalizer.values()));
+  }
+
+  // A pseudo-enum of Status instances mapped 1:1 with values in Code. This simplifies construction
+  // patterns for derived instances of Status.
+  /** The operation completed successfully. */
+  public static final Status OK = Code.OK.toStatus();
+  /** The operation was cancelled (typically by the caller). */
+  public static final Status CANCELLED = Code.CANCELLED.toStatus();
+  /** Unknown error. See {@link Code#UNKNOWN}. */
+  public static final Status UNKNOWN = Code.UNKNOWN.toStatus();
+  /** Client specified an invalid argument. See {@link Code#INVALID_ARGUMENT}. */
+  public static final Status INVALID_ARGUMENT = Code.INVALID_ARGUMENT.toStatus();
+  /** Deadline expired before operation could complete. See {@link Code#DEADLINE_EXCEEDED}. */
+  public static final Status DEADLINE_EXCEEDED = Code.DEADLINE_EXCEEDED.toStatus();
+  /** Some requested entity (e.g., file or directory) was not found. */
+  public static final Status NOT_FOUND = Code.NOT_FOUND.toStatus();
+  /** Some entity that we attempted to create (e.g., file or directory) already exists. */
+  public static final Status ALREADY_EXISTS = Code.ALREADY_EXISTS.toStatus();
+  /**
+   * The caller does not have permission to execute the specified operation. See {@link
+   * Code#PERMISSION_DENIED}.
+   */
+  public static final Status PERMISSION_DENIED = Code.PERMISSION_DENIED.toStatus();
+  /** The request does not have valid authentication credentials for the operation. */
+  public static final Status UNAUTHENTICATED = Code.UNAUTHENTICATED.toStatus();
+  /**
+   * Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system
+   * is out of space.
+   */
+  public static final Status RESOURCE_EXHAUSTED = Code.RESOURCE_EXHAUSTED.toStatus();
+  /**
+   * Operation was rejected because the system is not in a state required for the operation's
+   * execution. See {@link Code#FAILED_PRECONDITION}.
+   */
+  public static final Status FAILED_PRECONDITION =
+      Code.FAILED_PRECONDITION.toStatus();
+  /**
+   * The operation was aborted, typically due to a concurrency issue like sequencer check failures,
+   * transaction aborts, etc. See {@link Code#ABORTED}.
+   */
+  public static final Status ABORTED = Code.ABORTED.toStatus();
+  /** Operation was attempted past the valid range. See {@link Code#OUT_OF_RANGE}. */
+  public static final Status OUT_OF_RANGE = Code.OUT_OF_RANGE.toStatus();
+  /** Operation is not implemented or not supported/enabled in this service. */
+  public static final Status UNIMPLEMENTED = Code.UNIMPLEMENTED.toStatus();
+  /** Internal errors. See {@link Code#INTERNAL}. */
+  public static final Status INTERNAL = Code.INTERNAL.toStatus();
+  /** The service is currently unavailable. See {@link Code#UNAVAILABLE}. */
+  public static final Status UNAVAILABLE = Code.UNAVAILABLE.toStatus();
+  /** Unrecoverable data loss or corruption. */
+  public static final Status DATA_LOSS = Code.DATA_LOSS.toStatus();
+
+  /**
+   * Return a {@link Status} given a canonical error {@link Code} value.
+   */
+  public static Status fromCodeValue(int codeValue) {
+    if (codeValue < 0 || codeValue > STATUS_LIST.size()) {
+      return UNKNOWN.withDescription("Unknown code " + codeValue);
+    } else {
+      return STATUS_LIST.get(codeValue);
+    }
+  }
+
+  private static Status fromCodeValue(byte[] asciiCodeValue) {
+    if (asciiCodeValue.length == 1 && asciiCodeValue[0] == '0') {
+      return Status.OK;
+    }
+    return fromCodeValueSlow(asciiCodeValue);
+  }
+
+  @SuppressWarnings("fallthrough")
+  private static Status fromCodeValueSlow(byte[] asciiCodeValue) {
+    int index = 0;
+    int codeValue = 0;
+    switch (asciiCodeValue.length) {
+      case 2:
+        if (asciiCodeValue[index] < '0' || asciiCodeValue[index] > '9') {
+          break;
+        }
+        codeValue += (asciiCodeValue[index++] - '0') * 10;
+        // fall through
+      case 1:
+        if (asciiCodeValue[index] < '0' || asciiCodeValue[index] > '9') {
+          break;
+        }
+        codeValue += asciiCodeValue[index] - '0';
+        if (codeValue < STATUS_LIST.size()) {
+          return STATUS_LIST.get(codeValue);
+        }
+        break;
+      default:
+        break;
+    }
+    return UNKNOWN.withDescription("Unknown code " + new String(asciiCodeValue, US_ASCII));
+  }
+
+  /**
+   * Return a {@link Status} given a canonical error {@link Code} object.
+   */
+  public static Status fromCode(Code code) {
+    return code.toStatus();
+  }
+
+  /**
+   * Key to bind status code to trailing metadata.
+   */
+  static final Metadata.Key<Status> CODE_KEY
+      = Metadata.Key.of("grpc-status", false /* not pseudo */, new StatusCodeMarshaller());
+
+  /**
+   * Marshals status messages for ({@link #MESSAGE_KEY}.  gRPC does not use binary coding of
+   * status messages by default, which makes sending arbitrary strings difficult.  This marshaller
+   * uses ASCII printable characters by default, and percent encodes (e.g. %0A) all non ASCII bytes.
+   * This leads to normal text being mostly readable (especially useful for debugging), and special
+   * text still being sent.
+   *
+   * <p>By default, the HTTP spec says that header values must be encoded using a strict subset of
+   * ASCII (See RFC 7230 section 3.2.6).  HTTP/2 HPACK allows use of arbitrary binary headers, but
+   * we do not use them for interoperating with existing HTTP/1.1 code.  Since the grpc-message
+   * is encoded to such a header, it needs to not use forbidden characters.
+   *
+   * <p>This marshaller works by converting the passed in string into UTF-8, checking to see if
+   * each individual byte is an allowable byte, and then either percent encoding or passing it
+   * through.  When percent encoding, the byte is converted into hexadecimal notation with a '%'
+   * prepended.
+   *
+   * <p>When unmarshalling, bytes are passed through unless they match the "%XX" pattern.  If they
+   * do match, the unmarshaller attempts to convert them back into their original UTF-8 byte
+   * sequence.  After the input header bytes are converted into UTF-8 bytes, the new byte array is
+   * reinterpretted back as a string.
+   */
+  private static final TrustedAsciiMarshaller<String> STATUS_MESSAGE_MARSHALLER =
+      new StatusMessageMarshaller();
+
+  /**
+   * Key to bind status message to trailing metadata.
+   */
+  static final Metadata.Key<String> MESSAGE_KEY =
+      Metadata.Key.of("grpc-message", false /* not pseudo */, STATUS_MESSAGE_MARSHALLER);
+
+  /**
+   * Extract an error {@link Status} from the causal chain of a {@link Throwable}.
+   * If no status can be found, a status is created with {@link Code#UNKNOWN} as its code and
+   * {@code t} as its cause.
+   *
+   * @return non-{@code null} status
+   */
+  public static Status fromThrowable(Throwable t) {
+    Throwable cause = checkNotNull(t, "t");
+    while (cause != null) {
+      if (cause instanceof StatusException) {
+        return ((StatusException) cause).getStatus();
+      } else if (cause instanceof StatusRuntimeException) {
+        return ((StatusRuntimeException) cause).getStatus();
+      }
+      cause = cause.getCause();
+    }
+    // Couldn't find a cause with a Status
+    return UNKNOWN.withCause(t);
+  }
+
+  /**
+   * Extract an error trailers from the causal chain of a {@link Throwable}.
+   *
+   * @return the trailers or {@code null} if not found.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4683")
+  public static Metadata trailersFromThrowable(Throwable t) {
+    Throwable cause = checkNotNull(t, "t");
+    while (cause != null) {
+      if (cause instanceof StatusException) {
+        return ((StatusException) cause).getTrailers();
+      } else if (cause instanceof StatusRuntimeException) {
+        return ((StatusRuntimeException) cause).getTrailers();
+      }
+      cause = cause.getCause();
+    }
+    return null;
+  }
+
+  static String formatThrowableMessage(Status status) {
+    if (status.description == null) {
+      return status.code.toString();
+    } else {
+      return status.code + ": " + status.description;
+    }
+  }
+
+  private final Code code;
+  private final String description;
+  private final Throwable cause;
+
+  private Status(Code code) {
+    this(code, null, null);
+  }
+
+  private Status(Code code, @Nullable String description, @Nullable Throwable cause) {
+    this.code = checkNotNull(code, "code");
+    this.description = description;
+    this.cause = cause;
+  }
+
+  /**
+   * Create a derived instance of {@link Status} with the given cause.
+   * However, the cause is not transmitted from server to client.
+   */
+  public Status withCause(Throwable cause) {
+    if (Objects.equal(this.cause, cause)) {
+      return this;
+    }
+    return new Status(this.code, this.description, cause);
+  }
+
+  /**
+   * Create a derived instance of {@link Status} with the given description.  Leading and trailing
+   * whitespace may be removed; this may change in the future.
+   */
+  public Status withDescription(String description) {
+    if (Objects.equal(this.description, description)) {
+      return this;
+    }
+    return new Status(this.code, description, this.cause);
+  }
+
+  /**
+   * Create a derived instance of {@link Status} augmenting the current description with
+   * additional detail.  Leading and trailing whitespace may be removed; this may change in the
+   * future.
+   */
+  public Status augmentDescription(String additionalDetail) {
+    if (additionalDetail == null) {
+      return this;
+    } else if (this.description == null) {
+      return new Status(this.code, additionalDetail, this.cause);
+    } else {
+      return new Status(this.code, this.description + "\n" + additionalDetail, this.cause);
+    }
+  }
+
+  /**
+   * The canonical status code.
+   */
+  public Code getCode() {
+    return code;
+  }
+
+  /**
+   * A description of this status for human consumption.
+   */
+  @Nullable
+  public String getDescription() {
+    return description;
+  }
+
+  /**
+   * The underlying cause of an error.
+   * Note that the cause is not transmitted from server to client.
+   */
+  @Nullable
+  public Throwable getCause() {
+    return cause;
+  }
+
+  /**
+   * Is this status OK, i.e., not an error.
+   */
+  public boolean isOk() {
+    return Code.OK == code;
+  }
+
+  /**
+   * Convert this {@link Status} to a {@link RuntimeException}. Use {@link #fromThrowable}
+   * to recover this {@link Status} instance when the returned exception is in the causal chain.
+   */
+  public StatusRuntimeException asRuntimeException() {
+    return new StatusRuntimeException(this);
+  }
+
+  /**
+   * Same as {@link #asRuntimeException()} but includes the provided trailers in the returned
+   * exception.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4683")
+  public StatusRuntimeException asRuntimeException(@Nullable Metadata trailers) {
+    return new StatusRuntimeException(this, trailers);
+  }
+
+  /**
+   * Convert this {@link Status} to an {@link Exception}. Use {@link #fromThrowable}
+   * to recover this {@link Status} instance when the returned exception is in the causal chain.
+   */
+  public StatusException asException() {
+    return new StatusException(this);
+  }
+
+  /**
+   * Same as {@link #asException()} but includes the provided trailers in the returned exception.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4683")
+  public StatusException asException(@Nullable Metadata trailers) {
+    return new StatusException(this, trailers);
+  }
+
+  /** A string representation of the status useful for debugging. */
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("code", code.name())
+        .add("description", description)
+        .add("cause", cause != null ? getStackTraceAsString(cause) : cause)
+        .toString();
+  }
+
+  private static final class StatusCodeMarshaller implements TrustedAsciiMarshaller<Status> {
+    @Override
+    public byte[] toAsciiString(Status status) {
+      return status.getCode().valueAscii();
+    }
+
+    @Override
+    public Status parseAsciiString(byte[] serialized) {
+      return fromCodeValue(serialized);
+    }
+  }
+
+  private static final class StatusMessageMarshaller implements TrustedAsciiMarshaller<String> {
+
+    private static final byte[] HEX =
+        {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
+
+    @Override
+    public byte[] toAsciiString(String value) {
+      byte[] valueBytes = value.getBytes(UTF_8);
+      for (int i = 0; i < valueBytes.length; i++) {
+        byte b = valueBytes[i];
+        // If there are only non escaping characters, skip the slow path.
+        if (isEscapingChar(b)) {
+          return toAsciiStringSlow(valueBytes, i);
+        }
+      }
+      return valueBytes;
+    }
+
+    private static boolean isEscapingChar(byte b) {
+      return b < ' ' || b >= '~' || b == '%';
+    }
+
+    /**
+     * @param valueBytes the UTF-8 bytes
+     * @param ri The reader index, pointed at the first byte that needs escaping.
+     */
+    private static byte[] toAsciiStringSlow(byte[] valueBytes, int ri) {
+      byte[] escapedBytes = new byte[ri + (valueBytes.length - ri) * 3];
+      // copy over the good bytes
+      if (ri != 0) {
+        System.arraycopy(valueBytes, 0, escapedBytes, 0, ri);
+      }
+      int wi = ri;
+      for (; ri < valueBytes.length; ri++) {
+        byte b = valueBytes[ri];
+        // Manually implement URL encoding, per the gRPC spec.
+        if (isEscapingChar(b)) {
+          escapedBytes[wi] = '%';
+          escapedBytes[wi + 1] = HEX[(b >> 4) & 0xF];
+          escapedBytes[wi + 2] = HEX[b & 0xF];
+          wi += 3;
+          continue;
+        }
+        escapedBytes[wi++] = b;
+      }
+      byte[] dest = new byte[wi];
+      System.arraycopy(escapedBytes, 0, dest, 0, wi);
+
+      return dest;
+    }
+
+    @SuppressWarnings("deprecation") // Use fast but deprecated String ctor
+    @Override
+    public String parseAsciiString(byte[] value) {
+      for (int i = 0; i < value.length; i++) {
+        byte b = value[i];
+        if (b < ' ' || b >= '~' || (b == '%' && i + 2 < value.length)) {
+          return parseAsciiStringSlow(value);
+        }
+      }
+      return new String(value, 0);
+    }
+
+    private static String parseAsciiStringSlow(byte[] value) {
+      ByteBuffer buf = ByteBuffer.allocate(value.length);
+      for (int i = 0; i < value.length;) {
+        if (value[i] == '%' && i + 2 < value.length) {
+          try {
+            buf.put((byte)Integer.parseInt(new String(value, i + 1, 2, US_ASCII), 16));
+            i += 3;
+            continue;
+          } catch (NumberFormatException e) {
+            // ignore, fall through, just push the bytes.
+          }
+        }
+        buf.put(value[i]);
+        i += 1;
+      }
+      return new String(buf.array(), 0, buf.position(), UTF_8);
+    }
+  }
+
+  /**
+   * Equality on Statuses is not well defined.  Instead, do comparison based on their Code with
+   * {@link #getCode}.  The description and cause of the Status are unlikely to be stable, and
+   * additional fields may be added to Status in the future.
+   */
+  @Override
+  public boolean equals(Object obj) {
+    return super.equals(obj);
+  }
+
+  /**
+   * Hash codes on Statuses are not well defined.
+   *
+   * @see #equals
+   */
+  @Override
+  public int hashCode() {
+    return super.hashCode();
+  }
+}
diff --git a/core/src/main/java/io/grpc/StatusException.java b/core/src/main/java/io/grpc/StatusException.java
new file mode 100644
index 0000000..e89ac16
--- /dev/null
+++ b/core/src/main/java/io/grpc/StatusException.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import javax.annotation.Nullable;
+
+/**
+ * {@link Status} in Exception form, for propagating Status information via exceptions. This is
+ * semantically equivalent to {@link StatusRuntimeException}, except for usage in APIs that promote
+ * checked exceptions. gRPC's stubs favor {@code StatusRuntimeException}.
+ */
+public class StatusException extends Exception {
+  private static final long serialVersionUID = -660954903976144640L;
+  private final Status status;
+  private final Metadata trailers;
+  private final boolean fillInStackTrace;
+
+  /**
+   * Constructs an exception with both a status.  See also {@link Status#asException()}.
+   *
+   * @since 1.0.0
+   */
+  public StatusException(Status status) {
+    this(status, null);
+  }
+
+  /**
+   * Constructs an exception with both a status and trailers.  See also
+   * {@link Status#asException(Metadata)}.
+   *
+   * @since 1.0.0
+   */
+  public StatusException(Status status, @Nullable Metadata trailers) {
+    this(status, trailers, /*fillInStackTrace=*/ true);
+  }
+
+  StatusException(Status status, @Nullable Metadata trailers, boolean fillInStackTrace) {
+    super(Status.formatThrowableMessage(status), status.getCause());
+    this.status = status;
+    this.trailers = trailers;
+    this.fillInStackTrace = fillInStackTrace;
+    fillInStackTrace();
+  }
+
+  @Override
+  public synchronized Throwable fillInStackTrace() {
+    // Let's observe final variables in two states!  This works because Throwable will invoke this
+    // method before fillInStackTrace is set, thus doing nothing.  After the constructor has set
+    // fillInStackTrace, this method will properly fill it in.  Additionally, sub classes may call
+    // this normally, because fillInStackTrace will either be set, or this method will be
+    // overriden.
+    return fillInStackTrace ? super.fillInStackTrace() : this;
+  }
+
+  /**
+   * Returns the status code as a {@link Status} object.
+   *
+   * @since 1.0.0
+   */
+  public final Status getStatus() {
+    return status;
+  }
+
+  /**
+   * Returns the received trailers.
+   *
+   * @since 1.0.0
+   */
+  public final Metadata getTrailers() {
+    return trailers;
+  }
+}
diff --git a/core/src/main/java/io/grpc/StatusRuntimeException.java b/core/src/main/java/io/grpc/StatusRuntimeException.java
new file mode 100644
index 0000000..27e2e61
--- /dev/null
+++ b/core/src/main/java/io/grpc/StatusRuntimeException.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import javax.annotation.Nullable;
+
+/**
+ * {@link Status} in RuntimeException form, for propagating Status information via exceptions.
+ *
+ * @see StatusException
+ */
+public class StatusRuntimeException extends RuntimeException {
+
+  private static final long serialVersionUID = 1950934672280720624L;
+  private final Status status;
+  private final Metadata trailers;
+
+  private final boolean fillInStackTrace;
+
+  /**
+   * Constructs the exception with both a status.  See also {@link Status#asException()}.
+   *
+   * @since 1.0.0
+   */
+  public StatusRuntimeException(Status status) {
+    this(status, null);
+  }
+
+  /**
+   * Constructs the exception with both a status and trailers.  See also
+   * {@link Status#asException(Metadata)}.
+   *
+   * @since 1.0.0
+   */
+  public StatusRuntimeException(Status status, @Nullable Metadata trailers) {
+    this(status, trailers, /*fillInStackTrace=*/ true);
+  }
+
+  StatusRuntimeException(Status status, @Nullable Metadata trailers, boolean fillInStackTrace) {
+    super(Status.formatThrowableMessage(status), status.getCause());
+    this.status = status;
+    this.trailers = trailers;
+    this.fillInStackTrace = fillInStackTrace;
+    fillInStackTrace();
+  }
+
+  @Override
+  public synchronized Throwable fillInStackTrace() {
+    // Let's observe final variables in two states!  This works because Throwable will invoke this
+    // method before fillInStackTrace is set, thus doing nothing.  After the constructor has set
+    // fillInStackTrace, this method will properly fill it in.  Additionally, sub classes may call
+    // this normally, because fillInStackTrace will either be set, or this method will be
+    // overriden.
+    return fillInStackTrace ? super.fillInStackTrace() : this;
+  }
+
+  /**
+   * Returns the status code as a {@link Status} object.
+   *
+   * @since 1.0.0
+   */
+  public final Status getStatus() {
+    return status;
+  }
+
+  /**
+   * Returns the received trailers.
+   *
+   * @since 1.0.0
+   */
+  public final Metadata getTrailers() {
+    return trailers;
+  }
+}
diff --git a/core/src/main/java/io/grpc/StreamTracer.java b/core/src/main/java/io/grpc/StreamTracer.java
new file mode 100644
index 0000000..66b3de8
--- /dev/null
+++ b/core/src/main/java/io/grpc/StreamTracer.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Listens to events on a stream to collect metrics.
+ *
+ * <p>DO NOT MOCK: Use TestStreamTracer. Mocks are not thread-safe
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
+@ThreadSafe
+public abstract class StreamTracer {
+  /**
+   * Stream is closed.  This will be called exactly once.
+   */
+  public void streamClosed(Status status) {
+  }
+
+  /**
+   * An outbound message has been passed to the stream.  This is called as soon as the stream knows
+   * about the message, but doesn't have further guarantee such as whether the message is serialized
+   * or not.
+   *
+   * @param seqNo the sequential number of the message within the stream, starting from 0.  It can
+   *              be used to correlate with {@link #outboundMessageSent} for the same message.
+   */
+  public void outboundMessage(int seqNo) {
+  }
+
+  /**
+   * An inbound message has been received by the stream.  This is called as soon as the stream knows
+   * about the message, but doesn't have further guarantee such as whether the message is
+   * deserialized or not.
+   *
+   * @param seqNo the sequential number of the message within the stream, starting from 0.  It can
+   *              be used to correlate with {@link #inboundMessageRead} for the same message.
+   */
+  public void inboundMessage(int seqNo) {
+  }
+
+  /**
+   * An outbound message has been serialized and sent to the transport.
+   *
+   * @param seqNo the sequential number of the message within the stream, starting from 0.  It can
+   *              be used to correlate with {@link #outboundMessage(int)} for the same message.
+   * @param optionalWireSize the wire size of the message. -1 if unknown
+   * @param optionalUncompressedSize the uncompressed serialized size of the message. -1 if unknown
+   */
+  public void outboundMessageSent(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+  }
+
+  /**
+   * An inbound message has been fully read from the transport.
+   *
+   * @param seqNo the sequential number of the message within the stream, starting from 0.  It can
+   *              be used to correlate with {@link #inboundMessage(int)} for the same message.
+   * @param optionalWireSize the wire size of the message. -1 if unknown
+   * @param optionalUncompressedSize the uncompressed serialized size of the message. -1 if unknown
+   */
+  public void inboundMessageRead(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+  }
+
+  /**
+   * The wire size of some outbound data is revealed. This can only used to record the accumulative
+   * outbound wire size. There is no guarantee wrt timing or granularity of this method.
+   */
+  public void outboundWireSize(long bytes) {
+  }
+
+  /**
+   * The uncompressed size of some outbound data is revealed. This can only used to record the
+   * accumulative outbound uncompressed size. There is no guarantee wrt timing or granularity of
+   * this method.
+   */
+  public void outboundUncompressedSize(long bytes) {
+  }
+
+  /**
+   * The wire size of some inbound data is revealed. This can only be used to record the
+   * accumulative received wire size. There is no guarantee wrt timing or granularity of this
+   * method.
+   */
+  public void inboundWireSize(long bytes) {
+  }
+
+  /**
+   * The uncompressed size of some inbound data is revealed. This can only used to record the
+   * accumulative received uncompressed size. There is no guarantee wrt timing or granularity of
+   * this method.
+   */
+  public void inboundUncompressedSize(long bytes) {
+  }
+}
diff --git a/core/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java b/core/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java
new file mode 100644
index 0000000..8606e75
--- /dev/null
+++ b/core/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.inprocess;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.grpc.ExperimentalApi;
+import io.grpc.Internal;
+import io.grpc.internal.AbstractManagedChannelImplBuilder;
+import io.grpc.internal.ClientTransportFactory;
+import io.grpc.internal.ConnectionClientTransport;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.SharedResourceHolder;
+import java.net.SocketAddress;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+/**
+ * Builder for a channel that issues in-process requests. Clients identify the in-process server by
+ * its name.
+ *
+ * <p>The channel is intended to be fully-featured, high performance, and useful in testing.
+ *
+ * <p>For usage examples, see {@link InProcessServerBuilder}.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1783")
+public final class InProcessChannelBuilder extends
+        AbstractManagedChannelImplBuilder<InProcessChannelBuilder> {
+  /**
+   * Create a channel builder that will connect to the server with the given name.
+   *
+   * @param name the identity of the server to connect to
+   * @return a new builder
+   */
+  public static InProcessChannelBuilder forName(String name) {
+    return new InProcessChannelBuilder(name);
+  }
+
+  /**
+   * Always fails.  Call {@link #forName} instead.
+   */
+  public static InProcessChannelBuilder forTarget(String target) {
+    throw new UnsupportedOperationException("call forName() instead");
+  }
+
+  /**
+   * Always fails.  Call {@link #forName} instead.
+   */
+  public static InProcessChannelBuilder forAddress(String name, int port) {
+    throw new UnsupportedOperationException("call forName() instead");
+  }
+
+  private final String name;
+  private ScheduledExecutorService scheduledExecutorService;
+
+  private InProcessChannelBuilder(String name) {
+    super(new InProcessSocketAddress(name), "localhost");
+    this.name = checkNotNull(name, "name");
+    // In-process transport should not record its traffic to the stats module.
+    // https://github.com/grpc/grpc-java/issues/2284
+    setStatsRecordStartedRpcs(false);
+    setStatsRecordFinishedRpcs(false);
+  }
+
+  @Override
+  public final InProcessChannelBuilder maxInboundMessageSize(int max) {
+    // TODO(carl-mastrangelo): maybe throw an exception since this not enforced?
+    return super.maxInboundMessageSize(max);
+  }
+
+  /**
+   * Does nothing.
+   */
+  @Override
+  public InProcessChannelBuilder useTransportSecurity() {
+    return this;
+  }
+
+  /**
+   * Does nothing.
+   *
+   * @deprecated use {@link #usePlaintext()} instead.
+   */
+  @Override
+  @Deprecated
+  public InProcessChannelBuilder usePlaintext(boolean skipNegotiation) {
+    return this;
+  }
+
+  /**
+   * Does nothing.
+   */
+  @Override
+  public InProcessChannelBuilder usePlaintext() {
+    return this;
+  }
+
+  /** Does nothing. */
+  @Override
+  public InProcessChannelBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
+    return this;
+  }
+
+  /** Does nothing. */
+  @Override
+  public InProcessChannelBuilder keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
+    return this;
+  }
+
+  /** Does nothing. */
+  @Override
+  public InProcessChannelBuilder keepAliveWithoutCalls(boolean enable) {
+    return this;
+  }
+
+  /**
+   * Provides a custom scheduled executor service.
+   *
+   * <p>It's an optional parameter. If the user has not provided a scheduled executor service when
+   * the channel is built, the builder will use a static cached thread pool.
+   *
+   * @return this
+   *
+   * @since 1.11.0
+   */
+  public InProcessChannelBuilder scheduledExecutorService(
+      ScheduledExecutorService scheduledExecutorService) {
+    this.scheduledExecutorService =
+        checkNotNull(scheduledExecutorService, "scheduledExecutorService");
+    return this;
+  }
+
+  @Override
+  @Internal
+  protected ClientTransportFactory buildTransportFactory() {
+    return new InProcessClientTransportFactory(name, scheduledExecutorService);
+  }
+
+  /**
+   * Creates InProcess transports. Exposed for internal use, as it should be private.
+   */
+  static final class InProcessClientTransportFactory implements ClientTransportFactory {
+    private final String name;
+    private final ScheduledExecutorService timerService;
+    private final boolean useSharedTimer;
+    private boolean closed;
+
+    private InProcessClientTransportFactory(
+        String name, @Nullable ScheduledExecutorService scheduledExecutorService) {
+      this.name = name;
+      useSharedTimer = scheduledExecutorService == null;
+      timerService = useSharedTimer
+          ? SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE) : scheduledExecutorService;
+    }
+
+    @Override
+    public ConnectionClientTransport newClientTransport(
+        SocketAddress addr, ClientTransportOptions options) {
+      if (closed) {
+        throw new IllegalStateException("The transport factory is closed.");
+      }
+      return new InProcessTransport(name, options.getAuthority(), options.getUserAgent());
+    }
+
+    @Override
+    public ScheduledExecutorService getScheduledExecutorService() {
+      return timerService;
+    }
+
+    @Override
+    public void close() {
+      if (closed) {
+        return;
+      }
+      closed = true;
+      if (useSharedTimer) {
+        SharedResourceHolder.release(GrpcUtil.TIMER_SERVICE, timerService);
+      }
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/inprocess/InProcessServer.java b/core/src/main/java/io/grpc/inprocess/InProcessServer.java
new file mode 100644
index 0000000..93709a0
--- /dev/null
+++ b/core/src/main/java/io/grpc/inprocess/InProcessServer.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.inprocess;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.ServerStreamTracer;
+import io.grpc.internal.InternalServer;
+import io.grpc.internal.ObjectPool;
+import io.grpc.internal.ServerListener;
+import io.grpc.internal.ServerTransportListener;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ScheduledExecutorService;
+import javax.annotation.concurrent.ThreadSafe;
+
+@ThreadSafe
+final class InProcessServer implements InternalServer {
+  private static final ConcurrentMap<String, InProcessServer> registry
+      = new ConcurrentHashMap<String, InProcessServer>();
+
+  static InProcessServer findServer(String name) {
+    return registry.get(name);
+  }
+
+  private final String name;
+  private final List<ServerStreamTracer.Factory> streamTracerFactories;
+  private ServerListener listener;
+  private boolean shutdown;
+  /** Defaults to be a SharedResourcePool. */
+  private final ObjectPool<ScheduledExecutorService> schedulerPool;
+  /**
+   * Only used to make sure the scheduler has at least one reference. Since child transports can
+   * outlive this server, they must get their own reference.
+   */
+  private ScheduledExecutorService scheduler;
+
+  InProcessServer(
+      String name, ObjectPool<ScheduledExecutorService> schedulerPool,
+      List<ServerStreamTracer.Factory> streamTracerFactories) {
+    this.name = name;
+    this.schedulerPool = schedulerPool;
+    this.streamTracerFactories =
+        Collections.unmodifiableList(checkNotNull(streamTracerFactories, "streamTracerFactories"));
+  }
+
+  @Override
+  public void start(ServerListener serverListener) throws IOException {
+    this.listener = serverListener;
+    this.scheduler = schedulerPool.getObject();
+    // Must be last, as channels can start connecting after this point.
+    if (registry.putIfAbsent(name, this) != null) {
+      throw new IOException("name already registered: " + name);
+    }
+  }
+
+  @Override
+  public int getPort() {
+    return -1;
+  }
+
+  @Override
+  public List<InternalInstrumented<SocketStats>> getListenSockets() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public void shutdown() {
+    if (!registry.remove(name, this)) {
+      throw new AssertionError();
+    }
+    scheduler = schedulerPool.returnObject(scheduler);
+    synchronized (this) {
+      shutdown = true;
+      listener.serverShutdown();
+    }
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("name", name).toString();
+  }
+
+  synchronized ServerTransportListener register(InProcessTransport transport) {
+    if (shutdown) {
+      return null;
+    }
+    return listener.transportCreated(transport);
+  }
+
+  ObjectPool<ScheduledExecutorService> getScheduledExecutorServicePool() {
+    return schedulerPool;
+  }
+
+  List<ServerStreamTracer.Factory> getStreamTracerFactories() {
+    return streamTracerFactories;
+  }
+}
diff --git a/core/src/main/java/io/grpc/inprocess/InProcessServerBuilder.java b/core/src/main/java/io/grpc/inprocess/InProcessServerBuilder.java
new file mode 100644
index 0000000..205eb4b
--- /dev/null
+++ b/core/src/main/java/io/grpc/inprocess/InProcessServerBuilder.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.inprocess;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Preconditions;
+import io.grpc.ExperimentalApi;
+import io.grpc.ServerStreamTracer;
+import io.grpc.internal.AbstractServerImplBuilder;
+import io.grpc.internal.FixedObjectPool;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.ObjectPool;
+import io.grpc.internal.SharedResourcePool;
+import java.io.File;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Builder for a server that services in-process requests. Clients identify the in-process server by
+ * its name.
+ *
+ * <p>The server is intended to be fully-featured, high performance, and useful in testing.
+ *
+ * <h3>Using JUnit TestRule</h3>
+ * The class "GrpcServerRule" (from "grpc-java/testing") is a JUnit TestRule that
+ * creates a {@link InProcessServer} and a {@link io.grpc.ManagedChannel ManagedChannel}. This
+ * test rule contains the boilerplate code shown below. The classes "HelloWorldServerTest" and
+ * "HelloWorldClientTest" (from "grpc-java/examples") demonstrate basic usage.
+ *
+ * <h3>Usage example</h3>
+ * <h4>Server and client channel setup</h4>
+ * <pre>
+ *   String uniqueName = InProcessServerBuilder.generateName();
+ *   Server server = InProcessServerBuilder.forName(uniqueName)
+ *       .directExecutor() // directExecutor is fine for unit tests
+ *       .addService(&#47;* your code here *&#47;)
+ *       .build().start();
+ *   ManagedChannel channel = InProcessChannelBuilder.forName(uniqueName)
+ *       .directExecutor()
+ *       .build();
+ * </pre>
+ *
+ * <h4>Usage in tests</h4>
+ * The channel can be used normally. A blocking stub example:
+ * <pre>
+ *   TestServiceGrpc.TestServiceBlockingStub blockingStub =
+ *       TestServiceGrpc.newBlockingStub(channel);
+ * </pre>
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1783")
+public final class InProcessServerBuilder
+    extends AbstractServerImplBuilder<InProcessServerBuilder> {
+  /**
+   * Create a server builder that will bind with the given name.
+   *
+   * @param name the identity of the server for clients to connect to
+   * @return a new builder
+   */
+  public static InProcessServerBuilder forName(String name) {
+    return new InProcessServerBuilder(name);
+  }
+
+  /**
+   * Always fails.  Call {@link #forName} instead.
+   */
+  public static InProcessServerBuilder forPort(int port) {
+    throw new UnsupportedOperationException("call forName() instead");
+  }
+
+  /**
+   * Generates a new server name that is unique each time.
+   */
+  public static String generateName() {
+    return UUID.randomUUID().toString();
+  }
+
+  private final String name;
+  private ObjectPool<ScheduledExecutorService> schedulerPool =
+      SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE);
+
+  private InProcessServerBuilder(String name) {
+    this.name = Preconditions.checkNotNull(name, "name");
+    // In-process transport should not record its traffic to the stats module.
+    // https://github.com/grpc/grpc-java/issues/2284
+    setStatsRecordStartedRpcs(false);
+    setStatsRecordFinishedRpcs(false);
+    // Disable handshake timeout because it is unnecessary, and can trigger Thread creation that can
+    // break some environments (like tests).
+    handshakeTimeout(Long.MAX_VALUE, TimeUnit.SECONDS);
+  }
+
+  /**
+   * Provides a custom scheduled executor service.
+   *
+   * <p>It's an optional parameter. If the user has not provided a scheduled executor service when
+   * the channel is built, the builder will use a static cached thread pool.
+   *
+   * @return this
+   *
+   * @since 1.11.0
+   */
+  public InProcessServerBuilder scheduledExecutorService(
+      ScheduledExecutorService scheduledExecutorService) {
+    schedulerPool = new FixedObjectPool<ScheduledExecutorService>(
+        checkNotNull(scheduledExecutorService, "scheduledExecutorService"));
+    return this;
+  }
+
+  @Override
+  protected InProcessServer buildTransportServer(
+      List<ServerStreamTracer.Factory> streamTracerFactories) {
+    return new InProcessServer(name, schedulerPool, streamTracerFactories);
+  }
+
+  @Override
+  public InProcessServerBuilder useTransportSecurity(File certChain, File privateKey) {
+    throw new UnsupportedOperationException("TLS not supported in InProcessServer");
+  }
+}
diff --git a/core/src/main/java/io/grpc/inprocess/InProcessSocketAddress.java b/core/src/main/java/io/grpc/inprocess/InProcessSocketAddress.java
new file mode 100644
index 0000000..e5f0515
--- /dev/null
+++ b/core/src/main/java/io/grpc/inprocess/InProcessSocketAddress.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.inprocess;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.SocketAddress;
+
+/**
+ * Custom SocketAddress class for {@link InProcessTransport}.
+ */
+public final class InProcessSocketAddress extends SocketAddress {
+  private static final long serialVersionUID = -2803441206326023474L;
+
+  private final String name;
+
+  /**
+   * @param name - The name of the inprocess channel or server.
+   * @since 1.0.0
+   */
+  public InProcessSocketAddress(String name) {
+    this.name = checkNotNull(name, "name");
+  }
+
+  /**
+   * Gets the name of the inprocess channel or server.
+   *
+   * @since 1.0.0
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * @since 1.14.0
+   */
+  @Override
+  public String toString() {
+    return name;
+  }
+
+  /**
+   * @since 1.15.0
+   */
+  @Override
+  public int hashCode() {
+    return name.hashCode();
+  }
+
+  /**
+   * @since 1.15.0
+   */
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof InProcessSocketAddress)) {
+      return false;
+    }
+    return name.equals(((InProcessSocketAddress) obj).name);
+  }
+}
diff --git a/core/src/main/java/io/grpc/inprocess/InProcessTransport.java b/core/src/main/java/io/grpc/inprocess/InProcessTransport.java
new file mode 100644
index 0000000..7cf1a89
--- /dev/null
+++ b/core/src/main/java/io/grpc/inprocess/InProcessTransport.java
@@ -0,0 +1,721 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.inprocess;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.internal.GrpcUtil.TIMEOUT_KEY;
+import static java.lang.Math.max;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.Attributes;
+import io.grpc.CallCredentials;
+import io.grpc.CallOptions;
+import io.grpc.Compressor;
+import io.grpc.Deadline;
+import io.grpc.Decompressor;
+import io.grpc.DecompressorRegistry;
+import io.grpc.Grpc;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalLogId;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.SecurityLevel;
+import io.grpc.ServerStreamTracer;
+import io.grpc.Status;
+import io.grpc.internal.ClientStream;
+import io.grpc.internal.ClientStreamListener;
+import io.grpc.internal.ConnectionClientTransport;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.ManagedClientTransport;
+import io.grpc.internal.NoopClientStream;
+import io.grpc.internal.ObjectPool;
+import io.grpc.internal.ServerStream;
+import io.grpc.internal.ServerStreamListener;
+import io.grpc.internal.ServerTransport;
+import io.grpc.internal.ServerTransportListener;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.StreamListener;
+import java.io.InputStream;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+@ThreadSafe
+final class InProcessTransport implements ServerTransport, ConnectionClientTransport {
+  private static final Logger log = Logger.getLogger(InProcessTransport.class.getName());
+
+  private final InternalLogId logId = InternalLogId.allocate(getClass().getName());
+  private final String name;
+  private final String authority;
+  private final String userAgent;
+  private ObjectPool<ScheduledExecutorService> serverSchedulerPool;
+  private ScheduledExecutorService serverScheduler;
+  private ServerTransportListener serverTransportListener;
+  private Attributes serverStreamAttributes;
+  private ManagedClientTransport.Listener clientTransportListener;
+  @GuardedBy("this")
+  private boolean shutdown;
+  @GuardedBy("this")
+  private boolean terminated;
+  @GuardedBy("this")
+  private Status shutdownStatus;
+  @GuardedBy("this")
+  private Set<InProcessStream> streams = new HashSet<InProcessStream>();
+  @GuardedBy("this")
+  private List<ServerStreamTracer.Factory> serverStreamTracerFactories;
+  private final Attributes attributes = Attributes.newBuilder()
+      .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
+      .build();
+
+  public InProcessTransport(String name, String authority, String userAgent) {
+    this.name = name;
+    this.authority = authority;
+    this.userAgent = GrpcUtil.getGrpcUserAgent("inprocess", userAgent);
+  }
+
+  @CheckReturnValue
+  @Override
+  public synchronized Runnable start(ManagedClientTransport.Listener listener) {
+    this.clientTransportListener = listener;
+    InProcessServer server = InProcessServer.findServer(name);
+    if (server != null) {
+      serverSchedulerPool = server.getScheduledExecutorServicePool();
+      serverScheduler = serverSchedulerPool.getObject();
+      serverStreamTracerFactories = server.getStreamTracerFactories();
+      // Must be semi-initialized; past this point, can begin receiving requests
+      serverTransportListener = server.register(this);
+    }
+    if (serverTransportListener == null) {
+      shutdownStatus = Status.UNAVAILABLE.withDescription("Could not find server: " + name);
+      final Status localShutdownStatus = shutdownStatus;
+      return new Runnable() {
+        @Override
+        public void run() {
+          synchronized (InProcessTransport.this) {
+            notifyShutdown(localShutdownStatus);
+            notifyTerminated();
+          }
+        }
+      };
+    }
+    return new Runnable() {
+      @Override
+      @SuppressWarnings("deprecation")
+      public void run() {
+        synchronized (InProcessTransport.this) {
+          Attributes serverTransportAttrs = Attributes.newBuilder()
+              .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, new InProcessSocketAddress(name))
+              .build();
+          serverStreamAttributes = serverTransportListener.transportReady(serverTransportAttrs);
+          clientTransportListener.transportReady();
+        }
+      }
+    };
+  }
+
+  @Override
+  public synchronized ClientStream newStream(
+      final MethodDescriptor<?, ?> method, final Metadata headers, final CallOptions callOptions) {
+    if (shutdownStatus != null) {
+      final Status capturedStatus = shutdownStatus;
+      final StatsTraceContext statsTraceCtx =
+          StatsTraceContext.newClientContext(callOptions, headers);
+      return new NoopClientStream() {
+        @Override
+        public void start(ClientStreamListener listener) {
+          statsTraceCtx.clientOutboundHeaders();
+          statsTraceCtx.streamClosed(capturedStatus);
+          listener.closed(capturedStatus, new Metadata());
+        }
+      };
+    }
+    headers.put(GrpcUtil.USER_AGENT_KEY, userAgent);
+    return new InProcessStream(method, headers, callOptions, authority).clientStream;
+  }
+
+  @Override
+  public synchronized void ping(final PingCallback callback, Executor executor) {
+    if (terminated) {
+      final Status shutdownStatus = this.shutdownStatus;
+      executor.execute(new Runnable() {
+        @Override
+        public void run() {
+          callback.onFailure(shutdownStatus.asRuntimeException());
+        }
+      });
+    } else {
+      executor.execute(new Runnable() {
+        @Override
+        public void run() {
+          callback.onSuccess(0);
+        }
+      });
+    }
+  }
+
+  @Override
+  public synchronized void shutdown(Status reason) {
+    // Can be called multiple times: once for ManagedClientTransport, once for ServerTransport.
+    if (shutdown) {
+      return;
+    }
+    shutdownStatus = reason;
+    notifyShutdown(reason);
+    if (streams.isEmpty()) {
+      notifyTerminated();
+    }
+  }
+
+  @Override
+  public synchronized void shutdown() {
+    shutdown(Status.UNAVAILABLE.withDescription("InProcessTransport shutdown by the server-side"));
+  }
+
+  @Override
+  public void shutdownNow(Status reason) {
+    checkNotNull(reason, "reason");
+    List<InProcessStream> streamsCopy;
+    synchronized (this) {
+      shutdown(reason);
+      if (terminated) {
+        return;
+      }
+      streamsCopy = new ArrayList<>(streams);
+    }
+    for (InProcessStream stream : streamsCopy) {
+      stream.clientStream.cancel(reason);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("logId", logId.getId())
+        .add("name", name)
+        .toString();
+  }
+
+  @Override
+  public InternalLogId getLogId() {
+    return logId;
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    return attributes;
+  }
+
+  @Override
+  public ScheduledExecutorService getScheduledExecutorService() {
+    return serverScheduler;
+  }
+
+  @Override
+  public ListenableFuture<SocketStats> getStats() {
+    SettableFuture<SocketStats> ret = SettableFuture.create();
+    ret.set(null);
+    return ret;
+  }
+
+  private synchronized void notifyShutdown(Status s) {
+    if (shutdown) {
+      return;
+    }
+    shutdown = true;
+    clientTransportListener.transportShutdown(s);
+  }
+
+  private synchronized void notifyTerminated() {
+    if (terminated) {
+      return;
+    }
+    terminated = true;
+    if (serverScheduler != null) {
+      serverScheduler = serverSchedulerPool.returnObject(serverScheduler);
+    }
+    clientTransportListener.transportTerminated();
+    if (serverTransportListener != null) {
+      serverTransportListener.transportTerminated();
+    }
+  }
+
+  private class InProcessStream {
+    private final InProcessClientStream clientStream;
+    private final InProcessServerStream serverStream;
+    private final Metadata headers;
+    private final MethodDescriptor<?, ?> method;
+    private volatile String authority;
+
+    private InProcessStream(
+        MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions,
+        String authority) {
+      this.method = checkNotNull(method, "method");
+      this.headers = checkNotNull(headers, "headers");
+      this.authority = authority;
+      this.clientStream = new InProcessClientStream(callOptions, headers);
+      this.serverStream = new InProcessServerStream(method, headers);
+    }
+
+    // Can be called multiple times due to races on both client and server closing at same time.
+    private void streamClosed() {
+      synchronized (InProcessTransport.this) {
+        boolean justRemovedAnElement = streams.remove(this);
+        if (streams.isEmpty() && justRemovedAnElement) {
+          clientTransportListener.transportInUse(false);
+          if (shutdown) {
+            notifyTerminated();
+          }
+        }
+      }
+    }
+
+    private class InProcessServerStream implements ServerStream {
+      final StatsTraceContext statsTraceCtx;
+      @GuardedBy("this")
+      private ClientStreamListener clientStreamListener;
+      @GuardedBy("this")
+      private int clientRequested;
+      @GuardedBy("this")
+      private ArrayDeque<StreamListener.MessageProducer> clientReceiveQueue =
+          new ArrayDeque<StreamListener.MessageProducer>();
+      @GuardedBy("this")
+      private Status clientNotifyStatus;
+      @GuardedBy("this")
+      private Metadata clientNotifyTrailers;
+      // Only is intended to prevent double-close when client cancels.
+      @GuardedBy("this")
+      private boolean closed;
+      @GuardedBy("this")
+      private int outboundSeqNo;
+
+      InProcessServerStream(MethodDescriptor<?, ?> method, Metadata headers) {
+        statsTraceCtx = StatsTraceContext.newServerContext(
+            serverStreamTracerFactories, method.getFullMethodName(), headers);
+      }
+
+      private synchronized void setListener(ClientStreamListener listener) {
+        clientStreamListener = listener;
+      }
+
+      @Override
+      public void setListener(ServerStreamListener serverStreamListener) {
+        clientStream.setListener(serverStreamListener);
+      }
+
+      @Override
+      public void request(int numMessages) {
+        boolean onReady = clientStream.serverRequested(numMessages);
+        if (onReady) {
+          synchronized (this) {
+            if (!closed) {
+              clientStreamListener.onReady();
+            }
+          }
+        }
+      }
+
+      // This method is the only reason we have to synchronize field accesses.
+      /**
+       * Client requested more messages.
+       *
+       * @return whether onReady should be called on the server
+       */
+      private synchronized boolean clientRequested(int numMessages) {
+        if (closed) {
+          return false;
+        }
+        boolean previouslyReady = clientRequested > 0;
+        clientRequested += numMessages;
+        while (clientRequested > 0 && !clientReceiveQueue.isEmpty()) {
+          clientRequested--;
+          clientStreamListener.messagesAvailable(clientReceiveQueue.poll());
+        }
+        // Attempt being reentrant-safe
+        if (closed) {
+          return false;
+        }
+        if (clientReceiveQueue.isEmpty() && clientNotifyStatus != null) {
+          closed = true;
+          clientStream.statsTraceCtx.streamClosed(clientNotifyStatus);
+          clientStreamListener.closed(clientNotifyStatus, clientNotifyTrailers);
+        }
+        boolean nowReady = clientRequested > 0;
+        return !previouslyReady && nowReady;
+      }
+
+      private void clientCancelled(Status status) {
+        internalCancel(status);
+      }
+
+      @Override
+      public synchronized void writeMessage(InputStream message) {
+        if (closed) {
+          return;
+        }
+        statsTraceCtx.outboundMessage(outboundSeqNo);
+        statsTraceCtx.outboundMessageSent(outboundSeqNo, -1, -1);
+        clientStream.statsTraceCtx.inboundMessage(outboundSeqNo);
+        clientStream.statsTraceCtx.inboundMessageRead(outboundSeqNo, -1, -1);
+        outboundSeqNo++;
+        StreamListener.MessageProducer producer = new SingleMessageProducer(message);
+        if (clientRequested > 0) {
+          clientRequested--;
+          clientStreamListener.messagesAvailable(producer);
+        } else {
+          clientReceiveQueue.add(producer);
+        }
+      }
+
+      @Override
+      public void flush() {}
+
+      @Override
+      public synchronized boolean isReady() {
+        if (closed) {
+          return false;
+        }
+        return clientRequested > 0;
+      }
+
+      @Override
+      public synchronized void writeHeaders(Metadata headers) {
+        if (closed) {
+          return;
+        }
+        clientStream.statsTraceCtx.clientInboundHeaders();
+        clientStreamListener.headersRead(headers);
+      }
+
+      @Override
+      public void close(Status status, Metadata trailers) {
+        // clientStream.serverClosed must happen before clientStreamListener.closed, otherwise
+        // clientStreamListener.closed can trigger clientStream.cancel (see code in
+        // ClientCalls.blockingUnaryCall), which may race with clientStream.serverClosed as both are
+        // calling internalCancel().
+        clientStream.serverClosed(Status.OK, status);
+
+        Status clientStatus = stripCause(status);
+        synchronized (this) {
+          if (closed) {
+            return;
+          }
+          if (clientReceiveQueue.isEmpty()) {
+            closed = true;
+            clientStream.statsTraceCtx.streamClosed(clientStatus);
+            clientStreamListener.closed(clientStatus, trailers);
+          } else {
+            clientNotifyStatus = clientStatus;
+            clientNotifyTrailers = trailers;
+          }
+        }
+
+        streamClosed();
+      }
+
+      @Override
+      public void cancel(Status status) {
+        if (!internalCancel(Status.CANCELLED.withDescription("server cancelled stream"))) {
+          return;
+        }
+        clientStream.serverClosed(status, status);
+        streamClosed();
+      }
+
+      private synchronized boolean internalCancel(Status clientStatus) {
+        if (closed) {
+          return false;
+        }
+        closed = true;
+        StreamListener.MessageProducer producer;
+        while ((producer = clientReceiveQueue.poll()) != null) {
+          InputStream message;
+          while ((message = producer.next()) != null) {
+            try {
+              message.close();
+            } catch (Throwable t) {
+              log.log(Level.WARNING, "Exception closing stream", t);
+            }
+          }
+        }
+        clientStream.statsTraceCtx.streamClosed(clientStatus);
+        clientStreamListener.closed(clientStatus, new Metadata());
+        return true;
+      }
+
+      @Override
+      public void setMessageCompression(boolean enable) {
+        // noop
+      }
+
+      @Override
+      public void setCompressor(Compressor compressor) {}
+
+      @Override
+      public void setDecompressor(Decompressor decompressor) {}
+
+      @Override public Attributes getAttributes() {
+        return serverStreamAttributes;
+      }
+
+      @Override
+      public String getAuthority() {
+        return InProcessStream.this.authority;
+      }
+
+      @Override
+      public StatsTraceContext statsTraceContext() {
+        return statsTraceCtx;
+      }
+    }
+
+    private class InProcessClientStream implements ClientStream {
+      final StatsTraceContext statsTraceCtx;
+      @GuardedBy("this")
+      private ServerStreamListener serverStreamListener;
+      @GuardedBy("this")
+      private int serverRequested;
+      @GuardedBy("this")
+      private ArrayDeque<StreamListener.MessageProducer> serverReceiveQueue =
+          new ArrayDeque<StreamListener.MessageProducer>();
+      @GuardedBy("this")
+      private boolean serverNotifyHalfClose;
+      // Only is intended to prevent double-close when server closes.
+      @GuardedBy("this")
+      private boolean closed;
+      @GuardedBy("this")
+      private int outboundSeqNo;
+
+      InProcessClientStream(CallOptions callOptions, Metadata headers) {
+        statsTraceCtx = StatsTraceContext.newClientContext(callOptions, headers);
+      }
+
+      private synchronized void setListener(ServerStreamListener listener) {
+        this.serverStreamListener = listener;
+      }
+
+      @Override
+      public void request(int numMessages) {
+        boolean onReady = serverStream.clientRequested(numMessages);
+        if (onReady) {
+          synchronized (this) {
+            if (!closed) {
+              serverStreamListener.onReady();
+            }
+          }
+        }
+      }
+
+      // This method is the only reason we have to synchronize field accesses.
+      /**
+       * Client requested more messages.
+       *
+       * @return whether onReady should be called on the server
+       */
+      private synchronized boolean serverRequested(int numMessages) {
+        if (closed) {
+          return false;
+        }
+        boolean previouslyReady = serverRequested > 0;
+        serverRequested += numMessages;
+        while (serverRequested > 0 && !serverReceiveQueue.isEmpty()) {
+          serverRequested--;
+          serverStreamListener.messagesAvailable(serverReceiveQueue.poll());
+        }
+        if (serverReceiveQueue.isEmpty() && serverNotifyHalfClose) {
+          serverNotifyHalfClose = false;
+          serverStreamListener.halfClosed();
+        }
+        boolean nowReady = serverRequested > 0;
+        return !previouslyReady && nowReady;
+      }
+
+      private void serverClosed(Status serverListenerStatus, Status serverTracerStatus) {
+        internalCancel(serverListenerStatus, serverTracerStatus);
+      }
+
+      @Override
+      public synchronized void writeMessage(InputStream message) {
+        if (closed) {
+          return;
+        }
+        statsTraceCtx.outboundMessage(outboundSeqNo);
+        statsTraceCtx.outboundMessageSent(outboundSeqNo, -1, -1);
+        serverStream.statsTraceCtx.inboundMessage(outboundSeqNo);
+        serverStream.statsTraceCtx.inboundMessageRead(outboundSeqNo, -1, -1);
+        outboundSeqNo++;
+        StreamListener.MessageProducer producer = new SingleMessageProducer(message);
+        if (serverRequested > 0) {
+          serverRequested--;
+          serverStreamListener.messagesAvailable(producer);
+        } else {
+          serverReceiveQueue.add(producer);
+        }
+      }
+
+      @Override
+      public void flush() {}
+
+      @Override
+      public synchronized boolean isReady() {
+        if (closed) {
+          return false;
+        }
+        return serverRequested > 0;
+      }
+
+      // Must be thread-safe for shutdownNow()
+      @Override
+      public void cancel(Status reason) {
+        Status serverStatus = stripCause(reason);
+        if (!internalCancel(serverStatus, serverStatus)) {
+          return;
+        }
+        serverStream.clientCancelled(reason);
+        streamClosed();
+      }
+
+      private synchronized boolean internalCancel(
+          Status serverListenerStatus, Status serverTracerStatus) {
+        if (closed) {
+          return false;
+        }
+        closed = true;
+
+        StreamListener.MessageProducer producer;
+        while ((producer = serverReceiveQueue.poll()) != null) {
+          InputStream message;
+          while ((message = producer.next()) != null) {
+            try {
+              message.close();
+            } catch (Throwable t) {
+              log.log(Level.WARNING, "Exception closing stream", t);
+            }
+          }
+        }
+        serverStream.statsTraceCtx.streamClosed(serverTracerStatus);
+        serverStreamListener.closed(serverListenerStatus);
+        return true;
+      }
+
+      @Override
+      public synchronized void halfClose() {
+        if (closed) {
+          return;
+        }
+        if (serverReceiveQueue.isEmpty()) {
+          serverStreamListener.halfClosed();
+        } else {
+          serverNotifyHalfClose = true;
+        }
+      }
+
+      @Override
+      public void setMessageCompression(boolean enable) {}
+
+      @Override
+      public void setAuthority(String string) {
+        InProcessStream.this.authority = string;
+      }
+
+      @Override
+      public void start(ClientStreamListener listener) {
+        serverStream.setListener(listener);
+
+        synchronized (InProcessTransport.this) {
+          statsTraceCtx.clientOutboundHeaders();
+          streams.add(InProcessTransport.InProcessStream.this);
+          if (streams.size() == 1) {
+            clientTransportListener.transportInUse(true);
+          }
+          serverTransportListener.streamCreated(serverStream, method.getFullMethodName(), headers);
+        }
+      }
+
+      @Override
+      public Attributes getAttributes() {
+        return Attributes.EMPTY;
+      }
+
+      @Override
+      public void setCompressor(Compressor compressor) {}
+
+      @Override
+      public void setFullStreamDecompression(boolean fullStreamDecompression) {}
+
+      @Override
+      public void setDecompressorRegistry(DecompressorRegistry decompressorRegistry) {}
+
+      @Override
+      public void setMaxInboundMessageSize(int maxSize) {}
+
+      @Override
+      public void setMaxOutboundMessageSize(int maxSize) {}
+
+      @Override
+      public void setDeadline(Deadline deadline) {
+        headers.discardAll(TIMEOUT_KEY);
+        long effectiveTimeout = max(0, deadline.timeRemaining(TimeUnit.NANOSECONDS));
+        headers.put(TIMEOUT_KEY, effectiveTimeout);
+      }
+    }
+  }
+
+  /**
+   * Returns a new status with the same code and description, but stripped of any other information
+   * (i.e. cause).
+   *
+   * <p>This is, so that the InProcess transport behaves in the same way as the other transports,
+   * when exchanging statuses between client and server and vice versa.
+   */
+  private static Status stripCause(Status status) {
+    if (status == null) {
+      return null;
+    }
+    return Status
+        .fromCodeValue(status.getCode().value())
+        .withDescription(status.getDescription());
+  }
+
+  private static class SingleMessageProducer implements StreamListener.MessageProducer {
+    private InputStream message;
+
+    private SingleMessageProducer(InputStream message) {
+      this.message = message;
+    }
+
+    @Nullable
+    @Override
+    public InputStream next() {
+      InputStream messageToReturn = message;
+      message = null;
+      return messageToReturn;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/inprocess/package-info.java b/core/src/main/java/io/grpc/inprocess/package-info.java
new file mode 100644
index 0000000..56887ad
--- /dev/null
+++ b/core/src/main/java/io/grpc/inprocess/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/**
+ * The in-process transport which is for when a server is in the same process as the client.
+ */
+package io.grpc.inprocess;
diff --git a/core/src/main/java/io/grpc/internal/AbstractClientStream.java b/core/src/main/java/io/grpc/internal/AbstractClientStream.java
new file mode 100644
index 0000000..c100998
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/AbstractClientStream.java
@@ -0,0 +1,536 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static io.grpc.internal.GrpcUtil.CONTENT_ENCODING_KEY;
+import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
+import static io.grpc.internal.GrpcUtil.TIMEOUT_KEY;
+import static java.lang.Math.max;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import io.grpc.Codec;
+import io.grpc.Compressor;
+import io.grpc.Deadline;
+import io.grpc.Decompressor;
+import io.grpc.DecompressorRegistry;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.ClientStreamListener.RpcProgress;
+import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * The abstract base class for {@link ClientStream} implementations. Extending classes only need to
+ * implement {@link #transportState()} and {@link #abstractClientStreamSink()}. Must only be called
+ * from the sending application thread.
+ */
+public abstract class AbstractClientStream extends AbstractStream
+    implements ClientStream, MessageFramer.Sink {
+
+  private static final Logger log = Logger.getLogger(AbstractClientStream.class.getName());
+
+  /**
+   * A sink for outbound operations, separated from the stream simply to avoid name
+   * collisions/confusion. Only called from application thread.
+   */
+  protected interface Sink {
+    /** 
+     * Sends the request headers to the remote end point.
+     *
+     * @param metadata the metadata to be sent
+     * @param payload the payload needs to be sent in the headers if not null. Should only be used
+     *     when sending an unary GET request
+     */
+    void writeHeaders(Metadata metadata, @Nullable byte[] payload);
+
+    /**
+     * Sends an outbound frame to the remote end point.
+     *
+     * @param frame a buffer containing the chunk of data to be sent, or {@code null} if {@code
+     *     endOfStream} with no data to send
+     * @param endOfStream {@code true} if this is the last frame; {@code flush} is guaranteed to be
+     *     {@code true} if this is {@code true}
+     * @param flush {@code true} if more data may not be arriving soon
+     * @Param numMessages the number of messages this series of frames represents, must be >= 0.
+     */
+    void writeFrame(
+        @Nullable WritableBuffer frame, boolean endOfStream, boolean flush, int numMessages);
+
+    /**
+     * Requests up to the given number of messages from the call to be delivered to the client. This
+     * should end up triggering {@link TransportState#requestMessagesFromDeframer(int)} on the
+     * transport thread.
+     */
+    void request(int numMessages);
+
+    /**
+     * Tears down the stream, typically in the event of a timeout. This method may be called
+     * multiple times and from any thread.
+     *
+     * <p>This is a clone of {@link ClientStream#cancel(Status)};
+     * {@link AbstractClientStream#cancel} delegates to this method.
+     */
+    void cancel(Status status);
+  }
+
+  private final TransportTracer transportTracer;
+  private final Framer framer;
+  private boolean useGet;
+  private Metadata headers;
+  /**
+   * Whether cancel() has been called. This is not strictly necessary, but removes the delay between
+   * cancel() being called and isReady() beginning to return false, since cancel is commonly
+   * processed asynchronously.
+   */
+  private volatile boolean cancelled;
+
+  protected AbstractClientStream(
+      WritableBufferAllocator bufferAllocator,
+      StatsTraceContext statsTraceCtx,
+      TransportTracer transportTracer,
+      Metadata headers,
+      boolean useGet) {
+    checkNotNull(headers, "headers");
+    this.transportTracer = checkNotNull(transportTracer, "transportTracer");
+    this.useGet = useGet;
+    if (!useGet) {
+      framer = new MessageFramer(this, bufferAllocator, statsTraceCtx);
+      this.headers = headers;
+    } else {
+      framer = new GetFramer(headers, statsTraceCtx);
+    }
+  }
+
+  @Override
+  public void setDeadline(Deadline deadline) {
+    headers.discardAll(TIMEOUT_KEY);
+    long effectiveTimeout = max(0, deadline.timeRemaining(TimeUnit.NANOSECONDS));
+    headers.put(TIMEOUT_KEY, effectiveTimeout);
+  }
+
+  @Override
+  public void setMaxOutboundMessageSize(int maxSize) {
+    framer.setMaxOutboundMessageSize(maxSize);
+  }
+
+  @Override
+  public void setMaxInboundMessageSize(int maxSize) {
+    transportState().setMaxInboundMessageSize(maxSize);
+  }
+
+  @Override
+  public final void setFullStreamDecompression(boolean fullStreamDecompression) {
+    transportState().setFullStreamDecompression(fullStreamDecompression);
+  }
+
+  @Override
+  public final void setDecompressorRegistry(DecompressorRegistry decompressorRegistry) {
+    transportState().setDecompressorRegistry(decompressorRegistry);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  protected abstract TransportState transportState();
+
+  @Override
+  public final void start(ClientStreamListener listener) {
+    transportState().setListener(listener);
+    if (!useGet) {
+      abstractClientStreamSink().writeHeaders(headers, null);
+      headers = null;
+    }
+  }
+
+  /**
+   * Sink for transport to be called to perform outbound operations. Each stream must have its own
+   * unique sink.
+   */
+  protected abstract Sink abstractClientStreamSink();
+
+  @Override
+  protected final Framer framer() {
+    return framer;
+  }
+
+  @Override
+  public final void request(int numMessages) {
+    abstractClientStreamSink().request(numMessages);
+  }
+
+  @Override
+  public final void deliverFrame(
+      WritableBuffer frame, boolean endOfStream, boolean flush, int numMessages) {
+    Preconditions.checkArgument(frame != null || endOfStream, "null frame before EOS");
+    abstractClientStreamSink().writeFrame(frame, endOfStream, flush, numMessages);
+  }
+
+  @Override
+  public final void halfClose() {
+    if (!transportState().isOutboundClosed()) {
+      transportState().setOutboundClosed();
+      endOfMessages();
+    }
+  }
+
+  @Override
+  public final void cancel(Status reason) {
+    Preconditions.checkArgument(!reason.isOk(), "Should not cancel with OK status");
+    cancelled = true;
+    abstractClientStreamSink().cancel(reason);
+  }
+
+  @Override
+  public final boolean isReady() {
+    return super.isReady() && !cancelled;
+  }
+
+  protected TransportTracer getTransportTracer() {
+    return transportTracer;
+  }
+
+  /** This should only called from the transport thread. */
+  protected abstract static class TransportState extends AbstractStream.TransportState {
+    /** Whether listener.closed() has been called. */
+    private final StatsTraceContext statsTraceCtx;
+    private boolean listenerClosed;
+    private ClientStreamListener listener;
+    private boolean fullStreamDecompression;
+    private DecompressorRegistry decompressorRegistry = DecompressorRegistry.getDefaultInstance();
+
+    private boolean deframerClosed = false;
+    private Runnable deframerClosedTask;
+
+    /** Whether the client has half-closed the stream. */
+    private volatile boolean outboundClosed;
+
+    /**
+     * Whether the stream is closed from the transport's perspective. This can differ from {@link
+     * #listenerClosed} because there may still be messages buffered to deliver to the application.
+     */
+    private boolean statusReported;
+    private Metadata trailers;
+    private Status trailerStatus;
+
+    protected TransportState(
+        int maxMessageSize,
+        StatsTraceContext statsTraceCtx,
+        TransportTracer transportTracer) {
+      super(maxMessageSize, statsTraceCtx, transportTracer);
+      this.statsTraceCtx = checkNotNull(statsTraceCtx, "statsTraceCtx");
+    }
+
+    private void setFullStreamDecompression(boolean fullStreamDecompression) {
+      this.fullStreamDecompression = fullStreamDecompression;
+    }
+
+    private void setDecompressorRegistry(DecompressorRegistry decompressorRegistry) {
+      checkState(this.listener == null, "Already called start");
+      this.decompressorRegistry =
+          checkNotNull(decompressorRegistry, "decompressorRegistry");
+    }
+
+    @VisibleForTesting
+    public final void setListener(ClientStreamListener listener) {
+      checkState(this.listener == null, "Already called setListener");
+      this.listener = checkNotNull(listener, "listener");
+    }
+
+    @Override
+    public void deframerClosed(boolean hasPartialMessage) {
+      deframerClosed = true;
+
+      if (trailerStatus != null) {
+        if (trailerStatus.isOk() && hasPartialMessage) {
+          trailerStatus = Status.INTERNAL.withDescription("Encountered end-of-stream mid-frame");
+          trailers = new Metadata();
+        }
+        transportReportStatus(trailerStatus, false, trailers);
+      } else {
+        checkState(statusReported, "status should have been reported on deframer closed");
+      }
+
+      if (deframerClosedTask != null) {
+        deframerClosedTask.run();
+        deframerClosedTask = null;
+      }
+    }
+
+    @Override
+    protected final ClientStreamListener listener() {
+      return listener;
+    }
+
+    private final void setOutboundClosed() {
+      outboundClosed = true;
+    }
+
+    protected final boolean isOutboundClosed() {
+      return outboundClosed;
+    }
+
+    /**
+     * Called by transport implementations when they receive headers.
+     *
+     * @param headers the parsed headers
+     */
+    protected void inboundHeadersReceived(Metadata headers) {
+      checkState(!statusReported, "Received headers on closed stream");
+      statsTraceCtx.clientInboundHeaders();
+
+      boolean compressedStream = false;
+      String streamEncoding = headers.get(CONTENT_ENCODING_KEY);
+      if (fullStreamDecompression && streamEncoding != null) {
+        if (streamEncoding.equalsIgnoreCase("gzip")) {
+          setFullStreamDecompressor(new GzipInflatingBuffer());
+          compressedStream = true;
+        } else if (!streamEncoding.equalsIgnoreCase("identity")) {
+          deframeFailed(
+              Status.INTERNAL
+                  .withDescription(
+                      String.format("Can't find full stream decompressor for %s", streamEncoding))
+                  .asRuntimeException());
+          return;
+        }
+      }
+
+      String messageEncoding = headers.get(MESSAGE_ENCODING_KEY);
+      if (messageEncoding != null) {
+        Decompressor decompressor = decompressorRegistry.lookupDecompressor(messageEncoding);
+        if (decompressor == null) {
+          deframeFailed(
+              Status.INTERNAL
+                  .withDescription(String.format("Can't find decompressor for %s", messageEncoding))
+                  .asRuntimeException());
+          return;
+        } else if (decompressor != Codec.Identity.NONE) {
+          if (compressedStream) {
+            deframeFailed(
+                Status.INTERNAL
+                    .withDescription(
+                        String.format("Full stream and gRPC message encoding cannot both be set"))
+                    .asRuntimeException());
+            return;
+          }
+          setDecompressor(decompressor);
+        }
+      }
+
+      listener().headersRead(headers);
+    }
+
+    /**
+     * Processes the contents of a received data frame from the server.
+     *
+     * @param frame the received data frame. Its ownership is transferred to this method.
+     */
+    protected void inboundDataReceived(ReadableBuffer frame) {
+      checkNotNull(frame, "frame");
+      boolean needToCloseFrame = true;
+      try {
+        if (statusReported) {
+          log.log(Level.INFO, "Received data on closed stream");
+          return;
+        }
+
+        needToCloseFrame = false;
+        deframe(frame);
+      } finally {
+        if (needToCloseFrame) {
+          frame.close();
+        }
+      }
+    }
+
+    /**
+     * Processes the trailers and status from the server.
+     *
+     * @param trailers the received trailers
+     * @param status the status extracted from the trailers
+     */
+    protected void inboundTrailersReceived(Metadata trailers, Status status) {
+      checkNotNull(status, "status");
+      checkNotNull(trailers, "trailers");
+      if (statusReported) {
+        log.log(Level.INFO, "Received trailers on closed stream:\n {1}\n {2}",
+            new Object[]{status, trailers});
+        return;
+      }
+      this.trailers = trailers;
+      trailerStatus = status;
+      closeDeframer(false);
+    }
+
+    /**
+     * Report stream closure with status to the application layer if not already reported. This
+     * method must be called from the transport thread.
+     *
+     * @param status the new status to set
+     * @param stopDelivery if {@code true}, interrupts any further delivery of inbound messages that
+     *        may already be queued up in the deframer. If {@code false}, the listener will be
+     *        notified immediately after all currently completed messages in the deframer have been
+     *        delivered to the application.
+     * @param trailers new instance of {@code Trailers}, either empty or those returned by the
+     *        server
+     */
+    public final void transportReportStatus(final Status status, boolean stopDelivery,
+        final Metadata trailers) {
+      transportReportStatus(status, RpcProgress.PROCESSED, stopDelivery, trailers);
+    }
+
+    /**
+     * Report stream closure with status to the application layer if not already reported. This
+     * method must be called from the transport thread.
+     *
+     * @param status the new status to set
+     * @param rpcProgress RPC progress that the
+     *        {@link ClientStreamListener#closed(Status, RpcProgress, Metadata)}
+     *        will receive
+     * @param stopDelivery if {@code true}, interrupts any further delivery of inbound messages that
+     *        may already be queued up in the deframer. If {@code false}, the listener will be
+     *        notified immediately after all currently completed messages in the deframer have been
+     *        delivered to the application.
+     * @param trailers new instance of {@code Trailers}, either empty or those returned by the
+     *        server
+     */
+    public final void transportReportStatus(
+        final Status status, final RpcProgress rpcProgress, boolean stopDelivery,
+        final Metadata trailers) {
+      checkNotNull(status, "status");
+      checkNotNull(trailers, "trailers");
+      // If stopDelivery, we continue in case previous invocation is waiting for stall
+      if (statusReported && !stopDelivery) {
+        return;
+      }
+      statusReported = true;
+      onStreamDeallocated();
+
+      if (deframerClosed) {
+        deframerClosedTask = null;
+        closeListener(status, rpcProgress, trailers);
+      } else {
+        deframerClosedTask =
+            new Runnable() {
+              @Override
+              public void run() {
+                closeListener(status, rpcProgress, trailers);
+              }
+            };
+        closeDeframer(stopDelivery);
+      }
+    }
+
+    /**
+     * Closes the listener if not previously closed.
+     *
+     * @throws IllegalStateException if the call has not yet been started.
+     */
+    private void closeListener(
+        Status status, RpcProgress rpcProgress, Metadata trailers) {
+      if (!listenerClosed) {
+        listenerClosed = true;
+        statsTraceCtx.streamClosed(status);
+        listener().closed(status, rpcProgress, trailers);
+        if (getTransportTracer() != null) {
+          getTransportTracer().reportStreamClosed(status.isOk());
+        }
+      }
+    }
+  }
+
+  private class GetFramer implements Framer {
+    private Metadata headers;
+    private boolean closed;
+    private final StatsTraceContext statsTraceCtx;
+    private byte[] payload;
+
+    public GetFramer(Metadata headers, StatsTraceContext statsTraceCtx) {
+      this.headers = checkNotNull(headers, "headers");
+      this.statsTraceCtx = checkNotNull(statsTraceCtx, "statsTraceCtx");
+    }
+
+    @Override
+    public void writePayload(InputStream message) {
+      checkState(payload == null, "writePayload should not be called multiple times");
+      try {
+        payload = IoUtils.toByteArray(message);
+      } catch (java.io.IOException ex) {
+        throw new RuntimeException(ex);
+      }
+      statsTraceCtx.outboundMessage(0);
+      statsTraceCtx.outboundMessageSent(0, payload.length, payload.length);
+      statsTraceCtx.outboundUncompressedSize(payload.length);
+      // NB(zhangkun83): this is not accurate, because the underlying transport will probably encode
+      // it using e.g., base64.  However, we are not supposed to know such detail here.
+      //
+      // We don't want to move this line to where the encoding happens either, because we'd better
+      // contain the message stats reporting in Framer as suggested in StatsTraceContext.
+      // Scattering the reporting sites increases the risk of mis-counting or double-counting.
+      //
+      // Because the payload is usually very small, people shouldn't care about the size difference
+      // caused by encoding.
+      statsTraceCtx.outboundWireSize(payload.length);
+    }
+
+    @Override
+    public void flush() {}
+
+    @Override
+    public boolean isClosed() {
+      return closed;
+    }
+
+    /** Closes, with flush. */
+    @Override
+    public void close() {
+      closed = true;
+      checkState(payload != null,
+          "Lack of request message. GET request is only supported for unary requests");
+      abstractClientStreamSink().writeHeaders(headers, payload);
+      payload = null;
+      headers = null;
+    }
+
+    /** Closes, without flush. */
+    @Override
+    public void dispose() {
+      closed = true;
+      payload = null;
+      headers = null;
+    }
+
+    // Compression is not supported for GET encoding.
+    @Override
+    public Framer setMessageCompression(boolean enable) {
+      return this;
+    }
+
+    @Override
+    public Framer setCompressor(Compressor compressor) {
+      return this;
+    }
+
+    // TODO(zsurocking): support this
+    @Override
+    public void setMaxOutboundMessageSize(int maxSize) {}
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java b/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java
new file mode 100644
index 0000000..2f12574
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/AbstractManagedChannelImplBuilder.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.Attributes;
+import io.grpc.BinaryLog;
+import io.grpc.ClientInterceptor;
+import io.grpc.CompressorRegistry;
+import io.grpc.DecompressorRegistry;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.InternalChannelz;
+import io.grpc.LoadBalancer;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.NameResolver;
+import io.grpc.NameResolverProvider;
+import io.opencensus.trace.Tracing;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+/**
+ * The base class for channel builders.
+ *
+ * @param <T> The concrete type of this builder.
+ */
+public abstract class AbstractManagedChannelImplBuilder
+        <T extends AbstractManagedChannelImplBuilder<T>> extends ManagedChannelBuilder<T> {
+  private static final String DIRECT_ADDRESS_SCHEME = "directaddress";
+
+  public static ManagedChannelBuilder<?> forAddress(String name, int port) {
+    throw new UnsupportedOperationException("Subclass failed to hide static factory");
+  }
+
+  public static ManagedChannelBuilder<?> forTarget(String target) {
+    throw new UnsupportedOperationException("Subclass failed to hide static factory");
+  }
+
+  /**
+   * An idle timeout larger than this would disable idle mode.
+   */
+  @VisibleForTesting
+  static final long IDLE_MODE_MAX_TIMEOUT_DAYS = 30;
+
+  /**
+   * The default idle timeout.
+   */
+  @VisibleForTesting
+  static final long IDLE_MODE_DEFAULT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(30);
+
+  /**
+   * An idle timeout smaller than this would be capped to it.
+   */
+  @VisibleForTesting
+  static final long IDLE_MODE_MIN_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(1);
+
+  private static final ObjectPool<? extends Executor> DEFAULT_EXECUTOR_POOL =
+      SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
+
+  private static final NameResolver.Factory DEFAULT_NAME_RESOLVER_FACTORY =
+      NameResolverProvider.asFactory();
+
+  private static final DecompressorRegistry DEFAULT_DECOMPRESSOR_REGISTRY =
+      DecompressorRegistry.getDefaultInstance();
+
+  private static final CompressorRegistry DEFAULT_COMPRESSOR_REGISTRY =
+      CompressorRegistry.getDefaultInstance();
+
+  private static final long DEFAULT_RETRY_BUFFER_SIZE_IN_BYTES = 1L << 24;  // 16M
+  private static final long DEFAULT_PER_RPC_BUFFER_LIMIT_IN_BYTES = 1L << 20; // 1M
+
+  ObjectPool<? extends Executor> executorPool = DEFAULT_EXECUTOR_POOL;
+
+  private final List<ClientInterceptor> interceptors = new ArrayList<>();
+
+  // Access via getter, which may perform authority override as needed
+  private NameResolver.Factory nameResolverFactory = DEFAULT_NAME_RESOLVER_FACTORY;
+
+  final String target;
+
+  @Nullable
+  private final SocketAddress directServerAddress;
+
+  @Nullable
+  String userAgent;
+
+  @VisibleForTesting
+  @Nullable
+  String authorityOverride;
+
+
+  @Nullable LoadBalancer.Factory loadBalancerFactory;
+
+  boolean fullStreamDecompression;
+
+  DecompressorRegistry decompressorRegistry = DEFAULT_DECOMPRESSOR_REGISTRY;
+
+  CompressorRegistry compressorRegistry = DEFAULT_COMPRESSOR_REGISTRY;
+
+  long idleTimeoutMillis = IDLE_MODE_DEFAULT_TIMEOUT_MILLIS;
+
+  int maxRetryAttempts = 5;
+  int maxHedgedAttempts = 5;
+  long retryBufferSize = DEFAULT_RETRY_BUFFER_SIZE_IN_BYTES;
+  long perRpcBufferLimit = DEFAULT_PER_RPC_BUFFER_LIMIT_IN_BYTES;
+  boolean retryEnabled = false; // TODO(zdapeng): default to true
+  // Temporarily disable retry when stats or tracing is enabled to avoid breakage, until we know
+  // what should be the desired behavior for retry + stats/tracing.
+  // TODO(zdapeng): delete me
+  boolean temporarilyDisableRetry;
+
+  InternalChannelz channelz = InternalChannelz.instance();
+  int maxTraceEvents;
+
+  protected TransportTracer.Factory transportTracerFactory = TransportTracer.getDefaultFactory();
+
+  private int maxInboundMessageSize = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
+
+  @Nullable
+  BinaryLog binlog;
+
+  /**
+   * Sets the maximum message size allowed for a single gRPC frame. If an inbound messages
+   * larger than this limit is received it will not be processed and the RPC will fail with
+   * RESOURCE_EXHAUSTED.
+   */
+  // Can be overridden by subclasses.
+  @Override
+  public T maxInboundMessageSize(int max) {
+    checkArgument(max >= 0, "negative max");
+    maxInboundMessageSize = max;
+    return thisT();
+  }
+
+  protected final int maxInboundMessageSize() {
+    return maxInboundMessageSize;
+  }
+
+  private boolean statsEnabled = true;
+  private boolean recordStartedRpcs = true;
+  private boolean recordFinishedRpcs = true;
+  private boolean tracingEnabled = true;
+
+  @Nullable
+  private CensusStatsModule censusStatsOverride;
+
+  protected AbstractManagedChannelImplBuilder(String target) {
+    this.target = Preconditions.checkNotNull(target, "target");
+    this.directServerAddress = null;
+  }
+
+  /**
+   * Returns a target string for the SocketAddress. It is only used as a placeholder, because
+   * DirectAddressNameResolverFactory will not actually try to use it. However, it must be a valid
+   * URI.
+   */
+  @VisibleForTesting
+  static String makeTargetStringForDirectAddress(SocketAddress address) {
+    try {
+      return new URI(DIRECT_ADDRESS_SCHEME, "", "/" + address, null).toString();
+    } catch (URISyntaxException e) {
+      // It should not happen.
+      throw new RuntimeException(e);
+    }
+  }
+
+  protected AbstractManagedChannelImplBuilder(SocketAddress directServerAddress, String authority) {
+    this.target = makeTargetStringForDirectAddress(directServerAddress);
+    this.directServerAddress = directServerAddress;
+    this.nameResolverFactory = new DirectAddressNameResolverFactory(directServerAddress, authority);
+  }
+
+  @Override
+  public final T directExecutor() {
+    return executor(MoreExecutors.directExecutor());
+  }
+
+  @Override
+  public final T executor(Executor executor) {
+    if (executor != null) {
+      this.executorPool = new FixedObjectPool<Executor>(executor);
+    } else {
+      this.executorPool = DEFAULT_EXECUTOR_POOL;
+    }
+    return thisT();
+  }
+
+  @Override
+  public final T intercept(List<ClientInterceptor> interceptors) {
+    this.interceptors.addAll(interceptors);
+    return thisT();
+  }
+
+  @Override
+  public final T intercept(ClientInterceptor... interceptors) {
+    return intercept(Arrays.asList(interceptors));
+  }
+
+  @Override
+  public final T nameResolverFactory(NameResolver.Factory resolverFactory) {
+    Preconditions.checkState(directServerAddress == null,
+        "directServerAddress is set (%s), which forbids the use of NameResolverFactory",
+        directServerAddress);
+    if (resolverFactory != null) {
+      this.nameResolverFactory = resolverFactory;
+    } else {
+      this.nameResolverFactory = DEFAULT_NAME_RESOLVER_FACTORY;
+    }
+    return thisT();
+  }
+
+  @Override
+  public final T loadBalancerFactory(LoadBalancer.Factory loadBalancerFactory) {
+    Preconditions.checkState(directServerAddress == null,
+        "directServerAddress is set (%s), which forbids the use of LoadBalancer.Factory",
+        directServerAddress);
+    this.loadBalancerFactory = loadBalancerFactory;
+    return thisT();
+  }
+
+  @Override
+  public final T enableFullStreamDecompression() {
+    this.fullStreamDecompression = true;
+    return thisT();
+  }
+
+  @Override
+  public final T decompressorRegistry(DecompressorRegistry registry) {
+    if (registry != null) {
+      this.decompressorRegistry = registry;
+    } else {
+      this.decompressorRegistry = DEFAULT_DECOMPRESSOR_REGISTRY;
+    }
+    return thisT();
+  }
+
+  @Override
+  public final T compressorRegistry(CompressorRegistry registry) {
+    if (registry != null) {
+      this.compressorRegistry = registry;
+    } else {
+      this.compressorRegistry = DEFAULT_COMPRESSOR_REGISTRY;
+    }
+    return thisT();
+  }
+
+  @Override
+  public final T userAgent(@Nullable String userAgent) {
+    this.userAgent = userAgent;
+    return thisT();
+  }
+
+  @Override
+  public final T overrideAuthority(String authority) {
+    this.authorityOverride = checkAuthority(authority);
+    return thisT();
+  }
+
+  @Override
+  public final T idleTimeout(long value, TimeUnit unit) {
+    checkArgument(value > 0, "idle timeout is %s, but must be positive", value);
+    // We convert to the largest unit to avoid overflow
+    if (unit.toDays(value) >= IDLE_MODE_MAX_TIMEOUT_DAYS) {
+      // This disables idle mode
+      this.idleTimeoutMillis = ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE;
+    } else {
+      this.idleTimeoutMillis = Math.max(unit.toMillis(value), IDLE_MODE_MIN_TIMEOUT_MILLIS);
+    }
+    return thisT();
+  }
+
+  @Override
+  public final T maxRetryAttempts(int maxRetryAttempts) {
+    this.maxRetryAttempts = maxRetryAttempts;
+    return thisT();
+  }
+
+  @Override
+  public final T maxHedgedAttempts(int maxHedgedAttempts) {
+    this.maxHedgedAttempts = maxHedgedAttempts;
+    return thisT();
+  }
+
+  @Override
+  public final T retryBufferSize(long bytes) {
+    checkArgument(bytes > 0L, "retry buffer size must be positive");
+    retryBufferSize = bytes;
+    return thisT();
+  }
+
+  @Override
+  public final T perRpcBufferLimit(long bytes) {
+    checkArgument(bytes > 0L, "per RPC buffer limit must be positive");
+    perRpcBufferLimit = bytes;
+    return thisT();
+  }
+
+  @Override
+  public final T disableRetry() {
+    retryEnabled = false;
+    return thisT();
+  }
+
+  @Override
+  public final T enableRetry() {
+    retryEnabled = true;
+    return thisT();
+  }
+
+  @Override
+  public final T setBinaryLog(BinaryLog binlog) {
+    this.binlog = binlog;
+    return thisT();
+  }
+
+  @Override
+  public T maxTraceEvents(int maxTraceEvents) {
+    checkArgument(maxTraceEvents >= 0, "maxTraceEvents must be non-negative");
+    this.maxTraceEvents = maxTraceEvents;
+    return thisT();
+  }
+
+  /**
+   * Override the default stats implementation.
+   */
+  @VisibleForTesting
+  protected final T overrideCensusStatsModule(CensusStatsModule censusStats) {
+    this.censusStatsOverride = censusStats;
+    return thisT();
+  }
+
+  /**
+   * Disable or enable stats features.  Enabled by default.
+   */
+  protected void setStatsEnabled(boolean value) {
+    statsEnabled = value;
+  }
+
+  /**
+   * Disable or enable stats recording for RPC upstarts.  Effective only if {@link
+   * #setStatsEnabled} is set to true.  Enabled by default.
+   */
+  protected void setStatsRecordStartedRpcs(boolean value) {
+    recordStartedRpcs = value;
+  }
+
+  /**
+   * Disable or enable stats recording for RPC completions.  Effective only if {@link
+   * #setStatsEnabled} is set to true.  Enabled by default.
+   */
+  protected void setStatsRecordFinishedRpcs(boolean value) {
+    recordFinishedRpcs = value;
+  }
+
+  /**
+   * Disable or enable tracing features.  Enabled by default.
+   */
+  protected void setTracingEnabled(boolean value) {
+    tracingEnabled = value;
+  }
+
+  @VisibleForTesting
+  final long getIdleTimeoutMillis() {
+    return idleTimeoutMillis;
+  }
+
+  /**
+   * Verifies the authority is valid.  This method exists as an escape hatch for putting in an
+   * authority that is valid, but would fail the default validation provided by this
+   * implementation.
+   */
+  protected String checkAuthority(String authority) {
+    return GrpcUtil.checkAuthority(authority);
+  }
+
+  @Override
+  public ManagedChannel build() {
+    return new ManagedChannelOrphanWrapper(new ManagedChannelImpl(
+        this,
+        buildTransportFactory(),
+        // TODO(carl-mastrangelo): Allow clients to pass this in
+        new ExponentialBackoffPolicy.Provider(),
+        SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR),
+        GrpcUtil.STOPWATCH_SUPPLIER,
+        getEffectiveInterceptors(),
+        TimeProvider.SYSTEM_TIME_PROVIDER));
+  }
+
+  // Temporarily disable retry when stats or tracing is enabled to avoid breakage, until we know
+  // what should be the desired behavior for retry + stats/tracing.
+  // TODO(zdapeng): FIX IT
+  @VisibleForTesting
+  final List<ClientInterceptor> getEffectiveInterceptors() {
+    List<ClientInterceptor> effectiveInterceptors =
+        new ArrayList<>(this.interceptors);
+    temporarilyDisableRetry = false;
+    if (statsEnabled) {
+      temporarilyDisableRetry = true;
+      CensusStatsModule censusStats = this.censusStatsOverride;
+      if (censusStats == null) {
+        censusStats = new CensusStatsModule(GrpcUtil.STOPWATCH_SUPPLIER, true);
+      }
+      // First interceptor runs last (see ClientInterceptors.intercept()), so that no
+      // other interceptor can override the tracer factory we set in CallOptions.
+      effectiveInterceptors.add(
+          0, censusStats.getClientInterceptor(recordStartedRpcs, recordFinishedRpcs));
+    }
+    if (tracingEnabled) {
+      temporarilyDisableRetry = true;
+      CensusTracingModule censusTracing =
+          new CensusTracingModule(Tracing.getTracer(),
+              Tracing.getPropagationComponent().getBinaryFormat());
+      effectiveInterceptors.add(0, censusTracing.getClientInterceptor());
+    }
+    return effectiveInterceptors;
+  }
+
+  /**
+   * Subclasses should override this method to provide the {@link ClientTransportFactory}
+   * appropriate for this channel. This method is meant for Transport implementors and should not
+   * be used by normal users.
+   */
+  protected abstract ClientTransportFactory buildTransportFactory();
+
+  /**
+   * Subclasses can override this method to provide additional parameters to {@link
+   * NameResolver.Factory#newNameResolver}. The default implementation returns {@link
+   * Attributes#EMPTY}.
+   */
+  protected Attributes getNameResolverParams() {
+    return Attributes.EMPTY;
+  }
+
+  /**
+   * Returns a {@link NameResolver.Factory} for the channel.
+   */
+  NameResolver.Factory getNameResolverFactory() {
+    if (authorityOverride == null) {
+      return nameResolverFactory;
+    } else {
+      return new OverrideAuthorityNameResolverFactory(nameResolverFactory, authorityOverride);
+    }
+  }
+
+  private static class DirectAddressNameResolverFactory extends NameResolver.Factory {
+    final SocketAddress address;
+    final String authority;
+
+    DirectAddressNameResolverFactory(SocketAddress address, String authority) {
+      this.address = address;
+      this.authority = authority;
+    }
+
+    @Override
+    public NameResolver newNameResolver(URI notUsedUri, Attributes params) {
+      return new NameResolver() {
+        @Override
+        public String getServiceAuthority() {
+          return authority;
+        }
+
+        @Override
+        public void start(final Listener listener) {
+          listener.onAddresses(
+              Collections.singletonList(new EquivalentAddressGroup(address)),
+              Attributes.EMPTY);
+        }
+
+        @Override
+        public void shutdown() {}
+      };
+    }
+
+    @Override
+    public String getDefaultScheme() {
+      return DIRECT_ADDRESS_SCHEME;
+    }
+  }
+
+  /**
+   * Returns the correctly typed version of the builder.
+   */
+  private T thisT() {
+    @SuppressWarnings("unchecked")
+    T thisT = (T) this;
+    return thisT;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/AbstractReadableBuffer.java b/core/src/main/java/io/grpc/internal/AbstractReadableBuffer.java
new file mode 100644
index 0000000..e43b7a7
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/AbstractReadableBuffer.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+/**
+ * Abstract base class for {@link ReadableBuffer} implementations.
+ */
+public abstract class AbstractReadableBuffer implements ReadableBuffer {
+  @Override
+  public final int readInt() {
+    checkReadable(4);
+    int b1 = readUnsignedByte();
+    int b2 = readUnsignedByte();
+    int b3 = readUnsignedByte();
+    int b4 = readUnsignedByte();
+    return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
+  }
+
+  @Override
+  public boolean hasArray() {
+    return false;
+  }
+
+  @Override
+  public byte[] array() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public int arrayOffset() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void close() {}
+
+  protected final void checkReadable(int length) {
+    if (readableBytes() < length) {
+      throw new IndexOutOfBoundsException();
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java b/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java
new file mode 100644
index 0000000..72744d8
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/AbstractServerImplBuilder.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.BinaryLog;
+import io.grpc.BindableService;
+import io.grpc.CompressorRegistry;
+import io.grpc.Context;
+import io.grpc.DecompressorRegistry;
+import io.grpc.HandlerRegistry;
+import io.grpc.Internal;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalNotifyOnServerBuild;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.ServerInterceptor;
+import io.grpc.ServerMethodDefinition;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.ServerStreamTracer;
+import io.grpc.ServerTransportFilter;
+import io.opencensus.trace.Tracing;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+/**
+ * The base class for server builders.
+ *
+ * @param <T> The concrete type for this builder.
+ */
+public abstract class AbstractServerImplBuilder<T extends AbstractServerImplBuilder<T>>
+        extends ServerBuilder<T> {
+
+  public static ServerBuilder<?> forPort(int port) {
+    throw new UnsupportedOperationException("Subclass failed to hide static factory");
+  }
+
+  private static final ObjectPool<? extends Executor> DEFAULT_EXECUTOR_POOL =
+      SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
+  private static final HandlerRegistry DEFAULT_FALLBACK_REGISTRY = new HandlerRegistry() {
+      @Override
+      public List<ServerServiceDefinition> getServices() {
+        return Collections.emptyList();
+      }
+
+      @Override
+      @Nullable
+      public ServerMethodDefinition<?, ?> lookupMethod(
+          String methodName, @Nullable String authority) {
+        return null;
+      }
+    };
+  private static final DecompressorRegistry DEFAULT_DECOMPRESSOR_REGISTRY =
+      DecompressorRegistry.getDefaultInstance();
+  private static final CompressorRegistry DEFAULT_COMPRESSOR_REGISTRY =
+      CompressorRegistry.getDefaultInstance();
+  private static final long DEFAULT_HANDSHAKE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(120);
+
+  final InternalHandlerRegistry.Builder registryBuilder =
+      new InternalHandlerRegistry.Builder();
+
+  final List<ServerTransportFilter> transportFilters =
+      new ArrayList<>();
+
+  final List<ServerInterceptor> interceptors = new ArrayList<>();
+
+  private final List<InternalNotifyOnServerBuild> notifyOnBuildList =
+      new ArrayList<>();
+
+  private final List<ServerStreamTracer.Factory> streamTracerFactories =
+      new ArrayList<ServerStreamTracer.Factory>();
+
+  HandlerRegistry fallbackRegistry = DEFAULT_FALLBACK_REGISTRY;
+
+  ObjectPool<? extends Executor> executorPool = DEFAULT_EXECUTOR_POOL;
+
+  DecompressorRegistry decompressorRegistry = DEFAULT_DECOMPRESSOR_REGISTRY;
+
+  CompressorRegistry compressorRegistry = DEFAULT_COMPRESSOR_REGISTRY;
+
+  long handshakeTimeoutMillis = DEFAULT_HANDSHAKE_TIMEOUT_MILLIS;
+
+  @Nullable
+  private CensusStatsModule censusStatsOverride;
+
+  private boolean statsEnabled = true;
+  private boolean recordStartedRpcs = true;
+  private boolean recordFinishedRpcs = true;
+  private boolean tracingEnabled = true;
+
+  @Nullable
+  protected BinaryLog binlog;
+  protected TransportTracer.Factory transportTracerFactory = TransportTracer.getDefaultFactory();
+
+  protected InternalChannelz channelz = InternalChannelz.instance();
+  protected CallTracer.Factory callTracerFactory = CallTracer.getDefaultFactory();
+
+  @Override
+  public final T directExecutor() {
+    return executor(MoreExecutors.directExecutor());
+  }
+
+  @Override
+  public final T executor(@Nullable Executor executor) {
+    if (executor != null) {
+      this.executorPool = new FixedObjectPool<Executor>(executor);
+    } else {
+      this.executorPool = DEFAULT_EXECUTOR_POOL;
+    }
+    return thisT();
+  }
+
+  @Override
+  public final T addService(ServerServiceDefinition service) {
+    registryBuilder.addService(service);
+    return thisT();
+  }
+
+  @Override
+  public final T addService(BindableService bindableService) {
+    if (bindableService instanceof InternalNotifyOnServerBuild) {
+      notifyOnBuildList.add((InternalNotifyOnServerBuild) bindableService);
+    }
+    return addService(bindableService.bindService());
+  }
+
+  @Override
+  public final T addTransportFilter(ServerTransportFilter filter) {
+    transportFilters.add(checkNotNull(filter, "filter"));
+    return thisT();
+  }
+
+  @Override
+  public final T intercept(ServerInterceptor interceptor) {
+    interceptors.add(interceptor);
+    return thisT();
+  }
+
+  @Override
+  public final T addStreamTracerFactory(ServerStreamTracer.Factory factory) {
+    streamTracerFactories.add(checkNotNull(factory, "factory"));
+    return thisT();
+  }
+
+  @Override
+  public final T fallbackHandlerRegistry(HandlerRegistry registry) {
+    if (registry != null) {
+      this.fallbackRegistry = registry;
+    } else {
+      this.fallbackRegistry = DEFAULT_FALLBACK_REGISTRY;
+    }
+    return thisT();
+  }
+
+  @Override
+  public final T decompressorRegistry(DecompressorRegistry registry) {
+    if (registry != null) {
+      decompressorRegistry = registry;
+    } else {
+      decompressorRegistry = DEFAULT_DECOMPRESSOR_REGISTRY;
+    }
+    return thisT();
+  }
+
+  @Override
+  public final T compressorRegistry(CompressorRegistry registry) {
+    if (registry != null) {
+      compressorRegistry = registry;
+    } else {
+      compressorRegistry = DEFAULT_COMPRESSOR_REGISTRY;
+    }
+    return thisT();
+  }
+
+  @Override
+  public final T handshakeTimeout(long timeout, TimeUnit unit) {
+    checkArgument(timeout > 0, "handshake timeout is %s, but must be positive", timeout);
+    handshakeTimeoutMillis = unit.toMillis(timeout);
+    return thisT();
+  }
+
+  @Override
+  public final T setBinaryLog(BinaryLog binaryLog) {
+    this.binlog = binaryLog;
+    return thisT();
+  }
+
+  /**
+   * Override the default stats implementation.
+   */
+  @VisibleForTesting
+  protected T overrideCensusStatsModule(CensusStatsModule censusStats) {
+    this.censusStatsOverride = censusStats;
+    return thisT();
+  }
+
+  /**
+   * Disable or enable stats features.  Enabled by default.
+   */
+  protected void setStatsEnabled(boolean value) {
+    statsEnabled = value;
+  }
+
+  /**
+   * Disable or enable stats recording for RPC upstarts.  Effective only if {@link
+   * #setStatsEnabled} is set to true.  Enabled by default.
+   */
+  protected void setStatsRecordStartedRpcs(boolean value) {
+    recordStartedRpcs = value;
+  }
+
+  /**
+   * Disable or enable stats recording for RPC completions.  Effective only if {@link
+   * #setStatsEnabled} is set to true.  Enabled by default.
+   */
+  protected void setStatsRecordFinishedRpcs(boolean value) {
+    recordFinishedRpcs = value;
+  }
+
+  /**
+   * Disable or enable tracing features.  Enabled by default.
+   */
+  protected void setTracingEnabled(boolean value) {
+    tracingEnabled = value;
+  }
+
+  @Override
+  public Server build() {
+    ServerImpl server = new ServerImpl(
+        this,
+        buildTransportServer(Collections.unmodifiableList(getTracerFactories())),
+        Context.ROOT);
+    for (InternalNotifyOnServerBuild notifyTarget : notifyOnBuildList) {
+      notifyTarget.notifyOnBuild(server);
+    }
+    return server;
+  }
+
+  @VisibleForTesting
+  final List<ServerStreamTracer.Factory> getTracerFactories() {
+    ArrayList<ServerStreamTracer.Factory> tracerFactories =
+        new ArrayList<ServerStreamTracer.Factory>();
+    if (statsEnabled) {
+      CensusStatsModule censusStats = this.censusStatsOverride;
+      if (censusStats == null) {
+        censusStats = new CensusStatsModule(GrpcUtil.STOPWATCH_SUPPLIER, true);
+      }
+      tracerFactories.add(
+          censusStats.getServerTracerFactory(recordStartedRpcs, recordFinishedRpcs));
+    }
+    if (tracingEnabled) {
+      CensusTracingModule censusTracing =
+          new CensusTracingModule(Tracing.getTracer(),
+              Tracing.getPropagationComponent().getBinaryFormat());
+      tracerFactories.add(censusTracing.getServerTracerFactory());
+    }
+    tracerFactories.addAll(streamTracerFactories);
+    return tracerFactories;
+  }
+
+  /**
+   * Children of AbstractServerBuilder should override this method to provide transport specific
+   * information for the server.  This method is mean for Transport implementors and should not be
+   * used by normal users.
+   *
+   * @param streamTracerFactories an immutable list of stream tracer factories
+   */
+  @Internal
+  protected abstract io.grpc.internal.InternalServer buildTransportServer(
+      List<ServerStreamTracer.Factory> streamTracerFactories);
+
+  private T thisT() {
+    @SuppressWarnings("unchecked")
+    T thisT = (T) this;
+    return thisT;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/AbstractServerStream.java b/core/src/main/java/io/grpc/internal/AbstractServerStream.java
new file mode 100644
index 0000000..562dbc4
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/AbstractServerStream.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.Preconditions;
+import io.grpc.Attributes;
+import io.grpc.Decompressor;
+import io.grpc.InternalStatus;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import javax.annotation.Nullable;
+
+/**
+ * Abstract base class for {@link ServerStream} implementations. Extending classes only need to
+ * implement {@link #transportState()} and {@link #abstractServerStreamSink()}. Must only be called
+ * from the sending application thread.
+ */
+public abstract class AbstractServerStream extends AbstractStream
+    implements ServerStream, MessageFramer.Sink {
+  /**
+   * A sink for outbound operations, separated from the stream simply to avoid name
+   * collisions/confusion. Only called from application thread.
+   */
+  protected interface Sink {
+    /**
+     * Sends response headers to the remote end point.
+     *
+     * @param headers the headers to be sent to client.
+     */
+    void writeHeaders(Metadata headers);
+
+    /**
+     * Sends an outbound frame to the remote end point.
+     *
+     * @param frame a buffer containing the chunk of data to be sent.
+     * @param flush {@code true} if more data may not be arriving soon
+     * @param numMessages the number of messages this frame represents
+     */
+    void writeFrame(@Nullable WritableBuffer frame, boolean flush, int numMessages);
+
+    /**
+     * Sends trailers to the remote end point. This call implies end of stream.
+     *
+     * @param trailers metadata to be sent to the end point
+     * @param headersSent {@code true} if response headers have already been sent.
+     * @param status the status that the call ended with
+     */
+    void writeTrailers(Metadata trailers, boolean headersSent, Status status);
+
+    /**
+     * Requests up to the given number of messages from the call to be delivered. This should end up
+     * triggering {@link TransportState#requestMessagesFromDeframer(int)} on the transport thread.
+     */
+    void request(int numMessages);
+
+    /**
+     * Tears down the stream, typically in the event of a timeout. This method may be called
+     * multiple times and from any thread.
+     *
+     * <p>This is a clone of {@link ServerStream#cancel(Status)}.
+     */
+    void cancel(Status status);
+  }
+
+  private final MessageFramer framer;
+  private final StatsTraceContext statsTraceCtx;
+  private boolean outboundClosed;
+  private boolean headersSent;
+
+  protected AbstractServerStream(
+      WritableBufferAllocator bufferAllocator, StatsTraceContext statsTraceCtx) {
+    this.statsTraceCtx = Preconditions.checkNotNull(statsTraceCtx, "statsTraceCtx");
+    framer = new MessageFramer(this, bufferAllocator, statsTraceCtx);
+  }
+
+  @Override
+  protected abstract TransportState transportState();
+
+  /**
+   * Sink for transport to be called to perform outbound operations. Each stream must have its own
+   * unique sink.
+   */
+  protected abstract Sink abstractServerStreamSink();
+
+  @Override
+  protected final MessageFramer framer() {
+    return framer;
+  }
+
+  @Override
+  public final void request(int numMessages) {
+    abstractServerStreamSink().request(numMessages);
+  }
+
+  @Override
+  public final void writeHeaders(Metadata headers) {
+    Preconditions.checkNotNull(headers, "headers");
+
+    headersSent = true;
+    abstractServerStreamSink().writeHeaders(headers);
+  }
+
+  @Override
+  public final void deliverFrame(
+      WritableBuffer frame, boolean endOfStream, boolean flush, int numMessages) {
+    // Since endOfStream is triggered by the sending of trailers, avoid flush here and just flush
+    // after the trailers.
+    abstractServerStreamSink().writeFrame(frame, endOfStream ? false : flush, numMessages);
+  }
+
+  @Override
+  public final void close(Status status, Metadata trailers) {
+    Preconditions.checkNotNull(status, "status");
+    Preconditions.checkNotNull(trailers, "trailers");
+    if (!outboundClosed) {
+      outboundClosed = true;
+      endOfMessages();
+      addStatusToTrailers(trailers, status);
+      // Safe to set without synchronization because access is tightly controlled.
+      // closedStatus is only set from here, and is read from a place that has happen-after
+      // guarantees with respect to here.
+      transportState().setClosedStatus(status);
+      abstractServerStreamSink().writeTrailers(trailers, headersSent, status);
+    }
+  }
+
+  private void addStatusToTrailers(Metadata trailers, Status status) {
+    trailers.discardAll(InternalStatus.CODE_KEY);
+    trailers.discardAll(InternalStatus.MESSAGE_KEY);
+    trailers.put(InternalStatus.CODE_KEY, status);
+    if (status.getDescription() != null) {
+      trailers.put(InternalStatus.MESSAGE_KEY, status.getDescription());
+    }
+  }
+
+  @Override
+  public final void cancel(Status status) {
+    abstractServerStreamSink().cancel(status);
+  }
+
+  @Override
+  public final boolean isReady() {
+    return super.isReady();
+  }
+
+  @Override
+  public final void setDecompressor(Decompressor decompressor) {
+    transportState().setDecompressor(Preconditions.checkNotNull(decompressor, "decompressor"));
+  }
+
+  @Override public Attributes getAttributes() {
+    return Attributes.EMPTY;
+  }
+
+  @Override
+  public String getAuthority() {
+    return null;
+  }
+
+  @Override
+  public final void setListener(ServerStreamListener serverStreamListener) {
+    transportState().setListener(serverStreamListener);
+  }
+
+  @Override
+  public StatsTraceContext statsTraceContext() {
+    return statsTraceCtx;
+  }
+
+  /**
+   * This should only called from the transport thread (except for private interactions with
+   * {@code AbstractServerStream}).
+   */
+  protected abstract static class TransportState extends AbstractStream.TransportState {
+    /** Whether listener.closed() has been called. */
+    private boolean listenerClosed;
+    private ServerStreamListener listener;
+    private final StatsTraceContext statsTraceCtx;
+
+    private boolean endOfStream = false;
+    private boolean deframerClosed = false;
+    private boolean immediateCloseRequested = false;
+    private Runnable deframerClosedTask;
+    /** The status that the application used to close this stream. */
+    @Nullable
+    private Status closedStatus;
+
+    protected TransportState(
+        int maxMessageSize,
+        StatsTraceContext statsTraceCtx,
+        TransportTracer transportTracer) {
+      super(
+          maxMessageSize,
+          statsTraceCtx,
+          Preconditions.checkNotNull(transportTracer, "transportTracer"));
+      this.statsTraceCtx = Preconditions.checkNotNull(statsTraceCtx, "statsTraceCtx");
+    }
+
+    /**
+     * Sets the listener to receive notifications. Must be called in the context of the transport
+     * thread.
+     */
+    public final void setListener(ServerStreamListener listener) {
+      Preconditions.checkState(this.listener == null, "setListener should be called only once");
+      this.listener = Preconditions.checkNotNull(listener, "listener");
+    }
+
+    @Override
+    public final void onStreamAllocated() {
+      super.onStreamAllocated();
+      getTransportTracer().reportRemoteStreamStarted();
+    }
+
+    @Override
+    public void deframerClosed(boolean hasPartialMessage) {
+      deframerClosed = true;
+      if (endOfStream) {
+        if (!immediateCloseRequested && hasPartialMessage) {
+          // We've received the entire stream and have data available but we don't have
+          // enough to read the next frame ... this is bad.
+          deframeFailed(
+              Status.INTERNAL
+                  .withDescription("Encountered end-of-stream mid-frame")
+                  .asRuntimeException());
+          deframerClosedTask = null;
+          return;
+        }
+        listener.halfClosed();
+      }
+      if (deframerClosedTask != null) {
+        deframerClosedTask.run();
+        deframerClosedTask = null;
+      }
+    }
+
+    @Override
+    protected ServerStreamListener listener() {
+      return listener;
+    }
+
+    /**
+     * Called in the transport thread to process the content of an inbound DATA frame from the
+     * client.
+     *
+     * @param frame the inbound HTTP/2 DATA frame. If this buffer is not used immediately, it must
+     *              be retained.
+     * @param endOfStream {@code true} if no more data will be received on the stream.
+     */
+    public void inboundDataReceived(ReadableBuffer frame, boolean endOfStream) {
+      Preconditions.checkState(!this.endOfStream, "Past end of stream");
+      // Deframe the message. If a failure occurs, deframeFailed will be called.
+      deframe(frame);
+      if (endOfStream) {
+        this.endOfStream = true;
+        closeDeframer(false);
+      }
+    }
+
+    /**
+     * Notifies failure to the listener of the stream. The transport is responsible for notifying
+     * the client of the failure independent of this method.
+     *
+     * <p>Unlike {@link #close(Status, Metadata)}, this method is only called from the
+     * transport. The transport should use this method instead of {@code close(Status)} for internal
+     * errors to prevent exposing unexpected states and exceptions to the application.
+     *
+     * @param status the error status. Must not be {@link Status#OK}.
+     */
+    public final void transportReportStatus(final Status status) {
+      Preconditions.checkArgument(!status.isOk(), "status must not be OK");
+      if (deframerClosed) {
+        deframerClosedTask = null;
+        closeListener(status);
+      } else {
+        deframerClosedTask =
+            new Runnable() {
+              @Override
+              public void run() {
+                closeListener(status);
+              }
+            };
+        immediateCloseRequested = true;
+        closeDeframer(true);
+      }
+    }
+
+    /**
+     * Indicates the stream is considered completely closed and there is no further opportunity for
+     * error. It calls the listener's {@code closed()} if it was not already done by {@link
+     * #transportReportStatus}.
+     */
+    public void complete() {
+      if (deframerClosed) {
+        deframerClosedTask = null;
+        closeListener(Status.OK);
+      } else {
+        deframerClosedTask =
+            new Runnable() {
+              @Override
+              public void run() {
+                closeListener(Status.OK);
+              }
+            };
+        immediateCloseRequested = true;
+        closeDeframer(true);
+      }
+    }
+
+    /**
+     * Closes the listener if not previously closed and frees resources. {@code newStatus} is a
+     * status generated by gRPC. It is <b>not</b> the status the stream closed with.
+     */
+    private void closeListener(Status newStatus) {
+      // If newStatus is OK, the application must have already called AbstractServerStream.close()
+      // and the status passed in there was the actual status of the RPC.
+      // If newStatus non-OK, then the RPC ended some other way and the server application did
+      // not initiate the termination.
+      Preconditions.checkState(!newStatus.isOk() || closedStatus != null);
+      if (!listenerClosed) {
+        if (!newStatus.isOk()) {
+          statsTraceCtx.streamClosed(newStatus);
+          getTransportTracer().reportStreamClosed(false);
+        } else {
+          statsTraceCtx.streamClosed(closedStatus);
+          getTransportTracer().reportStreamClosed(closedStatus.isOk());
+        }
+        listenerClosed = true;
+        onStreamDeallocated();
+        listener().closed(newStatus);
+      }
+    }
+
+    /**
+     * Stores the {@code Status} that the application used to close this stream.
+     */
+    private void setClosedStatus(Status closeStatus) {
+      Preconditions.checkState(closedStatus == null, "closedStatus can only be set once");
+      closedStatus = closeStatus;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/AbstractStream.java b/core/src/main/java/io/grpc/internal/AbstractStream.java
new file mode 100644
index 0000000..ff9f376
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/AbstractStream.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.Codec;
+import io.grpc.Compressor;
+import io.grpc.Decompressor;
+import java.io.InputStream;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * The stream and stream state as used by the application. Must only be called from the sending
+ * application thread.
+ */
+public abstract class AbstractStream implements Stream {
+  /** The framer to use for sending messages. */
+  protected abstract Framer framer();
+
+  /**
+   * Obtain the transport state corresponding to this stream. Each stream must have its own unique
+   * transport state.
+   */
+  protected abstract TransportState transportState();
+
+  @Override
+  public final void setMessageCompression(boolean enable) {
+    framer().setMessageCompression(enable);
+  }
+
+  @Override
+  public final void writeMessage(InputStream message) {
+    checkNotNull(message, "message");
+    try {
+      if (!framer().isClosed()) {
+        framer().writePayload(message);
+      }
+    } finally {
+      GrpcUtil.closeQuietly(message);
+    }
+  }
+
+  @Override
+  public final void flush() {
+    if (!framer().isClosed()) {
+      framer().flush();
+    }
+  }
+
+  /**
+   * Closes the underlying framer. Should be called when the outgoing stream is gracefully closed
+   * (half closure on client; closure on server).
+   */
+  protected final void endOfMessages() {
+    framer().close();
+  }
+
+  @Override
+  public final void setCompressor(Compressor compressor) {
+    framer().setCompressor(checkNotNull(compressor, "compressor"));
+  }
+
+  @Override
+  public boolean isReady() {
+    if (framer().isClosed()) {
+      return false;
+    }
+    return transportState().isReady();
+  }
+
+  /**
+   * Event handler to be called by the subclass when a number of bytes are being queued for sending
+   * to the remote endpoint.
+   *
+   * @param numBytes the number of bytes being sent.
+   */
+  protected final void onSendingBytes(int numBytes) {
+    transportState().onSendingBytes(numBytes);
+  }
+
+  /**
+   * Stream state as used by the transport. This should only called from the transport thread
+   * (except for private interactions with {@code AbstractStream}).
+   */
+  public abstract static class TransportState
+      implements ApplicationThreadDeframer.TransportExecutor, MessageDeframer.Listener {
+    /**
+     * The default number of queued bytes for a given stream, below which
+     * {@link StreamListener#onReady()} will be called.
+     */
+    @VisibleForTesting
+    public static final int DEFAULT_ONREADY_THRESHOLD = 32 * 1024;
+
+    private Deframer deframer;
+    private final Object onReadyLock = new Object();
+    private final StatsTraceContext statsTraceCtx;
+    private final TransportTracer transportTracer;
+
+    /**
+     * The number of bytes currently queued, waiting to be sent. When this falls below
+     * DEFAULT_ONREADY_THRESHOLD, {@link StreamListener#onReady()} will be called.
+     */
+    @GuardedBy("onReadyLock")
+    private int numSentBytesQueued;
+    /**
+     * Indicates the stream has been created on the connection. This implies that the stream is no
+     * longer limited by MAX_CONCURRENT_STREAMS.
+     */
+    @GuardedBy("onReadyLock")
+    private boolean allocated;
+    /**
+     * Indicates that the stream no longer exists for the transport. Implies that the application
+     * should be discouraged from sending, because doing so would have no effect.
+     */
+    @GuardedBy("onReadyLock")
+    private boolean deallocated;
+
+    protected TransportState(
+        int maxMessageSize,
+        StatsTraceContext statsTraceCtx,
+        TransportTracer transportTracer) {
+      this.statsTraceCtx = checkNotNull(statsTraceCtx, "statsTraceCtx");
+      this.transportTracer = checkNotNull(transportTracer, "transportTracer");
+      deframer = new MessageDeframer(
+          this,
+          Codec.Identity.NONE,
+          maxMessageSize,
+          statsTraceCtx,
+          transportTracer);
+    }
+
+    protected void setFullStreamDecompressor(GzipInflatingBuffer fullStreamDecompressor) {
+      deframer.setFullStreamDecompressor(fullStreamDecompressor);
+      deframer = new ApplicationThreadDeframer(this, this, (MessageDeframer) deframer);
+    }
+
+    final void setMaxInboundMessageSize(int maxSize) {
+      deframer.setMaxInboundMessageSize(maxSize);
+    }
+
+    /**
+     * Override this method to provide a stream listener.
+     */
+    protected abstract StreamListener listener();
+
+    @Override
+    public void messagesAvailable(StreamListener.MessageProducer producer) {
+      listener().messagesAvailable(producer);
+    }
+
+    /**
+     * Closes the deframer and frees any resources. After this method is called, additional calls
+     * will have no effect.
+     *
+     * <p>When {@code stopDelivery} is false, the deframer will wait to close until any already
+     * queued messages have been delivered.
+     *
+     * <p>The deframer will invoke {@link #deframerClosed(boolean)} upon closing.
+     *
+     * @param stopDelivery interrupt pending deliveries and close immediately
+     */
+    protected final void closeDeframer(boolean stopDelivery) {
+      if (stopDelivery) {
+        deframer.close();
+      } else {
+        deframer.closeWhenComplete();
+      }
+    }
+
+    /**
+     * Called to parse a received frame and attempt delivery of any completed messages. Must be
+     * called from the transport thread.
+     */
+    protected final void deframe(final ReadableBuffer frame) {
+      try {
+        deframer.deframe(frame);
+      } catch (Throwable t) {
+        deframeFailed(t);
+      }
+    }
+
+    /**
+     * Called to request the given number of messages from the deframer. Must be called from the
+     * transport thread.
+     */
+    public final void requestMessagesFromDeframer(final int numMessages) {
+      try {
+        deframer.request(numMessages);
+      } catch (Throwable t) {
+        deframeFailed(t);
+      }
+    }
+
+    public final StatsTraceContext getStatsTraceContext() {
+      return statsTraceCtx;
+    }
+
+    protected final void setDecompressor(Decompressor decompressor) {
+      deframer.setDecompressor(decompressor);
+    }
+
+    private boolean isReady() {
+      synchronized (onReadyLock) {
+        return allocated && numSentBytesQueued < DEFAULT_ONREADY_THRESHOLD && !deallocated;
+      }
+    }
+
+    /**
+     * Event handler to be called by the subclass when the stream's headers have passed any
+     * connection flow control (i.e., MAX_CONCURRENT_STREAMS). It may call the listener's {@link
+     * StreamListener#onReady()} handler if appropriate. This must be called from the transport
+     * thread, since the listener may be called back directly.
+     */
+    protected void onStreamAllocated() {
+      checkState(listener() != null);
+      synchronized (onReadyLock) {
+        checkState(!allocated, "Already allocated");
+        allocated = true;
+      }
+      notifyIfReady();
+    }
+
+    /**
+     * Notify that the stream does not exist in a usable state any longer. This causes {@link
+     * AbstractStream#isReady()} to return {@code false} from this point forward.
+     *
+     * <p>This does not generally need to be called explicitly by the transport, as it is handled
+     * implicitly by {@link AbstractClientStream} and {@link AbstractServerStream}.
+     */
+    protected final void onStreamDeallocated() {
+      synchronized (onReadyLock) {
+        deallocated = true;
+      }
+    }
+
+    /**
+     * Event handler to be called by the subclass when a number of bytes are being queued for
+     * sending to the remote endpoint.
+     *
+     * @param numBytes the number of bytes being sent.
+     */
+    private void onSendingBytes(int numBytes) {
+      synchronized (onReadyLock) {
+        numSentBytesQueued += numBytes;
+      }
+    }
+
+    /**
+     * Event handler to be called by the subclass when a number of bytes has been sent to the remote
+     * endpoint. May call back the listener's {@link StreamListener#onReady()} handler if
+     * appropriate.  This must be called from the transport thread, since the listener may be called
+     * back directly.
+     *
+     * @param numBytes the number of bytes that were sent.
+     */
+    public final void onSentBytes(int numBytes) {
+      boolean doNotify;
+      synchronized (onReadyLock) {
+        checkState(allocated,
+            "onStreamAllocated was not called, but it seems the stream is active");
+        boolean belowThresholdBefore = numSentBytesQueued < DEFAULT_ONREADY_THRESHOLD;
+        numSentBytesQueued -= numBytes;
+        boolean belowThresholdAfter = numSentBytesQueued < DEFAULT_ONREADY_THRESHOLD;
+        doNotify = !belowThresholdBefore && belowThresholdAfter;
+      }
+      if (doNotify) {
+        notifyIfReady();
+      }
+    }
+
+    protected TransportTracer getTransportTracer() {
+      return transportTracer;
+    }
+
+    private void notifyIfReady() {
+      boolean doNotify;
+      synchronized (onReadyLock) {
+        doNotify = isReady();
+      }
+      if (doNotify) {
+        listener().onReady();
+      }
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/AbstractSubchannel.java b/core/src/main/java/io/grpc/internal/AbstractSubchannel.java
new file mode 100644
index 0000000..2d36e7a
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/AbstractSubchannel.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.InternalChannelz.ChannelStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.LoadBalancer;
+import javax.annotation.Nullable;
+
+/**
+ * The base interface of the Subchannels returned by {@link
+ * io.grpc.LoadBalancer.Helper#createSubchannel}.
+ */
+abstract class AbstractSubchannel extends LoadBalancer.Subchannel {
+
+  /**
+   * Same as {@link InternalSubchannel#obtainActiveTransport}.
+   */
+  @Nullable
+  abstract ClientTransport obtainActiveTransport();
+
+  /**
+   * Returns the InternalSubchannel as an {@code Instrumented<T>} for the sole purpose of channelz
+   * unit tests.
+   */
+  @VisibleForTesting
+  abstract InternalInstrumented<ChannelStats> getInternalSubchannel();
+}
diff --git a/core/src/main/java/io/grpc/internal/ApplicationThreadDeframer.java b/core/src/main/java/io/grpc/internal/ApplicationThreadDeframer.java
new file mode 100644
index 0000000..1efe472
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ApplicationThreadDeframer.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.grpc.Decompressor;
+import java.io.InputStream;
+import java.util.ArrayDeque;
+import java.util.Queue;
+import javax.annotation.Nullable;
+
+/**
+ * Sits between {@link AbstractStream.TransportState} and {@link MessageDeframer} to deframe in the
+ * client thread. Calls from the transport to the deframer are wrapped into an {@link
+ * InitializingMessageProducer} and given back to the transport, where they will be run on the
+ * client thread. Calls from the deframer back to the transport use {@link
+ * TransportExecutor#runOnTransportThread} to run on the transport thread.
+ */
+public class ApplicationThreadDeframer implements Deframer, MessageDeframer.Listener {
+  interface TransportExecutor {
+    void runOnTransportThread(Runnable r);
+  }
+
+  private final MessageDeframer.Listener storedListener;
+  private final MessageDeframer deframer;
+  private final TransportExecutor transportExecutor;
+
+  /** Queue for messages returned by the deframer when deframing in the application thread. */
+  private final Queue<InputStream> messageReadQueue = new ArrayDeque<InputStream>();
+
+  ApplicationThreadDeframer(
+      MessageDeframer.Listener listener,
+      TransportExecutor transportExecutor,
+      MessageDeframer deframer) {
+    this.storedListener = checkNotNull(listener, "listener");
+    this.transportExecutor = checkNotNull(transportExecutor, "transportExecutor");
+    deframer.setListener(this);
+    this.deframer = deframer;
+  }
+
+  @Override
+  public void setMaxInboundMessageSize(int messageSize) {
+    deframer.setMaxInboundMessageSize(messageSize);
+  }
+
+  @Override
+  public void setDecompressor(Decompressor decompressor) {
+    deframer.setDecompressor(decompressor);
+  }
+
+  @Override
+  public void setFullStreamDecompressor(GzipInflatingBuffer fullStreamDecompressor) {
+    deframer.setFullStreamDecompressor(fullStreamDecompressor);
+  }
+
+  @Override
+  public void request(final int numMessages) {
+    storedListener.messagesAvailable(
+        new InitializingMessageProducer(
+            new Runnable() {
+              @Override
+              public void run() {
+                if (deframer.isClosed()) {
+                  return;
+                }
+                try {
+                  deframer.request(numMessages);
+                } catch (Throwable t) {
+                  storedListener.deframeFailed(t);
+                  deframer.close(); // unrecoverable state
+                }
+              }
+            }));
+  }
+
+  @Override
+  public void deframe(final ReadableBuffer data) {
+    storedListener.messagesAvailable(
+        new InitializingMessageProducer(
+            new Runnable() {
+              @Override
+              public void run() {
+                try {
+                  deframer.deframe(data);
+                } catch (Throwable t) {
+                  deframeFailed(t);
+                  deframer.close(); // unrecoverable state
+                }
+              }
+            }));
+  }
+
+  @Override
+  public void closeWhenComplete() {
+    storedListener.messagesAvailable(
+        new InitializingMessageProducer(
+            new Runnable() {
+              @Override
+              public void run() {
+                deframer.closeWhenComplete();
+              }
+            }));
+  }
+
+  @Override
+  public void close() {
+    deframer.stopDelivery();
+    storedListener.messagesAvailable(
+        new InitializingMessageProducer(
+            new Runnable() {
+              @Override
+              public void run() {
+                deframer.close();
+              }
+            }));
+  }
+
+  @Override
+  public void bytesRead(final int numBytes) {
+    transportExecutor.runOnTransportThread(
+        new Runnable() {
+          @Override
+          public void run() {
+            storedListener.bytesRead(numBytes);
+          }
+        });
+  }
+
+  @Override
+  public void messagesAvailable(StreamListener.MessageProducer producer) {
+    InputStream message;
+    while ((message = producer.next()) != null) {
+      messageReadQueue.add(message);
+    }
+  }
+
+  @Override
+  public void deframerClosed(final boolean hasPartialMessage) {
+    transportExecutor.runOnTransportThread(
+        new Runnable() {
+          @Override
+          public void run() {
+            storedListener.deframerClosed(hasPartialMessage);
+          }
+        });
+  }
+
+  @Override
+  public void deframeFailed(final Throwable cause) {
+    transportExecutor.runOnTransportThread(
+        new Runnable() {
+          @Override
+          public void run() {
+            storedListener.deframeFailed(cause);
+          }
+        });
+  }
+
+  private class InitializingMessageProducer implements StreamListener.MessageProducer {
+    private final Runnable runnable;
+    private boolean initialized = false;
+
+    private InitializingMessageProducer(Runnable runnable) {
+      this.runnable = runnable;
+    }
+
+    private void initialize() {
+      if (!initialized) {
+        runnable.run();
+        initialized = true;
+      }
+    }
+
+    @Nullable
+    @Override
+    public InputStream next() {
+      initialize();
+      return messageReadQueue.poll();
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/AtomicBackoff.java b/core/src/main/java/io/grpc/internal/AtomicBackoff.java
new file mode 100644
index 0000000..68ba9be
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/AtomicBackoff.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.Preconditions;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * A {@code long} atomically updated due to errors caused by the value being too small.
+ */
+@ThreadSafe
+public final class AtomicBackoff {
+  private static final Logger log = Logger.getLogger(AtomicBackoff.class.getName());
+
+  private final String name;
+  private final AtomicLong value = new AtomicLong();
+
+  /** Construct an atomic with initial value {@code value}. {@code name} is used for logging. */
+  public AtomicBackoff(String name, long value) {
+    Preconditions.checkArgument(value > 0, "value must be positive");
+    this.name = name;
+    this.value.set(value);
+  }
+
+  /** Returns the current state. The state instance's value does not change of time. */
+  public State getState() {
+    return new State(value.get());
+  }
+
+  @ThreadSafe
+  public final class State {
+    private final long savedValue;
+
+    private State(long value) {
+      this.savedValue = value;
+    }
+
+    public long get() {
+      return savedValue;
+    }
+
+    /**
+     * Causes future invocations of {@link AtomicBackoff#getState} to have a value at least double
+     * this state's value. Subsequent calls to this method will not increase the value further.
+     */
+    public void backoff() {
+      // Use max to handle overflow
+      long newValue = Math.max(savedValue * 2, savedValue);
+      boolean swapped = value.compareAndSet(savedValue, newValue);
+      // Even if swapped is false, the current value should be at least as large as newValue
+      assert value.get() >= newValue;
+      if (swapped) {
+        log.log(Level.WARNING, "Increased {0} to {1}", new Object[] {name, newValue});
+      }
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/AtomicLongCounter.java b/core/src/main/java/io/grpc/internal/AtomicLongCounter.java
new file mode 100644
index 0000000..83c685a
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/AtomicLongCounter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * An implementation of {@link LongCounter} that is just an {@Link AtomicLong}.
+ */
+final class AtomicLongCounter implements LongCounter {
+  private final AtomicLong counter = new AtomicLong();
+
+  /**
+   * Creates an {@link AtomicLongCounter}.
+   */
+  AtomicLongCounter() {
+  }
+
+  @Override
+  public void add(long delta) {
+    counter.getAndAdd(delta);
+  }
+
+  @Override
+  public long value() {
+    return counter.get();
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java b/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java
new file mode 100644
index 0000000..80505bc
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.Attributes;
+import io.grpc.ConnectivityState;
+import io.grpc.ConnectivityStateInfo;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.LoadBalancer;
+import io.grpc.LoadBalancer.Helper;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.PickFirstBalancerFactory;
+import io.grpc.Status;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+final class AutoConfiguredLoadBalancerFactory extends LoadBalancer.Factory {
+
+  @VisibleForTesting
+  static final String ROUND_ROBIN_LOAD_BALANCER_FACTORY_NAME =
+      "io.grpc.util.RoundRobinLoadBalancerFactory";
+  @VisibleForTesting
+  static final String GRPCLB_LOAD_BALANCER_FACTORY_NAME =
+      "io.grpc.grpclb.GrpclbLoadBalancerFactory";
+
+  AutoConfiguredLoadBalancerFactory() {}
+
+  @Override
+  public LoadBalancer newLoadBalancer(Helper helper) {
+    return new AutoConfiguredLoadBalancer(helper);
+  }
+
+  private static final class NoopLoadBalancer extends LoadBalancer {
+
+    @Override
+    public void handleResolvedAddressGroups(List<EquivalentAddressGroup> s, Attributes a) {}
+
+    @Override
+    public void handleNameResolutionError(Status error) {}
+
+    @Override
+    public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {}
+
+    @Override
+    public void shutdown() {}
+  }
+
+
+  @VisibleForTesting
+  static final class AutoConfiguredLoadBalancer extends LoadBalancer {
+    private final Helper helper;
+    private LoadBalancer delegate;
+    private LoadBalancer.Factory delegateFactory;
+
+    AutoConfiguredLoadBalancer(Helper helper) {
+      this.helper = helper;
+      delegateFactory = PickFirstBalancerFactory.getInstance();
+      delegate = delegateFactory.newLoadBalancer(helper);
+    }
+
+    //  Must be run inside ChannelExecutor.
+    @Override
+    public void handleResolvedAddressGroups(
+        List<EquivalentAddressGroup> servers, Attributes attributes) {
+      Map<String, Object> configMap = attributes.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG);
+      Factory newlbf;
+      try {
+        newlbf = decideLoadBalancerFactory(servers, configMap);
+      } catch (RuntimeException e) {
+        Status s = Status.INTERNAL
+            .withDescription("Failed to pick a load balancer from service config")
+            .withCause(e);
+        helper.updateBalancingState(ConnectivityState.TRANSIENT_FAILURE, new FailingPicker(s));
+        delegate.shutdown();
+        delegateFactory = null;
+        delegate = new NoopLoadBalancer();
+        return;
+      }
+
+      if (newlbf != null && newlbf != delegateFactory) {
+        helper.updateBalancingState(ConnectivityState.CONNECTING, new EmptyPicker());
+        delegate.shutdown();
+        delegateFactory = newlbf;
+        delegate = delegateFactory.newLoadBalancer(helper);
+      }
+      getDelegate().handleResolvedAddressGroups(servers, attributes);
+    }
+
+    @Override
+    public void handleNameResolutionError(Status error) {
+      getDelegate().handleNameResolutionError(error);
+    }
+
+    @Override
+    public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
+      getDelegate().handleSubchannelState(subchannel, stateInfo);
+    }
+
+    @Override
+    public void shutdown() {
+      delegate.shutdown();
+      delegate = null;
+    }
+
+    @VisibleForTesting
+    LoadBalancer getDelegate() {
+      return delegate;
+    }
+
+    @VisibleForTesting
+    void setDelegate(LoadBalancer lb) {
+      delegate = lb;
+    }
+
+    @VisibleForTesting
+    LoadBalancer.Factory getDelegateFactory() {
+      return delegateFactory;
+    }
+
+    /**
+     * Picks a load balancer based on given criteria.  In order of preference:
+     *
+     * <ol>
+     *   <li>User provided lb on the channel.  This is a degenerate case and not handled here.</li>
+     *   <li>gRPCLB if on the class path and any gRPC LB balancer addresses are present</li>
+     *   <li>RoundRobin if on the class path and picked by the service config</li>
+     *   <li>PickFirst if the service config choice does not specify</li>
+     * </ol>
+     *
+     * @param servers The list of servers reported
+     * @param config the service config object
+     * @return the new load balancer factory, or null if the existing lb should be used.
+     */
+    @Nullable
+    @VisibleForTesting
+    static LoadBalancer.Factory decideLoadBalancerFactory(
+        List<EquivalentAddressGroup> servers, @Nullable Map<String, Object> config) {
+      // Check for balancer addresses
+      boolean haveBalancerAddress = false;
+      for (EquivalentAddressGroup s : servers) {
+        if (s.getAttributes().get(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY) != null) {
+          haveBalancerAddress = true;
+          break;
+        }
+      }
+
+      if (haveBalancerAddress) {
+        try {
+          Class<?> lbFactoryClass = Class.forName(GRPCLB_LOAD_BALANCER_FACTORY_NAME);
+          Method getInstance = lbFactoryClass.getMethod("getInstance");
+          return (LoadBalancer.Factory) getInstance.invoke(null);
+        } catch (RuntimeException e) {
+          throw e;
+        } catch (Exception e) {
+          throw new RuntimeException("Can't get GRPCLB, but balancer addresses were present", e);
+        }
+      }
+
+      String serviceConfigChoiceBalancingPolicy = null;
+      if (config != null) {
+        serviceConfigChoiceBalancingPolicy =
+            ServiceConfigUtil.getLoadBalancingPolicyFromServiceConfig(config);
+      }
+
+      // Check for an explicitly present lb choice
+      if (serviceConfigChoiceBalancingPolicy != null) {
+        if (serviceConfigChoiceBalancingPolicy.toUpperCase(Locale.ROOT).equals("ROUND_ROBIN")) {
+          try {
+            Class<?> lbFactoryClass = Class.forName(ROUND_ROBIN_LOAD_BALANCER_FACTORY_NAME);
+            Method getInstance = lbFactoryClass.getMethod("getInstance");
+            return (LoadBalancer.Factory) getInstance.invoke(null);
+          } catch (RuntimeException e) {
+            throw e;
+          } catch (Exception e) {
+            throw new RuntimeException("Can't get Round Robin LB", e);
+          }
+        }
+        throw new IllegalArgumentException(
+            "Unknown service config policy: " + serviceConfigChoiceBalancingPolicy);
+      }
+
+      return PickFirstBalancerFactory.getInstance();
+    }
+  }
+
+  private static final class EmptyPicker extends SubchannelPicker {
+
+    @Override
+    public PickResult pickSubchannel(PickSubchannelArgs args) {
+      return PickResult.withNoResult();
+    }
+  }
+
+  private static final class FailingPicker extends SubchannelPicker {
+    private final Status failure;
+
+    FailingPicker(Status failure) {
+      this.failure = failure;
+    }
+
+    @Override
+    public PickResult pickSubchannel(PickSubchannelArgs args) {
+      return PickResult.withError(failure);
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/BackoffPolicy.java b/core/src/main/java/io/grpc/internal/BackoffPolicy.java
new file mode 100644
index 0000000..acbe84b
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/BackoffPolicy.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+/**
+ * Determines how long to wait before doing some action (typically a retry, or a reconnect).
+ */
+public interface BackoffPolicy {
+  public interface Provider {
+    BackoffPolicy get();
+  }
+
+  /**
+   * @return The number of nanoseconds to wait.
+   */
+  long nextBackoffNanos();
+}
+
diff --git a/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java
new file mode 100644
index 0000000..df2e0a0
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/CallCredentialsApplyingTransportFactory.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.MoreObjects.firstNonNull;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.grpc.Attributes;
+import io.grpc.CallCredentials;
+import io.grpc.CallOptions;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.SecurityLevel;
+import io.grpc.Status;
+import java.net.SocketAddress;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+
+final class CallCredentialsApplyingTransportFactory implements ClientTransportFactory {
+  private final ClientTransportFactory delegate;
+  private final Executor appExecutor;
+
+  CallCredentialsApplyingTransportFactory(
+      ClientTransportFactory delegate, Executor appExecutor) {
+    this.delegate = checkNotNull(delegate, "delegate");
+    this.appExecutor = checkNotNull(appExecutor, "appExecutor");
+  }
+
+  @Override
+  public ConnectionClientTransport newClientTransport(
+      SocketAddress serverAddress, ClientTransportOptions options) {
+    return new CallCredentialsApplyingTransport(
+        delegate.newClientTransport(serverAddress, options), options.getAuthority());
+  }
+
+  @Override
+  public ScheduledExecutorService getScheduledExecutorService() {
+    return delegate.getScheduledExecutorService();
+  }
+
+  @Override
+  public void close() {
+    delegate.close();
+  }
+
+  private class CallCredentialsApplyingTransport extends ForwardingConnectionClientTransport {
+    private final ConnectionClientTransport delegate;
+    private final String authority;
+
+    CallCredentialsApplyingTransport(ConnectionClientTransport delegate, String authority) {
+      this.delegate = checkNotNull(delegate, "delegate");
+      this.authority = checkNotNull(authority, "authority");
+    }
+
+    @Override
+    protected ConnectionClientTransport delegate() {
+      return delegate;
+    }
+
+    @Override
+    public ClientStream newStream(
+        MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions) {
+      CallCredentials creds = callOptions.getCredentials();
+      if (creds != null) {
+        MetadataApplierImpl applier = new MetadataApplierImpl(
+            delegate, method, headers, callOptions);
+        Attributes.Builder effectiveAttrsBuilder = Attributes.newBuilder()
+            .set(CallCredentials.ATTR_AUTHORITY, authority)
+            .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.NONE)
+            .setAll(delegate.getAttributes());
+        if (callOptions.getAuthority() != null) {
+          effectiveAttrsBuilder.set(CallCredentials.ATTR_AUTHORITY, callOptions.getAuthority());
+        }
+        try {
+          creds.applyRequestMetadata(method, effectiveAttrsBuilder.build(),
+              firstNonNull(callOptions.getExecutor(), appExecutor), applier);
+        } catch (Throwable t) {
+          applier.fail(Status.UNAUTHENTICATED
+              .withDescription("Credentials should use fail() instead of throwing exceptions")
+              .withCause(t));
+        }
+        return applier.returnStream();
+      } else {
+        return delegate.newStream(method, headers, callOptions);
+      }
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/CallTracer.java b/core/src/main/java/io/grpc/internal/CallTracer.java
new file mode 100644
index 0000000..330323d
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/CallTracer.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static io.grpc.internal.TimeProvider.SYSTEM_TIME_PROVIDER;
+
+import io.grpc.InternalChannelz.ChannelStats;
+import io.grpc.InternalChannelz.ServerStats;
+
+/**
+ * A collection of call stats for channelz.
+ */
+final class CallTracer {
+  private final TimeProvider timeProvider;
+  private final LongCounter callsStarted = LongCounterFactory.create();
+  private final LongCounter callsSucceeded = LongCounterFactory.create();
+  private final LongCounter callsFailed = LongCounterFactory.create();
+  private volatile long lastCallStartedNanos;
+
+  CallTracer(TimeProvider timeProvider) {
+    this.timeProvider = timeProvider;
+  }
+
+  public void reportCallStarted() {
+    callsStarted.add(1);
+    lastCallStartedNanos = timeProvider.currentTimeNanos();
+  }
+
+  public void reportCallEnded(boolean success) {
+    if (success) {
+      callsSucceeded.add(1);
+    } else {
+      callsFailed.add(1);
+    }
+  }
+
+  void updateBuilder(ChannelStats.Builder builder) {
+    builder
+        .setCallsStarted(callsStarted.value())
+        .setCallsSucceeded(callsSucceeded.value())
+        .setCallsFailed(callsFailed.value())
+        .setLastCallStartedNanos(lastCallStartedNanos);
+  }
+
+  void updateBuilder(ServerStats.Builder builder) {
+    builder
+        .setCallsStarted(callsStarted.value())
+        .setCallsSucceeded(callsSucceeded.value())
+        .setCallsFailed(callsFailed.value())
+        .setLastCallStartedNanos(lastCallStartedNanos);
+  }
+
+  public interface Factory {
+    CallTracer create();
+  }
+
+  static final Factory DEFAULT_FACTORY = new Factory() {
+    @Override
+    public CallTracer create() {
+      return new CallTracer(SYSTEM_TIME_PROVIDER);
+    }
+  };
+
+  public static Factory getDefaultFactory() {
+    return DEFAULT_FACTORY;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/CensusStatsModule.java b/core/src/main/java/io/grpc/internal/CensusStatsModule.java
new file mode 100644
index 0000000..c107ea9
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/CensusStatsModule.java
@@ -0,0 +1,686 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static io.opencensus.tags.unsafe.ContextUtils.TAG_CONTEXT_KEY;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Supplier;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ClientStreamTracer;
+import io.grpc.Context;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerStreamTracer;
+import io.grpc.Status;
+import io.grpc.StreamTracer;
+import io.opencensus.contrib.grpc.metrics.RpcMeasureConstants;
+import io.opencensus.stats.MeasureMap;
+import io.opencensus.stats.Stats;
+import io.opencensus.stats.StatsRecorder;
+import io.opencensus.tags.TagContext;
+import io.opencensus.tags.TagValue;
+import io.opencensus.tags.Tagger;
+import io.opencensus.tags.Tags;
+import io.opencensus.tags.propagation.TagContextBinarySerializer;
+import io.opencensus.tags.propagation.TagContextSerializationException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Provides factories for {@link StreamTracer} that records stats to Census.
+ *
+ * <p>On the client-side, a factory is created for each call, because ClientCall starts earlier than
+ * the ClientStream, and in some cases may even not create a ClientStream at all.  Therefore, it's
+ * the factory that reports the summary to Census.
+ *
+ * <p>On the server-side, there is only one ServerStream per each ServerCall, and ServerStream
+ * starts earlier than the ServerCall.  Therefore, only one tracer is created per stream/call and
+ * it's the tracer that reports the summary to Census.
+ */
+public final class CensusStatsModule {
+  private static final Logger logger = Logger.getLogger(CensusStatsModule.class.getName());
+  private static final double NANOS_PER_MILLI = TimeUnit.MILLISECONDS.toNanos(1);
+  private static final ClientTracer BLANK_CLIENT_TRACER = new ClientTracer();
+
+  private final Tagger tagger;
+  private final StatsRecorder statsRecorder;
+  private final Supplier<Stopwatch> stopwatchSupplier;
+  @VisibleForTesting
+  final Metadata.Key<TagContext> statsHeader;
+  private final boolean propagateTags;
+
+  /**
+   * Creates a {@link CensusStatsModule} with the default OpenCensus implementation.
+   */
+  CensusStatsModule(Supplier<Stopwatch> stopwatchSupplier, boolean propagateTags) {
+    this(
+        Tags.getTagger(),
+        Tags.getTagPropagationComponent().getBinarySerializer(),
+        Stats.getStatsRecorder(),
+        stopwatchSupplier,
+        propagateTags);
+  }
+
+  /**
+   * Creates a {@link CensusStatsModule} with the given OpenCensus implementation.
+   */
+  public CensusStatsModule(
+      final Tagger tagger,
+      final TagContextBinarySerializer tagCtxSerializer,
+      StatsRecorder statsRecorder, Supplier<Stopwatch> stopwatchSupplier,
+      boolean propagateTags) {
+    this.tagger = checkNotNull(tagger, "tagger");
+    this.statsRecorder = checkNotNull(statsRecorder, "statsRecorder");
+    checkNotNull(tagCtxSerializer, "tagCtxSerializer");
+    this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier");
+    this.propagateTags = propagateTags;
+    this.statsHeader =
+        Metadata.Key.of("grpc-tags-bin", new Metadata.BinaryMarshaller<TagContext>() {
+            @Override
+            public byte[] toBytes(TagContext context) {
+              // TODO(carl-mastrangelo): currently we only make sure the correctness. We may need to
+              // optimize out the allocation and copy in the future.
+              try {
+                return tagCtxSerializer.toByteArray(context);
+              } catch (TagContextSerializationException e) {
+                throw new RuntimeException(e);
+              }
+            }
+
+            @Override
+            public TagContext parseBytes(byte[] serialized) {
+              try {
+                return tagCtxSerializer.fromByteArray(serialized);
+              } catch (Exception e) {
+                logger.log(Level.FINE, "Failed to parse stats header", e);
+                return tagger.empty();
+              }
+            }
+          });
+  }
+
+  /**
+   * Creates a {@link ClientCallTracer} for a new call.
+   */
+  @VisibleForTesting
+  ClientCallTracer newClientCallTracer(
+      TagContext parentCtx, String fullMethodName,
+      boolean recordStartedRpcs, boolean recordFinishedRpcs) {
+    return new ClientCallTracer(
+        this, parentCtx, fullMethodName, recordStartedRpcs, recordFinishedRpcs);
+  }
+
+  /**
+   * Returns the server tracer factory.
+   */
+  ServerStreamTracer.Factory getServerTracerFactory(
+      boolean recordStartedRpcs, boolean recordFinishedRpcs) {
+    return new ServerTracerFactory(recordStartedRpcs, recordFinishedRpcs);
+  }
+
+  /**
+   * Returns the client interceptor that facilitates Census-based stats reporting.
+   */
+  ClientInterceptor getClientInterceptor(boolean recordStartedRpcs, boolean recordFinishedRpcs) {
+    return new StatsClientInterceptor(recordStartedRpcs, recordFinishedRpcs);
+  }
+
+  private static final class ClientTracer extends ClientStreamTracer {
+
+    @Nullable private static final AtomicLongFieldUpdater<ClientTracer> outboundMessageCountUpdater;
+    @Nullable private static final AtomicLongFieldUpdater<ClientTracer> inboundMessageCountUpdater;
+    @Nullable private static final AtomicLongFieldUpdater<ClientTracer> outboundWireSizeUpdater;
+    @Nullable private static final AtomicLongFieldUpdater<ClientTracer> inboundWireSizeUpdater;
+
+    @Nullable
+    private static final AtomicLongFieldUpdater<ClientTracer> outboundUncompressedSizeUpdater;
+
+    @Nullable
+    private static final AtomicLongFieldUpdater<ClientTracer> inboundUncompressedSizeUpdater;
+
+    /**
+     * When using Atomic*FieldUpdater, some Samsung Android 5.0.x devices encounter a bug in their
+     * JDK reflection API that triggers a NoSuchFieldException. When this occurs, we fallback to
+     * (potentially racy) direct updates of the volatile variables.
+     */
+    static {
+      AtomicLongFieldUpdater<ClientTracer> tmpOutboundMessageCountUpdater;
+      AtomicLongFieldUpdater<ClientTracer> tmpInboundMessageCountUpdater;
+      AtomicLongFieldUpdater<ClientTracer> tmpOutboundWireSizeUpdater;
+      AtomicLongFieldUpdater<ClientTracer> tmpInboundWireSizeUpdater;
+      AtomicLongFieldUpdater<ClientTracer> tmpOutboundUncompressedSizeUpdater;
+      AtomicLongFieldUpdater<ClientTracer> tmpInboundUncompressedSizeUpdater;
+      try {
+        tmpOutboundMessageCountUpdater =
+            AtomicLongFieldUpdater.newUpdater(ClientTracer.class, "outboundMessageCount");
+        tmpInboundMessageCountUpdater =
+            AtomicLongFieldUpdater.newUpdater(ClientTracer.class, "inboundMessageCount");
+        tmpOutboundWireSizeUpdater =
+            AtomicLongFieldUpdater.newUpdater(ClientTracer.class, "outboundWireSize");
+        tmpInboundWireSizeUpdater =
+            AtomicLongFieldUpdater.newUpdater(ClientTracer.class, "inboundWireSize");
+        tmpOutboundUncompressedSizeUpdater =
+            AtomicLongFieldUpdater.newUpdater(ClientTracer.class, "outboundUncompressedSize");
+        tmpInboundUncompressedSizeUpdater =
+            AtomicLongFieldUpdater.newUpdater(ClientTracer.class, "inboundUncompressedSize");
+      } catch (Throwable t) {
+        logger.log(Level.SEVERE, "Creating atomic field updaters failed", t);
+        tmpOutboundMessageCountUpdater = null;
+        tmpInboundMessageCountUpdater = null;
+        tmpOutboundWireSizeUpdater = null;
+        tmpInboundWireSizeUpdater = null;
+        tmpOutboundUncompressedSizeUpdater = null;
+        tmpInboundUncompressedSizeUpdater = null;
+      }
+      outboundMessageCountUpdater = tmpOutboundMessageCountUpdater;
+      inboundMessageCountUpdater = tmpInboundMessageCountUpdater;
+      outboundWireSizeUpdater = tmpOutboundWireSizeUpdater;
+      inboundWireSizeUpdater = tmpInboundWireSizeUpdater;
+      outboundUncompressedSizeUpdater = tmpOutboundUncompressedSizeUpdater;
+      inboundUncompressedSizeUpdater = tmpInboundUncompressedSizeUpdater;
+    }
+
+    volatile long outboundMessageCount;
+    volatile long inboundMessageCount;
+    volatile long outboundWireSize;
+    volatile long inboundWireSize;
+    volatile long outboundUncompressedSize;
+    volatile long inboundUncompressedSize;
+
+    @Override
+    @SuppressWarnings("NonAtomicVolatileUpdate")
+    public void outboundWireSize(long bytes) {
+      if (outboundWireSizeUpdater != null) {
+        outboundWireSizeUpdater.getAndAdd(this, bytes);
+      } else {
+        outboundWireSize += bytes;
+      }
+    }
+
+    @Override
+    @SuppressWarnings("NonAtomicVolatileUpdate")
+    public void inboundWireSize(long bytes) {
+      if (inboundWireSizeUpdater != null) {
+        inboundWireSizeUpdater.getAndAdd(this, bytes);
+      } else {
+        inboundWireSize += bytes;
+      }
+    }
+
+    @Override
+    @SuppressWarnings("NonAtomicVolatileUpdate")
+    public void outboundUncompressedSize(long bytes) {
+      if (outboundUncompressedSizeUpdater != null) {
+        outboundUncompressedSizeUpdater.getAndAdd(this, bytes);
+      } else {
+        outboundUncompressedSize += bytes;
+      }
+    }
+
+    @Override
+    @SuppressWarnings("NonAtomicVolatileUpdate")
+    public void inboundUncompressedSize(long bytes) {
+      if (inboundUncompressedSizeUpdater != null) {
+        inboundUncompressedSizeUpdater.getAndAdd(this, bytes);
+      } else {
+        inboundUncompressedSize += bytes;
+      }
+    }
+
+    @Override
+    @SuppressWarnings("NonAtomicVolatileUpdate")
+    public void inboundMessage(int seqNo) {
+      if (inboundMessageCountUpdater != null) {
+        inboundMessageCountUpdater.getAndIncrement(this);
+      } else {
+        inboundMessageCount++;
+      }
+    }
+
+    @Override
+    @SuppressWarnings("NonAtomicVolatileUpdate")
+    public void outboundMessage(int seqNo) {
+      if (outboundMessageCountUpdater != null) {
+        outboundMessageCountUpdater.getAndIncrement(this);
+      } else {
+        outboundMessageCount++;
+      }
+    }
+  }
+
+
+
+  @VisibleForTesting
+  static final class ClientCallTracer extends ClientStreamTracer.Factory {
+    @Nullable
+    private static final AtomicReferenceFieldUpdater<ClientCallTracer, ClientTracer>
+        streamTracerUpdater;
+
+    @Nullable private static final AtomicIntegerFieldUpdater<ClientCallTracer> callEndedUpdater;
+
+    /**
+     * When using Atomic*FieldUpdater, some Samsung Android 5.0.x devices encounter a bug in their
+     * JDK reflection API that triggers a NoSuchFieldException. When this occurs, we fallback to
+     * (potentially racy) direct updates of the volatile variables.
+     */
+    static {
+      AtomicReferenceFieldUpdater<ClientCallTracer, ClientTracer> tmpStreamTracerUpdater;
+      AtomicIntegerFieldUpdater<ClientCallTracer> tmpCallEndedUpdater;
+      try {
+        tmpStreamTracerUpdater =
+            AtomicReferenceFieldUpdater.newUpdater(
+                ClientCallTracer.class, ClientTracer.class, "streamTracer");
+        tmpCallEndedUpdater =
+            AtomicIntegerFieldUpdater.newUpdater(ClientCallTracer.class, "callEnded");
+      } catch (Throwable t) {
+        logger.log(Level.SEVERE, "Creating atomic field updaters failed", t);
+        tmpStreamTracerUpdater = null;
+        tmpCallEndedUpdater = null;
+      }
+      streamTracerUpdater = tmpStreamTracerUpdater;
+      callEndedUpdater = tmpCallEndedUpdater;
+    }
+
+    private final CensusStatsModule module;
+    private final Stopwatch stopwatch;
+    private volatile ClientTracer streamTracer;
+    private volatile int callEnded;
+    private final TagContext parentCtx;
+    private final TagContext startCtx;
+    private final boolean recordFinishedRpcs;
+
+    ClientCallTracer(
+        CensusStatsModule module,
+        TagContext parentCtx,
+        String fullMethodName,
+        boolean recordStartedRpcs,
+        boolean recordFinishedRpcs) {
+      this.module = module;
+      this.parentCtx = checkNotNull(parentCtx);
+      this.startCtx =
+          module.tagger.toBuilder(parentCtx)
+          .put(RpcMeasureConstants.RPC_METHOD, TagValue.create(fullMethodName)).build();
+      this.stopwatch = module.stopwatchSupplier.get().start();
+      this.recordFinishedRpcs = recordFinishedRpcs;
+      if (recordStartedRpcs) {
+        module.statsRecorder.newMeasureMap().put(RpcMeasureConstants.RPC_CLIENT_STARTED_COUNT, 1)
+            .record(startCtx);
+      }
+    }
+
+    @Override
+    public ClientStreamTracer newClientStreamTracer(CallOptions callOptions, Metadata headers) {
+      ClientTracer tracer = new ClientTracer();
+      // TODO(zhangkun83): Once retry or hedging is implemented, a ClientCall may start more than
+      // one streams.  We will need to update this file to support them.
+      if (streamTracerUpdater != null) {
+        checkState(
+            streamTracerUpdater.compareAndSet(this, null, tracer),
+            "Are you creating multiple streams per call? This class doesn't yet support this case");
+      } else {
+        checkState(
+            streamTracer == null,
+            "Are you creating multiple streams per call? This class doesn't yet support this case");
+        streamTracer = tracer;
+      }
+      if (module.propagateTags) {
+        headers.discardAll(module.statsHeader);
+        if (!module.tagger.empty().equals(parentCtx)) {
+          headers.put(module.statsHeader, parentCtx);
+        }
+      }
+      return tracer;
+    }
+
+    /**
+     * Record a finished call and mark the current time as the end time.
+     *
+     * <p>Can be called from any thread without synchronization.  Calling it the second time or more
+     * is a no-op.
+     */
+    void callEnded(Status status) {
+      if (callEndedUpdater != null) {
+        if (callEndedUpdater.getAndSet(this, 1) != 0) {
+          return;
+        }
+      } else {
+        if (callEnded != 0) {
+          return;
+        }
+        callEnded = 1;
+      }
+      if (!recordFinishedRpcs) {
+        return;
+      }
+      stopwatch.stop();
+      long roundtripNanos = stopwatch.elapsed(TimeUnit.NANOSECONDS);
+      ClientTracer tracer = streamTracer;
+      if (tracer == null) {
+        tracer = BLANK_CLIENT_TRACER;
+      }
+      MeasureMap measureMap = module.statsRecorder.newMeasureMap()
+          .put(RpcMeasureConstants.RPC_CLIENT_FINISHED_COUNT, 1)
+          // The latency is double value
+          .put(RpcMeasureConstants.RPC_CLIENT_ROUNDTRIP_LATENCY, roundtripNanos / NANOS_PER_MILLI)
+          .put(RpcMeasureConstants.RPC_CLIENT_REQUEST_COUNT, tracer.outboundMessageCount)
+          .put(RpcMeasureConstants.RPC_CLIENT_RESPONSE_COUNT, tracer.inboundMessageCount)
+          .put(RpcMeasureConstants.RPC_CLIENT_REQUEST_BYTES, tracer.outboundWireSize)
+          .put(RpcMeasureConstants.RPC_CLIENT_RESPONSE_BYTES, tracer.inboundWireSize)
+          .put(
+              RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES,
+              tracer.outboundUncompressedSize)
+          .put(
+              RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES,
+              tracer.inboundUncompressedSize);
+      if (!status.isOk()) {
+        measureMap.put(RpcMeasureConstants.RPC_CLIENT_ERROR_COUNT, 1);
+      }
+      measureMap.record(
+          module
+              .tagger
+              .toBuilder(startCtx)
+              .put(RpcMeasureConstants.RPC_STATUS, TagValue.create(status.getCode().toString()))
+              .build());
+    }
+  }
+
+  private static final class ServerTracer extends ServerStreamTracer {
+    @Nullable private static final AtomicIntegerFieldUpdater<ServerTracer> streamClosedUpdater;
+    @Nullable private static final AtomicLongFieldUpdater<ServerTracer> outboundMessageCountUpdater;
+    @Nullable private static final AtomicLongFieldUpdater<ServerTracer> inboundMessageCountUpdater;
+    @Nullable private static final AtomicLongFieldUpdater<ServerTracer> outboundWireSizeUpdater;
+    @Nullable private static final AtomicLongFieldUpdater<ServerTracer> inboundWireSizeUpdater;
+
+    @Nullable
+    private static final AtomicLongFieldUpdater<ServerTracer> outboundUncompressedSizeUpdater;
+
+    @Nullable
+    private static final AtomicLongFieldUpdater<ServerTracer> inboundUncompressedSizeUpdater;
+
+    /**
+     * When using Atomic*FieldUpdater, some Samsung Android 5.0.x devices encounter a bug in their
+     * JDK reflection API that triggers a NoSuchFieldException. When this occurs, we fallback to
+     * (potentially racy) direct updates of the volatile variables.
+     */
+    static {
+      AtomicIntegerFieldUpdater<ServerTracer> tmpStreamClosedUpdater;
+      AtomicLongFieldUpdater<ServerTracer> tmpOutboundMessageCountUpdater;
+      AtomicLongFieldUpdater<ServerTracer> tmpInboundMessageCountUpdater;
+      AtomicLongFieldUpdater<ServerTracer> tmpOutboundWireSizeUpdater;
+      AtomicLongFieldUpdater<ServerTracer> tmpInboundWireSizeUpdater;
+      AtomicLongFieldUpdater<ServerTracer> tmpOutboundUncompressedSizeUpdater;
+      AtomicLongFieldUpdater<ServerTracer> tmpInboundUncompressedSizeUpdater;
+      try {
+        tmpStreamClosedUpdater =
+            AtomicIntegerFieldUpdater.newUpdater(ServerTracer.class, "streamClosed");
+        tmpOutboundMessageCountUpdater =
+            AtomicLongFieldUpdater.newUpdater(ServerTracer.class, "outboundMessageCount");
+        tmpInboundMessageCountUpdater =
+            AtomicLongFieldUpdater.newUpdater(ServerTracer.class, "inboundMessageCount");
+        tmpOutboundWireSizeUpdater =
+            AtomicLongFieldUpdater.newUpdater(ServerTracer.class, "outboundWireSize");
+        tmpInboundWireSizeUpdater =
+            AtomicLongFieldUpdater.newUpdater(ServerTracer.class, "inboundWireSize");
+        tmpOutboundUncompressedSizeUpdater =
+            AtomicLongFieldUpdater.newUpdater(ServerTracer.class, "outboundUncompressedSize");
+        tmpInboundUncompressedSizeUpdater =
+            AtomicLongFieldUpdater.newUpdater(ServerTracer.class, "inboundUncompressedSize");
+      } catch (Throwable t) {
+        logger.log(Level.SEVERE, "Creating atomic field updaters failed", t);
+        tmpStreamClosedUpdater = null;
+        tmpOutboundMessageCountUpdater = null;
+        tmpInboundMessageCountUpdater = null;
+        tmpOutboundWireSizeUpdater = null;
+        tmpInboundWireSizeUpdater = null;
+        tmpOutboundUncompressedSizeUpdater = null;
+        tmpInboundUncompressedSizeUpdater = null;
+      }
+      streamClosedUpdater = tmpStreamClosedUpdater;
+      outboundMessageCountUpdater = tmpOutboundMessageCountUpdater;
+      inboundMessageCountUpdater = tmpInboundMessageCountUpdater;
+      outboundWireSizeUpdater = tmpOutboundWireSizeUpdater;
+      inboundWireSizeUpdater = tmpInboundWireSizeUpdater;
+      outboundUncompressedSizeUpdater = tmpOutboundUncompressedSizeUpdater;
+      inboundUncompressedSizeUpdater = tmpInboundUncompressedSizeUpdater;
+    }
+
+    private final CensusStatsModule module;
+    private final TagContext parentCtx;
+    private volatile int streamClosed;
+    private final Stopwatch stopwatch;
+    private final Tagger tagger;
+    private final boolean recordFinishedRpcs;
+    private volatile long outboundMessageCount;
+    private volatile long inboundMessageCount;
+    private volatile long outboundWireSize;
+    private volatile long inboundWireSize;
+    private volatile long outboundUncompressedSize;
+    private volatile long inboundUncompressedSize;
+
+    ServerTracer(
+        CensusStatsModule module,
+        TagContext parentCtx,
+        Supplier<Stopwatch> stopwatchSupplier,
+        Tagger tagger,
+        boolean recordStartedRpcs,
+        boolean recordFinishedRpcs) {
+      this.module = module;
+      this.parentCtx = checkNotNull(parentCtx, "parentCtx");
+      this.stopwatch = stopwatchSupplier.get().start();
+      this.tagger = tagger;
+      this.recordFinishedRpcs = recordFinishedRpcs;
+      if (recordStartedRpcs) {
+        module.statsRecorder.newMeasureMap().put(RpcMeasureConstants.RPC_SERVER_STARTED_COUNT, 1)
+            .record(parentCtx);
+      }
+    }
+
+    @Override
+    @SuppressWarnings("NonAtomicVolatileUpdate")
+    public void outboundWireSize(long bytes) {
+      if (outboundWireSizeUpdater != null) {
+        outboundWireSizeUpdater.getAndAdd(this, bytes);
+      } else {
+        outboundWireSize += bytes;
+      }
+    }
+
+    @Override
+    @SuppressWarnings("NonAtomicVolatileUpdate")
+    public void inboundWireSize(long bytes) {
+      if (inboundWireSizeUpdater != null) {
+        inboundWireSizeUpdater.getAndAdd(this, bytes);
+      } else {
+        inboundWireSize += bytes;
+      }
+    }
+
+    @Override
+    @SuppressWarnings("NonAtomicVolatileUpdate")
+    public void outboundUncompressedSize(long bytes) {
+      if (outboundUncompressedSizeUpdater != null) {
+        outboundUncompressedSizeUpdater.getAndAdd(this, bytes);
+      } else {
+        outboundUncompressedSize += bytes;
+      }
+    }
+
+    @Override
+    @SuppressWarnings("NonAtomicVolatileUpdate")
+    public void inboundUncompressedSize(long bytes) {
+      if (inboundUncompressedSizeUpdater != null) {
+        inboundUncompressedSizeUpdater.getAndAdd(this, bytes);
+      } else {
+        inboundUncompressedSize += bytes;
+      }
+    }
+
+    @Override
+    @SuppressWarnings("NonAtomicVolatileUpdate")
+    public void inboundMessage(int seqNo) {
+      if (inboundMessageCountUpdater != null) {
+        inboundMessageCountUpdater.getAndIncrement(this);
+      } else {
+        inboundMessageCount++;
+      }
+    }
+
+    @Override
+    @SuppressWarnings("NonAtomicVolatileUpdate")
+    public void outboundMessage(int seqNo) {
+      if (outboundMessageCountUpdater != null) {
+        outboundMessageCountUpdater.getAndIncrement(this);
+      } else {
+        outboundMessageCount++;
+      }
+    }
+
+    /**
+     * Record a finished stream and mark the current time as the end time.
+     *
+     * <p>Can be called from any thread without synchronization.  Calling it the second time or more
+     * is a no-op.
+     */
+    @Override
+    public void streamClosed(Status status) {
+      if (streamClosedUpdater != null) {
+        if (streamClosedUpdater.getAndSet(this, 1) != 0) {
+          return;
+        }
+      } else {
+        if (streamClosed != 0) {
+          return;
+        }
+        streamClosed = 1;
+      }
+      if (!recordFinishedRpcs) {
+        return;
+      }
+      stopwatch.stop();
+      long elapsedTimeNanos = stopwatch.elapsed(TimeUnit.NANOSECONDS);
+      MeasureMap measureMap = module.statsRecorder.newMeasureMap()
+          .put(RpcMeasureConstants.RPC_SERVER_FINISHED_COUNT, 1)
+          // The latency is double value
+          .put(RpcMeasureConstants.RPC_SERVER_SERVER_LATENCY, elapsedTimeNanos / NANOS_PER_MILLI)
+          .put(RpcMeasureConstants.RPC_SERVER_RESPONSE_COUNT, outboundMessageCount)
+          .put(RpcMeasureConstants.RPC_SERVER_REQUEST_COUNT, inboundMessageCount)
+          .put(RpcMeasureConstants.RPC_SERVER_RESPONSE_BYTES, outboundWireSize)
+          .put(RpcMeasureConstants.RPC_SERVER_REQUEST_BYTES, inboundWireSize)
+          .put(RpcMeasureConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES, outboundUncompressedSize)
+          .put(RpcMeasureConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES, inboundUncompressedSize);
+      if (!status.isOk()) {
+        measureMap.put(RpcMeasureConstants.RPC_SERVER_ERROR_COUNT, 1);
+      }
+      measureMap.record(
+          module
+              .tagger
+              .toBuilder(parentCtx)
+              .put(RpcMeasureConstants.RPC_STATUS, TagValue.create(status.getCode().toString()))
+              .build());
+    }
+
+    @Override
+    public Context filterContext(Context context) {
+      if (!tagger.empty().equals(parentCtx)) {
+        return context.withValue(TAG_CONTEXT_KEY, parentCtx);
+      }
+      return context;
+    }
+  }
+
+  @VisibleForTesting
+  final class ServerTracerFactory extends ServerStreamTracer.Factory {
+    private final boolean recordStartedRpcs;
+    private final boolean recordFinishedRpcs;
+
+    ServerTracerFactory(boolean recordStartedRpcs, boolean recordFinishedRpcs) {
+      this.recordStartedRpcs = recordStartedRpcs;
+      this.recordFinishedRpcs = recordFinishedRpcs;
+    }
+
+    @Override
+    public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata headers) {
+      TagContext parentCtx = headers.get(statsHeader);
+      if (parentCtx == null) {
+        parentCtx = tagger.empty();
+      }
+      parentCtx =
+          tagger
+              .toBuilder(parentCtx)
+              .put(RpcMeasureConstants.RPC_METHOD, TagValue.create(fullMethodName))
+              .build();
+      return new ServerTracer(
+          CensusStatsModule.this,
+          parentCtx,
+          stopwatchSupplier,
+          tagger,
+          recordStartedRpcs,
+          recordFinishedRpcs);
+    }
+  }
+
+  @VisibleForTesting
+  final class StatsClientInterceptor implements ClientInterceptor {
+    private final boolean recordStartedRpcs;
+    private final boolean recordFinishedRpcs;
+
+    StatsClientInterceptor(boolean recordStartedRpcs, boolean recordFinishedRpcs) {
+      this.recordStartedRpcs = recordStartedRpcs;
+      this.recordFinishedRpcs = recordFinishedRpcs;
+    }
+
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+        MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+      // New RPCs on client-side inherit the tag context from the current Context.
+      TagContext parentCtx = tagger.getCurrentTagContext();
+      final ClientCallTracer tracerFactory =
+          newClientCallTracer(parentCtx, method.getFullMethodName(),
+              recordStartedRpcs, recordFinishedRpcs);
+      ClientCall<ReqT, RespT> call =
+          next.newCall(method, callOptions.withStreamTracerFactory(tracerFactory));
+      return new SimpleForwardingClientCall<ReqT, RespT>(call) {
+        @Override
+        public void start(Listener<RespT> responseListener, Metadata headers) {
+          delegate().start(
+              new SimpleForwardingClientCallListener<RespT>(responseListener) {
+                @Override
+                public void onClose(Status status, Metadata trailers) {
+                  tracerFactory.callEnded(status);
+                  super.onClose(status, trailers);
+                }
+              },
+              headers);
+        }
+      };
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/CensusTracingModule.java b/core/src/main/java/io/grpc/internal/CensusTracingModule.java
new file mode 100644
index 0000000..1269f3f
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/CensusTracingModule.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.opencensus.trace.unsafe.ContextUtils.CONTEXT_SPAN_KEY;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ClientStreamTracer;
+import io.grpc.Context;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerStreamTracer;
+import io.grpc.StreamTracer;
+import io.opencensus.trace.BlankSpan;
+import io.opencensus.trace.EndSpanOptions;
+import io.opencensus.trace.MessageEvent;
+import io.opencensus.trace.MessageEvent.Type;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.Status;
+import io.opencensus.trace.Tracer;
+import io.opencensus.trace.propagation.BinaryFormat;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Provides factories for {@link StreamTracer} that records traces to Census.
+ *
+ * <p>On the client-side, a factory is created for each call, because ClientCall starts earlier than
+ * the ClientStream, and in some cases may even not create a ClientStream at all.  Therefore, it's
+ * the factory that reports the summary to Census.
+ *
+ * <p>On the server-side, there is only one ServerStream per each ServerCall, and ServerStream
+ * starts earlier than the ServerCall.  Therefore, only one tracer is created per stream/call and
+ * it's the tracer that reports the summary to Census.
+ */
+final class CensusTracingModule {
+  private static final Logger logger = Logger.getLogger(CensusTracingModule.class.getName());
+
+  @Nullable private static final AtomicIntegerFieldUpdater<ClientCallTracer> callEndedUpdater;
+
+  @Nullable private static final AtomicIntegerFieldUpdater<ServerTracer> streamClosedUpdater;
+
+  /**
+   * When using Atomic*FieldUpdater, some Samsung Android 5.0.x devices encounter a bug in their JDK
+   * reflection API that triggers a NoSuchFieldException. When this occurs, we fallback to
+   * (potentially racy) direct updates of the volatile variables.
+   */
+  static {
+    AtomicIntegerFieldUpdater<ClientCallTracer> tmpCallEndedUpdater;
+    AtomicIntegerFieldUpdater<ServerTracer> tmpStreamClosedUpdater;
+    try {
+      tmpCallEndedUpdater =
+          AtomicIntegerFieldUpdater.newUpdater(ClientCallTracer.class, "callEnded");
+      tmpStreamClosedUpdater =
+          AtomicIntegerFieldUpdater.newUpdater(ServerTracer.class, "streamClosed");
+    } catch (Throwable t) {
+      logger.log(Level.SEVERE, "Creating atomic field updaters failed", t);
+      tmpCallEndedUpdater = null;
+      tmpStreamClosedUpdater = null;
+    }
+    callEndedUpdater = tmpCallEndedUpdater;
+    streamClosedUpdater = tmpStreamClosedUpdater;
+  }
+
+  private final Tracer censusTracer;
+  @VisibleForTesting
+  final Metadata.Key<SpanContext> tracingHeader;
+  private final TracingClientInterceptor clientInterceptor = new TracingClientInterceptor();
+  private final ServerTracerFactory serverTracerFactory = new ServerTracerFactory();
+
+  CensusTracingModule(
+      Tracer censusTracer, final BinaryFormat censusPropagationBinaryFormat) {
+    this.censusTracer = checkNotNull(censusTracer, "censusTracer");
+    checkNotNull(censusPropagationBinaryFormat, "censusPropagationBinaryFormat");
+    this.tracingHeader =
+        Metadata.Key.of("grpc-trace-bin", new Metadata.BinaryMarshaller<SpanContext>() {
+            @Override
+            public byte[] toBytes(SpanContext context) {
+              return censusPropagationBinaryFormat.toByteArray(context);
+            }
+
+            @Override
+            public SpanContext parseBytes(byte[] serialized) {
+              try {
+                return censusPropagationBinaryFormat.fromByteArray(serialized);
+              } catch (Exception e) {
+                logger.log(Level.FINE, "Failed to parse tracing header", e);
+                return SpanContext.INVALID;
+              }
+            }
+          });
+  }
+
+  /**
+   * Creates a {@link ClientCallTracer} for a new call.
+   */
+  @VisibleForTesting
+  ClientCallTracer newClientCallTracer(@Nullable Span parentSpan, MethodDescriptor<?, ?> method) {
+    return new ClientCallTracer(parentSpan, method);
+  }
+
+  /**
+   * Returns the server tracer factory.
+   */
+  ServerStreamTracer.Factory getServerTracerFactory() {
+    return serverTracerFactory;
+  }
+
+  /**
+   * Returns the client interceptor that facilitates Census-based stats reporting.
+   */
+  ClientInterceptor getClientInterceptor() {
+    return clientInterceptor;
+  }
+
+  @VisibleForTesting
+  static Status convertStatus(io.grpc.Status grpcStatus) {
+    Status status;
+    switch (grpcStatus.getCode()) {
+      case OK:
+        status = Status.OK;
+        break;
+      case CANCELLED:
+        status = Status.CANCELLED;
+        break;
+      case UNKNOWN:
+        status = Status.UNKNOWN;
+        break;
+      case INVALID_ARGUMENT:
+        status = Status.INVALID_ARGUMENT;
+        break;
+      case DEADLINE_EXCEEDED:
+        status = Status.DEADLINE_EXCEEDED;
+        break;
+      case NOT_FOUND:
+        status = Status.NOT_FOUND;
+        break;
+      case ALREADY_EXISTS:
+        status = Status.ALREADY_EXISTS;
+        break;
+      case PERMISSION_DENIED:
+        status = Status.PERMISSION_DENIED;
+        break;
+      case RESOURCE_EXHAUSTED:
+        status = Status.RESOURCE_EXHAUSTED;
+        break;
+      case FAILED_PRECONDITION:
+        status = Status.FAILED_PRECONDITION;
+        break;
+      case ABORTED:
+        status = Status.ABORTED;
+        break;
+      case OUT_OF_RANGE:
+        status = Status.OUT_OF_RANGE;
+        break;
+      case UNIMPLEMENTED:
+        status = Status.UNIMPLEMENTED;
+        break;
+      case INTERNAL:
+        status = Status.INTERNAL;
+        break;
+      case UNAVAILABLE:
+        status = Status.UNAVAILABLE;
+        break;
+      case DATA_LOSS:
+        status = Status.DATA_LOSS;
+        break;
+      case UNAUTHENTICATED:
+        status = Status.UNAUTHENTICATED;
+        break;
+      default:
+        throw new AssertionError("Unhandled status code " + grpcStatus.getCode());
+    }
+    if (grpcStatus.getDescription() != null) {
+      status = status.withDescription(grpcStatus.getDescription());
+    }
+    return status;
+  }
+
+  private static EndSpanOptions createEndSpanOptions(
+      io.grpc.Status status, boolean sampledToLocalTracing) {
+    return EndSpanOptions.builder()
+        .setStatus(convertStatus(status))
+        .setSampleToLocalSpanStore(sampledToLocalTracing)
+        .build();
+  }
+
+  private static void recordMessageEvent(
+      Span span, MessageEvent.Type type,
+      int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+    MessageEvent.Builder eventBuilder = MessageEvent.builder(type, seqNo);
+    if (optionalUncompressedSize != -1) {
+      eventBuilder.setUncompressedMessageSize(optionalUncompressedSize);
+    }
+    if (optionalWireSize != -1) {
+      eventBuilder.setCompressedMessageSize(optionalWireSize);
+    }
+    span.addMessageEvent(eventBuilder.build());
+  }
+
+  @VisibleForTesting
+  final class ClientCallTracer extends ClientStreamTracer.Factory {
+    volatile int callEnded;
+
+    private final boolean isSampledToLocalTracing;
+    private final Span span;
+
+    ClientCallTracer(@Nullable Span parentSpan, MethodDescriptor<?, ?> method) {
+      checkNotNull(method, "method");
+      this.isSampledToLocalTracing = method.isSampledToLocalTracing();
+      this.span =
+          censusTracer
+              .spanBuilderWithExplicitParent(
+                  generateTraceSpanName(false, method.getFullMethodName()),
+                  parentSpan)
+              .setRecordEvents(true)
+              .startSpan();
+    }
+
+    @Override
+    public ClientStreamTracer newClientStreamTracer(CallOptions callOptions, Metadata headers) {
+      if (span != BlankSpan.INSTANCE) {
+        headers.discardAll(tracingHeader);
+        headers.put(tracingHeader, span.getContext());
+      }
+      return new ClientTracer(span);
+    }
+
+    /**
+     * Record a finished call and mark the current time as the end time.
+     *
+     * <p>Can be called from any thread without synchronization.  Calling it the second time or more
+     * is a no-op.
+     */
+    void callEnded(io.grpc.Status status) {
+      if (callEndedUpdater != null) {
+        if (callEndedUpdater.getAndSet(this, 1) != 0) {
+          return;
+        }
+      } else {
+        if (callEnded != 0) {
+          return;
+        }
+        callEnded = 1;
+      }
+      span.end(createEndSpanOptions(status, isSampledToLocalTracing));
+    }
+  }
+
+  private static final class ClientTracer extends ClientStreamTracer {
+    private final Span span;
+
+    ClientTracer(Span span) {
+      this.span = checkNotNull(span, "span");
+    }
+
+    @Override
+    public void outboundMessageSent(
+        int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+      recordMessageEvent(
+          span, Type.SENT, seqNo, optionalWireSize, optionalUncompressedSize);
+    }
+
+    @Override
+    public void inboundMessageRead(
+        int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+      recordMessageEvent(
+          span, Type.RECEIVED, seqNo, optionalWireSize, optionalUncompressedSize);
+    }
+  }
+
+
+  private final class ServerTracer extends ServerStreamTracer {
+    private final Span span;
+    volatile boolean isSampledToLocalTracing;
+    volatile int streamClosed;
+
+    ServerTracer(String fullMethodName, @Nullable SpanContext remoteSpan) {
+      checkNotNull(fullMethodName, "fullMethodName");
+      this.span =
+          censusTracer
+              .spanBuilderWithRemoteParent(
+                  generateTraceSpanName(true, fullMethodName),
+                  remoteSpan)
+              .setRecordEvents(true)
+              .startSpan();
+    }
+
+    @Override
+    public void serverCallStarted(ServerCallInfo<?, ?> callInfo) {
+      isSampledToLocalTracing = callInfo.getMethodDescriptor().isSampledToLocalTracing();
+    }
+
+    /**
+     * Record a finished stream and mark the current time as the end time.
+     *
+     * <p>Can be called from any thread without synchronization.  Calling it the second time or more
+     * is a no-op.
+     */
+    @Override
+    public void streamClosed(io.grpc.Status status) {
+      if (streamClosedUpdater != null) {
+        if (streamClosedUpdater.getAndSet(this, 1) != 0) {
+          return;
+        }
+      } else {
+        if (streamClosed != 0) {
+          return;
+        }
+        streamClosed = 1;
+      }
+      span.end(createEndSpanOptions(status, isSampledToLocalTracing));
+    }
+
+    @Override
+    public Context filterContext(Context context) {
+      // Access directly the unsafe trace API to create the new Context. This is a safe usage
+      // because gRPC always creates a new Context for each of the server calls and does not
+      // inherit from the parent Context.
+      return context.withValue(CONTEXT_SPAN_KEY, span);
+    }
+
+    @Override
+    public void outboundMessageSent(
+        int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+      recordMessageEvent(
+          span, Type.SENT, seqNo, optionalWireSize, optionalUncompressedSize);
+    }
+
+    @Override
+    public void inboundMessageRead(
+        int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+      recordMessageEvent(
+          span, Type.RECEIVED, seqNo, optionalWireSize, optionalUncompressedSize);
+    }
+  }
+
+  @VisibleForTesting
+  final class ServerTracerFactory extends ServerStreamTracer.Factory {
+    @SuppressWarnings("ReferenceEquality")
+    @Override
+    public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata headers) {
+      SpanContext remoteSpan = headers.get(tracingHeader);
+      if (remoteSpan == SpanContext.INVALID) {
+        remoteSpan = null;
+      }
+      return new ServerTracer(fullMethodName, remoteSpan);
+    }
+  }
+
+  @VisibleForTesting
+  final class TracingClientInterceptor implements ClientInterceptor {
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+        MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+      // New RPCs on client-side inherit the tracing context from the current Context.
+      // Safe usage of the unsafe trace API because CONTEXT_SPAN_KEY.get() returns the same value
+      // as Tracer.getCurrentSpan() except when no value available when the return value is null
+      // for the direct access and BlankSpan when Tracer API is used.
+      final ClientCallTracer tracerFactory = newClientCallTracer(CONTEXT_SPAN_KEY.get(), method);
+      ClientCall<ReqT, RespT> call =
+          next.newCall(
+              method,
+              callOptions.withStreamTracerFactory(tracerFactory));
+      return new SimpleForwardingClientCall<ReqT, RespT>(call) {
+        @Override
+        public void start(Listener<RespT> responseListener, Metadata headers) {
+          delegate().start(
+              new SimpleForwardingClientCallListener<RespT>(responseListener) {
+                @Override
+                public void onClose(io.grpc.Status status, Metadata trailers) {
+                  tracerFactory.callEnded(status);
+                  super.onClose(status, trailers);
+                }
+              },
+              headers);
+        }
+      };
+    }
+  }
+
+  /**
+   * Convert a full method name to a tracing span name.
+   *
+   * @param isServer {@code false} if the span is on the client-side, {@code true} if on the
+   *                 server-side
+   * @param fullMethodName the method name as returned by
+   *        {@link MethodDescriptor#getFullMethodName}.
+   */
+  @VisibleForTesting
+  static String generateTraceSpanName(boolean isServer, String fullMethodName) {
+    String prefix = isServer ? "Recv" : "Sent";
+    return prefix + "." + fullMethodName.replace('/', '.');
+  }
+
+}
diff --git a/core/src/main/java/io/grpc/internal/ChannelExecutor.java b/core/src/main/java/io/grpc/internal/ChannelExecutor.java
new file mode 100644
index 0000000..20a29a1
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ChannelExecutor.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * The thread-less Channel Executor used to run the state mutation logic in {@link
+ * ManagedChannelImpl}, {@link InternalSubchannel} and {@link io.grpc.LoadBalancer}s.
+ *
+ * <p>Tasks are queued until {@link #drain} is called.  Tasks are guaranteed to be run in the same
+ * order as they are submitted.
+ */
+@ThreadSafe
+class ChannelExecutor {
+  private static final Logger log = Logger.getLogger(ChannelExecutor.class.getName());
+
+  private final Object lock = new Object();
+
+  @GuardedBy("lock")
+  private final Queue<Runnable> queue = new ArrayDeque<Runnable>();
+  @GuardedBy("lock")
+  private boolean draining;
+
+  /**
+   * Run all tasks in the queue in the current thread, if no other thread is in this method.
+   * Otherwise do nothing.
+   *
+   * <p>Upon returning, it guarantees that all tasks submitted by {@code executeLater()} before it
+   * have been or will eventually be run, while not requiring any more calls to {@code drain()}.
+   */
+  final void drain() {
+    boolean drainLeaseAcquired = false;
+    while (true) {
+      Runnable runnable;
+      synchronized (lock) {
+        if (!drainLeaseAcquired) {
+          if (draining) {
+            return;
+          }
+          draining = true;
+          drainLeaseAcquired = true;
+        }
+        runnable = queue.poll();
+        if (runnable == null) {
+          draining = false;
+          break;
+        }
+      }
+      try {
+        runnable.run();
+      } catch (Throwable t) {
+        handleUncaughtThrowable(t);
+      }
+    }
+  }
+
+  /**
+   * Enqueues a task that will be run when {@link #drain} is called.
+   *
+   * @return this ChannelExecutor
+   */
+  final ChannelExecutor executeLater(Runnable runnable) {
+    synchronized (lock) {
+      queue.add(checkNotNull(runnable, "runnable is null"));
+    }
+    return this;
+  }
+
+  @VisibleForTesting
+  final int numPendingTasks() {
+    synchronized (lock) {
+      return queue.size();
+    }
+  }
+
+  /**
+   * Handle a throwable from a task.
+   *
+   * <p>The default implementation logs a warning.
+   */
+  void handleUncaughtThrowable(Throwable t) {
+    log.log(Level.WARNING, "Runnable threw exception in ChannelExecutor", t);
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ChannelTracer.java b/core/src/main/java/io/grpc/internal/ChannelTracer.java
new file mode 100644
index 0000000..be9f4a9
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ChannelTracer.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.grpc.InternalChannelz.ChannelStats;
+import io.grpc.InternalChannelz.ChannelTrace;
+import io.grpc.InternalChannelz.ChannelTrace.Event;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Tracks a collections of channel tracing events for a channel/subchannel.
+ */
+final class ChannelTracer {
+  private final Object lock = new Object();
+  @GuardedBy("lock")
+  private final Collection<Event> events;
+  private final long channelCreationTimeNanos;
+
+  @GuardedBy("lock")
+  private int eventsLogged;
+
+  /**
+   * Creates a channel tracer and log the creation event of the underlying channel.
+   *
+   * @param channelType Chennel, Subchannel, or OobChannel
+   */
+  ChannelTracer(final int maxEvents, long channelCreationTimeNanos, String channelType) {
+    checkArgument(maxEvents > 0, "maxEvents must be greater than zero");
+    checkNotNull(channelType, "channelType");
+    events = new ArrayDeque<Event>() {
+      @GuardedBy("lock")
+      @Override
+      public boolean add(Event event) {
+        if (size() == maxEvents) {
+          removeFirst();
+        }
+        eventsLogged++;
+        return super.add(event);
+      }
+    };
+    this.channelCreationTimeNanos = channelCreationTimeNanos;
+
+    reportEvent(new ChannelTrace.Event.Builder()
+        .setDescription(channelType + " created")
+        .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+        // passing the timestamp in as a parameter instead of computing it right here because when
+        // parent channel and subchannel both report the same event of the subchannel (e.g. creation
+        // event of the subchannel) we want the timestamps to be exactly the same.
+        .setTimestampNanos(channelCreationTimeNanos)
+        .build());
+  }
+
+  void reportEvent(Event event) {
+    synchronized (lock) {
+      events.add(event);
+    }
+  }
+
+  void updateBuilder(ChannelStats.Builder builder) {
+    List<Event> eventsSnapshot;
+    int eventsLoggedSnapshot;
+    synchronized (lock) {
+      eventsLoggedSnapshot = eventsLogged;
+      eventsSnapshot = new ArrayList<>(events);
+    }
+    builder.setChannelTrace(new ChannelTrace.Builder()
+        .setNumEventsLogged(eventsLoggedSnapshot)
+        .setCreationTimeNanos(channelCreationTimeNanos)
+        .setEvents(eventsSnapshot)
+        .build());
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ClientCallImpl.java b/core/src/main/java/io/grpc/internal/ClientCallImpl.java
new file mode 100644
index 0000000..99bf9dd
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ClientCallImpl.java
@@ -0,0 +1,614 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+import static io.grpc.Contexts.statusFromCancelled;
+import static io.grpc.Status.DEADLINE_EXCEEDED;
+import static io.grpc.internal.GrpcUtil.CONTENT_ACCEPT_ENCODING_KEY;
+import static io.grpc.internal.GrpcUtil.CONTENT_ENCODING_KEY;
+import static io.grpc.internal.GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY;
+import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
+import static java.lang.Math.max;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import io.grpc.Attributes;
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.Codec;
+import io.grpc.Compressor;
+import io.grpc.CompressorRegistry;
+import io.grpc.Context;
+import io.grpc.Context.CancellationListener;
+import io.grpc.Deadline;
+import io.grpc.DecompressorRegistry;
+import io.grpc.InternalDecompressorRegistry;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.Status;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Implementation of {@link ClientCall}.
+ */
+final class ClientCallImpl<ReqT, RespT> extends ClientCall<ReqT, RespT> {
+
+  private static final Logger log = Logger.getLogger(ClientCallImpl.class.getName());
+  private static final byte[] FULL_STREAM_DECOMPRESSION_ENCODINGS
+      = "gzip".getBytes(Charset.forName("US-ASCII"));
+
+  private final MethodDescriptor<ReqT, RespT> method;
+  private final Executor callExecutor;
+  private final CallTracer channelCallsTracer;
+  private final Context context;
+  private volatile ScheduledFuture<?> deadlineCancellationFuture;
+  private final boolean unaryRequest;
+  private final CallOptions callOptions;
+  private final boolean retryEnabled;
+  private ClientStream stream;
+  private volatile boolean cancelListenersShouldBeRemoved;
+  private boolean cancelCalled;
+  private boolean halfCloseCalled;
+  private final ClientTransportProvider clientTransportProvider;
+  private final CancellationListener cancellationListener = new ContextCancellationListener();
+  private final ScheduledExecutorService deadlineCancellationExecutor;
+  private boolean fullStreamDecompression;
+  private DecompressorRegistry decompressorRegistry = DecompressorRegistry.getDefaultInstance();
+  private CompressorRegistry compressorRegistry = CompressorRegistry.getDefaultInstance();
+
+  ClientCallImpl(
+      MethodDescriptor<ReqT, RespT> method, Executor executor, CallOptions callOptions,
+      ClientTransportProvider clientTransportProvider,
+      ScheduledExecutorService deadlineCancellationExecutor,
+      CallTracer channelCallsTracer,
+      boolean retryEnabled) {
+    this.method = method;
+    // If we know that the executor is a direct executor, we don't need to wrap it with a
+    // SerializingExecutor. This is purely for performance reasons.
+    // See https://github.com/grpc/grpc-java/issues/368
+    this.callExecutor = executor == directExecutor()
+        ? new SerializeReentrantCallsDirectExecutor()
+        : new SerializingExecutor(executor);
+    this.channelCallsTracer = channelCallsTracer;
+    // Propagate the context from the thread which initiated the call to all callbacks.
+    this.context = Context.current();
+    this.unaryRequest = method.getType() == MethodType.UNARY
+        || method.getType() == MethodType.SERVER_STREAMING;
+    this.callOptions = callOptions;
+    this.clientTransportProvider = clientTransportProvider;
+    this.deadlineCancellationExecutor = deadlineCancellationExecutor;
+    this.retryEnabled = retryEnabled;
+  }
+
+  private final class ContextCancellationListener implements CancellationListener {
+    @Override
+    public void cancelled(Context context) {
+      stream.cancel(statusFromCancelled(context));
+    }
+  }
+
+  /**
+   * Provider of {@link ClientTransport}s.
+   */
+  // TODO(zdapeng): replace the two APIs with a single API: newStream()
+  interface ClientTransportProvider {
+    /**
+     * Returns a transport for a new call.
+     *
+     * @param args object containing call arguments.
+     */
+    ClientTransport get(PickSubchannelArgs args);
+
+    <ReqT> RetriableStream<ReqT> newRetriableStream(
+        MethodDescriptor<ReqT, ?> method,
+        CallOptions callOptions,
+        Metadata headers,
+        Context context);
+
+  }
+
+  ClientCallImpl<ReqT, RespT> setFullStreamDecompression(boolean fullStreamDecompression) {
+    this.fullStreamDecompression = fullStreamDecompression;
+    return this;
+  }
+
+  ClientCallImpl<ReqT, RespT> setDecompressorRegistry(DecompressorRegistry decompressorRegistry) {
+    this.decompressorRegistry = decompressorRegistry;
+    return this;
+  }
+
+  ClientCallImpl<ReqT, RespT> setCompressorRegistry(CompressorRegistry compressorRegistry) {
+    this.compressorRegistry = compressorRegistry;
+    return this;
+  }
+
+  @VisibleForTesting
+  static void prepareHeaders(
+      Metadata headers,
+      DecompressorRegistry decompressorRegistry,
+      Compressor compressor,
+      boolean fullStreamDecompression) {
+    headers.discardAll(MESSAGE_ENCODING_KEY);
+    if (compressor != Codec.Identity.NONE) {
+      headers.put(MESSAGE_ENCODING_KEY, compressor.getMessageEncoding());
+    }
+
+    headers.discardAll(MESSAGE_ACCEPT_ENCODING_KEY);
+    byte[] advertisedEncodings =
+        InternalDecompressorRegistry.getRawAdvertisedMessageEncodings(decompressorRegistry);
+    if (advertisedEncodings.length != 0) {
+      headers.put(MESSAGE_ACCEPT_ENCODING_KEY, advertisedEncodings);
+    }
+
+    headers.discardAll(CONTENT_ENCODING_KEY);
+    headers.discardAll(CONTENT_ACCEPT_ENCODING_KEY);
+    if (fullStreamDecompression) {
+      headers.put(CONTENT_ACCEPT_ENCODING_KEY, FULL_STREAM_DECOMPRESSION_ENCODINGS);
+    }
+  }
+
+  @Override
+  public void start(final Listener<RespT> observer, Metadata headers) {
+    checkState(stream == null, "Already started");
+    checkState(!cancelCalled, "call was cancelled");
+    checkNotNull(observer, "observer");
+    checkNotNull(headers, "headers");
+
+    if (context.isCancelled()) {
+      // Context is already cancelled so no need to create a real stream, just notify the observer
+      // of cancellation via callback on the executor
+      stream = NoopClientStream.INSTANCE;
+      class ClosedByContext extends ContextRunnable {
+        ClosedByContext() {
+          super(context);
+        }
+
+        @Override
+        public void runInContext() {
+          closeObserver(observer, statusFromCancelled(context), new Metadata());
+        }
+      }
+
+      callExecutor.execute(new ClosedByContext());
+      return;
+    }
+    final String compressorName = callOptions.getCompressor();
+    Compressor compressor = null;
+    if (compressorName != null) {
+      compressor = compressorRegistry.lookupCompressor(compressorName);
+      if (compressor == null) {
+        stream = NoopClientStream.INSTANCE;
+        class ClosedByNotFoundCompressor extends ContextRunnable {
+          ClosedByNotFoundCompressor() {
+            super(context);
+          }
+
+          @Override
+          public void runInContext() {
+            closeObserver(
+                observer,
+                Status.INTERNAL.withDescription(
+                    String.format("Unable to find compressor by name %s", compressorName)),
+                new Metadata());
+          }
+        }
+
+        callExecutor.execute(new ClosedByNotFoundCompressor());
+        return;
+      }
+    } else {
+      compressor = Codec.Identity.NONE;
+    }
+    prepareHeaders(headers, decompressorRegistry, compressor, fullStreamDecompression);
+
+    Deadline effectiveDeadline = effectiveDeadline();
+    boolean deadlineExceeded = effectiveDeadline != null && effectiveDeadline.isExpired();
+    if (!deadlineExceeded) {
+      logIfContextNarrowedTimeout(
+          effectiveDeadline, callOptions.getDeadline(), context.getDeadline());
+      if (retryEnabled) {
+        stream = clientTransportProvider.newRetriableStream(method, callOptions, headers, context);
+      } else {
+        ClientTransport transport = clientTransportProvider.get(
+            new PickSubchannelArgsImpl(method, headers, callOptions));
+        Context origContext = context.attach();
+        try {
+          stream = transport.newStream(method, headers, callOptions);
+        } finally {
+          context.detach(origContext);
+        }
+      }
+    } else {
+      stream = new FailingClientStream(
+          DEADLINE_EXCEEDED.withDescription("deadline exceeded: " + effectiveDeadline));
+    }
+
+    if (callOptions.getAuthority() != null) {
+      stream.setAuthority(callOptions.getAuthority());
+    }
+    if (callOptions.getMaxInboundMessageSize() != null) {
+      stream.setMaxInboundMessageSize(callOptions.getMaxInboundMessageSize());
+    }
+    if (callOptions.getMaxOutboundMessageSize() != null) {
+      stream.setMaxOutboundMessageSize(callOptions.getMaxOutboundMessageSize());
+    }
+    if (effectiveDeadline != null) {
+      stream.setDeadline(effectiveDeadline);
+    }
+    stream.setCompressor(compressor);
+    if (fullStreamDecompression) {
+      stream.setFullStreamDecompression(fullStreamDecompression);
+    }
+    stream.setDecompressorRegistry(decompressorRegistry);
+    channelCallsTracer.reportCallStarted();
+    stream.start(new ClientStreamListenerImpl(observer));
+
+    // Delay any sources of cancellation after start(), because most of the transports are broken if
+    // they receive cancel before start. Issue #1343 has more details
+
+    // Propagate later Context cancellation to the remote side.
+    context.addListener(cancellationListener, directExecutor());
+    if (effectiveDeadline != null
+        // If the context has the effective deadline, we don't need to schedule an extra task.
+        && context.getDeadline() != effectiveDeadline
+        // If the channel has been terminated, we don't need to schedule an extra task.
+        && deadlineCancellationExecutor != null) {
+      deadlineCancellationFuture = startDeadlineTimer(effectiveDeadline);
+    }
+    if (cancelListenersShouldBeRemoved) {
+      // Race detected! ClientStreamListener.closed may have been called before
+      // deadlineCancellationFuture was set / context listener added, thereby preventing the future
+      // and listener from being cancelled. Go ahead and cancel again, just to be sure it
+      // was cancelled.
+      removeContextListenerAndCancelDeadlineFuture();
+    }
+  }
+
+  private static void logIfContextNarrowedTimeout(
+      Deadline effectiveDeadline, @Nullable Deadline outerCallDeadline,
+      @Nullable Deadline callDeadline) {
+    if (!log.isLoggable(Level.FINE) || effectiveDeadline == null
+        || outerCallDeadline != effectiveDeadline) {
+      return;
+    }
+
+    long effectiveTimeout = max(0, effectiveDeadline.timeRemaining(TimeUnit.NANOSECONDS));
+    StringBuilder builder = new StringBuilder(String.format(
+        "Call timeout set to '%d' ns, due to context deadline.", effectiveTimeout));
+    if (callDeadline == null) {
+      builder.append(" Explicit call timeout was not set.");
+    } else {
+      long callTimeout = callDeadline.timeRemaining(TimeUnit.NANOSECONDS);
+      builder.append(String.format(" Explicit call timeout was '%d' ns.", callTimeout));
+    }
+
+    log.fine(builder.toString());
+  }
+
+  private void removeContextListenerAndCancelDeadlineFuture() {
+    context.removeListener(cancellationListener);
+    ScheduledFuture<?> f = deadlineCancellationFuture;
+    if (f != null) {
+      f.cancel(false);
+    }
+  }
+
+  private class DeadlineTimer implements Runnable {
+    private final long remainingNanos;
+
+    DeadlineTimer(long remainingNanos) {
+      this.remainingNanos = remainingNanos;
+    }
+
+    @Override
+    public void run() {
+      // DelayedStream.cancel() is safe to call from a thread that is different from where the
+      // stream is created.
+      stream.cancel(DEADLINE_EXCEEDED.augmentDescription(
+          String.format("deadline exceeded after %dns", remainingNanos)));
+    }
+  }
+
+  private ScheduledFuture<?> startDeadlineTimer(Deadline deadline) {
+    long remainingNanos = deadline.timeRemaining(TimeUnit.NANOSECONDS);
+    return deadlineCancellationExecutor.schedule(
+        new LogExceptionRunnable(
+            new DeadlineTimer(remainingNanos)), remainingNanos, TimeUnit.NANOSECONDS);
+  }
+
+  @Nullable
+  private Deadline effectiveDeadline() {
+    // Call options and context are immutable, so we don't need to cache the deadline.
+    return min(callOptions.getDeadline(), context.getDeadline());
+  }
+
+  @Nullable
+  private static Deadline min(@Nullable Deadline deadline0, @Nullable Deadline deadline1) {
+    if (deadline0 == null) {
+      return deadline1;
+    }
+    if (deadline1 == null) {
+      return deadline0;
+    }
+    return deadline0.minimum(deadline1);
+  }
+
+  @Override
+  public void request(int numMessages) {
+    checkState(stream != null, "Not started");
+    checkArgument(numMessages >= 0, "Number requested must be non-negative");
+    stream.request(numMessages);
+  }
+
+  @Override
+  public void cancel(@Nullable String message, @Nullable Throwable cause) {
+    if (message == null && cause == null) {
+      cause = new CancellationException("Cancelled without a message or cause");
+      log.log(Level.WARNING, "Cancelling without a message or cause is suboptimal", cause);
+    }
+    if (cancelCalled) {
+      return;
+    }
+    cancelCalled = true;
+    try {
+      // Cancel is called in exception handling cases, so it may be the case that the
+      // stream was never successfully created or start has never been called.
+      if (stream != null) {
+        Status status = Status.CANCELLED;
+        if (message != null) {
+          status = status.withDescription(message);
+        } else {
+          status = status.withDescription("Call cancelled without message");
+        }
+        if (cause != null) {
+          status = status.withCause(cause);
+        }
+        stream.cancel(status);
+      }
+    } finally {
+      removeContextListenerAndCancelDeadlineFuture();
+    }
+  }
+
+  @Override
+  public void halfClose() {
+    checkState(stream != null, "Not started");
+    checkState(!cancelCalled, "call was cancelled");
+    checkState(!halfCloseCalled, "call already half-closed");
+    halfCloseCalled = true;
+    stream.halfClose();
+  }
+
+  @Override
+  public void sendMessage(ReqT message) {
+    checkState(stream != null, "Not started");
+    checkState(!cancelCalled, "call was cancelled");
+    checkState(!halfCloseCalled, "call was half-closed");
+    try {
+      if (stream instanceof RetriableStream) {
+        @SuppressWarnings("unchecked")
+        RetriableStream<ReqT> retriableStream = ((RetriableStream<ReqT>) stream);
+        retriableStream.sendMessage(message);
+      } else {
+        stream.writeMessage(method.streamRequest(message));
+      }
+    } catch (RuntimeException e) {
+      stream.cancel(Status.CANCELLED.withCause(e).withDescription("Failed to stream message"));
+      return;
+    } catch (Error e) {
+      stream.cancel(Status.CANCELLED.withDescription("Client sendMessage() failed with Error"));
+      throw e;
+    }
+    // For unary requests, we don't flush since we know that halfClose should be coming soon. This
+    // allows us to piggy-back the END_STREAM=true on the last message frame without opening the
+    // possibility of broken applications forgetting to call halfClose without noticing.
+    if (!unaryRequest) {
+      stream.flush();
+    }
+  }
+
+  @Override
+  public void setMessageCompression(boolean enabled) {
+    checkState(stream != null, "Not started");
+    stream.setMessageCompression(enabled);
+  }
+
+  @Override
+  public boolean isReady() {
+    return stream.isReady();
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    if (stream != null) {
+      return stream.getAttributes();
+    }
+    return Attributes.EMPTY;
+  }
+
+  private void closeObserver(Listener<RespT> observer, Status status, Metadata trailers) {
+    observer.onClose(status, trailers);
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("method", method).toString();
+  }
+
+  private class ClientStreamListenerImpl implements ClientStreamListener {
+    private final Listener<RespT> observer;
+    private boolean closed;
+
+    public ClientStreamListenerImpl(Listener<RespT> observer) {
+      this.observer = checkNotNull(observer, "observer");
+    }
+
+    @Override
+    public void headersRead(final Metadata headers) {
+      class HeadersRead extends ContextRunnable {
+        HeadersRead() {
+          super(context);
+        }
+
+        @Override
+        public final void runInContext() {
+          try {
+            if (closed) {
+              return;
+            }
+            observer.onHeaders(headers);
+          } catch (Throwable t) {
+            Status status =
+                Status.CANCELLED.withCause(t).withDescription("Failed to read headers");
+            stream.cancel(status);
+            close(status, new Metadata());
+          }
+        }
+      }
+
+      callExecutor.execute(new HeadersRead());
+    }
+
+    @Override
+    public void messagesAvailable(final MessageProducer producer) {
+      class MessagesAvailable extends ContextRunnable {
+        MessagesAvailable() {
+          super(context);
+        }
+
+        @Override
+        public final void runInContext() {
+          if (closed) {
+            GrpcUtil.closeQuietly(producer);
+            return;
+          }
+
+          InputStream message;
+          try {
+            while ((message = producer.next()) != null) {
+              try {
+                observer.onMessage(method.parseResponse(message));
+              } catch (Throwable t) {
+                GrpcUtil.closeQuietly(message);
+                throw t;
+              }
+              message.close();
+            }
+          } catch (Throwable t) {
+            GrpcUtil.closeQuietly(producer);
+            Status status =
+                Status.CANCELLED.withCause(t).withDescription("Failed to read message.");
+            stream.cancel(status);
+            close(status, new Metadata());
+          }
+        }
+      }
+
+      callExecutor.execute(new MessagesAvailable());
+    }
+
+    /**
+     * Must be called from application thread.
+     */
+    private void close(Status status, Metadata trailers) {
+      closed = true;
+      cancelListenersShouldBeRemoved = true;
+      try {
+        closeObserver(observer, status, trailers);
+      } finally {
+        removeContextListenerAndCancelDeadlineFuture();
+        channelCallsTracer.reportCallEnded(status.isOk());
+      }
+    }
+
+    @Override
+    public void closed(Status status, Metadata trailers) {
+      closed(status, RpcProgress.PROCESSED, trailers);
+    }
+
+    @Override
+    public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) {
+      Deadline deadline = effectiveDeadline();
+      if (status.getCode() == Status.Code.CANCELLED && deadline != null) {
+        // When the server's deadline expires, it can only reset the stream with CANCEL and no
+        // description. Since our timer may be delayed in firing, we double-check the deadline and
+        // turn the failure into the likely more helpful DEADLINE_EXCEEDED status.
+        if (deadline.isExpired()) {
+          status = DEADLINE_EXCEEDED;
+          // Replace trailers to prevent mixing sources of status and trailers.
+          trailers = new Metadata();
+        }
+      }
+      final Status savedStatus = status;
+      final Metadata savedTrailers = trailers;
+      class StreamClosed extends ContextRunnable {
+        StreamClosed() {
+          super(context);
+        }
+
+        @Override
+        public final void runInContext() {
+          if (closed) {
+            // We intentionally don't keep the status or metadata from the server.
+            return;
+          }
+          close(savedStatus, savedTrailers);
+        }
+      }
+
+      callExecutor.execute(new StreamClosed());
+    }
+
+    @Override
+    public void onReady() {
+      class StreamOnReady extends ContextRunnable {
+        StreamOnReady() {
+          super(context);
+        }
+
+        @Override
+        public final void runInContext() {
+          try {
+            observer.onReady();
+          } catch (Throwable t) {
+            Status status =
+                Status.CANCELLED.withCause(t).withDescription("Failed to call onReady.");
+            stream.cancel(status);
+            close(status, new Metadata());
+          }
+        }
+      }
+
+      callExecutor.execute(new StreamOnReady());
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ClientStream.java b/core/src/main/java/io/grpc/internal/ClientStream.java
new file mode 100644
index 0000000..19203dd
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ClientStream.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Attributes;
+import io.grpc.Deadline;
+import io.grpc.DecompressorRegistry;
+import io.grpc.Status;
+import javax.annotation.Nonnull;
+
+/**
+ * Extension of {@link Stream} to support client-side termination semantics.
+ *
+ * <p>An implementation doesn't need to be thread-safe. All methods are expected to execute quickly.
+ */
+public interface ClientStream extends Stream {
+
+  /**
+   * Abnormally terminates the stream. After calling this method, no further messages will be
+   * sent or received, however it may still be possible to receive buffered messages for a brief
+   * period until {@link ClientStreamListener#closed} is called. This method may only be called
+   * after {@link #start}, but else is safe to be called at any time and multiple times and
+   * from any thread.
+   *
+   * @param reason must be non-OK
+   */
+  void cancel(Status reason);
+
+  /**
+   * Closes the local side of this stream and flushes any remaining messages. After this is called,
+   * no further messages may be sent on this stream, but additional messages may be received until
+   * the remote end-point is closed. This method may only be called once, and only after
+   * {@link #start}.
+   */
+  void halfClose();
+
+  /**
+   * Override the default authority with {@code authority}. May only be called before {@link
+   * #start}.
+   */
+  void setAuthority(String authority);
+
+  /**
+   * Enables full-stream decompression, allowing the client stream to use {@link
+   * GzipInflatingBuffer} to decode inbound GZIP compressed streams.
+   */
+  void setFullStreamDecompression(boolean fullStreamDecompression);
+
+  /**
+   * Sets the registry to find a decompressor for the framer. May only be called before {@link
+   * #start}. If the transport does not support compression, this may do nothing.
+   *
+   * @param decompressorRegistry the registry of decompressors for decoding responses
+   */
+  void setDecompressorRegistry(DecompressorRegistry decompressorRegistry);
+
+  /**
+   * Starts stream. This method may only be called once.  It is safe to do latent initialization of
+   * the stream up until {@link #start} is called.
+   *
+   * <p>This method should not throw any exceptions.
+   *
+   * @param listener non-{@code null} listener of stream events
+   */
+  void start(ClientStreamListener listener);
+
+  /**
+   * Sets the max size accepted from the remote endpoint.
+   */
+  void setMaxInboundMessageSize(int maxSize);
+
+  /**
+   * Sets the max size sent to the remote endpoint.
+   */
+  void setMaxOutboundMessageSize(int maxSize);
+
+  /**
+   * Sets the effective deadline of the RPC.
+   */
+  void setDeadline(@Nonnull Deadline deadline);
+
+  /**
+   * Attributes that the stream holds at the current moment.
+   */
+  Attributes getAttributes();
+}
diff --git a/core/src/main/java/io/grpc/internal/ClientStreamListener.java b/core/src/main/java/io/grpc/internal/ClientStreamListener.java
new file mode 100644
index 0000000..6374d55
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ClientStreamListener.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Metadata;
+import io.grpc.Status;
+
+/** An observer of client-side stream events. */
+public interface ClientStreamListener extends StreamListener {
+  /**
+   * Called upon receiving all header information from the remote end-point. Note that transports
+   * are not required to call this method if no header information is received, this would occur
+   * when a stream immediately terminates with an error and only
+   * {@link #closed(io.grpc.Status, Metadata)} is called.
+   *
+   * <p>This method should return quickly, as the same thread may be used to process other streams.
+   *
+   * @param headers the fully buffered received headers.
+   */
+  void headersRead(Metadata headers);
+
+  /**
+   * Called when the stream is fully closed. {@link
+   * io.grpc.Status.Code#OK} is the only status code that is guaranteed
+   * to have been sent from the remote server. Any other status code may have been caused by
+   * abnormal stream termination. This is guaranteed to always be the final call on a listener. No
+   * further callbacks will be issued.
+   *
+   * <p>This method should return quickly, as the same thread may be used to process other streams.
+   *
+   * @param status details about the remote closure
+   * @param trailers trailing metadata
+   */
+  // TODO(zdapeng): remove this method in favor of the 3-arg one.
+  void closed(Status status, Metadata trailers);
+
+  /**
+   * Called when the stream is fully closed. {@link
+   * io.grpc.Status.Code#OK} is the only status code that is guaranteed
+   * to have been sent from the remote server. Any other status code may have been caused by
+   * abnormal stream termination. This is guaranteed to always be the final call on a listener. No
+   * further callbacks will be issued.
+   *
+   * <p>This method should return quickly, as the same thread may be used to process other streams.
+   *
+   * @param status details about the remote closure
+   * @param rpcProgress RPC progress when client stream listener is closed
+   * @param trailers trailing metadata
+   */
+  void closed(Status status, RpcProgress rpcProgress, Metadata trailers);
+
+  /**
+   * The progress of the RPC when client stream listener is closed.
+   */
+  enum RpcProgress {
+    /**
+     * The RPC is processed by the server normally.
+     */
+    PROCESSED,
+    /**
+     * The RPC is not processed by the server's application logic.
+     */
+    REFUSED,
+    /**
+     * The RPC is dropped (by load balancer).
+     */
+    DROPPED
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ClientTransport.java b/core/src/main/java/io/grpc/internal/ClientTransport.java
new file mode 100644
index 0000000..cc8471a
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ClientTransport.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.CallOptions;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import java.util.concurrent.Executor;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * The client-side transport typically encapsulating a single connection to a remote
+ * server. However, streams created before the client has discovered any server address may
+ * eventually be issued on different connections.  All methods on the transport and its callbacks
+ * are expected to execute quickly.
+ */
+@ThreadSafe
+public interface ClientTransport extends InternalInstrumented<SocketStats> {
+
+  /**
+   * Creates a new stream for sending messages to a remote end-point.
+   *
+   * <p>This method returns immediately and does not wait for any validation of the request. If
+   * creation fails for any reason, {@link ClientStreamListener#closed} will be called to provide
+   * the error information. Any sent messages for this stream will be buffered until creation has
+   * completed (either successfully or unsuccessfully).
+   *
+   * <p>This method is called under the {@link io.grpc.Context} of the {@link io.grpc.ClientCall}.
+   *
+   * @param method the descriptor of the remote method to be called for this stream.
+   * @param headers to send at the beginning of the call
+   * @param callOptions runtime options of the call
+   * @return the newly created stream.
+   */
+  // TODO(nmittler): Consider also throwing for stopping.
+  ClientStream newStream(MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions);
+
+  /**
+   * Pings a remote endpoint. When an acknowledgement is received, the given callback will be
+   * invoked using the given executor.
+   *
+   * <p>Pings are not necessarily sent to the same endpont, thus a successful ping only means at
+   * least one endpoint responded, but doesn't imply the availability of other endpoints (if there
+   * is any).
+   *
+   * <p>This is an optional method. Transports that do not have any mechanism by which to ping the
+   * remote endpoint may throw {@link UnsupportedOperationException}.
+   */
+  void ping(PingCallback callback, Executor executor);
+
+  /**
+   * A callback that is invoked when the acknowledgement to a {@link #ping} is received. Exactly one
+   * of the two methods should be called per {@link #ping}.
+   */
+  interface PingCallback {
+
+    /**
+     * Invoked when a ping is acknowledged. The given argument is the round-trip time of the ping,
+     * in nanoseconds.
+     *
+     * @param roundTripTimeNanos the round-trip duration between the ping being sent and the
+     *     acknowledgement received
+     */
+    void onSuccess(long roundTripTimeNanos);
+
+    /**
+     * Invoked when a ping fails. The given argument is the cause of the failure.
+     *
+     * @param cause the cause of the ping failure
+     */
+    void onFailure(Throwable cause);
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ClientTransportFactory.java b/core/src/main/java/io/grpc/internal/ClientTransportFactory.java
new file mode 100644
index 0000000..a26d41d
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ClientTransportFactory.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import io.grpc.Attributes;
+import java.io.Closeable;
+import java.net.SocketAddress;
+import java.util.concurrent.ScheduledExecutorService;
+import javax.annotation.Nullable;
+
+/** Pre-configured factory for creating {@link ConnectionClientTransport} instances. */
+public interface ClientTransportFactory extends Closeable {
+  /**
+   * Creates an unstarted transport for exclusive use. Ownership of {@code options} is passed to the
+   * callee; the caller should not reuse or read from the options after this method is called.
+   *
+   * @param serverAddress the address that the transport is connected to
+   * @param options additional configuration
+   */
+  ConnectionClientTransport newClientTransport(
+      SocketAddress serverAddress,
+      ClientTransportOptions options);
+
+  /**
+   * Returns an executor for scheduling provided by the transport. The service should be configured
+   * to allow cancelled scheduled runnables to be GCed.
+   *
+   * <p>The executor should not be used after the factory has been closed. The caller should ensure
+   * any outstanding tasks are cancelled before the factory is closed. However, it is a
+   * <a href="https://github.com/grpc/grpc-java/issues/1981">known issue</a> that ClientCallImpl may
+   * use this executor after close, so implementations should not go out of their way to prevent
+   * usage.
+   */
+  ScheduledExecutorService getScheduledExecutorService();
+
+  /**
+   * Releases any resources.
+   *
+   * <p>After this method has been called, it's no longer valid to call
+   * {@link #newClientTransport}. No guarantees about thread-safety are made.
+   */
+  @Override
+  void close();
+
+  /**
+   * Options passed to {@link #newClientTransport(SocketAddress, ClientTransportOptions)}. Although
+   * it is safe to save this object if received, it is generally expected that the useful fields are
+   * copied and then the options object is discarded. This allows using {@code final} for those
+   * fields as well as avoids retaining unused objects contained in the options.
+   */
+  public static final class ClientTransportOptions {
+    private String authority = "unknown-authority";
+    private Attributes eagAttributes = Attributes.EMPTY;
+    private @Nullable String userAgent;
+    private @Nullable ProxyParameters proxyParameters;
+
+    public String getAuthority() {
+      return authority;
+    }
+
+    /** Sets the non-null authority. */
+    public ClientTransportOptions setAuthority(String authority) {
+      this.authority = Preconditions.checkNotNull(authority, "authority");
+      return this;
+    }
+
+    public Attributes getEagAttributes() {
+      return eagAttributes;
+    }
+
+    /** Sets the non-null EquivalentAddressGroup's attributes. */
+    public ClientTransportOptions setEagAttributes(Attributes eagAttributes) {
+      Preconditions.checkNotNull(eagAttributes, "eagAttributes");
+      this.eagAttributes = eagAttributes;
+      return this;
+    }
+
+    @Nullable
+    public String getUserAgent() {
+      return userAgent;
+    }
+
+    public ClientTransportOptions setUserAgent(@Nullable String userAgent) {
+      this.userAgent = userAgent;
+      return this;
+    }
+
+    @Nullable
+    public ProxyParameters getProxyParameters() {
+      return proxyParameters;
+    }
+
+    public ClientTransportOptions setProxyParameters(@Nullable ProxyParameters proxyParameters) {
+      this.proxyParameters = proxyParameters;
+      return this;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(authority, eagAttributes, userAgent, proxyParameters);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (!(o instanceof ClientTransportOptions)) {
+        return false;
+      }
+      ClientTransportOptions that = (ClientTransportOptions) o;
+      return this.authority.equals(that.authority)
+          && this.eagAttributes.equals(that.eagAttributes)
+          && Objects.equal(this.userAgent, that.userAgent)
+          && Objects.equal(this.proxyParameters, that.proxyParameters);
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/CompositeReadableBuffer.java b/core/src/main/java/io/grpc/internal/CompositeReadableBuffer.java
new file mode 100644
index 0000000..8f5f496
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/CompositeReadableBuffer.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/**
+ * A {@link ReadableBuffer} that is composed of 0 or more {@link ReadableBuffer}s. This provides a
+ * facade that allows multiple buffers to be treated as one.
+ *
+ * <p>When a buffer is added to a composite, its life cycle is controlled by the composite. Once
+ * the composite has read past the end of a given buffer, that buffer is automatically closed and
+ * removed from the composite.
+ */
+public class CompositeReadableBuffer extends AbstractReadableBuffer {
+
+  private int readableBytes;
+  private final Queue<ReadableBuffer> buffers = new ArrayDeque<ReadableBuffer>();
+
+  /**
+   * Adds a new {@link ReadableBuffer} at the end of the buffer list. After a buffer is added, it is
+   * expected that this {@code CompositeBuffer} has complete ownership. Any attempt to modify the
+   * buffer (i.e. modifying the readable bytes) may result in corruption of the internal state of
+   * this {@code CompositeBuffer}.
+   */
+  public void addBuffer(ReadableBuffer buffer) {
+    if (!(buffer instanceof CompositeReadableBuffer)) {
+      buffers.add(buffer);
+      readableBytes += buffer.readableBytes();
+      return;
+    }
+
+    CompositeReadableBuffer compositeBuffer = (CompositeReadableBuffer) buffer;
+    while (!compositeBuffer.buffers.isEmpty()) {
+      ReadableBuffer subBuffer = compositeBuffer.buffers.remove();
+      buffers.add(subBuffer);
+    }
+    readableBytes += compositeBuffer.readableBytes;
+    compositeBuffer.readableBytes = 0;
+    compositeBuffer.close();
+  }
+
+  @Override
+  public int readableBytes() {
+    return readableBytes;
+  }
+
+  @Override
+  public int readUnsignedByte() {
+    ReadOperation op = new ReadOperation() {
+      @Override
+      int readInternal(ReadableBuffer buffer, int length) {
+        return buffer.readUnsignedByte();
+      }
+    };
+    execute(op, 1);
+    return op.value;
+  }
+
+  @Override
+  public void skipBytes(int length) {
+    execute(new ReadOperation() {
+      @Override
+      public int readInternal(ReadableBuffer buffer, int length) {
+        buffer.skipBytes(length);
+        return 0;
+      }
+    }, length);
+  }
+
+  @Override
+  public void readBytes(final byte[] dest, final int destOffset, int length) {
+    execute(new ReadOperation() {
+      int currentOffset = destOffset;
+      @Override
+      public int readInternal(ReadableBuffer buffer, int length) {
+        buffer.readBytes(dest, currentOffset, length);
+        currentOffset += length;
+        return 0;
+      }
+    }, length);
+  }
+
+  @Override
+  public void readBytes(final ByteBuffer dest) {
+    execute(new ReadOperation() {
+      @Override
+      public int readInternal(ReadableBuffer buffer, int length) {
+        // Change the limit so that only lengthToCopy bytes are available.
+        int prevLimit = dest.limit();
+        dest.limit(dest.position() + length);
+
+        // Write the bytes and restore the original limit.
+        buffer.readBytes(dest);
+        dest.limit(prevLimit);
+        return 0;
+      }
+    }, dest.remaining());
+  }
+
+  @Override
+  public void readBytes(final OutputStream dest, int length) throws IOException {
+    ReadOperation op = new ReadOperation() {
+      @Override
+      public int readInternal(ReadableBuffer buffer, int length) throws IOException {
+        buffer.readBytes(dest, length);
+        return 0;
+      }
+    };
+    execute(op, length);
+
+    // If an exception occurred, throw it.
+    if (op.isError()) {
+      throw op.ex;
+    }
+  }
+
+  @Override
+  public CompositeReadableBuffer readBytes(int length) {
+    checkReadable(length);
+    readableBytes -= length;
+
+    CompositeReadableBuffer newBuffer = new CompositeReadableBuffer();
+    while (length > 0) {
+      ReadableBuffer buffer = buffers.peek();
+      if (buffer.readableBytes() > length) {
+        newBuffer.addBuffer(buffer.readBytes(length));
+        length = 0;
+      } else {
+        newBuffer.addBuffer(buffers.poll());
+        length -= buffer.readableBytes();
+      }
+    }
+    return newBuffer;
+  }
+
+  @Override
+  public void close() {
+    while (!buffers.isEmpty()) {
+      buffers.remove().close();
+    }
+  }
+
+  /**
+   * Executes the given {@link ReadOperation} against the {@link ReadableBuffer}s required to
+   * satisfy the requested {@code length}.
+   */
+  private void execute(ReadOperation op, int length) {
+    checkReadable(length);
+
+    if (!buffers.isEmpty()) {
+      advanceBufferIfNecessary();
+    }
+
+    for (; length > 0 && !buffers.isEmpty(); advanceBufferIfNecessary()) {
+      ReadableBuffer buffer = buffers.peek();
+      int lengthToCopy = Math.min(length, buffer.readableBytes());
+
+      // Perform the read operation for this buffer.
+      op.read(buffer, lengthToCopy);
+      if (op.isError()) {
+        return;
+      }
+
+      length -= lengthToCopy;
+      readableBytes -= lengthToCopy;
+    }
+
+    if (length > 0) {
+      // Should never get here.
+      throw new AssertionError("Failed executing read operation");
+    }
+  }
+
+  /**
+   * If the current buffer is exhausted, removes and closes it.
+   */
+  private void advanceBufferIfNecessary() {
+    ReadableBuffer buffer = buffers.peek();
+    if (buffer.readableBytes() == 0) {
+      buffers.remove().close();
+    }
+  }
+
+  /**
+   * A simple read operation to perform on a single {@link ReadableBuffer}. All state management for
+   * the buffers is done by {@link CompositeReadableBuffer#execute(ReadOperation, int)}.
+   */
+  private abstract static class ReadOperation {
+    /**
+     * Only used by {@link CompositeReadableBuffer#readUnsignedByte()}.
+     */
+    int value;
+
+    /**
+     * Only used by {@link CompositeReadableBuffer#readBytes(OutputStream, int)}.
+     */
+    IOException ex;
+
+    final void read(ReadableBuffer buffer, int length) {
+      try {
+        value = readInternal(buffer, length);
+      } catch (IOException e) {
+        ex = e;
+      }
+    }
+
+    final boolean isError() {
+      return ex != null;
+    }
+
+    abstract int readInternal(ReadableBuffer buffer, int length) throws IOException;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ConnectionClientTransport.java b/core/src/main/java/io/grpc/internal/ConnectionClientTransport.java
new file mode 100644
index 0000000..8385316
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ConnectionClientTransport.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Attributes;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * A {@link ManagedClientTransport} that is based on a connection.
+ */
+@ThreadSafe
+public interface ConnectionClientTransport extends ManagedClientTransport {
+  /**
+   * Returns a set of attributes, which may vary depending on the state of the transport. The keys
+   * should define in what states they will be present.
+   */
+  Attributes getAttributes();
+}
diff --git a/core/src/main/java/io/grpc/internal/ConnectivityStateManager.java b/core/src/main/java/io/grpc/internal/ConnectivityStateManager.java
new file mode 100644
index 0000000..c67c5e7
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ConnectivityStateManager.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.grpc.ConnectivityState;
+import io.grpc.ManagedChannel;
+import java.util.ArrayList;
+import java.util.concurrent.Executor;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * Manages connectivity states of the channel. Used for {@link ManagedChannel#getState} to read the
+ * current state of the channel, for {@link ManagedChannel#notifyWhenStateChanged} to add
+ * listeners to state change events, and for {@link io.grpc.LoadBalancer.Helper#updateBalancingState
+ * LoadBalancer.Helper#updateBalancingState} to update the state and run the {@link #gotoState}s.
+ */
+@NotThreadSafe
+final class ConnectivityStateManager {
+  private ArrayList<Listener> listeners = new ArrayList<>();
+
+  private volatile ConnectivityState state = ConnectivityState.IDLE;
+
+  /**
+   * Adds a listener for state change event.
+   *
+   * <p>The {@code executor} must be one that can run RPC call listeners.
+   */
+  void notifyWhenStateChanged(Runnable callback, Executor executor, ConnectivityState source) {
+    checkNotNull(callback, "callback");
+    checkNotNull(executor, "executor");
+    checkNotNull(source, "source");
+
+    Listener stateChangeListener = new Listener(callback, executor);
+    if (state != source) {
+      stateChangeListener.runInExecutor();
+    } else {
+      listeners.add(stateChangeListener);
+    }
+  }
+
+  /**
+   * Connectivity state is changed to the specified value. Will trigger some notifications that have
+   * been registered earlier by {@link ManagedChannel#notifyWhenStateChanged}.
+   */
+  void gotoState(@Nonnull ConnectivityState newState) {
+    checkNotNull(newState, "newState");
+    if (state != newState && state != ConnectivityState.SHUTDOWN) {
+      state = newState;
+      if (listeners.isEmpty()) {
+        return;
+      }
+      // Swap out callback list before calling them, because a callback may register new callbacks,
+      // if run in direct executor, can cause ConcurrentModificationException.
+      ArrayList<Listener> savedListeners = listeners;
+      listeners = new ArrayList<>();
+      for (Listener listener : savedListeners) {
+        listener.runInExecutor();
+      }
+    }
+  }
+
+  /**
+   * Gets the current connectivity state of the channel. This method is threadsafe.
+   */
+  ConnectivityState getState() {
+    ConnectivityState stateCopy = state;
+    if (stateCopy == null) {
+      throw new UnsupportedOperationException("Channel state API is not implemented");
+    }
+    return stateCopy;
+  }
+
+  private static final class Listener {
+    final Runnable callback;
+    final Executor executor;
+
+    Listener(Runnable callback, Executor executor) {
+      this.callback = callback;
+      this.executor = executor;
+    }
+
+    void runInExecutor() {
+      executor.execute(callback);
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ContextRunnable.java b/core/src/main/java/io/grpc/internal/ContextRunnable.java
new file mode 100644
index 0000000..9d82b39
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ContextRunnable.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Context;
+
+/**
+ * Utility base implementation of {@link Runnable} that performs the same function as
+ * {@link Context#wrap(Runnable)} without requiring the construction of an additional object.
+ */
+abstract class ContextRunnable implements Runnable {
+
+  private final Context context;
+
+  public ContextRunnable(Context context) {
+    this.context = context;
+  }
+
+  @Override
+  public final void run() {
+    Context previous = context.attach();
+    try {
+      runInContext();
+    } finally {
+      context.detach(previous);
+    }
+  }
+
+  public abstract void runInContext();
+}
diff --git a/core/src/main/java/io/grpc/internal/Deframer.java b/core/src/main/java/io/grpc/internal/Deframer.java
new file mode 100644
index 0000000..c85537f
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/Deframer.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Decompressor;
+
+/** Interface for deframing gRPC messages. */
+public interface Deframer {
+
+  void setMaxInboundMessageSize(int messageSize);
+
+  /**
+   * Sets the decompressor available to use. The message encoding for the stream comes later in
+   * time, and thus will not be available at the time of construction. This should only be set once,
+   * since the compression codec cannot change after the headers have been sent.
+   *
+   * @param decompressor the decompressing wrapper.
+   */
+  void setDecompressor(Decompressor decompressor);
+
+  /**
+   * Sets the decompressor used for full-stream decompression. Full-stream decompression disables
+   * any per-message decompressor set by {@link #setDecompressor}.
+   *
+   * @param fullStreamDecompressor the decompressing wrapper
+   */
+  void setFullStreamDecompressor(GzipInflatingBuffer fullStreamDecompressor);
+
+  /**
+   * Requests up to the given number of messages from the call. No additional messages will be
+   * delivered.
+   *
+   * <p>If {@link #close()} has been called, this method will have no effect.
+   *
+   * @param numMessages the requested number of messages to be delivered to the listener.
+   */
+  void request(int numMessages);
+
+  /**
+   * Adds the given data to this deframer and attempts delivery to the listener.
+   *
+   * @param data the raw data read from the remote endpoint. Must be non-null.
+   */
+  void deframe(ReadableBuffer data);
+
+  /** Close when any messages currently queued have been requested and delivered. */
+  void closeWhenComplete();
+
+  /**
+   * Closes this deframer and frees any resources. After this method is called, additional calls
+   * will have no effect.
+   */
+  void close();
+}
diff --git a/core/src/main/java/io/grpc/internal/DelayedClientTransport.java b/core/src/main/java/io/grpc/internal/DelayedClientTransport.java
new file mode 100644
index 0000000..dbe195f
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/DelayedClientTransport.java
@@ -0,0 +1,384 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.CallOptions;
+import io.grpc.Context;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalLogId;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.concurrent.Executor;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * A client transport that queues requests before a real transport is available. When {@link
+ * #reprocess} is called, this class applies the provided {@link SubchannelPicker} to pick a
+ * transport for each pending stream.
+ *
+ * <p>This transport owns every stream that it has created until a real transport has been picked
+ * for that stream, at which point the ownership of the stream is transferred to the real transport,
+ * thus the delayed transport stops owning the stream.
+ */
+final class DelayedClientTransport implements ManagedClientTransport {
+  private final InternalLogId lodId = InternalLogId.allocate(getClass().getName());
+
+  private final Object lock = new Object();
+
+  private final Executor defaultAppExecutor;
+  private final ChannelExecutor channelExecutor;
+
+  private Runnable reportTransportInUse;
+  private Runnable reportTransportNotInUse;
+  private Runnable reportTransportTerminated;
+  private Listener listener;
+
+  @Nonnull
+  @GuardedBy("lock")
+  private Collection<PendingStream> pendingStreams = new LinkedHashSet<PendingStream>();
+
+  /**
+   * When {@code shutdownStatus != null && !hasPendingStreams()}, then the transport is considered
+   * terminated.
+   */
+  @GuardedBy("lock")
+  private Status shutdownStatus;
+
+  /**
+   * The last picker that {@link #reprocess} has used. May be set to null when the channel has moved
+   * to idle.
+   */
+  @GuardedBy("lock")
+  @Nullable
+  private SubchannelPicker lastPicker;
+
+  @GuardedBy("lock")
+  private long lastPickerVersion;
+
+  /**
+   * Creates a new delayed transport.
+   *
+   * @param defaultAppExecutor pending streams will create real streams and run bufferred operations
+   *        in an application executor, which will be this executor, unless there is on provided in
+   *        {@link CallOptions}.
+   * @param channelExecutor all listener callbacks of the delayed transport will be run from this
+   *        ChannelExecutor.
+   */
+  DelayedClientTransport(Executor defaultAppExecutor, ChannelExecutor channelExecutor) {
+    this.defaultAppExecutor = defaultAppExecutor;
+    this.channelExecutor = channelExecutor;
+  }
+
+  @Override
+  public final Runnable start(final Listener listener) {
+    this.listener = listener;
+    reportTransportInUse = new Runnable() {
+        @Override
+        public void run() {
+          listener.transportInUse(true);
+        }
+      };
+    reportTransportNotInUse = new Runnable() {
+        @Override
+        public void run() {
+          listener.transportInUse(false);
+        }
+      };
+    reportTransportTerminated = new Runnable() {
+        @Override
+        public void run() {
+          listener.transportTerminated();
+        }
+      };
+    return null;
+  }
+
+  /**
+   * If a {@link SubchannelPicker} is being, or has been provided via {@link #reprocess}, the last
+   * picker will be consulted.
+   *
+   * <p>Otherwise, if the delayed transport is not shutdown, then a {@link PendingStream} is
+   * returned; if the transport is shutdown, then a {@link FailingClientStream} is returned.
+   */
+  @Override
+  public final ClientStream newStream(
+      MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions) {
+    try {
+      SubchannelPicker picker;
+      PickSubchannelArgs args = new PickSubchannelArgsImpl(method, headers, callOptions);
+      long pickerVersion = -1;
+      synchronized (lock) {
+        if (shutdownStatus == null) {
+          if (lastPicker == null) {
+            return createPendingStream(args);
+          }
+          picker = lastPicker;
+          pickerVersion = lastPickerVersion;
+        } else {
+          return new FailingClientStream(shutdownStatus);
+        }
+      }
+      while (true) {
+        PickResult pickResult = picker.pickSubchannel(args);
+        ClientTransport transport = GrpcUtil.getTransportFromPickResult(pickResult,
+            callOptions.isWaitForReady());
+        if (transport != null) {
+          return transport.newStream(
+              args.getMethodDescriptor(), args.getHeaders(), args.getCallOptions());
+        }
+        // This picker's conclusion is "buffer".  If there hasn't been a newer picker set (possible
+        // race with reprocess()), we will buffer it.  Otherwise, will try with the new picker.
+        synchronized (lock) {
+          if (shutdownStatus != null) {
+            return new FailingClientStream(shutdownStatus);
+          }
+          if (pickerVersion == lastPickerVersion) {
+            return createPendingStream(args);
+          }
+          picker = lastPicker;
+          pickerVersion = lastPickerVersion;
+        }
+      }
+    } finally {
+      channelExecutor.drain();
+    }
+  }
+
+  /**
+   * Caller must call {@code channelExecutor.drain()} outside of lock because this method may
+   * schedule tasks on channelExecutor.
+   */
+  @GuardedBy("lock")
+  private PendingStream createPendingStream(PickSubchannelArgs args) {
+    PendingStream pendingStream = new PendingStream(args);
+    pendingStreams.add(pendingStream);
+    if (getPendingStreamsCount() == 1) {
+      channelExecutor.executeLater(reportTransportInUse);
+    }
+    return pendingStream;
+  }
+
+  @Override
+  public final void ping(final PingCallback callback, Executor executor) {
+    throw new UnsupportedOperationException("This method is not expected to be called");
+  }
+
+  @Override
+  public ListenableFuture<SocketStats> getStats() {
+    SettableFuture<SocketStats> ret = SettableFuture.create();
+    ret.set(null);
+    return ret;
+  }
+
+  /**
+   * Prevents creating any new streams.  Buffered streams are not failed and may still proceed
+   * when {@link #reprocess} is called.  The delayed transport will be terminated when there is no
+   * more buffered streams.
+   */
+  @Override
+  public final void shutdown(final Status status) {
+    synchronized (lock) {
+      if (shutdownStatus != null) {
+        return;
+      }
+      shutdownStatus = status;
+      channelExecutor.executeLater(new Runnable() {
+          @Override
+          public void run() {
+            listener.transportShutdown(status);
+          }
+        });
+      if (!hasPendingStreams() && reportTransportTerminated != null) {
+        channelExecutor.executeLater(reportTransportTerminated);
+        reportTransportTerminated = null;
+      }
+    }
+    channelExecutor.drain();
+  }
+
+  /**
+   * Shuts down this transport and cancels all streams that it owns, hence immediately terminates
+   * this transport.
+   */
+  @Override
+  public final void shutdownNow(Status status) {
+    shutdown(status);
+    Collection<PendingStream> savedPendingStreams;
+    Runnable savedReportTransportTerminated;
+    synchronized (lock) {
+      savedPendingStreams = pendingStreams;
+      savedReportTransportTerminated = reportTransportTerminated;
+      reportTransportTerminated = null;
+      if (!pendingStreams.isEmpty()) {
+        pendingStreams = Collections.<PendingStream>emptyList();
+      }
+    }
+    if (savedReportTransportTerminated != null) {
+      for (PendingStream stream : savedPendingStreams) {
+        stream.cancel(status);
+      }
+      channelExecutor.executeLater(savedReportTransportTerminated).drain();
+    }
+    // If savedReportTransportTerminated == null, transportTerminated() has already been called in
+    // shutdown().
+  }
+
+  public final boolean hasPendingStreams() {
+    synchronized (lock) {
+      return !pendingStreams.isEmpty();
+    }
+  }
+
+  @VisibleForTesting
+  final int getPendingStreamsCount() {
+    synchronized (lock) {
+      return pendingStreams.size();
+    }
+  }
+
+  /**
+   * Use the picker to try picking a transport for every pending stream, proceed the stream if the
+   * pick is successful, otherwise keep it pending.
+   *
+   * <p>This method may be called concurrently with {@code newStream()}, and it's safe.  All pending
+   * streams will be served by the latest picker (if a same picker is given more than once, they are
+   * considered different pickers) as soon as possible.
+   *
+   * <p>This method <strong>must not</strong> be called concurrently with itself.
+   */
+  final void reprocess(@Nullable SubchannelPicker picker) {
+    ArrayList<PendingStream> toProcess;
+    synchronized (lock) {
+      lastPicker = picker;
+      lastPickerVersion++;
+      if (picker == null || !hasPendingStreams()) {
+        return;
+      }
+      toProcess = new ArrayList<>(pendingStreams);
+    }
+    ArrayList<PendingStream> toRemove = new ArrayList<>();
+
+    for (final PendingStream stream : toProcess) {
+      PickResult pickResult = picker.pickSubchannel(stream.args);
+      CallOptions callOptions = stream.args.getCallOptions();
+      final ClientTransport transport = GrpcUtil.getTransportFromPickResult(pickResult,
+          callOptions.isWaitForReady());
+      if (transport != null) {
+        Executor executor = defaultAppExecutor;
+        // createRealStream may be expensive. It will start real streams on the transport. If
+        // there are pending requests, they will be serialized too, which may be expensive. Since
+        // we are now on transport thread, we need to offload the work to an executor.
+        if (callOptions.getExecutor() != null) {
+          executor = callOptions.getExecutor();
+        }
+        executor.execute(new Runnable() {
+            @Override
+            public void run() {
+              stream.createRealStream(transport);
+            }
+          });
+        toRemove.add(stream);
+      }  // else: stay pending
+    }
+
+    synchronized (lock) {
+      // Between this synchronized and the previous one:
+      //   - Streams may have been cancelled, which may turn pendingStreams into emptiness.
+      //   - shutdown() may be called, which may turn pendingStreams into null.
+      if (!hasPendingStreams()) {
+        return;
+      }
+      pendingStreams.removeAll(toRemove);
+      // Because delayed transport is long-lived, we take this opportunity to down-size the
+      // hashmap.
+      if (pendingStreams.isEmpty()) {
+        pendingStreams = new LinkedHashSet<PendingStream>();
+      }
+      if (!hasPendingStreams()) {
+        // There may be a brief gap between delayed transport clearing in-use state, and first real
+        // transport starting streams and setting in-use state.  During the gap the whole channel's
+        // in-use state may be false. However, it shouldn't cause spurious switching to idleness
+        // (which would shutdown the transports and LoadBalancer) because the gap should be shorter
+        // than IDLE_MODE_DEFAULT_TIMEOUT_MILLIS (1 second).
+        channelExecutor.executeLater(reportTransportNotInUse);
+        if (shutdownStatus != null && reportTransportTerminated != null) {
+          channelExecutor.executeLater(reportTransportTerminated);
+          reportTransportTerminated = null;
+        }
+      }
+    }
+    channelExecutor.drain();
+  }
+
+  // TODO(carl-mastrangelo): remove this once the Subchannel change is in.
+  @Override
+  public InternalLogId getLogId() {
+    return lodId;
+  }
+
+  private class PendingStream extends DelayedStream {
+    private final PickSubchannelArgs args;
+    private final Context context = Context.current();
+
+    private PendingStream(PickSubchannelArgs args) {
+      this.args = args;
+    }
+
+    private void createRealStream(ClientTransport transport) {
+      ClientStream realStream;
+      Context origContext = context.attach();
+      try {
+        realStream = transport.newStream(
+            args.getMethodDescriptor(), args.getHeaders(), args.getCallOptions());
+      } finally {
+        context.detach(origContext);
+      }
+      setStream(realStream);
+    }
+
+    @Override
+    public void cancel(Status reason) {
+      super.cancel(reason);
+      synchronized (lock) {
+        if (reportTransportTerminated != null) {
+          boolean justRemovedAnElement = pendingStreams.remove(this);
+          if (!hasPendingStreams() && justRemovedAnElement) {
+            channelExecutor.executeLater(reportTransportNotInUse);
+            if (shutdownStatus != null) {
+              channelExecutor.executeLater(reportTransportTerminated);
+              reportTransportTerminated = null;
+            }
+          }
+        }
+      }
+      channelExecutor.drain();
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/DelayedStream.java b/core/src/main/java/io/grpc/internal/DelayedStream.java
new file mode 100644
index 0000000..549e47d
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/DelayedStream.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.Attributes;
+import io.grpc.Compressor;
+import io.grpc.Deadline;
+import io.grpc.DecompressorRegistry;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * A stream that queues requests before the transport is available, and delegates to a real stream
+ * implementation when the transport is available.
+ *
+ * <p>{@code ClientStream} itself doesn't require thread-safety. However, the state of {@code
+ * DelayedStream} may be internally altered by different threads, thus internal synchronization is
+ * necessary.
+ */
+class DelayedStream implements ClientStream {
+  /** {@code true} once realStream is valid and all pending calls have been drained. */
+  private volatile boolean passThrough;
+  /**
+   * Non-{@code null} iff start has been called. Used to assert methods are called in appropriate
+   * order, but also used if an error occurrs before {@code realStream} is set.
+   */
+  private ClientStreamListener listener;
+  /** Must hold {@code this} lock when setting. */
+  private ClientStream realStream;
+  @GuardedBy("this")
+  private Status error;
+  @GuardedBy("this")
+  private List<Runnable> pendingCalls = new ArrayList<>();
+  @GuardedBy("this")
+  private DelayedStreamListener delayedListener;
+
+  @Override
+  public void setMaxInboundMessageSize(final int maxSize) {
+    if (passThrough) {
+      realStream.setMaxInboundMessageSize(maxSize);
+    } else {
+      delayOrExecute(new Runnable() {
+        @Override
+        public void run() {
+          realStream.setMaxInboundMessageSize(maxSize);
+        }
+      });
+    }
+  }
+
+  @Override
+  public void setMaxOutboundMessageSize(final int maxSize) {
+    if (passThrough) {
+      realStream.setMaxOutboundMessageSize(maxSize);
+    } else {
+      delayOrExecute(new Runnable() {
+        @Override
+        public void run() {
+          realStream.setMaxOutboundMessageSize(maxSize);
+        }
+      });
+    }
+  }
+
+  @Override
+  public void setDeadline(final Deadline deadline) {
+    delayOrExecute(new Runnable() {
+      @Override
+      public void run() {
+        realStream.setDeadline(deadline);
+      }
+    });
+  }
+
+  /**
+   * Transfers all pending and future requests and mutations to the given stream.
+   *
+   * <p>No-op if either this method or {@link #cancel} have already been called.
+   */
+  // When this method returns, passThrough is guaranteed to be true
+  final void setStream(ClientStream stream) {
+    synchronized (this) {
+      // If realStream != null, then either setStream() or cancel() has been called.
+      if (realStream != null) {
+        return;
+      }
+      realStream = checkNotNull(stream, "stream");
+    }
+
+    drainPendingCalls();
+  }
+
+  /**
+   * Called to transition {@code passThrough} to {@code true}. This method is not safe to be called
+   * multiple times; the caller must ensure it will only be called once, ever. {@code this} lock
+   * should not be held when calling this method.
+   */
+  private void drainPendingCalls() {
+    assert realStream != null;
+    assert !passThrough;
+    List<Runnable> toRun = new ArrayList<>();
+    DelayedStreamListener delayedListener = null;
+    while (true) {
+      synchronized (this) {
+        if (pendingCalls.isEmpty()) {
+          pendingCalls = null;
+          passThrough = true;
+          delayedListener = this.delayedListener;
+          break;
+        }
+        // Since there were pendingCalls, we need to process them. To maintain ordering we can't set
+        // passThrough=true until we run all pendingCalls, but new Runnables may be added after we
+        // drop the lock. So we will have to re-check pendingCalls.
+        List<Runnable> tmp = toRun;
+        toRun = pendingCalls;
+        pendingCalls = tmp;
+      }
+      for (Runnable runnable : toRun) {
+        // Must not call transport while lock is held to prevent deadlocks.
+        // TODO(ejona): exception handling
+        runnable.run();
+      }
+      toRun.clear();
+    }
+    if (delayedListener != null) {
+      delayedListener.drainPendingCallbacks();
+    }
+  }
+
+  /**
+   * Enqueue the runnable or execute it now. Call sites that may be called many times may want avoid
+   * this method if {@code passThrough == true}.
+   *
+   * <p>Note that this method is no more thread-safe than {@code runnable}. It is thread-safe if and
+   * only if {@code runnable} is thread-safe.
+   */
+  private void delayOrExecute(Runnable runnable) {
+    synchronized (this) {
+      if (!passThrough) {
+        pendingCalls.add(runnable);
+        return;
+      }
+    }
+    runnable.run();
+  }
+
+  @Override
+  public void setAuthority(final String authority) {
+    checkState(listener == null, "May only be called before start");
+    checkNotNull(authority, "authority");
+    delayOrExecute(new Runnable() {
+      @Override
+      public void run() {
+        realStream.setAuthority(authority);
+      }
+    });
+  }
+
+  @Override
+  public void start(ClientStreamListener listener) {
+    checkState(this.listener == null, "already started");
+
+    Status savedError;
+    boolean savedPassThrough;
+    synchronized (this) {
+      this.listener = checkNotNull(listener, "listener");
+      // If error != null, then cancel() has been called and was unable to close the listener
+      savedError = error;
+      savedPassThrough = passThrough;
+      if (!savedPassThrough) {
+        listener = delayedListener = new DelayedStreamListener(listener);
+      }
+    }
+    if (savedError != null) {
+      listener.closed(savedError, new Metadata());
+      return;
+    }
+
+    if (savedPassThrough) {
+      realStream.start(listener);
+    } else {
+      final ClientStreamListener finalListener = listener;
+      delayOrExecute(new Runnable() {
+        @Override
+        public void run() {
+          realStream.start(finalListener);
+        }
+      });
+    }
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    checkState(passThrough, "Called getAttributes before attributes are ready");
+    return realStream.getAttributes();
+  }
+
+  @Override
+  public void writeMessage(final InputStream message) {
+    checkNotNull(message, "message");
+    if (passThrough) {
+      realStream.writeMessage(message);
+    } else {
+      delayOrExecute(new Runnable() {
+        @Override
+        public void run() {
+          realStream.writeMessage(message);
+        }
+      });
+    }
+  }
+
+  @Override
+  public void flush() {
+    if (passThrough) {
+      realStream.flush();
+    } else {
+      delayOrExecute(new Runnable() {
+        @Override
+        public void run() {
+          realStream.flush();
+        }
+      });
+    }
+  }
+
+  // When this method returns, passThrough is guaranteed to be true
+  @Override
+  public void cancel(final Status reason) {
+    checkNotNull(reason, "reason");
+    boolean delegateToRealStream = true;
+    ClientStreamListener listenerToClose = null;
+    synchronized (this) {
+      // If realStream != null, then either setStream() or cancel() has been called
+      if (realStream == null) {
+        realStream = NoopClientStream.INSTANCE;
+        delegateToRealStream = false;
+
+        // If listener == null, then start() will later call listener with 'error'
+        listenerToClose = listener;
+        error = reason;
+      }
+    }
+    if (delegateToRealStream) {
+      delayOrExecute(new Runnable() {
+        @Override
+        public void run() {
+          realStream.cancel(reason);
+        }
+      });
+    } else {
+      if (listenerToClose != null) {
+        listenerToClose.closed(reason, new Metadata());
+      }
+      drainPendingCalls();
+    }
+  }
+
+  @Override
+  public void halfClose() {
+    delayOrExecute(new Runnable() {
+      @Override
+      public void run() {
+        realStream.halfClose();
+      }
+    });
+  }
+
+  @Override
+  public void request(final int numMessages) {
+    if (passThrough) {
+      realStream.request(numMessages);
+    } else {
+      delayOrExecute(new Runnable() {
+        @Override
+        public void run() {
+          realStream.request(numMessages);
+        }
+      });
+    }
+  }
+
+  @Override
+  public void setCompressor(final Compressor compressor) {
+    checkNotNull(compressor, "compressor");
+    delayOrExecute(new Runnable() {
+      @Override
+      public void run() {
+        realStream.setCompressor(compressor);
+      }
+    });
+  }
+
+  @Override
+  public void setFullStreamDecompression(final boolean fullStreamDecompression) {
+    delayOrExecute(
+        new Runnable() {
+          @Override
+          public void run() {
+            realStream.setFullStreamDecompression(fullStreamDecompression);
+          }
+        });
+  }
+
+  @Override
+  public void setDecompressorRegistry(final DecompressorRegistry decompressorRegistry) {
+    checkNotNull(decompressorRegistry, "decompressorRegistry");
+    delayOrExecute(new Runnable() {
+      @Override
+      public void run() {
+        realStream.setDecompressorRegistry(decompressorRegistry);
+      }
+    });
+  }
+
+  @Override
+  public boolean isReady() {
+    if (passThrough) {
+      return realStream.isReady();
+    } else {
+      return false;
+    }
+  }
+
+  @Override
+  public void setMessageCompression(final boolean enable) {
+    if (passThrough) {
+      realStream.setMessageCompression(enable);
+    } else {
+      delayOrExecute(new Runnable() {
+        @Override
+        public void run() {
+          realStream.setMessageCompression(enable);
+        }
+      });
+    }
+  }
+
+  @VisibleForTesting
+  ClientStream getRealStream() {
+    return realStream;
+  }
+
+  private static class DelayedStreamListener implements ClientStreamListener {
+    private final ClientStreamListener realListener;
+    private volatile boolean passThrough;
+    @GuardedBy("this")
+    private List<Runnable> pendingCallbacks = new ArrayList<>();
+
+    public DelayedStreamListener(ClientStreamListener listener) {
+      this.realListener = listener;
+    }
+
+    private void delayOrExecute(Runnable runnable) {
+      synchronized (this) {
+        if (!passThrough) {
+          pendingCallbacks.add(runnable);
+          return;
+        }
+      }
+      runnable.run();
+    }
+
+    @Override
+    public void messagesAvailable(final MessageProducer producer) {
+      if (passThrough) {
+        realListener.messagesAvailable(producer);
+      } else {
+        delayOrExecute(new Runnable() {
+          @Override
+          public void run() {
+            realListener.messagesAvailable(producer);
+          }
+        });
+      }
+    }
+
+    @Override
+    public void onReady() {
+      if (passThrough) {
+        realListener.onReady();
+      } else {
+        delayOrExecute(new Runnable() {
+          @Override
+          public void run() {
+            realListener.onReady();
+          }
+        });
+      }
+    }
+
+    @Override
+    public void headersRead(final Metadata headers) {
+      delayOrExecute(new Runnable() {
+        @Override
+        public void run() {
+          realListener.headersRead(headers);
+        }
+      });
+    }
+
+    @Override
+    public void closed(final Status status, final Metadata trailers) {
+      delayOrExecute(new Runnable() {
+        @Override
+        public void run() {
+          realListener.closed(status, trailers);
+        }
+      });
+    }
+
+    @Override
+    public void closed(
+        final Status status, final RpcProgress rpcProgress,
+        final Metadata trailers) {
+      delayOrExecute(new Runnable() {
+        @Override
+        public void run() {
+          realListener.closed(status, rpcProgress, trailers);
+        }
+      });
+    }
+
+    public void drainPendingCallbacks() {
+      assert !passThrough;
+      List<Runnable> toRun = new ArrayList<>();
+      while (true) {
+        synchronized (this) {
+          if (pendingCallbacks.isEmpty()) {
+            pendingCallbacks = null;
+            passThrough = true;
+            break;
+          }
+          // Since there were pendingCallbacks, we need to process them. To maintain ordering we
+          // can't set passThrough=true until we run all pendingCallbacks, but new Runnables may be
+          // added after we drop the lock. So we will have to re-check pendingCallbacks.
+          List<Runnable> tmp = toRun;
+          toRun = pendingCallbacks;
+          pendingCallbacks = tmp;
+        }
+        for (Runnable runnable : toRun) {
+          // Avoid calling listener while lock is held to prevent deadlocks.
+          // TODO(ejona): exception handling
+          runnable.run();
+        }
+        toRun.clear();
+      }
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/DnsNameResolver.java b/core/src/main/java/io/grpc/internal/DnsNameResolver.java
new file mode 100644
index 0000000..490bd82
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/DnsNameResolver.java
@@ -0,0 +1,629 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Verify;
+import io.grpc.Attributes;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.NameResolver;
+import io.grpc.Status;
+import io.grpc.internal.SharedResourceHolder.Resource;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * A DNS-based {@link NameResolver}.
+ *
+ * <p>Each {@code A} or {@code AAAA} record emits an {@link EquivalentAddressGroup} in the list
+ * passed to {@link NameResolver.Listener#onAddresses(List, Attributes)}
+ *
+ * @see DnsNameResolverProvider
+ */
+final class DnsNameResolver extends NameResolver {
+
+  private static final Logger logger = Logger.getLogger(DnsNameResolver.class.getName());
+
+  private static final String SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY = "clientLanguage";
+  private static final String SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY = "percentage";
+  private static final String SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY = "clientHostname";
+  private static final String SERVICE_CONFIG_CHOICE_SERVICE_CONFIG_KEY = "serviceConfig";
+
+  // From https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md
+  static final String SERVICE_CONFIG_PREFIX = "_grpc_config=";
+  private static final Set<String> SERVICE_CONFIG_CHOICE_KEYS =
+      Collections.unmodifiableSet(
+          new HashSet<String>(
+              Arrays.asList(
+                  SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY,
+                  SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY,
+                  SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY,
+                  SERVICE_CONFIG_CHOICE_SERVICE_CONFIG_KEY)));
+
+  // From https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md
+  private static final String SERVICE_CONFIG_NAME_PREFIX = "_grpc_config.";
+  // From https://github.com/grpc/proposal/blob/master/A5-grpclb-in-dns.md
+  private static final String GRPCLB_NAME_PREFIX = "_grpclb._tcp.";
+
+  private static final String JNDI_PROPERTY =
+      System.getProperty("io.grpc.internal.DnsNameResolverProvider.enable_jndi", "true");
+  private static final String JNDI_SRV_PROPERTY =
+      System.getProperty("io.grpc.internal.DnsNameResolverProvider.enable_grpclb", "false");
+  private static final String JNDI_TXT_PROPERTY =
+      System.getProperty("io.grpc.internal.DnsNameResolverProvider.enable_service_config", "false");
+
+  /**
+   * Java networking system properties name for caching DNS result.
+   *
+   * <p>Default value is -1 (cache forever) if security manager is installed. If security manager is
+   * not installed, the ttl value is {@code null} which falls back to {@link
+   * #DEFAULT_NETWORK_CACHE_TTL_SECONDS gRPC default value}.
+   */
+  @VisibleForTesting
+  static final String NETWORKADDRESS_CACHE_TTL_PROPERTY = "networkaddress.cache.ttl";
+  /** Default DNS cache duration if network cache ttl value is not specified ({@code null}). */
+  @VisibleForTesting
+  static final long DEFAULT_NETWORK_CACHE_TTL_SECONDS = 30;
+
+  @VisibleForTesting
+  static boolean enableJndi = Boolean.parseBoolean(JNDI_PROPERTY);
+  @VisibleForTesting
+  static boolean enableSrv = Boolean.parseBoolean(JNDI_SRV_PROPERTY);
+  @VisibleForTesting
+  static boolean enableTxt = Boolean.parseBoolean(JNDI_TXT_PROPERTY);
+
+
+  private static final ResourceResolverFactory resourceResolverFactory =
+      getResourceResolverFactory(DnsNameResolver.class.getClassLoader());
+
+  @VisibleForTesting
+  final ProxyDetector proxyDetector;
+
+  /** Access through {@link #getLocalHostname}. */
+  private static String localHostname;
+
+  private final Random random = new Random();
+
+  private volatile AddressResolver addressResolver = JdkAddressResolver.INSTANCE;
+  private final AtomicReference<ResourceResolver> resourceResolver =
+      new AtomicReference<ResourceResolver>();
+
+  private final String authority;
+  private final String host;
+  private final int port;
+  private final Resource<ExecutorService> executorResource;
+  private final long networkAddressCacheTtlNanos;
+  private final Stopwatch stopwatch;
+  @GuardedBy("this")
+  private boolean shutdown;
+  @GuardedBy("this")
+  private ExecutorService executor;
+  @GuardedBy("this")
+  private boolean resolving;
+  @GuardedBy("this")
+  private Listener listener;
+  private ResolutionResults cachedResolutionResults;
+
+  DnsNameResolver(@Nullable String nsAuthority, String name, Attributes params,
+      Resource<ExecutorService> executorResource, ProxyDetector proxyDetector,
+      Stopwatch stopwatch) {
+    // TODO: if a DNS server is provided as nsAuthority, use it.
+    // https://www.captechconsulting.com/blogs/accessing-the-dusty-corners-of-dns-with-java
+    this.executorResource = executorResource;
+    // Must prepend a "//" to the name when constructing a URI, otherwise it will be treated as an
+    // opaque URI, thus the authority and host of the resulted URI would be null.
+    URI nameUri = URI.create("//" + checkNotNull(name, "name"));
+    Preconditions.checkArgument(nameUri.getHost() != null, "Invalid DNS name: %s", name);
+    authority = Preconditions.checkNotNull(nameUri.getAuthority(),
+        "nameUri (%s) doesn't have an authority", nameUri);
+    host = nameUri.getHost();
+    if (nameUri.getPort() == -1) {
+      Integer defaultPort = params.get(NameResolver.Factory.PARAMS_DEFAULT_PORT);
+      if (defaultPort != null) {
+        port = defaultPort;
+      } else {
+        throw new IllegalArgumentException(
+            "name '" + name + "' doesn't contain a port, and default port is not set in params");
+      }
+    } else {
+      port = nameUri.getPort();
+    }
+    this.proxyDetector = proxyDetector;
+    this.stopwatch = Preconditions.checkNotNull(stopwatch, "stopwatch");
+    this.networkAddressCacheTtlNanos = getNetworkAddressCacheTtlNanos();
+  }
+
+  @Override
+  public final String getServiceAuthority() {
+    return authority;
+  }
+
+  @Override
+  public final synchronized void start(Listener listener) {
+    Preconditions.checkState(this.listener == null, "already started");
+    executor = SharedResourceHolder.get(executorResource);
+    this.listener = Preconditions.checkNotNull(listener, "listener");
+    resolve();
+  }
+
+  @Override
+  public final synchronized void refresh() {
+    Preconditions.checkState(listener != null, "not started");
+    resolve();
+  }
+
+  private final Runnable resolutionRunnable = new Runnable() {
+      @Override
+      public void run() {
+        Listener savedListener;
+        synchronized (DnsNameResolver.this) {
+          if (shutdown) {
+            return;
+          }
+          boolean resourceRefreshRequired = cachedResolutionResults == null
+              || networkAddressCacheTtlNanos == 0
+              || (networkAddressCacheTtlNanos > 0
+                  && stopwatch.elapsed(TimeUnit.NANOSECONDS) > networkAddressCacheTtlNanos);
+          if (!resourceRefreshRequired) {
+            return;
+          }
+          savedListener = listener;
+          resolving = true;
+        }
+        try {
+          InetSocketAddress destination = InetSocketAddress.createUnresolved(host, port);
+          ProxyParameters proxy;
+          try {
+            proxy = proxyDetector.proxyFor(destination);
+          } catch (IOException e) {
+            savedListener.onError(
+                Status.UNAVAILABLE.withDescription("Unable to resolve host " + host).withCause(e));
+            return;
+          }
+          if (proxy != null) {
+            EquivalentAddressGroup server =
+                new EquivalentAddressGroup(
+                    new ProxySocketAddress(destination, proxy));
+            savedListener.onAddresses(Collections.singletonList(server), Attributes.EMPTY);
+            return;
+          }
+
+          ResolutionResults resolutionResults;
+          try {
+            ResourceResolver resourceResolver = null;
+            if (enableJndi) {
+              resourceResolver = getResourceResolver();
+            }
+            resolutionResults =
+                resolveAll(addressResolver, resourceResolver, enableSrv, enableTxt, host);
+            cachedResolutionResults = resolutionResults;
+            if (networkAddressCacheTtlNanos > 0) {
+              stopwatch.reset().start();
+            }
+          } catch (Exception e) {
+            savedListener.onError(
+                Status.UNAVAILABLE.withDescription("Unable to resolve host " + host).withCause(e));
+            return;
+          }
+          // Each address forms an EAG
+          List<EquivalentAddressGroup> servers = new ArrayList<>();
+          for (InetAddress inetAddr : resolutionResults.addresses) {
+            servers.add(new EquivalentAddressGroup(new InetSocketAddress(inetAddr, port)));
+          }
+          servers.addAll(resolutionResults.balancerAddresses);
+
+          Attributes.Builder attrs = Attributes.newBuilder();
+          if (!resolutionResults.txtRecords.isEmpty()) {
+            Map<String, Object> serviceConfig = null;
+            try {
+              for (Map<String, Object> possibleConfig :
+                  parseTxtResults(resolutionResults.txtRecords)) {
+                try {
+                  serviceConfig =
+                      maybeChooseServiceConfig(possibleConfig, random, getLocalHostname());
+                } catch (RuntimeException e) {
+                  logger.log(Level.WARNING, "Bad service config choice " + possibleConfig, e);
+                }
+                if (serviceConfig != null) {
+                  break;
+                }
+              }
+            } catch (RuntimeException e) {
+              logger.log(Level.WARNING, "Can't parse service Configs", e);
+            }
+            if (serviceConfig != null) {
+              attrs.set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig);
+            }
+          } else {
+            logger.log(Level.FINE, "No TXT records found for {0}", new Object[]{host});
+          }
+          savedListener.onAddresses(servers, attrs.build());
+        } finally {
+          synchronized (DnsNameResolver.this) {
+            resolving = false;
+          }
+        }
+      }
+    };
+
+  /** Returns value of network address cache ttl property. */
+  private static long getNetworkAddressCacheTtlNanos() {
+    String cacheTtlPropertyValue = System.getProperty(NETWORKADDRESS_CACHE_TTL_PROPERTY);
+    long cacheTtl = DEFAULT_NETWORK_CACHE_TTL_SECONDS;
+    if (cacheTtlPropertyValue != null) {
+      try {
+        cacheTtl = Long.parseLong(cacheTtlPropertyValue);
+      } catch (NumberFormatException e) {
+        logger.log(
+            Level.WARNING,
+            "Property({0}) valid is not valid number format({1}), fall back to default({2})",
+            new Object[] {NETWORKADDRESS_CACHE_TTL_PROPERTY, cacheTtlPropertyValue, cacheTtl});
+      }
+    }
+    return cacheTtl > 0 ? TimeUnit.SECONDS.toNanos(cacheTtl) : cacheTtl;
+  }
+
+  @GuardedBy("this")
+  private void resolve() {
+    if (resolving || shutdown) {
+      return;
+    }
+    executor.execute(resolutionRunnable);
+  }
+
+  @Override
+  public final synchronized void shutdown() {
+    if (shutdown) {
+      return;
+    }
+    shutdown = true;
+    if (executor != null) {
+      executor = SharedResourceHolder.release(executorResource, executor);
+    }
+  }
+
+  final int getPort() {
+    return port;
+  }
+
+  @VisibleForTesting
+  static ResolutionResults resolveAll(
+      AddressResolver addressResolver,
+      @Nullable ResourceResolver resourceResolver,
+      boolean requestSrvRecords,
+      boolean requestTxtRecords,
+      String name) {
+    List<? extends InetAddress> addresses = Collections.emptyList();
+    Exception addressesException = null;
+    List<EquivalentAddressGroup> balancerAddresses = Collections.emptyList();
+    Exception balancerAddressesException = null;
+    List<String> txtRecords = Collections.emptyList();
+    Exception txtRecordsException = null;
+
+    try {
+      addresses = addressResolver.resolveAddress(name);
+    } catch (Exception e) {
+      addressesException = e;
+    }
+    if (resourceResolver != null) {
+      if (requestSrvRecords) {
+        try {
+          balancerAddresses =
+              resourceResolver.resolveSrv(addressResolver, GRPCLB_NAME_PREFIX + name);
+        } catch (Exception e) {
+          balancerAddressesException = e;
+        }
+      }
+      if (requestTxtRecords) {
+        boolean balancerLookupFailedOrNotAttempted =
+            !requestSrvRecords || balancerAddressesException != null;
+        boolean dontResolveTxt =
+            (addressesException != null) && balancerLookupFailedOrNotAttempted;
+        // Only do the TXT record lookup if one of the above address resolutions succeeded.
+        if (!dontResolveTxt) {
+          try {
+            txtRecords = resourceResolver.resolveTxt(SERVICE_CONFIG_NAME_PREFIX + name);
+          } catch (Exception e) {
+            txtRecordsException = e;
+          }
+        }
+      }
+    }
+    try {
+      if (addressesException != null && balancerAddressesException != null) {
+        throw new RuntimeException(addressesException);
+      }
+    } finally {
+      if (addressesException != null) {
+        logger.log(Level.FINE, "Address resolution failure", addressesException);
+      }
+      if (balancerAddressesException != null) {
+        logger.log(Level.FINE, "Balancer resolution failure", balancerAddressesException);
+      }
+      if (txtRecordsException != null) {
+        logger.log(Level.FINE, "ServiceConfig resolution failure", txtRecordsException);
+      }
+    }
+    return new ResolutionResults(addresses, txtRecords, balancerAddresses);
+  }
+
+  @SuppressWarnings("unchecked")
+  @VisibleForTesting
+  static List<Map<String, Object>> parseTxtResults(List<String> txtRecords) {
+    List<Map<String, Object>> serviceConfigs = new ArrayList<Map<String, Object>>();
+    for (String txtRecord : txtRecords) {
+      if (txtRecord.startsWith(SERVICE_CONFIG_PREFIX)) {
+        List<Map<String, Object>> choices;
+        try {
+          Object rawChoices = JsonParser.parse(txtRecord.substring(SERVICE_CONFIG_PREFIX.length()));
+          if (!(rawChoices instanceof List)) {
+            throw new IOException("wrong type " + rawChoices);
+          }
+          List<Object> listChoices = (List<Object>) rawChoices;
+          for (Object obj : listChoices) {
+            if (!(obj instanceof Map)) {
+              throw new IOException("wrong element type " + rawChoices);
+            }
+          }
+          choices = (List<Map<String, Object>>) (List<?>) listChoices;
+        } catch (IOException e) {
+          logger.log(Level.WARNING, "Bad service config: " + txtRecord, e);
+          continue;
+        }
+        serviceConfigs.addAll(choices);
+      } else {
+        logger.log(Level.FINE, "Ignoring non service config {0}", new Object[]{txtRecord});
+      }
+    }
+    return serviceConfigs;
+  }
+
+  @Nullable
+  private static final Double getPercentageFromChoice(
+      Map<String, Object> serviceConfigChoice) {
+    if (!serviceConfigChoice.containsKey(SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY)) {
+      return null;
+    }
+    return ServiceConfigUtil.getDouble(serviceConfigChoice, SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY);
+  }
+
+  @Nullable
+  private static final List<String> getClientLanguagesFromChoice(
+      Map<String, Object> serviceConfigChoice) {
+    if (!serviceConfigChoice.containsKey(SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY)) {
+      return null;
+    }
+    return ServiceConfigUtil.checkStringList(
+        ServiceConfigUtil.getList(serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY));
+  }
+
+  @Nullable
+  private static final List<String> getHostnamesFromChoice(
+      Map<String, Object> serviceConfigChoice) {
+    if (!serviceConfigChoice.containsKey(SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY)) {
+      return null;
+    }
+    return ServiceConfigUtil.checkStringList(
+        ServiceConfigUtil.getList(serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY));
+  }
+
+  /**
+   * Determines if a given Service Config choice applies, and if so, returns it.
+   *
+   * @see <a href="https://github.com/grpc/proposal/blob/master/A2-service-configs-in-dns.md">
+   *   Service Config in DNS</a>
+   * @param choice The service config choice.
+   * @return The service config object or {@code null} if this choice does not apply.
+   */
+  @Nullable
+  @SuppressWarnings("BetaApi") // Verify isn't all that beta
+  @VisibleForTesting
+  static Map<String, Object> maybeChooseServiceConfig(
+      Map<String, Object> choice, Random random, String hostname) {
+    for (Entry<String, ?> entry : choice.entrySet()) {
+      Verify.verify(SERVICE_CONFIG_CHOICE_KEYS.contains(entry.getKey()), "Bad key: %s", entry);
+    }
+
+    List<String> clientLanguages = getClientLanguagesFromChoice(choice);
+    if (clientLanguages != null && !clientLanguages.isEmpty()) {
+      boolean javaPresent = false;
+      for (String lang : clientLanguages) {
+        if ("java".equalsIgnoreCase(lang)) {
+          javaPresent = true;
+          break;
+        }
+      }
+      if (!javaPresent) {
+        return null;
+      }
+    }
+    Double percentage = getPercentageFromChoice(choice);
+    if (percentage != null) {
+      int pct = percentage.intValue();
+      Verify.verify(pct >= 0 && pct <= 100, "Bad percentage: %s", percentage);
+      if (random.nextInt(100) >= pct) {
+        return null;
+      }
+    }
+    List<String> clientHostnames = getHostnamesFromChoice(choice);
+    if (clientHostnames != null && !clientHostnames.isEmpty()) {
+      boolean hostnamePresent = false;
+      for (String clientHostname : clientHostnames) {
+        if (clientHostname.equals(hostname)) {
+          hostnamePresent = true;
+          break;
+        }
+      }
+      if (!hostnamePresent) {
+        return null;
+      }
+    }
+    return ServiceConfigUtil.getObject(choice, SERVICE_CONFIG_CHOICE_SERVICE_CONFIG_KEY);
+  }
+
+  /**
+   * Describes the results from a DNS query.
+   */
+  @VisibleForTesting
+  static final class ResolutionResults {
+    final List<? extends InetAddress> addresses;
+    final List<String> txtRecords;
+    final List<EquivalentAddressGroup> balancerAddresses;
+
+    ResolutionResults(
+        List<? extends InetAddress> addresses,
+        List<String> txtRecords,
+        List<EquivalentAddressGroup> balancerAddresses) {
+      this.addresses = Collections.unmodifiableList(checkNotNull(addresses, "addresses"));
+      this.txtRecords = Collections.unmodifiableList(checkNotNull(txtRecords, "txtRecords"));
+      this.balancerAddresses =
+          Collections.unmodifiableList(checkNotNull(balancerAddresses, "balancerAddresses"));
+    }
+  }
+
+  @VisibleForTesting
+  void setAddressResolver(AddressResolver addressResolver) {
+    this.addressResolver = addressResolver;
+  }
+
+  /**
+   * {@link ResourceResolverFactory} is a factory for making resource resolvers.  It supports
+   * optionally checking if the factory is available.
+   */
+  interface ResourceResolverFactory {
+
+    /**
+     * Creates a new resource resolver.  The return value is {@code null} iff
+     * {@link #unavailabilityCause()} is not null;
+     */
+    @Nullable ResourceResolver newResourceResolver();
+
+    /**
+     * Returns the reason why the resource resolver cannot be created.  The return value is
+     * {@code null} if {@link #newResourceResolver()} is suitable for use.
+     */
+    @Nullable Throwable unavailabilityCause();
+  }
+
+  /**
+   * AddressResolver resolves a hostname into a list of addresses.
+   */
+  interface AddressResolver {
+    List<InetAddress> resolveAddress(String host) throws Exception;
+  }
+
+  private enum JdkAddressResolver implements AddressResolver {
+    INSTANCE;
+
+    @Override
+    public List<InetAddress> resolveAddress(String host) throws UnknownHostException {
+      return Collections.unmodifiableList(Arrays.asList(InetAddress.getAllByName(host)));
+    }
+  }
+
+  /**
+   * {@link ResourceResolver} is a Dns ResourceRecord resolver.
+   */
+  interface ResourceResolver {
+    List<String> resolveTxt(String host) throws Exception;
+
+    List<EquivalentAddressGroup> resolveSrv(
+        AddressResolver addressResolver, String host) throws Exception;
+  }
+
+  @Nullable
+  private ResourceResolver getResourceResolver() {
+    ResourceResolver rr;
+    if ((rr = resourceResolver.get()) == null) {
+      if (resourceResolverFactory != null) {
+        assert resourceResolverFactory.unavailabilityCause() == null;
+        rr = resourceResolverFactory.newResourceResolver();
+      }
+    }
+    return rr;
+  }
+
+  @Nullable
+  @VisibleForTesting
+  static ResourceResolverFactory getResourceResolverFactory(ClassLoader loader) {
+    Class<? extends ResourceResolverFactory> jndiClazz;
+    try {
+      jndiClazz =
+          Class.forName("io.grpc.internal.JndiResourceResolverFactory", true, loader)
+              .asSubclass(ResourceResolverFactory.class);
+    } catch (ClassNotFoundException e) {
+      logger.log(Level.FINE, "Unable to find JndiResourceResolverFactory, skipping.", e);
+      return null;
+    }
+    Constructor<? extends ResourceResolverFactory> jndiCtor;
+    try {
+      jndiCtor = jndiClazz.getConstructor();
+    } catch (Exception e) {
+      logger.log(Level.FINE, "Can't find JndiResourceResolverFactory ctor, skipping.", e);
+      return null;
+    }
+    ResourceResolverFactory rrf;
+    try {
+      rrf = jndiCtor.newInstance();
+    } catch (Exception e) {
+      logger.log(Level.FINE, "Can't construct JndiResourceResolverFactory, skipping.", e);
+      return null;
+    }
+    if (rrf.unavailabilityCause() != null) {
+      logger.log(
+          Level.FINE,
+          "JndiResourceResolverFactory not available, skipping.",
+          rrf.unavailabilityCause());
+    }
+    return rrf;
+  }
+
+  private static String getLocalHostname() {
+    if (localHostname == null) {
+      try {
+        localHostname = InetAddress.getLocalHost().getHostName();
+      } catch (UnknownHostException e) {
+        throw new RuntimeException(e);
+      }
+    }
+    return localHostname;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/DnsNameResolverProvider.java b/core/src/main/java/io/grpc/internal/DnsNameResolverProvider.java
new file mode 100644
index 0000000..d0db539
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/DnsNameResolverProvider.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+import io.grpc.Attributes;
+import io.grpc.NameResolverProvider;
+import java.net.URI;
+
+/**
+ * A provider for {@link DnsNameResolver}.
+ *
+ * <p>It resolves a target URI whose scheme is {@code "dns"}. The (optional) authority of the target
+ * URI is reserved for the address of alternative DNS server (not implemented yet). The path of the
+ * target URI, excluding the leading slash {@code '/'}, is treated as the host name and the optional
+ * port to be resolved by DNS. Example target URIs:
+ *
+ * <ul>
+ *   <li>{@code "dns:///foo.googleapis.com:8080"} (using default DNS)</li>
+ *   <li>{@code "dns://8.8.8.8/foo.googleapis.com:8080"} (using alternative DNS (not implemented
+ *   yet))</li>
+ *   <li>{@code "dns:///foo.googleapis.com"} (without port)</li>
+ * </ul>
+ */
+public final class DnsNameResolverProvider extends NameResolverProvider {
+
+  private static final String SCHEME = "dns";
+
+  @Override
+  public DnsNameResolver newNameResolver(URI targetUri, Attributes params) {
+    if (SCHEME.equals(targetUri.getScheme())) {
+      String targetPath = Preconditions.checkNotNull(targetUri.getPath(), "targetPath");
+      Preconditions.checkArgument(targetPath.startsWith("/"),
+          "the path component (%s) of the target (%s) must start with '/'", targetPath, targetUri);
+      String name = targetPath.substring(1);
+      return new DnsNameResolver(
+          targetUri.getAuthority(),
+          name,
+          params,
+          GrpcUtil.SHARED_CHANNEL_EXECUTOR,
+          GrpcUtil.getDefaultProxyDetector(),
+          Stopwatch.createUnstarted());
+    } else {
+      return null;
+    }
+  }
+
+  @Override
+  public String getDefaultScheme() {
+    return SCHEME;
+  }
+
+  @Override
+  protected boolean isAvailable() {
+    return true;
+  }
+
+  @Override
+  protected int priority() {
+    return 5;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ExponentialBackoffPolicy.java b/core/src/main/java/io/grpc/internal/ExponentialBackoffPolicy.java
new file mode 100644
index 0000000..686fe85
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ExponentialBackoffPolicy.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Retry Policy for Transport reconnection.  Initial parameters from
+ * https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
+ *
+ * <p>TODO(carl-mastrangelo): add unit tests for this class
+ */
+public final class ExponentialBackoffPolicy implements BackoffPolicy {
+  public static final class Provider implements BackoffPolicy.Provider {
+    @Override
+    public BackoffPolicy get() {
+      return new ExponentialBackoffPolicy();
+    }
+  }
+
+  private Random random = new Random();
+  private long initialBackoffNanos = TimeUnit.SECONDS.toNanos(1);
+  private long maxBackoffNanos = TimeUnit.MINUTES.toNanos(2);
+  private double multiplier = 1.6;
+  private double jitter = .2;
+
+  private long nextBackoffNanos = initialBackoffNanos;
+
+  @Override
+  public long nextBackoffNanos() {
+    long currentBackoffNanos = nextBackoffNanos;
+    nextBackoffNanos = Math.min((long) (currentBackoffNanos * multiplier), maxBackoffNanos);
+    return currentBackoffNanos
+        + uniformRandom(-jitter * currentBackoffNanos, jitter * currentBackoffNanos);
+  }
+
+  private long uniformRandom(double low, double high) {
+    checkArgument(high >= low);
+    double mag = high - low;
+    return (long) (random.nextDouble() * mag + low);
+  }
+
+  /*
+   * No guice and no flags means we get to implement these setters for testing ourselves.  Do not
+   * call these from non-test code.
+   */
+
+  @VisibleForTesting
+  ExponentialBackoffPolicy setRandom(Random random) {
+    this.random = random;
+    return this;
+  }
+
+  @VisibleForTesting
+  ExponentialBackoffPolicy setInitialBackoffNanos(long initialBackoffNanos) {
+    this.initialBackoffNanos = initialBackoffNanos;
+    return this;
+  }
+
+  @VisibleForTesting
+  ExponentialBackoffPolicy setMaxBackoffNanos(long maxBackoffNanos) {
+    this.maxBackoffNanos = maxBackoffNanos;
+    return this;
+  }
+
+  @VisibleForTesting
+  ExponentialBackoffPolicy setMultiplier(double multiplier) {
+    this.multiplier = multiplier;
+    return this;
+  }
+
+  @VisibleForTesting
+  ExponentialBackoffPolicy setJitter(double jitter) {
+    this.jitter = jitter;
+    return this;
+  }
+}
+
diff --git a/core/src/main/java/io/grpc/internal/FailingClientStream.java b/core/src/main/java/io/grpc/internal/FailingClientStream.java
new file mode 100644
index 0000000..798779f
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/FailingClientStream.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.ClientStreamListener.RpcProgress;
+
+/**
+ * An implementation of {@link ClientStream} that fails (by calling {@link
+ * ClientStreamListener#closed}) when started, and silently does nothing for the other operations.
+ */
+public final class FailingClientStream extends NoopClientStream {
+  private boolean started;
+  private final Status error;
+  private final RpcProgress rpcProgress;
+
+  /**
+   * Creates a {@code FailingClientStream} that would fail with the given error.
+   */
+  public FailingClientStream(Status error) {
+    this(error, RpcProgress.PROCESSED);
+  }
+
+  /**
+   * Creates a {@code FailingClientStream} that would fail with the given error.
+   */
+  public FailingClientStream(Status error, RpcProgress rpcProgress) {
+    Preconditions.checkArgument(!error.isOk(), "error must not be OK");
+    this.error = error;
+    this.rpcProgress = rpcProgress;
+  }
+
+  @Override
+  public void start(ClientStreamListener listener) {
+    Preconditions.checkState(!started, "already started");
+    started = true;
+    listener.closed(error, rpcProgress, new Metadata());
+  }
+
+  @VisibleForTesting
+  Status getError() {
+    return error;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/FailingClientTransport.java b/core/src/main/java/io/grpc/internal/FailingClientTransport.java
new file mode 100644
index 0000000..25d2001
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/FailingClientTransport.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.CallOptions;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalLogId;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.internal.ClientStreamListener.RpcProgress;
+import java.util.concurrent.Executor;
+
+/**
+ * A client transport that creates streams that will immediately fail when started.
+ */
+class FailingClientTransport implements ClientTransport {
+  @VisibleForTesting
+  final Status error;
+  private final RpcProgress rpcProgress;
+
+  FailingClientTransport(Status error, RpcProgress rpcProgress) {
+    Preconditions.checkArgument(!error.isOk(), "error must not be OK");
+    this.error = error;
+    this.rpcProgress = rpcProgress;
+  }
+
+  @Override
+  public ClientStream newStream(
+      MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions) {
+    return new FailingClientStream(error, rpcProgress);
+  }
+
+  @Override
+  public void ping(final PingCallback callback, Executor executor) {
+    executor.execute(new Runnable() {
+        @Override public void run() {
+          callback.onFailure(error.asException());
+        }
+      });
+  }
+
+  @Override
+  public ListenableFuture<SocketStats> getStats() {
+    SettableFuture<SocketStats> ret = SettableFuture.create();
+    ret.set(null);
+    return ret;
+  }
+
+  @Override
+  public InternalLogId getLogId() {
+    throw new UnsupportedOperationException("Not a real transport");
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/FixedObjectPool.java b/core/src/main/java/io/grpc/internal/FixedObjectPool.java
new file mode 100644
index 0000000..7ad6d46
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/FixedObjectPool.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * An object pool that always returns the same instance and does nothing when returning the object.
+ */
+public final class FixedObjectPool<T> implements ObjectPool<T> {
+  private final T object;
+
+  public FixedObjectPool(T object) {
+    this.object = Preconditions.checkNotNull(object, "object");
+  }
+
+  @Override
+  public T getObject() {
+    return object;
+  }
+
+  @Override
+  public T returnObject(Object returned) {
+    return null;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ForwardingClientStream.java b/core/src/main/java/io/grpc/internal/ForwardingClientStream.java
new file mode 100644
index 0000000..b1d25d5
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ForwardingClientStream.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.MoreObjects;
+import io.grpc.Attributes;
+import io.grpc.Compressor;
+import io.grpc.Deadline;
+import io.grpc.DecompressorRegistry;
+import io.grpc.Status;
+import java.io.InputStream;
+
+abstract class ForwardingClientStream implements ClientStream {
+  protected abstract ClientStream delegate();
+
+  @Override
+  public void request(int numMessages) {
+    delegate().request(numMessages);
+  }
+
+  @Override
+  public void writeMessage(InputStream message) {
+    delegate().writeMessage(message);
+  }
+
+  @Override
+  public void flush() {
+    delegate().flush();
+  }
+
+  @Override
+  public boolean isReady() {
+    return delegate().isReady();
+  }
+
+  @Override
+  public void setCompressor(Compressor compressor) {
+    delegate().setCompressor(compressor);
+  }
+
+  @Override
+  public void setMessageCompression(boolean enable) {
+    delegate().setMessageCompression(enable);
+  }
+
+  @Override
+  public void cancel(Status reason) {
+    delegate().cancel(reason);
+  }
+
+  @Override
+  public void halfClose() {
+    delegate().halfClose();
+  }
+
+  @Override
+  public void setAuthority(String authority) {
+    delegate().setAuthority(authority);
+  }
+
+  @Override
+  public void setFullStreamDecompression(boolean fullStreamDecompression) {
+    delegate().setFullStreamDecompression(fullStreamDecompression);
+  }
+
+  @Override
+  public void setDecompressorRegistry(DecompressorRegistry decompressorRegistry) {
+    delegate().setDecompressorRegistry(decompressorRegistry);
+  }
+
+  @Override
+  public void start(ClientStreamListener listener) {
+    delegate().start(listener);
+  }
+
+  @Override
+  public void setMaxInboundMessageSize(int maxSize) {
+    delegate().setMaxInboundMessageSize(maxSize);
+  }
+
+  @Override
+  public void setMaxOutboundMessageSize(int maxSize) {
+    delegate().setMaxOutboundMessageSize(maxSize);
+  }
+
+  @Override
+  public void setDeadline(Deadline deadline) {
+    delegate().setDeadline(deadline);
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    return delegate().getAttributes();
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ForwardingClientStreamListener.java b/core/src/main/java/io/grpc/internal/ForwardingClientStreamListener.java
new file mode 100644
index 0000000..d4e7790
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ForwardingClientStreamListener.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.MoreObjects;
+import io.grpc.Metadata;
+import io.grpc.Status;
+
+abstract class ForwardingClientStreamListener implements ClientStreamListener {
+
+  protected abstract ClientStreamListener delegate();
+
+  @Override
+  public void headersRead(Metadata headers) {
+    delegate().headersRead(headers);
+  }
+
+  @Override
+  public void closed(Status status, Metadata trailers) {
+    delegate().closed(status, trailers);
+  }
+
+  @Override
+  public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) {
+    delegate().closed(status, rpcProgress, trailers);
+  }
+
+  @Override
+  public void messagesAvailable(MessageProducer producer) {
+    delegate().messagesAvailable(producer);
+  }
+
+  @Override
+  public void onReady() {
+    delegate().onReady();
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ForwardingConnectionClientTransport.java b/core/src/main/java/io/grpc/internal/ForwardingConnectionClientTransport.java
new file mode 100644
index 0000000..e54f8b1
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ForwardingConnectionClientTransport.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.util.concurrent.ListenableFuture;
+import io.grpc.Attributes;
+import io.grpc.CallOptions;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalLogId;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import java.util.concurrent.Executor;
+
+abstract class ForwardingConnectionClientTransport implements ConnectionClientTransport {
+  @Override
+  public Runnable start(Listener listener) {
+    return delegate().start(listener);
+  }
+
+  @Override
+  public void shutdown(Status status) {
+    delegate().shutdown(status);
+  }
+
+  @Override
+  public void shutdownNow(Status status) {
+    delegate().shutdownNow(status);
+  }
+
+  @Override
+  public ClientStream newStream(
+      MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions) {
+    return delegate().newStream(method, headers, callOptions);
+  }
+
+  @Override
+  public void ping(PingCallback callback, Executor executor) {
+    delegate().ping(callback, executor);
+  }
+
+  @Override
+  public InternalLogId getLogId() {
+    return delegate().getLogId();
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    return delegate().getAttributes();
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("delegate", delegate()).toString();
+  }
+
+  @Override
+  public ListenableFuture<SocketStats> getStats() {
+    return delegate().getStats();
+  }
+
+  protected abstract ConnectionClientTransport delegate();
+}
diff --git a/core/src/main/java/io/grpc/internal/ForwardingManagedChannel.java b/core/src/main/java/io/grpc/internal/ForwardingManagedChannel.java
new file mode 100644
index 0000000..7ef4ce4
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ForwardingManagedChannel.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.MoreObjects;
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.ConnectivityState;
+import io.grpc.ManagedChannel;
+import io.grpc.MethodDescriptor;
+import java.util.concurrent.TimeUnit;
+
+abstract class ForwardingManagedChannel extends ManagedChannel {
+
+  private final ManagedChannel delegate;
+
+  ForwardingManagedChannel(ManagedChannel delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public ManagedChannel shutdown() {
+    return delegate.shutdown();
+  }
+
+  @Override
+  public boolean isShutdown() {
+    return delegate.isShutdown();
+  }
+
+  @Override
+  public boolean isTerminated() {
+    return delegate.isTerminated();
+  }
+
+  @Override
+  public ManagedChannel shutdownNow() {
+    return delegate.shutdownNow();
+  }
+
+  @Override
+  public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+    return delegate.awaitTermination(timeout, unit);
+  }
+
+  @Override
+  public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
+      MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) {
+    return delegate.newCall(methodDescriptor, callOptions);
+  }
+
+  @Override
+  public String authority() {
+    return delegate.authority();
+  }
+
+  @Override
+  public ConnectivityState getState(boolean requestConnection) {
+    return delegate.getState(requestConnection);
+  }
+
+  @Override
+  public void notifyWhenStateChanged(ConnectivityState source, Runnable callback) {
+    delegate.notifyWhenStateChanged(source, callback);
+  }
+
+  @Override
+  public void resetConnectBackoff() {
+    delegate.resetConnectBackoff();
+  }
+
+  @Override
+  public void enterIdle() {
+    delegate.enterIdle();
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("delegate", delegate).toString();
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ForwardingNameResolver.java b/core/src/main/java/io/grpc/internal/ForwardingNameResolver.java
new file mode 100644
index 0000000..954e1dc
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ForwardingNameResolver.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+import io.grpc.NameResolver;
+
+/**
+* A forwarding class to ensure non overridden methods are forwarded to the delegate.
+ */
+abstract class ForwardingNameResolver extends NameResolver {
+  private final NameResolver delegate;
+
+  ForwardingNameResolver(NameResolver delegate) {
+    checkNotNull(delegate, "delegate can not be null");
+    this.delegate = delegate;
+  }
+
+  @Override
+  public String getServiceAuthority() {
+    return delegate.getServiceAuthority();
+  }
+
+  @Override
+  public void start(Listener listener) {
+    delegate.start(listener);
+  }
+
+  @Override
+  public void shutdown() {
+    delegate.shutdown();
+  }
+
+  @Override
+  public void refresh() {
+    delegate.refresh();
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("delegate", delegate).toString();
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ForwardingReadableBuffer.java b/core/src/main/java/io/grpc/internal/ForwardingReadableBuffer.java
new file mode 100644
index 0000000..03fafee
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ForwardingReadableBuffer.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * Base class for a wrapper around another {@link ReadableBuffer}.
+ *
+ * <p>This class just passes every operation through to the underlying buffer. Subclasses may
+ * override methods to intercept certain operations.
+ */
+public abstract class ForwardingReadableBuffer implements ReadableBuffer {
+
+  private final ReadableBuffer buf;
+
+  /**
+   * Constructor.
+   *
+   * @param buf the underlying buffer
+   */
+  public ForwardingReadableBuffer(ReadableBuffer buf) {
+    this.buf = Preconditions.checkNotNull(buf, "buf");
+  }
+
+  @Override
+  public int readableBytes() {
+    return buf.readableBytes();
+  }
+
+  @Override
+  public int readUnsignedByte() {
+    return buf.readUnsignedByte();
+  }
+
+  @Override
+  public int readInt() {
+    return buf.readInt();
+  }
+
+  @Override
+  public void skipBytes(int length) {
+    buf.skipBytes(length);
+  }
+
+  @Override
+  public void readBytes(byte[] dest, int destOffset, int length) {
+    buf.readBytes(dest, destOffset, length);
+  }
+
+  @Override
+  public void readBytes(ByteBuffer dest) {
+    buf.readBytes(dest);
+  }
+
+  @Override
+  public void readBytes(OutputStream dest, int length) throws IOException {
+    buf.readBytes(dest, length);
+  }
+
+  @Override
+  public ReadableBuffer readBytes(int length) {
+    return buf.readBytes(length);
+  }
+
+  @Override
+  public boolean hasArray() {
+    return buf.hasArray();
+  }
+
+  @Override
+  public byte[] array() {
+    return buf.array();
+  }
+
+  @Override
+  public int arrayOffset() {
+    return buf.arrayOffset();
+  }
+
+  @Override
+  public void close() {
+    buf.close();
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this).add("delegate", buf).toString();
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/Framer.java b/core/src/main/java/io/grpc/internal/Framer.java
new file mode 100644
index 0000000..3cb341e
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/Framer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Compressor;
+import java.io.InputStream;
+
+/** Interface for framing gRPC messages. */
+public interface Framer {
+  /**
+   * Writes out a payload message.
+   *
+   * @param message contains the message to be written out. It will be completely consumed.
+   */
+  void writePayload(InputStream message);
+
+  /** Flush the buffered payload. */
+  void flush();
+
+  /** Returns whether the framer is closed. */
+  boolean isClosed();
+
+  /** Closes, with flush. */
+  void close();
+
+  /** Closes, without flush. */
+  void dispose();
+
+  /** Enable or disable compression. */
+  Framer setMessageCompression(boolean enable);
+
+  /** Set the compressor used for compression. */
+  Framer setCompressor(Compressor compressor);
+
+  /** Set a size limit for each outbound message. */ 
+  void setMaxOutboundMessageSize(int maxSize);
+}
diff --git a/core/src/main/java/io/grpc/internal/GrpcAttributes.java b/core/src/main/java/io/grpc/internal/GrpcAttributes.java
new file mode 100644
index 0000000..67da06f
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/GrpcAttributes.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Attributes;
+import java.util.Map;
+
+/**
+ * Special attributes that are only useful to gRPC.
+ */
+public final class GrpcAttributes {
+  /**
+   * Attribute key for service config.
+   */
+  public static final Attributes.Key<Map<String, Object>> NAME_RESOLVER_SERVICE_CONFIG =
+      Attributes.Key.create("service-config");
+
+  /**
+   * The naming authority of a gRPC LB server address.  It is an address-group-level attribute,
+   * present when the address group is a LoadBalancer.
+   */
+  public static final Attributes.Key<String> ATTR_LB_ADDR_AUTHORITY =
+      Attributes.Key.create("io.grpc.grpclb.lbAddrAuthority");
+
+  /**
+   * Whether this EquivalentAddressGroup was provided by a GRPCLB server. It would be rare for this
+   * value to be {@code false}; generally it would be better to not have the key present at all.
+   */
+  public static final Attributes.Key<Boolean> ATTR_LB_PROVIDED_BACKEND =
+      Attributes.Key.create("io.grpc.grpclb.lbProvidedBackend");
+
+  private GrpcAttributes() {}
+}
diff --git a/core/src/main/java/io/grpc/internal/GrpcUtil.java b/core/src/main/java/io/grpc/internal/GrpcUtil.java
new file mode 100644
index 0000000..5398299
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/GrpcUtil.java
@@ -0,0 +1,762 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Supplier;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalLogId;
+import io.grpc.InternalMetadata;
+import io.grpc.InternalMetadata.TrustedAsciiMarshaller;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.internal.ClientStreamListener.RpcProgress;
+import io.grpc.internal.SharedResourceHolder.Resource;
+import io.grpc.internal.StreamListener.MessageProducer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Common utilities for GRPC.
+ */
+public final class GrpcUtil {
+
+  private static final Logger log = Logger.getLogger(GrpcUtil.class.getName());
+
+  public static final Charset US_ASCII = Charset.forName("US-ASCII");
+
+  // AppEngine runtimes have constraints on threading and socket handling
+  // that need to be accommodated.
+  public static final boolean IS_RESTRICTED_APPENGINE =
+      System.getProperty("com.google.appengine.runtime.environment") != null
+          && "1.7".equals(System.getProperty("java.specification.version"));
+
+  /**
+   * {@link io.grpc.Metadata.Key} for the timeout header.
+   */
+  public static final Metadata.Key<Long> TIMEOUT_KEY =
+          Metadata.Key.of(GrpcUtil.TIMEOUT, new TimeoutMarshaller());
+
+  /**
+   * {@link io.grpc.Metadata.Key} for the message encoding header.
+   */
+  public static final Metadata.Key<String> MESSAGE_ENCODING_KEY =
+          Metadata.Key.of(GrpcUtil.MESSAGE_ENCODING, Metadata.ASCII_STRING_MARSHALLER);
+
+  /**
+   * {@link io.grpc.Metadata.Key} for the accepted message encodings header.
+   */
+  public static final Metadata.Key<byte[]> MESSAGE_ACCEPT_ENCODING_KEY =
+      InternalMetadata.keyOf(GrpcUtil.MESSAGE_ACCEPT_ENCODING, new AcceptEncodingMarshaller());
+
+  /**
+   * {@link io.grpc.Metadata.Key} for the stream's content encoding header.
+   */
+  public static final Metadata.Key<String> CONTENT_ENCODING_KEY =
+      Metadata.Key.of(GrpcUtil.CONTENT_ENCODING, Metadata.ASCII_STRING_MARSHALLER);
+
+  /**
+   * {@link io.grpc.Metadata.Key} for the stream's accepted content encoding header.
+   */
+  public static final Metadata.Key<byte[]> CONTENT_ACCEPT_ENCODING_KEY =
+      InternalMetadata.keyOf(GrpcUtil.CONTENT_ACCEPT_ENCODING, new AcceptEncodingMarshaller());
+
+  private static final class AcceptEncodingMarshaller implements TrustedAsciiMarshaller<byte[]> {
+    @Override
+    public byte[] toAsciiString(byte[] value) {
+      return value;
+    }
+
+    @Override
+    public byte[] parseAsciiString(byte[] serialized) {
+      return serialized;
+    }
+  }
+
+  /**
+   * {@link io.grpc.Metadata.Key} for the Content-Type request/response header.
+   */
+  public static final Metadata.Key<String> CONTENT_TYPE_KEY =
+          Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER);
+
+  /**
+   * {@link io.grpc.Metadata.Key} for the Transfer encoding.
+   */
+  public static final Metadata.Key<String> TE_HEADER =
+      Metadata.Key.of("te", Metadata.ASCII_STRING_MARSHALLER);
+
+  /**
+   * {@link io.grpc.Metadata.Key} for the Content-Type request/response header.
+   */
+  public static final Metadata.Key<String> USER_AGENT_KEY =
+          Metadata.Key.of("user-agent", Metadata.ASCII_STRING_MARSHALLER);
+
+  /**
+   * The default port for plain-text connections.
+   */
+  public static final int DEFAULT_PORT_PLAINTEXT = 80;
+
+  /**
+   * The default port for SSL connections.
+   */
+  public static final int DEFAULT_PORT_SSL = 443;
+
+  /**
+   * Content-Type used for GRPC-over-HTTP/2.
+   */
+  public static final String CONTENT_TYPE_GRPC = "application/grpc";
+
+  /**
+   * The HTTP method used for GRPC requests.
+   */
+  public static final String HTTP_METHOD = "POST";
+
+  /**
+   * The TE (transport encoding) header for requests over HTTP/2.
+   */
+  public static final String TE_TRAILERS = "trailers";
+
+  /**
+   * The Timeout header name.
+   */
+  public static final String TIMEOUT = "grpc-timeout";
+
+  /**
+   * The message encoding (i.e. compression) that can be used in the stream.
+   */
+  public static final String MESSAGE_ENCODING = "grpc-encoding";
+
+  /**
+   * The accepted message encodings (i.e. compression) that can be used in the stream.
+   */
+  public static final String MESSAGE_ACCEPT_ENCODING = "grpc-accept-encoding";
+
+  /**
+   * The content-encoding used to compress the full gRPC stream.
+   */
+  public static final String CONTENT_ENCODING = "content-encoding";
+
+  /**
+   * The accepted content-encodings that can be used to compress the full gRPC stream.
+   */
+  public static final String CONTENT_ACCEPT_ENCODING = "accept-encoding";
+
+  /**
+   * The default maximum uncompressed size (in bytes) for inbound messages. Defaults to 4 MiB.
+   */
+  public static final int DEFAULT_MAX_MESSAGE_SIZE = 4 * 1024 * 1024;
+
+  /**
+   * The default maximum size (in bytes) for inbound header/trailer.
+   */
+  // Update documentation in public-facing Builders when changing this value.
+  public static final int DEFAULT_MAX_HEADER_LIST_SIZE = 8192;
+
+  public static final Splitter ACCEPT_ENCODING_SPLITTER = Splitter.on(',').trimResults();
+
+  private static final String IMPLEMENTATION_VERSION = "1.16.0-SNAPSHOT"; // CURRENT_GRPC_VERSION
+
+  /**
+   * The default delay in nanos before we send a keepalive.
+   */
+  public static final long DEFAULT_KEEPALIVE_TIME_NANOS = TimeUnit.MINUTES.toNanos(1);
+
+  /**
+   * The default timeout in nanos for a keepalive ping request.
+   */
+  public static final long DEFAULT_KEEPALIVE_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(20L);
+
+  /**
+   * The magic keepalive time value that disables client keepalive.
+   */
+  public static final long KEEPALIVE_TIME_NANOS_DISABLED = Long.MAX_VALUE;
+
+  /**
+   * The default delay in nanos for server keepalive.
+   */
+  public static final long DEFAULT_SERVER_KEEPALIVE_TIME_NANOS = TimeUnit.HOURS.toNanos(2L);
+
+  /**
+   * The default timeout in nanos for a server keepalive ping request.
+   */
+  public static final long DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(20L);
+
+  /**
+   * The magic keepalive time value that disables keepalive.
+   */
+  public static final long SERVER_KEEPALIVE_TIME_NANOS_DISABLED = Long.MAX_VALUE;
+
+  /**
+   * The default proxy detector.
+   */
+  public static final ProxyDetector DEFAULT_PROXY_DETECTOR = new ProxyDetectorImpl();
+
+  /**
+   * A proxy detector that always claims no proxy is needed.
+   */
+  public static final ProxyDetector NOOP_PROXY_DETECTOR = new ProxyDetector() {
+    @Nullable
+    @Override
+    public ProxyParameters proxyFor(SocketAddress targetServerAddress) {
+      return null;
+    }
+  };
+
+  /**
+   * Returns a proxy detector appropriate for the current environment.
+   */
+  public static ProxyDetector getDefaultProxyDetector() {
+    if (IS_RESTRICTED_APPENGINE) {
+      return NOOP_PROXY_DETECTOR;
+    } else {
+      return DEFAULT_PROXY_DETECTOR;
+    }
+  }
+
+  /**
+   * Maps HTTP error response status codes to transport codes, as defined in <a
+   * href="https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md">
+   * http-grpc-status-mapping.md</a>. Never returns a status for which {@code status.isOk()} is
+   * {@code true}.
+   */
+  public static Status httpStatusToGrpcStatus(int httpStatusCode) {
+    return httpStatusToGrpcCode(httpStatusCode).toStatus()
+        .withDescription("HTTP status code " + httpStatusCode);
+  }
+
+  private static Status.Code httpStatusToGrpcCode(int httpStatusCode) {
+    if (httpStatusCode >= 100 && httpStatusCode < 200) {
+      // 1xx. These headers should have been ignored.
+      return Status.Code.INTERNAL;
+    }
+    switch (httpStatusCode) {
+      case HttpURLConnection.HTTP_BAD_REQUEST:  // 400
+      case 431: // Request Header Fields Too Large
+        // TODO(carl-mastrangelo): this should be added to the http-grpc-status-mapping.md doc.
+        return Status.Code.INTERNAL;
+      case HttpURLConnection.HTTP_UNAUTHORIZED:  // 401
+        return Status.Code.UNAUTHENTICATED;
+      case HttpURLConnection.HTTP_FORBIDDEN:  // 403
+        return Status.Code.PERMISSION_DENIED;
+      case HttpURLConnection.HTTP_NOT_FOUND:  // 404
+        return Status.Code.UNIMPLEMENTED;
+      case 429:  // Too Many Requests
+      case HttpURLConnection.HTTP_BAD_GATEWAY:  // 502
+      case HttpURLConnection.HTTP_UNAVAILABLE:  // 503
+      case HttpURLConnection.HTTP_GATEWAY_TIMEOUT:  // 504
+        return Status.Code.UNAVAILABLE;
+      default:
+        return Status.Code.UNKNOWN;
+    }
+  }
+
+  /**
+   * All error codes identified by the HTTP/2 spec. Used in GOAWAY and RST_STREAM frames.
+   */
+  public enum Http2Error {
+    /**
+     * Servers implementing a graceful shutdown of the connection will send {@code GOAWAY} with
+     * {@code NO_ERROR}. In this case it is important to indicate to the application that the
+     * request should be retried (i.e. {@link Status#UNAVAILABLE}).
+     */
+    NO_ERROR(0x0, Status.UNAVAILABLE),
+    PROTOCOL_ERROR(0x1, Status.INTERNAL),
+    INTERNAL_ERROR(0x2, Status.INTERNAL),
+    FLOW_CONTROL_ERROR(0x3, Status.INTERNAL),
+    SETTINGS_TIMEOUT(0x4, Status.INTERNAL),
+    STREAM_CLOSED(0x5, Status.INTERNAL),
+    FRAME_SIZE_ERROR(0x6, Status.INTERNAL),
+    REFUSED_STREAM(0x7, Status.UNAVAILABLE),
+    CANCEL(0x8, Status.CANCELLED),
+    COMPRESSION_ERROR(0x9, Status.INTERNAL),
+    CONNECT_ERROR(0xA, Status.INTERNAL),
+    ENHANCE_YOUR_CALM(0xB, Status.RESOURCE_EXHAUSTED.withDescription("Bandwidth exhausted")),
+    INADEQUATE_SECURITY(0xC, Status.PERMISSION_DENIED.withDescription("Permission denied as "
+        + "protocol is not secure enough to call")),
+    HTTP_1_1_REQUIRED(0xD, Status.UNKNOWN);
+
+    // Populate a mapping of code to enum value for quick look-up.
+    private static final Http2Error[] codeMap = buildHttp2CodeMap();
+
+    private static Http2Error[] buildHttp2CodeMap() {
+      Http2Error[] errors = Http2Error.values();
+      int size = (int) errors[errors.length - 1].code() + 1;
+      Http2Error[] http2CodeMap = new Http2Error[size];
+      for (Http2Error error : errors) {
+        int index = (int) error.code();
+        http2CodeMap[index] = error;
+      }
+      return http2CodeMap;
+    }
+
+    private final int code;
+    // Status is not guaranteed to be deeply immutable. Don't care though, since that's only true
+    // when there are exceptions in the Status, which is not true here.
+    @SuppressWarnings("ImmutableEnumChecker")
+    private final Status status;
+
+    Http2Error(int code, Status status) {
+      this.code = code;
+      this.status = status.augmentDescription("HTTP/2 error code: " + this.name());
+    }
+
+    /**
+     * Gets the code for this error used on the wire.
+     */
+    public long code() {
+      return code;
+    }
+
+    /**
+     * Gets the {@link Status} associated with this HTTP/2 code.
+     */
+    public Status status() {
+      return status;
+    }
+
+    /**
+     * Looks up the HTTP/2 error code enum value for the specified code.
+     *
+     * @param code an HTTP/2 error code value.
+     * @return the HTTP/2 error code enum or {@code null} if not found.
+     */
+    public static Http2Error forCode(long code) {
+      if (code >= codeMap.length || code < 0) {
+        return null;
+      }
+      return codeMap[(int) code];
+    }
+
+    /**
+     * Looks up the {@link Status} from the given HTTP/2 error code. This is preferred over {@code
+     * forCode(code).status()}, to more easily conform to HTTP/2:
+     *
+     * <blockquote>Unknown or unsupported error codes MUST NOT trigger any special behavior.
+     * These MAY be treated by an implementation as being equivalent to INTERNAL_ERROR.</blockquote>
+     *
+     * @param code the HTTP/2 error code.
+     * @return a {@link Status} representing the given error.
+     */
+    public static Status statusForCode(long code) {
+      Http2Error error = forCode(code);
+      if (error == null) {
+        // This "forgets" the message of INTERNAL_ERROR while keeping the same status code.
+        Status.Code statusCode = INTERNAL_ERROR.status().getCode();
+        return Status.fromCodeValue(statusCode.value())
+            .withDescription("Unrecognized HTTP/2 error code: " + code);
+      }
+
+      return error.status();
+    }
+  }
+
+  /**
+   * Indicates whether or not the given value is a valid gRPC content-type.
+   */
+  public static boolean isGrpcContentType(String contentType) {
+    if (contentType == null) {
+      return false;
+    }
+
+    if (CONTENT_TYPE_GRPC.length() > contentType.length()) {
+      return false;
+    }
+
+    contentType = contentType.toLowerCase();
+    if (!contentType.startsWith(CONTENT_TYPE_GRPC)) {
+      // Not a gRPC content-type.
+      return false;
+    }
+
+    if (contentType.length() == CONTENT_TYPE_GRPC.length()) {
+      // The strings match exactly.
+      return true;
+    }
+
+    // The contentType matches, but is longer than the expected string.
+    // We need to support variations on the content-type (e.g. +proto, +json) as defined by the
+    // gRPC wire spec.
+    char nextChar = contentType.charAt(CONTENT_TYPE_GRPC.length());
+    return nextChar == '+' || nextChar == ';';
+  }
+
+  /**
+   * Gets the User-Agent string for the gRPC transport.
+   */
+  public static String getGrpcUserAgent(
+      String transportName, @Nullable String applicationUserAgent) {
+    StringBuilder builder = new StringBuilder();
+    if (applicationUserAgent != null) {
+      builder.append(applicationUserAgent);
+      builder.append(' ');
+    }
+    builder.append("grpc-java-");
+    builder.append(transportName);
+    builder.append('/');
+    builder.append(IMPLEMENTATION_VERSION);
+    return builder.toString();
+  }
+
+  /**
+   * Parse an authority into a URI for retrieving the host and port.
+   */
+  public static URI authorityToUri(String authority) {
+    Preconditions.checkNotNull(authority, "authority");
+    URI uri;
+    try {
+      uri = new URI(null, authority, null, null, null);
+    } catch (URISyntaxException ex) {
+      throw new IllegalArgumentException("Invalid authority: " + authority, ex);
+    }
+    return uri;
+  }
+
+  /**
+   * Verify {@code authority} is valid for use with gRPC. The syntax must be valid and it must not
+   * include userinfo.
+   *
+   * @return the {@code authority} provided
+   */
+  public static String checkAuthority(String authority) {
+    URI uri = authorityToUri(authority);
+    checkArgument(uri.getHost() != null, "No host in authority '%s'", authority);
+    checkArgument(uri.getUserInfo() == null,
+        "Userinfo must not be present on authority: '%s'", authority);
+    return authority;
+  }
+
+  /**
+   * Combine a host and port into an authority string.
+   */
+  public static String authorityFromHostAndPort(String host, int port) {
+    try {
+      return new URI(null, null, host, port, null, null, null).getAuthority();
+    } catch (URISyntaxException ex) {
+      throw new IllegalArgumentException("Invalid host or port: " + host + " " + port, ex);
+    }
+  }
+
+  /**
+   * Shared executor for channels.
+   */
+  public static final Resource<ExecutorService> SHARED_CHANNEL_EXECUTOR =
+      new Resource<ExecutorService>() {
+        private static final String NAME = "grpc-default-executor";
+        @Override
+        public ExecutorService create() {
+          return Executors.newCachedThreadPool(getThreadFactory(NAME + "-%d", true));
+        }
+
+        @Override
+        public void close(ExecutorService instance) {
+          instance.shutdown();
+        }
+
+        @Override
+        public String toString() {
+          return NAME;
+        }
+      };
+
+  /**
+   * Shared single-threaded executor for managing channel timers.
+   */
+  public static final Resource<ScheduledExecutorService> TIMER_SERVICE =
+      new Resource<ScheduledExecutorService>() {
+        @Override
+        public ScheduledExecutorService create() {
+          // We don't use newSingleThreadScheduledExecutor because it doesn't return a
+          // ScheduledThreadPoolExecutor.
+          ScheduledExecutorService service = Executors.newScheduledThreadPool(
+              1,
+              getThreadFactory("grpc-timer-%d", true));
+
+          // If there are long timeouts that are cancelled, they will not actually be removed from
+          // the executors queue.  This forces immediate removal upon cancellation to avoid a
+          // memory leak.  Reflection is used because we cannot use methods added in Java 1.7.  If
+          // the method does not exist, we give up.  Note that the method is not present in 1.6, but
+          // _is_ present in the android standard library.
+          try {
+            Method method = service.getClass().getMethod("setRemoveOnCancelPolicy", boolean.class);
+            method.invoke(service, true);
+          } catch (NoSuchMethodException e) {
+            // no op
+          } catch (RuntimeException e) {
+            throw e;
+          } catch (Exception e) {
+            throw new RuntimeException(e);
+          }
+
+          return service;
+        }
+
+        @Override
+        public void close(ScheduledExecutorService instance) {
+          instance.shutdown();
+        }
+      };
+
+
+  /**
+   * Get a {@link ThreadFactory} suitable for use in the current environment.
+   * @param nameFormat to apply to threads created by the factory.
+   * @param daemon {@code true} if the threads the factory creates are daemon threads, {@code false}
+   *     otherwise.
+   * @return a {@link ThreadFactory}.
+   */
+  public static ThreadFactory getThreadFactory(String nameFormat, boolean daemon) {
+    if (IS_RESTRICTED_APPENGINE) {
+      @SuppressWarnings("BetaApi")
+      ThreadFactory factory = MoreExecutors.platformThreadFactory();
+      return factory;
+    } else {
+      return new ThreadFactoryBuilder()
+          .setDaemon(daemon)
+          .setNameFormat(nameFormat)
+          .build();
+    }
+  }
+
+  /**
+   * The factory of default Stopwatches.
+   */
+  public static final Supplier<Stopwatch> STOPWATCH_SUPPLIER = new Supplier<Stopwatch>() {
+      @Override
+      public Stopwatch get() {
+        return Stopwatch.createUnstarted();
+      }
+    };
+
+  /**
+   * Returns the host via {@link InetSocketAddress#getHostString} if it is possible,
+   * i.e. in jdk >= 7.
+   * Otherwise, return it via {@link InetSocketAddress#getHostName} which may incur a DNS lookup.
+   */
+  public static String getHost(InetSocketAddress addr) {
+    try {
+      Method getHostStringMethod = InetSocketAddress.class.getMethod("getHostString");
+      return (String) getHostStringMethod.invoke(addr);
+    } catch (NoSuchMethodException e) {
+      // noop
+    } catch (IllegalAccessException e) {
+      // noop
+    } catch (InvocationTargetException e) {
+      // noop
+    }
+    return addr.getHostName();
+  }
+
+  /**
+   * Marshals a nanoseconds representation of the timeout to and from a string representation,
+   * consisting of an ASCII decimal representation of a number with at most 8 digits, followed by a
+   * unit:
+   * n = nanoseconds
+   * u = microseconds
+   * m = milliseconds
+   * S = seconds
+   * M = minutes
+   * H = hours
+   *
+   * <p>The representation is greedy with respect to precision. That is, 2 seconds will be
+   * represented as `2000000u`.</p>
+   *
+   * <p>See <a href="https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests">the
+   * request header definition</a></p>
+   */
+  @VisibleForTesting
+  static class TimeoutMarshaller implements Metadata.AsciiMarshaller<Long> {
+
+    @Override
+    public String toAsciiString(Long timeoutNanos) {
+      long cutoff = 100000000;
+      TimeUnit unit = TimeUnit.NANOSECONDS;
+      if (timeoutNanos < 0) {
+        throw new IllegalArgumentException("Timeout too small");
+      } else if (timeoutNanos < cutoff) {
+        return timeoutNanos + "n";
+      } else if (timeoutNanos < cutoff * 1000L) {
+        return unit.toMicros(timeoutNanos) + "u";
+      } else if (timeoutNanos < cutoff * 1000L * 1000L) {
+        return unit.toMillis(timeoutNanos) + "m";
+      } else if (timeoutNanos < cutoff * 1000L * 1000L * 1000L) {
+        return unit.toSeconds(timeoutNanos) + "S";
+      } else if (timeoutNanos < cutoff * 1000L * 1000L * 1000L * 60L) {
+        return unit.toMinutes(timeoutNanos) + "M";
+      } else {
+        return unit.toHours(timeoutNanos) + "H";
+      }
+    }
+
+    @Override
+    public Long parseAsciiString(String serialized) {
+      checkArgument(serialized.length() > 0, "empty timeout");
+      checkArgument(serialized.length() <= 9, "bad timeout format");
+      long value = Long.parseLong(serialized.substring(0, serialized.length() - 1));
+      char unit = serialized.charAt(serialized.length() - 1);
+      switch (unit) {
+        case 'n':
+          return value;
+        case 'u':
+          return TimeUnit.MICROSECONDS.toNanos(value);
+        case 'm':
+          return TimeUnit.MILLISECONDS.toNanos(value);
+        case 'S':
+          return TimeUnit.SECONDS.toNanos(value);
+        case 'M':
+          return TimeUnit.MINUTES.toNanos(value);
+        case 'H':
+          return TimeUnit.HOURS.toNanos(value);
+        default:
+          throw new IllegalArgumentException(String.format("Invalid timeout unit: %s", unit));
+      }
+    }
+  }
+
+  /**
+   * Returns a transport out of a PickResult, or {@code null} if the result is "buffer".
+   */
+  @Nullable
+  static ClientTransport getTransportFromPickResult(PickResult result, boolean isWaitForReady) {
+    final ClientTransport transport;
+    Subchannel subchannel = result.getSubchannel();
+    if (subchannel != null) {
+      transport = ((AbstractSubchannel) subchannel).obtainActiveTransport();
+    } else {
+      transport = null;
+    }
+    if (transport != null) {
+      final ClientStreamTracer.Factory streamTracerFactory = result.getStreamTracerFactory();
+      if (streamTracerFactory == null) {
+        return transport;
+      }
+      return new ClientTransport() {
+        @Override
+        public ClientStream newStream(
+            MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions) {
+          return transport.newStream(
+              method, headers, callOptions.withStreamTracerFactory(streamTracerFactory));
+        }
+
+        @Override
+        public void ping(PingCallback callback, Executor executor) {
+          transport.ping(callback, executor);
+        }
+
+        @Override
+        public InternalLogId getLogId() {
+          return transport.getLogId();
+        }
+
+        @Override
+        public ListenableFuture<SocketStats> getStats() {
+          return transport.getStats();
+        }
+      };
+    }
+    if (!result.getStatus().isOk()) {
+      if (result.isDrop()) {
+        return new FailingClientTransport(result.getStatus(), RpcProgress.DROPPED);
+      }
+      if (!isWaitForReady) {
+        return new FailingClientTransport(result.getStatus(), RpcProgress.PROCESSED);
+      }
+    }
+    return null;
+  }
+
+  /** Quietly closes all messages in MessageProducer. */
+  static void closeQuietly(MessageProducer producer) {
+    InputStream message;
+    while ((message = producer.next()) != null) {
+      closeQuietly(message);
+    }
+  }
+
+  /**
+   * Closes an InputStream, ignoring IOExceptions.
+   * This method exists because Guava's {@code Closeables.closeQuietly()} is beta.
+   */
+  public static void closeQuietly(@Nullable InputStream message) {
+    if (message == null) {
+      return;
+    }
+    try {
+      message.close();
+    } catch (IOException ioException) {
+      // do nothing except log
+      log.log(Level.WARNING, "exception caught in closeQuietly", ioException);
+    }
+  }
+
+  /**
+   * Checks whether the given item exists in the iterable.  This is copied from Guava Collect's
+   * {@code Iterables.contains()} because Guava Collect is not Android-friendly thus core can't
+   * depend on it.
+   */
+  static <T> boolean iterableContains(Iterable<T> iterable, T item) {
+    if (iterable instanceof Collection) {
+      Collection<?> collection = (Collection<?>) iterable;
+      try {
+        return collection.contains(item);
+      } catch (NullPointerException e) {
+        return false;
+      } catch (ClassCastException e) {
+        return false;
+      }
+    }
+    for (T i : iterable) {
+      if (Objects.equal(i, item)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private GrpcUtil() {}
+}
diff --git a/core/src/main/java/io/grpc/internal/GzipInflatingBuffer.java b/core/src/main/java/io/grpc/internal/GzipInflatingBuffer.java
new file mode 100644
index 0000000..9994597
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/GzipInflatingBuffer.java
@@ -0,0 +1,474 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.Closeable;
+import java.util.zip.CRC32;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+import java.util.zip.ZipException;
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * Processes gzip streams, delegating to {@link Inflater} to perform on-demand inflation of the
+ * deflated blocks. Like {@link java.util.zip.GZIPInputStream}, this handles concatenated gzip
+ * streams. Unlike {@link java.util.zip.GZIPInputStream}, this allows for incremental processing of
+ * gzip streams, allowing data to be inflated as it arrives over the wire.
+ *
+ * <p>This also frees the inflate context when the end of a gzip stream is reached without another
+ * concatenated stream available to inflate.
+ */
+@NotThreadSafe
+class GzipInflatingBuffer implements Closeable {
+
+  private static final int INFLATE_BUFFER_SIZE = 512;
+  private static final int UNSIGNED_SHORT_SIZE = 2;
+
+  private static final int GZIP_MAGIC = 0x8b1f;
+
+  private static final int GZIP_HEADER_MIN_SIZE = 10;
+  private static final int GZIP_TRAILER_SIZE = 8;
+
+  private static final int HEADER_CRC_FLAG = 2;
+  private static final int HEADER_EXTRA_FLAG = 4;
+  private static final int HEADER_NAME_FLAG = 8;
+  private static final int HEADER_COMMENT_FLAG = 16;
+
+  /**
+   * Reads gzip header and trailer bytes from the inflater's buffer (if bytes beyond the inflate
+   * block were given to the inflater) and then from {@code gzippedData}, and handles updating the
+   * CRC and the count of gzipped bytes consumed.
+   */
+  private class GzipMetadataReader {
+
+    /**
+     * Returns the next unsigned byte, adding it the CRC and incrementing {@code bytesConsumed}.
+     *
+     * <p>It is the responsibility of the caller to verify and reset the CRC as needed, as well as
+     * caching the current CRC value when necessary before invoking this method.
+     */
+    private int readUnsignedByte() {
+      int bytesRemainingInInflaterInput = inflaterInputEnd - inflaterInputStart;
+      int b;
+      if (bytesRemainingInInflaterInput > 0) {
+        b = inflaterInput[inflaterInputStart] & 0xFF;
+        inflaterInputStart += 1;
+      } else {
+        b = gzippedData.readUnsignedByte();
+      }
+      crc.update(b);
+      bytesConsumed += 1;
+      return b;
+    }
+
+    /**
+     * Skips {@code length} bytes, adding them to the CRC and adding {@code length} to {@code
+     * bytesConsumed}.
+     *
+     * <p>It is the responsibility of the caller to verify and reset the CRC as needed, as well as
+     * caching the current CRC value when necessary before invoking this method.
+     */
+    private void skipBytes(int length) {
+      int bytesToSkip = length;
+      int bytesRemainingInInflaterInput = inflaterInputEnd - inflaterInputStart;
+
+      if (bytesRemainingInInflaterInput > 0) {
+        int bytesToGetFromInflaterInput = Math.min(bytesRemainingInInflaterInput, bytesToSkip);
+        crc.update(inflaterInput, inflaterInputStart, bytesToGetFromInflaterInput);
+        inflaterInputStart += bytesToGetFromInflaterInput;
+        bytesToSkip -= bytesToGetFromInflaterInput;
+      }
+
+      if (bytesToSkip > 0) {
+        byte[] buf = new byte[512];
+        int total = 0;
+        while (total < bytesToSkip) {
+          int toRead = Math.min(bytesToSkip - total, buf.length);
+          gzippedData.readBytes(buf, 0, toRead);
+          crc.update(buf, 0, toRead);
+          total += toRead;
+        }
+      }
+
+      bytesConsumed += length;
+    }
+
+    private int readableBytes() {
+      return (inflaterInputEnd - inflaterInputStart) + gzippedData.readableBytes();
+    }
+
+    /** Skip over a zero-terminated byte sequence. Returns true when the zero byte is read. */
+    private boolean readBytesUntilZero() {
+      while (readableBytes() > 0) {
+        if (readUnsignedByte() == 0) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    /** Reads unsigned short in Little-Endian byte order. */
+    private int readUnsignedShort() {
+      return readUnsignedByte() | (readUnsignedByte() << 8);
+    }
+
+    /** Reads unsigned integer in Little-Endian byte order. */
+    private long readUnsignedInt() {
+      long s = readUnsignedShort();
+      return ((long) readUnsignedShort() << 16) | s;
+    }
+  }
+
+  private enum State {
+    HEADER,
+    HEADER_EXTRA_LEN,
+    HEADER_EXTRA,
+    HEADER_NAME,
+    HEADER_COMMENT,
+    HEADER_CRC,
+    INITIALIZE_INFLATER,
+    INFLATING,
+    INFLATER_NEEDS_INPUT,
+    TRAILER
+  }
+
+  /**
+   * This buffer holds all input gzipped data, consisting of blocks of deflated data and the
+   * surrounding gzip headers and trailers. All access to the Gzip headers and trailers must be made
+   * via {@link GzipMetadataReader}.
+   */
+  private final CompositeReadableBuffer gzippedData = new CompositeReadableBuffer();
+
+  private final CRC32 crc = new CRC32();
+
+  private final GzipMetadataReader gzipMetadataReader = new GzipMetadataReader();
+  private final byte[] inflaterInput = new byte[INFLATE_BUFFER_SIZE];
+  private int inflaterInputStart;
+  private int inflaterInputEnd;
+  private Inflater inflater;
+  private State state = State.HEADER;
+  private boolean closed = false;
+
+  /** Extra state variables for parsing gzip header flags. */
+  private int gzipHeaderFlag;
+  private int headerExtraToRead;
+
+  /* Number of inflated bytes per gzip stream, used to validate the gzip trailer. */
+  private long expectedGzipTrailerIsize;
+
+  /**
+   * Tracks gzipped bytes (including gzip metadata and deflated blocks) consumed during {@link
+   * #inflateBytes} calls.
+   */
+  private int bytesConsumed = 0;
+
+  /** Tracks deflated bytes (excluding gzip metadata) consumed by the inflater. */
+  private int deflatedBytesConsumed = 0;
+
+  private boolean isStalled = true;
+
+  /**
+   * Returns true when more bytes must be added via {@link #addGzippedBytes} to enable additional
+   * calls to {@link #inflateBytes} to make progress.
+   */
+  boolean isStalled() {
+    checkState(!closed, "GzipInflatingBuffer is closed");
+    return isStalled;
+  }
+
+  /**
+   * Returns true when there is gzippedData that has not been input to the inflater or the inflater
+   * has not consumed all of its input, or all data has been consumed but we are at not at the
+   * boundary between gzip streams.
+   */
+  boolean hasPartialData() {
+    checkState(!closed, "GzipInflatingBuffer is closed");
+    return gzipMetadataReader.readableBytes() != 0 || state != State.HEADER;
+  }
+
+  /**
+   * Adds more gzipped data, which will be consumed only when needed to fulfill requests made via
+   * {@link #inflateBytes}.
+   */
+  void addGzippedBytes(ReadableBuffer buffer) {
+    checkState(!closed, "GzipInflatingBuffer is closed");
+    gzippedData.addBuffer(buffer);
+    isStalled = false;
+  }
+
+  @Override
+  public void close() {
+    if (!closed) {
+      closed = true;
+      gzippedData.close();
+      if (inflater != null) {
+        inflater.end();
+        inflater = null;
+      }
+    }
+  }
+
+  /**
+   * Reports bytes consumed by calls to {@link #inflateBytes} since the last invocation of this
+   * method, then resets the count to zero.
+   */
+  int getAndResetBytesConsumed() {
+    int savedBytesConsumed = bytesConsumed;
+    bytesConsumed = 0;
+    return savedBytesConsumed;
+  }
+
+  /**
+   * Reports bytes consumed by the inflater since the last invocation of this method, then resets
+   * the count to zero.
+   */
+  int getAndResetDeflatedBytesConsumed() {
+    int savedDeflatedBytesConsumed = deflatedBytesConsumed;
+    deflatedBytesConsumed = 0;
+    return savedDeflatedBytesConsumed;
+  }
+
+  /**
+   * Attempts to inflate {@code length} bytes of data into {@code b}.
+   *
+   * <p>Any gzipped bytes consumed by this method will be added to the counter returned by {@link
+   * #getAndResetBytesConsumed()}. This method may consume gzipped bytes without writing any data to
+   * {@code b}, and may also write data to {@code b} without consuming additional gzipped bytes (if
+   * the inflater on an earlier call consumed the bytes necessary to produce output).
+   *
+   * @param b the destination array to receive the bytes.
+   * @param offset the starting offset in the destination array.
+   * @param length the number of bytes to be copied.
+   * @throws IndexOutOfBoundsException if {@code b} is too small to hold the requested bytes.
+   */
+  int inflateBytes(byte[] b, int offset, int length) throws DataFormatException, ZipException {
+    checkState(!closed, "GzipInflatingBuffer is closed");
+
+    int bytesRead = 0;
+    int missingBytes;
+    boolean madeProgress = true;
+    while (madeProgress && (missingBytes = length - bytesRead) > 0) {
+      switch (state) {
+        case HEADER:
+          madeProgress = processHeader();
+          break;
+        case HEADER_EXTRA_LEN:
+          madeProgress = processHeaderExtraLen();
+          break;
+        case HEADER_EXTRA:
+          madeProgress = processHeaderExtra();
+          break;
+        case HEADER_NAME:
+          madeProgress = processHeaderName();
+          break;
+        case HEADER_COMMENT:
+          madeProgress = processHeaderComment();
+          break;
+        case HEADER_CRC:
+          madeProgress = processHeaderCrc();
+          break;
+        case INITIALIZE_INFLATER:
+          madeProgress = initializeInflater();
+          break;
+        case INFLATING:
+          bytesRead += inflate(b, offset + bytesRead, missingBytes);
+          if (state == State.TRAILER) {
+            // Eagerly process trailer, if available, to validate CRC.
+            madeProgress = processTrailer();
+          } else {
+            // Continue in INFLATING until we have the required bytes or we transition to
+            // INFLATER_NEEDS_INPUT
+            madeProgress = true;
+          }
+          break;
+        case INFLATER_NEEDS_INPUT:
+          madeProgress = fill();
+          break;
+        case TRAILER:
+          madeProgress = processTrailer();
+          break;
+        default:
+          throw new AssertionError("Invalid state: " + state);
+      }
+    }
+    // If we finished a gzip block, check if we have enough bytes to read another header
+    isStalled =
+        !madeProgress
+            || (state == State.HEADER && gzipMetadataReader.readableBytes() < GZIP_HEADER_MIN_SIZE);
+
+    return bytesRead;
+  }
+
+  private boolean processHeader() throws ZipException {
+    if (gzipMetadataReader.readableBytes() < GZIP_HEADER_MIN_SIZE) {
+      return false;
+    }
+    if (gzipMetadataReader.readUnsignedShort() != GZIP_MAGIC) {
+      throw new ZipException("Not in GZIP format");
+    }
+    if (gzipMetadataReader.readUnsignedByte() != 8) {
+      throw new ZipException("Unsupported compression method");
+    }
+    gzipHeaderFlag = gzipMetadataReader.readUnsignedByte();
+    gzipMetadataReader.skipBytes(6 /* remaining header bytes */);
+    state = State.HEADER_EXTRA_LEN;
+    return true;
+  }
+
+  private boolean processHeaderExtraLen() {
+    if ((gzipHeaderFlag & HEADER_EXTRA_FLAG) != HEADER_EXTRA_FLAG) {
+      state = State.HEADER_NAME;
+      return true;
+    }
+    if (gzipMetadataReader.readableBytes() < UNSIGNED_SHORT_SIZE) {
+      return false;
+    }
+    headerExtraToRead = gzipMetadataReader.readUnsignedShort();
+    state = State.HEADER_EXTRA;
+    return true;
+  }
+
+  private boolean processHeaderExtra() {
+    if (gzipMetadataReader.readableBytes() < headerExtraToRead) {
+      return false;
+    }
+    gzipMetadataReader.skipBytes(headerExtraToRead);
+    state = State.HEADER_NAME;
+    return true;
+  }
+
+  private boolean processHeaderName() {
+    if ((gzipHeaderFlag & HEADER_NAME_FLAG) != HEADER_NAME_FLAG) {
+      state = State.HEADER_COMMENT;
+      return true;
+    }
+    if (!gzipMetadataReader.readBytesUntilZero()) {
+      return false;
+    }
+    state = State.HEADER_COMMENT;
+    return true;
+  }
+
+  private boolean processHeaderComment() {
+    if ((gzipHeaderFlag & HEADER_COMMENT_FLAG) != HEADER_COMMENT_FLAG) {
+      state = State.HEADER_CRC;
+      return true;
+    }
+    if (!gzipMetadataReader.readBytesUntilZero()) {
+      return false;
+    }
+    state = State.HEADER_CRC;
+    return true;
+  }
+
+  private boolean processHeaderCrc() throws ZipException {
+    if ((gzipHeaderFlag & HEADER_CRC_FLAG) != HEADER_CRC_FLAG) {
+      state = State.INITIALIZE_INFLATER;
+      return true;
+    }
+    if (gzipMetadataReader.readableBytes() < UNSIGNED_SHORT_SIZE) {
+      return false;
+    }
+    int desiredCrc16 = (int) crc.getValue() & 0xffff;
+    if (desiredCrc16 != gzipMetadataReader.readUnsignedShort()) {
+      throw new ZipException("Corrupt GZIP header");
+    }
+    state = State.INITIALIZE_INFLATER;
+    return true;
+  }
+
+  private boolean initializeInflater() {
+    if (inflater == null) {
+      inflater = new Inflater(true);
+    } else {
+      inflater.reset();
+    }
+    crc.reset();
+    int bytesRemainingInInflaterInput = inflaterInputEnd - inflaterInputStart;
+    if (bytesRemainingInInflaterInput > 0) {
+      inflater.setInput(inflaterInput, inflaterInputStart, bytesRemainingInInflaterInput);
+      state = State.INFLATING;
+    } else {
+      state = State.INFLATER_NEEDS_INPUT;
+    }
+    return true;
+  }
+
+  private int inflate(byte[] b, int off, int len) throws DataFormatException, ZipException {
+    checkState(inflater != null, "inflater is null");
+
+    try {
+      int inflaterTotalIn = inflater.getTotalIn();
+      int n = inflater.inflate(b, off, len);
+      int bytesConsumedDelta = inflater.getTotalIn() - inflaterTotalIn;
+      bytesConsumed += bytesConsumedDelta;
+      deflatedBytesConsumed += bytesConsumedDelta;
+      inflaterInputStart += bytesConsumedDelta;
+      crc.update(b, off, n);
+
+      if (inflater.finished()) {
+        // Save bytes written to check against the trailer ISIZE
+        expectedGzipTrailerIsize = (inflater.getBytesWritten() & 0xffffffffL);
+
+        state = State.TRAILER;
+      } else if (inflater.needsInput()) {
+        state = State.INFLATER_NEEDS_INPUT;
+      }
+
+      return n;
+    } catch (DataFormatException e) {
+      // Wrap the exception so tests can check for a specific prefix
+      throw new DataFormatException("Inflater data format exception: " + e.getMessage());
+    }
+  }
+
+  private boolean fill() {
+    checkState(inflater != null, "inflater is null");
+    checkState(inflaterInputStart == inflaterInputEnd, "inflaterInput has unconsumed bytes");
+    int bytesToAdd = Math.min(gzippedData.readableBytes(), INFLATE_BUFFER_SIZE);
+    if (bytesToAdd == 0) {
+      return false;
+    }
+    inflaterInputStart = 0;
+    inflaterInputEnd = bytesToAdd;
+    gzippedData.readBytes(inflaterInput, inflaterInputStart, bytesToAdd);
+    inflater.setInput(inflaterInput, inflaterInputStart, bytesToAdd);
+    state = State.INFLATING;
+    return true;
+  }
+
+  private boolean processTrailer() throws ZipException {
+    if (inflater != null
+        && gzipMetadataReader.readableBytes() <= GZIP_HEADER_MIN_SIZE + GZIP_TRAILER_SIZE) {
+      // We don't have enough bytes to begin inflating a concatenated gzip stream, drop context
+      inflater.end();
+      inflater = null;
+    }
+    if (gzipMetadataReader.readableBytes() < GZIP_TRAILER_SIZE) {
+      return false;
+    }
+    if (crc.getValue() != gzipMetadataReader.readUnsignedInt()
+        || expectedGzipTrailerIsize != gzipMetadataReader.readUnsignedInt()) {
+      throw new ZipException("Corrupt GZIP trailer");
+    }
+    crc.reset();
+    state = State.HEADER;
+    return true;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/HedgingPolicy.java b/core/src/main/java/io/grpc/internal/HedgingPolicy.java
new file mode 100644
index 0000000..6f70350
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/HedgingPolicy.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+import io.grpc.Status.Code;
+import java.util.Collections;
+import java.util.Set;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Hedging policy data object.
+ */
+@Immutable
+final class HedgingPolicy {
+  final int maxAttempts;
+  final long hedgingDelayNanos;
+  final Set<Code> nonFatalStatusCodes;
+
+  /** No hedging. */
+  static final HedgingPolicy DEFAULT =
+      new HedgingPolicy(1, 0, Collections.<Code>emptySet());
+
+  /**
+   * The caller is supposed to have validated the arguments and handled throwing exception or
+   * logging warnings already, so we avoid repeating args check here.
+   */
+  HedgingPolicy(int maxAttempts, long hedgingDelayNanos, Set<Code> nonFatalStatusCodes) {
+    this.maxAttempts = maxAttempts;
+    this.hedgingDelayNanos = hedgingDelayNanos;
+    this.nonFatalStatusCodes = ImmutableSet.copyOf(nonFatalStatusCodes);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) {
+      return true;
+    }
+    if (other == null || getClass() != other.getClass()) {
+      return false;
+    }
+    HedgingPolicy that = (HedgingPolicy) other;
+    return maxAttempts == that.maxAttempts
+        && hedgingDelayNanos == that.hedgingDelayNanos
+        && Objects.equal(nonFatalStatusCodes, that.nonFatalStatusCodes);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(maxAttempts, hedgingDelayNanos, nonFatalStatusCodes);
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("maxAttempts", maxAttempts)
+        .add("hedgingDelayNanos", hedgingDelayNanos)
+        .add("nonFatalStatusCodes", nonFatalStatusCodes)
+        .toString();
+  }
+
+  /**
+   * Provides the most suitable hedging policy for a call.
+   */
+  interface Provider {
+
+    /**
+     * This method is used no more than once for each call. Never returns null.
+     */
+    HedgingPolicy get();
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/Http2ClientStreamTransportState.java b/core/src/main/java/io/grpc/internal/Http2ClientStreamTransportState.java
new file mode 100644
index 0000000..da9f7bc
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/Http2ClientStreamTransportState.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Preconditions;
+import io.grpc.InternalMetadata;
+import io.grpc.InternalStatus;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import java.nio.charset.Charset;
+import javax.annotation.Nullable;
+
+/**
+ * Base implementation for client streams using HTTP2 as the transport.
+ */
+public abstract class Http2ClientStreamTransportState extends AbstractClientStream.TransportState {
+
+  /**
+   * Metadata marshaller for HTTP status lines.
+   */
+  private static final InternalMetadata.TrustedAsciiMarshaller<Integer> HTTP_STATUS_MARSHALLER =
+      new InternalMetadata.TrustedAsciiMarshaller<Integer>() {
+        @Override
+        public byte[] toAsciiString(Integer value) {
+          throw new UnsupportedOperationException();
+        }
+
+        /**
+         * RFC 7231 says status codes are 3 digits long.
+         *
+         * @see <a href="https://tools.ietf.org/html/rfc7231#section-6">RFC 7231</a>
+         */
+        @Override
+        public Integer parseAsciiString(byte[] serialized) {
+          if (serialized.length >= 3) {
+            return (serialized[0] - '0') * 100 + (serialized[1] - '0') * 10 + (serialized[2] - '0');
+          }
+          throw new NumberFormatException(
+              "Malformed status code " + new String(serialized, InternalMetadata.US_ASCII));
+        }
+      };
+
+  private static final Metadata.Key<Integer> HTTP2_STATUS = InternalMetadata.keyOf(":status",
+      HTTP_STATUS_MARSHALLER);
+
+  /** When non-{@code null}, {@link #transportErrorMetadata} must also be non-{@code null}. */
+  private Status transportError;
+  private Metadata transportErrorMetadata;
+  private Charset errorCharset = Charsets.UTF_8;
+  private boolean headersReceived;
+
+  protected Http2ClientStreamTransportState(
+      int maxMessageSize,
+      StatsTraceContext statsTraceCtx,
+      TransportTracer transportTracer) {
+    super(maxMessageSize, statsTraceCtx, transportTracer);
+  }
+
+  /**
+   * Called to process a failure in HTTP/2 processing. It should notify the transport to cancel the
+   * stream and call {@code transportReportStatus()}.
+   */
+  protected abstract void http2ProcessingFailed(
+      Status status, boolean stopDelivery, Metadata trailers);
+
+  /**
+   * Called by subclasses whenever {@code Headers} are received from the transport.
+   *
+   * @param headers the received headers
+   */
+  protected void transportHeadersReceived(Metadata headers) {
+    Preconditions.checkNotNull(headers, "headers");
+    if (transportError != null) {
+      // Already received a transport error so just augment it. Something is really, really strange.
+      transportError = transportError.augmentDescription("headers: " + headers);
+      return;
+    }
+    try {
+      if (headersReceived) {
+        transportError = Status.INTERNAL.withDescription("Received headers twice");
+        return;
+      }
+      Integer httpStatus = headers.get(HTTP2_STATUS);
+      if (httpStatus != null && httpStatus >= 100 && httpStatus < 200) {
+        // Ignore the headers. See RFC 7540 §8.1
+        return;
+      }
+      headersReceived = true;
+
+      transportError = validateInitialMetadata(headers);
+      if (transportError != null) {
+        return;
+      }
+
+      stripTransportDetails(headers);
+      inboundHeadersReceived(headers);
+    } finally {
+      if (transportError != null) {
+        // Note we don't immediately report the transport error, instead we wait for more data on
+        // the stream so we can accumulate more detail into the error before reporting it.
+        transportError = transportError.augmentDescription("headers: " + headers);
+        transportErrorMetadata = headers;
+        errorCharset = extractCharset(headers);
+      }
+    }
+  }
+
+  /**
+   * Called by subclasses whenever a data frame is received from the transport.
+   *
+   * @param frame the received data frame
+   * @param endOfStream {@code true} if there will be no more data received for this stream
+   */
+  protected void transportDataReceived(ReadableBuffer frame, boolean endOfStream) {
+    if (transportError != null) {
+      // We've already detected a transport error and now we're just accumulating more detail
+      // for it.
+      transportError = transportError.augmentDescription("DATA-----------------------------\n"
+          + ReadableBuffers.readAsString(frame, errorCharset));
+      frame.close();
+      if (transportError.getDescription().length() > 1000 || endOfStream) {
+        http2ProcessingFailed(transportError, false, transportErrorMetadata);
+      }
+    } else {
+      if (!headersReceived) {
+        http2ProcessingFailed(
+            Status.INTERNAL.withDescription("headers not received before payload"),
+            false,
+            new Metadata());
+        return;
+      }
+      inboundDataReceived(frame);
+      if (endOfStream) {
+        // This is a protocol violation as we expect to receive trailers.
+        transportError =
+            Status.INTERNAL.withDescription("Received unexpected EOS on DATA frame from server.");
+        transportErrorMetadata = new Metadata();
+        transportReportStatus(transportError, false, transportErrorMetadata);
+      }
+    }
+  }
+
+  /**
+   * Called by subclasses for the terminal trailer metadata on a stream.
+   *
+   * @param trailers the received terminal trailer metadata
+   */
+  protected void transportTrailersReceived(Metadata trailers) {
+    Preconditions.checkNotNull(trailers, "trailers");
+    if (transportError == null && !headersReceived) {
+      transportError = validateInitialMetadata(trailers);
+      if (transportError != null) {
+        transportErrorMetadata = trailers;
+      }
+    }
+    if (transportError != null) {
+      transportError = transportError.augmentDescription("trailers: " + trailers);
+      http2ProcessingFailed(transportError, false, transportErrorMetadata);
+    } else {
+      Status status = statusFromTrailers(trailers);
+      stripTransportDetails(trailers);
+      inboundTrailersReceived(trailers, status);
+    }
+  }
+
+  /**
+   * Extract the response status from trailers.
+   */
+  private Status statusFromTrailers(Metadata trailers) {
+    Status status = trailers.get(InternalStatus.CODE_KEY);
+    if (status != null) {
+      return status.withDescription(trailers.get(InternalStatus.MESSAGE_KEY));
+    }
+    // No status; something is broken. Try to provide a resonanable error.
+    if (headersReceived) {
+      return Status.UNKNOWN.withDescription("missing GRPC status in response");
+    }
+    Integer httpStatus = trailers.get(HTTP2_STATUS);
+    if (httpStatus != null) {
+      status = GrpcUtil.httpStatusToGrpcStatus(httpStatus);
+    } else {
+      status = Status.INTERNAL.withDescription("missing HTTP status code");
+    }
+    return status.augmentDescription(
+        "missing GRPC status, inferred error from HTTP status code");
+  }
+
+  /**
+   * Inspect initial headers to make sure they conform to HTTP and gRPC, returning a {@code Status}
+   * on failure.
+   *
+   * @return status with description of failure, or {@code null} when valid
+   */
+  @Nullable
+  private Status validateInitialMetadata(Metadata headers) {
+    Integer httpStatus = headers.get(HTTP2_STATUS);
+    if (httpStatus == null) {
+      return Status.INTERNAL.withDescription("Missing HTTP status code");
+    }
+    String contentType = headers.get(GrpcUtil.CONTENT_TYPE_KEY);
+    if (!GrpcUtil.isGrpcContentType(contentType)) {
+      return GrpcUtil.httpStatusToGrpcStatus(httpStatus)
+          .augmentDescription("invalid content-type: " + contentType);
+    }
+    return null;
+  }
+
+  /**
+   * Inspect the raw metadata and figure out what charset is being used.
+   */
+  private static Charset extractCharset(Metadata headers) {
+    String contentType = headers.get(GrpcUtil.CONTENT_TYPE_KEY);
+    if (contentType != null) {
+      String[] split = contentType.split("charset=", 2);
+      try {
+        return Charset.forName(split[split.length - 1].trim());
+      } catch (Exception t) {
+        // Ignore and assume UTF-8
+      }
+    }
+    return Charsets.UTF_8;
+  }
+
+  /**
+   * Strip HTTP transport implementation details so they don't leak via metadata into
+   * the application layer.
+   */
+  private static void stripTransportDetails(Metadata metadata) {
+    metadata.discardAll(HTTP2_STATUS);
+    metadata.discardAll(InternalStatus.CODE_KEY);
+    metadata.discardAll(InternalStatus.MESSAGE_KEY);
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/Http2Ping.java b/core/src/main/java/io/grpc/internal/Http2Ping.java
new file mode 100644
index 0000000..1259d85
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/Http2Ping.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.Stopwatch;
+import io.grpc.internal.ClientTransport.PingCallback;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Represents an outstanding PING operation on an HTTP/2 channel. This can be used by HTTP/2-based
+ * transports to implement {@link ClientTransport#ping}.
+ *
+ * <p>A typical transport need only support one outstanding ping at a time. So, if a ping is
+ * requested while an operation is already in progress, the given callback is notified when the
+ * existing operation completes.
+ */
+public class Http2Ping {
+  private static final Logger log = Logger.getLogger(Http2Ping.class.getName());
+
+  /**
+   * The PING frame includes 8 octets of payload data, e.g. 64 bits.
+   */
+  private final long data;
+
+  /**
+   * Used to measure elapsed time.
+   */
+  private final Stopwatch stopwatch;
+
+  /**
+   * The registered callbacks and the executor used to invoke them.
+   */
+  @GuardedBy("this") private Map<PingCallback, Executor> callbacks
+      = new LinkedHashMap<PingCallback, Executor>();
+
+  /**
+   * False until the operation completes, either successfully (other side sent acknowledgement) or
+   * unsuccessfully.
+   */
+  @GuardedBy("this") private boolean completed;
+
+  /**
+   * If non-null, indicates the ping failed.
+   */
+  @GuardedBy("this") private Throwable failureCause;
+
+  /**
+   * The round-trip time for the ping, in nanoseconds. This value is only meaningful when
+   * {@link #completed} is true and {@link #failureCause} is null.
+   */
+  @GuardedBy("this") private long roundTripTimeNanos;
+
+  /**
+   * Creates a new ping operation. The caller is responsible for sending a ping on an HTTP/2 channel
+   * using the given payload. The caller is also responsible for starting the stopwatch when the
+   * PING frame is sent.
+   *
+   * @param data the ping payload
+   * @param stopwatch a stopwatch for measuring round-trip time
+   */
+  public Http2Ping(long data, Stopwatch stopwatch) {
+    this.data = data;
+    this.stopwatch = stopwatch;
+  }
+
+  /**
+   * Registers a callback that is invoked when the ping operation completes. If this ping operation
+   * is already completed, the callback is invoked immediately.
+   *
+   * @param callback the callback to invoke
+   * @param executor the executor to use
+   */
+  public void addCallback(final ClientTransport.PingCallback callback, Executor executor) {
+    Runnable runnable;
+    synchronized (this) {
+      if (!completed) {
+        callbacks.put(callback, executor);
+        return;
+      }
+      // otherwise, invoke callback immediately (but not while holding lock)
+      runnable = this.failureCause != null ? asRunnable(callback, failureCause)
+                                           : asRunnable(callback, roundTripTimeNanos);
+    }
+    doExecute(executor, runnable);
+  }
+
+  /**
+   * Returns the expected ping payload for this outstanding operation.
+   *
+   * @return the expected payload for this outstanding ping
+   */
+  public long payload() {
+    return data;
+  }
+
+  /**
+   * Completes this operation successfully. The stopwatch given during construction is used to
+   * measure the elapsed time. Registered callbacks are invoked and provided the measured elapsed
+   * time.
+   *
+   * @return true if the operation is marked as complete; false if it was already complete
+   */
+  public boolean complete() {
+    Map<ClientTransport.PingCallback, Executor> callbacks;
+    long roundTripTimeNanos;
+    synchronized (this) {
+      if (completed) {
+        return false;
+      }
+      completed = true;
+      roundTripTimeNanos = this.roundTripTimeNanos = stopwatch.elapsed(TimeUnit.NANOSECONDS);
+      callbacks = this.callbacks;
+      this.callbacks = null;
+    }
+    for (Map.Entry<ClientTransport.PingCallback, Executor> entry : callbacks.entrySet()) {
+      doExecute(entry.getValue(), asRunnable(entry.getKey(), roundTripTimeNanos));
+    }
+    return true;
+  }
+
+  /**
+   * Completes this operation exceptionally. Registered callbacks are invoked and provided the
+   * given throwable as the cause of failure.
+   *
+   * @param failureCause the cause of failure
+   */
+  public void failed(Throwable failureCause) {
+    Map<ClientTransport.PingCallback, Executor> callbacks;
+    synchronized (this) {
+      if (completed) {
+        return;
+      }
+      completed = true;
+      this.failureCause = failureCause;
+      callbacks = this.callbacks;
+      this.callbacks = null;
+    }
+    for (Map.Entry<ClientTransport.PingCallback, Executor> entry : callbacks.entrySet()) {
+      notifyFailed(entry.getKey(), entry.getValue(), failureCause);
+    }
+  }
+
+  /**
+   * Notifies the given callback that the ping operation failed.
+   *
+   * @param callback the callback
+   * @param executor the executor used to invoke the callback
+   * @param cause the cause of failure
+   */
+  public static void notifyFailed(PingCallback callback, Executor executor, Throwable cause) {
+    doExecute(executor, asRunnable(callback, cause));
+  }
+
+  /**
+   * Executes the given runnable. This prevents exceptions from propagating so that an exception
+   * thrown by one callback won't prevent subsequent callbacks from being executed.
+   */
+  private static void doExecute(Executor executor, Runnable runnable) {
+    try {
+      executor.execute(runnable);
+    } catch (Throwable th) {
+      log.log(Level.SEVERE, "Failed to execute PingCallback", th);
+    }
+  }
+
+  /**
+   * Returns a runnable that, when run, invokes the given callback, providing the given round-trip
+   * duration.
+   */
+  private static Runnable asRunnable(final ClientTransport.PingCallback callback,
+                                     final long roundTripTimeNanos) {
+    return new Runnable() {
+      @Override
+      public void run() {
+        callback.onSuccess(roundTripTimeNanos);
+      }
+    };
+
+  }
+
+  /**
+   * Returns a runnable that, when run, invokes the given callback, providing the given cause of
+   * failure.
+   */
+  private static Runnable asRunnable(final ClientTransport.PingCallback callback,
+                                     final Throwable failureCause) {
+    return new Runnable() {
+      @Override
+      public void run() {
+        callback.onFailure(failureCause);
+      }
+    };
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/InUseStateAggregator.java b/core/src/main/java/io/grpc/internal/InUseStateAggregator.java
new file mode 100644
index 0000000..4c9bba1
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/InUseStateAggregator.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import java.util.HashSet;
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * Aggregates the in-use state of a set of objects.
+ */
+@NotThreadSafe
+abstract class InUseStateAggregator<T> {
+
+  private final HashSet<T> inUseObjects = new HashSet<T>();
+
+  /**
+   * Update the in-use state of an object. Initially no object is in use.
+   *
+   * <p>This may call into {@link #handleInUse} or {@link #handleNotInUse} when appropriate.
+   */
+  final void updateObjectInUse(T object, boolean inUse) {
+    int origSize = inUseObjects.size();
+    if (inUse) {
+      inUseObjects.add(object);
+      if (origSize == 0) {
+        handleInUse();
+      }
+    } else {
+      boolean removed = inUseObjects.remove(object);
+      if (removed && origSize == 1) {
+        handleNotInUse();
+      }
+    }
+  }
+
+  final boolean isInUse() {
+    return !inUseObjects.isEmpty();
+  }
+
+  /**
+   * Called when the aggregated in-use state has changed to true, which means at least one object is
+   * in use.
+   */
+  abstract void handleInUse();
+
+  /**
+   * Called when the aggregated in-use state has changed to false, which means no object is in use.
+   */
+  abstract void handleNotInUse();
+}
diff --git a/core/src/main/java/io/grpc/internal/InternalHandlerRegistry.java b/core/src/main/java/io/grpc/internal/InternalHandlerRegistry.java
new file mode 100644
index 0000000..91f3311
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/InternalHandlerRegistry.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.HandlerRegistry;
+import io.grpc.ServerMethodDefinition;
+import io.grpc.ServerServiceDefinition;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+final class InternalHandlerRegistry extends HandlerRegistry {
+
+  private final List<ServerServiceDefinition> services;
+  private final Map<String, ServerMethodDefinition<?, ?>> methods;
+
+  private InternalHandlerRegistry(
+      List<ServerServiceDefinition> services, Map<String, ServerMethodDefinition<?, ?>> methods) {
+    this.services = services;
+    this.methods = methods;
+  }
+
+  /**
+   * Returns the service definitions in this registry.
+   */
+  @Override
+  public List<ServerServiceDefinition> getServices() {
+    return services;
+  }
+
+  @Nullable
+  @Override
+  public ServerMethodDefinition<?, ?> lookupMethod(String methodName, @Nullable String authority) {
+    // TODO (carl-mastrangelo): honor authority header.
+    return methods.get(methodName);
+  }
+
+  static final class Builder {
+
+    // Store per-service first, to make sure services are added/replaced atomically.
+    private final HashMap<String, ServerServiceDefinition> services =
+        new LinkedHashMap<String, ServerServiceDefinition>();
+
+    Builder addService(ServerServiceDefinition service) {
+      services.put(service.getServiceDescriptor().getName(), service);
+      return this;
+    }
+
+    InternalHandlerRegistry build() {
+      Map<String, ServerMethodDefinition<?, ?>> map =
+          new HashMap<String, ServerMethodDefinition<?, ?>>();
+      for (ServerServiceDefinition service : services.values()) {
+        for (ServerMethodDefinition<?, ?> method : service.getMethods()) {
+          map.put(method.getMethodDescriptor().getFullMethodName(), method);
+        }
+      }
+      return new InternalHandlerRegistry(
+          Collections.unmodifiableList(new ArrayList<>(services.values())),
+          Collections.unmodifiableMap(map));
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/InternalServer.java b/core/src/main/java/io/grpc/internal/InternalServer.java
new file mode 100644
index 0000000..ebe6288
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/InternalServer.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalInstrumented;
+import java.io.IOException;
+import java.util.List;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * An object that accepts new incoming connections. This would commonly encapsulate a bound socket
+ * that {@code accept()}s new connections.
+ */
+@ThreadSafe
+public interface InternalServer {
+  /**
+   * Starts transport. Implementations must not call {@code listener} until after {@code start()}
+   * returns. The method only returns after it has done the equivalent of bind()ing, so it will be
+   * able to service any connections created after returning.
+   *
+   * @param listener non-{@code null} listener of server events
+   * @throws IOException if unable to bind
+   */
+  void start(ServerListener listener) throws IOException;
+
+  /**
+   * Initiates an orderly shutdown of the server. Existing transports continue, but new transports
+   * will not be created (once {@link ServerListener#serverShutdown()} callback is called). This
+   * method may only be called once.
+   */
+  void shutdown();
+
+  /**
+   * Returns what underlying port the server is listening on, or -1 if the port number is not
+   * available or does not make sense.
+   */
+  int getPort();
+
+  /**
+   * Returns the listen sockets of this server. May return an empty list but never returns null.
+   */
+  List<InternalInstrumented<SocketStats>> getListenSockets();
+}
diff --git a/core/src/main/java/io/grpc/internal/InternalSubchannel.java b/core/src/main/java/io/grpc/internal/InternalSubchannel.java
new file mode 100644
index 0000000..879b7d8
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/InternalSubchannel.java
@@ -0,0 +1,791 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static io.grpc.ConnectivityState.CONNECTING;
+import static io.grpc.ConnectivityState.IDLE;
+import static io.grpc.ConnectivityState.READY;
+import static io.grpc.ConnectivityState.SHUTDOWN;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Supplier;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import com.google.errorprone.annotations.ForOverride;
+import io.grpc.Attributes;
+import io.grpc.CallOptions;
+import io.grpc.ConnectivityState;
+import io.grpc.ConnectivityStateInfo;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.ChannelStats;
+import io.grpc.InternalChannelz.ChannelTrace;
+import io.grpc.InternalInstrumented;
+import io.grpc.InternalLogId;
+import io.grpc.InternalWithLogId;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Transports for a single {@link SocketAddress}.
+ */
+@ThreadSafe
+final class InternalSubchannel implements InternalInstrumented<ChannelStats> {
+  private static final Logger log = Logger.getLogger(InternalSubchannel.class.getName());
+
+  private final InternalLogId logId = InternalLogId.allocate(getClass().getName());
+  private final String authority;
+  private final String userAgent;
+  private final BackoffPolicy.Provider backoffPolicyProvider;
+  private final Callback callback;
+  private final ClientTransportFactory transportFactory;
+  private final ScheduledExecutorService scheduledExecutor;
+  private final InternalChannelz channelz;
+  private final CallTracer callsTracer;
+  @CheckForNull
+  private final ChannelTracer channelTracer;
+  private final TimeProvider timeProvider;
+
+  // File-specific convention: methods without GuardedBy("lock") MUST NOT be called under the lock.
+  private final Object lock = new Object();
+
+  // File-specific convention:
+  //
+  // 1. In a method without GuardedBy("lock"), executeLater() MUST be followed by a drain() later in
+  // the same method.
+  //
+  // 2. drain() MUST NOT be called under "lock".
+  //
+  // 3. Every synchronized("lock") must be inside a try-finally which calls drain() in "finally".
+  private final ChannelExecutor channelExecutor;
+
+  /**
+   * The index of the address corresponding to pendingTransport/activeTransport, or at beginning if
+   * both are null.
+   */
+  @GuardedBy("lock")
+  private Index addressIndex;
+
+  /**
+   * The policy to control back off between reconnects. Non-{@code null} when a reconnect task is
+   * scheduled.
+   */
+  @GuardedBy("lock")
+  private BackoffPolicy reconnectPolicy;
+
+  /**
+   * Timer monitoring duration since entering CONNECTING state.
+   */
+  @GuardedBy("lock")
+  private final Stopwatch connectingTimer;
+
+  @GuardedBy("lock")
+  @Nullable
+  private ScheduledFuture<?> reconnectTask;
+
+  @GuardedBy("lock")
+  private boolean reconnectCanceled;
+
+  /**
+   * All transports that are not terminated. At the very least the value of {@link #activeTransport}
+   * will be present, but previously used transports that still have streams or are stopping may
+   * also be present.
+   */
+  @GuardedBy("lock")
+  private final Collection<ConnectionClientTransport> transports = new ArrayList<>();
+
+  // Must only be used from channelExecutor
+  private final InUseStateAggregator<ConnectionClientTransport> inUseStateAggregator =
+      new InUseStateAggregator<ConnectionClientTransport>() {
+        @Override
+        void handleInUse() {
+          callback.onInUse(InternalSubchannel.this);
+        }
+
+        @Override
+        void handleNotInUse() {
+          callback.onNotInUse(InternalSubchannel.this);
+        }
+      };
+
+  /**
+   * The to-be active transport, which is not ready yet.
+   */
+  @GuardedBy("lock")
+  @Nullable
+  private ConnectionClientTransport pendingTransport;
+
+  /**
+   * The transport for new outgoing requests. 'lock' must be held when assigning to it. Non-null
+   * only in READY state.
+   */
+  @Nullable
+  private volatile ManagedClientTransport activeTransport;
+
+  @GuardedBy("lock")
+  private ConnectivityStateInfo state = ConnectivityStateInfo.forNonError(IDLE);
+
+  @GuardedBy("lock")
+  private Status shutdownReason;
+
+  InternalSubchannel(List<EquivalentAddressGroup> addressGroups, String authority, String userAgent,
+      BackoffPolicy.Provider backoffPolicyProvider,
+      ClientTransportFactory transportFactory, ScheduledExecutorService scheduledExecutor,
+      Supplier<Stopwatch> stopwatchSupplier, ChannelExecutor channelExecutor, Callback callback,
+      InternalChannelz channelz, CallTracer callsTracer, @Nullable ChannelTracer channelTracer,
+      TimeProvider timeProvider) {
+    Preconditions.checkNotNull(addressGroups, "addressGroups");
+    Preconditions.checkArgument(!addressGroups.isEmpty(), "addressGroups is empty");
+    checkListHasNoNulls(addressGroups, "addressGroups contains null entry");
+    this.addressIndex = new Index(
+        Collections.unmodifiableList(new ArrayList<>(addressGroups)));
+    this.authority = authority;
+    this.userAgent = userAgent;
+    this.backoffPolicyProvider = backoffPolicyProvider;
+    this.transportFactory = transportFactory;
+    this.scheduledExecutor = scheduledExecutor;
+    this.connectingTimer = stopwatchSupplier.get();
+    this.channelExecutor = channelExecutor;
+    this.callback = callback;
+    this.channelz = channelz;
+    this.callsTracer = callsTracer;
+    this.channelTracer = channelTracer;
+    this.timeProvider = timeProvider;
+  }
+
+  /**
+   * Returns a READY transport that will be used to create new streams.
+   *
+   * <p>Returns {@code null} if the state is not READY.  Will try to connect if state is IDLE.
+   */
+  @Nullable
+  ClientTransport obtainActiveTransport() {
+    ClientTransport savedTransport = activeTransport;
+    if (savedTransport != null) {
+      return savedTransport;
+    }
+    try {
+      synchronized (lock) {
+        savedTransport = activeTransport;
+        // Check again, since it could have changed before acquiring the lock
+        if (savedTransport != null) {
+          return savedTransport;
+        }
+        if (state.getState() == IDLE) {
+          gotoNonErrorState(CONNECTING);
+          startNewTransport();
+        }
+      }
+    } finally {
+      channelExecutor.drain();
+    }
+    return null;
+  }
+
+  @GuardedBy("lock")
+  private void startNewTransport() {
+    Preconditions.checkState(reconnectTask == null, "Should have no reconnectTask scheduled");
+
+    if (addressIndex.isAtBeginning()) {
+      connectingTimer.reset().start();
+    }
+    SocketAddress address = addressIndex.getCurrentAddress();
+
+    ProxyParameters proxy = null;
+    if (address instanceof ProxySocketAddress) {
+      proxy = ((ProxySocketAddress) address).getProxyParameters();
+      address = ((ProxySocketAddress) address).getAddress();
+    }
+
+    ClientTransportFactory.ClientTransportOptions options =
+        new ClientTransportFactory.ClientTransportOptions()
+          .setAuthority(authority)
+          .setEagAttributes(addressIndex.getCurrentEagAttributes())
+          .setUserAgent(userAgent)
+          .setProxyParameters(proxy);
+    ConnectionClientTransport transport =
+        new CallTracingTransport(
+            transportFactory.newClientTransport(address, options), callsTracer);
+    channelz.addClientSocket(transport);
+    if (log.isLoggable(Level.FINE)) {
+      log.log(Level.FINE, "[{0}] Created {1} for {2}",
+          new Object[] {logId, transport.getLogId(), address});
+    }
+    pendingTransport = transport;
+    transports.add(transport);
+    Runnable runnable = transport.start(new TransportListener(transport, address));
+    if (runnable != null) {
+      channelExecutor.executeLater(runnable);
+    }
+  }
+
+  /**
+   * Only called after all addresses attempted and failed (TRANSIENT_FAILURE).
+   * @param status the causal status when the channel begins transition to
+   *     TRANSIENT_FAILURE.
+   */
+  @GuardedBy("lock")
+  private void scheduleBackoff(final Status status) {
+    class EndOfCurrentBackoff implements Runnable {
+      @Override
+      public void run() {
+        try {
+          synchronized (lock) {
+            reconnectTask = null;
+            if (reconnectCanceled) {
+              // Even though cancelReconnectTask() will cancel this task, the task may have already
+              // started when it's being canceled.
+              return;
+            }
+            gotoNonErrorState(CONNECTING);
+            startNewTransport();
+          }
+        } catch (Throwable t) {
+          log.log(Level.WARNING, "Exception handling end of backoff", t);
+        } finally {
+          channelExecutor.drain();
+        }
+      }
+    }
+
+    gotoState(ConnectivityStateInfo.forTransientFailure(status));
+    if (reconnectPolicy == null) {
+      reconnectPolicy = backoffPolicyProvider.get();
+    }
+    long delayNanos =
+        reconnectPolicy.nextBackoffNanos() - connectingTimer.elapsed(TimeUnit.NANOSECONDS);
+    if (log.isLoggable(Level.FINE)) {
+      log.log(Level.FINE, "[{0}] Scheduling backoff for {1} ns", new Object[]{logId, delayNanos});
+    }
+    Preconditions.checkState(reconnectTask == null, "previous reconnectTask is not done");
+    reconnectCanceled = false;
+    reconnectTask = scheduledExecutor.schedule(
+        new LogExceptionRunnable(new EndOfCurrentBackoff()),
+        delayNanos,
+        TimeUnit.NANOSECONDS);
+  }
+
+  /**
+   * Immediately attempt to reconnect if the current state is TRANSIENT_FAILURE. Otherwise this
+   * method has no effect.
+   */
+  void resetConnectBackoff() {
+    try {
+      synchronized (lock) {
+        if (state.getState() != TRANSIENT_FAILURE) {
+          return;
+        }
+        cancelReconnectTask();
+        gotoNonErrorState(CONNECTING);
+        startNewTransport();
+      }
+    } finally {
+      channelExecutor.drain();
+    }
+  }
+
+  @GuardedBy("lock")
+  private void gotoNonErrorState(ConnectivityState newState) {
+    gotoState(ConnectivityStateInfo.forNonError(newState));
+  }
+
+  @GuardedBy("lock")
+  private void gotoState(final ConnectivityStateInfo newState) {
+    if (state.getState() != newState.getState()) {
+      Preconditions.checkState(state.getState() != SHUTDOWN,
+          "Cannot transition out of SHUTDOWN to " + newState);
+      state = newState;
+      if (channelTracer != null) {
+        channelTracer.reportEvent(
+            new ChannelTrace.Event.Builder()
+                .setDescription("Entering " + state + " state")
+                .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+                .setTimestampNanos(timeProvider.currentTimeNanos())
+                .build());
+      }
+      channelExecutor.executeLater(new Runnable() {
+          @Override
+          public void run() {
+            callback.onStateChange(InternalSubchannel.this, newState);
+          }
+        });
+    }
+  }
+
+  /** Replaces the existing addresses, avoiding unnecessary reconnects. */
+  public void updateAddresses(List<EquivalentAddressGroup> newAddressGroups) {
+    Preconditions.checkNotNull(newAddressGroups, "newAddressGroups");
+    checkListHasNoNulls(newAddressGroups, "newAddressGroups contains null entry");
+    Preconditions.checkArgument(!newAddressGroups.isEmpty(), "newAddressGroups is empty");
+    newAddressGroups =
+        Collections.unmodifiableList(new ArrayList<>(newAddressGroups));
+    ManagedClientTransport savedTransport = null;
+    try {
+      synchronized (lock) {
+        SocketAddress previousAddress = addressIndex.getCurrentAddress();
+        addressIndex.updateGroups(newAddressGroups);
+        if (state.getState() == READY || state.getState() == CONNECTING) {
+          if (!addressIndex.seekTo(previousAddress)) {
+            // Forced to drop the connection
+            if (state.getState() == READY) {
+              savedTransport = activeTransport;
+              activeTransport = null;
+              addressIndex.reset();
+              gotoNonErrorState(IDLE);
+            } else {
+              savedTransport = pendingTransport;
+              pendingTransport = null;
+              addressIndex.reset();
+              startNewTransport();
+            }
+          }
+        }
+      }
+    } finally {
+      channelExecutor.drain();
+    }
+    if (savedTransport != null) {
+      savedTransport.shutdown(
+          Status.UNAVAILABLE.withDescription(
+              "InternalSubchannel closed transport due to address change"));
+    }
+  }
+
+  public void shutdown(Status reason) {
+    ManagedClientTransport savedActiveTransport;
+    ConnectionClientTransport savedPendingTransport;
+    try {
+      synchronized (lock) {
+        if (state.getState() == SHUTDOWN) {
+          return;
+        }
+        shutdownReason = reason;
+        gotoNonErrorState(SHUTDOWN);
+        savedActiveTransport = activeTransport;
+        savedPendingTransport = pendingTransport;
+        activeTransport = null;
+        pendingTransport = null;
+        addressIndex.reset();
+        if (transports.isEmpty()) {
+          handleTermination();
+          if (log.isLoggable(Level.FINE)) {
+            log.log(Level.FINE, "[{0}] Terminated in shutdown()", logId);
+          }
+        }  // else: the callback will be run once all transports have been terminated
+        cancelReconnectTask();
+      }
+    } finally {
+      channelExecutor.drain();
+    }
+    if (savedActiveTransport != null) {
+      savedActiveTransport.shutdown(reason);
+    }
+    if (savedPendingTransport != null) {
+      savedPendingTransport.shutdown(reason);
+    }
+  }
+
+  @Override
+  public String toString() {
+    // addressGroupsCopy being a little stale is fine, just avoid calling toString with the lock
+    // since there may be many addresses.
+    Object addressGroupsCopy;
+    synchronized (lock) {
+      addressGroupsCopy = addressIndex.getGroups();
+    }
+    return MoreObjects.toStringHelper(this)
+          .add("logId", logId.getId())
+          .add("addressGroups", addressGroupsCopy)
+          .toString();
+  }
+
+  @GuardedBy("lock")
+  private void handleTermination() {
+    channelExecutor.executeLater(new Runnable() {
+        @Override
+        public void run() {
+          callback.onTerminated(InternalSubchannel.this);
+        }
+      });
+  }
+
+  private void handleTransportInUseState(
+      final ConnectionClientTransport transport, final boolean inUse) {
+    channelExecutor.executeLater(new Runnable() {
+        @Override
+        public void run() {
+          inUseStateAggregator.updateObjectInUse(transport, inUse);
+        }
+      }).drain();
+  }
+
+  void shutdownNow(Status reason) {
+    shutdown(reason);
+    Collection<ManagedClientTransport> transportsCopy;
+    try {
+      synchronized (lock) {
+        transportsCopy = new ArrayList<ManagedClientTransport>(transports);
+      }
+    } finally {
+      channelExecutor.drain();
+    }
+    for (ManagedClientTransport transport : transportsCopy) {
+      transport.shutdownNow(reason);
+    }
+  }
+
+  List<EquivalentAddressGroup> getAddressGroups() {
+    try {
+      synchronized (lock) {
+        return addressIndex.getGroups();
+      }
+    } finally {
+      channelExecutor.drain();
+    }
+  }
+
+  @GuardedBy("lock")
+  private void cancelReconnectTask() {
+    if (reconnectTask != null) {
+      reconnectTask.cancel(false);
+      reconnectCanceled = true;
+      reconnectTask = null;
+      reconnectPolicy = null;
+    }
+  }
+
+  @Override
+  public InternalLogId getLogId() {
+    return logId;
+  }
+
+
+  @Override
+  public ListenableFuture<ChannelStats> getStats() {
+    SettableFuture<ChannelStats> ret = SettableFuture.create();
+    ChannelStats.Builder builder = new ChannelStats.Builder();
+
+    List<EquivalentAddressGroup> addressGroupsSnapshot;
+    List<InternalWithLogId> transportsSnapshot;
+    synchronized (lock) {
+      addressGroupsSnapshot = addressIndex.getGroups();
+      transportsSnapshot = new ArrayList<InternalWithLogId>(transports);
+    }
+
+    builder.setTarget(addressGroupsSnapshot.toString()).setState(getState());
+    builder.setSockets(transportsSnapshot);
+    callsTracer.updateBuilder(builder);
+    if (channelTracer != null) {
+      channelTracer.updateBuilder(builder);
+    }
+    ret.set(builder.build());
+    return ret;
+  }
+
+  @VisibleForTesting
+  ConnectivityState getState() {
+    try {
+      synchronized (lock) {
+        return state.getState();
+      }
+    } finally {
+      channelExecutor.drain();
+    }
+  }
+
+  private static void checkListHasNoNulls(List<?> list, String msg) {
+    for (Object item : list) {
+      Preconditions.checkNotNull(item, msg);
+    }
+  }
+
+  /** Listener for real transports. */
+  private class TransportListener implements ManagedClientTransport.Listener {
+    final ConnectionClientTransport transport;
+    final SocketAddress address;
+
+    TransportListener(ConnectionClientTransport transport, SocketAddress address) {
+      this.transport = transport;
+      this.address = address;
+    }
+
+    @Override
+    public void transportReady() {
+      if (log.isLoggable(Level.FINE)) {
+        log.log(Level.FINE, "[{0}] {1} for {2} is ready",
+            new Object[] {logId, transport.getLogId(), address});
+      }
+      Status savedShutdownReason;
+      try {
+        synchronized (lock) {
+          savedShutdownReason = shutdownReason;
+          reconnectPolicy = null;
+          if (savedShutdownReason != null) {
+            // activeTransport should have already been set to null by shutdown(). We keep it null.
+            Preconditions.checkState(activeTransport == null,
+                "Unexpected non-null activeTransport");
+          } else if (pendingTransport == transport) {
+            gotoNonErrorState(READY);
+            activeTransport = transport;
+            pendingTransport = null;
+          }
+        }
+      } finally {
+        channelExecutor.drain();
+      }
+      if (savedShutdownReason != null) {
+        transport.shutdown(savedShutdownReason);
+      }
+    }
+
+    @Override
+    public void transportInUse(boolean inUse) {
+      handleTransportInUseState(transport, inUse);
+    }
+
+    @Override
+    public void transportShutdown(Status s) {
+      if (log.isLoggable(Level.FINE)) {
+        log.log(Level.FINE, "[{0}] {1} for {2} is being shutdown with status {3}",
+            new Object[] {logId, transport.getLogId(), address, s});
+      }
+      try {
+        synchronized (lock) {
+          if (state.getState() == SHUTDOWN) {
+            return;
+          }
+          if (activeTransport == transport) {
+            gotoNonErrorState(IDLE);
+            activeTransport = null;
+            addressIndex.reset();
+          } else if (pendingTransport == transport) {
+            Preconditions.checkState(state.getState() == CONNECTING,
+                "Expected state is CONNECTING, actual state is %s", state.getState());
+            addressIndex.increment();
+            // Continue reconnect if there are still addresses to try.
+            if (!addressIndex.isValid()) {
+              pendingTransport = null;
+              addressIndex.reset();
+              // Initiate backoff
+              // Transition to TRANSIENT_FAILURE
+              scheduleBackoff(s);
+            } else {
+              startNewTransport();
+            }
+          }
+        }
+      } finally {
+        channelExecutor.drain();
+      }
+    }
+
+    @Override
+    public void transportTerminated() {
+      if (log.isLoggable(Level.FINE)) {
+        log.log(Level.FINE, "[{0}] {1} for {2} is terminated",
+            new Object[] {logId, transport.getLogId(), address});
+      }
+      channelz.removeClientSocket(transport);
+      handleTransportInUseState(transport, false);
+      try {
+        synchronized (lock) {
+          transports.remove(transport);
+          if (state.getState() == SHUTDOWN && transports.isEmpty()) {
+            if (log.isLoggable(Level.FINE)) {
+              log.log(Level.FINE, "[{0}] Terminated in transportTerminated()", logId);
+            }
+            handleTermination();
+          }
+        }
+      } finally {
+        channelExecutor.drain();
+      }
+      Preconditions.checkState(activeTransport != transport,
+          "activeTransport still points to this transport. "
+          + "Seems transportShutdown() was not called.");
+    }
+  }
+
+  // All methods are called in channelExecutor, which is a serializing executor.
+  abstract static class Callback {
+    /**
+     * Called when the subchannel is terminated, which means it's shut down and all transports
+     * have been terminated.
+     */
+    @ForOverride
+    void onTerminated(InternalSubchannel is) { }
+
+    /**
+     * Called when the subchannel's connectivity state has changed.
+     */
+    @ForOverride
+    void onStateChange(InternalSubchannel is, ConnectivityStateInfo newState) { }
+
+    /**
+     * Called when the subchannel's in-use state has changed to true, which means at least one
+     * transport is in use.
+     */
+    @ForOverride
+    void onInUse(InternalSubchannel is) { }
+
+    /**
+     * Called when the subchannel's in-use state has changed to false, which means no transport is
+     * in use.
+     */
+    @ForOverride
+    void onNotInUse(InternalSubchannel is) { }
+  }
+
+  @VisibleForTesting
+  static final class CallTracingTransport extends ForwardingConnectionClientTransport {
+    private final ConnectionClientTransport delegate;
+    private final CallTracer callTracer;
+
+    private CallTracingTransport(ConnectionClientTransport delegate, CallTracer callTracer) {
+      this.delegate = delegate;
+      this.callTracer = callTracer;
+    }
+
+    @Override
+    protected ConnectionClientTransport delegate() {
+      return delegate;
+    }
+
+    @Override
+    public ClientStream newStream(
+        MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions) {
+      final ClientStream streamDelegate = super.newStream(method, headers, callOptions);
+      return new ForwardingClientStream() {
+        @Override
+        protected ClientStream delegate() {
+          return streamDelegate;
+        }
+
+        @Override
+        public void start(final ClientStreamListener listener) {
+          callTracer.reportCallStarted();
+          super.start(new ForwardingClientStreamListener() {
+            @Override
+            protected ClientStreamListener delegate() {
+              return listener;
+            }
+
+            @Override
+            public void closed(Status status, Metadata trailers) {
+              callTracer.reportCallEnded(status.isOk());
+              super.closed(status, trailers);
+            }
+
+            @Override
+            public void closed(
+                Status status, RpcProgress rpcProgress, Metadata trailers) {
+              callTracer.reportCallEnded(status.isOk());
+              super.closed(status, rpcProgress, trailers);
+            }
+          });
+        }
+      };
+    }
+  }
+
+  /** Index as in 'i', the pointer to an entry. Not a "search index." */
+  @VisibleForTesting
+  static final class Index {
+    private List<EquivalentAddressGroup> addressGroups;
+    private int groupIndex;
+    private int addressIndex;
+
+    public Index(List<EquivalentAddressGroup> groups) {
+      this.addressGroups = groups;
+    }
+
+    public boolean isValid() {
+      // addressIndex will never be invalid
+      return groupIndex < addressGroups.size();
+    }
+
+    public boolean isAtBeginning() {
+      return groupIndex == 0 && addressIndex == 0;
+    }
+
+    public void increment() {
+      EquivalentAddressGroup group = addressGroups.get(groupIndex);
+      addressIndex++;
+      if (addressIndex >= group.getAddresses().size()) {
+        groupIndex++;
+        addressIndex = 0;
+      }
+    }
+
+    public void reset() {
+      groupIndex = 0;
+      addressIndex = 0;
+    }
+
+    public SocketAddress getCurrentAddress() {
+      return addressGroups.get(groupIndex).getAddresses().get(addressIndex);
+    }
+
+    public Attributes getCurrentEagAttributes() {
+      return addressGroups.get(groupIndex).getAttributes();
+    }
+
+    public List<EquivalentAddressGroup> getGroups() {
+      return addressGroups;
+    }
+
+    /** Update to new groups, resetting the current index. */
+    public void updateGroups(List<EquivalentAddressGroup> newGroups) {
+      addressGroups = newGroups;
+      reset();
+    }
+
+    /** Returns false if the needle was not found and the current index was left unchanged. */
+    public boolean seekTo(SocketAddress needle) {
+      for (int i = 0; i < addressGroups.size(); i++) {
+        EquivalentAddressGroup group = addressGroups.get(i);
+        int j = group.getAddresses().indexOf(needle);
+        if (j == -1) {
+          continue;
+        }
+        this.groupIndex = i;
+        this.addressIndex = j;
+        return true;
+      }
+      return false;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/IoUtils.java b/core/src/main/java/io/grpc/internal/IoUtils.java
new file mode 100644
index 0000000..f66b3bb
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/IoUtils.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/** Common IoUtils for thrift and nanopb to convert inputstream to bytes. */
+public final class IoUtils {
+
+  /** maximum buffer to be read is 16 KB. */
+  private static final int MAX_BUFFER_LENGTH = 16384;
+
+  /** Returns the byte array. */
+  public static byte[] toByteArray(InputStream in) throws IOException {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    copy(in, out);
+    return out.toByteArray();
+  }
+
+  /** Copies the data from input stream to output stream. */
+  public static long copy(InputStream from, OutputStream to) throws IOException {
+    // Copied from guava com.google.common.io.ByteStreams because its API is unstable (beta)
+    checkNotNull(from);
+    checkNotNull(to);
+    byte[] buf = new byte[MAX_BUFFER_LENGTH];
+    long total = 0;
+    while (true) {
+      int r = from.read(buf);
+      if (r == -1) {
+        break;
+      }
+      to.write(buf, 0, r);
+      total += r;
+    }
+    return total;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/JndiResourceResolverFactory.java b/core/src/main/java/io/grpc/internal/JndiResourceResolverFactory.java
new file mode 100644
index 0000000..b095266
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/JndiResourceResolverFactory.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Verify;
+import io.grpc.Attributes;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.internal.DnsNameResolver.AddressResolver;
+import io.grpc.internal.DnsNameResolver.ResourceResolver;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import javax.annotation.Nullable;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
+
+/**
+ * {@link JndiResourceResolverFactory} resolves additional records for the DnsNameResolver.
+ */
+final class JndiResourceResolverFactory implements DnsNameResolver.ResourceResolverFactory {
+
+  @Nullable
+  private static final Throwable JNDI_UNAVAILABILITY_CAUSE = initJndi();
+
+  // @UsedReflectively
+  public JndiResourceResolverFactory() {}
+
+  /**
+   * Returns whether the JNDI DNS resolver is available.  This is accomplished by looking up a
+   * particular class.  It is believed to be the default (only?) DNS resolver that will actually be
+   * used.  It is provided by the OpenJDK, but unlikely Android.  Actual resolution will be done by
+   * using a service provider when a hostname query is present, so the {@code DnsContextFactory}
+   * may not actually be used to perform the query.  This is believed to be "okay."
+   */
+  @Nullable
+  @SuppressWarnings("LiteralClassName")
+  private static Throwable initJndi() {
+    if (GrpcUtil.IS_RESTRICTED_APPENGINE) {
+      return new UnsupportedOperationException(
+          "Currently running in an AppEngine restricted environment");
+    }
+    try {
+      Class.forName("javax.naming.directory.InitialDirContext");
+      Class.forName("com.sun.jndi.dns.DnsContextFactory");
+    } catch (ClassNotFoundException e) {
+      return e;
+    } catch (RuntimeException e) {
+      return e;
+    } catch (Error e) {
+      return e;
+    }
+    return null;
+  }
+
+  @Nullable
+  @Override
+  public ResourceResolver newResourceResolver() {
+    if (unavailabilityCause() != null) {
+      return null;
+    }
+    return new JndiResourceResolver();
+  }
+
+  @Nullable
+  @Override
+  public Throwable unavailabilityCause() {
+    return JNDI_UNAVAILABILITY_CAUSE;
+  }
+
+  @VisibleForTesting
+  static final class JndiResourceResolver implements DnsNameResolver.ResourceResolver {
+    private static final Logger logger =
+        Logger.getLogger(JndiResourceResolver.class.getName());
+
+    private static final Pattern whitespace = Pattern.compile("\\s+");
+
+    @Override
+    public List<String> resolveTxt(String serviceConfigHostname) throws NamingException {
+      checkAvailable();
+      if (logger.isLoggable(Level.FINER)) {
+        logger.log(
+            Level.FINER, "About to query TXT records for {0}", new Object[]{serviceConfigHostname});
+      }
+      List<String> serviceConfigRawTxtRecords =
+          getAllRecords("TXT", "dns:///" + serviceConfigHostname);
+      if (logger.isLoggable(Level.FINER)) {
+        logger.log(
+            Level.FINER, "Found {0} TXT records", new Object[]{serviceConfigRawTxtRecords.size()});
+      }
+      List<String> serviceConfigTxtRecords =
+          new ArrayList<>(serviceConfigRawTxtRecords.size());
+      for (String serviceConfigRawTxtRecord : serviceConfigRawTxtRecords) {
+        serviceConfigTxtRecords.add(unquote(serviceConfigRawTxtRecord));
+      }
+      return Collections.unmodifiableList(serviceConfigTxtRecords);
+    }
+
+    @Override
+    public List<EquivalentAddressGroup> resolveSrv(
+        AddressResolver addressResolver, String grpclbHostname) throws Exception {
+      checkAvailable();
+      if (logger.isLoggable(Level.FINER)) {
+        logger.log(
+            Level.FINER, "About to query SRV records for {0}", new Object[]{grpclbHostname});
+      }
+      List<String> grpclbSrvRecords =
+          getAllRecords("SRV", "dns:///" + grpclbHostname);
+      if (logger.isLoggable(Level.FINER)) {
+        logger.log(
+            Level.FINER, "Found {0} SRV records", new Object[]{grpclbSrvRecords.size()});
+      }
+      List<EquivalentAddressGroup> balancerAddresses =
+          new ArrayList<>(grpclbSrvRecords.size());
+      Exception first = null;
+      Level level = Level.WARNING;
+      for (String srvRecord : grpclbSrvRecords) {
+        try {
+          SrvRecord record = parseSrvRecord(srvRecord);
+
+          List<? extends InetAddress> addrs = addressResolver.resolveAddress(record.host);
+          List<SocketAddress> sockaddrs = new ArrayList<>(addrs.size());
+          for (InetAddress addr : addrs) {
+            sockaddrs.add(new InetSocketAddress(addr, record.port));
+          }
+          Attributes attrs = Attributes.newBuilder()
+              .set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, record.host)
+              .build();
+          balancerAddresses.add(
+              new EquivalentAddressGroup(Collections.unmodifiableList(sockaddrs), attrs));
+        } catch (UnknownHostException e) {
+          logger.log(level, "Can't find address for SRV record " + srvRecord, e);
+          // TODO(carl-mastrangelo): these should be added by addSuppressed when we have Java 7.
+          if (first == null) {
+            first = e;
+            level = Level.FINE;
+          }
+        } catch (RuntimeException e) {
+          logger.log(level, "Failed to construct SRV record " + srvRecord, e);
+          if (first == null) {
+            first = e;
+            level = Level.FINE;
+          }
+        }
+      }
+      if (balancerAddresses.isEmpty() && first != null) {
+        throw first;
+      }
+      return Collections.unmodifiableList(balancerAddresses);
+    }
+
+    @VisibleForTesting
+    static final class SrvRecord {
+      SrvRecord(String host, int port) {
+        this.host = host;
+        this.port = port;
+      }
+
+      final String host;
+      final int port;
+    }
+
+    @VisibleForTesting
+    @SuppressWarnings("BetaApi") // Verify is only kinda beta
+    static SrvRecord parseSrvRecord(String rawRecord) {
+      String[] parts = whitespace.split(rawRecord);
+      Verify.verify(parts.length == 4, "Bad SRV Record: %s", rawRecord);
+      return new SrvRecord(parts[3], Integer.parseInt(parts[2]));
+    }
+
+    @IgnoreJRERequirement
+    private static List<String> getAllRecords(String recordType, String name)
+        throws NamingException {
+      String[] rrType = new String[]{recordType};
+      List<String> records = new ArrayList<>();
+
+      @SuppressWarnings("JdkObsolete")
+      Hashtable<String, String> env = new Hashtable<String, String>();
+      env.put("com.sun.jndi.ldap.connect.timeout", "5000");
+      env.put("com.sun.jndi.ldap.read.timeout", "5000");
+      DirContext dirContext = new InitialDirContext(env);
+
+      try {
+        javax.naming.directory.Attributes attrs = dirContext.getAttributes(name, rrType);
+        NamingEnumeration<? extends Attribute> rrGroups = attrs.getAll();
+
+        try {
+          while (rrGroups.hasMore()) {
+            Attribute rrEntry = rrGroups.next();
+            assert Arrays.asList(rrType).contains(rrEntry.getID());
+            NamingEnumeration<?> rrValues = rrEntry.getAll();
+            try {
+              while (rrValues.hasMore()) {
+                records.add(String.valueOf(rrValues.next()));
+              }
+            } catch (NamingException ne) {
+              closeThenThrow(rrValues, ne);
+            }
+            rrValues.close();
+          }
+        } catch (NamingException ne) {
+          closeThenThrow(rrGroups, ne);
+        }
+        rrGroups.close();
+      } catch (NamingException ne) {
+        closeThenThrow(dirContext, ne);
+      }
+      dirContext.close();
+
+      return records;
+    }
+
+    @IgnoreJRERequirement
+    private static void closeThenThrow(NamingEnumeration<?> namingEnumeration, NamingException e)
+        throws NamingException {
+      try {
+        namingEnumeration.close();
+      } catch (NamingException ignored) {
+        // ignore
+      }
+      throw e;
+    }
+
+    @IgnoreJRERequirement
+    private static void closeThenThrow(DirContext ctx, NamingException e) throws NamingException {
+      try {
+        ctx.close();
+      } catch (NamingException ignored) {
+        // ignore
+      }
+      throw e;
+    }
+
+    /**
+     * Undo the quoting done in {@link com.sun.jndi.dns.ResourceRecord#decodeTxt}.
+     */
+    @VisibleForTesting
+    static String unquote(String txtRecord) {
+      StringBuilder sb = new StringBuilder(txtRecord.length());
+      boolean inquote = false;
+      for (int i = 0; i < txtRecord.length(); i++) {
+        char c = txtRecord.charAt(i);
+        if (!inquote) {
+          if (c == ' ') {
+            continue;
+          } else if (c == '"') {
+            inquote = true;
+            continue;
+          }
+        } else {
+          if (c == '"') {
+            inquote = false;
+            continue;
+          } else if (c == '\\') {
+            c = txtRecord.charAt(++i);
+            assert c == '"' || c == '\\';
+          }
+        }
+        sb.append(c);
+      }
+      return sb.toString();
+    }
+
+    private static void checkAvailable() {
+      if (JNDI_UNAVAILABILITY_CAUSE != null) {
+        throw new UnsupportedOperationException(
+            "JNDI is not currently available", JNDI_UNAVAILABILITY_CAUSE);
+      }
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/JsonParser.java b/core/src/main/java/io/grpc/internal/JsonParser.java
new file mode 100644
index 0000000..c6daf21
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/JsonParser.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Parses JSON with as few preconceived notions as possible.
+ */
+public final class JsonParser {
+
+  private static final Logger logger = Logger.getLogger(JsonParser.class.getName());
+
+  private JsonParser() {}
+
+  /**
+   * Parses a json string, returning either a {@code Map<String, Object>}, {@code List<Object>},
+   * {@code String}, {@code Double}, {@code Boolean}, or {@code null}.
+   */
+  @SuppressWarnings("unchecked")
+  public static Object parse(String raw) throws IOException {
+    JsonReader jr = new JsonReader(new StringReader(raw));
+    try {
+      return parseRecursive(jr);
+    } finally {
+      try {
+        jr.close();
+      } catch (IOException e) {
+        logger.log(Level.WARNING, "Failed to close", e);
+      }
+    }
+  }
+
+  private static Object parseRecursive(JsonReader jr) throws IOException {
+    checkState(jr.hasNext(), "unexpected end of JSON");
+    switch (jr.peek()) {
+      case BEGIN_ARRAY:
+        return parseJsonArray(jr);
+      case BEGIN_OBJECT:
+        return parseJsonObject(jr);
+      case STRING:
+        return jr.nextString();
+      case NUMBER:
+        return jr.nextDouble();
+      case BOOLEAN:
+        return jr.nextBoolean();
+      case NULL:
+        return parseJsonNull(jr);
+      default:
+        throw new IllegalStateException("Bad token: " + jr.getPath());
+    }
+  }
+
+  private static Map<String, Object> parseJsonObject(JsonReader jr) throws IOException {
+    jr.beginObject();
+    Map<String, Object> obj = new LinkedHashMap<String, Object>();
+    while (jr.hasNext()) {
+      String name = jr.nextName();
+      Object value = parseRecursive(jr);
+      obj.put(name, value);
+    }
+    checkState(jr.peek() == JsonToken.END_OBJECT, "Bad token: " + jr.getPath());
+    jr.endObject();
+    return Collections.unmodifiableMap(obj);
+  }
+
+  private static List<Object> parseJsonArray(JsonReader jr) throws IOException {
+    jr.beginArray();
+    List<Object> array = new ArrayList<>();
+    while (jr.hasNext()) {
+      Object value = parseRecursive(jr);
+      array.add(value);
+    }
+    checkState(jr.peek() == JsonToken.END_ARRAY, "Bad token: " + jr.getPath());
+    jr.endArray();
+    return Collections.unmodifiableList(array);
+  }
+
+  private static Void parseJsonNull(JsonReader jr) throws IOException {
+    jr.nextNull();
+    return null;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/KeepAliveManager.java b/core/src/main/java/io/grpc/internal/KeepAliveManager.java
new file mode 100644
index 0000000..b85c47e
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/KeepAliveManager.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.Status;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Manages keepalive pings.
+ */
+public class KeepAliveManager {
+  private static final SystemTicker SYSTEM_TICKER = new SystemTicker();
+  private static final long MIN_KEEPALIVE_TIME_NANOS = TimeUnit.SECONDS.toNanos(10);
+  private static final long MIN_KEEPALIVE_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(10L);
+
+  private final ScheduledExecutorService scheduler;
+  private final Ticker ticker;
+  private final KeepAlivePinger keepAlivePinger;
+  private final boolean keepAliveDuringTransportIdle;
+  private State state = State.IDLE;
+  private long nextKeepaliveTime;
+  private ScheduledFuture<?> shutdownFuture;
+  private ScheduledFuture<?> pingFuture;
+  private final Runnable shutdown = new LogExceptionRunnable(new Runnable() {
+    @Override
+    public void run() {
+      boolean shouldShutdown = false;
+      synchronized (KeepAliveManager.this) {
+        if (state != State.DISCONNECTED) {
+          // We haven't received a ping response within the timeout. The connection is likely gone
+          // already. Shutdown the transport and fail all existing rpcs.
+          state = State.DISCONNECTED;
+          shouldShutdown = true;
+        }
+      }
+      if (shouldShutdown) {
+        keepAlivePinger.onPingTimeout();
+      }
+    }
+  });
+  private final Runnable sendPing = new LogExceptionRunnable(new Runnable() {
+    @Override
+    public void run() {
+      pingFuture = null;
+      boolean shouldSendPing = false;
+      synchronized (KeepAliveManager.this) {
+        if (state == State.PING_SCHEDULED) {
+          shouldSendPing = true;
+          state = State.PING_SENT;
+          // Schedule a shutdown. It fires if we don't receive the ping response within the timeout.
+          shutdownFuture = scheduler.schedule(shutdown, keepAliveTimeoutInNanos,
+              TimeUnit.NANOSECONDS);
+        } else if (state == State.PING_DELAYED) {
+          // We have received some data. Reschedule the ping with the new time.
+          pingFuture = scheduler.schedule(
+              sendPing,
+              nextKeepaliveTime - ticker.read(),
+              TimeUnit.NANOSECONDS);
+          state = State.PING_SCHEDULED;
+        }
+      }
+      if (shouldSendPing) {
+        // Send the ping.
+        keepAlivePinger.ping();
+      }
+    }
+  });
+
+  private long keepAliveTimeInNanos;
+  private long keepAliveTimeoutInNanos;
+
+  private enum State {
+    /*
+     * We don't need to do any keepalives. This means the transport has no active rpcs and
+     * keepAliveDuringTransportIdle == false.
+     */
+    IDLE,
+    /*
+     * We have scheduled a ping to be sent in the future. We may decide to delay it if we receive
+     * some data.
+     */
+    PING_SCHEDULED,
+    /*
+     * We need to delay the scheduled keepalive ping.
+     */
+    PING_DELAYED,
+    /*
+     * The ping has been sent out. Waiting for a ping response.
+     */
+    PING_SENT,
+    /*
+     * Transport goes idle after ping has been sent.
+     */
+    IDLE_AND_PING_SENT,
+    /*
+     * The transport has been disconnected. We won't do keepalives any more.
+     */
+    DISCONNECTED,
+  }
+
+  /**
+   * Creates a KeepAliverManager.
+   */
+  public KeepAliveManager(KeepAlivePinger keepAlivePinger, ScheduledExecutorService scheduler,
+                          long keepAliveTimeInNanos, long keepAliveTimeoutInNanos,
+                          boolean keepAliveDuringTransportIdle) {
+    this(keepAlivePinger, scheduler, SYSTEM_TICKER, keepAliveTimeInNanos, keepAliveTimeoutInNanos,
+        keepAliveDuringTransportIdle);
+  }
+
+  @VisibleForTesting
+  KeepAliveManager(KeepAlivePinger keepAlivePinger, ScheduledExecutorService scheduler,
+                   Ticker ticker, long keepAliveTimeInNanos, long keepAliveTimeoutInNanos,
+                   boolean keepAliveDuringTransportIdle) {
+    this.keepAlivePinger = checkNotNull(keepAlivePinger, "keepAlivePinger");
+    this.scheduler = checkNotNull(scheduler, "scheduler");
+    this.ticker = checkNotNull(ticker, "ticker");
+    this.keepAliveTimeInNanos = keepAliveTimeInNanos;
+    this.keepAliveTimeoutInNanos = keepAliveTimeoutInNanos;
+    this.keepAliveDuringTransportIdle = keepAliveDuringTransportIdle;
+    nextKeepaliveTime = ticker.read() + keepAliveTimeInNanos;
+  }
+
+  /** Start keepalive monitoring. */
+  public synchronized void onTransportStarted() {
+    if (keepAliveDuringTransportIdle) {
+      onTransportActive();
+    }
+  }
+
+  /**
+   * Transport has received some data so that we can delay sending keepalives.
+   */
+  public synchronized void onDataReceived() {
+    nextKeepaliveTime = ticker.read() + keepAliveTimeInNanos;
+    // We do not cancel the ping future here. This avoids constantly scheduling and cancellation in
+    // a busy transport. Instead, we update the status here and reschedule later. So we actually
+    // keep one sendPing task always in flight when there're active rpcs.
+    if (state == State.PING_SCHEDULED) {
+      state = State.PING_DELAYED;
+    } else if (state == State.PING_SENT || state == State.IDLE_AND_PING_SENT) {
+      // Ping acked or effectively ping acked. Cancel shutdown, and then if not idle,
+      // schedule a new keep-alive ping.
+      if (shutdownFuture != null) {
+        shutdownFuture.cancel(false);
+      }
+      if (state == State.IDLE_AND_PING_SENT) {
+        // not to schedule new pings until onTransportActive
+        state = State.IDLE;
+        return;
+      }
+      // schedule a new ping
+      state = State.PING_SCHEDULED;
+      checkState(pingFuture == null, "There should be no outstanding pingFuture");
+      pingFuture = scheduler.schedule(sendPing, keepAliveTimeInNanos, TimeUnit.NANOSECONDS);
+    }
+  }
+
+  /**
+   * Transport has active streams. Start sending keepalives if necessary.
+   */
+  public synchronized void onTransportActive() {
+    if (state == State.IDLE) {
+      // When the transport goes active, we do not reset the nextKeepaliveTime. This allows us to
+      // quickly check whether the connection is still working.
+      state = State.PING_SCHEDULED;
+      if (pingFuture == null) {
+        pingFuture = scheduler.schedule(
+            sendPing,
+            nextKeepaliveTime - ticker.read(),
+            TimeUnit.NANOSECONDS);
+      }
+    } else if (state == State.IDLE_AND_PING_SENT) {
+      state = State.PING_SENT;
+    } // Other states are possible when keepAliveDuringTransportIdle == true
+  }
+
+  /**
+   * Transport has finished all streams.
+   */
+  public synchronized void onTransportIdle() {
+    if (keepAliveDuringTransportIdle) {
+      return;
+    }
+    if (state == State.PING_SCHEDULED || state == State.PING_DELAYED) {
+      state = State.IDLE;
+    }
+    if (state == State.PING_SENT) {
+      state = State.IDLE_AND_PING_SENT;
+    }
+  }
+
+  /**
+   * Transport is being terminated. We no longer need to do keepalives.
+   */
+  public synchronized void onTransportTermination() {
+    if (state != State.DISCONNECTED) {
+      state = State.DISCONNECTED;
+      if (shutdownFuture != null) {
+        shutdownFuture.cancel(false);
+      }
+      if (pingFuture != null) {
+        pingFuture.cancel(false);
+        pingFuture = null;
+      }
+    }
+  }
+
+  /**
+   * Bumps keepalive time to 10 seconds if the specified value was smaller than that.
+   */
+  public static long clampKeepAliveTimeInNanos(long keepAliveTimeInNanos) {
+    return Math.max(keepAliveTimeInNanos, MIN_KEEPALIVE_TIME_NANOS);
+  }
+
+  /**
+   * Bumps keepalive timeout to 10 milliseconds if the specified value was smaller than that.
+   */
+  public static long clampKeepAliveTimeoutInNanos(long keepAliveTimeoutInNanos) {
+    return Math.max(keepAliveTimeoutInNanos, MIN_KEEPALIVE_TIMEOUT_NANOS);
+  }
+
+  public interface KeepAlivePinger {
+    /**
+     * Sends out a keep-alive ping.
+     */
+    void ping();
+
+    /**
+     * Callback when Ping Ack was not received in KEEPALIVE_TIMEOUT. Should shutdown the transport.
+     */
+    void onPingTimeout();
+  }
+
+  /**
+   * Default client side {@link KeepAlivePinger}.
+   */
+  public static final class ClientKeepAlivePinger implements KeepAlivePinger {
+    private final ConnectionClientTransport transport;
+
+    public ClientKeepAlivePinger(ConnectionClientTransport transport) {
+      this.transport = transport;
+    }
+
+    @Override
+    public void ping() {
+      transport.ping(new ClientTransport.PingCallback() {
+        @Override
+        public void onSuccess(long roundTripTimeNanos) {}
+
+        @Override
+        public void onFailure(Throwable cause) {
+          transport.shutdownNow(Status.UNAVAILABLE.withDescription(
+              "Keepalive failed. The connection is likely gone"));
+        }
+      }, MoreExecutors.directExecutor());
+    }
+
+    @Override
+    public void onPingTimeout() {
+      transport.shutdownNow(Status.UNAVAILABLE.withDescription(
+          "Keepalive failed. The connection is likely gone"));
+    }
+  }
+
+  // TODO(zsurocking): Classes below are copied from Deadline.java. We should consider share the
+  // code.
+
+  /** Time source representing nanoseconds since fixed but arbitrary point in time. */
+  abstract static class Ticker {
+    /** Returns the number of nanoseconds since this source's epoch. */
+    public abstract long read();
+  }
+
+  private static class SystemTicker extends Ticker {
+    @Override
+    public long read() {
+      return System.nanoTime();
+    }
+  }
+}
+
diff --git a/core/src/main/java/io/grpc/internal/LogExceptionRunnable.java b/core/src/main/java/io/grpc/internal/LogExceptionRunnable.java
new file mode 100644
index 0000000..507c6fe
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/LogExceptionRunnable.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A simple wrapper for a {@link Runnable} that logs any exception thrown by it, before
+ * re-throwing it.
+ */
+public final class LogExceptionRunnable implements Runnable {
+
+  private static final Logger log = Logger.getLogger(LogExceptionRunnable.class.getName());
+
+  private final Runnable task;
+
+  public LogExceptionRunnable(Runnable task) {
+    this.task = checkNotNull(task, "task");
+  }
+
+  @Override
+  public void run() {
+    try {
+      task.run();
+    } catch (Throwable t) {
+      log.log(Level.SEVERE, "Exception while executing runnable " + task, t);
+      MoreThrowables.throwIfUnchecked(t);
+      throw new AssertionError(t);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "LogExceptionRunnable(" + task + ")";
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/LongCounter.java b/core/src/main/java/io/grpc/internal/LongCounter.java
new file mode 100644
index 0000000..a88930b
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/LongCounter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+/**
+ * An interface for a long base counter.
+ */
+public interface LongCounter {
+  /** Adds the delta to this counter. */
+  void add(long delta);
+
+  /** Returns the value of this counter. */
+  long value();
+}
diff --git a/core/src/main/java/io/grpc/internal/LongCounterFactory.java b/core/src/main/java/io/grpc/internal/LongCounterFactory.java
new file mode 100644
index 0000000..0942139
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/LongCounterFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+/**
+ * A factory for creating {@link LongCounter} objects. The concrete implementation returned may
+ * be platform dependent.
+ */
+final class LongCounterFactory {
+  /**
+   * Creates a LongCounter.
+   */
+  public static LongCounter create() {
+    if (ReflectionLongAdderCounter.isAvailable()) {
+      return new ReflectionLongAdderCounter();
+    } else {
+      return new AtomicLongCounter();
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
new file mode 100644
index 0000000..dc203b0
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ManagedChannelImpl.java
@@ -0,0 +1,1466 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static io.grpc.ConnectivityState.IDLE;
+import static io.grpc.ConnectivityState.SHUTDOWN;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+import static io.grpc.internal.ServiceConfigInterceptor.HEDGING_POLICY_KEY;
+import static io.grpc.internal.ServiceConfigInterceptor.RETRY_POLICY_KEY;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Supplier;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.Attributes;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ClientInterceptors;
+import io.grpc.ClientStreamTracer;
+import io.grpc.CompressorRegistry;
+import io.grpc.ConnectivityState;
+import io.grpc.ConnectivityStateInfo;
+import io.grpc.Context;
+import io.grpc.DecompressorRegistry;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.ChannelStats;
+import io.grpc.InternalChannelz.ChannelTrace;
+import io.grpc.InternalInstrumented;
+import io.grpc.InternalLogId;
+import io.grpc.InternalWithLogId;
+import io.grpc.LoadBalancer;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.NameResolver;
+import io.grpc.Status;
+import io.grpc.internal.ClientCallImpl.ClientTransportProvider;
+import io.grpc.internal.RetriableStream.ChannelBufferMeter;
+import io.grpc.internal.RetriableStream.Throttle;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+/** A communication channel for making outgoing RPCs. */
+@ThreadSafe
+final class ManagedChannelImpl extends ManagedChannel implements
+    InternalInstrumented<ChannelStats> {
+  static final Logger logger = Logger.getLogger(ManagedChannelImpl.class.getName());
+
+  // Matching this pattern means the target string is a URI target or at least intended to be one.
+  // A URI target must be an absolute hierarchical URI.
+  // From RFC 2396: scheme = alpha *( alpha | digit | "+" | "-" | "." )
+  @VisibleForTesting
+  static final Pattern URI_PATTERN = Pattern.compile("[a-zA-Z][a-zA-Z0-9+.-]*:/.*");
+
+  static final long IDLE_TIMEOUT_MILLIS_DISABLE = -1;
+
+  @VisibleForTesting
+  static final long SUBCHANNEL_SHUTDOWN_DELAY_SECONDS = 5;
+
+  @VisibleForTesting
+  static final Status SHUTDOWN_NOW_STATUS =
+      Status.UNAVAILABLE.withDescription("Channel shutdownNow invoked");
+
+  @VisibleForTesting
+  static final Status SHUTDOWN_STATUS =
+      Status.UNAVAILABLE.withDescription("Channel shutdown invoked");
+
+  @VisibleForTesting
+  static final Status SUBCHANNEL_SHUTDOWN_STATUS =
+      Status.UNAVAILABLE.withDescription("Subchannel shutdown invoked");
+
+  private final InternalLogId logId = InternalLogId.allocate(getClass().getName());
+  private final String target;
+  private final NameResolver.Factory nameResolverFactory;
+  private final Attributes nameResolverParams;
+  private final LoadBalancer.Factory loadBalancerFactory;
+  private final ClientTransportFactory transportFactory;
+  private final Executor executor;
+  private final ObjectPool<? extends Executor> executorPool;
+  private final ObjectPool<? extends Executor> oobExecutorPool;
+  private final TimeProvider timeProvider;
+  private final int maxTraceEvents;
+
+  private final ChannelExecutor channelExecutor = new ChannelExecutor() {
+      @Override
+      void handleUncaughtThrowable(Throwable t) {
+        super.handleUncaughtThrowable(t);
+        panic(t);
+      }
+    };
+
+  private boolean fullStreamDecompression;
+
+  private final DecompressorRegistry decompressorRegistry;
+  private final CompressorRegistry compressorRegistry;
+
+  private final Supplier<Stopwatch> stopwatchSupplier;
+  /** The timout before entering idle mode. */
+  private final long idleTimeoutMillis;
+
+  private final ConnectivityStateManager channelStateManager = new ConnectivityStateManager();
+
+  private final ServiceConfigInterceptor serviceConfigInterceptor;
+
+  private final BackoffPolicy.Provider backoffPolicyProvider;
+
+  /**
+   * We delegate to this channel, so that we can have interceptors as necessary. If there aren't
+   * any interceptors and the {@link io.grpc.BinaryLog} is {@code null} then this will just be a
+   * {@link RealChannel}.
+   */
+  private final Channel interceptorChannel;
+  @Nullable private final String userAgent;
+
+  // Only null after channel is terminated. Must be assigned from the channelExecutor.
+  private NameResolver nameResolver;
+
+  // Must be accessed from the channelExecutor.
+  private boolean nameResolverStarted;
+
+  // null when channel is in idle mode.  Must be assigned from channelExecutor.
+  @Nullable
+  private LbHelperImpl lbHelper;
+
+  // Must ONLY be assigned from updateSubchannelPicker(), which is called from channelExecutor.
+  // null if channel is in idle mode.
+  @Nullable
+  private volatile SubchannelPicker subchannelPicker;
+
+  // Must be accessed from the channelExecutor
+  private boolean panicMode;
+
+  // Must be mutated from channelExecutor
+  // If any monitoring hook to be added later needs to get a snapshot of this Set, we could
+  // switch to a ConcurrentHashMap.
+  private final Set<InternalSubchannel> subchannels = new HashSet<InternalSubchannel>(16, .75f);
+
+  // Must be mutated from channelExecutor
+  private final Set<OobChannel> oobChannels = new HashSet<OobChannel>(1, .75f);
+
+  // reprocess() must be run from channelExecutor
+  private final DelayedClientTransport delayedTransport;
+  private final UncommittedRetriableStreamsRegistry uncommittedRetriableStreamsRegistry
+      = new UncommittedRetriableStreamsRegistry();
+
+  // Shutdown states.
+  //
+  // Channel's shutdown process:
+  // 1. shutdown(): stop accepting new calls from applications
+  //   1a shutdown <- true
+  //   1b subchannelPicker <- null
+  //   1c delayedTransport.shutdown()
+  // 2. delayedTransport terminated: stop stream-creation functionality
+  //   2a terminating <- true
+  //   2b loadBalancer.shutdown()
+  //     * LoadBalancer will shutdown subchannels and OOB channels
+  //   2c loadBalancer <- null
+  //   2d nameResolver.shutdown()
+  //   2e nameResolver <- null
+  // 3. All subchannels and OOB channels terminated: Channel considered terminated
+
+  private final AtomicBoolean shutdown = new AtomicBoolean(false);
+  // Must only be mutated and read from channelExecutor
+  private boolean shutdownNowed;
+  // Must be mutated from channelExecutor
+  private volatile boolean terminating;
+  // Must be mutated from channelExecutor
+  private volatile boolean terminated;
+  private final CountDownLatch terminatedLatch = new CountDownLatch(1);
+
+  private final CallTracer.Factory callTracerFactory;
+  private final CallTracer channelCallTracer;
+  @CheckForNull
+  private final ChannelTracer channelTracer;
+  private final InternalChannelz channelz;
+  @CheckForNull
+  private Boolean haveBackends; // a flag for doing channel tracing when flipped
+  @Nullable
+  private Map<String, Object> lastServiceConfig; // used for channel tracing when value changed
+
+  // One instance per channel.
+  private final ChannelBufferMeter channelBufferUsed = new ChannelBufferMeter();
+
+  @Nullable
+  private Throttle throttle;
+
+  private final long perRpcBufferLimit;
+  private final long channelBufferLimit;
+
+  // Temporary false flag that can skip the retry code path.
+  private final boolean retryEnabled;
+
+  // Called from channelExecutor
+  private final ManagedClientTransport.Listener delayedTransportListener =
+      new ManagedClientTransport.Listener() {
+        @Override
+        public void transportShutdown(Status s) {
+          checkState(shutdown.get(), "Channel must have been shut down");
+        }
+
+        @Override
+        public void transportReady() {
+          // Don't care
+        }
+
+        @Override
+        public void transportInUse(final boolean inUse) {
+          inUseStateAggregator.updateObjectInUse(delayedTransport, inUse);
+        }
+
+        @Override
+        public void transportTerminated() {
+          checkState(shutdown.get(), "Channel must have been shut down");
+          terminating = true;
+          shutdownNameResolverAndLoadBalancer(false);
+          // No need to call channelStateManager since we are already in SHUTDOWN state.
+          // Until LoadBalancer is shutdown, it may still create new subchannels.  We catch them
+          // here.
+          maybeShutdownNowSubchannels();
+          maybeTerminateChannel();
+        }
+      };
+
+  // Must be called from channelExecutor
+  private void maybeShutdownNowSubchannels() {
+    if (shutdownNowed) {
+      for (InternalSubchannel subchannel : subchannels) {
+        subchannel.shutdownNow(SHUTDOWN_NOW_STATUS);
+      }
+      for (OobChannel oobChannel : oobChannels) {
+        oobChannel.getInternalSubchannel().shutdownNow(SHUTDOWN_NOW_STATUS);
+      }
+    }
+  }
+
+  // Must be accessed from channelExecutor
+  @VisibleForTesting
+  final InUseStateAggregator<Object> inUseStateAggregator =
+      new InUseStateAggregator<Object>() {
+        @Override
+        void handleInUse() {
+          exitIdleMode();
+        }
+
+        @Override
+        void handleNotInUse() {
+          if (shutdown.get()) {
+            return;
+          }
+          rescheduleIdleTimer();
+        }
+      };
+
+  @Override
+  public ListenableFuture<ChannelStats> getStats() {
+    final SettableFuture<ChannelStats> ret = SettableFuture.create();
+    // subchannels and oobchannels can only be accessed from channelExecutor
+    channelExecutor.executeLater(new Runnable() {
+      @Override
+      public void run() {
+        ChannelStats.Builder builder = new InternalChannelz.ChannelStats.Builder();
+        channelCallTracer.updateBuilder(builder);
+        if (channelTracer != null) {
+          channelTracer.updateBuilder(builder);
+        }
+        builder.setTarget(target).setState(channelStateManager.getState());
+        List<InternalWithLogId> children = new ArrayList<>();
+        children.addAll(subchannels);
+        children.addAll(oobChannels);
+        builder.setSubchannels(children);
+        ret.set(builder.build());
+      }
+    }).drain();
+    return ret;
+  }
+
+  @Override
+  public InternalLogId getLogId() {
+    return logId;
+  }
+
+  // Run from channelExecutor
+  private class IdleModeTimer implements Runnable {
+
+    @Override
+    public void run() {
+      enterIdleMode();
+    }
+  }
+
+  // Must be called from channelExecutor
+  private void shutdownNameResolverAndLoadBalancer(boolean verifyActive) {
+    if (verifyActive) {
+      checkState(nameResolver != null, "nameResolver is null");
+      checkState(lbHelper != null, "lbHelper is null");
+    }
+    if (nameResolver != null) {
+      cancelNameResolverBackoff();
+      nameResolver.shutdown();
+      nameResolver = null;
+      nameResolverStarted = false;
+    }
+    if (lbHelper != null) {
+      lbHelper.lb.shutdown();
+      lbHelper = null;
+    }
+    subchannelPicker = null;
+  }
+
+  /**
+   * Make the channel exit idle mode, if it's in it.
+   *
+   * <p>Must be called from channelExecutor
+   */
+  @VisibleForTesting
+  void exitIdleMode() {
+    if (shutdown.get() || panicMode) {
+      return;
+    }
+    if (inUseStateAggregator.isInUse()) {
+      // Cancel the timer now, so that a racing due timer will not put Channel on idleness
+      // when the caller of exitIdleMode() is about to use the returned loadBalancer.
+      cancelIdleTimer(false);
+    } else {
+      // exitIdleMode() may be called outside of inUseStateAggregator.handleNotInUse() while
+      // isInUse() == false, in which case we still need to schedule the timer.
+      rescheduleIdleTimer();
+    }
+    if (lbHelper != null) {
+      return;
+    }
+    logger.log(Level.FINE, "[{0}] Exiting idle mode", getLogId());
+    lbHelper = new LbHelperImpl(nameResolver);
+    lbHelper.lb = loadBalancerFactory.newLoadBalancer(lbHelper);
+
+    NameResolverListenerImpl listener = new NameResolverListenerImpl(lbHelper);
+    try {
+      nameResolver.start(listener);
+      nameResolverStarted = true;
+    } catch (Throwable t) {
+      listener.onError(Status.fromThrowable(t));
+    }
+  }
+
+  // Must be run from channelExecutor
+  private void enterIdleMode() {
+    logger.log(Level.FINE, "[{0}] Entering idle mode", getLogId());
+    // nameResolver and loadBalancer are guaranteed to be non-null.  If any of them were null,
+    // either the idleModeTimer ran twice without exiting the idle mode, or the task in shutdown()
+    // did not cancel idleModeTimer, or enterIdle() ran while shutdown or in idle, all of
+    // which are bugs.
+    shutdownNameResolverAndLoadBalancer(true);
+    delayedTransport.reprocess(null);
+    nameResolver = getNameResolver(target, nameResolverFactory, nameResolverParams);
+    if (channelTracer != null) {
+      channelTracer.reportEvent(
+          new ChannelTrace.Event.Builder()
+              .setDescription("Entering IDLE state")
+              .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+              .setTimestampNanos(timeProvider.currentTimeNanos())
+              .build());
+    }
+    channelStateManager.gotoState(IDLE);
+    if (inUseStateAggregator.isInUse()) {
+      exitIdleMode();
+    }
+  }
+
+  // Must be run from channelExecutor
+  private void cancelIdleTimer(boolean permanent) {
+    idleTimer.cancel(permanent);
+  }
+
+  // Always run from channelExecutor
+  private void rescheduleIdleTimer() {
+    if (idleTimeoutMillis == IDLE_TIMEOUT_MILLIS_DISABLE) {
+      return;
+    }
+    idleTimer.reschedule(idleTimeoutMillis, TimeUnit.MILLISECONDS);
+  }
+
+  // Run from channelExecutor
+  @VisibleForTesting
+  class NameResolverRefresh implements Runnable {
+    // Only mutated from channelExecutor
+    boolean cancelled;
+
+    @Override
+    public void run() {
+      if (cancelled) {
+        // Race detected: this task was scheduled on channelExecutor before
+        // cancelNameResolverBackoff() could cancel the timer.
+        return;
+      }
+      nameResolverRefreshFuture = null;
+      nameResolverRefresh = null;
+      if (nameResolver != null) {
+        nameResolver.refresh();
+      }
+    }
+  }
+
+  // Must be used from channelExecutor
+  @Nullable private ScheduledFuture<?> nameResolverRefreshFuture;
+  // Must be used from channelExecutor
+  @Nullable private NameResolverRefresh nameResolverRefresh;
+  // The policy to control backoff between name resolution attempts. Non-null when an attempt is
+  // scheduled. Must be used from channelExecutor
+  @Nullable private BackoffPolicy nameResolverBackoffPolicy;
+
+  // Must be run from channelExecutor
+  private void cancelNameResolverBackoff() {
+    if (nameResolverRefreshFuture != null) {
+      nameResolverRefreshFuture.cancel(false);
+      nameResolverRefresh.cancelled = true;
+      nameResolverRefreshFuture = null;
+      nameResolverRefresh = null;
+      nameResolverBackoffPolicy = null;
+    }
+  }
+
+  private final ClientTransportProvider transportProvider = new ClientTransportProvider() {
+    @Override
+    public ClientTransport get(PickSubchannelArgs args) {
+      SubchannelPicker pickerCopy = subchannelPicker;
+      if (shutdown.get()) {
+        // If channel is shut down, delayedTransport is also shut down which will fail the stream
+        // properly.
+        return delayedTransport;
+      }
+      if (pickerCopy == null) {
+        channelExecutor.executeLater(new Runnable() {
+            @Override
+            public void run() {
+              exitIdleMode();
+            }
+          }).drain();
+        return delayedTransport;
+      }
+      // There is no need to reschedule the idle timer here.
+      //
+      // pickerCopy != null, which means idle timer has not expired when this method starts.
+      // Even if idle timer expires right after we grab pickerCopy, and it shuts down LoadBalancer
+      // which calls Subchannel.shutdown(), the InternalSubchannel will be actually shutdown after
+      // SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, which gives the caller time to start RPC on it.
+      //
+      // In most cases the idle timer is scheduled to fire after the transport has created the
+      // stream, which would have reported in-use state to the channel that would have cancelled
+      // the idle timer.
+      PickResult pickResult = pickerCopy.pickSubchannel(args);
+      ClientTransport transport = GrpcUtil.getTransportFromPickResult(
+          pickResult, args.getCallOptions().isWaitForReady());
+      if (transport != null) {
+        return transport;
+      }
+      return delayedTransport;
+    }
+
+    @Override
+    public <ReqT> RetriableStream<ReqT> newRetriableStream(
+        final MethodDescriptor<ReqT, ?> method,
+        final CallOptions callOptions,
+        final Metadata headers,
+        final Context context) {
+      checkState(retryEnabled, "retry should be enabled");
+      return new RetriableStream<ReqT>(
+          method, headers, channelBufferUsed, perRpcBufferLimit, channelBufferLimit,
+          getCallExecutor(callOptions), transportFactory.getScheduledExecutorService(),
+          callOptions.getOption(RETRY_POLICY_KEY), callOptions.getOption(HEDGING_POLICY_KEY),
+          throttle) {
+        @Override
+        Status prestart() {
+          return uncommittedRetriableStreamsRegistry.add(this);
+        }
+
+        @Override
+        void postCommit() {
+          uncommittedRetriableStreamsRegistry.remove(this);
+        }
+
+        @Override
+        ClientStream newSubstream(ClientStreamTracer.Factory tracerFactory, Metadata newHeaders) {
+          CallOptions newOptions = callOptions.withStreamTracerFactory(tracerFactory);
+          ClientTransport transport =
+              get(new PickSubchannelArgsImpl(method, newHeaders, newOptions));
+          Context origContext = context.attach();
+          try {
+            return transport.newStream(method, newHeaders, newOptions);
+          } finally {
+            context.detach(origContext);
+          }
+        }
+      };
+    }
+  };
+
+  private final Rescheduler idleTimer;
+
+  ManagedChannelImpl(
+      AbstractManagedChannelImplBuilder<?> builder,
+      ClientTransportFactory clientTransportFactory,
+      BackoffPolicy.Provider backoffPolicyProvider,
+      ObjectPool<? extends Executor> oobExecutorPool,
+      Supplier<Stopwatch> stopwatchSupplier,
+      List<ClientInterceptor> interceptors,
+      final TimeProvider timeProvider) {
+    this.target = checkNotNull(builder.target, "target");
+    this.nameResolverFactory = builder.getNameResolverFactory();
+    this.nameResolverParams = checkNotNull(builder.getNameResolverParams(), "nameResolverParams");
+    this.nameResolver = getNameResolver(target, nameResolverFactory, nameResolverParams);
+    if (builder.loadBalancerFactory == null) {
+      this.loadBalancerFactory = new AutoConfiguredLoadBalancerFactory();
+    } else {
+      this.loadBalancerFactory = builder.loadBalancerFactory;
+    }
+    this.executorPool = checkNotNull(builder.executorPool, "executorPool");
+    this.oobExecutorPool = checkNotNull(oobExecutorPool, "oobExecutorPool");
+    this.executor = checkNotNull(executorPool.getObject(), "executor");
+    this.delayedTransport = new DelayedClientTransport(this.executor, this.channelExecutor);
+    this.delayedTransport.start(delayedTransportListener);
+    this.backoffPolicyProvider = backoffPolicyProvider;
+    this.transportFactory =
+        new CallCredentialsApplyingTransportFactory(clientTransportFactory, this.executor);
+    this.retryEnabled = builder.retryEnabled && !builder.temporarilyDisableRetry;
+    serviceConfigInterceptor = new ServiceConfigInterceptor(
+        retryEnabled, builder.maxRetryAttempts, builder.maxHedgedAttempts);
+    Channel channel = new RealChannel();
+    channel = ClientInterceptors.intercept(channel, serviceConfigInterceptor);
+    if (builder.binlog != null) {
+      channel = builder.binlog.wrapChannel(channel);
+    }
+    this.interceptorChannel = ClientInterceptors.intercept(channel, interceptors);
+    this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier");
+    if (builder.idleTimeoutMillis == IDLE_TIMEOUT_MILLIS_DISABLE) {
+      this.idleTimeoutMillis = builder.idleTimeoutMillis;
+    } else {
+      checkArgument(
+          builder.idleTimeoutMillis
+              >= AbstractManagedChannelImplBuilder.IDLE_MODE_MIN_TIMEOUT_MILLIS,
+          "invalid idleTimeoutMillis %s", builder.idleTimeoutMillis);
+      this.idleTimeoutMillis = builder.idleTimeoutMillis;
+    }
+
+    final class AutoDrainChannelExecutor implements Executor {
+
+      @Override
+      public void execute(Runnable command) {
+        channelExecutor.executeLater(command);
+        channelExecutor.drain();
+      }
+    }
+
+    idleTimer = new Rescheduler(
+        new IdleModeTimer(),
+        new AutoDrainChannelExecutor(),
+        transportFactory.getScheduledExecutorService(),
+        stopwatchSupplier.get());
+    this.fullStreamDecompression = builder.fullStreamDecompression;
+    this.decompressorRegistry = checkNotNull(builder.decompressorRegistry, "decompressorRegistry");
+    this.compressorRegistry = checkNotNull(builder.compressorRegistry, "compressorRegistry");
+    this.userAgent = builder.userAgent;
+
+    this.channelBufferLimit = builder.retryBufferSize;
+    this.perRpcBufferLimit = builder.perRpcBufferLimit;
+    this.timeProvider = checkNotNull(timeProvider, "timeProvider");
+    this.callTracerFactory = new CallTracer.Factory() {
+      @Override
+      public CallTracer create() {
+        return new CallTracer(timeProvider);
+      }
+    };
+    channelCallTracer = callTracerFactory.create();
+    this.channelz = checkNotNull(builder.channelz);
+    channelz.addRootChannel(this);
+
+    maxTraceEvents = builder.maxTraceEvents;
+    if (maxTraceEvents > 0) {
+      long currentTimeNanos = timeProvider.currentTimeNanos();
+      channelTracer = new ChannelTracer(builder.maxTraceEvents, currentTimeNanos, "Channel");
+    } else {
+      channelTracer = null;
+    }
+    logger.log(Level.FINE, "[{0}] Created with target {1}", new Object[] {getLogId(), target});
+  }
+
+  @VisibleForTesting
+  static NameResolver getNameResolver(String target, NameResolver.Factory nameResolverFactory,
+      Attributes nameResolverParams) {
+    // Finding a NameResolver. Try using the target string as the URI. If that fails, try prepending
+    // "dns:///".
+    URI targetUri = null;
+    StringBuilder uriSyntaxErrors = new StringBuilder();
+    try {
+      targetUri = new URI(target);
+      // For "localhost:8080" this would likely cause newNameResolver to return null, because
+      // "localhost" is parsed as the scheme. Will fall into the next branch and try
+      // "dns:///localhost:8080".
+    } catch (URISyntaxException e) {
+      // Can happen with ip addresses like "[::1]:1234" or 127.0.0.1:1234.
+      uriSyntaxErrors.append(e.getMessage());
+    }
+    if (targetUri != null) {
+      NameResolver resolver = nameResolverFactory.newNameResolver(targetUri, nameResolverParams);
+      if (resolver != null) {
+        return resolver;
+      }
+      // "foo.googleapis.com:8080" cause resolver to be null, because "foo.googleapis.com" is an
+      // unmapped scheme. Just fall through and will try "dns:///foo.googleapis.com:8080"
+    }
+
+    // If we reached here, the targetUri couldn't be used.
+    if (!URI_PATTERN.matcher(target).matches()) {
+      // It doesn't look like a URI target. Maybe it's an authority string. Try with the default
+      // scheme from the factory.
+      try {
+        targetUri = new URI(nameResolverFactory.getDefaultScheme(), "", "/" + target, null);
+      } catch (URISyntaxException e) {
+        // Should not be possible.
+        throw new IllegalArgumentException(e);
+      }
+      NameResolver resolver = nameResolverFactory.newNameResolver(targetUri, nameResolverParams);
+      if (resolver != null) {
+        return resolver;
+      }
+    }
+    throw new IllegalArgumentException(String.format(
+        "cannot find a NameResolver for %s%s",
+        target, uriSyntaxErrors.length() > 0 ? " (" + uriSyntaxErrors + ")" : ""));
+  }
+
+  /**
+   * Initiates an orderly shutdown in which preexisting calls continue but new calls are immediately
+   * cancelled.
+   */
+  @Override
+  public ManagedChannelImpl shutdown() {
+    logger.log(Level.FINE, "[{0}] shutdown() called", getLogId());
+    if (!shutdown.compareAndSet(false, true)) {
+      return this;
+    }
+
+    // Put gotoState(SHUTDOWN) as early into the channelExecutor's queue as possible.
+    // delayedTransport.shutdown() may also add some tasks into the queue. But some things inside
+    // delayedTransport.shutdown() like setting delayedTransport.shutdown = true are not run in the
+    // channelExecutor's queue and should not be blocked, so we do not drain() immediately here.
+    channelExecutor.executeLater(new Runnable() {
+      @Override
+      public void run() {
+        if (channelTracer != null) {
+          channelTracer.reportEvent(new ChannelTrace.Event.Builder()
+              .setDescription("Entering SHUTDOWN state")
+              .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+              .setTimestampNanos(timeProvider.currentTimeNanos())
+              .build());
+        }
+        channelStateManager.gotoState(SHUTDOWN);
+      }
+    });
+
+    uncommittedRetriableStreamsRegistry.onShutdown(SHUTDOWN_STATUS);
+    channelExecutor.executeLater(new Runnable() {
+        @Override
+        public void run() {
+          cancelIdleTimer(/* permanent= */ true);
+        }
+      }).drain();
+    logger.log(Level.FINE, "[{0}] Shutting down", getLogId());
+    return this;
+  }
+
+  /**
+   * Initiates a forceful shutdown in which preexisting and new calls are cancelled. Although
+   * forceful, the shutdown process is still not instantaneous; {@link #isTerminated()} will likely
+   * return {@code false} immediately after this method returns.
+   */
+  @Override
+  public ManagedChannelImpl shutdownNow() {
+    logger.log(Level.FINE, "[{0}] shutdownNow() called", getLogId());
+    shutdown();
+    uncommittedRetriableStreamsRegistry.onShutdownNow(SHUTDOWN_NOW_STATUS);
+    channelExecutor.executeLater(new Runnable() {
+        @Override
+        public void run() {
+          if (shutdownNowed) {
+            return;
+          }
+          shutdownNowed = true;
+          maybeShutdownNowSubchannels();
+        }
+      }).drain();
+    return this;
+  }
+
+  // Called from channelExecutor
+  @VisibleForTesting
+  void panic(final Throwable t) {
+    if (panicMode) {
+      // Preserve the first panic information
+      return;
+    }
+    panicMode = true;
+    cancelIdleTimer(/* permanent= */ true);
+    shutdownNameResolverAndLoadBalancer(false);
+    SubchannelPicker newPicker = new SubchannelPicker() {
+      final PickResult panicPickResult =
+          PickResult.withDrop(
+              Status.INTERNAL.withDescription("Panic! This is a bug!").withCause(t));
+      @Override
+      public PickResult pickSubchannel(PickSubchannelArgs args) {
+        return panicPickResult;
+      }
+    };
+    updateSubchannelPicker(newPicker);
+    if (channelTracer != null) {
+      channelTracer.reportEvent(
+          new ChannelTrace.Event.Builder()
+              .setDescription("Entering TRANSIENT_FAILURE state")
+              .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+              .setTimestampNanos(timeProvider.currentTimeNanos())
+              .build());
+    }
+    channelStateManager.gotoState(TRANSIENT_FAILURE);
+  }
+
+  // Called from channelExecutor
+  private void updateSubchannelPicker(SubchannelPicker newPicker) {
+    subchannelPicker = newPicker;
+    delayedTransport.reprocess(newPicker);
+  }
+
+  @Override
+  public boolean isShutdown() {
+    return shutdown.get();
+  }
+
+  @Override
+  public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+    return terminatedLatch.await(timeout, unit);
+  }
+
+  @Override
+  public boolean isTerminated() {
+    return terminated;
+  }
+
+  /*
+   * Creates a new outgoing call on the channel.
+   */
+  @Override
+  public <ReqT, RespT> ClientCall<ReqT, RespT> newCall(MethodDescriptor<ReqT, RespT> method,
+      CallOptions callOptions) {
+    return interceptorChannel.newCall(method, callOptions);
+  }
+
+  @Override
+  public String authority() {
+    return interceptorChannel.authority();
+  }
+
+  private Executor getCallExecutor(CallOptions callOptions) {
+    Executor executor = callOptions.getExecutor();
+    if (executor == null) {
+      executor = this.executor;
+    }
+    return executor;
+  }
+
+  private class RealChannel extends Channel {
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> newCall(MethodDescriptor<ReqT, RespT> method,
+        CallOptions callOptions) {
+      return new ClientCallImpl<ReqT, RespT>(
+              method,
+              getCallExecutor(callOptions),
+              callOptions,
+              transportProvider,
+              terminated ? null : transportFactory.getScheduledExecutorService(),
+              channelCallTracer,
+              retryEnabled)
+          .setFullStreamDecompression(fullStreamDecompression)
+          .setDecompressorRegistry(decompressorRegistry)
+          .setCompressorRegistry(compressorRegistry);
+    }
+
+    @Override
+    public String authority() {
+      String authority = nameResolver.getServiceAuthority();
+      return checkNotNull(authority, "authority");
+    }
+  }
+
+  /**
+   * Terminate the channel if termination conditions are met.
+   */
+  // Must be run from channelExecutor
+  private void maybeTerminateChannel() {
+    if (terminated) {
+      return;
+    }
+    if (shutdown.get() && subchannels.isEmpty() && oobChannels.isEmpty()) {
+      logger.log(Level.FINE, "[{0}] Terminated", getLogId());
+      channelz.removeRootChannel(this);
+      terminated = true;
+      terminatedLatch.countDown();
+      executorPool.returnObject(executor);
+      // Release the transport factory so that it can deallocate any resources.
+      transportFactory.close();
+    }
+  }
+
+  @Override
+  public ConnectivityState getState(boolean requestConnection) {
+    ConnectivityState savedChannelState = channelStateManager.getState();
+    if (requestConnection && savedChannelState == IDLE) {
+      channelExecutor.executeLater(
+          new Runnable() {
+            @Override
+            public void run() {
+              exitIdleMode();
+              if (subchannelPicker != null) {
+                subchannelPicker.requestConnection();
+              }
+            }
+          }).drain();
+    }
+    return savedChannelState;
+  }
+
+  @Override
+  public void notifyWhenStateChanged(final ConnectivityState source, final Runnable callback) {
+    channelExecutor.executeLater(
+        new Runnable() {
+          @Override
+          public void run() {
+            channelStateManager.notifyWhenStateChanged(callback, executor, source);
+          }
+        }).drain();
+  }
+
+  @Override
+  public void resetConnectBackoff() {
+    channelExecutor
+        .executeLater(
+            new Runnable() {
+              @Override
+              public void run() {
+                if (shutdown.get()) {
+                  return;
+                }
+                if (nameResolverRefreshFuture != null) {
+                  checkState(nameResolverStarted, "name resolver must be started");
+                  cancelNameResolverBackoff();
+                  nameResolver.refresh();
+                }
+                for (InternalSubchannel subchannel : subchannels) {
+                  subchannel.resetConnectBackoff();
+                }
+                for (OobChannel oobChannel : oobChannels) {
+                  oobChannel.resetConnectBackoff();
+                }
+              }
+            })
+        .drain();
+  }
+
+  @Override
+  public void enterIdle() {
+    class PrepareToLoseNetworkRunnable implements Runnable {
+      @Override
+      public void run() {
+        if (shutdown.get() || lbHelper == null) {
+          return;
+        }
+        cancelIdleTimer(/* permanent= */ false);
+        enterIdleMode();
+      }
+    }
+
+    channelExecutor.executeLater(new PrepareToLoseNetworkRunnable()).drain();
+  }
+
+  /**
+   * A registry that prevents channel shutdown from killing existing retry attempts that are in
+   * backoff.
+   */
+  private final class UncommittedRetriableStreamsRegistry {
+    // TODO(zdapeng): This means we would acquire a lock for each new retry-able stream,
+    // it's worthwhile to look for a lock-free approach.
+    final Object lock = new Object();
+
+    @GuardedBy("lock")
+    Collection<ClientStream> uncommittedRetriableStreams = new HashSet<ClientStream>();
+
+    @GuardedBy("lock")
+    Status shutdownStatus;
+
+    void onShutdown(Status reason) {
+      boolean shouldShutdownDelayedTransport = false;
+      synchronized (lock) {
+        if (shutdownStatus != null) {
+          return;
+        }
+        shutdownStatus = reason;
+        // Keep the delayedTransport open until there is no more uncommitted streams, b/c those
+        // retriable streams, which may be in backoff and not using any transport, are already
+        // started RPCs.
+        if (uncommittedRetriableStreams.isEmpty()) {
+          shouldShutdownDelayedTransport = true;
+        }
+      }
+
+      if (shouldShutdownDelayedTransport) {
+        delayedTransport.shutdown(reason);
+      }
+    }
+
+    void onShutdownNow(Status reason) {
+      onShutdown(reason);
+      Collection<ClientStream> streams;
+
+      synchronized (lock) {
+        streams = new ArrayList<>(uncommittedRetriableStreams);
+      }
+
+      for (ClientStream stream : streams) {
+        stream.cancel(reason);
+      }
+      delayedTransport.shutdownNow(reason);
+    }
+
+    /**
+     * Registers a RetriableStream and return null if not shutdown, otherwise just returns the
+     * shutdown Status.
+     */
+    @Nullable
+    Status add(RetriableStream<?> retriableStream) {
+      synchronized (lock) {
+        if (shutdownStatus != null) {
+          return shutdownStatus;
+        }
+        uncommittedRetriableStreams.add(retriableStream);
+        return null;
+      }
+    }
+
+    void remove(RetriableStream<?> retriableStream) {
+      Status shutdownStatusCopy = null;
+
+      synchronized (lock) {
+        uncommittedRetriableStreams.remove(retriableStream);
+        if (uncommittedRetriableStreams.isEmpty()) {
+          shutdownStatusCopy = shutdownStatus;
+          // Because retriable transport is long-lived, we take this opportunity to down-size the
+          // hashmap.
+          uncommittedRetriableStreams = new HashSet<ClientStream>();
+        }
+      }
+
+      if (shutdownStatusCopy != null) {
+        delayedTransport.shutdown(shutdownStatusCopy);
+      }
+    }
+  }
+
+  private class LbHelperImpl extends LoadBalancer.Helper {
+    LoadBalancer lb;
+    final NameResolver nr;
+
+    LbHelperImpl(NameResolver nr) {
+      this.nr = checkNotNull(nr, "NameResolver");
+    }
+
+    // Must be called from channelExecutor
+    private void handleInternalSubchannelState(ConnectivityStateInfo newState) {
+      if (newState.getState() == TRANSIENT_FAILURE || newState.getState() == IDLE) {
+        nr.refresh();
+      }
+    }
+
+    @Override
+    public AbstractSubchannel createSubchannel(
+        List<EquivalentAddressGroup> addressGroups, Attributes attrs) {
+      checkNotNull(addressGroups, "addressGroups");
+      checkNotNull(attrs, "attrs");
+      // TODO(ejona): can we be even stricter? Like loadBalancer == null?
+      checkState(!terminated, "Channel is terminated");
+      final SubchannelImpl subchannel = new SubchannelImpl(attrs);
+      ChannelTracer subchannelTracer = null;
+      long subchannelCreationTime = timeProvider.currentTimeNanos();
+      if (maxTraceEvents > 0) {
+        subchannelTracer = new ChannelTracer(maxTraceEvents, subchannelCreationTime, "Subchannel");
+      }
+      final InternalSubchannel internalSubchannel = new InternalSubchannel(
+          addressGroups,
+          authority(),
+          userAgent,
+          backoffPolicyProvider,
+          transportFactory,
+          transportFactory.getScheduledExecutorService(),
+          stopwatchSupplier,
+          channelExecutor,
+          new InternalSubchannel.Callback() {
+            // All callbacks are run in channelExecutor
+            @Override
+            void onTerminated(InternalSubchannel is) {
+              subchannels.remove(is);
+              channelz.removeSubchannel(is);
+              maybeTerminateChannel();
+            }
+
+            @Override
+            void onStateChange(InternalSubchannel is, ConnectivityStateInfo newState) {
+              handleInternalSubchannelState(newState);
+              // Call LB only if it's not shutdown.  If LB is shutdown, lbHelper won't match.
+              if (LbHelperImpl.this == ManagedChannelImpl.this.lbHelper) {
+                lb.handleSubchannelState(subchannel, newState);
+              }
+            }
+
+            @Override
+            void onInUse(InternalSubchannel is) {
+              inUseStateAggregator.updateObjectInUse(is, true);
+            }
+
+            @Override
+            void onNotInUse(InternalSubchannel is) {
+              inUseStateAggregator.updateObjectInUse(is, false);
+            }
+          },
+          channelz,
+          callTracerFactory.create(),
+          subchannelTracer,
+          timeProvider);
+      if (channelTracer != null) {
+        channelTracer.reportEvent(new ChannelTrace.Event.Builder()
+            .setDescription("Child channel created")
+            .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+            .setTimestampNanos(subchannelCreationTime)
+            .setSubchannelRef(internalSubchannel)
+            .build());
+      }
+      channelz.addSubchannel(internalSubchannel);
+      subchannel.subchannel = internalSubchannel;
+      logger.log(Level.FINE, "[{0}] {1} created for {2}",
+          new Object[] {getLogId(), internalSubchannel.getLogId(), addressGroups});
+      runSerialized(new Runnable() {
+          @Override
+          public void run() {
+            if (terminating) {
+              // Because runSerialized() doesn't guarantee the runnable has been executed upon when
+              // returning, the subchannel may still be returned to the balancer without being
+              // shutdown even if "terminating" is already true.  The subchannel will not be used in
+              // this case, because delayed transport has terminated when "terminating" becomes
+              // true, and no more requests will be sent to balancer beyond this point.
+              internalSubchannel.shutdown(SHUTDOWN_STATUS);
+            }
+            if (!terminated) {
+              // If channel has not terminated, it will track the subchannel and block termination
+              // for it.
+              subchannels.add(internalSubchannel);
+            }
+          }
+        });
+      return subchannel;
+    }
+
+    @Override
+    public void updateBalancingState(
+        final ConnectivityState newState, final SubchannelPicker newPicker) {
+      checkNotNull(newState, "newState");
+      checkNotNull(newPicker, "newPicker");
+
+      runSerialized(
+          new Runnable() {
+            @Override
+            public void run() {
+              if (LbHelperImpl.this != lbHelper) {
+                return;
+              }
+              updateSubchannelPicker(newPicker);
+              // It's not appropriate to report SHUTDOWN state from lb.
+              // Ignore the case of newState == SHUTDOWN for now.
+              if (newState != SHUTDOWN) {
+                if (channelTracer != null) {
+                  channelTracer.reportEvent(
+                      new ChannelTrace.Event.Builder()
+                          .setDescription("Entering " + newState + " state")
+                          .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+                          .setTimestampNanos(timeProvider.currentTimeNanos())
+                          .build());
+                }
+                channelStateManager.gotoState(newState);
+              }
+            }
+          });
+    }
+
+    @Override
+    public void updateSubchannelAddresses(
+        LoadBalancer.Subchannel subchannel, List<EquivalentAddressGroup> addrs) {
+      checkArgument(subchannel instanceof SubchannelImpl,
+          "subchannel must have been returned from createSubchannel");
+      ((SubchannelImpl) subchannel).subchannel.updateAddresses(addrs);
+    }
+
+    @Override
+    public ManagedChannel createOobChannel(EquivalentAddressGroup addressGroup, String authority) {
+      // TODO(ejona): can we be even stricter? Like terminating?
+      checkState(!terminated, "Channel is terminated");
+      long oobChannelCreationTime = timeProvider.currentTimeNanos();
+      ChannelTracer oobChannelTracer = null;
+      ChannelTracer subchannelTracer = null;
+      if (channelTracer != null) {
+        oobChannelTracer = new ChannelTracer(maxTraceEvents, oobChannelCreationTime, "OobChannel");
+      }
+      final OobChannel oobChannel = new OobChannel(
+          authority, oobExecutorPool, transportFactory.getScheduledExecutorService(),
+          channelExecutor, callTracerFactory.create(), oobChannelTracer, channelz, timeProvider);
+      if (channelTracer != null) {
+        channelTracer.reportEvent(new ChannelTrace.Event.Builder()
+            .setDescription("Child channel created")
+            .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+            .setTimestampNanos(oobChannelCreationTime)
+            .setChannelRef(oobChannel)
+            .build());
+        subchannelTracer = new ChannelTracer(maxTraceEvents, oobChannelCreationTime, "Subchannel");
+      }
+      final InternalSubchannel internalSubchannel = new InternalSubchannel(
+          Collections.singletonList(addressGroup),
+          authority, userAgent, backoffPolicyProvider, transportFactory,
+          transportFactory.getScheduledExecutorService(), stopwatchSupplier, channelExecutor,
+          // All callback methods are run from channelExecutor
+          new InternalSubchannel.Callback() {
+            @Override
+            void onTerminated(InternalSubchannel is) {
+              oobChannels.remove(oobChannel);
+              channelz.removeSubchannel(is);
+              oobChannel.handleSubchannelTerminated();
+              maybeTerminateChannel();
+            }
+
+            @Override
+            void onStateChange(InternalSubchannel is, ConnectivityStateInfo newState) {
+              handleInternalSubchannelState(newState);
+              oobChannel.handleSubchannelStateChange(newState);
+            }
+          },
+          channelz,
+          callTracerFactory.create(),
+          subchannelTracer,
+          timeProvider);
+      if (oobChannelTracer != null) {
+        oobChannelTracer.reportEvent(new ChannelTrace.Event.Builder()
+            .setDescription("Child channel created")
+            .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+            .setTimestampNanos(oobChannelCreationTime)
+            .setSubchannelRef(internalSubchannel)
+            .build());
+      }
+      channelz.addSubchannel(oobChannel);
+      channelz.addSubchannel(internalSubchannel);
+      oobChannel.setSubchannel(internalSubchannel);
+      runSerialized(new Runnable() {
+          @Override
+          public void run() {
+            if (terminating) {
+              oobChannel.shutdown();
+            }
+            if (!terminated) {
+              // If channel has not terminated, it will track the subchannel and block termination
+              // for it.
+              oobChannels.add(oobChannel);
+            }
+          }
+        });
+      return oobChannel;
+    }
+
+    @Override
+    public void updateOobChannelAddresses(ManagedChannel channel, EquivalentAddressGroup eag) {
+      checkArgument(channel instanceof OobChannel,
+          "channel must have been returned from createOobChannel");
+      ((OobChannel) channel).updateAddresses(eag);
+    }
+
+    @Override
+    public String getAuthority() {
+      return ManagedChannelImpl.this.authority();
+    }
+
+    @Override
+    public NameResolver.Factory getNameResolverFactory() {
+      return nameResolverFactory;
+    }
+
+    @Override
+    public void runSerialized(Runnable task) {
+      channelExecutor.executeLater(task).drain();
+    }
+  }
+
+  private class NameResolverListenerImpl implements NameResolver.Listener {
+    final LbHelperImpl helper;
+
+    NameResolverListenerImpl(LbHelperImpl helperImpl) {
+      this.helper = helperImpl;
+    }
+
+    @Override
+    public void onAddresses(final List<EquivalentAddressGroup> servers, final Attributes config) {
+      if (servers.isEmpty()) {
+        onError(Status.UNAVAILABLE.withDescription("NameResolver returned an empty list"));
+        return;
+      }
+      if (logger.isLoggable(Level.FINE)) {
+        logger.log(Level.FINE, "[{0}] resolved address: {1}, config={2}",
+            new Object[]{getLogId(), servers, config});
+      }
+
+      if (channelTracer != null && (haveBackends == null || !haveBackends)) {
+        channelTracer.reportEvent(new ChannelTrace.Event.Builder()
+            .setDescription("Address resolved: " + servers)
+            .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+            .setTimestampNanos(timeProvider.currentTimeNanos())
+            .build());
+        haveBackends = true;
+      }
+      final Map<String, Object> serviceConfig =
+          config.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG);
+      if (channelTracer != null && serviceConfig != null
+          && !serviceConfig.equals(lastServiceConfig)) {
+        channelTracer.reportEvent(new ChannelTrace.Event.Builder()
+            .setDescription("Service config changed")
+            .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+            .setTimestampNanos(timeProvider.currentTimeNanos())
+            .build());
+        lastServiceConfig = serviceConfig;
+      }
+
+      final class NamesResolved implements Runnable {
+        @Override
+        public void run() {
+          // Call LB only if it's not shutdown.  If LB is shutdown, lbHelper won't match.
+          if (NameResolverListenerImpl.this.helper != ManagedChannelImpl.this.lbHelper) {
+            return;
+          }
+
+          nameResolverBackoffPolicy = null;
+
+          if (serviceConfig != null) {
+            try {
+              serviceConfigInterceptor.handleUpdate(serviceConfig);
+              if (retryEnabled) {
+                throttle = getThrottle(config);
+              }
+            } catch (RuntimeException re) {
+              logger.log(
+                  Level.WARNING,
+                  "[" + getLogId() + "] Unexpected exception from parsing service config",
+                  re);
+            }
+          }
+
+          helper.lb.handleResolvedAddressGroups(servers, config);
+        }
+      }
+
+      helper.runSerialized(new NamesResolved());
+    }
+
+    @Override
+    public void onError(final Status error) {
+      checkArgument(!error.isOk(), "the error status must not be OK");
+      logger.log(Level.WARNING, "[{0}] Failed to resolve name. status={1}",
+          new Object[] {getLogId(), error});
+      if (channelTracer != null && (haveBackends == null || haveBackends)) {
+        channelTracer.reportEvent(new ChannelTrace.Event.Builder()
+            .setDescription("Failed to resolve name")
+            .setSeverity(ChannelTrace.Event.Severity.CT_WARNING)
+            .setTimestampNanos(timeProvider.currentTimeNanos())
+            .build());
+        haveBackends = false;
+      }
+      channelExecutor
+          .executeLater(
+              new Runnable() {
+                @Override
+                public void run() {
+                  // Call LB only if it's not shutdown.  If LB is shutdown, lbHelper won't match.
+                  if (NameResolverListenerImpl.this.helper != ManagedChannelImpl.this.lbHelper) {
+                    return;
+                  }
+                  helper.lb.handleNameResolutionError(error);
+                  if (nameResolverRefreshFuture != null) {
+                    // The name resolver may invoke onError multiple times, but we only want to
+                    // schedule one backoff attempt
+                    // TODO(ericgribkoff) Update contract of NameResolver.Listener or decide if we
+                    // want to reset the backoff interval upon repeated onError() calls
+                    return;
+                  }
+                  if (nameResolverBackoffPolicy == null) {
+                    nameResolverBackoffPolicy = backoffPolicyProvider.get();
+                  }
+                  long delayNanos = nameResolverBackoffPolicy.nextBackoffNanos();
+                  if (logger.isLoggable(Level.FINE)) {
+                    logger.log(
+                        Level.FINE,
+                        "[{0}] Scheduling DNS resolution backoff for {1} ns",
+                        new Object[] {logId, delayNanos});
+                  }
+                  nameResolverRefresh = new NameResolverRefresh();
+                  nameResolverRefreshFuture =
+                      transportFactory
+                          .getScheduledExecutorService()
+                          .schedule(nameResolverRefresh, delayNanos, TimeUnit.NANOSECONDS);
+                }
+              })
+          .drain();
+    }
+  }
+
+  @Nullable
+  private static Throttle getThrottle(Attributes config) {
+    return ServiceConfigUtil.getThrottlePolicy(
+        config.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG));
+  }
+
+  private final class SubchannelImpl extends AbstractSubchannel {
+    // Set right after SubchannelImpl is created.
+    InternalSubchannel subchannel;
+    final Object shutdownLock = new Object();
+    final Attributes attrs;
+
+    @GuardedBy("shutdownLock")
+    boolean shutdownRequested;
+    @GuardedBy("shutdownLock")
+    ScheduledFuture<?> delayedShutdownTask;
+
+    SubchannelImpl(Attributes attrs) {
+      this.attrs = checkNotNull(attrs, "attrs");
+    }
+
+    @Override
+    ClientTransport obtainActiveTransport() {
+      return subchannel.obtainActiveTransport();
+    }
+
+    @Override
+    InternalInstrumented<ChannelStats> getInternalSubchannel() {
+      return subchannel;
+    }
+
+    @Override
+    public void shutdown() {
+      synchronized (shutdownLock) {
+        if (shutdownRequested) {
+          if (terminating && delayedShutdownTask != null) {
+            // shutdown() was previously called when terminating == false, thus a delayed shutdown()
+            // was scheduled.  Now since terminating == true, We should expedite the shutdown.
+            delayedShutdownTask.cancel(false);
+            delayedShutdownTask = null;
+            // Will fall through to the subchannel.shutdown() at the end.
+          } else {
+            return;
+          }
+        } else {
+          shutdownRequested = true;
+        }
+        // Add a delay to shutdown to deal with the race between 1) a transport being picked and
+        // newStream() being called on it, and 2) its Subchannel is shut down by LoadBalancer (e.g.,
+        // because of address change, or because LoadBalancer is shutdown by Channel entering idle
+        // mode). If (2) wins, the app will see a spurious error. We work around this by delaying
+        // shutdown of Subchannel for a few seconds here.
+        //
+        // TODO(zhangkun83): consider a better approach
+        // (https://github.com/grpc/grpc-java/issues/2562).
+        if (!terminating) {
+          delayedShutdownTask = transportFactory.getScheduledExecutorService().schedule(
+              new LogExceptionRunnable(
+                  new Runnable() {
+                    @Override
+                    public void run() {
+                      subchannel.shutdown(SUBCHANNEL_SHUTDOWN_STATUS);
+                    }
+                  }), SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, TimeUnit.SECONDS);
+          return;
+        }
+      }
+      // When terminating == true, no more real streams will be created. It's safe and also
+      // desirable to shutdown timely.
+      subchannel.shutdown(SHUTDOWN_STATUS);
+    }
+
+    @Override
+    public void requestConnection() {
+      subchannel.obtainActiveTransport();
+    }
+
+    @Override
+    public List<EquivalentAddressGroup> getAllAddresses() {
+      return subchannel.getAddressGroups();
+    }
+
+    @Override
+    public Attributes getAttributes() {
+      return attrs;
+    }
+
+    @Override
+    public String toString() {
+      return subchannel.getLogId().toString();
+    }
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("logId", logId.getId())
+        .add("target", target)
+        .toString();
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ManagedChannelOrphanWrapper.java b/core/src/main/java/io/grpc/internal/ManagedChannelOrphanWrapper.java
new file mode 100644
index 0000000..8ae83de
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ManagedChannelOrphanWrapper.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.ManagedChannel;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+final class ManagedChannelOrphanWrapper extends ForwardingManagedChannel {
+  private static final ReferenceQueue<ManagedChannelOrphanWrapper> refqueue =
+      new ReferenceQueue<ManagedChannelOrphanWrapper>();
+  // Retain the References so they don't get GC'd
+  private static final ConcurrentMap<ManagedChannelReference, ManagedChannelReference> refs =
+      new ConcurrentHashMap<ManagedChannelReference, ManagedChannelReference>();
+  private static final Logger logger =
+      Logger.getLogger(ManagedChannelOrphanWrapper.class.getName());
+
+  private final ManagedChannelReference phantom;
+
+  ManagedChannelOrphanWrapper(ManagedChannel delegate) {
+    this(delegate, refqueue, refs);
+  }
+
+  @VisibleForTesting
+  ManagedChannelOrphanWrapper(
+      ManagedChannel delegate,
+      ReferenceQueue<ManagedChannelOrphanWrapper> refqueue,
+      ConcurrentMap<ManagedChannelReference, ManagedChannelReference> refs) {
+    super(delegate);
+    phantom = new ManagedChannelReference(this, delegate, refqueue, refs);
+  }
+
+  @Override
+  public ManagedChannel shutdown() {
+    phantom.shutdown = true;
+    return super.shutdown();
+  }
+
+  @Override
+  public ManagedChannel shutdownNow() {
+    phantom.shutdownNow = true;
+    return super.shutdownNow();
+  }
+
+  @Override
+  public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+    boolean ret = super.awaitTermination(timeout, unit);
+    if (ret) {
+      phantom.clear();
+    }
+    return ret;
+  }
+
+  @VisibleForTesting
+  static final class ManagedChannelReference extends WeakReference<ManagedChannelOrphanWrapper> {
+
+    private static final String ALLOCATION_SITE_PROPERTY_NAME =
+        "io.grpc.ManagedChannel.enableAllocationTracking";
+
+    private static final boolean ENABLE_ALLOCATION_TRACKING =
+        Boolean.parseBoolean(System.getProperty(ALLOCATION_SITE_PROPERTY_NAME, "true"));
+    private static final RuntimeException missingCallSite = missingCallSite();
+
+    private final ReferenceQueue<ManagedChannelOrphanWrapper> refqueue;
+    private final ConcurrentMap<ManagedChannelReference, ManagedChannelReference> refs;
+
+    private final ManagedChannel channel;
+    private final Reference<RuntimeException> allocationSite;
+    private volatile boolean shutdown;
+    private volatile boolean shutdownNow;
+
+    ManagedChannelReference(
+        ManagedChannelOrphanWrapper orphanable,
+        ManagedChannel channel,
+        ReferenceQueue<ManagedChannelOrphanWrapper> refqueue,
+        ConcurrentMap<ManagedChannelReference, ManagedChannelReference> refs) {
+      super(orphanable, refqueue);
+      allocationSite = new SoftReference<RuntimeException>(
+          ENABLE_ALLOCATION_TRACKING
+              ? new RuntimeException("ManagedChannel allocation site")
+              : missingCallSite);
+      this.channel = channel;
+      this.refqueue = refqueue;
+      this.refs = refs;
+      this.refs.put(this, this);
+      cleanQueue(refqueue);
+    }
+
+    /**
+     * This clear() is *not* called automatically by the JVM.  As this is a weak ref, the reference
+     * will be cleared automatically by the JVM, but will not be removed from {@link #refs}.
+     * We do it here to avoid this ending up on the reference queue.
+     */
+    @Override
+    public void clear() {
+      clearInternal();
+      // We run this here to periodically clean up the queue if at least some of the channels are
+      // being shutdown properly.
+      cleanQueue(refqueue);
+    }
+
+    // avoid reentrancy
+    private void clearInternal() {
+      super.clear();
+      refs.remove(this);
+      allocationSite.clear();
+    }
+
+    private static RuntimeException missingCallSite() {
+      RuntimeException e = new RuntimeException(
+          "ManagedChannel allocation site not recorded.  Set -D"
+              + ALLOCATION_SITE_PROPERTY_NAME + "=true to enable it");
+      e.setStackTrace(new StackTraceElement[0]);
+      return e;
+    }
+
+    @VisibleForTesting
+    static int cleanQueue(ReferenceQueue<ManagedChannelOrphanWrapper> refqueue) {
+      ManagedChannelReference ref;
+      int orphanedChannels = 0;
+      while ((ref = (ManagedChannelReference) refqueue.poll()) != null) {
+        RuntimeException maybeAllocationSite = ref.allocationSite.get();
+        ref.clearInternal(); // technically the reference is gone already.
+        if (!(ref.shutdown && ref.channel.isTerminated())) {
+          orphanedChannels++;
+          Level level = ref.shutdownNow ? Level.FINE : Level.SEVERE;
+          if (logger.isLoggable(level)) {
+            String fmt =
+                "*~*~*~ Channel {0} was not "
+                // Prefer to complain about shutdown if neither has been called.
+                + (!ref.shutdown ? "shutdown" : "terminated")
+                + " properly!!! ~*~*~*"
+                + System.getProperty("line.separator")
+                + "    Make sure to call shutdown()/shutdownNow() and wait "
+                + "until awaitTermination() returns true.";
+            LogRecord lr = new LogRecord(level, fmt);
+            lr.setLoggerName(logger.getName());
+            lr.setParameters(new Object[]{ref.channel.toString()});
+            lr.setThrown(maybeAllocationSite);
+            logger.log(lr);
+          }
+        }
+      }
+      return orphanedChannels;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ManagedClientTransport.java b/core/src/main/java/io/grpc/internal/ManagedClientTransport.java
new file mode 100644
index 0000000..47cf53a
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ManagedClientTransport.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Status;
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * A {@link ClientTransport} that has life-cycle management.
+ *
+ * <p>{@link #start} must be the first method call to this interface and return before calling other
+ * methods.
+ *
+ * <p>Typically the transport owns the streams it creates through {@link #newStream}, while some
+ * implementations may transfer the streams to somewhere else. Either way they must conform to the
+ * contract defined by {@link #shutdown}, {@link Listener#transportShutdown} and
+ * {@link Listener#transportTerminated}.
+ */
+@ThreadSafe
+public interface ManagedClientTransport extends ClientTransport {
+
+  /**
+   * Starts transport. This method may only be called once.
+   *
+   * <p>Implementations must not call {@code listener} from within {@link #start}; implementations
+   * are expected to notify listener on a separate thread or when the returned {@link Runnable} is
+   * run. This method and the returned {@code Runnable} should not throw any exceptions.
+   *
+   * @param listener non-{@code null} listener of transport events
+   * @return a {@link Runnable} that is executed after-the-fact by the original caller, typically
+   *     after locks are released
+   */
+  @CheckReturnValue
+  @Nullable
+  Runnable start(Listener listener);
+
+  /**
+   * Initiates an orderly shutdown of the transport.  Existing streams continue, but the transport
+   * will not own any new streams.  New streams will either fail (once
+   * {@link Listener#transportShutdown} callback called), or be transferred off this transport (in
+   * which case they may succeed).  This method may only be called once.
+   */
+  void shutdown(Status reason);
+
+  /**
+   * Initiates a forceful shutdown in which preexisting and new calls are closed. Existing calls
+   * should be closed with the provided {@code reason}.
+   */
+  void shutdownNow(Status reason);
+
+  /**
+   * Receives notifications for the transport life-cycle events. Implementation does not need to be
+   * thread-safe, so notifications must be properly synchronized externally.
+   */
+  interface Listener {
+    /**
+     * The transport is shutting down. This transport will stop owning new streams, but existing
+     * streams may continue, and the transport may still be able to process {@link #newStream} as
+     * long as it doesn't own the new streams. Shutdown could have been caused by an error or normal
+     * operation.  It is possible that this method is called without {@link #shutdown} being called.
+     *
+     * <p>This is called exactly once, and must be called prior to {@link #transportTerminated}.
+     *
+     * @param s the reason for the shutdown.
+     */
+    void transportShutdown(Status s);
+
+    /**
+     * The transport completed shutting down. All resources have been released. All streams have
+     * either been closed or transferred off this transport. This transport may still be able to
+     * process {@link #newStream} as long as it doesn't own the new streams.
+     *
+     * <p>This is called exactly once, and must be called after {@link #transportShutdown} has been
+     * called.
+     */
+    void transportTerminated();
+
+    /**
+     * The transport is ready to accept traffic, because the connection is established.  This is
+     * called at most once.
+     */
+    void transportReady();
+
+    /**
+     * Called whenever the transport's in-use state has changed. A transport is in-use when it has
+     * at least one stream.
+     */
+    void transportInUse(boolean inUse);
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/MessageDeframer.java b/core/src/main/java/io/grpc/internal/MessageDeframer.java
new file mode 100644
index 0000000..5ca4907
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/MessageDeframer.java
@@ -0,0 +1,537 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.Codec;
+import io.grpc.Decompressor;
+import io.grpc.Status;
+import java.io.Closeable;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.DataFormatException;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * Deframer for GRPC frames.
+ *
+ * <p>This class is not thread-safe. Unless otherwise stated, all calls to public methods should be
+ * made in the deframing thread.
+ */
+@NotThreadSafe
+public class MessageDeframer implements Closeable, Deframer {
+  private static final int HEADER_LENGTH = 5;
+  private static final int COMPRESSED_FLAG_MASK = 1;
+  private static final int RESERVED_MASK = 0xFE;
+  private static final int MAX_BUFFER_SIZE = 1024 * 1024 * 2;
+
+  /**
+   * A listener of deframing events. These methods will be invoked from the deframing thread.
+   */
+  public interface Listener {
+
+    /**
+     * Called when the given number of bytes has been read from the input source of the deframer.
+     * This is typically used to indicate to the underlying transport that more data can be
+     * accepted.
+     *
+     * @param numBytes the number of bytes read from the deframer's input source.
+     */
+    void bytesRead(int numBytes);
+
+    /**
+     * Called to deliver the next complete message.
+     *
+     * @param producer single message producer wrapping the message.
+     */
+    void messagesAvailable(StreamListener.MessageProducer producer);
+
+    /**
+     * Called when the deframer closes.
+     *
+     * @param hasPartialMessage whether the deframer contained an incomplete message at closing.
+     */
+    void deframerClosed(boolean hasPartialMessage);
+
+    /**
+     * Called when a {@link #deframe(ReadableBuffer)} operation failed.
+     *
+     * @param cause the actual failure
+     */
+    void deframeFailed(Throwable cause);
+  }
+
+  private enum State {
+    HEADER, BODY
+  }
+
+  private Listener listener;
+  private int maxInboundMessageSize;
+  private final StatsTraceContext statsTraceCtx;
+  private final TransportTracer transportTracer;
+  private Decompressor decompressor;
+  private GzipInflatingBuffer fullStreamDecompressor;
+  private byte[] inflatedBuffer;
+  private int inflatedIndex;
+  private State state = State.HEADER;
+  private int requiredLength = HEADER_LENGTH;
+  private boolean compressedFlag;
+  private CompositeReadableBuffer nextFrame;
+  private CompositeReadableBuffer unprocessed = new CompositeReadableBuffer();
+  private long pendingDeliveries;
+  private boolean inDelivery = false;
+  private int currentMessageSeqNo = -1;
+  private int inboundBodyWireSize;
+
+  private boolean closeWhenComplete = false;
+  private volatile boolean stopDelivery = false;
+
+  /**
+   * Create a deframer.
+   *
+   * @param listener listener for deframer events.
+   * @param decompressor the compression used if a compressed frame is encountered, with
+   *  {@code NONE} meaning unsupported
+   * @param maxMessageSize the maximum allowed size for received messages.
+   */
+  public MessageDeframer(
+      Listener listener,
+      Decompressor decompressor,
+      int maxMessageSize,
+      StatsTraceContext statsTraceCtx,
+      TransportTracer transportTracer) {
+    this.listener = checkNotNull(listener, "sink");
+    this.decompressor = checkNotNull(decompressor, "decompressor");
+    this.maxInboundMessageSize = maxMessageSize;
+    this.statsTraceCtx = checkNotNull(statsTraceCtx, "statsTraceCtx");
+    this.transportTracer = checkNotNull(transportTracer, "transportTracer");
+  }
+
+  void setListener(Listener listener) {
+    this.listener = listener;
+  }
+
+  @Override
+  public void setMaxInboundMessageSize(int messageSize) {
+    maxInboundMessageSize = messageSize;
+  }
+
+  @Override
+  public void setDecompressor(Decompressor decompressor) {
+    checkState(fullStreamDecompressor == null, "Already set full stream decompressor");
+    this.decompressor = checkNotNull(decompressor, "Can't pass an empty decompressor");
+  }
+
+  @Override
+  public void setFullStreamDecompressor(GzipInflatingBuffer fullStreamDecompressor) {
+    checkState(decompressor == Codec.Identity.NONE, "per-message decompressor already set");
+    checkState(this.fullStreamDecompressor == null, "full stream decompressor already set");
+    this.fullStreamDecompressor =
+        checkNotNull(fullStreamDecompressor, "Can't pass a null full stream decompressor");
+    unprocessed = null;
+  }
+
+  @Override
+  public void request(int numMessages) {
+    checkArgument(numMessages > 0, "numMessages must be > 0");
+    if (isClosed()) {
+      return;
+    }
+    pendingDeliveries += numMessages;
+    deliver();
+  }
+
+  @Override
+  public void deframe(ReadableBuffer data) {
+    checkNotNull(data, "data");
+    boolean needToCloseData = true;
+    try {
+      if (!isClosedOrScheduledToClose()) {
+        if (fullStreamDecompressor != null) {
+          fullStreamDecompressor.addGzippedBytes(data);
+        } else {
+          unprocessed.addBuffer(data);
+        }
+        needToCloseData = false;
+
+        deliver();
+      }
+    } finally {
+      if (needToCloseData) {
+        data.close();
+      }
+    }
+  }
+
+  @Override
+  public void closeWhenComplete() {
+    if (isClosed()) {
+      return;
+    } else if (isStalled()) {
+      close();
+    } else {
+      closeWhenComplete = true;
+    }
+  }
+
+  /**
+   * Sets a flag to interrupt delivery of any currently queued messages. This may be invoked outside
+   * of the deframing thread, and must be followed by a call to {@link #close()} in the deframing
+   * thread. Without a subsequent call to {@link #close()}, the deframer may hang waiting for
+   * additional messages before noticing that the {@code stopDelivery} flag has been set.
+   */
+  void stopDelivery() {
+    stopDelivery = true;
+  }
+
+  @Override
+  public void close() {
+    if (isClosed()) {
+      return;
+    }
+    boolean hasPartialMessage = nextFrame != null && nextFrame.readableBytes() > 0;
+    try {
+      if (fullStreamDecompressor != null) {
+        hasPartialMessage = hasPartialMessage || fullStreamDecompressor.hasPartialData();
+        fullStreamDecompressor.close();
+      }
+      if (unprocessed != null) {
+        unprocessed.close();
+      }
+      if (nextFrame != null) {
+        nextFrame.close();
+      }
+    } finally {
+      fullStreamDecompressor = null;
+      unprocessed = null;
+      nextFrame = null;
+    }
+    listener.deframerClosed(hasPartialMessage);
+  }
+
+  /**
+   * Indicates whether or not this deframer has been closed.
+   */
+  public boolean isClosed() {
+    return unprocessed == null && fullStreamDecompressor == null;
+  }
+
+  /** Returns true if this deframer has already been closed or scheduled to close. */
+  private boolean isClosedOrScheduledToClose() {
+    return isClosed() || closeWhenComplete;
+  }
+
+  private boolean isStalled() {
+    if (fullStreamDecompressor != null) {
+      return fullStreamDecompressor.isStalled();
+    } else {
+      return unprocessed.readableBytes() == 0;
+    }
+  }
+
+  /**
+   * Reads and delivers as many messages to the listener as possible.
+   */
+  private void deliver() {
+    // We can have reentrancy here when using a direct executor, triggered by calls to
+    // request more messages. This is safe as we simply loop until pendingDelivers = 0
+    if (inDelivery) {
+      return;
+    }
+    inDelivery = true;
+    try {
+      // Process the uncompressed bytes.
+      while (!stopDelivery && pendingDeliveries > 0 && readRequiredBytes()) {
+        switch (state) {
+          case HEADER:
+            processHeader();
+            break;
+          case BODY:
+            // Read the body and deliver the message.
+            processBody();
+
+            // Since we've delivered a message, decrement the number of pending
+            // deliveries remaining.
+            pendingDeliveries--;
+            break;
+          default:
+            throw new AssertionError("Invalid state: " + state);
+        }
+      }
+
+      if (stopDelivery) {
+        close();
+        return;
+      }
+
+      /*
+       * We are stalled when there are no more bytes to process. This allows delivering errors as
+       * soon as the buffered input has been consumed, independent of whether the application
+       * has requested another message.  At this point in the function, either all frames have been
+       * delivered, or unprocessed is empty.  If there is a partial message, it will be inside next
+       * frame and not in unprocessed.  If there is extra data but no pending deliveries, it will
+       * be in unprocessed.
+       */
+      if (closeWhenComplete && isStalled()) {
+        close();
+      }
+    } finally {
+      inDelivery = false;
+    }
+  }
+
+  /**
+   * Attempts to read the required bytes into nextFrame.
+   *
+   * @return {@code true} if all of the required bytes have been read.
+   */
+  private boolean readRequiredBytes() {
+    int totalBytesRead = 0;
+    int deflatedBytesRead = 0;
+    try {
+      if (nextFrame == null) {
+        nextFrame = new CompositeReadableBuffer();
+      }
+
+      // Read until the buffer contains all the required bytes.
+      int missingBytes;
+      while ((missingBytes = requiredLength - nextFrame.readableBytes()) > 0) {
+        if (fullStreamDecompressor != null) {
+          try {
+            if (inflatedBuffer == null || inflatedIndex == inflatedBuffer.length) {
+              inflatedBuffer = new byte[Math.min(missingBytes, MAX_BUFFER_SIZE)];
+              inflatedIndex = 0;
+            }
+            int bytesToRead = Math.min(missingBytes, inflatedBuffer.length - inflatedIndex);
+            int n = fullStreamDecompressor.inflateBytes(inflatedBuffer, inflatedIndex, bytesToRead);
+            totalBytesRead += fullStreamDecompressor.getAndResetBytesConsumed();
+            deflatedBytesRead += fullStreamDecompressor.getAndResetDeflatedBytesConsumed();
+            if (n == 0) {
+              // No more inflated data is available.
+              return false;
+            }
+            nextFrame.addBuffer(ReadableBuffers.wrap(inflatedBuffer, inflatedIndex, n));
+            inflatedIndex += n;
+          } catch (IOException e) {
+            throw new RuntimeException(e);
+          } catch (DataFormatException e) {
+            throw new RuntimeException(e);
+          }
+        } else {
+          if (unprocessed.readableBytes() == 0) {
+            // No more data is available.
+            return false;
+          }
+          int toRead = Math.min(missingBytes, unprocessed.readableBytes());
+          totalBytesRead += toRead;
+          nextFrame.addBuffer(unprocessed.readBytes(toRead));
+        }
+      }
+      return true;
+    } finally {
+      if (totalBytesRead > 0) {
+        listener.bytesRead(totalBytesRead);
+        if (state == State.BODY) {
+          if (fullStreamDecompressor != null) {
+            // With compressed streams, totalBytesRead can include gzip header and trailer metadata
+            statsTraceCtx.inboundWireSize(deflatedBytesRead);
+            inboundBodyWireSize += deflatedBytesRead;
+          } else {
+            statsTraceCtx.inboundWireSize(totalBytesRead);
+            inboundBodyWireSize += totalBytesRead;
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Processes the GRPC compression header which is composed of the compression flag and the outer
+   * frame length.
+   */
+  private void processHeader() {
+    int type = nextFrame.readUnsignedByte();
+    if ((type & RESERVED_MASK) != 0) {
+      throw Status.INTERNAL.withDescription(
+          "gRPC frame header malformed: reserved bits not zero")
+          .asRuntimeException();
+    }
+    compressedFlag = (type & COMPRESSED_FLAG_MASK) != 0;
+
+    // Update the required length to include the length of the frame.
+    requiredLength = nextFrame.readInt();
+    if (requiredLength < 0 || requiredLength > maxInboundMessageSize) {
+      throw Status.RESOURCE_EXHAUSTED.withDescription(
+          String.format("gRPC message exceeds maximum size %d: %d",
+              maxInboundMessageSize, requiredLength))
+          .asRuntimeException();
+    }
+
+    currentMessageSeqNo++;
+    statsTraceCtx.inboundMessage(currentMessageSeqNo);
+    transportTracer.reportMessageReceived();
+    // Continue reading the frame body.
+    state = State.BODY;
+  }
+
+  /**
+   * Processes the GRPC message body, which depending on frame header flags may be compressed.
+   */
+  private void processBody() {
+    // There is no reliable way to get the uncompressed size per message when it's compressed,
+    // because the uncompressed bytes are provided through an InputStream whose total size is
+    // unknown until all bytes are read, and we don't know when it happens.
+    statsTraceCtx.inboundMessageRead(currentMessageSeqNo, inboundBodyWireSize, -1);
+    inboundBodyWireSize = 0;
+    InputStream stream = compressedFlag ? getCompressedBody() : getUncompressedBody();
+    nextFrame = null;
+    listener.messagesAvailable(new SingleMessageProducer(stream));
+
+    // Done with this frame, begin processing the next header.
+    state = State.HEADER;
+    requiredLength = HEADER_LENGTH;
+  }
+
+  private InputStream getUncompressedBody() {
+    statsTraceCtx.inboundUncompressedSize(nextFrame.readableBytes());
+    return ReadableBuffers.openStream(nextFrame, true);
+  }
+
+  private InputStream getCompressedBody() {
+    if (decompressor == Codec.Identity.NONE) {
+      throw Status.INTERNAL.withDescription(
+          "Can't decode compressed gRPC message as compression not configured")
+          .asRuntimeException();
+    }
+
+    try {
+      // Enforce the maxMessageSize limit on the returned stream.
+      InputStream unlimitedStream =
+          decompressor.decompress(ReadableBuffers.openStream(nextFrame, true));
+      return new SizeEnforcingInputStream(
+          unlimitedStream, maxInboundMessageSize, statsTraceCtx);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * An {@link InputStream} that enforces the {@link #maxMessageSize} limit for compressed frames.
+   */
+  @VisibleForTesting
+  static final class SizeEnforcingInputStream extends FilterInputStream {
+    private final int maxMessageSize;
+    private final StatsTraceContext statsTraceCtx;
+    private long maxCount;
+    private long count;
+    private long mark = -1;
+
+    SizeEnforcingInputStream(InputStream in, int maxMessageSize, StatsTraceContext statsTraceCtx) {
+      super(in);
+      this.maxMessageSize = maxMessageSize;
+      this.statsTraceCtx = statsTraceCtx;
+    }
+
+    @Override
+    public int read() throws IOException {
+      int result = in.read();
+      if (result != -1) {
+        count++;
+      }
+      verifySize();
+      reportCount();
+      return result;
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+      int result = in.read(b, off, len);
+      if (result != -1) {
+        count += result;
+      }
+      verifySize();
+      reportCount();
+      return result;
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+      long result = in.skip(n);
+      count += result;
+      verifySize();
+      reportCount();
+      return result;
+    }
+
+    @Override
+    public synchronized void mark(int readlimit) {
+      in.mark(readlimit);
+      mark = count;
+      // it's okay to mark even if mark isn't supported, as reset won't work
+    }
+
+    @Override
+    public synchronized void reset() throws IOException {
+      if (!in.markSupported()) {
+        throw new IOException("Mark not supported");
+      }
+      if (mark == -1) {
+        throw new IOException("Mark not set");
+      }
+
+      in.reset();
+      count = mark;
+    }
+
+    private void reportCount() {
+      if (count > maxCount) {
+        statsTraceCtx.inboundUncompressedSize(count - maxCount);
+        maxCount = count;
+      }
+    }
+
+    private void verifySize() {
+      if (count > maxMessageSize) {
+        throw Status.RESOURCE_EXHAUSTED.withDescription(String.format(
+                "Compressed gRPC message exceeds maximum size %d: %d bytes read",
+                maxMessageSize, count)).asRuntimeException();
+      }
+    }
+  }
+
+  private static class SingleMessageProducer implements StreamListener.MessageProducer {
+    private InputStream message;
+
+    private SingleMessageProducer(InputStream message) {
+      this.message = message;
+    }
+
+    @Nullable
+    @Override
+    public InputStream next() {
+      InputStream messageToReturn = message;
+      message = null;
+      return messageToReturn;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/MessageFramer.java b/core/src/main/java/io/grpc/internal/MessageFramer.java
new file mode 100644
index 0000000..dfac24e
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/MessageFramer.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.Math.min;
+
+import io.grpc.Codec;
+import io.grpc.Compressor;
+import io.grpc.Drainable;
+import io.grpc.KnownLength;
+import io.grpc.Status;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nullable;
+
+/**
+ * Encodes gRPC messages to be delivered via the transport layer which implements {@link
+ * MessageFramer.Sink}.
+ */
+public class MessageFramer implements Framer {
+
+  private static final int NO_MAX_OUTBOUND_MESSAGE_SIZE = -1;
+
+  /**
+   * Sink implemented by the transport layer to receive frames and forward them to their
+   * destination.
+   */
+  public interface Sink {
+    /**
+     * Delivers a frame via the transport.
+     *
+     * @param frame a non-empty buffer to deliver or {@code null} if the framer is being
+     *              closed and there is no data to deliver.
+     * @param endOfStream whether the frame is the last one for the GRPC stream
+     * @param flush {@code true} if more data may not be arriving soon
+     * @param numMessages the number of messages that this series of frames represents
+     */
+    void deliverFrame(
+        @Nullable WritableBuffer frame,
+        boolean endOfStream,
+        boolean flush,
+        int numMessages);
+  }
+
+  private static final int HEADER_LENGTH = 5;
+  private static final byte UNCOMPRESSED = 0;
+  private static final byte COMPRESSED = 1;
+
+  private final Sink sink;
+  // effectively final.  Can only be set once.
+  private int maxOutboundMessageSize = NO_MAX_OUTBOUND_MESSAGE_SIZE;
+  private WritableBuffer buffer;
+  private Compressor compressor = Codec.Identity.NONE;
+  private boolean messageCompression = true;
+  private final OutputStreamAdapter outputStreamAdapter = new OutputStreamAdapter();
+  private final byte[] headerScratch = new byte[HEADER_LENGTH];
+  private final WritableBufferAllocator bufferAllocator;
+  private final StatsTraceContext statsTraceCtx;
+  // transportTracer is nullable until it is integrated with client transports
+  private boolean closed;
+
+  // Tracing and stats-related states
+  private int messagesBuffered;
+  private int currentMessageSeqNo = -1;
+  private long currentMessageWireSize;
+
+  /**
+   * Creates a {@code MessageFramer}.
+   *
+   * @param sink the sink used to deliver frames to the transport
+   * @param bufferAllocator allocates buffers that the transport can commit to the wire.
+   */
+  public MessageFramer(
+      Sink sink, WritableBufferAllocator bufferAllocator, StatsTraceContext statsTraceCtx) {
+    this.sink = checkNotNull(sink, "sink");
+    this.bufferAllocator = checkNotNull(bufferAllocator, "bufferAllocator");
+    this.statsTraceCtx = checkNotNull(statsTraceCtx, "statsTraceCtx");
+  }
+
+  @Override
+  public MessageFramer setCompressor(Compressor compressor) {
+    this.compressor = checkNotNull(compressor, "Can't pass an empty compressor");
+    return this;
+  }
+
+  @Override
+  public MessageFramer setMessageCompression(boolean enable) {
+    messageCompression = enable;
+    return this;
+  }
+
+  @Override
+  public void setMaxOutboundMessageSize(int maxSize) {
+    checkState(maxOutboundMessageSize == NO_MAX_OUTBOUND_MESSAGE_SIZE, "max size already set");
+    maxOutboundMessageSize = maxSize;
+  }
+
+  /**
+   * Writes out a payload message.
+   *
+   * @param message contains the message to be written out. It will be completely consumed.
+   */
+  @Override
+  public void writePayload(InputStream message) {
+    verifyNotClosed();
+    messagesBuffered++;
+    currentMessageSeqNo++;
+    currentMessageWireSize = 0;
+    statsTraceCtx.outboundMessage(currentMessageSeqNo);
+    boolean compressed = messageCompression && compressor != Codec.Identity.NONE;
+    int written = -1;
+    int messageLength = -2;
+    try {
+      messageLength = getKnownLength(message);
+      if (messageLength != 0 && compressed) {
+        written = writeCompressed(message, messageLength);
+      } else {
+        written = writeUncompressed(message, messageLength);
+      }
+    } catch (IOException e) {
+      // This should not be possible, since sink#deliverFrame doesn't throw.
+      throw Status.INTERNAL
+          .withDescription("Failed to frame message")
+          .withCause(e)
+          .asRuntimeException();
+    } catch (RuntimeException e) {
+      throw Status.INTERNAL
+          .withDescription("Failed to frame message")
+          .withCause(e)
+          .asRuntimeException();
+    }
+
+    if (messageLength != -1 && written != messageLength) {
+      String err = String.format("Message length inaccurate %s != %s", written, messageLength);
+      throw Status.INTERNAL.withDescription(err).asRuntimeException();
+    }
+    statsTraceCtx.outboundUncompressedSize(written);
+    statsTraceCtx.outboundWireSize(currentMessageWireSize);
+    statsTraceCtx.outboundMessageSent(currentMessageSeqNo, currentMessageWireSize, written);
+  }
+
+  private int writeUncompressed(InputStream message, int messageLength) throws IOException {
+    if (messageLength != -1) {
+      currentMessageWireSize = messageLength;
+      return writeKnownLengthUncompressed(message, messageLength);
+    }
+    BufferChainOutputStream bufferChain = new BufferChainOutputStream();
+    int written = writeToOutputStream(message, bufferChain);
+    if (maxOutboundMessageSize >= 0 && written > maxOutboundMessageSize) {
+      throw Status.RESOURCE_EXHAUSTED
+          .withDescription(
+              String.format("message too large %d > %d", written , maxOutboundMessageSize))
+          .asRuntimeException();
+    }
+    writeBufferChain(bufferChain, false);
+    return written;
+  }
+
+  private int writeCompressed(InputStream message, int unusedMessageLength) throws IOException {
+    BufferChainOutputStream bufferChain = new BufferChainOutputStream();
+
+    OutputStream compressingStream = compressor.compress(bufferChain);
+    int written;
+    try {
+      written = writeToOutputStream(message, compressingStream);
+    } finally {
+      compressingStream.close();
+    }
+    if (maxOutboundMessageSize >= 0 && written > maxOutboundMessageSize) {
+      throw Status.RESOURCE_EXHAUSTED
+          .withDescription(
+              String.format("message too large %d > %d", written , maxOutboundMessageSize))
+          .asRuntimeException();
+    }
+
+    writeBufferChain(bufferChain, true);
+    return written;
+  }
+
+  private int getKnownLength(InputStream inputStream) throws IOException {
+    if (inputStream instanceof KnownLength || inputStream instanceof ByteArrayInputStream) {
+      return inputStream.available();
+    }
+    return -1;
+  }
+
+  /**
+   * Write an unserialized message with a known length, uncompressed.
+   */
+  private int writeKnownLengthUncompressed(InputStream message, int messageLength)
+      throws IOException {
+    if (maxOutboundMessageSize >= 0 && messageLength > maxOutboundMessageSize) {
+      throw Status.RESOURCE_EXHAUSTED
+          .withDescription(
+              String.format("message too large %d > %d", messageLength , maxOutboundMessageSize))
+          .asRuntimeException();
+    }
+    ByteBuffer header = ByteBuffer.wrap(headerScratch);
+    header.put(UNCOMPRESSED);
+    header.putInt(messageLength);
+    // Allocate the initial buffer chunk based on frame header + payload length.
+    // Note that the allocator may allocate a buffer larger or smaller than this length
+    if (buffer == null) {
+      buffer = bufferAllocator.allocate(header.position() + messageLength);
+    }
+    writeRaw(headerScratch, 0, header.position());
+    return writeToOutputStream(message, outputStreamAdapter);
+  }
+
+  /**
+   * Write a message that has been serialized to a sequence of buffers.
+   */
+  private void writeBufferChain(BufferChainOutputStream bufferChain, boolean compressed) {
+    ByteBuffer header = ByteBuffer.wrap(headerScratch);
+    header.put(compressed ? COMPRESSED : UNCOMPRESSED);
+    int messageLength = bufferChain.readableBytes();
+    header.putInt(messageLength);
+    WritableBuffer writeableHeader = bufferAllocator.allocate(HEADER_LENGTH);
+    writeableHeader.write(headerScratch, 0, header.position());
+    if (messageLength == 0) {
+      // the payload had 0 length so make the header the current buffer.
+      buffer = writeableHeader;
+      return;
+    }
+    // Note that we are always delivering a small message to the transport here which
+    // may incur transport framing overhead as it may be sent separately to the contents
+    // of the GRPC frame.
+    // The final message may not be completely written because we do not flush the last buffer.
+    // Do not report the last message as sent.
+    sink.deliverFrame(writeableHeader, false, false, messagesBuffered - 1);
+    messagesBuffered = 1;
+    // Commit all except the last buffer to the sink
+    List<WritableBuffer> bufferList = bufferChain.bufferList;
+    for (int i = 0; i < bufferList.size() - 1; i++) {
+      sink.deliverFrame(bufferList.get(i), false, false, 0);
+    }
+    // Assign the current buffer to the last in the chain so it can be used
+    // for future writes or written with end-of-stream=true on close.
+    buffer = bufferList.get(bufferList.size() - 1);
+    currentMessageWireSize = messageLength;
+  }
+
+  private static int writeToOutputStream(InputStream message, OutputStream outputStream)
+      throws IOException {
+    if (message instanceof Drainable) {
+      return ((Drainable) message).drainTo(outputStream);
+    } else {
+      // This makes an unnecessary copy of the bytes when bytebuf supports array(). However, we
+      // expect performance-critical code to support flushTo().
+      long written = IoUtils.copy(message, outputStream);
+      checkArgument(written <= Integer.MAX_VALUE, "Message size overflow: %s", written);
+      return (int) written;
+    }
+  }
+
+  private void writeRaw(byte[] b, int off, int len) {
+    while (len > 0) {
+      if (buffer != null && buffer.writableBytes() == 0) {
+        commitToSink(false, false);
+      }
+      if (buffer == null) {
+        // Request a buffer allocation using the message length as a hint.
+        buffer = bufferAllocator.allocate(len);
+      }
+      int toWrite = min(len, buffer.writableBytes());
+      buffer.write(b, off, toWrite);
+      off += toWrite;
+      len -= toWrite;
+    }
+  }
+
+  /**
+   * Flushes any buffered data in the framer to the sink.
+   */
+  @Override
+  public void flush() {
+    if (buffer != null && buffer.readableBytes() > 0) {
+      commitToSink(false, true);
+    }
+  }
+
+  /**
+   * Indicates whether or not this framer has been closed via a call to either
+   * {@link #close()} or {@link #dispose()}.
+   */
+  @Override
+  public boolean isClosed() {
+    return closed;
+  }
+
+  /**
+   * Flushes and closes the framer and releases any buffers. After the framer is closed or
+   * disposed, additional calls to this method will have no affect.
+   */
+  @Override
+  public void close() {
+    if (!isClosed()) {
+      closed = true;
+      // With the current code we don't expect readableBytes > 0 to be possible here, added
+      // defensively to prevent buffer leak issues if the framer code changes later.
+      if (buffer != null && buffer.readableBytes() == 0) {
+        releaseBuffer();
+      }
+      commitToSink(true, true);
+    }
+  }
+
+  /**
+   * Closes the framer and releases any buffers, but does not flush. After the framer is
+   * closed or disposed, additional calls to this method will have no affect.
+   */
+  @Override
+  public void dispose() {
+    closed = true;
+    releaseBuffer();
+  }
+
+  private void releaseBuffer() {
+    if (buffer != null) {
+      buffer.release();
+      buffer = null;
+    }
+  }
+
+  private void commitToSink(boolean endOfStream, boolean flush) {
+    WritableBuffer buf = buffer;
+    buffer = null;
+    sink.deliverFrame(buf, endOfStream, flush, messagesBuffered);
+    messagesBuffered = 0;
+  }
+
+  private void verifyNotClosed() {
+    if (isClosed()) {
+      throw new IllegalStateException("Framer already closed");
+    }
+  }
+
+  /** OutputStream whose write()s are passed to the framer. */
+  private class OutputStreamAdapter extends OutputStream {
+    /**
+     * This is slow, don't call it.  If you care about write overhead, use a BufferedOutputStream.
+     * Better yet, you can use your own single byte buffer and call
+     * {@link #write(byte[], int, int)}.
+     */
+    @Override
+    public void write(int b) {
+      byte[] singleByte = new byte[]{(byte)b};
+      write(singleByte, 0, 1);
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) {
+      writeRaw(b, off, len);
+    }
+  }
+
+  /**
+   * Produce a collection of {@link WritableBuffer} instances from the data written to an
+   * {@link OutputStream}.
+   */
+  private final class BufferChainOutputStream extends OutputStream {
+    private final List<WritableBuffer> bufferList = new ArrayList<>();
+    private WritableBuffer current;
+
+    /**
+     * This is slow, don't call it.  If you care about write overhead, use a BufferedOutputStream.
+     * Better yet, you can use your own single byte buffer and call
+     * {@link #write(byte[], int, int)}.
+     */
+    @Override
+    public void write(int b) throws IOException {
+      if (current != null && current.writableBytes() > 0) {
+        current.write((byte)b);
+        return;
+      }
+      byte[] singleByte = new byte[]{(byte)b};
+      write(singleByte, 0, 1);
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) {
+      if (current == null) {
+        // Request len bytes initially from the allocator, it may give us more.
+        current = bufferAllocator.allocate(len);
+        bufferList.add(current);
+      }
+      while (len > 0) {
+        int canWrite = Math.min(len, current.writableBytes());
+        if (canWrite == 0) {
+          // Assume message is twice as large as previous assumption if were still not done,
+          // the allocator may allocate more or less than this amount.
+          int needed = Math.max(len, current.readableBytes() * 2);
+          current = bufferAllocator.allocate(needed);
+          bufferList.add(current);
+        } else {
+          current.write(b, off, canWrite);
+          off += canWrite;
+          len -= canWrite;
+        }
+      }
+    }
+
+    private int readableBytes() {
+      int readable = 0;
+      for (WritableBuffer writableBuffer : bufferList) {
+        readable += writableBuffer.readableBytes();
+      }
+      return readable;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java b/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java
new file mode 100644
index 0000000..48e0066
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/MetadataApplierImpl.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import io.grpc.CallCredentials.MetadataApplier;
+import io.grpc.CallOptions;
+import io.grpc.Context;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+final class MetadataApplierImpl implements MetadataApplier {
+  private final ClientTransport transport;
+  private final MethodDescriptor<?, ?> method;
+  private final Metadata origHeaders;
+  private final CallOptions callOptions;
+  private final Context ctx;
+
+  private final Object lock = new Object();
+
+  // null if neither apply() or returnStream() are called.
+  // Needs this lock because apply() and returnStream() may race
+  @GuardedBy("lock")
+  @Nullable
+  private ClientStream returnedStream;
+
+  boolean finalized;
+
+  // not null if returnStream() was called before apply()
+  DelayedStream delayedStream;
+
+  MetadataApplierImpl(
+      ClientTransport transport, MethodDescriptor<?, ?> method, Metadata origHeaders,
+      CallOptions callOptions) {
+    this.transport = transport;
+    this.method = method;
+    this.origHeaders = origHeaders;
+    this.callOptions = callOptions;
+    this.ctx = Context.current();
+  }
+
+  @Override
+  public void apply(Metadata headers) {
+    checkState(!finalized, "apply() or fail() already called");
+    checkNotNull(headers, "headers");
+    origHeaders.merge(headers);
+    ClientStream realStream;
+    Context origCtx = ctx.attach();
+    try {
+      realStream = transport.newStream(method, origHeaders, callOptions);
+    } finally {
+      ctx.detach(origCtx);
+    }
+    finalizeWith(realStream);
+  }
+
+  @Override
+  public void fail(Status status) {
+    checkArgument(!status.isOk(), "Cannot fail with OK status");
+    checkState(!finalized, "apply() or fail() already called");
+    finalizeWith(new FailingClientStream(status));
+  }
+
+  private void finalizeWith(ClientStream stream) {
+    checkState(!finalized, "already finalized");
+    finalized = true;
+    synchronized (lock) {
+      if (returnedStream == null) {
+        // Fast path: returnStream() hasn't been called, the call will use the
+        // real stream directly.
+        returnedStream = stream;
+        return;
+      }
+    }
+    // returnStream() has been called before me, thus delayedStream must have been
+    // created.
+    checkState(delayedStream != null, "delayedStream is null");
+    delayedStream.setStream(stream);
+  }
+
+  /**
+   * Return a stream on which the RPC will run on.
+   */
+  ClientStream returnStream() {
+    synchronized (lock) {
+      if (returnedStream == null) {
+        // apply() has not been called, needs to buffer the requests.
+        delayedStream = new DelayedStream();
+        return returnedStream = delayedStream;
+      } else {
+        return returnedStream;
+      }
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/MoreThrowables.java b/core/src/main/java/io/grpc/internal/MoreThrowables.java
new file mode 100644
index 0000000..f49f237
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/MoreThrowables.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.Preconditions;
+
+/** Utility functions when interacting with {@link Throwable}s. */
+// TODO(ejona): Delete this once we've upgraded to Guava 20 or later.
+public final class MoreThrowables {
+  /**
+   * Throws {code t} if it is an instance of {@link RuntimeException} or {@link Error}.
+   *
+   * <p>This is intended to mimic Guava's method by the same name, but which is unavailable to us
+   * due to compatibility with older Guava versions.
+   */
+  public static void throwIfUnchecked(Throwable t) {
+    Preconditions.checkNotNull(t);
+    if (t instanceof RuntimeException) {
+      throw (RuntimeException) t;
+    }
+    if (t instanceof Error) {
+      throw (Error) t;
+    }
+  }
+
+  // Prevent instantiation
+  private MoreThrowables() {}
+}
diff --git a/core/src/main/java/io/grpc/internal/NoopClientStream.java b/core/src/main/java/io/grpc/internal/NoopClientStream.java
new file mode 100644
index 0000000..7e9d927
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/NoopClientStream.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Attributes;
+import io.grpc.Compressor;
+import io.grpc.Deadline;
+import io.grpc.DecompressorRegistry;
+import io.grpc.Status;
+import java.io.InputStream;
+import javax.annotation.Nonnull;
+
+/**
+ * An implementation of {@link ClientStream} that silently does nothing for the operations.
+ */
+public class NoopClientStream implements ClientStream {
+  public static final NoopClientStream INSTANCE = new NoopClientStream();
+
+  @Override
+  public void setAuthority(String authority) {}
+
+  @Override
+  public void start(ClientStreamListener listener) {}
+
+  @Override
+  public Attributes getAttributes() {
+    return Attributes.EMPTY;
+  }
+
+  @Override
+  public void request(int numMessages) {}
+
+  @Override
+  public void writeMessage(InputStream message) {}
+
+  @Override
+  public void flush() {}
+
+  @Override
+  public boolean isReady() {
+    return false;
+  }
+
+  @Override
+  public void cancel(Status status) {}
+
+  @Override
+  public void halfClose() {}
+
+  @Override
+  public void setMessageCompression(boolean enable) {
+    // noop
+  }
+
+  @Override
+  public void setCompressor(Compressor compressor) {}
+
+  @Override
+  public void setFullStreamDecompression(boolean fullStreamDecompression) {}
+
+  @Override
+  public void setDecompressorRegistry(DecompressorRegistry decompressorRegistry) {}
+
+  @Override
+  public void setMaxInboundMessageSize(int maxSize) {}
+
+  @Override
+  public void setMaxOutboundMessageSize(int maxSize) {}
+
+  @Override
+  public void setDeadline(@Nonnull Deadline deadline) {}
+}
diff --git a/core/src/main/java/io/grpc/internal/ObjectPool.java b/core/src/main/java/io/grpc/internal/ObjectPool.java
new file mode 100644
index 0000000..13547bc
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ObjectPool.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * An object pool.
+ */
+@ThreadSafe
+public interface ObjectPool<T> {
+  /**
+   * Get an object from the pool.
+   */
+  T getObject();
+
+  /**
+   * Return the object to the pool.  The caller should not use the object beyond this point.
+   *
+   * @return always {@code null}
+   */
+  T returnObject(Object object);
+}
diff --git a/core/src/main/java/io/grpc/internal/OobChannel.java b/core/src/main/java/io/grpc/internal/OobChannel.java
new file mode 100644
index 0000000..494bb9f
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/OobChannel.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.Attributes;
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.ConnectivityState;
+import io.grpc.ConnectivityStateInfo;
+import io.grpc.Context;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.ChannelStats;
+import io.grpc.InternalChannelz.ChannelTrace;
+import io.grpc.InternalInstrumented;
+import io.grpc.InternalLogId;
+import io.grpc.InternalWithLogId;
+import io.grpc.LoadBalancer;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.internal.ClientCallImpl.ClientTransportProvider;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * A ManagedChannel backed by a single {@link InternalSubchannel} and used for {@link LoadBalancer}
+ * to its own RPC needs.
+ */
+@ThreadSafe
+final class OobChannel extends ManagedChannel implements InternalInstrumented<ChannelStats> {
+  private static final Logger log = Logger.getLogger(OobChannel.class.getName());
+
+  private InternalSubchannel subchannel;
+  private AbstractSubchannel subchannelImpl;
+  private SubchannelPicker subchannelPicker;
+
+  private final InternalLogId logId = InternalLogId.allocate(getClass().getName());
+  private final String authority;
+  private final DelayedClientTransport delayedTransport;
+  private final InternalChannelz channelz;
+  private final ObjectPool<? extends Executor> executorPool;
+  private final Executor executor;
+  private final ScheduledExecutorService deadlineCancellationExecutor;
+  private final CountDownLatch terminatedLatch = new CountDownLatch(1);
+  private volatile boolean shutdown;
+  private final CallTracer channelCallsTracer;
+  @CheckForNull
+  private final ChannelTracer channelTracer;
+  private final TimeProvider timeProvider;
+
+  private final ClientTransportProvider transportProvider = new ClientTransportProvider() {
+    @Override
+    public ClientTransport get(PickSubchannelArgs args) {
+      // delayed transport's newStream() always acquires a lock, but concurrent performance doesn't
+      // matter here because OOB communication should be sparse, and it's not on application RPC's
+      // critical path.
+      return delayedTransport;
+    }
+
+    @Override
+    public <ReqT> RetriableStream<ReqT> newRetriableStream(MethodDescriptor<ReqT, ?> method,
+        CallOptions callOptions, Metadata headers, Context context) {
+      throw new UnsupportedOperationException("OobChannel should not create retriable streams");
+    }
+  };
+
+  OobChannel(
+      String authority, ObjectPool<? extends Executor> executorPool,
+      ScheduledExecutorService deadlineCancellationExecutor, ChannelExecutor channelExecutor,
+      CallTracer callsTracer, @Nullable  ChannelTracer channelTracer, InternalChannelz channelz,
+      TimeProvider timeProvider) {
+    this.authority = checkNotNull(authority, "authority");
+    this.executorPool = checkNotNull(executorPool, "executorPool");
+    this.executor = checkNotNull(executorPool.getObject(), "executor");
+    this.deadlineCancellationExecutor = checkNotNull(
+        deadlineCancellationExecutor, "deadlineCancellationExecutor");
+    this.delayedTransport = new DelayedClientTransport(executor, channelExecutor);
+    this.channelz = Preconditions.checkNotNull(channelz);
+    this.delayedTransport.start(new ManagedClientTransport.Listener() {
+        @Override
+        public void transportShutdown(Status s) {
+          // Don't care
+        }
+
+        @Override
+        public void transportTerminated() {
+          subchannelImpl.shutdown();
+        }
+
+        @Override
+        public void transportReady() {
+          // Don't care
+        }
+
+        @Override
+        public void transportInUse(boolean inUse) {
+          // Don't care
+        }
+      });
+    this.channelCallsTracer = callsTracer;
+    this.channelTracer = channelTracer;
+    this.timeProvider = timeProvider;
+  }
+
+  // Must be called only once, right after the OobChannel is created.
+  void setSubchannel(final InternalSubchannel subchannel) {
+    log.log(Level.FINE, "[{0}] Created with [{1}]", new Object[] {this, subchannel});
+    this.subchannel = subchannel;
+    subchannelImpl = new AbstractSubchannel() {
+        @Override
+        public void shutdown() {
+          subchannel.shutdown(Status.UNAVAILABLE.withDescription("OobChannel is shutdown"));
+        }
+
+        @Override
+        ClientTransport obtainActiveTransport() {
+          return subchannel.obtainActiveTransport();
+        }
+
+        @Override
+        InternalInstrumented<ChannelStats> getInternalSubchannel() {
+          return subchannel;
+        }
+
+        @Override
+        public void requestConnection() {
+          subchannel.obtainActiveTransport();
+        }
+
+        @Override
+        public List<EquivalentAddressGroup> getAllAddresses() {
+          return subchannel.getAddressGroups();
+        }
+
+        @Override
+        public Attributes getAttributes() {
+          return Attributes.EMPTY;
+        }
+    };
+
+    subchannelPicker = new SubchannelPicker() {
+        final PickResult result = PickResult.withSubchannel(subchannelImpl);
+
+        @Override
+        public PickResult pickSubchannel(PickSubchannelArgs args) {
+          return result;
+        }
+      };
+    delayedTransport.reprocess(subchannelPicker);
+  }
+
+  void updateAddresses(EquivalentAddressGroup eag) {
+    subchannel.updateAddresses(Collections.singletonList(eag));
+  }
+
+  @Override
+  public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
+      MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) {
+    return new ClientCallImpl<RequestT, ResponseT>(methodDescriptor,
+        callOptions.getExecutor() == null ? executor : callOptions.getExecutor(),
+        callOptions, transportProvider, deadlineCancellationExecutor, channelCallsTracer,
+        false /* retryEnabled */);
+  }
+
+  @Override
+  public String authority() {
+    return authority;
+  }
+
+  @Override
+  public boolean isTerminated() {
+    return terminatedLatch.getCount() == 0;
+  }
+
+  @Override
+  public boolean awaitTermination(long time, TimeUnit unit) throws InterruptedException {
+    return terminatedLatch.await(time, unit);
+  }
+
+  @Override
+  public ConnectivityState getState(boolean requestConnectionIgnored) {
+    if (subchannel == null) {
+      return ConnectivityState.IDLE;
+    }
+    return subchannel.getState();
+  }
+
+  @Override
+  public ManagedChannel shutdown() {
+    shutdown = true;
+    delayedTransport.shutdown(Status.UNAVAILABLE.withDescription("OobChannel.shutdown() called"));
+    return this;
+  }
+
+  @Override
+  public boolean isShutdown() {
+    return shutdown;
+  }
+
+  @Override
+  public ManagedChannel shutdownNow() {
+    shutdown = true;
+    delayedTransport.shutdownNow(
+        Status.UNAVAILABLE.withDescription("OobChannel.shutdownNow() called"));
+    return this;
+  }
+
+  void handleSubchannelStateChange(final ConnectivityStateInfo newState) {
+    if (channelTracer != null) {
+      channelTracer.reportEvent(
+          new ChannelTrace.Event.Builder()
+              .setDescription("Entering " + newState.getState() + " state")
+              .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+              .setTimestampNanos(timeProvider.currentTimeNanos())
+              .build());
+    }
+    switch (newState.getState()) {
+      case READY:
+      case IDLE:
+        delayedTransport.reprocess(subchannelPicker);
+        break;
+      case TRANSIENT_FAILURE:
+        delayedTransport.reprocess(new SubchannelPicker() {
+            final PickResult errorResult = PickResult.withError(newState.getStatus());
+
+            @Override
+            public PickResult pickSubchannel(PickSubchannelArgs args) {
+              return errorResult;
+            }
+          });
+        break;
+      default:
+        // Do nothing
+    }
+  }
+
+  // must be run from channel executor
+  void handleSubchannelTerminated() {
+    channelz.removeSubchannel(this);
+    // When delayedTransport is terminated, it shuts down subchannel.  Therefore, at this point
+    // both delayedTransport and subchannel have terminated.
+    executorPool.returnObject(executor);
+    terminatedLatch.countDown();
+  }
+
+  @VisibleForTesting
+  Subchannel getSubchannel() {
+    return subchannelImpl;
+  }
+
+  InternalSubchannel getInternalSubchannel() {
+    return subchannel;
+  }
+
+  @Override
+  public ListenableFuture<ChannelStats> getStats() {
+    final SettableFuture<ChannelStats> ret = SettableFuture.create();
+    final ChannelStats.Builder builder = new ChannelStats.Builder();
+    channelCallsTracer.updateBuilder(builder);
+    if (channelTracer != null) {
+      channelTracer.updateBuilder(builder);
+    }
+    builder
+        .setTarget(authority)
+        .setState(subchannel.getState())
+        .setSubchannels(Collections.<InternalWithLogId>singletonList(subchannel));
+    ret.set(builder.build());
+    return ret;
+  }
+
+  @Override
+  public InternalLogId getLogId() {
+    return logId;
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("logId", logId.getId())
+        .add("authority", authority)
+        .toString();
+  }
+
+  @Override
+  public void resetConnectBackoff() {
+    subchannel.resetConnectBackoff();
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/OverrideAuthorityNameResolverFactory.java b/core/src/main/java/io/grpc/internal/OverrideAuthorityNameResolverFactory.java
new file mode 100644
index 0000000..1e2154a
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/OverrideAuthorityNameResolverFactory.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Attributes;
+import io.grpc.NameResolver;
+import java.net.URI;
+import javax.annotation.Nullable;
+
+/**
+ * A wrapper class that overrides the authority of a NameResolver, while preserving all other
+ * functionality.
+ */
+final class OverrideAuthorityNameResolverFactory extends NameResolver.Factory {
+  private final NameResolver.Factory delegate;
+  private final String authorityOverride;
+
+  /**
+   * Constructor for the {@link NameResolver.Factory}
+   *
+   * @param delegate The actual underlying factory that will produce the a {@link NameResolver}
+   * @param authorityOverride The authority that will be returned by {@link
+   *   NameResolver#getServiceAuthority()}
+   */
+  OverrideAuthorityNameResolverFactory(NameResolver.Factory delegate, String authorityOverride) {
+    this.delegate = delegate;
+    this.authorityOverride = authorityOverride;
+  }
+
+  @Nullable
+  @Override
+  public NameResolver newNameResolver(URI targetUri, Attributes params) {
+    final NameResolver resolver = delegate.newNameResolver(targetUri, params);
+    // Do not wrap null values. We do not want to impede error signaling.
+    if (resolver == null) {
+      return null;
+    }
+    return new ForwardingNameResolver(resolver) {
+      @Override
+      public String getServiceAuthority() {
+        return authorityOverride;
+      }
+    };
+  }
+
+  @Override
+  public String getDefaultScheme() {
+    return delegate.getDefaultScheme();
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/PickSubchannelArgsImpl.java b/core/src/main/java/io/grpc/internal/PickSubchannelArgsImpl.java
new file mode 100644
index 0000000..b6ec5bf
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/PickSubchannelArgsImpl.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Objects;
+import io.grpc.CallOptions;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+
+final class PickSubchannelArgsImpl extends PickSubchannelArgs {
+  private final CallOptions callOptions;
+  private final Metadata headers;
+  private final MethodDescriptor<?, ?> method;
+
+  /**
+   * Creates call args object for given method with its call options, metadata.
+   */
+  PickSubchannelArgsImpl(MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions) {
+    this.method = checkNotNull(method, "method");
+    this.headers = checkNotNull(headers, "headers");
+    this.callOptions = checkNotNull(callOptions, "callOptions");
+  }
+
+  @Override
+  public Metadata getHeaders() {
+    return headers;
+  }
+
+  @Override
+  public CallOptions getCallOptions() {
+    return callOptions;
+  }
+
+  @Override
+  public MethodDescriptor<?, ?> getMethodDescriptor() {
+    return method;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    PickSubchannelArgsImpl that = (PickSubchannelArgsImpl) o;
+    return Objects.equal(callOptions, that.callOptions)
+        && Objects.equal(headers, that.headers)
+        && Objects.equal(method, that.method);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(callOptions, headers, method);
+  }
+
+  @Override
+  public final String toString() {
+    return "[method=" + method + " headers=" + headers + " callOptions=" + callOptions + "]";
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ProxyDetector.java b/core/src/main/java/io/grpc/internal/ProxyDetector.java
new file mode 100644
index 0000000..5dc9629
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ProxyDetector.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import java.io.IOException;
+import java.net.SocketAddress;
+import javax.annotation.Nullable;
+
+/**
+ * A utility class to detect which proxy, if any, should be used for a given
+ * {@link java.net.SocketAddress}. This class performs network requests to resolve address names,
+ * and should only be used in places that are expected to do IO such as the
+ * {@link io.grpc.NameResolver}.
+ */
+public interface ProxyDetector {
+  /**
+   * Given a target address, returns which proxy address should be used. If no proxy should be
+   * used, then return value will be null. The address of the {@link ProxyParameters} is always
+   * resolved. This throws if the proxy address cannot be resolved.
+   */
+  @Nullable
+  ProxyParameters proxyFor(SocketAddress targetServerAddress) throws IOException;
+}
diff --git a/core/src/main/java/io/grpc/internal/ProxyDetectorImpl.java b/core/src/main/java/io/grpc/internal/ProxyDetectorImpl.java
new file mode 100644
index 0000000..1219d13
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ProxyDetectorImpl.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Supplier;
+import java.io.IOException;
+import java.net.Authenticator;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.PasswordAuthentication;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * A utility class that detects proxies using {@link ProxySelector} and detects authentication
+ * credentials using {@link Authenticator}.
+ *
+ */
+class ProxyDetectorImpl implements ProxyDetector {
+  // To validate this code: set up a local squid proxy instance, and
+  // try to communicate with grpc-test.sandbox.googleapis.com:443.
+  // The endpoint runs an instance of TestServiceGrpc, see
+  // AbstractInteropTest for an example how to run a
+  // TestService.EmptyCall RPC.
+  //
+  // The instructions below assume Squid 3.5.23 and a recent
+  // version of Debian.
+  //
+  // Set the contents of /etc/squid/squid.conf to be:
+  // WARNING: THESE CONFIGS HAVE NOT BEEN REVIEWED FOR SECURITY, DO
+  // NOT USE OUTSIDE OF TESTING. COMMENT OUT THIS WARNING TO
+  // UNBREAK THE CONFIG FILE.
+  // acl SSL_ports port 443
+  // acl Safe_ports port 80
+  // acl Safe_ports port 21
+  // acl Safe_ports port 443
+  // acl Safe_ports port 70
+  // acl Safe_ports port 210
+  // acl Safe_ports port 1025-65535
+  // acl Safe_ports port 280
+  // acl Safe_ports port 488
+  // acl Safe_ports port 591
+  // acl Safe_ports port 777
+  // acl CONNECT method CONNECT
+  // http_access deny !Safe_ports
+  // http_access deny CONNECT !SSL_ports
+  // http_access allow localhost manager
+  // http_access deny manager
+  // http_access allow localhost
+  // http_access deny all
+  // http_port 3128
+  // coredump_dir /var/spool/squid
+  // refresh_pattern ^ftp: 1440 20% 10080
+  // refresh_pattern ^gopher: 1440 0% 1440
+  // refresh_pattern -i (/cgi-bin/|\?) 0 0% 0
+  // refresh_pattern . 0 20% 4320
+  //
+  // Restart squid:
+  // $ sudo /etc/init.d/squid restart
+  //
+  // To test with passwords:
+  //
+  // Run this command and follow the instructions to set up a user/pass:
+  // $ sudo htpasswd -c /etc/squid/passwd myuser1
+  //
+  // Make the file readable to squid:
+  // $ sudo chmod 644 /etc/squid/passwd
+  //
+  // Validate the username and password, you should see OK printed:
+  // $ /usr/lib/squid3/basic_ncsa_auth /etc/squid/passwd
+  // myuser1 <your password here>
+  //
+  // Add these additional lines to the beginning of squid.conf (the ordering matters):
+  // auth_param basic program /usr/lib/squid3/basic_ncsa_auth /etc/squid/passwd
+  // auth_param basic children 5
+  // auth_param basic realm Squid proxy-caching web server
+  // auth_param basic credentialsttl 2 hours
+  // acl ncsa_users proxy_auth REQUIRED
+  // http_access allow ncsa_users
+  //
+  // Restart squid:
+  // $ sudo /etc/init.d/squid restart
+  //
+  // In both cases, start the JVM with -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=3128 to
+  // configure the proxy. For passwords, use java.net.Authenticator.setDefault().
+  //
+  // Testing with curl, no password:
+  // $ curl -U myuser1:pass1 -x http://localhost:3128 -L grpc.io
+  // Testing with curl, with password:
+  // $ curl -U myuser1:pass1 -x http://localhost:3128 -L grpc.io
+  //
+  // It may be helpful to monitor the squid access logs:
+  // $ sudo tail -f /var/log/squid/access.log
+
+  private static final Logger log = Logger.getLogger(ProxyDetectorImpl.class.getName());
+  private static final AuthenticationProvider DEFAULT_AUTHENTICATOR = new AuthenticationProvider() {
+    @Override
+    public PasswordAuthentication requestPasswordAuthentication(
+        String host, InetAddress addr, int port, String protocol, String prompt, String scheme) {
+      URL url = null;
+      try {
+        url = new URL(protocol, host, port, "");
+      } catch (MalformedURLException e) {
+        // let url be null
+        log.log(
+            Level.WARNING,
+            String.format("failed to create URL for Authenticator: %s %s", protocol, host));
+      }
+      // TODO(spencerfang): consider using java.security.AccessController here
+      return Authenticator.requestPasswordAuthentication(
+          host, addr, port, protocol, prompt, scheme, url, Authenticator.RequestorType.PROXY);
+    }
+  };
+  private static final Supplier<ProxySelector> DEFAULT_PROXY_SELECTOR =
+      new Supplier<ProxySelector>() {
+        @Override
+        public ProxySelector get() {
+          // TODO(spencerfang): consider using java.security.AccessController here
+          return ProxySelector.getDefault();
+        }
+      };
+
+  /**
+   * @deprecated Use the standard Java proxy configuration instead with flags such as:
+   *     -Dhttps.proxyHost=HOST -Dhttps.proxyPort=PORT
+   */
+  @Deprecated
+  private static final String GRPC_PROXY_ENV_VAR = "GRPC_PROXY_EXP";
+  // Do not hard code a ProxySelector because the global default ProxySelector can change
+  private final Supplier<ProxySelector> proxySelector;
+  private final AuthenticationProvider authenticationProvider;
+  private final ProxyParameters override;
+
+  // We want an HTTPS proxy, which operates on the entire data stream (See IETF rfc2817).
+  static final String PROXY_SCHEME = "https";
+
+  /**
+   * A proxy selector that uses the global {@link ProxySelector#getDefault()} and
+   * {@link ProxyDetectorImpl.AuthenticationProvider} to detect proxy parameters.
+   */
+  public ProxyDetectorImpl() {
+    this(DEFAULT_PROXY_SELECTOR, DEFAULT_AUTHENTICATOR, System.getenv(GRPC_PROXY_ENV_VAR));
+  }
+
+  @VisibleForTesting
+  ProxyDetectorImpl(
+      Supplier<ProxySelector> proxySelector,
+      AuthenticationProvider authenticationProvider,
+      @Nullable String proxyEnvString) {
+    this.proxySelector = checkNotNull(proxySelector);
+    this.authenticationProvider = checkNotNull(authenticationProvider);
+    if (proxyEnvString != null) {
+      override = new ProxyParameters(overrideProxy(proxyEnvString), null, null);
+    } else {
+      override = null;
+    }
+  }
+
+  @Nullable
+  @Override
+  public ProxyParameters proxyFor(SocketAddress targetServerAddress) throws IOException {
+    if (override != null) {
+      return override;
+    }
+    if (!(targetServerAddress instanceof InetSocketAddress)) {
+      return null;
+    }
+    return detectProxy((InetSocketAddress) targetServerAddress);
+  }
+
+  private ProxyParameters detectProxy(InetSocketAddress targetAddr) throws IOException {
+    URI uri;
+    String host;
+    try {
+      host = GrpcUtil.getHost(targetAddr);
+    } catch (Throwable t) {
+      // Workaround for Android API levels < 19 if getHostName causes a NetworkOnMainThreadException
+      log.log(Level.WARNING, "Failed to get host for proxy lookup, proceeding without proxy", t);
+      return null;
+    }
+    try {
+      uri =
+          new URI(
+              PROXY_SCHEME,
+              null, /* userInfo */
+              host,
+              targetAddr.getPort(),
+              null, /* path */
+              null, /* query */
+              null /* fragment */);
+    } catch (final URISyntaxException e) {
+      log.log(
+          Level.WARNING,
+          "Failed to construct URI for proxy lookup, proceeding without proxy",
+          e);
+      return null;
+    }
+
+    ProxySelector proxySelector = this.proxySelector.get();
+    if (proxySelector == null) {
+      log.log(Level.FINE, "proxy selector is null, so continuing without proxy lookup");
+      return null;
+    }
+
+    List<Proxy> proxies = proxySelector.select(uri);
+    if (proxies.size() > 1) {
+      log.warning("More than 1 proxy detected, gRPC will select the first one");
+    }
+    Proxy proxy = proxies.get(0);
+
+    if (proxy.type() == Proxy.Type.DIRECT) {
+      return null;
+    }
+    InetSocketAddress proxyAddr = (InetSocketAddress) proxy.address();
+    // The prompt string should be the realm as returned by the server.
+    // We don't have it because we are avoiding the full handshake.
+    String promptString = "";
+    PasswordAuthentication auth = authenticationProvider.requestPasswordAuthentication(
+        GrpcUtil.getHost(proxyAddr),
+        proxyAddr.getAddress(),
+        proxyAddr.getPort(),
+        PROXY_SCHEME,
+        promptString,
+        null);
+
+    final InetSocketAddress resolvedProxyAddr;
+    if (proxyAddr.isUnresolved()) {
+      InetAddress resolvedAddress = InetAddress.getByName(proxyAddr.getHostName());
+      resolvedProxyAddr = new InetSocketAddress(resolvedAddress, proxyAddr.getPort());
+    } else {
+      resolvedProxyAddr = proxyAddr;
+    }
+
+    if (auth == null) {
+      return new ProxyParameters(resolvedProxyAddr, null, null);
+    }
+
+    // TODO(spencerfang): users of ProxyParameters should clear the password when done
+    return new ProxyParameters(
+        resolvedProxyAddr, auth.getUserName(), new String(auth.getPassword()));
+  }
+
+  /**
+   * GRPC_PROXY_EXP is deprecated but let's maintain compatibility for now.
+   */
+  private static InetSocketAddress overrideProxy(String proxyHostPort) {
+    if (proxyHostPort == null) {
+      return null;
+    }
+
+    String[] parts = proxyHostPort.split(":", 2);
+    int port = 80;
+    if (parts.length > 1) {
+      port = Integer.parseInt(parts[1]);
+    }
+    log.warning(
+        "Detected GRPC_PROXY_EXP and will honor it, but this feature will "
+            + "be removed in a future release. Use the JVM flags "
+            + "\"-Dhttps.proxyHost=HOST -Dhttps.proxyPort=PORT\" to set the https proxy for "
+            + "this JVM.");
+    return new InetSocketAddress(parts[0], port);
+  }
+
+  /**
+   * This interface makes unit testing easier by avoiding direct calls to static methods.
+   */
+  interface AuthenticationProvider {
+    PasswordAuthentication requestPasswordAuthentication(
+        String host,
+        InetAddress addr,
+        int port,
+        String protocol,
+        String prompt,
+        String scheme);
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ProxyParameters.java b/core/src/main/java/io/grpc/internal/ProxyParameters.java
new file mode 100644
index 0000000..b880550
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ProxyParameters.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import java.net.InetSocketAddress;
+import javax.annotation.Nullable;
+
+/**
+ * Used to express the result of a proxy lookup.
+ */
+public final class ProxyParameters {
+  public final InetSocketAddress proxyAddress;
+  @Nullable public final String username;
+  @Nullable public final String password;
+
+  /** Creates an instance. */
+  public ProxyParameters(
+      InetSocketAddress proxyAddress,
+      @Nullable String username,
+      @Nullable String password) {
+    Preconditions.checkNotNull(proxyAddress);
+    // The resolution must be done by the ProxyParameters producer, because consumers
+    // may not be allowed to do IO.
+    Preconditions.checkState(!proxyAddress.isUnresolved());
+    this.proxyAddress = proxyAddress;
+    this.username = username;
+    this.password = password;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof ProxyParameters)) {
+      return false;
+    }
+    ProxyParameters that = (ProxyParameters) o;
+    return Objects.equal(proxyAddress, that.proxyAddress)
+        && Objects.equal(username, that.username)
+        && Objects.equal(password, that.password);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(proxyAddress, username, password);
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ProxySocketAddress.java b/core/src/main/java/io/grpc/internal/ProxySocketAddress.java
new file mode 100644
index 0000000..e55c8fe
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ProxySocketAddress.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import java.net.SocketAddress;
+
+/**
+ * A data structure to associate a {@link SocketAddress} with {@link ProxyParameters}.
+ */
+final class ProxySocketAddress extends SocketAddress {
+  private static final long serialVersionUID = -6854992294603212793L;
+
+  private final SocketAddress address;
+  private final ProxyParameters proxyParameters;
+
+  @VisibleForTesting
+  ProxySocketAddress(SocketAddress address, ProxyParameters proxyParameters) {
+    this.address = Preconditions.checkNotNull(address);
+    this.proxyParameters = Preconditions.checkNotNull(proxyParameters);
+  }
+
+  public ProxyParameters getProxyParameters() {
+    return proxyParameters;
+  }
+
+  public SocketAddress getAddress() {
+    return address;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ReadableBuffer.java b/core/src/main/java/io/grpc/internal/ReadableBuffer.java
new file mode 100644
index 0000000..7d2ca7e
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ReadableBuffer.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * Interface for an abstract byte buffer. Buffers are intended to be a read-only, except for the
+ * read position which is incremented after each read call.
+ *
+ * <p>Buffers may optionally expose a backing array for optimization purposes, similar to what is
+ * done in {@link ByteBuffer}. It is not expected that callers will attempt to modify the backing
+ * array.
+ */
+public interface ReadableBuffer extends Closeable {
+
+  /**
+   * Gets the current number of readable bytes remaining in this buffer.
+   */
+  int readableBytes();
+
+  /**
+   * Reads the next unsigned byte from this buffer and increments the read position by 1.
+   *
+   * @throws IndexOutOfBoundsException if required bytes are not readable
+   */
+  int readUnsignedByte();
+
+  /**
+   * Reads a 4-byte signed integer from this buffer using big-endian byte ordering. Increments the
+   * read position by 4.
+   *
+   * @throws IndexOutOfBoundsException if required bytes are not readable
+   */
+  int readInt();
+
+  /**
+   * Increments the read position by the given length.
+   *
+   * @throws IndexOutOfBoundsException if required bytes are not readable
+   */
+  void skipBytes(int length);
+
+  /**
+   * Reads {@code length} bytes from this buffer and writes them to the destination array.
+   * Increments the read position by {@code length}.
+   *
+   * @param dest the destination array to receive the bytes.
+   * @param destOffset the starting offset in the destination array.
+   * @param length the number of bytes to be copied.
+   * @throws IndexOutOfBoundsException if required bytes are not readable or {@code dest} is too
+   *     small.
+   */
+  void readBytes(byte[] dest, int destOffset, int length);
+
+  /**
+   * Reads from this buffer until the destination's position reaches its limit, and increases the
+   * read position by the number of the transferred bytes.
+   *
+   * @param dest the destination buffer to receive the bytes.
+   * @throws IndexOutOfBoundsException if required bytes are not readable
+   */
+  void readBytes(ByteBuffer dest);
+
+  /**
+   * Reads {@code length} bytes from this buffer and writes them to the destination stream.
+   * Increments the read position by {@code length}. If the required bytes are not readable, throws
+   * {@link IndexOutOfBoundsException}.
+   *
+   * @param dest the destination stream to receive the bytes.
+   * @param length the number of bytes to be copied.
+   * @throws IOException thrown if any error was encountered while writing to the stream.
+   * @throws IndexOutOfBoundsException if required bytes are not readable
+   */
+  void readBytes(OutputStream dest, int length) throws IOException;
+
+  /**
+   * Reads {@code length} bytes from this buffer and returns a new Buffer containing them. Some
+   * implementations may return a Buffer sharing the backing memory with this buffer to prevent
+   * copying. However, that means that the returned buffer may keep the (possibly much larger)
+   * backing memory in use even after this buffer is closed.
+   *
+   * @param length the number of bytes to contain in returned Buffer.
+   * @throws IndexOutOfBoundsException if required bytes are not readable
+   */
+  ReadableBuffer readBytes(int length);
+
+  /**
+   * Indicates whether or not this buffer exposes a backing array.
+   */
+  boolean hasArray();
+
+  /**
+   * Gets the backing array for this buffer. This is an optional method, so callers should first
+   * check {@link #hasArray}.
+   *
+   * @throws UnsupportedOperationException the buffer does not support this method
+   */
+  byte[] array();
+
+  /**
+   * Gets the offset in the backing array of the current read position. This is an optional method,
+   * so callers should first check {@link #hasArray}
+   *
+   * @throws UnsupportedOperationException the buffer does not support this method
+   */
+  int arrayOffset();
+
+  /**
+   * Closes this buffer and releases any resources.
+   */
+  @Override
+  void close();
+}
diff --git a/core/src/main/java/io/grpc/internal/ReadableBuffers.java b/core/src/main/java/io/grpc/internal/ReadableBuffers.java
new file mode 100644
index 0000000..4f65ff1
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ReadableBuffers.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Charsets.UTF_8;
+
+import com.google.common.base.Preconditions;
+import io.grpc.KnownLength;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+/**
+ * Utility methods for creating {@link ReadableBuffer} instances.
+ */
+public final class ReadableBuffers {
+  private static final ReadableBuffer EMPTY_BUFFER = new ByteArrayWrapper(new byte[0]);
+
+  /**
+   * Returns an empty {@link ReadableBuffer} instance.
+   */
+  public static ReadableBuffer empty() {
+    return EMPTY_BUFFER;
+  }
+
+  /**
+   * Shortcut for {@code wrap(bytes, 0, bytes.length}.
+   */
+  public static ReadableBuffer wrap(byte[] bytes) {
+    return new ByteArrayWrapper(bytes, 0, bytes.length);
+  }
+
+  /**
+   * Creates a new {@link ReadableBuffer} that is backed by the given byte array.
+   *
+   * @param bytes the byte array being wrapped.
+   * @param offset the starting offset for the buffer within the byte array.
+   * @param length the length of the buffer from the {@code offset} index.
+   */
+  public static ReadableBuffer wrap(byte[] bytes, int offset, int length) {
+    return new ByteArrayWrapper(bytes, offset, length);
+  }
+
+  /**
+   * Creates a new {@link ReadableBuffer} that is backed by the given {@link ByteBuffer}. Calls to
+   * read from the buffer will increment the position of the {@link ByteBuffer}.
+   */
+  public static ReadableBuffer wrap(ByteBuffer bytes) {
+    return new ByteReadableBufferWrapper(bytes);
+  }
+
+  /**
+   * Reads an entire {@link ReadableBuffer} to a new array. After calling this method, the buffer
+   * will contain no readable bytes.
+   */
+  public static byte[] readArray(ReadableBuffer buffer) {
+    Preconditions.checkNotNull(buffer, "buffer");
+    int length = buffer.readableBytes();
+    byte[] bytes = new byte[length];
+    buffer.readBytes(bytes, 0, length);
+    return bytes;
+  }
+
+  /**
+   * Reads the entire {@link ReadableBuffer} to a new {@link String} with the given charset.
+   */
+  public static String readAsString(ReadableBuffer buffer, Charset charset) {
+    Preconditions.checkNotNull(charset, "charset");
+    byte[] bytes = readArray(buffer);
+    return new String(bytes, charset);
+  }
+
+  /**
+   * Reads the entire {@link ReadableBuffer} to a new {@link String} using UTF-8 decoding.
+   */
+  public static String readAsStringUtf8(ReadableBuffer buffer) {
+    return readAsString(buffer, UTF_8);
+  }
+
+  /**
+   * Creates a new {@link InputStream} backed by the given buffer. Any read taken on the stream will
+   * automatically increment the read position of this buffer. Closing the stream, however, does not
+   * affect the original buffer.
+   *
+   * @param buffer the buffer backing the new {@link InputStream}.
+   * @param owner if {@code true}, the returned stream will close the buffer when closed.
+   */
+  public static InputStream openStream(ReadableBuffer buffer, boolean owner) {
+    return new BufferInputStream(owner ? buffer : ignoreClose(buffer));
+  }
+
+  /**
+   * Decorates the given {@link ReadableBuffer} to ignore calls to {@link ReadableBuffer#close}.
+   *
+   * @param buffer the buffer to be decorated.
+   * @return a wrapper around {@code buffer} that ignores calls to {@link ReadableBuffer#close}.
+   */
+  public static ReadableBuffer ignoreClose(ReadableBuffer buffer) {
+    return new ForwardingReadableBuffer(buffer) {
+      @Override
+      public void close() {
+        // Ignore.
+      }
+    };
+  }
+
+  /**
+   * A {@link ReadableBuffer} that is backed by a byte array.
+   */
+  private static class ByteArrayWrapper extends AbstractReadableBuffer {
+    int offset;
+    final int end;
+    final byte[] bytes;
+
+    ByteArrayWrapper(byte[] bytes) {
+      this(bytes, 0, bytes.length);
+    }
+
+    ByteArrayWrapper(byte[] bytes, int offset, int length) {
+      Preconditions.checkArgument(offset >= 0, "offset must be >= 0");
+      Preconditions.checkArgument(length >= 0, "length must be >= 0");
+      Preconditions.checkArgument(offset + length <= bytes.length,
+          "offset + length exceeds array boundary");
+      this.bytes = Preconditions.checkNotNull(bytes, "bytes");
+      this.offset = offset;
+      this.end = offset + length;
+    }
+
+    @Override
+    public int readableBytes() {
+      return end - offset;
+    }
+
+    @Override
+    public void skipBytes(int length) {
+      checkReadable(length);
+      offset += length;
+    }
+
+    @Override
+    public int readUnsignedByte() {
+      checkReadable(1);
+      return bytes[offset++] & 0xFF;
+    }
+
+    @Override
+    public void readBytes(byte[] dest, int destIndex, int length) {
+      System.arraycopy(bytes, offset, dest, destIndex, length);
+      offset += length;
+    }
+
+    @Override
+    public void readBytes(ByteBuffer dest) {
+      Preconditions.checkNotNull(dest, "dest");
+      int length = dest.remaining();
+      checkReadable(length);
+      dest.put(bytes, offset, length);
+      offset += length;
+    }
+
+    @Override
+    public void readBytes(OutputStream dest, int length) throws IOException {
+      checkReadable(length);
+      dest.write(bytes, offset, length);
+      offset += length;
+    }
+
+    @Override
+    public ByteArrayWrapper readBytes(int length) {
+      checkReadable(length);
+      int originalOffset = offset;
+      offset += length;
+      return new ByteArrayWrapper(bytes, originalOffset, length);
+    }
+
+    @Override
+    public boolean hasArray() {
+      return true;
+    }
+
+    @Override
+    public byte[] array() {
+      return bytes;
+    }
+
+    @Override
+    public int arrayOffset() {
+      return offset;
+    }
+  }
+
+  /**
+   * A {@link ReadableBuffer} that is backed by a {@link ByteBuffer}.
+   */
+  private static class ByteReadableBufferWrapper extends AbstractReadableBuffer {
+    final ByteBuffer bytes;
+
+    ByteReadableBufferWrapper(ByteBuffer bytes) {
+      this.bytes = Preconditions.checkNotNull(bytes, "bytes");
+    }
+
+    @Override
+    public int readableBytes() {
+      return bytes.remaining();
+    }
+
+    @Override
+    public int readUnsignedByte() {
+      checkReadable(1);
+      return bytes.get() & 0xFF;
+    }
+
+    @Override
+    public void skipBytes(int length) {
+      checkReadable(length);
+      bytes.position(bytes.position() + length);
+    }
+
+    @Override
+    public void readBytes(byte[] dest, int destOffset, int length) {
+      checkReadable(length);
+      bytes.get(dest, destOffset, length);
+    }
+
+    @Override
+    public void readBytes(ByteBuffer dest) {
+      Preconditions.checkNotNull(dest, "dest");
+      int length = dest.remaining();
+      checkReadable(length);
+
+      // Change the limit so that only length bytes are available.
+      int prevLimit = bytes.limit();
+      bytes.limit(bytes.position() + length);
+
+      // Write the bytes and restore the original limit.
+      dest.put(bytes);
+      bytes.limit(prevLimit);
+    }
+
+    @Override
+    public void readBytes(OutputStream dest, int length) throws IOException {
+      checkReadable(length);
+      if (hasArray()) {
+        dest.write(array(), arrayOffset(), length);
+        bytes.position(bytes.position() + length);
+      } else {
+        // The buffer doesn't support array(). Copy the data to an intermediate buffer.
+        byte[] array = new byte[length];
+        bytes.get(array);
+        dest.write(array);
+      }
+    }
+
+    @Override
+    public ByteReadableBufferWrapper readBytes(int length) {
+      checkReadable(length);
+      ByteBuffer buffer = bytes.duplicate();
+      buffer.limit(bytes.position() + length);
+      bytes.position(bytes.position() + length);
+      return new ByteReadableBufferWrapper(buffer);
+    }
+
+    @Override
+    public boolean hasArray() {
+      return bytes.hasArray();
+    }
+
+    @Override
+    public byte[] array() {
+      return bytes.array();
+    }
+
+    @Override
+    public int arrayOffset() {
+      return bytes.arrayOffset() + bytes.position();
+    }
+  }
+
+  /**
+   * An {@link InputStream} that is backed by a {@link ReadableBuffer}.
+   */
+  private static final class BufferInputStream extends InputStream implements KnownLength {
+    final ReadableBuffer buffer;
+
+    public BufferInputStream(ReadableBuffer buffer) {
+      this.buffer = Preconditions.checkNotNull(buffer, "buffer");
+    }
+
+    @Override
+    public int available() throws IOException {
+      return buffer.readableBytes();
+    }
+
+    @Override
+    public int read() {
+      if (buffer.readableBytes() == 0) {
+        // EOF.
+        return -1;
+      }
+      return buffer.readUnsignedByte();
+    }
+
+    @Override
+    public int read(byte[] dest, int destOffset, int length) throws IOException {
+      if (buffer.readableBytes() == 0) {
+        // EOF.
+        return -1;
+      }
+
+      length = Math.min(buffer.readableBytes(), length);
+      buffer.readBytes(dest, destOffset, length);
+      return length;
+    }
+
+    @Override
+    public void close() throws IOException {
+      buffer.close();
+    }
+  }
+
+  private ReadableBuffers() {}
+}
diff --git a/core/src/main/java/io/grpc/internal/ReflectionLongAdderCounter.java b/core/src/main/java/io/grpc/internal/ReflectionLongAdderCounter.java
new file mode 100644
index 0000000..6126706
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ReflectionLongAdderCounter.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A {@link LongCounter} that is implemented with a JDK8 {@link LongAdder}. Instantiates the object
+ * and invokes methods reflectively to avoid a compile time dependency on LongAdder.
+ */
+public final class ReflectionLongAdderCounter implements LongCounter {
+  private static final Logger logger = Logger.getLogger(ReflectionLongAdderCounter.class.getName());
+  private static final Constructor<?> defaultConstructor;
+  private static final Method addMethod;
+  private static final Method sumMethod;
+  private static final RuntimeException initializationException;
+
+  private final Object instance;
+
+  static {
+    Class<?> klass = null;
+    Constructor<?> defaultConstructorLookup = null;
+    Method addMethodLookup = null;
+    Method sumMethodLookup = null;
+    Throwable caught = null;
+    try {
+      klass = Class.forName("java.util.concurrent.atomic.LongAdder");
+
+      addMethodLookup = klass.getMethod("add", Long.TYPE);
+      sumMethodLookup = klass.getMethod("sum");
+
+      Constructor<?>[] constructors = klass.getConstructors();
+      for (Constructor<?> ctor : constructors) {
+        if (ctor.getParameterTypes().length == 0) {
+          defaultConstructorLookup = ctor;
+          break;
+        }
+      }
+    } catch (Throwable e) {
+      logger.log(
+          Level.FINE,
+          "LongAdder can not be found via reflection, this is normal for JDK7 and below",
+          e);
+      caught = e;
+    }
+
+    if (caught == null && defaultConstructorLookup != null) {
+      defaultConstructor = defaultConstructorLookup;
+      addMethod = addMethodLookup;
+      sumMethod = sumMethodLookup;
+      initializationException = null;
+    } else {
+      defaultConstructor = null;
+      addMethod = null;
+      sumMethod = null;
+      initializationException = new RuntimeException(caught);
+    }
+  }
+
+  ReflectionLongAdderCounter() {
+    if (initializationException != null) {
+      throw initializationException;
+    }
+    try {
+      instance = defaultConstructor.newInstance();
+    } catch (InstantiationException e) {
+      throw new RuntimeException(e);
+    } catch (IllegalAccessException e) {
+      throw new RuntimeException(e);
+    } catch (InvocationTargetException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Returns true if the environment supports LongAdder. In other words, we are running in >= JDK8.
+   */
+  static boolean isAvailable() {
+    return initializationException == null;
+  }
+
+  @Override
+  public void add(long delta) {
+    try {
+      addMethod.invoke(instance, delta);
+    } catch (IllegalAccessException e) {
+      throw new RuntimeException(e);
+    } catch (InvocationTargetException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public long value() {
+    try {
+      return (Long) sumMethod.invoke(instance);
+    } catch (IllegalAccessException e) {
+      throw new RuntimeException();
+    } catch (InvocationTargetException e) {
+      throw new RuntimeException();
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/Rescheduler.java b/core/src/main/java/io/grpc/internal/Rescheduler.java
new file mode 100644
index 0000000..75a106e
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/Rescheduler.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Stopwatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Reschedules a runnable lazily.
+ */
+final class Rescheduler {
+
+  // deps
+  private final ScheduledExecutorService scheduler;
+  private final Executor serializingExecutor;
+  private final Runnable runnable;
+
+  // state
+  private final Stopwatch stopwatch;
+  private long runAtNanos;
+  private boolean enabled;
+  private ScheduledFuture<?> wakeUp;
+
+  Rescheduler(
+      Runnable r,
+      Executor serializingExecutor,
+      ScheduledExecutorService scheduler,
+      Stopwatch stopwatch) {
+    this.runnable = r;
+    this.serializingExecutor = serializingExecutor;
+    this.scheduler = scheduler;
+    this.stopwatch = stopwatch;
+    stopwatch.start();
+  }
+
+  /* must be called from the {@link #serializingExecutor} originally passed in. */
+  void reschedule(long delay, TimeUnit timeUnit) {
+    long delayNanos = timeUnit.toNanos(delay);
+    long newRunAtNanos = nanoTime() + delayNanos;
+    enabled = true;
+    if (newRunAtNanos - runAtNanos < 0 || wakeUp == null) {
+      if (wakeUp != null) {
+        wakeUp.cancel(false);
+      }
+      wakeUp = scheduler.schedule(new FutureRunnable(), delayNanos, TimeUnit.NANOSECONDS);
+    }
+    runAtNanos = newRunAtNanos;
+  }
+
+  // must be called from channel executor
+  void cancel(boolean permanent) {
+    enabled = false;
+    if (permanent && wakeUp != null) {
+      wakeUp.cancel(false);
+      wakeUp = null;
+    }
+  }
+
+  private final class FutureRunnable implements Runnable {
+    @Override
+    public void run() {
+      Rescheduler.this.serializingExecutor.execute(new ChannelFutureRunnable());
+    }
+
+    private boolean isEnabled() {
+      return Rescheduler.this.enabled;
+    }
+  }
+
+  private final class ChannelFutureRunnable implements Runnable {
+
+    @Override
+    public void run() {
+      if (!enabled) {
+        wakeUp = null;
+        return;
+      }
+      long now = nanoTime();
+      if (runAtNanos - now > 0) {
+        wakeUp = scheduler.schedule(
+            new FutureRunnable(), runAtNanos - now,  TimeUnit.NANOSECONDS);
+      } else {
+        enabled = false;
+        wakeUp = null;
+        runnable.run();
+      }
+    }
+  }
+
+  @VisibleForTesting
+  static boolean isEnabled(Runnable r) {
+    return ((FutureRunnable) r).isEnabled();
+  }
+
+  private long nanoTime() {
+    return stopwatch.elapsed(TimeUnit.NANOSECONDS);
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/RetriableStream.java b/core/src/main/java/io/grpc/internal/RetriableStream.java
new file mode 100644
index 0000000..cde8635
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/RetriableStream.java
@@ -0,0 +1,1014 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import io.grpc.Attributes;
+import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
+import io.grpc.Compressor;
+import io.grpc.Deadline;
+import io.grpc.DecompressorRegistry;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+/** A logical {@link ClientStream} that is retriable. */
+abstract class RetriableStream<ReqT> implements ClientStream {
+  @VisibleForTesting
+  static final Metadata.Key<String> GRPC_PREVIOUS_RPC_ATTEMPTS =
+      Metadata.Key.of("grpc-previous-rpc-attempts", Metadata.ASCII_STRING_MARSHALLER);
+
+  @VisibleForTesting
+  static final Metadata.Key<String> GRPC_RETRY_PUSHBACK_MS =
+      Metadata.Key.of("grpc-retry-pushback-ms", Metadata.ASCII_STRING_MARSHALLER);
+
+  private static final Status CANCELLED_BECAUSE_COMMITTED =
+      Status.CANCELLED.withDescription("Stream thrown away because RetriableStream committed");
+
+  private final MethodDescriptor<ReqT, ?> method;
+  private final Executor callExecutor;
+  private final ScheduledExecutorService scheduledExecutorService;
+  // Must not modify it.
+  private final Metadata headers;
+  private final RetryPolicy.Provider retryPolicyProvider;
+  private final HedgingPolicy.Provider hedgingPolicyProvider;
+  private RetryPolicy retryPolicy;
+
+  /** Must be held when updating state, accessing state.buffer, or certain substream attributes. */
+  private final Object lock = new Object();
+
+  private final ChannelBufferMeter channelBufferUsed;
+  private final long perRpcBufferLimit;
+  private final long channelBufferLimit;
+  @Nullable
+  private final Throttle throttle;
+
+  private volatile State state = new State(
+      new ArrayList<BufferEntry>(8), Collections.<Substream>emptyList(), null, false, false);
+
+  /**
+   * Either transparent retry happened or reached server's application logic.
+   */
+  private boolean noMoreTransparentRetry;
+
+  // Used for recording the share of buffer used for the current call out of the channel buffer.
+  // This field would not be necessary if there is no channel buffer limit.
+  @GuardedBy("lock")
+  private long perRpcBufferUsed;
+
+  private ClientStreamListener masterListener;
+  private Future<?> scheduledRetry;
+  private long nextBackoffIntervalNanos;
+
+  RetriableStream(
+      MethodDescriptor<ReqT, ?> method, Metadata headers,
+      ChannelBufferMeter channelBufferUsed, long perRpcBufferLimit, long channelBufferLimit,
+      Executor callExecutor, ScheduledExecutorService scheduledExecutorService,
+      RetryPolicy.Provider retryPolicyProvider, HedgingPolicy.Provider hedgingPolicyProvider,
+      @Nullable Throttle throttle) {
+    this.method = method;
+    this.channelBufferUsed = channelBufferUsed;
+    this.perRpcBufferLimit = perRpcBufferLimit;
+    this.channelBufferLimit = channelBufferLimit;
+    this.callExecutor = callExecutor;
+    this.scheduledExecutorService = scheduledExecutorService;
+    this.headers = headers;
+    this.retryPolicyProvider = checkNotNull(retryPolicyProvider, "retryPolicyProvider");
+    this.hedgingPolicyProvider = checkNotNull(hedgingPolicyProvider, "hedgingPolicyProvider");
+    this.throttle = throttle;
+  }
+
+  @Nullable // null if already committed
+  @CheckReturnValue
+  private Runnable commit(final Substream winningSubstream) {
+    synchronized (lock) {
+      if (state.winningSubstream != null) {
+        return null;
+      }
+      final Collection<Substream> savedDrainedSubstreams = state.drainedSubstreams;
+
+      state = state.committed(winningSubstream);
+
+      // subtract the share of this RPC from channelBufferUsed.
+      channelBufferUsed.addAndGet(-perRpcBufferUsed);
+
+      class CommitTask implements Runnable {
+        @Override
+        public void run() {
+          // For hedging only, not needed for normal retry
+          // TODO(zdapeng): also cancel all the scheduled hedges.
+          for (Substream substream : savedDrainedSubstreams) {
+            if (substream != winningSubstream) {
+              substream.stream.cancel(CANCELLED_BECAUSE_COMMITTED);
+            }
+          }
+
+          postCommit();
+        }
+      }
+
+      return new CommitTask();
+    }
+  }
+
+  abstract void postCommit();
+
+  /**
+   * Calls commit() and if successful runs the post commit task.
+   */
+  private void commitAndRun(Substream winningSubstream) {
+    Runnable postCommitTask = commit(winningSubstream);
+
+    if (postCommitTask != null) {
+      postCommitTask.run();
+    }
+  }
+
+
+  private Substream createSubstream(int previousAttempts) {
+    Substream sub = new Substream(previousAttempts);
+    // one tracer per substream
+    final ClientStreamTracer bufferSizeTracer = new BufferSizeTracer(sub);
+    ClientStreamTracer.Factory tracerFactory = new ClientStreamTracer.Factory() {
+      @Override
+      public ClientStreamTracer newClientStreamTracer(CallOptions callOptions, Metadata headers) {
+        return bufferSizeTracer;
+      }
+    };
+
+    Metadata newHeaders = updateHeaders(headers, previousAttempts);
+    // NOTICE: This set _must_ be done before stream.start() and it actually is.
+    sub.stream = newSubstream(tracerFactory, newHeaders);
+    return sub;
+  }
+
+  /**
+   * Creates a new physical ClientStream that represents a retry/hedging attempt. The returned
+   * Client stream is not yet started.
+   */
+  abstract ClientStream newSubstream(
+      ClientStreamTracer.Factory tracerFactory, Metadata headers);
+
+  /** Adds grpc-previous-rpc-attempts in the headers of a retry/hedging RPC. */
+  @VisibleForTesting
+  final Metadata updateHeaders(
+      Metadata originalHeaders, int previousAttempts) {
+    Metadata newHeaders = new Metadata();
+    newHeaders.merge(originalHeaders);
+    if (previousAttempts > 0) {
+      newHeaders.put(GRPC_PREVIOUS_RPC_ATTEMPTS, String.valueOf(previousAttempts));
+    }
+    return newHeaders;
+  }
+
+  private void drain(Substream substream) {
+    int index = 0;
+    int chunk = 0x80;
+    List<BufferEntry> list = null;
+
+    while (true) {
+      State savedState;
+
+      synchronized (lock) {
+        savedState = state;
+        if (savedState.winningSubstream != null && savedState.winningSubstream != substream) {
+          // committed but not me
+          break;
+        }
+        if (index == savedState.buffer.size()) { // I'm drained
+          state = savedState.substreamDrained(substream);
+          return;
+        }
+
+        if (substream.closed) {
+          return;
+        }
+
+        int stop = Math.min(index + chunk, savedState.buffer.size());
+        if (list == null) {
+          list = new ArrayList<>(savedState.buffer.subList(index, stop));
+        } else {
+          list.clear();
+          list.addAll(savedState.buffer.subList(index, stop));
+        }
+        index = stop;
+      }
+
+      for (BufferEntry bufferEntry : list) {
+        savedState = state;
+        if (savedState.winningSubstream != null && savedState.winningSubstream != substream) {
+          // committed but not me
+          break;
+        }
+        if (savedState.cancelled) {
+          checkState(
+              savedState.winningSubstream == substream,
+              "substream should be CANCELLED_BECAUSE_COMMITTED already");
+          return;
+        }
+        bufferEntry.runWith(substream);
+      }
+    }
+
+    substream.stream.cancel(CANCELLED_BECAUSE_COMMITTED);
+  }
+
+  /**
+   * Runs pre-start tasks. Returns the Status of shutdown if the channel is shutdown.
+   */
+  @CheckReturnValue
+  @Nullable
+  abstract Status prestart();
+
+  /** Starts the first PRC attempt. */
+  @Override
+  public final void start(ClientStreamListener listener) {
+    masterListener = listener;
+
+    Status shutdownStatus = prestart();
+
+    if (shutdownStatus != null) {
+      cancel(shutdownStatus);
+      return;
+    }
+
+    class StartEntry implements BufferEntry {
+      @Override
+      public void runWith(Substream substream) {
+        substream.stream.start(new Sublistener(substream));
+      }
+    }
+
+    synchronized (lock) {
+      state.buffer.add(new StartEntry());
+    }
+
+    Substream substream = createSubstream(0);
+    drain(substream);
+
+    // TODO(zdapeng): schedule hedging if needed
+  }
+
+  @Override
+  public final void cancel(Status reason) {
+    Substream noopSubstream = new Substream(0 /* previousAttempts doesn't matter here */);
+    noopSubstream.stream = new NoopClientStream();
+    Runnable runnable = commit(noopSubstream);
+
+    if (runnable != null) {
+      Future<?> savedScheduledRetry = scheduledRetry;
+      if (savedScheduledRetry != null) {
+        savedScheduledRetry.cancel(false);
+        scheduledRetry = null;
+      }
+      masterListener.closed(reason, new Metadata());
+      runnable.run();
+      return;
+    }
+
+    state.winningSubstream.stream.cancel(reason);
+    synchronized (lock) {
+      // This is not required, but causes a short-circuit in the draining process.
+      state = state.cancelled();
+    }
+  }
+
+  private void delayOrExecute(BufferEntry bufferEntry) {
+    Collection<Substream> savedDrainedSubstreams;
+    synchronized (lock) {
+      if (!state.passThrough) {
+        state.buffer.add(bufferEntry);
+      }
+      savedDrainedSubstreams = state.drainedSubstreams;
+    }
+
+    for (Substream substream : savedDrainedSubstreams) {
+      bufferEntry.runWith(substream);
+    }
+  }
+
+  /**
+   * Do not use it directly. Use {@link #sendMessage(ReqT)} instead because we don't use InputStream
+   * for buffering.
+   */
+  @Override
+  public final void writeMessage(InputStream message) {
+    throw new IllegalStateException("RetriableStream.writeMessage() should not be called directly");
+  }
+
+  final void sendMessage(final ReqT message) {
+    State savedState = state;
+    if (savedState.passThrough) {
+      savedState.winningSubstream.stream.writeMessage(method.streamRequest(message));
+      return;
+    }
+
+    class SendMessageEntry implements BufferEntry {
+      @Override
+      public void runWith(Substream substream) {
+        substream.stream.writeMessage(method.streamRequest(message));
+      }
+    }
+
+    delayOrExecute(new SendMessageEntry());
+  }
+
+  @Override
+  public final void request(final int numMessages) {
+    State savedState = state;
+    if (savedState.passThrough) {
+      savedState.winningSubstream.stream.request(numMessages);
+      return;
+    }
+
+    class RequestEntry implements BufferEntry {
+      @Override
+      public void runWith(Substream substream) {
+        substream.stream.request(numMessages);
+      }
+    }
+
+    delayOrExecute(new RequestEntry());
+  }
+
+  @Override
+  public final void flush() {
+    State savedState = state;
+    if (savedState.passThrough) {
+      savedState.winningSubstream.stream.flush();
+      return;
+    }
+
+    class FlushEntry implements BufferEntry {
+      @Override
+      public void runWith(Substream substream) {
+        substream.stream.flush();
+      }
+    }
+
+    delayOrExecute(new FlushEntry());
+  }
+
+  @Override
+  public final boolean isReady() {
+    for (Substream substream : state.drainedSubstreams) {
+      if (substream.stream.isReady()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public final void setCompressor(final Compressor compressor) {
+    class CompressorEntry implements BufferEntry {
+      @Override
+      public void runWith(Substream substream) {
+        substream.stream.setCompressor(compressor);
+      }
+    }
+
+    delayOrExecute(new CompressorEntry());
+  }
+
+  @Override
+  public final void setFullStreamDecompression(final boolean fullStreamDecompression) {
+    class FullStreamDecompressionEntry implements BufferEntry {
+      @Override
+      public void runWith(Substream substream) {
+        substream.stream.setFullStreamDecompression(fullStreamDecompression);
+      }
+    }
+
+    delayOrExecute(new FullStreamDecompressionEntry());
+  }
+
+  @Override
+  public final void setMessageCompression(final boolean enable) {
+    class MessageCompressionEntry implements BufferEntry {
+      @Override
+      public void runWith(Substream substream) {
+        substream.stream.setMessageCompression(enable);
+      }
+    }
+
+    delayOrExecute(new MessageCompressionEntry());
+  }
+
+  @Override
+  public final void halfClose() {
+    class HalfCloseEntry implements BufferEntry {
+      @Override
+      public void runWith(Substream substream) {
+        substream.stream.halfClose();
+      }
+    }
+
+    delayOrExecute(new HalfCloseEntry());
+  }
+
+  @Override
+  public final void setAuthority(final String authority) {
+    class AuthorityEntry implements BufferEntry {
+      @Override
+      public void runWith(Substream substream) {
+        substream.stream.setAuthority(authority);
+      }
+    }
+
+    delayOrExecute(new AuthorityEntry());
+  }
+
+  @Override
+  public final void setDecompressorRegistry(final DecompressorRegistry decompressorRegistry) {
+    class DecompressorRegistryEntry implements BufferEntry {
+      @Override
+      public void runWith(Substream substream) {
+        substream.stream.setDecompressorRegistry(decompressorRegistry);
+      }
+    }
+
+    delayOrExecute(new DecompressorRegistryEntry());
+  }
+
+  @Override
+  public final void setMaxInboundMessageSize(final int maxSize) {
+    class MaxInboundMessageSizeEntry implements BufferEntry {
+      @Override
+      public void runWith(Substream substream) {
+        substream.stream.setMaxInboundMessageSize(maxSize);
+      }
+    }
+
+    delayOrExecute(new MaxInboundMessageSizeEntry());
+  }
+
+  @Override
+  public final void setMaxOutboundMessageSize(final int maxSize) {
+    class MaxOutboundMessageSizeEntry implements BufferEntry {
+      @Override
+      public void runWith(Substream substream) {
+        substream.stream.setMaxOutboundMessageSize(maxSize);
+      }
+    }
+
+    delayOrExecute(new MaxOutboundMessageSizeEntry());
+  }
+
+  @Override
+  public final void setDeadline(final Deadline deadline) {
+    class DeadlineEntry implements BufferEntry {
+      @Override
+      public void runWith(Substream substream) {
+        substream.stream.setDeadline(deadline);
+      }
+    }
+
+    delayOrExecute(new DeadlineEntry());
+  }
+
+  @Override
+  public final Attributes getAttributes() {
+    if (state.winningSubstream != null) {
+      return state.winningSubstream.stream.getAttributes();
+    }
+    return Attributes.EMPTY;
+  }
+
+  private static Random random = new Random();
+
+  @VisibleForTesting
+  static void setRandom(Random random) {
+    RetriableStream.random = random;
+  }
+
+  boolean hasHedging() {
+    return false;
+  }
+
+  private interface BufferEntry {
+    /** Replays the buffer entry with the given stream. */
+    void runWith(Substream substream);
+  }
+
+  private final class Sublistener implements ClientStreamListener {
+    final Substream substream;
+
+    Sublistener(Substream substream) {
+      this.substream = substream;
+    }
+
+    @Override
+    public void headersRead(Metadata headers) {
+      commitAndRun(substream);
+      if (state.winningSubstream == substream) {
+        masterListener.headersRead(headers);
+        if (throttle != null) {
+          throttle.onSuccess();
+        }
+      }
+    }
+
+    @Override
+    public void closed(Status status, Metadata trailers) {
+      closed(status, RpcProgress.PROCESSED, trailers);
+    }
+
+    @Override
+    public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) {
+      synchronized (lock) {
+        state = state.substreamClosed(substream);
+      }
+
+      // handle a race between buffer limit exceeded and closed, when setting
+      // substream.bufferLimitExceeded = true happens before state.substreamClosed(substream).
+      if (substream.bufferLimitExceeded) {
+        commitAndRun(substream);
+        if (state.winningSubstream == substream) {
+          masterListener.closed(status, trailers);
+        }
+        return;
+      }
+
+      if (state.winningSubstream == null) {
+        if (rpcProgress == RpcProgress.REFUSED && !noMoreTransparentRetry) {
+          // TODO(zdapeng): in hedging case noMoreTransparentRetry might need be synchronized.
+          noMoreTransparentRetry = true;
+          callExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+              // transparent retry
+              Substream newSubstream = createSubstream(
+                  substream.previousAttempts);
+              drain(newSubstream);
+            }
+          });
+          return;
+        } else if (rpcProgress == RpcProgress.DROPPED) {
+          // For normal retry, nothing need be done here, will just commit.
+          // For hedging:
+          // TODO(zdapeng): cancel all scheduled hedges (TBD)
+        } else {
+          noMoreTransparentRetry = true;
+
+          if (retryPolicy == null) {
+            retryPolicy = retryPolicyProvider.get();
+            nextBackoffIntervalNanos = retryPolicy.initialBackoffNanos;
+          }
+
+          RetryPlan retryPlan = makeRetryDecision(retryPolicy, status, trailers);
+          if (retryPlan.shouldRetry) {
+            // The check state.winningSubstream == null, checking if is not already committed, is
+            // racy, but is still safe b/c the retry will also handle committed/cancellation
+            scheduledRetry = scheduledExecutorService.schedule(
+                new Runnable() {
+                  @Override
+                  public void run() {
+                    scheduledRetry = null;
+                    callExecutor.execute(new Runnable() {
+                      @Override
+                      public void run() {
+                        // retry
+                        Substream newSubstream = createSubstream(substream.previousAttempts + 1);
+                        drain(newSubstream);
+                      }
+                    });
+                  }
+                },
+                retryPlan.backoffNanos,
+                TimeUnit.NANOSECONDS);
+            return;
+          }
+        }
+      }
+
+      if (!hasHedging()) {
+        commitAndRun(substream);
+        if (state.winningSubstream == substream) {
+          masterListener.closed(status, trailers);
+        }
+      }
+
+      // TODO(zdapeng): in hedge case, if this is a fatal status, cancel all the other attempts, and
+      // close the masterListener.
+    }
+
+    /**
+     * Decides in current situation whether or not the RPC should retry and if it should retry how
+     * long the backoff should be. The decision does not take the commitment status into account, so
+     * caller should check it separately.
+     */
+    // TODO(zdapeng): add HedgingPolicy as param
+    private RetryPlan makeRetryDecision(RetryPolicy retryPolicy, Status status, Metadata trailer) {
+      boolean shouldRetry = false;
+      long backoffNanos = 0L;
+      boolean isRetryableStatusCode = retryPolicy.retryableStatusCodes.contains(status.getCode());
+
+      String pushbackStr = trailer.get(GRPC_RETRY_PUSHBACK_MS);
+      Integer pushbackMillis = null;
+      if (pushbackStr != null) {
+        try {
+          pushbackMillis = Integer.valueOf(pushbackStr);
+        } catch (NumberFormatException e) {
+          pushbackMillis = -1;
+        }
+      }
+
+      boolean isThrottled = false;
+      if (throttle != null) {
+        if (isRetryableStatusCode || (pushbackMillis != null && pushbackMillis < 0)) {
+          isThrottled = !throttle.onQualifiedFailureThenCheckIsAboveThreshold();
+        }
+      }
+
+      if (retryPolicy.maxAttempts > substream.previousAttempts + 1 && !isThrottled) {
+        if (pushbackMillis == null) {
+          if (isRetryableStatusCode) {
+            shouldRetry = true;
+            backoffNanos = (long) (nextBackoffIntervalNanos * random.nextDouble());
+            nextBackoffIntervalNanos = Math.min(
+                (long) (nextBackoffIntervalNanos * retryPolicy.backoffMultiplier),
+                retryPolicy.maxBackoffNanos);
+
+          } // else no retry
+        } else if (pushbackMillis >= 0) {
+          shouldRetry = true;
+          backoffNanos = TimeUnit.MILLISECONDS.toNanos(pushbackMillis);
+          nextBackoffIntervalNanos = retryPolicy.initialBackoffNanos;
+        } // else no retry
+      } // else no retry
+
+      // TODO(zdapeng): transparent retry
+      // TODO(zdapeng): hedging
+      return new RetryPlan(shouldRetry, backoffNanos);
+    }
+
+    @Override
+    public void messagesAvailable(MessageProducer producer) {
+      State savedState = state;
+      checkState(
+          savedState.winningSubstream != null, "Headers should be received prior to messages.");
+      if (savedState.winningSubstream != substream) {
+        return;
+      }
+      masterListener.messagesAvailable(producer);
+    }
+
+    @Override
+    public void onReady() {
+      // TODO(zdapeng): the more correct way to handle onReady
+      if (state.drainedSubstreams.contains(substream)) {
+        masterListener.onReady();
+      }
+    }
+  }
+
+  private static final class State {
+    /** Committed and the winning substream drained. */
+    final boolean passThrough;
+
+    /** A list of buffered ClientStream runnables. Set to Null once passThrough. */
+    @Nullable final List<BufferEntry> buffer;
+
+    /**
+     * Unmodifiable collection of all the substreams that are drained. Exceptional cases: Singleton
+     * once passThrough; Empty if committed but not passTrough.
+     */
+    final Collection<Substream> drainedSubstreams;
+
+    /** Null until committed. */
+    @Nullable final Substream winningSubstream;
+
+    /** Not required to set to true when cancelled, but can short-circuit the draining process. */
+    final boolean cancelled;
+
+    State(
+        @Nullable List<BufferEntry> buffer,
+        Collection<Substream> drainedSubstreams,
+        @Nullable Substream winningSubstream,
+        boolean cancelled,
+        boolean passThrough) {
+      this.buffer = buffer;
+      this.drainedSubstreams =
+          checkNotNull(drainedSubstreams, "drainedSubstreams");
+      this.winningSubstream = winningSubstream;
+      this.cancelled = cancelled;
+      this.passThrough = passThrough;
+
+      checkState(!passThrough || buffer == null, "passThrough should imply buffer is null");
+      checkState(
+          !passThrough || winningSubstream != null,
+          "passThrough should imply winningSubstream != null");
+      checkState(
+          !passThrough
+              || (drainedSubstreams.size() == 1 && drainedSubstreams.contains(winningSubstream))
+              || (drainedSubstreams.size() == 0 && winningSubstream.closed),
+          "passThrough should imply winningSubstream is drained");
+      checkState(!cancelled || winningSubstream != null, "cancelled should imply committed");
+    }
+
+    @CheckReturnValue
+    // GuardedBy RetriableStream.lock
+    State cancelled() {
+      return new State(buffer, drainedSubstreams, winningSubstream, true, passThrough);
+    }
+
+    /** The given substream is drained. */
+    @CheckReturnValue
+    // GuardedBy RetriableStream.lock
+    State substreamDrained(Substream substream) {
+      checkState(!passThrough, "Already passThrough");
+
+      Collection<Substream> drainedSubstreams;
+      
+      if (substream.closed) {
+        drainedSubstreams = this.drainedSubstreams;
+      } else if (this.drainedSubstreams.isEmpty()) {
+        // optimize for 0-retry, which is most of the cases.
+        drainedSubstreams = Collections.singletonList(substream);
+      } else {
+        drainedSubstreams = new ArrayList<>(this.drainedSubstreams);
+        drainedSubstreams.add(substream);
+        drainedSubstreams = Collections.unmodifiableCollection(drainedSubstreams);
+      }
+
+      boolean passThrough = winningSubstream != null;
+
+      List<BufferEntry> buffer = this.buffer;
+      if (passThrough) {
+        checkState(
+            winningSubstream == substream, "Another RPC attempt has already committed");
+        buffer = null;
+      }
+
+      return new State(buffer, drainedSubstreams, winningSubstream, cancelled, passThrough);
+    }
+
+    /** The given substream is closed. */
+    @CheckReturnValue
+    // GuardedBy RetriableStream.lock
+    State substreamClosed(Substream substream) {
+      substream.closed = true;
+      if (this.drainedSubstreams.contains(substream)) {
+        Collection<Substream> drainedSubstreams = new ArrayList<>(this.drainedSubstreams);
+        drainedSubstreams.remove(substream);
+        drainedSubstreams = Collections.unmodifiableCollection(drainedSubstreams);
+        return new State(buffer, drainedSubstreams, winningSubstream, cancelled, passThrough);
+      } else {
+        return this;
+      }
+    }
+
+    @CheckReturnValue
+    // GuardedBy RetriableStream.lock
+    State committed(Substream winningSubstream) {
+      checkState(this.winningSubstream == null, "Already committed");
+
+      boolean passThrough = false;
+      List<BufferEntry> buffer = this.buffer;
+      Collection<Substream> drainedSubstreams;
+
+      if (this.drainedSubstreams.contains(winningSubstream)) {
+        passThrough = true;
+        buffer = null;
+        drainedSubstreams = Collections.singleton(winningSubstream);
+      } else {
+        drainedSubstreams = Collections.emptyList();
+      }
+
+      return new State(buffer, drainedSubstreams, winningSubstream, cancelled, passThrough);
+    }
+  }
+
+  /**
+   * A wrapper of a physical stream of a retry/hedging attempt, that comes with some useful
+   *  attributes.
+   */
+  private static final class Substream {
+    ClientStream stream;
+
+    // GuardedBy RetriableStream.lock
+    boolean closed;
+
+    // setting to true must be GuardedBy RetriableStream.lock
+    boolean bufferLimitExceeded;
+
+    final int previousAttempts;
+
+    Substream(int previousAttempts) {
+      this.previousAttempts = previousAttempts;
+    }
+  }
+
+
+  /**
+   * Traces the buffer used by a substream.
+   */
+  class BufferSizeTracer extends ClientStreamTracer {
+    // Each buffer size tracer is dedicated to one specific substream.
+    private final Substream substream;
+
+    @GuardedBy("lock")
+    long bufferNeeded;
+
+    BufferSizeTracer(Substream substream) {
+      this.substream = substream;
+    }
+
+    /**
+     * A message is sent to the wire, so its reference would be released if no retry or
+     * hedging were involved. So at this point we have to hold the reference of the message longer
+     * for retry, and we need to increment {@code substream.bufferNeeded}.
+     */
+    @Override
+    public void outboundWireSize(long bytes) {
+      if (state.winningSubstream != null) {
+        return;
+      }
+
+      Runnable postCommitTask = null;
+
+      // TODO(zdapeng): avoid using the same lock for both in-bound and out-bound.
+      synchronized (lock) {
+        if (state.winningSubstream != null || substream.closed) {
+          return;
+        }
+        bufferNeeded += bytes;
+        if (bufferNeeded <= perRpcBufferUsed) {
+          return;
+        }
+
+        if (bufferNeeded > perRpcBufferLimit) {
+          substream.bufferLimitExceeded = true;
+        } else {
+          // Only update channelBufferUsed when perRpcBufferUsed is not exceeding perRpcBufferLimit.
+          long savedChannelBufferUsed =
+              channelBufferUsed.addAndGet(bufferNeeded - perRpcBufferUsed);
+          perRpcBufferUsed = bufferNeeded;
+
+          if (savedChannelBufferUsed > channelBufferLimit) {
+            substream.bufferLimitExceeded = true;
+          }
+        }
+
+        if (substream.bufferLimitExceeded) {
+          postCommitTask = commit(substream);
+        }
+      }
+
+      if (postCommitTask != null) {
+        postCommitTask.run();
+      }
+    }
+  }
+
+  /**
+   *  Used to keep track of the total amount of memory used to buffer retryable or hedged RPCs for
+   *  the Channel. There should be a single instance of it for each channel.
+   */
+  static final class ChannelBufferMeter {
+    private final AtomicLong bufferUsed = new AtomicLong();
+
+    @VisibleForTesting
+    long addAndGet(long newBytesUsed) {
+      return bufferUsed.addAndGet(newBytesUsed);
+    }
+  }
+
+  /**
+   * Used for retry throttling.
+   */
+  static final class Throttle {
+
+    private static final int THREE_DECIMAL_PLACES_SCALE_UP = 1000;
+
+    /**
+     * 1000 times the maxTokens field of the retryThrottling policy in service config.
+     * The number of tokens starts at maxTokens. The token_count will always be between 0 and
+     * maxTokens.
+     */
+    final int maxTokens;
+
+    /**
+     * Half of {@code maxTokens}.
+     */
+    final int threshold;
+
+    /**
+     * 1000 times the tokenRatio field of the retryThrottling policy in service config.
+     */
+    final int tokenRatio;
+
+    final AtomicInteger tokenCount = new AtomicInteger();
+
+    Throttle(float maxTokens, float tokenRatio) {
+      // tokenRatio is up to 3 decimal places
+      this.tokenRatio = (int) (tokenRatio * THREE_DECIMAL_PLACES_SCALE_UP);
+      this.maxTokens = (int) (maxTokens * THREE_DECIMAL_PLACES_SCALE_UP);
+      this.threshold = this.maxTokens / 2;
+      tokenCount.set(this.maxTokens);
+    }
+
+    @VisibleForTesting
+    boolean isAboveThreshold() {
+      return tokenCount.get() > threshold;
+    }
+
+    /**
+     * Counts down the token on qualified failure and checks if it is above the threshold
+     * atomically. Qualified failure is a failure with a retryable or non-fatal status code or with
+     * a not-to-retry pushback.
+     */
+    @VisibleForTesting
+    boolean onQualifiedFailureThenCheckIsAboveThreshold() {
+      while (true) {
+        int currentCount = tokenCount.get();
+        if (currentCount == 0) {
+          return false;
+        }
+        int decremented = currentCount - (1 * THREE_DECIMAL_PLACES_SCALE_UP);
+        boolean updated = tokenCount.compareAndSet(currentCount, Math.max(decremented, 0));
+        if (updated) {
+          return decremented > threshold;
+        }
+      }
+    }
+
+    @VisibleForTesting
+    void onSuccess() {
+      while (true) {
+        int currentCount = tokenCount.get();
+        if (currentCount == maxTokens) {
+          break;
+        }
+        int incremented = currentCount + tokenRatio;
+        boolean updated = tokenCount.compareAndSet(currentCount, Math.min(incremented, maxTokens));
+        if (updated) {
+          break;
+        }
+      }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (!(o instanceof Throttle)) {
+        return false;
+      }
+      Throttle that = (Throttle) o;
+      return maxTokens == that.maxTokens && tokenRatio == that.tokenRatio;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(maxTokens, tokenRatio);
+    }
+  }
+
+  private static final class RetryPlan {
+    final boolean shouldRetry;
+    // TODO(zdapeng) boolean hasHedging
+    final long backoffNanos;
+
+    RetryPlan(boolean shouldRetry, long backoffNanos) {
+      this.shouldRetry = shouldRetry;
+      this.backoffNanos = backoffNanos;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/RetryPolicy.java b/core/src/main/java/io/grpc/internal/RetryPolicy.java
new file mode 100644
index 0000000..928e5dc
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/RetryPolicy.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+import io.grpc.Status;
+import io.grpc.Status.Code;
+import java.util.Collections;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Retry policy data object.
+ */
+@Immutable
+final class RetryPolicy {
+  final int maxAttempts;
+  final long initialBackoffNanos;
+  final long maxBackoffNanos;
+  final double backoffMultiplier;
+  final Set<Code> retryableStatusCodes;
+
+  /** No retry. */
+  static final RetryPolicy DEFAULT =
+      new RetryPolicy(1, 0, 0, 1, Collections.<Status.Code>emptySet());
+
+  /**
+   * The caller is supposed to have validated the arguments and handled throwing exception or
+   * logging warnings already, so we avoid repeating args check here.
+   */
+  RetryPolicy(
+      int maxAttempts,
+      long initialBackoffNanos,
+      long maxBackoffNanos,
+      double backoffMultiplier,
+      @Nonnull Set<Code> retryableStatusCodes) {
+    this.maxAttempts = maxAttempts;
+    this.initialBackoffNanos = initialBackoffNanos;
+    this.maxBackoffNanos = maxBackoffNanos;
+    this.backoffMultiplier = backoffMultiplier;
+    this.retryableStatusCodes = ImmutableSet.copyOf(retryableStatusCodes);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(
+        maxAttempts,
+        initialBackoffNanos,
+        maxBackoffNanos,
+        backoffMultiplier,
+        retryableStatusCodes);
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (!(other instanceof RetryPolicy)) {
+      return false;
+    }
+    RetryPolicy that = (RetryPolicy) other;
+    return this.maxAttempts == that.maxAttempts
+        && this.initialBackoffNanos == that.initialBackoffNanos
+        && this.maxBackoffNanos == that.maxBackoffNanos
+        && Double.compare(this.backoffMultiplier, that.backoffMultiplier) == 0
+        && Objects.equal(this.retryableStatusCodes, that.retryableStatusCodes);
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("maxAttempts", maxAttempts)
+        .add("initialBackoffNanos", initialBackoffNanos)
+        .add("maxBackoffNanos", maxBackoffNanos)
+        .add("backoffMultiplier", backoffMultiplier)
+        .add("retryableStatusCodes", retryableStatusCodes)
+        .toString();
+  }
+
+  /**
+   * Provides the most suitable retry policy for a call.
+   */
+  interface Provider {
+
+    /**
+     * This method is used no more than once for each call. Never returns null.
+     */
+    RetryPolicy get();
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/SerializeReentrantCallsDirectExecutor.java b/core/src/main/java/io/grpc/internal/SerializeReentrantCallsDirectExecutor.java
new file mode 100644
index 0000000..685caf1
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/SerializeReentrantCallsDirectExecutor.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.Preconditions;
+import java.util.ArrayDeque;
+import java.util.concurrent.Executor;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Executes a task directly in the calling thread, unless it's a reentrant call in which case the
+ * task is enqueued and executed once the calling task completes.
+ *
+ * <p>The {@code Executor} assumes that reentrant calls are rare and its fast path is thus
+ * optimized for that - avoiding queuing and additional object creation altogether.
+ *
+ * <p>This class is not thread-safe.
+ */
+class SerializeReentrantCallsDirectExecutor implements Executor {
+
+  private static final Logger log =
+      Logger.getLogger(SerializeReentrantCallsDirectExecutor.class.getName());
+
+  private boolean executing;
+  // Lazily initialized if a reentrant call is detected.
+  private ArrayDeque<Runnable> taskQueue;
+
+  @Override
+  public void execute(Runnable task) {
+    Preconditions.checkNotNull(task, "'task' must not be null.");
+    if (!executing) {
+      executing = true;
+      try {
+        task.run();
+      } catch (Throwable t) {
+        log.log(Level.SEVERE, "Exception while executing runnable " + task, t);
+      } finally {
+        if (taskQueue != null) {
+          completeQueuedTasks();
+        }
+        executing = false;
+      }
+    } else {
+      enqueue(task);
+    }
+  }
+
+  private void completeQueuedTasks() {
+    Runnable task = null;
+    while ((task = taskQueue.poll()) != null) {
+      try {
+        task.run();
+      } catch (Throwable t) {
+        // Log it and keep going
+        log.log(Level.SEVERE, "Exception while executing runnable " + task, t);
+      }
+    }
+  }
+
+  private void enqueue(Runnable r) {
+    if (taskQueue == null) {
+      taskQueue = new ArrayDeque<Runnable>(4);
+    }
+    taskQueue.add(r);
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/SerializingExecutor.java b/core/src/main/java/io/grpc/internal/SerializingExecutor.java
new file mode 100644
index 0000000..9d9234e
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/SerializingExecutor.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Preconditions;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Executor ensuring that all {@link Runnable} tasks submitted are executed in order
+ * using the provided {@link Executor}, and serially such that no two will ever be
+ * running at the same time.
+ */
+// TODO(madongfly): figure out a way to not expose it or move it to transport package.
+public final class SerializingExecutor implements Executor, Runnable {
+  private static final Logger log =
+      Logger.getLogger(SerializingExecutor.class.getName());
+
+  // When using Atomic*FieldUpdater, some Samsung Android 5.0.x devices encounter a bug in their JDK
+  // reflection API that triggers a NoSuchFieldException. When this occurs, fallback to a
+  // synchronized implementation.
+  private static final AtomicHelper atomicHelper = getAtomicHelper();
+
+  private static AtomicHelper getAtomicHelper() {
+    AtomicHelper helper;
+    try {
+      helper =
+          new FieldUpdaterAtomicHelper(
+              AtomicIntegerFieldUpdater.newUpdater(SerializingExecutor.class, "runState"));
+    } catch (Throwable t) {
+      log.log(Level.SEVERE, "FieldUpdaterAtomicHelper failed", t);
+      helper = new SynchronizedAtomicHelper();
+    }
+    return helper;
+  }
+
+  private static final int STOPPED = 0;
+  private static final int RUNNING = -1;
+
+  /** Underlying executor that all submitted Runnable objects are run on. */
+  private final Executor executor;
+
+  /** A list of Runnables to be run in order. */
+  private final Queue<Runnable> runQueue = new ConcurrentLinkedQueue<Runnable>();
+
+  private volatile int runState = STOPPED;
+
+  /**
+   * Creates a SerializingExecutor, running tasks using {@code executor}.
+   *
+   * @param executor Executor in which tasks should be run. Must not be null.
+   */
+  public SerializingExecutor(Executor executor) {
+    Preconditions.checkNotNull(executor, "'executor' must not be null.");
+    this.executor = executor;
+  }
+
+  /**
+   * Runs the given runnable strictly after all Runnables that were submitted
+   * before it, and using the {@code executor} passed to the constructor.     .
+   */
+  @Override
+  public void execute(Runnable r) {
+    runQueue.add(checkNotNull(r, "'r' must not be null."));
+    schedule(r);
+  }
+
+  private void schedule(@Nullable Runnable removable) {
+    if (atomicHelper.runStateCompareAndSet(this, STOPPED, RUNNING)) {
+      boolean success = false;
+      try {
+        executor.execute(this);
+        success = true;
+      } finally {
+        // It is possible that at this point that there are still tasks in
+        // the queue, it would be nice to keep trying but the error may not
+        // be recoverable.  So we update our state and propagate so that if
+        // our caller deems it recoverable we won't be stuck.
+        if (!success) {
+          if (removable != null) {
+            // This case can only be reached if 'this' was not currently running, and we failed to
+            // reschedule.  The item should still be in the queue for removal.
+            // ConcurrentLinkedQueue claims that null elements are not allowed, but seems to not
+            // throw if the item to remove is null.  If removable is present in the queue twice,
+            // the wrong one may be removed.  It doesn't seem possible for this case to exist today.
+            // This is important to run in case of RejectedExectuionException, so that future calls
+            // to execute don't succeed and accidentally run a previous runnable.
+            runQueue.remove(removable);
+          }
+          atomicHelper.runStateSet(this, STOPPED);
+        }
+      }
+    }
+  }
+
+  @Override
+  public void run() {
+    Runnable r;
+    try {
+      while ((r = runQueue.poll()) != null) {
+        try {
+          r.run();
+        } catch (RuntimeException e) {
+          // Log it and keep going.
+          log.log(Level.SEVERE, "Exception while executing runnable " + r, e);
+        }
+      }
+    } finally {
+      atomicHelper.runStateSet(this, STOPPED);
+    }
+    if (!runQueue.isEmpty()) {
+      // we didn't enqueue anything but someone else did.
+      schedule(null);
+    }
+  }
+
+  private abstract static class AtomicHelper {
+    public abstract boolean runStateCompareAndSet(SerializingExecutor obj, int expect, int update);
+
+    public abstract void runStateSet(SerializingExecutor obj, int newValue);
+  }
+
+  private static final class FieldUpdaterAtomicHelper extends AtomicHelper {
+    private final AtomicIntegerFieldUpdater<SerializingExecutor> runStateUpdater;
+
+    private FieldUpdaterAtomicHelper(
+        AtomicIntegerFieldUpdater<SerializingExecutor> runStateUpdater) {
+      this.runStateUpdater = runStateUpdater;
+    }
+
+    @Override
+    public boolean runStateCompareAndSet(SerializingExecutor obj, int expect, int update) {
+      return runStateUpdater.compareAndSet(obj, expect, update);
+    }
+
+    @Override
+    public void runStateSet(SerializingExecutor obj, int newValue) {
+      runStateUpdater.set(obj, newValue);
+    }
+  }
+
+  private static final class SynchronizedAtomicHelper extends AtomicHelper {
+    @Override
+    public boolean runStateCompareAndSet(SerializingExecutor obj, int expect, int update) {
+      synchronized (obj) {
+        if (obj.runState == expect) {
+          obj.runState = update;
+          return true;
+        }
+        return false;
+      }
+    }
+
+    @Override
+    public void runStateSet(SerializingExecutor obj, int newValue) {
+      synchronized (obj) {
+        obj.runState = newValue;
+      }
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ServerCallImpl.java b/core/src/main/java/io/grpc/internal/ServerCallImpl.java
new file mode 100644
index 0000000..24a852d
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ServerCallImpl.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static io.grpc.internal.GrpcUtil.ACCEPT_ENCODING_SPLITTER;
+import static io.grpc.internal.GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY;
+import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.Attributes;
+import io.grpc.Codec;
+import io.grpc.Compressor;
+import io.grpc.CompressorRegistry;
+import io.grpc.Context;
+import io.grpc.DecompressorRegistry;
+import io.grpc.InternalDecompressorRegistry;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerCall;
+import io.grpc.Status;
+import java.io.InputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+final class ServerCallImpl<ReqT, RespT> extends ServerCall<ReqT, RespT> {
+
+  private static final Logger log = Logger.getLogger(ServerCallImpl.class.getName());
+
+  @VisibleForTesting
+  static final String TOO_MANY_RESPONSES = "Too many responses";
+  @VisibleForTesting
+  static final String MISSING_RESPONSE = "Completed without a response";
+
+  private final ServerStream stream;
+  private final MethodDescriptor<ReqT, RespT> method;
+  private final Context.CancellableContext context;
+  private final byte[] messageAcceptEncoding;
+  private final DecompressorRegistry decompressorRegistry;
+  private final CompressorRegistry compressorRegistry;
+  private CallTracer serverCallTracer;
+
+  // state
+  private volatile boolean cancelled;
+  private boolean sendHeadersCalled;
+  private boolean closeCalled;
+  private Compressor compressor;
+  private boolean messageSent;
+
+  ServerCallImpl(ServerStream stream, MethodDescriptor<ReqT, RespT> method,
+      Metadata inboundHeaders, Context.CancellableContext context,
+      DecompressorRegistry decompressorRegistry, CompressorRegistry compressorRegistry,
+      CallTracer serverCallTracer) {
+    this.stream = stream;
+    this.method = method;
+    this.context = context;
+    this.messageAcceptEncoding = inboundHeaders.get(MESSAGE_ACCEPT_ENCODING_KEY);
+    this.decompressorRegistry = decompressorRegistry;
+    this.compressorRegistry = compressorRegistry;
+    this.serverCallTracer = serverCallTracer;
+    this.serverCallTracer.reportCallStarted();
+  }
+
+  @Override
+  public void request(int numMessages) {
+    stream.request(numMessages);
+  }
+
+  @Override
+  public void sendHeaders(Metadata headers) {
+    checkState(!sendHeadersCalled, "sendHeaders has already been called");
+    checkState(!closeCalled, "call is closed");
+
+    headers.discardAll(MESSAGE_ENCODING_KEY);
+    if (compressor == null) {
+      compressor = Codec.Identity.NONE;
+    } else {
+      if (messageAcceptEncoding != null) {
+        // TODO(carl-mastrangelo): remove the string allocation.
+        if (!GrpcUtil.iterableContains(
+            ACCEPT_ENCODING_SPLITTER.split(new String(messageAcceptEncoding, GrpcUtil.US_ASCII)),
+            compressor.getMessageEncoding())) {
+          // resort to using no compression.
+          compressor = Codec.Identity.NONE;
+        }
+      } else {
+        compressor = Codec.Identity.NONE;
+      }
+    }
+
+    // Always put compressor, even if it's identity.
+    headers.put(MESSAGE_ENCODING_KEY, compressor.getMessageEncoding());
+
+    stream.setCompressor(compressor);
+
+    headers.discardAll(MESSAGE_ACCEPT_ENCODING_KEY);
+    byte[] advertisedEncodings =
+        InternalDecompressorRegistry.getRawAdvertisedMessageEncodings(decompressorRegistry);
+    if (advertisedEncodings.length != 0) {
+      headers.put(MESSAGE_ACCEPT_ENCODING_KEY, advertisedEncodings);
+    }
+
+    // Don't check if sendMessage has been called, since it requires that sendHeaders was already
+    // called.
+    sendHeadersCalled = true;
+    stream.writeHeaders(headers);
+  }
+
+  @Override
+  public void sendMessage(RespT message) {
+    checkState(sendHeadersCalled, "sendHeaders has not been called");
+    checkState(!closeCalled, "call is closed");
+
+    if (method.getType().serverSendsOneMessage() && messageSent) {
+      internalClose(Status.INTERNAL.withDescription(TOO_MANY_RESPONSES));
+      return;
+    }
+
+    messageSent = true;
+    try {
+      InputStream resp = method.streamResponse(message);
+      stream.writeMessage(resp);
+      stream.flush();
+    } catch (RuntimeException e) {
+      close(Status.fromThrowable(e), new Metadata());
+    } catch (Error e) {
+      close(
+          Status.CANCELLED.withDescription("Server sendMessage() failed with Error"),
+          new Metadata());
+      throw e;
+    }
+  }
+
+  @Override
+  public void setMessageCompression(boolean enable) {
+    stream.setMessageCompression(enable);
+  }
+
+  @Override
+  public void setCompression(String compressorName) {
+    // Added here to give a better error message.
+    checkState(!sendHeadersCalled, "sendHeaders has been called");
+
+    compressor = compressorRegistry.lookupCompressor(compressorName);
+    checkArgument(compressor != null, "Unable to find compressor by name %s", compressorName);
+  }
+
+  @Override
+  public boolean isReady() {
+    return stream.isReady();
+  }
+
+  @Override
+  public void close(Status status, Metadata trailers) {
+    checkState(!closeCalled, "call already closed");
+    try {
+      closeCalled = true;
+
+      if (status.isOk() && method.getType().serverSendsOneMessage() && !messageSent) {
+        internalClose(Status.INTERNAL.withDescription(MISSING_RESPONSE));
+        return;
+      }
+
+      stream.close(status, trailers);
+    } finally {
+      serverCallTracer.reportCallEnded(status.isOk());
+    }
+  }
+
+  @Override
+  public boolean isCancelled() {
+    return cancelled;
+  }
+
+  ServerStreamListener newServerStreamListener(ServerCall.Listener<ReqT> listener) {
+    return new ServerStreamListenerImpl<ReqT>(this, listener, context);
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    return stream.getAttributes();
+  }
+
+  @Override
+  public String getAuthority() {
+    return stream.getAuthority();
+  }
+
+  @Override
+  public MethodDescriptor<ReqT, RespT> getMethodDescriptor() {
+    return method;
+  }
+
+  /**
+   * Close the {@link ServerStream} because an internal error occurred. Allow the application to
+   * run until completion, but silently ignore interactions with the {@link ServerStream} from now
+   * on.
+   */
+  private void internalClose(Status internalError) {
+    log.log(Level.WARNING, "Cancelling the stream with status {0}", new Object[] {internalError});
+    stream.cancel(internalError);
+    serverCallTracer.reportCallEnded(internalError.isOk()); // error so always false
+  }
+
+  /**
+   * All of these callbacks are assumed to called on an application thread, and the caller is
+   * responsible for handling thrown exceptions.
+   */
+  @VisibleForTesting
+  static final class ServerStreamListenerImpl<ReqT> implements ServerStreamListener {
+    private final ServerCallImpl<ReqT, ?> call;
+    private final ServerCall.Listener<ReqT> listener;
+    private final Context.CancellableContext context;
+
+    public ServerStreamListenerImpl(
+        ServerCallImpl<ReqT, ?> call, ServerCall.Listener<ReqT> listener,
+        Context.CancellableContext context) {
+      this.call = checkNotNull(call, "call");
+      this.listener = checkNotNull(listener, "listener must not be null");
+      this.context = checkNotNull(context, "context");
+      // Wire ourselves up so that if the context is cancelled, our flag call.cancelled also
+      // reflects the new state. Use a DirectExecutor so that it happens in the same thread
+      // as the caller of {@link Context#cancel}.
+      this.context.addListener(
+          new Context.CancellationListener() {
+            @Override
+            public void cancelled(Context context) {
+              ServerStreamListenerImpl.this.call.cancelled = true;
+            }
+          },
+          MoreExecutors.directExecutor());
+    }
+
+    @SuppressWarnings("Finally") // The code avoids suppressing the exception thrown from try
+    @Override
+    public void messagesAvailable(final MessageProducer producer) {
+      if (call.cancelled) {
+        GrpcUtil.closeQuietly(producer);
+        return;
+      }
+
+      InputStream message;
+      try {
+        while ((message = producer.next()) != null) {
+          try {
+            listener.onMessage(call.method.parseRequest(message));
+          } catch (Throwable t) {
+            GrpcUtil.closeQuietly(message);
+            throw t;
+          }
+          message.close();
+        }
+      } catch (Throwable t) {
+        GrpcUtil.closeQuietly(producer);
+        MoreThrowables.throwIfUnchecked(t);
+        throw new RuntimeException(t);
+      }
+    }
+
+    @Override
+    public void halfClosed() {
+      if (call.cancelled) {
+        return;
+      }
+
+      listener.onHalfClose();
+    }
+
+    @Override
+    public void closed(Status status) {
+      try {
+        if (status.isOk()) {
+          listener.onComplete();
+        } else {
+          call.cancelled = true;
+          listener.onCancel();
+        }
+      } finally {
+        // Cancel context after delivering RPC closure notification to allow the application to
+        // clean up and update any state based on whether onComplete or onCancel was called.
+        context.cancel(null);
+      }
+    }
+
+    @Override
+    public void onReady() {
+      if (call.cancelled) {
+        return;
+      }
+      listener.onReady();
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ServerCallInfoImpl.java b/core/src/main/java/io/grpc/internal/ServerCallInfoImpl.java
new file mode 100644
index 0000000..0e8b899
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ServerCallInfoImpl.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.Objects;
+import io.grpc.Attributes;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerStreamTracer.ServerCallInfo;
+import javax.annotation.Nullable;
+
+/**
+ * An implementation of {@link ServerCallInfo}.
+ */
+final class ServerCallInfoImpl<ReqT, RespT> extends ServerCallInfo<ReqT, RespT> {
+  private final MethodDescriptor<ReqT, RespT> methodDescriptor;
+  private final Attributes attributes;
+  private final String authority;
+
+  ServerCallInfoImpl(
+      MethodDescriptor<ReqT, RespT> methodDescriptor,
+      Attributes attributes,
+      @Nullable String authority) {
+    this.methodDescriptor = methodDescriptor;
+    this.attributes = attributes;
+    this.authority = authority;
+  }
+
+  @Override
+  public MethodDescriptor<ReqT, RespT> getMethodDescriptor() {
+    return methodDescriptor;
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    return attributes;
+  }
+
+  @Override
+  @Nullable
+  public String getAuthority() {
+    return authority;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (!(other instanceof ServerCallInfoImpl)) {
+      return false;
+    }
+    ServerCallInfoImpl<?, ?> that = (ServerCallInfoImpl) other;
+    return Objects.equal(methodDescriptor, that.methodDescriptor)
+        && Objects.equal(attributes, that.attributes)
+        && Objects.equal(authority, that.authority);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(methodDescriptor, attributes, authority);
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ServerImpl.java b/core/src/main/java/io/grpc/internal/ServerImpl.java
new file mode 100644
index 0000000..b4c29c7
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ServerImpl.java
@@ -0,0 +1,788 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+import static io.grpc.Contexts.statusFromCancelled;
+import static io.grpc.Status.DEADLINE_EXCEEDED;
+import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
+import static io.grpc.internal.GrpcUtil.TIMEOUT_KEY;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.Attributes;
+import io.grpc.BinaryLog;
+import io.grpc.CompressorRegistry;
+import io.grpc.Context;
+import io.grpc.Decompressor;
+import io.grpc.DecompressorRegistry;
+import io.grpc.HandlerRegistry;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.ServerStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.InternalLogId;
+import io.grpc.InternalServerInterceptors;
+import io.grpc.Metadata;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.ServerMethodDefinition;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.ServerTransportFilter;
+import io.grpc.Status;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Default implementation of {@link io.grpc.Server}, for creation by transports.
+ *
+ * <p>Expected usage (by a theoretical TCP transport):
+ * <pre><code>public class TcpTransportServerFactory {
+ *   public static Server newServer(Executor executor, HandlerRegistry registry,
+ *       String configuration) {
+ *     return new ServerImpl(executor, registry, new TcpTransportServer(configuration));
+ *   }
+ * }</code></pre>
+ *
+ * <p>Starting the server starts the underlying transport for servicing requests. Stopping the
+ * server stops servicing new requests and waits for all connections to terminate.
+ */
+public final class ServerImpl extends io.grpc.Server implements InternalInstrumented<ServerStats> {
+  private static final Logger log = Logger.getLogger(ServerImpl.class.getName());
+  private static final ServerStreamListener NOOP_LISTENER = new NoopListener();
+
+  private final InternalLogId logId = InternalLogId.allocate(getClass().getName());
+  private final ObjectPool<? extends Executor> executorPool;
+  /** Executor for application processing. Safe to read after {@link #start()}. */
+  private Executor executor;
+  private final HandlerRegistry registry;
+  private final HandlerRegistry fallbackRegistry;
+  private final List<ServerTransportFilter> transportFilters;
+  // This is iterated on a per-call basis.  Use an array instead of a Collection to avoid iterator
+  // creations.
+  private final ServerInterceptor[] interceptors;
+  private final long handshakeTimeoutMillis;
+  @GuardedBy("lock") private boolean started;
+  @GuardedBy("lock") private boolean shutdown;
+  /** non-{@code null} if immediate shutdown has been requested. */
+  @GuardedBy("lock") private Status shutdownNowStatus;
+  /** {@code true} if ServerListenerImpl.serverShutdown() was called. */
+  @GuardedBy("lock") private boolean serverShutdownCallbackInvoked;
+  @GuardedBy("lock") private boolean terminated;
+  /** Service encapsulating something similar to an accept() socket. */
+  private final InternalServer transportServer;
+  private final Object lock = new Object();
+  @GuardedBy("lock") private boolean transportServerTerminated;
+  /** {@code transportServer} and services encapsulating something similar to a TCP connection. */
+  @GuardedBy("lock") private final Collection<ServerTransport> transports =
+      new HashSet<ServerTransport>();
+
+  private final Context rootContext;
+
+  private final DecompressorRegistry decompressorRegistry;
+  private final CompressorRegistry compressorRegistry;
+  private final BinaryLog binlog;
+
+  private final InternalChannelz channelz;
+  private final CallTracer serverCallTracer;
+
+  /**
+   * Construct a server.
+   *
+   * @param builder builder with configuration for server
+   * @param transportServer transport server that will create new incoming transports
+   * @param rootContext context that callbacks for new RPCs should be derived from
+   */
+  ServerImpl(
+      AbstractServerImplBuilder<?> builder,
+      InternalServer transportServer,
+      Context rootContext) {
+    this.executorPool = Preconditions.checkNotNull(builder.executorPool, "executorPool");
+    this.registry = Preconditions.checkNotNull(builder.registryBuilder.build(), "registryBuilder");
+    this.fallbackRegistry =
+        Preconditions.checkNotNull(builder.fallbackRegistry, "fallbackRegistry");
+    this.transportServer = Preconditions.checkNotNull(transportServer, "transportServer");
+    // Fork from the passed in context so that it does not propagate cancellation, it only
+    // inherits values.
+    this.rootContext = Preconditions.checkNotNull(rootContext, "rootContext").fork();
+    this.decompressorRegistry = builder.decompressorRegistry;
+    this.compressorRegistry = builder.compressorRegistry;
+    this.transportFilters = Collections.unmodifiableList(
+        new ArrayList<>(builder.transportFilters));
+    this.interceptors =
+        builder.interceptors.toArray(new ServerInterceptor[builder.interceptors.size()]);
+    this.handshakeTimeoutMillis = builder.handshakeTimeoutMillis;
+    this.binlog = builder.binlog;
+    this.channelz = builder.channelz;
+    this.serverCallTracer = builder.callTracerFactory.create();
+
+    channelz.addServer(this);
+  }
+
+  /**
+   * Bind and start the server.
+   *
+   * @return {@code this} object
+   * @throws IllegalStateException if already started
+   * @throws IOException if unable to bind
+   */
+  @Override
+  public ServerImpl start() throws IOException {
+    synchronized (lock) {
+      checkState(!started, "Already started");
+      checkState(!shutdown, "Shutting down");
+      // Start and wait for any port to actually be bound.
+      transportServer.start(new ServerListenerImpl());
+      executor = Preconditions.checkNotNull(executorPool.getObject(), "executor");
+      started = true;
+      return this;
+    }
+  }
+
+  @Override
+  public int getPort() {
+    synchronized (lock) {
+      checkState(started, "Not started");
+      checkState(!terminated, "Already terminated");
+      return transportServer.getPort();
+    }
+  }
+
+  @Override
+  public List<ServerServiceDefinition> getServices() {
+    List<ServerServiceDefinition> fallbackServices = fallbackRegistry.getServices();
+    if (fallbackServices.isEmpty()) {
+      return registry.getServices();
+    } else {
+      List<ServerServiceDefinition> registryServices = registry.getServices();
+      int servicesCount = registryServices.size() + fallbackServices.size();
+      List<ServerServiceDefinition> services =
+          new ArrayList<>(servicesCount);
+      services.addAll(registryServices);
+      services.addAll(fallbackServices);
+      return Collections.unmodifiableList(services);
+    }
+  }
+
+  @Override
+  public List<ServerServiceDefinition> getImmutableServices() {
+    return registry.getServices();
+  }
+
+  @Override
+  public List<ServerServiceDefinition> getMutableServices() {
+    return Collections.unmodifiableList(fallbackRegistry.getServices());
+  }
+
+  /**
+   * Initiates an orderly shutdown in which preexisting calls continue but new calls are rejected.
+   */
+  @Override
+  public ServerImpl shutdown() {
+    boolean shutdownTransportServer;
+    synchronized (lock) {
+      if (shutdown) {
+        return this;
+      }
+      shutdown = true;
+      shutdownTransportServer = started;
+      if (!shutdownTransportServer) {
+        transportServerTerminated = true;
+        checkForTermination();
+      }
+    }
+    if (shutdownTransportServer) {
+      transportServer.shutdown();
+    }
+    return this;
+  }
+
+  @Override
+  public ServerImpl shutdownNow() {
+    shutdown();
+    Collection<ServerTransport> transportsCopy;
+    Status nowStatus = Status.UNAVAILABLE.withDescription("Server shutdownNow invoked");
+    boolean savedServerShutdownCallbackInvoked;
+    synchronized (lock) {
+      // Short-circuiting not strictly necessary, but prevents transports from needing to handle
+      // multiple shutdownNow invocations if shutdownNow is called multiple times.
+      if (shutdownNowStatus != null) {
+        return this;
+      }
+      shutdownNowStatus = nowStatus;
+      transportsCopy = new ArrayList<>(transports);
+      savedServerShutdownCallbackInvoked = serverShutdownCallbackInvoked;
+    }
+    // Short-circuiting not strictly necessary, but prevents transports from needing to handle
+    // multiple shutdownNow invocations, between here and the serverShutdown callback.
+    if (savedServerShutdownCallbackInvoked) {
+      // Have to call shutdownNow, because serverShutdown callback only called shutdown, not
+      // shutdownNow
+      for (ServerTransport transport : transportsCopy) {
+        transport.shutdownNow(nowStatus);
+      }
+    }
+    return this;
+  }
+
+  @Override
+  public boolean isShutdown() {
+    synchronized (lock) {
+      return shutdown;
+    }
+  }
+
+  @Override
+  public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+    synchronized (lock) {
+      long timeoutNanos = unit.toNanos(timeout);
+      long endTimeNanos = System.nanoTime() + timeoutNanos;
+      while (!terminated && (timeoutNanos = endTimeNanos - System.nanoTime()) > 0) {
+        NANOSECONDS.timedWait(lock, timeoutNanos);
+      }
+      return terminated;
+    }
+  }
+
+  @Override
+  public void awaitTermination() throws InterruptedException {
+    synchronized (lock) {
+      while (!terminated) {
+        lock.wait();
+      }
+    }
+  }
+
+  @Override
+  public boolean isTerminated() {
+    synchronized (lock) {
+      return terminated;
+    }
+  }
+
+  /**
+   * Remove transport service from accounting collection and notify of complete shutdown if
+   * necessary.
+   *
+   * @param transport service to remove
+   */
+  private void transportClosed(ServerTransport transport) {
+    synchronized (lock) {
+      if (!transports.remove(transport)) {
+        throw new AssertionError("Transport already removed");
+      }
+      channelz.removeServerSocket(ServerImpl.this, transport);
+      checkForTermination();
+    }
+  }
+
+  /** Notify of complete shutdown if necessary. */
+  private void checkForTermination() {
+    synchronized (lock) {
+      if (shutdown && transports.isEmpty() && transportServerTerminated) {
+        if (terminated) {
+          throw new AssertionError("Server already terminated");
+        }
+        terminated = true;
+        channelz.removeServer(this);
+        if (executor != null) {
+          executor = executorPool.returnObject(executor);
+        }
+        // TODO(carl-mastrangelo): move this outside the synchronized block.
+        lock.notifyAll();
+      }
+    }
+  }
+
+  private final class ServerListenerImpl implements ServerListener {
+    @Override
+    public ServerTransportListener transportCreated(ServerTransport transport) {
+      synchronized (lock) {
+        transports.add(transport);
+      }
+      ServerTransportListenerImpl stli = new ServerTransportListenerImpl(transport);
+      stli.init();
+      return stli;
+    }
+
+    @Override
+    public void serverShutdown() {
+      ArrayList<ServerTransport> copiedTransports;
+      Status shutdownNowStatusCopy;
+      synchronized (lock) {
+        // transports collection can be modified during shutdown(), even if we hold the lock, due
+        // to reentrancy.
+        copiedTransports = new ArrayList<>(transports);
+        shutdownNowStatusCopy = shutdownNowStatus;
+        serverShutdownCallbackInvoked = true;
+      }
+      for (ServerTransport transport : copiedTransports) {
+        if (shutdownNowStatusCopy == null) {
+          transport.shutdown();
+        } else {
+          transport.shutdownNow(shutdownNowStatusCopy);
+        }
+      }
+      synchronized (lock) {
+        transportServerTerminated = true;
+        checkForTermination();
+      }
+    }
+  }
+
+  private final class ServerTransportListenerImpl implements ServerTransportListener {
+    private final ServerTransport transport;
+    private Future<?> handshakeTimeoutFuture;
+    private Attributes attributes;
+
+    ServerTransportListenerImpl(ServerTransport transport) {
+      this.transport = transport;
+    }
+
+    public void init() {
+      class TransportShutdownNow implements Runnable {
+        @Override public void run() {
+          transport.shutdownNow(Status.CANCELLED.withDescription("Handshake timeout exceeded"));
+        }
+      }
+
+      if (handshakeTimeoutMillis != Long.MAX_VALUE) {
+        handshakeTimeoutFuture = transport.getScheduledExecutorService()
+            .schedule(new TransportShutdownNow(), handshakeTimeoutMillis, TimeUnit.MILLISECONDS);
+      } else {
+        // Noop, to avoid triggering Thread creation in InProcessServer
+        handshakeTimeoutFuture = new FutureTask<Void>(new Runnable() {
+          @Override public void run() {}
+        }, null);
+      }
+      channelz.addServerSocket(ServerImpl.this, transport);
+    }
+
+    @Override
+    public Attributes transportReady(Attributes attributes) {
+      handshakeTimeoutFuture.cancel(false);
+      handshakeTimeoutFuture = null;
+
+      for (ServerTransportFilter filter : transportFilters) {
+        attributes = Preconditions.checkNotNull(filter.transportReady(attributes),
+            "Filter %s returned null", filter);
+      }
+      this.attributes = attributes;
+      return attributes;
+    }
+
+    @Override
+    public void transportTerminated() {
+      if (handshakeTimeoutFuture != null) {
+        handshakeTimeoutFuture.cancel(false);
+        handshakeTimeoutFuture = null;
+      }
+      for (ServerTransportFilter filter : transportFilters) {
+        filter.transportTerminated(attributes);
+      }
+      transportClosed(transport);
+    }
+
+    @Override
+    public void streamCreated(
+        final ServerStream stream, final String methodName, final Metadata headers) {
+      if (headers.containsKey(MESSAGE_ENCODING_KEY)) {
+        String encoding = headers.get(MESSAGE_ENCODING_KEY);
+        Decompressor decompressor = decompressorRegistry.lookupDecompressor(encoding);
+        if (decompressor == null) {
+          stream.close(
+              Status.UNIMPLEMENTED.withDescription(
+                  String.format("Can't find decompressor for %s", encoding)),
+              new Metadata());
+          return;
+        }
+        stream.setDecompressor(decompressor);
+      }
+
+      final StatsTraceContext statsTraceCtx = Preconditions.checkNotNull(
+          stream.statsTraceContext(), "statsTraceCtx not present from stream");
+
+      final Context.CancellableContext context = createContext(stream, headers, statsTraceCtx);
+      final Executor wrappedExecutor;
+      // This is a performance optimization that avoids the synchronization and queuing overhead
+      // that comes with SerializingExecutor.
+      if (executor == directExecutor()) {
+        wrappedExecutor = new SerializeReentrantCallsDirectExecutor();
+      } else {
+        wrappedExecutor = new SerializingExecutor(executor);
+      }
+
+      final JumpToApplicationThreadServerStreamListener jumpListener
+          = new JumpToApplicationThreadServerStreamListener(
+              wrappedExecutor, executor, stream, context);
+      stream.setListener(jumpListener);
+      // Run in wrappedExecutor so jumpListener.setListener() is called before any callbacks
+      // are delivered, including any errors. Callbacks can still be triggered, but they will be
+      // queued.
+
+      final class StreamCreated extends ContextRunnable {
+
+        StreamCreated() {
+          super(context);
+        }
+
+        @Override
+        public void runInContext() {
+          ServerStreamListener listener = NOOP_LISTENER;
+          try {
+            ServerMethodDefinition<?, ?> method = registry.lookupMethod(methodName);
+            if (method == null) {
+              method = fallbackRegistry.lookupMethod(methodName, stream.getAuthority());
+            }
+            if (method == null) {
+              Status status = Status.UNIMPLEMENTED.withDescription(
+                  "Method not found: " + methodName);
+              // TODO(zhangkun83): this error may be recorded by the tracer, and if it's kept in
+              // memory as a map whose key is the method name, this would allow a misbehaving
+              // client to blow up the server in-memory stats storage by sending large number of
+              // distinct unimplemented method
+              // names. (https://github.com/grpc/grpc-java/issues/2285)
+              stream.close(status, new Metadata());
+              context.cancel(null);
+              return;
+            }
+            listener = startCall(stream, methodName, method, headers, context, statsTraceCtx);
+          } catch (RuntimeException e) {
+            stream.close(Status.fromThrowable(e), new Metadata());
+            context.cancel(null);
+            throw e;
+          } catch (Error e) {
+            stream.close(Status.fromThrowable(e), new Metadata());
+            context.cancel(null);
+            throw e;
+          } finally {
+            jumpListener.setListener(listener);
+          }
+        }
+      }
+
+      wrappedExecutor.execute(new StreamCreated());
+    }
+
+    private Context.CancellableContext createContext(
+        final ServerStream stream, Metadata headers, StatsTraceContext statsTraceCtx) {
+      Long timeoutNanos = headers.get(TIMEOUT_KEY);
+
+      Context baseContext = statsTraceCtx.serverFilterContext(rootContext);
+
+      if (timeoutNanos == null) {
+        return baseContext.withCancellation();
+      }
+
+      Context.CancellableContext context = baseContext.withDeadlineAfter(
+          timeoutNanos, NANOSECONDS, transport.getScheduledExecutorService());
+      final class ServerStreamCancellationListener implements Context.CancellationListener {
+        @Override
+        public void cancelled(Context context) {
+          Status status = statusFromCancelled(context);
+          if (DEADLINE_EXCEEDED.getCode().equals(status.getCode())) {
+            // This should rarely get run, since the client will likely cancel the stream before
+            // the timeout is reached.
+            stream.cancel(status);
+          }
+        }
+      }
+
+      context.addListener(new ServerStreamCancellationListener(), directExecutor());
+
+      return context;
+    }
+
+    /** Never returns {@code null}. */
+    private <ReqT, RespT> ServerStreamListener startCall(ServerStream stream, String fullMethodName,
+        ServerMethodDefinition<ReqT, RespT> methodDef, Metadata headers,
+        Context.CancellableContext context, StatsTraceContext statsTraceCtx) {
+      // TODO(ejona86): should we update fullMethodName to have the canonical path of the method?
+      statsTraceCtx.serverCallStarted(
+          new ServerCallInfoImpl<ReqT, RespT>(
+              methodDef.getMethodDescriptor(), // notify with original method descriptor
+              stream.getAttributes(),
+              stream.getAuthority()));
+      ServerCallHandler<ReqT, RespT> handler = methodDef.getServerCallHandler();
+      for (ServerInterceptor interceptor : interceptors) {
+        handler = InternalServerInterceptors.interceptCallHandler(interceptor, handler);
+      }
+      ServerMethodDefinition<ReqT, RespT> interceptedDef = methodDef.withServerCallHandler(handler);
+      ServerMethodDefinition<?, ?> wMethodDef = binlog == null
+          ? interceptedDef : binlog.wrapMethodDefinition(interceptedDef);
+      return startWrappedCall(fullMethodName, wMethodDef, stream, headers, context);
+    }
+
+    private <WReqT, WRespT> ServerStreamListener startWrappedCall(
+        String fullMethodName,
+        ServerMethodDefinition<WReqT, WRespT> methodDef,
+        ServerStream stream,
+        Metadata headers,
+        Context.CancellableContext context) {
+      ServerCallImpl<WReqT, WRespT> call = new ServerCallImpl<WReqT, WRespT>(
+          stream,
+          methodDef.getMethodDescriptor(),
+          headers,
+          context,
+          decompressorRegistry,
+          compressorRegistry,
+          serverCallTracer);
+
+      ServerCall.Listener<WReqT> listener =
+          methodDef.getServerCallHandler().startCall(call, headers);
+      if (listener == null) {
+        throw new NullPointerException(
+            "startCall() returned a null listener for method " + fullMethodName);
+      }
+      return call.newServerStreamListener(listener);
+    }
+  }
+
+  @Override
+  public InternalLogId getLogId() {
+    return logId;
+  }
+
+  @Override
+  public ListenableFuture<ServerStats> getStats() {
+    ServerStats.Builder builder
+        = new ServerStats.Builder()
+        .setListenSockets(transportServer.getListenSockets());
+    serverCallTracer.updateBuilder(builder);
+    SettableFuture<ServerStats> ret = SettableFuture.create();
+    ret.set(builder.build());
+    return ret;
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("logId", logId.getId())
+        .add("transportServer", transportServer)
+        .toString();
+  }
+
+  private static final class NoopListener implements ServerStreamListener {
+    @Override
+    public void messagesAvailable(MessageProducer producer) {
+      InputStream message;
+      while ((message = producer.next()) != null) {
+        try {
+          message.close();
+        } catch (IOException e) {
+          // Close any remaining messages
+          while ((message = producer.next()) != null) {
+            try {
+              message.close();
+            } catch (IOException ioException) {
+              // just log additional exceptions as we are already going to throw
+              log.log(Level.WARNING, "Exception closing stream", ioException);
+            }
+          }
+          throw new RuntimeException(e);
+        }
+      }
+    }
+
+    @Override
+    public void halfClosed() {}
+
+    @Override
+    public void closed(Status status) {}
+
+    @Override
+    public void onReady() {}
+  }
+
+  /**
+   * Dispatches callbacks onto an application-provided executor and correctly propagates
+   * exceptions.
+   */
+  @VisibleForTesting
+  static final class JumpToApplicationThreadServerStreamListener implements ServerStreamListener {
+    private final Executor callExecutor;
+    private final Executor cancelExecutor;
+    private final Context.CancellableContext context;
+    private final ServerStream stream;
+    // Only accessed from callExecutor.
+    private ServerStreamListener listener;
+
+    public JumpToApplicationThreadServerStreamListener(Executor executor,
+        Executor cancelExecutor, ServerStream stream, Context.CancellableContext context) {
+      this.callExecutor = executor;
+      this.cancelExecutor = cancelExecutor;
+      this.stream = stream;
+      this.context = context;
+    }
+
+    /**
+     * This call MUST be serialized on callExecutor to avoid races.
+     */
+    private ServerStreamListener getListener() {
+      if (listener == null) {
+        throw new IllegalStateException("listener unset");
+      }
+      return listener;
+    }
+
+    @VisibleForTesting
+    void setListener(ServerStreamListener listener) {
+      Preconditions.checkNotNull(listener, "listener must not be null");
+      Preconditions.checkState(this.listener == null, "Listener already set");
+      this.listener = listener;
+    }
+
+    /**
+     * Like {@link ServerCall#close(Status, Metadata)}, but thread-safe for internal use.
+     */
+    private void internalClose() {
+      // TODO(ejona86): this is not thread-safe :)
+      stream.close(Status.UNKNOWN, new Metadata());
+    }
+
+    @Override
+    public void messagesAvailable(final MessageProducer producer) {
+
+      final class MessagesAvailable extends ContextRunnable {
+
+        MessagesAvailable() {
+          super(context);
+        }
+
+        @Override
+        public void runInContext() {
+          try {
+            getListener().messagesAvailable(producer);
+          } catch (RuntimeException e) {
+            internalClose();
+            throw e;
+          } catch (Error e) {
+            internalClose();
+            throw e;
+          }
+        }
+      }
+
+      callExecutor.execute(new MessagesAvailable());
+    }
+
+    @Override
+    public void halfClosed() {
+      final class HalfClosed extends ContextRunnable {
+        HalfClosed() {
+          super(context);
+        }
+
+        @Override
+        public void runInContext() {
+          try {
+            getListener().halfClosed();
+          } catch (RuntimeException e) {
+            internalClose();
+            throw e;
+          } catch (Error e) {
+            internalClose();
+            throw e;
+          }
+        }
+      }
+
+      callExecutor.execute(new HalfClosed());
+    }
+
+    @Override
+    public void closed(final Status status) {
+      // For cancellations, promptly inform any users of the context that their work should be
+      // aborted. Otherwise, we can wait until pending work is done.
+      if (!status.isOk()) {
+        // The callExecutor might be busy doing user work. To avoid waiting, use an executor that
+        // is not serializing.
+        cancelExecutor.execute(new ContextCloser(context, status.getCause()));
+      }
+
+      final class Closed extends ContextRunnable {
+        Closed() {
+          super(context);
+        }
+
+        @Override
+        public void runInContext() {
+          getListener().closed(status);
+        }
+      }
+
+      callExecutor.execute(new Closed());
+    }
+
+    @Override
+    public void onReady() {
+      final class OnReady extends ContextRunnable {
+        OnReady() {
+          super(context);
+        }
+
+        @Override
+        public void runInContext() {
+          try {
+            getListener().onReady();
+          } catch (RuntimeException e) {
+            internalClose();
+            throw e;
+          } catch (Error e) {
+            internalClose();
+            throw e;
+          }
+        }
+      }
+
+      callExecutor.execute(new OnReady());
+    }
+  }
+
+  @VisibleForTesting
+  static final class ContextCloser implements Runnable {
+    private final Context.CancellableContext context;
+    private final Throwable cause;
+
+    ContextCloser(Context.CancellableContext context, Throwable cause) {
+      this.context = context;
+      this.cause = cause;
+    }
+
+    @Override
+    public void run() {
+      context.cancel(cause);
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ServerListener.java b/core/src/main/java/io/grpc/internal/ServerListener.java
new file mode 100644
index 0000000..abe296f
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ServerListener.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+/**
+ * A listener to a server for transport creation events. The listener need not be thread-safe, so
+ * notifications must be properly synchronized externally.
+ */
+public interface ServerListener {
+
+  /**
+   * Called upon the establishment of a new client connection.
+   *
+   * @param transport the new transport to be observed.
+   * @return a listener for stream creation events on the transport.
+   */
+  ServerTransportListener transportCreated(ServerTransport transport);
+
+  /**
+   * The server is shutting down. No new transports will be processed, but existing transports may
+   * continue. Shutdown is only caused by a call to {@link InternalServer#shutdown()}. All
+   * resources have been released.
+   */
+  void serverShutdown();
+}
diff --git a/core/src/main/java/io/grpc/internal/ServerStream.java b/core/src/main/java/io/grpc/internal/ServerStream.java
new file mode 100644
index 0000000..04b7853
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ServerStream.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Attributes;
+import io.grpc.Decompressor;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import javax.annotation.Nullable;
+
+/**
+ * Extension of {@link Stream} to support server-side termination semantics.
+ *
+ * <p>An implementation doesn't need to be thread-safe. All methods are expected to execute quickly.
+ */
+public interface ServerStream extends Stream {
+
+  /**
+   * Writes custom metadata as headers on the response stream sent to the client. This method may
+   * only be called once and cannot be called after calls to {@link Stream#writeMessage}
+   * or {@link #close}.
+   *
+   * @param headers to send to client.
+   */
+  void writeHeaders(Metadata headers);
+
+  /**
+   * Closes the stream for both reading and writing. A status code of
+   * {@link io.grpc.Status.Code#OK} implies normal termination of the
+   * stream. Any other value implies abnormal termination.
+   *
+   * <p>Attempts to read from or write to the stream after closing
+   * should be ignored by implementations, and should not throw
+   * exceptions.
+   *
+   * @param status details of the closure
+   * @param trailers an additional block of metadata to pass to the client on stream closure.
+   */
+  void close(Status status, Metadata trailers);
+
+
+  /**
+   * Tears down the stream, typically in the event of a timeout. This method may be called multiple
+   * times and from any thread.
+   */
+  void cancel(Status status);
+
+  /**
+   * Sets the decompressor on the deframer. If the transport does not support compression, this may
+   * do nothing.
+   *
+   * @param decompressor the decompressor to use.
+   */
+  void setDecompressor(Decompressor decompressor);
+
+  /**
+   * Attributes describing stream.  This is inherited from the transport attributes, and used
+   * as the basis of {@link io.grpc.ServerCall#getAttributes}.
+   *
+   * @return Attributes container
+   */
+  Attributes getAttributes();
+
+  /**
+   * Gets the authority this stream is addressed to.
+   * @return the authority string. {@code null} if not available.
+   */
+  @Nullable
+  String getAuthority();
+
+  /**
+   * Sets the server stream listener.
+   */
+  void setListener(ServerStreamListener serverStreamListener);
+
+  /**
+   * The context for recording stats and traces for this stream.
+   */
+  StatsTraceContext statsTraceContext();
+}
diff --git a/core/src/main/java/io/grpc/internal/ServerStreamListener.java b/core/src/main/java/io/grpc/internal/ServerStreamListener.java
new file mode 100644
index 0000000..e55217a
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ServerStreamListener.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Status;
+
+/** An observer of server-side stream events. */
+public interface ServerStreamListener extends StreamListener {
+  /**
+   * Called when the remote side of the transport gracefully closed, indicating the client had no
+   * more data to send. No further messages will be received on the stream.
+   *
+   * <p>This method should return quickly, as the same thread may be used to process other streams.
+   */
+  void halfClosed();
+
+  /**
+   * Called when the stream is fully closed. A status code of {@link
+   * io.grpc.Status.Code#OK} implies normal termination of the stream.
+   * Any other value implies abnormal termination. Since clients cannot send status, the passed
+   * status is always library-generated and only is concerned with transport-level stream shutdown
+   * (the call itself may have had a failing status, but if the stream terminated cleanly with the
+   * status appearing to have been sent, then the passed status here would be {@code OK}). This is
+   * guaranteed to always be the final call on a listener. No further callbacks will be issued.
+   *
+   * <p>This method should return quickly, as the same thread may be used to process other streams.
+   *
+   * @param status details about the remote closure
+   */
+  void closed(Status status);
+}
diff --git a/core/src/main/java/io/grpc/internal/ServerTransport.java b/core/src/main/java/io/grpc/internal/ServerTransport.java
new file mode 100644
index 0000000..1bda2f2
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ServerTransport.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.Status;
+import java.util.concurrent.ScheduledExecutorService;
+
+/** An inbound connection. */
+public interface ServerTransport extends InternalInstrumented<SocketStats> {
+  /**
+   * Initiates an orderly shutdown of the transport. Existing streams continue, but new streams will
+   * eventually begin failing. New streams "eventually" begin failing because shutdown may need to
+   * be processed on a separate thread. May only be called once.
+   */
+  void shutdown();
+
+  /**
+   * Initiates a forceful shutdown in which preexisting and new calls are closed. Existing calls
+   * should be closed with the provided {@code reason}.
+   */
+  void shutdownNow(Status reason);
+
+  /**
+   * Returns an executor for scheduling provided by the transport. The service should be configured
+   * to allow cancelled scheduled runnables to be GCed.
+   *
+   * <p>The executor may not be used after the transport terminates. The caller should ensure any
+   * outstanding tasks are cancelled when the transport terminates.
+   */
+  ScheduledExecutorService getScheduledExecutorService();
+}
diff --git a/core/src/main/java/io/grpc/internal/ServerTransportListener.java b/core/src/main/java/io/grpc/internal/ServerTransportListener.java
new file mode 100644
index 0000000..740236e
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ServerTransportListener.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Attributes;
+import io.grpc.Metadata;
+
+/**
+ * A observer of a server-side transport for stream creation events. Notifications must occur from
+ * the transport thread.
+ */
+public interface ServerTransportListener {
+  /**
+   * Called when a new stream was created by the remote client.
+   *
+   * @param stream the newly created stream.
+   * @param method the fully qualified method name being called on the server.
+   * @param headers containing metadata for the call.
+   */
+  void streamCreated(ServerStream stream, String method, Metadata headers);
+
+  /**
+   * The transport has finished all handshakes and is ready to process streams.
+   *
+   * @param attributes transport attributes
+   *
+   * @return the effective transport attributes that is used as the basis of call attributes
+   */
+  Attributes transportReady(Attributes attributes);
+
+  /**
+   * The transport completed shutting down. All resources have been released.
+   */
+  void transportTerminated();
+}
diff --git a/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java b/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java
new file mode 100644
index 0000000..b3616aa
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ServiceConfigInterceptor.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Verify.verify;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.Deadline;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status.Code;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+/**
+ * Modifies RPCs in in conformance with a Service Config.
+ */
+final class ServiceConfigInterceptor implements ClientInterceptor {
+
+  private static final Logger logger = Logger.getLogger(ServiceConfigInterceptor.class.getName());
+
+  // Map from method name to MethodInfo
+  @VisibleForTesting
+  final AtomicReference<Map<String, MethodInfo>> serviceMethodMap
+      = new AtomicReference<Map<String, MethodInfo>>();
+  @VisibleForTesting
+  final AtomicReference<Map<String, MethodInfo>> serviceMap
+      = new AtomicReference<Map<String, MethodInfo>>();
+
+  private final boolean retryEnabled;
+  private final int maxRetryAttemptsLimit;
+  private final int maxHedgedAttemptsLimit;
+
+  // Setting this to true and observing this equal to true are run in different threads.
+  private volatile boolean nameResolveComplete;
+
+  ServiceConfigInterceptor(
+      boolean retryEnabled, int maxRetryAttemptsLimit, int maxHedgedAttemptsLimit) {
+    this.retryEnabled = retryEnabled;
+    this.maxRetryAttemptsLimit = maxRetryAttemptsLimit;
+    this.maxHedgedAttemptsLimit = maxHedgedAttemptsLimit;
+  }
+
+  void handleUpdate(@Nonnull Map<String, Object> serviceConfig) {
+    Map<String, MethodInfo> newServiceMethodConfigs = new HashMap<String, MethodInfo>();
+    Map<String, MethodInfo> newServiceConfigs = new HashMap<String, MethodInfo>();
+
+    // Try and do as much validation here before we swap out the existing configuration.  In case
+    // the input is invalid, we don't want to lose the existing configuration.
+
+    List<Map<String, Object>> methodConfigs =
+        ServiceConfigUtil.getMethodConfigFromServiceConfig(serviceConfig);
+    if (methodConfigs == null) {
+      logger.log(Level.FINE, "No method configs found, skipping");
+      nameResolveComplete = true;
+      return;
+    }
+
+    for (Map<String, Object> methodConfig : methodConfigs) {
+      MethodInfo info = new MethodInfo(
+          methodConfig, retryEnabled, maxRetryAttemptsLimit, maxHedgedAttemptsLimit);
+
+      List<Map<String, Object>> nameList =
+          ServiceConfigUtil.getNameListFromMethodConfig(methodConfig);
+
+      checkArgument(
+          nameList != null && !nameList.isEmpty(), "no names in method config %s", methodConfig);
+      for (Map<String, Object> name : nameList) {
+        String serviceName = ServiceConfigUtil.getServiceFromName(name);
+        checkArgument(!Strings.isNullOrEmpty(serviceName), "missing service name");
+        String methodName = ServiceConfigUtil.getMethodFromName(name);
+        if (Strings.isNullOrEmpty(methodName)) {
+          // Service scoped config
+          checkArgument(
+              !newServiceConfigs.containsKey(serviceName), "Duplicate service %s", serviceName);
+          newServiceConfigs.put(serviceName, info);
+        } else {
+          // Method scoped config
+          String fullMethodName = MethodDescriptor.generateFullMethodName(serviceName, methodName);
+          checkArgument(
+              !newServiceMethodConfigs.containsKey(fullMethodName),
+              "Duplicate method name %s",
+              fullMethodName);
+          newServiceMethodConfigs.put(fullMethodName, info);
+        }
+      }
+    }
+
+    // Okay, service config is good, swap it.
+    serviceMethodMap.set(Collections.unmodifiableMap(newServiceMethodConfigs));
+    serviceMap.set(Collections.unmodifiableMap(newServiceConfigs));
+    nameResolveComplete = true;
+  }
+
+  /**
+   * Equivalent of MethodConfig from a ServiceConfig with restrictions from Channel setting.
+   */
+  static final class MethodInfo {
+    final Long timeoutNanos;
+    final Boolean waitForReady;
+    final Integer maxInboundMessageSize;
+    final Integer maxOutboundMessageSize;
+    final RetryPolicy retryPolicy;
+    final HedgingPolicy hedgingPolicy;
+
+    /**
+     * Constructor.
+     *
+     * @param retryEnabled when false, the argument maxRetryAttemptsLimit will have no effect.
+     */
+    MethodInfo(
+        Map<String, Object> methodConfig, boolean retryEnabled, int maxRetryAttemptsLimit,
+        int maxHedgedAttemptsLimit) {
+      timeoutNanos = ServiceConfigUtil.getTimeoutFromMethodConfig(methodConfig);
+      waitForReady = ServiceConfigUtil.getWaitForReadyFromMethodConfig(methodConfig);
+      maxInboundMessageSize =
+          ServiceConfigUtil.getMaxResponseMessageBytesFromMethodConfig(methodConfig);
+      if (maxInboundMessageSize != null) {
+        checkArgument(
+            maxInboundMessageSize >= 0,
+            "maxInboundMessageSize %s exceeds bounds", maxInboundMessageSize);
+      }
+      maxOutboundMessageSize =
+          ServiceConfigUtil.getMaxRequestMessageBytesFromMethodConfig(methodConfig);
+      if (maxOutboundMessageSize != null) {
+        checkArgument(
+            maxOutboundMessageSize >= 0,
+            "maxOutboundMessageSize %s exceeds bounds", maxOutboundMessageSize);
+      }
+
+      Map<String, Object> retryPolicyMap =
+          retryEnabled ? ServiceConfigUtil.getRetryPolicyFromMethodConfig(methodConfig) : null;
+      retryPolicy = retryPolicyMap == null
+          ? RetryPolicy.DEFAULT : retryPolicy(retryPolicyMap, maxRetryAttemptsLimit);
+
+      Map<String, Object> hedgingPolicyMap =
+          retryEnabled ? ServiceConfigUtil.getHedgingPolicyFromMethodConfig(methodConfig) : null;
+      hedgingPolicy = hedgingPolicyMap == null
+          ? HedgingPolicy.DEFAULT : hedgingPolicy(hedgingPolicyMap, maxHedgedAttemptsLimit);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(
+          timeoutNanos, waitForReady, maxInboundMessageSize, maxOutboundMessageSize, retryPolicy);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (!(other instanceof MethodInfo)) {
+        return false;
+      }
+      MethodInfo that = (MethodInfo) other;
+      return Objects.equal(this.timeoutNanos, that.timeoutNanos)
+          && Objects.equal(this.waitForReady, that.waitForReady)
+          && Objects.equal(this.maxInboundMessageSize, that.maxInboundMessageSize)
+          && Objects.equal(this.maxOutboundMessageSize, that.maxOutboundMessageSize)
+          && Objects.equal(this.retryPolicy, that.retryPolicy);
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("timeoutNanos", timeoutNanos)
+          .add("waitForReady", waitForReady)
+          .add("maxInboundMessageSize", maxInboundMessageSize)
+          .add("maxOutboundMessageSize", maxOutboundMessageSize)
+          .add("retryPolicy", retryPolicy)
+          .toString();
+    }
+
+    @SuppressWarnings("BetaApi") // Verify is stabilized since Guava v24.0
+    private static RetryPolicy retryPolicy(Map<String, Object> retryPolicy, int maxAttemptsLimit) {
+      int maxAttempts = checkNotNull(
+          ServiceConfigUtil.getMaxAttemptsFromRetryPolicy(retryPolicy),
+          "maxAttempts cannot be empty");
+      checkArgument(maxAttempts >= 2, "maxAttempts must be greater than 1: %s", maxAttempts);
+      maxAttempts = Math.min(maxAttempts, maxAttemptsLimit);
+
+      long initialBackoffNanos = checkNotNull(
+          ServiceConfigUtil.getInitialBackoffNanosFromRetryPolicy(retryPolicy),
+          "initialBackoff cannot be empty");
+      checkArgument(
+          initialBackoffNanos > 0,
+          "initialBackoffNanos must be greater than 0: %s",
+          initialBackoffNanos);
+
+      long maxBackoffNanos = checkNotNull(
+          ServiceConfigUtil.getMaxBackoffNanosFromRetryPolicy(retryPolicy),
+          "maxBackoff cannot be empty");
+      checkArgument(
+          maxBackoffNanos > 0, "maxBackoff must be greater than 0: %s", maxBackoffNanos);
+
+      double backoffMultiplier = checkNotNull(
+          ServiceConfigUtil.getBackoffMultiplierFromRetryPolicy(retryPolicy),
+          "backoffMultiplier cannot be empty");
+      checkArgument(
+          backoffMultiplier > 0,
+          "backoffMultiplier must be greater than 0: %s",
+          backoffMultiplier);
+
+      List<String> rawCodes =
+          ServiceConfigUtil.getRetryableStatusCodesFromRetryPolicy(retryPolicy);
+      checkNotNull(rawCodes, "rawCodes must be present");
+      checkArgument(!rawCodes.isEmpty(), "rawCodes can't be empty");
+      EnumSet<Code> codes = EnumSet.noneOf(Code.class);
+      // service config doesn't say if duplicates are allowed, so just accept them.
+      for (String rawCode : rawCodes) {
+        verify(!"OK".equals(rawCode), "rawCode can not be \"OK\"");
+        codes.add(Code.valueOf(rawCode));
+      }
+      Set<Code> retryableStatusCodes = Collections.unmodifiableSet(codes);
+
+      return new RetryPolicy(
+          maxAttempts, initialBackoffNanos, maxBackoffNanos, backoffMultiplier,
+          retryableStatusCodes);
+    }
+  }
+
+  @SuppressWarnings("BetaApi") // Verify is stabilized since Guava v24.0
+  private static HedgingPolicy hedgingPolicy(
+      Map<String, Object> hedgingPolicy, int maxAttemptsLimit) {
+    int maxAttempts = checkNotNull(
+        ServiceConfigUtil.getMaxAttemptsFromHedgingPolicy(hedgingPolicy),
+        "maxAttempts cannot be empty");
+    checkArgument(maxAttempts >= 2, "maxAttempts must be greater than 1: %s", maxAttempts);
+    maxAttempts = Math.min(maxAttempts, maxAttemptsLimit);
+
+    long hedgingDelayNanos = checkNotNull(
+        ServiceConfigUtil.getHedgingDelayNanosFromHedgingPolicy(hedgingPolicy),
+        "hedgingDelay cannot be empty");
+    checkArgument(
+        hedgingDelayNanos >= 0, "hedgingDelay must not be negative: %s", hedgingDelayNanos);
+
+    List<String> rawCodes =
+        ServiceConfigUtil.getNonFatalStatusCodesFromHedgingPolicy(hedgingPolicy);
+    checkNotNull(rawCodes, "rawCodes must be present");
+    checkArgument(!rawCodes.isEmpty(), "rawCodes can't be empty");
+    EnumSet<Code> codes = EnumSet.noneOf(Code.class);
+    // service config doesn't say if duplicates are allowed, so just accept them.
+    for (String rawCode : rawCodes) {
+      verify(!"OK".equals(rawCode), "rawCode can not be \"OK\"");
+      codes.add(Code.valueOf(rawCode));
+    }
+    Set<Code> nonFatalStatusCodes = Collections.unmodifiableSet(codes);
+
+    return new HedgingPolicy(maxAttempts, hedgingDelayNanos, nonFatalStatusCodes);
+  }
+
+  static final CallOptions.Key<RetryPolicy.Provider> RETRY_POLICY_KEY =
+      CallOptions.Key.create("internal-retry-policy");
+  static final CallOptions.Key<HedgingPolicy.Provider> HEDGING_POLICY_KEY =
+      CallOptions.Key.create("internal-hedging-policy");
+
+  @SuppressWarnings("BetaApi") // Verify is stabilized since Guava v24.0
+  @Override
+  public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+      final MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+    if (retryEnabled) {
+      if (nameResolveComplete) {
+        final RetryPolicy retryPolicy = getRetryPolicyFromConfig(method);
+        final class ImmediateRetryPolicyProvider implements RetryPolicy.Provider {
+          @Override
+          public RetryPolicy get() {
+            return retryPolicy;
+          }
+        }
+
+        final HedgingPolicy hedgingPolicy = getHedgingPolicyFromConfig(method);
+        final class ImmediateHedgingPolicyProvider implements HedgingPolicy.Provider {
+          @Override
+          public HedgingPolicy get() {
+            return hedgingPolicy;
+          }
+        }
+
+        verify(
+            retryPolicy.equals(RetryPolicy.DEFAULT) || hedgingPolicy.equals(HedgingPolicy.DEFAULT),
+            "Can not apply both retry and hedging policy for the method '%s'", method);
+
+        callOptions = callOptions
+            .withOption(RETRY_POLICY_KEY, new ImmediateRetryPolicyProvider())
+            .withOption(HEDGING_POLICY_KEY, new ImmediateHedgingPolicyProvider());
+      } else {
+        final class DelayedRetryPolicyProvider implements RetryPolicy.Provider {
+          /**
+           * Returns RetryPolicy.DEFAULT if name resolving is not complete at the moment the method
+           * is invoked, otherwise returns the RetryPolicy computed from service config.
+           *
+           * <p>Note that this method is used no more than once for each call.
+           */
+          @Override
+          public RetryPolicy get() {
+            if (!nameResolveComplete) {
+              return RetryPolicy.DEFAULT;
+            }
+            return getRetryPolicyFromConfig(method);
+          }
+        }
+
+        final class DelayedHedgingPolicyProvider implements HedgingPolicy.Provider {
+          /**
+           * Returns HedgingPolicy.DEFAULT if name resolving is not complete at the moment the
+           * method is invoked, otherwise returns the HedgingPolicy computed from service config.
+           *
+           * <p>Note that this method is used no more than once for each call.
+           */
+          @Override
+          public HedgingPolicy get() {
+            if (!nameResolveComplete) {
+              return HedgingPolicy.DEFAULT;
+            }
+            HedgingPolicy hedgingPolicy = getHedgingPolicyFromConfig(method);
+            verify(
+                hedgingPolicy.equals(HedgingPolicy.DEFAULT)
+                    || getRetryPolicyFromConfig(method).equals(RetryPolicy.DEFAULT),
+                "Can not apply both retry and hedging policy for the method '%s'", method);
+            return hedgingPolicy;
+          }
+        }
+
+        callOptions = callOptions
+            .withOption(RETRY_POLICY_KEY, new DelayedRetryPolicyProvider())
+            .withOption(HEDGING_POLICY_KEY, new DelayedHedgingPolicyProvider());
+      }
+    }
+
+    MethodInfo info = getMethodInfo(method);
+    if (info == null) {
+      return next.newCall(method, callOptions);
+    }
+
+    if (info.timeoutNanos != null) {
+      Deadline newDeadline = Deadline.after(info.timeoutNanos, TimeUnit.NANOSECONDS);
+      Deadline existingDeadline = callOptions.getDeadline();
+      // If the new deadline is sooner than the existing deadline, swap them.
+      if (existingDeadline == null || newDeadline.compareTo(existingDeadline) < 0) {
+        callOptions = callOptions.withDeadline(newDeadline);
+      }
+    }
+    if (info.waitForReady != null) {
+      callOptions =
+          info.waitForReady ? callOptions.withWaitForReady() : callOptions.withoutWaitForReady();
+    }
+    if (info.maxInboundMessageSize != null) {
+      Integer existingLimit = callOptions.getMaxInboundMessageSize();
+      if (existingLimit != null) {
+        callOptions = callOptions.withMaxInboundMessageSize(
+            Math.min(existingLimit, info.maxInboundMessageSize));
+      } else {
+        callOptions = callOptions.withMaxInboundMessageSize(info.maxInboundMessageSize);
+      }
+    }
+    if (info.maxOutboundMessageSize != null) {
+      Integer existingLimit = callOptions.getMaxOutboundMessageSize();
+      if (existingLimit != null) {
+        callOptions = callOptions.withMaxOutboundMessageSize(
+            Math.min(existingLimit, info.maxOutboundMessageSize));
+      } else {
+        callOptions = callOptions.withMaxOutboundMessageSize(info.maxOutboundMessageSize);
+      }
+    }
+
+    return next.newCall(method, callOptions);
+  }
+
+  @CheckForNull
+  private MethodInfo getMethodInfo(MethodDescriptor<?, ?> method) {
+    Map<String, MethodInfo> localServiceMethodMap = serviceMethodMap.get();
+    MethodInfo info = null;
+    if (localServiceMethodMap != null) {
+      info = localServiceMethodMap.get(method.getFullMethodName());
+    }
+    if (info == null) {
+      Map<String, MethodInfo> localServiceMap = serviceMap.get();
+      if (localServiceMap != null) {
+        info = localServiceMap.get(
+            MethodDescriptor.extractFullServiceName(method.getFullMethodName()));
+      }
+    }
+    return info;
+  }
+
+  @VisibleForTesting
+  RetryPolicy getRetryPolicyFromConfig(MethodDescriptor<?, ?> method) {
+    MethodInfo info = getMethodInfo(method);
+    return info == null ? RetryPolicy.DEFAULT : info.retryPolicy;
+  }
+
+  @VisibleForTesting
+  HedgingPolicy getHedgingPolicyFromConfig(MethodDescriptor<?, ?> method) {
+    MethodInfo info = getMethodInfo(method);
+    return info == null ? HedgingPolicy.DEFAULT : info.hedgingPolicy;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/ServiceConfigUtil.java b/core/src/main/java/io/grpc/internal/ServiceConfigUtil.java
new file mode 100644
index 0000000..5295327
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/ServiceConfigUtil.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.math.LongMath.checkedAdd;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.internal.RetriableStream.Throttle;
+import java.text.ParseException;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+/**
+ * Helper utility to work with service configs.
+ */
+@VisibleForTesting
+public final class ServiceConfigUtil {
+
+  private static final String SERVICE_CONFIG_METHOD_CONFIG_KEY = "methodConfig";
+  private static final String SERVICE_CONFIG_LOAD_BALANCING_POLICY_KEY = "loadBalancingPolicy";
+  private static final String SERVICE_CONFIG_STICKINESS_METADATA_KEY = "stickinessMetadataKey";
+  private static final String METHOD_CONFIG_NAME_KEY = "name";
+  private static final String METHOD_CONFIG_TIMEOUT_KEY = "timeout";
+  private static final String METHOD_CONFIG_WAIT_FOR_READY_KEY = "waitForReady";
+  private static final String METHOD_CONFIG_MAX_REQUEST_MESSAGE_BYTES_KEY =
+      "maxRequestMessageBytes";
+  private static final String METHOD_CONFIG_MAX_RESPONSE_MESSAGE_BYTES_KEY =
+      "maxResponseMessageBytes";
+  private static final String METHOD_CONFIG_RETRY_POLICY_KEY = "retryPolicy";
+  private static final String METHOD_CONFIG_HEDGING_POLICY_KEY = "hedgingPolicy";
+  private static final String NAME_SERVICE_KEY = "service";
+  private static final String NAME_METHOD_KEY = "method";
+  private static final String RETRY_POLICY_MAX_ATTEMPTS_KEY = "maxAttempts";
+  private static final String RETRY_POLICY_INITIAL_BACKOFF_KEY = "initialBackoff";
+  private static final String RETRY_POLICY_MAX_BACKOFF_KEY = "maxBackoff";
+  private static final String RETRY_POLICY_BACKOFF_MULTIPLIER_KEY = "backoffMultiplier";
+  private static final String RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY = "retryableStatusCodes";
+  private static final String HEDGING_POLICY_MAX_ATTEMPTS_KEY = "maxAttempts";
+  private static final String HEDGING_POLICY_HEDGING_DELAY_KEY = "hedgingDelay";
+  private static final String HEDGING_POLICY_NON_FATAL_STATUS_CODES_KEY = "nonFatalStatusCodes";
+
+  private static final long DURATION_SECONDS_MIN = -315576000000L;
+  private static final long DURATION_SECONDS_MAX = 315576000000L;
+
+  private ServiceConfigUtil() {}
+
+  @Nullable
+  static Throttle getThrottlePolicy(@Nullable Map<String, Object> serviceConfig) {
+    String retryThrottlingKey = "retryThrottling";
+    if (serviceConfig == null || !serviceConfig.containsKey(retryThrottlingKey)) {
+      return null;
+    }
+
+    /* schema as follows
+    {
+      "retryThrottling": {
+        // The number of tokens starts at maxTokens. The token_count will always be
+        // between 0 and maxTokens.
+        //
+        // This field is required and must be greater than zero.
+        "maxTokens": number,
+
+        // The amount of tokens to add on each successful RPC. Typically this will
+        // be some number between 0 and 1, e.g., 0.1.
+        //
+        // This field is required and must be greater than zero. Up to 3 decimal
+        // places are supported.
+        "tokenRatio": number
+      }
+    }
+    */
+
+    Map<String, Object> throttling = getObject(serviceConfig, retryThrottlingKey);
+
+    float maxTokens = getDouble(throttling, "maxTokens").floatValue();
+    float tokenRatio = getDouble(throttling, "tokenRatio").floatValue();
+    checkState(maxTokens > 0f, "maxToken should be greater than zero");
+    checkState(tokenRatio > 0f, "tokenRatio should be greater than zero");
+    return new Throttle(maxTokens, tokenRatio);
+  }
+
+  @Nullable
+  static Integer getMaxAttemptsFromRetryPolicy(Map<String, Object> retryPolicy) {
+    if (!retryPolicy.containsKey(RETRY_POLICY_MAX_ATTEMPTS_KEY)) {
+      return null;
+    }
+    return getDouble(retryPolicy, RETRY_POLICY_MAX_ATTEMPTS_KEY).intValue();
+  }
+
+  @Nullable
+  static Long getInitialBackoffNanosFromRetryPolicy(Map<String, Object> retryPolicy) {
+    if (!retryPolicy.containsKey(RETRY_POLICY_INITIAL_BACKOFF_KEY)) {
+      return null;
+    }
+    String rawInitialBackoff = getString(retryPolicy, RETRY_POLICY_INITIAL_BACKOFF_KEY);
+    try {
+      return parseDuration(rawInitialBackoff);
+    } catch (ParseException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Nullable
+  static Long getMaxBackoffNanosFromRetryPolicy(Map<String, Object> retryPolicy) {
+    if (!retryPolicy.containsKey(RETRY_POLICY_MAX_BACKOFF_KEY)) {
+      return null;
+    }
+    String rawMaxBackoff = getString(retryPolicy, RETRY_POLICY_MAX_BACKOFF_KEY);
+    try {
+      return parseDuration(rawMaxBackoff);
+    } catch (ParseException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Nullable
+  static Double getBackoffMultiplierFromRetryPolicy(Map<String, Object> retryPolicy) {
+    if (!retryPolicy.containsKey(RETRY_POLICY_BACKOFF_MULTIPLIER_KEY)) {
+      return null;
+    }
+    return getDouble(retryPolicy, RETRY_POLICY_BACKOFF_MULTIPLIER_KEY);
+  }
+
+  @Nullable
+  static List<String> getRetryableStatusCodesFromRetryPolicy(Map<String, Object> retryPolicy) {
+    if (!retryPolicy.containsKey(RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY)) {
+      return null;
+    }
+    return checkStringList(getList(retryPolicy, RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY));
+  }
+
+  @Nullable
+  static Integer getMaxAttemptsFromHedgingPolicy(Map<String, Object> hedgingPolicy) {
+    if (!hedgingPolicy.containsKey(HEDGING_POLICY_MAX_ATTEMPTS_KEY)) {
+      return null;
+    }
+    return getDouble(hedgingPolicy, RETRY_POLICY_MAX_ATTEMPTS_KEY).intValue();
+  }
+
+  @Nullable
+  static Long getHedgingDelayNanosFromHedgingPolicy(Map<String, Object> hedgingPolicy) {
+    if (!hedgingPolicy.containsKey(HEDGING_POLICY_HEDGING_DELAY_KEY)) {
+      return null;
+    }
+    String rawHedgingDelay = getString(hedgingPolicy, HEDGING_POLICY_HEDGING_DELAY_KEY);
+    try {
+      return parseDuration(rawHedgingDelay);
+    } catch (ParseException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Nullable
+  static List<String> getNonFatalStatusCodesFromHedgingPolicy(Map<String, Object> hedgingPolicy) {
+    if (!hedgingPolicy.containsKey(HEDGING_POLICY_NON_FATAL_STATUS_CODES_KEY)) {
+      return null;
+    }
+    return checkStringList(getList(hedgingPolicy, HEDGING_POLICY_NON_FATAL_STATUS_CODES_KEY));
+  }
+
+  @Nullable
+  static String getServiceFromName(Map<String, Object> name) {
+    if (!name.containsKey(NAME_SERVICE_KEY)) {
+      return null;
+    }
+    return getString(name, NAME_SERVICE_KEY);
+  }
+
+  @Nullable
+  static String getMethodFromName(Map<String, Object> name) {
+    if (!name.containsKey(NAME_METHOD_KEY)) {
+      return null;
+    }
+    return getString(name, NAME_METHOD_KEY);
+  }
+
+  @Nullable
+  static Map<String, Object> getRetryPolicyFromMethodConfig(Map<String, Object> methodConfig) {
+    if (!methodConfig.containsKey(METHOD_CONFIG_RETRY_POLICY_KEY)) {
+      return null;
+    }
+    return getObject(methodConfig, METHOD_CONFIG_RETRY_POLICY_KEY);
+  }
+
+  @Nullable
+  static Map<String, Object> getHedgingPolicyFromMethodConfig(Map<String, Object> methodConfig) {
+    if (!methodConfig.containsKey(METHOD_CONFIG_HEDGING_POLICY_KEY)) {
+      return null;
+    }
+    return getObject(methodConfig, METHOD_CONFIG_HEDGING_POLICY_KEY);
+  }
+
+  @Nullable
+  static List<Map<String, Object>> getNameListFromMethodConfig(Map<String, Object> methodConfig) {
+    if (!methodConfig.containsKey(METHOD_CONFIG_NAME_KEY)) {
+      return null;
+    }
+    return checkObjectList(getList(methodConfig, METHOD_CONFIG_NAME_KEY));
+  }
+
+  /**
+   * Returns the number of nanoseconds of timeout for the given method config.
+   *
+   * @return duration nanoseconds, or {@code null} if it isn't present.
+   */
+  @Nullable
+  static Long getTimeoutFromMethodConfig(Map<String, Object> methodConfig) {
+    if (!methodConfig.containsKey(METHOD_CONFIG_TIMEOUT_KEY)) {
+      return null;
+    }
+    String rawTimeout = getString(methodConfig, METHOD_CONFIG_TIMEOUT_KEY);
+    try {
+      return parseDuration(rawTimeout);
+    } catch (ParseException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Nullable
+  static Boolean getWaitForReadyFromMethodConfig(Map<String, Object> methodConfig) {
+    if (!methodConfig.containsKey(METHOD_CONFIG_WAIT_FOR_READY_KEY)) {
+      return null;
+    }
+    return getBoolean(methodConfig, METHOD_CONFIG_WAIT_FOR_READY_KEY);
+  }
+
+  @Nullable
+  static Integer getMaxRequestMessageBytesFromMethodConfig(Map<String, Object> methodConfig) {
+    if (!methodConfig.containsKey(METHOD_CONFIG_MAX_REQUEST_MESSAGE_BYTES_KEY)) {
+      return null;
+    }
+    return getDouble(methodConfig, METHOD_CONFIG_MAX_REQUEST_MESSAGE_BYTES_KEY).intValue();
+  }
+
+  @Nullable
+  static Integer getMaxResponseMessageBytesFromMethodConfig(Map<String, Object> methodConfig) {
+    if (!methodConfig.containsKey(METHOD_CONFIG_MAX_RESPONSE_MESSAGE_BYTES_KEY)) {
+      return null;
+    }
+    return getDouble(methodConfig, METHOD_CONFIG_MAX_RESPONSE_MESSAGE_BYTES_KEY).intValue();
+  }
+
+  @Nullable
+  static List<Map<String, Object>> getMethodConfigFromServiceConfig(
+      Map<String, Object> serviceConfig) {
+    if (!serviceConfig.containsKey(SERVICE_CONFIG_METHOD_CONFIG_KEY)) {
+      return null;
+    }
+    return checkObjectList(getList(serviceConfig, SERVICE_CONFIG_METHOD_CONFIG_KEY));
+  }
+
+  /**
+   * Extracts the load balancing policy from a service config, or {@code null}.
+   */
+  @Nullable
+  @VisibleForTesting
+  public static String getLoadBalancingPolicyFromServiceConfig(Map<String, Object> serviceConfig) {
+    if (!serviceConfig.containsKey(SERVICE_CONFIG_LOAD_BALANCING_POLICY_KEY)) {
+      return null;
+    }
+    return getString(serviceConfig, SERVICE_CONFIG_LOAD_BALANCING_POLICY_KEY);
+  }
+
+  /**
+   * Extracts the stickiness metadata key from a service config, or {@code null}.
+   */
+  @Nullable
+  public static String getStickinessMetadataKeyFromServiceConfig(
+      Map<String, Object> serviceConfig) {
+    if (!serviceConfig.containsKey(SERVICE_CONFIG_STICKINESS_METADATA_KEY)) {
+      return null;
+    }
+    return getString(serviceConfig, SERVICE_CONFIG_STICKINESS_METADATA_KEY);
+  }
+
+  /**
+   * Gets a list from an object for the given key.
+   */
+  @SuppressWarnings("unchecked")
+  static List<Object> getList(Map<String, Object> obj, String key) {
+    assert obj.containsKey(key);
+    Object value = checkNotNull(obj.get(key), "no such key %s", key);
+    if (value instanceof List) {
+      return (List<Object>) value;
+    }
+    throw new ClassCastException(
+        String.format("value %s for key %s in %s is not List", value, key, obj));
+  }
+
+  /**
+   * Gets an object from an object for the given key.
+   */
+  @SuppressWarnings("unchecked")
+  static Map<String, Object> getObject(Map<String, Object> obj, String key) {
+    assert obj.containsKey(key);
+    Object value = checkNotNull(obj.get(key), "no such key %s", key);
+    if (value instanceof Map) {
+      return (Map<String, Object>) value;
+    }
+    throw new ClassCastException(
+        String.format("value %s for key %s in %s is not object", value, key, obj));
+  }
+
+  /**
+   * Gets a double from an object for the given key.
+   */
+  @SuppressWarnings("unchecked")
+  static Double getDouble(Map<String, Object> obj, String key) {
+    assert obj.containsKey(key);
+    Object value = checkNotNull(obj.get(key), "no such key %s", key);
+    if (value instanceof Double) {
+      return (Double) value;
+    }
+    throw new ClassCastException(
+        String.format("value %s for key %s in %s is not Double", value, key, obj));
+  }
+
+  /**
+   * Gets a string from an object for the given key.
+   */
+  @SuppressWarnings("unchecked")
+  static String getString(Map<String, Object> obj, String key) {
+    assert obj.containsKey(key);
+    Object value = checkNotNull(obj.get(key), "no such key %s", key);
+    if (value instanceof String) {
+      return (String) value;
+    }
+    throw new ClassCastException(
+        String.format("value %s for key %s in %s is not String", value, key, obj));
+  }
+
+  /**
+   * Gets a string from an object for the given index.
+   */
+  @SuppressWarnings("unchecked")
+  static String getString(List<Object> list, int i) {
+    assert i >= 0 && i < list.size();
+    Object value = checkNotNull(list.get(i), "idx %s in %s is null", i, list);
+    if (value instanceof String) {
+      return (String) value;
+    }
+    throw new ClassCastException(
+        String.format("value %s for idx %d in %s is not String", value, i, list));
+  }
+
+  /**
+   * Gets a boolean from an object for the given key.
+   */
+  static Boolean getBoolean(Map<String, Object> obj, String key) {
+    assert obj.containsKey(key);
+    Object value = checkNotNull(obj.get(key), "no such key %s", key);
+    if (value instanceof Boolean) {
+      return (Boolean) value;
+    }
+    throw new ClassCastException(
+        String.format("value %s for key %s in %s is not Boolean", value, key, obj));
+  }
+
+  @SuppressWarnings("unchecked")
+  private static List<Map<String, Object>> checkObjectList(List<Object> rawList) {
+    for (int i = 0; i < rawList.size(); i++) {
+      if (!(rawList.get(i) instanceof Map)) {
+        throw new ClassCastException(
+            String.format("value %s for idx %d in %s is not object", rawList.get(i), i, rawList));
+      }
+    }
+    return (List<Map<String, Object>>) (List<?>) rawList;
+  }
+
+  @SuppressWarnings("unchecked")
+  static List<String> checkStringList(List<Object> rawList) {
+    for (int i = 0; i < rawList.size(); i++) {
+      if (!(rawList.get(i) instanceof String)) {
+        throw new ClassCastException(
+            String.format("value %s for idx %d in %s is not string", rawList.get(i), i, rawList));
+      }
+    }
+    return (List<String>) (List<?>) rawList;
+  }
+
+  /**
+   * Parse from a string to produce a duration.  Copy of
+   * {@link com.google.protobuf.util.Durations#parse}.
+   *
+   * @return A Duration parsed from the string.
+   * @throws ParseException if parsing fails.
+   */
+  private static long parseDuration(String value) throws ParseException {
+    // Must ended with "s".
+    if (value.isEmpty() || value.charAt(value.length() - 1) != 's') {
+      throw new ParseException("Invalid duration string: " + value, 0);
+    }
+    boolean negative = false;
+    if (value.charAt(0) == '-') {
+      negative = true;
+      value = value.substring(1);
+    }
+    String secondValue = value.substring(0, value.length() - 1);
+    String nanoValue = "";
+    int pointPosition = secondValue.indexOf('.');
+    if (pointPosition != -1) {
+      nanoValue = secondValue.substring(pointPosition + 1);
+      secondValue = secondValue.substring(0, pointPosition);
+    }
+    long seconds = Long.parseLong(secondValue);
+    int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue);
+    if (seconds < 0) {
+      throw new ParseException("Invalid duration string: " + value, 0);
+    }
+    if (negative) {
+      seconds = -seconds;
+      nanos = -nanos;
+    }
+    try {
+      return normalizedDuration(seconds, nanos);
+    } catch (IllegalArgumentException e) {
+      throw new ParseException("Duration value is out of range.", 0);
+    }
+  }
+
+  /**
+   * Copy of {@link com.google.protobuf.util.Timestamps#parseNanos}.
+   */
+  private static int parseNanos(String value) throws ParseException {
+    int result = 0;
+    for (int i = 0; i < 9; ++i) {
+      result = result * 10;
+      if (i < value.length()) {
+        if (value.charAt(i) < '0' || value.charAt(i) > '9') {
+          throw new ParseException("Invalid nanoseconds.", 0);
+        }
+        result += value.charAt(i) - '0';
+      }
+    }
+    return result;
+  }
+
+  private static final long NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1);
+
+  /**
+   * Copy of {@link com.google.protobuf.util.Durations#normalizedDuration}.
+   */
+  @SuppressWarnings("NarrowingCompoundAssignment")
+  private static long normalizedDuration(long seconds, int nanos) {
+    if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) {
+      seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND);
+      nanos %= NANOS_PER_SECOND;
+    }
+    if (seconds > 0 && nanos < 0) {
+      nanos += NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding)
+      seconds--; // no overflow since seconds is positive (and we're decrementing)
+    }
+    if (seconds < 0 && nanos > 0) {
+      nanos -= NANOS_PER_SECOND; // no overflow since nanos is positive (and we're subtracting)
+      seconds++; // no overflow since seconds is negative (and we're incrementing)
+    }
+    if (!durationIsValid(seconds, nanos)) {
+      throw new IllegalArgumentException(String.format(
+          "Duration is not valid. See proto definition for valid values. "
+              + "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]. "
+              + "Nanos (%s) must be in range [-999,999,999, +999,999,999]. "
+              + "Nanos must have the same sign as seconds", seconds, nanos));
+    }
+    return saturatedAdd(TimeUnit.SECONDS.toNanos(seconds), nanos);
+  }
+
+  /**
+   * Returns true if the given number of seconds and nanos is a valid {@code Duration}. The {@code
+   * seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The {@code nanos}
+   * value must be in the range [-999,999,999, +999,999,999].
+   *
+   * <p><b>Note:</b> Durations less than one second are represented with a 0 {@code seconds} field
+   * and a positive or negative {@code nanos} field. For durations of one second or more, a non-zero
+   * value for the {@code nanos} field must be of the same sign as the {@code seconds} field.
+   *
+   * <p>Copy of {@link com.google.protobuf.util.Duration#isValid}.</p>
+   */
+  private static boolean durationIsValid(long seconds, int nanos) {
+    if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) {
+      return false;
+    }
+    if (nanos < -999999999L || nanos >= NANOS_PER_SECOND) {
+      return false;
+    }
+    if (seconds < 0 || nanos < 0) {
+      if (seconds > 0 || nanos > 0) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns the sum of {@code a} and {@code b} unless it would overflow or underflow in which case
+   * {@code Long.MAX_VALUE} or {@code Long.MIN_VALUE} is returned, respectively.
+   *
+   * <p>Copy of {@link com.google.common.math.LongMath#saturatedAdd}.</p>
+   *
+   */
+  @SuppressWarnings("ShortCircuitBoolean")
+  private static long saturatedAdd(long a, long b) {
+    long naiveSum = a + b;
+    if ((a ^ b) < 0 | (a ^ naiveSum) >= 0) {
+      // If a and b have different signs or a has the same sign as the result then there was no
+      // overflow, return.
+      return naiveSum;
+    }
+    // we did over/under flow, if the sign is negative we should return MAX otherwise MIN
+    return Long.MAX_VALUE + ((naiveSum >>> (Long.SIZE - 1)) ^ 1);
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/SharedResourceHolder.java b/core/src/main/java/io/grpc/internal/SharedResourceHolder.java
new file mode 100644
index 0000000..3936814
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/SharedResourceHolder.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.Preconditions;
+import java.util.IdentityHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * A holder for shared resource singletons.
+ *
+ * <p>Components like client channels and servers need certain resources, e.g. a thread pool, to
+ * run. If the user has not provided such resources, these components will use a default one, which
+ * is shared as a static resource. This class holds these default resources and manages their
+ * life-cycles.
+ *
+ * <p>A resource is identified by the reference of a {@link Resource} object, which is typically a
+ * singleton, provided to the get() and release() methods. Each Resource object (not its class) maps
+ * to an object cached in the holder.
+ *
+ * <p>Resources are ref-counted and shut down after a delay when the ref-count reaches zero.
+ */
+@ThreadSafe
+public final class SharedResourceHolder {
+  static final long DESTROY_DELAY_SECONDS = 1;
+
+  // The sole holder instance.
+  private static final SharedResourceHolder holder = new SharedResourceHolder(
+      new ScheduledExecutorFactory() {
+        @Override
+        public ScheduledExecutorService createScheduledExecutor() {
+          return Executors.newSingleThreadScheduledExecutor(
+              GrpcUtil.getThreadFactory("grpc-shared-destroyer-%d", true));
+        }
+      });
+
+  private final IdentityHashMap<Resource<?>, Instance> instances =
+      new IdentityHashMap<Resource<?>, Instance>();
+
+  private final ScheduledExecutorFactory destroyerFactory;
+
+  private ScheduledExecutorService destroyer;
+
+  // Visible to tests that would need to create instances of the holder.
+  SharedResourceHolder(ScheduledExecutorFactory destroyerFactory) {
+    this.destroyerFactory = destroyerFactory;
+  }
+
+  /**
+   * Try to get an existing instance of the given resource. If an instance does not exist, create a
+   * new one with the given factory.
+   *
+   * @param resource the singleton object that identifies the requested static resource
+   */
+  public static <T> T get(Resource<T> resource) {
+    return holder.getInternal(resource);
+  }
+
+  /**
+   * Releases an instance of the given resource.
+   *
+   * <p>The instance must have been obtained from {@link #get(Resource)}. Otherwise will throw
+   * IllegalArgumentException.
+   *
+   * <p>Caller must not release a reference more than once. It's advisory that you clear the
+   * reference to the instance with the null returned by this method.
+   *
+   * @param resource the singleton Resource object that identifies the released static resource
+   * @param instance the released static resource
+   *
+   * @return a null which the caller can use to clear the reference to that instance.
+   */
+  public static <T> T release(final Resource<T> resource, final T instance) {
+    return holder.releaseInternal(resource, instance);
+  }
+
+  /**
+   * Visible to unit tests.
+   *
+   * @see #get(Resource)
+   */
+  @SuppressWarnings("unchecked")
+  synchronized <T> T getInternal(Resource<T> resource) {
+    Instance instance = instances.get(resource);
+    if (instance == null) {
+      instance = new Instance(resource.create());
+      instances.put(resource, instance);
+    }
+    if (instance.destroyTask != null) {
+      instance.destroyTask.cancel(false);
+      instance.destroyTask = null;
+    }
+    instance.refcount++;
+    return (T) instance.payload;
+  }
+
+  /**
+   * Visible to unit tests.
+   */
+  synchronized <T> T releaseInternal(final Resource<T> resource, final T instance) {
+    final Instance cached = instances.get(resource);
+    if (cached == null) {
+      throw new IllegalArgumentException("No cached instance found for " + resource);
+    }
+    Preconditions.checkArgument(instance == cached.payload, "Releasing the wrong instance");
+    Preconditions.checkState(cached.refcount > 0, "Refcount has already reached zero");
+    cached.refcount--;
+    if (cached.refcount == 0) {
+      if (GrpcUtil.IS_RESTRICTED_APPENGINE) {
+        // AppEngine must immediately release shared resources, particularly executors
+        // which could retain request-scoped threads which become zombies after the request
+        // completes.
+        resource.close(instance);
+        instances.remove(resource);
+      } else {
+        Preconditions.checkState(cached.destroyTask == null, "Destroy task already scheduled");
+        // Schedule a delayed task to destroy the resource.
+        if (destroyer == null) {
+          destroyer = destroyerFactory.createScheduledExecutor();
+        }
+        cached.destroyTask = destroyer.schedule(new LogExceptionRunnable(new Runnable() {
+          @Override
+          public void run() {
+            synchronized (SharedResourceHolder.this) {
+              // Refcount may have gone up since the task was scheduled. Re-check it.
+              if (cached.refcount == 0) {
+                resource.close(instance);
+                instances.remove(resource);
+                if (instances.isEmpty()) {
+                  destroyer.shutdown();
+                  destroyer = null;
+                }
+              }
+            }
+          }
+        }), DESTROY_DELAY_SECONDS, TimeUnit.SECONDS);
+      }
+    }
+    // Always returning null
+    return null;
+  }
+
+  /**
+   * Defines a resource, and the way to create and destroy instances of it.
+   */
+  public interface Resource<T> {
+    /**
+     * Create a new instance of the resource.
+     */
+    T create();
+
+    /**
+     * Destroy the given instance.
+     */
+    void close(T instance);
+  }
+
+  interface ScheduledExecutorFactory {
+    ScheduledExecutorService createScheduledExecutor();
+  }
+
+  private static class Instance {
+    final Object payload;
+    int refcount;
+    ScheduledFuture<?> destroyTask;
+
+    Instance(Object payload) {
+      this.payload = payload;
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/SharedResourcePool.java b/core/src/main/java/io/grpc/internal/SharedResourcePool.java
new file mode 100644
index 0000000..5789272
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/SharedResourcePool.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+/**
+ * An ObjectPool backed by a {@link SharedResourceHolder.Resource}.
+ */
+public final class SharedResourcePool<T> implements ObjectPool<T> {
+  private final SharedResourceHolder.Resource<T> resource;
+
+  private SharedResourcePool(SharedResourceHolder.Resource<T> resource) {
+    this.resource = resource;
+  }
+
+  public static <T> SharedResourcePool<T> forResource(SharedResourceHolder.Resource<T> resource) {
+    return new SharedResourcePool<T>(resource);
+  }
+
+  @Override
+  public T getObject() {
+    return SharedResourceHolder.get(resource);
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public T returnObject(Object object) {
+    SharedResourceHolder.release(resource, (T) object);
+    return null;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/StatsTraceContext.java b/core/src/main/java/io/grpc/internal/StatsTraceContext.java
new file mode 100644
index 0000000..52c2666
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/StatsTraceContext.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
+import io.grpc.Context;
+import io.grpc.Metadata;
+import io.grpc.ServerStreamTracer;
+import io.grpc.ServerStreamTracer.ServerCallInfo;
+import io.grpc.Status;
+import io.grpc.StreamTracer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * The stats and tracing information for a stream.
+ */
+@ThreadSafe
+public final class StatsTraceContext {
+  public static final StatsTraceContext NOOP = new StatsTraceContext(new StreamTracer[0]);
+
+  private final StreamTracer[] tracers;
+  private final AtomicBoolean closed = new AtomicBoolean(false);
+
+  /**
+   * Factory method for the client-side.
+   */
+  public static StatsTraceContext newClientContext(CallOptions callOptions, Metadata headers) {
+    List<ClientStreamTracer.Factory> factories = callOptions.getStreamTracerFactories();
+    if (factories.isEmpty()) {
+      return NOOP;
+    }
+    // This array will be iterated multiple times per RPC. Use primitive array instead of Collection
+    // so that for-each doesn't create an Iterator every time.
+    StreamTracer[] tracers = new StreamTracer[factories.size()];
+    for (int i = 0; i < tracers.length; i++) {
+      tracers[i] = factories.get(i).newClientStreamTracer(callOptions, headers);
+    }
+    return new StatsTraceContext(tracers);
+  }
+
+  /**
+   * Factory method for the server-side.
+   */
+  public static StatsTraceContext newServerContext(
+      List<ServerStreamTracer.Factory> factories, String fullMethodName, Metadata headers) {
+    if (factories.isEmpty()) {
+      return NOOP;
+    }
+    StreamTracer[] tracers = new StreamTracer[factories.size()];
+    for (int i = 0; i < tracers.length; i++) {
+      tracers[i] = factories.get(i).newServerStreamTracer(fullMethodName, headers);
+    }
+    return new StatsTraceContext(tracers);
+  }
+
+  @VisibleForTesting
+  StatsTraceContext(StreamTracer[] tracers) {
+    this.tracers = tracers;
+  }
+
+  /**
+   * Returns a copy of the tracer list.
+   */
+  @VisibleForTesting
+  public List<StreamTracer> getTracersForTest() {
+    return new ArrayList<>(Arrays.asList(tracers));
+  }
+
+  /**
+   * See {@link ClientStreamTracer#outboundHeaders}.  For client-side only.
+   *
+   * <p>Transport-specific, thus should be called by transport implementations.
+   */
+  public void clientOutboundHeaders() {
+    for (StreamTracer tracer : tracers) {
+      ((ClientStreamTracer) tracer).outboundHeaders();
+    }
+  }
+
+  /**
+   * See {@link ClientStreamTracer#inboundHeaders}.  For client-side only.
+   *
+   * <p>Called from abstract stream implementations.
+   */
+  public void clientInboundHeaders() {
+    for (StreamTracer tracer : tracers) {
+      ((ClientStreamTracer) tracer).inboundHeaders();
+    }
+  }
+
+  /**
+   * See {@link ServerStreamTracer#filterContext}.  For server-side only.
+   *
+   * <p>Called from {@link io.grpc.internal.ServerImpl}.
+   */
+  public <ReqT, RespT> Context serverFilterContext(Context context) {
+    Context ctx = checkNotNull(context, "context");
+    for (StreamTracer tracer : tracers) {
+      ctx = ((ServerStreamTracer) tracer).filterContext(ctx);
+      checkNotNull(ctx, "%s returns null context", tracer);
+    }
+    return ctx;
+  }
+
+  /**
+   * See {@link ServerStreamTracer#serverCallStarted}.  For server-side only.
+   *
+   * <p>Called from {@link io.grpc.internal.ServerImpl}.
+   */
+  public void serverCallStarted(ServerCallInfo<?, ?> callInfo) {
+    for (StreamTracer tracer : tracers) {
+      ((ServerStreamTracer) tracer).serverCallStarted(callInfo);
+    }
+  }
+
+  /**
+   * See {@link StreamTracer#streamClosed}. This may be called multiple times, and only the first
+   * value will be taken.
+   *
+   * <p>Called from abstract stream implementations.
+   */
+  public void streamClosed(Status status) {
+    if (closed.compareAndSet(false, true)) {
+      for (StreamTracer tracer : tracers) {
+        tracer.streamClosed(status);
+      }
+    }
+  }
+
+  /**
+   * See {@link StreamTracer#outboundMessage(int)}.
+   *
+   * <p>Called from {@link io.grpc.internal.Framer}.
+   */
+  public void outboundMessage(int seqNo) {
+    for (StreamTracer tracer : tracers) {
+      tracer.outboundMessage(seqNo);
+    }
+  }
+
+  /**
+   * See {@link StreamTracer#inboundMessage(int)}.
+   *
+   * <p>Called from {@link io.grpc.internal.MessageDeframer}.
+   */
+  public void inboundMessage(int seqNo) {
+    for (StreamTracer tracer : tracers) {
+      tracer.inboundMessage(seqNo);
+    }
+  }
+
+  /**
+   * See {@link StreamTracer#outboundMessageSent}.
+   *
+   * <p>Called from {@link io.grpc.internal.Framer}.
+   */
+  public void outboundMessageSent(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+    for (StreamTracer tracer : tracers) {
+      tracer.outboundMessageSent(seqNo, optionalWireSize, optionalUncompressedSize);
+    }
+  }
+
+  /**
+   * See {@link StreamTracer#inboundMessageRead}.
+   *
+   * <p>Called from {@link io.grpc.internal.MessageDeframer}.
+   */
+  public void inboundMessageRead(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+    for (StreamTracer tracer : tracers) {
+      tracer.inboundMessageRead(seqNo, optionalWireSize, optionalUncompressedSize);
+    }
+  }
+
+  /**
+   * See {@link StreamTracer#outboundUncompressedSize}.
+   *
+   * <p>Called from {@link io.grpc.internal.Framer}.
+   */
+  public void outboundUncompressedSize(long bytes) {
+    for (StreamTracer tracer : tracers) {
+      tracer.outboundUncompressedSize(bytes);
+    }
+  }
+
+  /**
+   * See {@link StreamTracer#outboundWireSize}.
+   *
+   * <p>Called from {@link io.grpc.internal.Framer}.
+   */
+  public void outboundWireSize(long bytes) {
+    for (StreamTracer tracer : tracers) {
+      tracer.outboundWireSize(bytes);
+    }
+  }
+
+  /**
+   * See {@link StreamTracer#inboundUncompressedSize}.
+   *
+   * <p>Called from {@link io.grpc.internal.MessageDeframer}.
+   */
+  public void inboundUncompressedSize(long bytes) {
+    for (StreamTracer tracer : tracers) {
+      tracer.inboundUncompressedSize(bytes);
+    }
+  }
+
+  /**
+   * See {@link StreamTracer#inboundWireSize}.
+   *
+   * <p>Called from {@link io.grpc.internal.MessageDeframer}.
+   */
+  public void inboundWireSize(long bytes) {
+    for (StreamTracer tracer : tracers) {
+      tracer.inboundWireSize(bytes);
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/Stream.java b/core/src/main/java/io/grpc/internal/Stream.java
new file mode 100644
index 0000000..f52b172
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/Stream.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Compressor;
+import java.io.InputStream;
+
+/**
+ * A single stream of communication between two end-points within a transport.
+ *
+ * <p>An implementation doesn't need to be thread-safe. All methods are expected to execute quickly.
+ */
+public interface Stream {
+  /**
+   * Requests up to the given number of messages from the call to be delivered via
+   * {@link StreamListener#messagesAvailable(StreamListener.MessageProducer)}. No additional
+   * messages will be delivered.  If the stream has a {@code start()} method, it must be called
+   * before requesting messages.
+   *
+   * @param numMessages the requested number of messages to be delivered to the listener.
+   */
+  void request(int numMessages);
+
+  /**
+   * Writes a message payload to the remote end-point. The bytes from the stream are immediately
+   * read by the Transport. Where possible callers should use streams that are
+   * {@link io.grpc.KnownLength} to improve efficiency. This method will always return immediately
+   * and will not wait for the write to complete.  If the stream has a {@code start()} method, it
+   * must be called before writing any messages.
+   *
+   * <p>It is recommended that the caller consult {@link #isReady()} before calling this method to
+   * avoid excessive buffering in the transport.
+   *
+   * <p>This method takes ownership of the InputStream, and implementations are responsible for
+   * calling {@link InputStream#close}.
+   *
+   * @param message stream containing the serialized message to be sent
+   */
+  void writeMessage(InputStream message);
+
+  /**
+   * Flushes any internally buffered messages to the remote end-point.
+   */
+  void flush();
+
+  /**
+   * If {@code true}, indicates that the transport is capable of sending additional messages without
+   * requiring excessive buffering internally. Otherwise, {@link StreamListener#onReady()} will be
+   * called when it turns {@code true}.
+   *
+   * <p>This is just a suggestion and the application is free to ignore it, however doing so may
+   * result in excessive buffering within the transport.
+   */
+  boolean isReady();
+
+  /**
+   * Sets the compressor on the framer.
+   *
+   * @param compressor the compressor to use
+   */
+  void setCompressor(Compressor compressor);
+
+  /**
+   * Enables per-message compression, if an encoding type has been negotiated.  If no message
+   * encoding has been negotiated, this is a no-op. By default per-message compression is enabled,
+   * but may not have any effect if compression is not enabled on the call.
+   */
+  void setMessageCompression(boolean enable);
+}
diff --git a/core/src/main/java/io/grpc/internal/StreamListener.java b/core/src/main/java/io/grpc/internal/StreamListener.java
new file mode 100644
index 0000000..090c055
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/StreamListener.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import java.io.InputStream;
+import javax.annotation.Nullable;
+
+/**
+ * An observer of {@link Stream} events. It is guaranteed to only have one concurrent callback at a
+ * time.
+ */
+public interface StreamListener {
+  /**
+   * Called upon receiving a message from the remote end-point.
+   *
+   * <p>Implementations must eventually drain the provided {@code producer} {@link MessageProducer}
+   * completely by invoking {@link MessageProducer#next()} to obtain deframed messages until the
+   * producer returns null.
+   *
+   * <p>This method should return quickly, as the same thread may be used to process other streams.
+   *
+   * @param producer supplier of deframed messages.
+   */
+  void messagesAvailable(MessageProducer producer);
+
+  /**
+   * This indicates that the transport is now capable of sending additional messages
+   * without requiring excessive buffering internally. This event is
+   * just a suggestion and the application is free to ignore it, however doing so may
+   * result in excessive buffering within the transport.
+   */
+  void onReady();
+
+  /**
+   * A producer for deframed gRPC messages.
+   */
+  interface MessageProducer {
+    /**
+     * Returns the next gRPC message, if the data has been received by the deframer and the
+     * application has requested another message.
+     *
+     * <p>The provided {@code message} {@link InputStream} must be closed by the listener.
+     *
+     * <p>This is intended to be used similar to an iterator, invoking {@code next()} to obtain
+     * messages until the producer returns null, at which point the producer may be discarded.
+     */
+    @Nullable
+    public InputStream next();
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/TimeProvider.java b/core/src/main/java/io/grpc/internal/TimeProvider.java
new file mode 100644
index 0000000..813ad38
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/TimeProvider.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Time source representing the current system time in nanos. Used to inject a fake clock
+ * into unit tests.
+ */
+public interface TimeProvider {
+  /** Returns the current nano time. */
+  long currentTimeNanos();
+
+  TimeProvider SYSTEM_TIME_PROVIDER = new TimeProvider() {
+    final long offsetNanos =
+        TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()) - System.nanoTime();
+
+    @Override
+    public long currentTimeNanos() {
+      return System.nanoTime() + offsetNanos;
+    }
+  };
+}
diff --git a/core/src/main/java/io/grpc/internal/TransportFrameUtil.java b/core/src/main/java/io/grpc/internal/TransportFrameUtil.java
new file mode 100644
index 0000000..e015740
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/TransportFrameUtil.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Charsets.US_ASCII;
+
+import com.google.common.io.BaseEncoding;
+import io.grpc.InternalMetadata;
+import io.grpc.Metadata;
+import java.util.Arrays;
+import java.util.logging.Logger;
+
+/**
+ * Utility functions for transport layer framing.
+ *
+ * <p>Within a given transport frame we reserve the first byte to indicate the type of compression
+ * used for the contents of the transport frame.
+ */
+public final class TransportFrameUtil {
+
+  private static final Logger logger = Logger.getLogger(TransportFrameUtil.class.getName());
+
+  private static final byte[] binaryHeaderSuffixBytes =
+      Metadata.BINARY_HEADER_SUFFIX.getBytes(US_ASCII);
+
+  /**
+   * Transform the given headers to a format where only spec-compliant ASCII characters are allowed.
+   * Binary header values are encoded by Base64 in the result.  It is safe to modify the returned
+   * array, but not to modify any of the underlying byte arrays.
+   *
+   * @return the interleaved keys and values.
+   */
+  @SuppressWarnings("BetaApi") // BaseEncoding is stable in Guava 20.0
+  public static byte[][] toHttp2Headers(Metadata headers) {
+    byte[][] serializedHeaders = InternalMetadata.serialize(headers);
+    // TODO(carl-mastrangelo): eventually remove this once all callers are updated.
+    if (serializedHeaders == null) {
+      return new byte[][]{};
+    }
+    int k = 0;
+    for (int i = 0; i < serializedHeaders.length; i += 2) {
+      byte[] key = serializedHeaders[i];
+      byte[] value = serializedHeaders[i + 1];
+      if (endsWith(key, binaryHeaderSuffixBytes)) {
+        // Binary header.
+        serializedHeaders[k] = key;
+        serializedHeaders[k + 1] = BaseEncoding.base64().encode(value).getBytes(US_ASCII);
+        k += 2;
+      } else {
+        // Non-binary header.
+        // Filter out headers that contain non-spec-compliant ASCII characters.
+        // TODO(zhangkun83): only do such check in development mode since it's expensive
+        if (isSpecCompliantAscii(value)) {
+          serializedHeaders[k] = key;
+          serializedHeaders[k + 1] = value;
+          k += 2;
+        } else {
+          String keyString = new String(key, US_ASCII);
+          logger.warning("Metadata key=" + keyString + ", value=" + Arrays.toString(value)
+              + " contains invalid ASCII characters");
+        }
+      }
+    }
+    // Fast path, everything worked out fine.
+    if (k == serializedHeaders.length) {
+      return serializedHeaders;
+    }
+    return Arrays.copyOfRange(serializedHeaders, 0, k);
+  }
+
+  /**
+   * Transform HTTP/2-compliant headers to the raw serialized format which can be deserialized by
+   * metadata marshallers. It decodes the Base64-encoded binary headers.  This function modifies
+   * the headers in place.  By modifying the input array.
+   *
+   * @param http2Headers the interleaved keys and values of HTTP/2-compliant headers
+   * @return the interleaved keys and values in the raw serialized format
+   */
+  @SuppressWarnings("BetaApi") // BaseEncoding is stable in Guava 20.0
+  public static byte[][] toRawSerializedHeaders(byte[][] http2Headers) {
+    for (int i = 0; i < http2Headers.length; i += 2) {
+      byte[] key = http2Headers[i];
+      byte[] value = http2Headers[i + 1];
+      http2Headers[i] = key;
+      if (endsWith(key, binaryHeaderSuffixBytes)) {
+        // Binary header
+        http2Headers[i + 1] = BaseEncoding.base64().decode(new String(value, US_ASCII));
+      } else {
+        // Non-binary header
+        // Nothing to do, the value is already in the right place.
+      }
+    }
+    return http2Headers;
+  }
+
+  /**
+   * Returns {@code true} if {@code subject} ends with {@code suffix}.
+   */
+  private static boolean endsWith(byte[] subject, byte[] suffix) {
+    int start = subject.length - suffix.length;
+    if (start < 0) {
+      return false;
+    }
+    for (int i = start; i < subject.length; i++) {
+      if (subject[i] != suffix[i - start]) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Returns {@code true} if {@code subject} contains only bytes that are spec-compliant ASCII
+   * characters and space.
+   */
+  private static boolean isSpecCompliantAscii(byte[] subject) {
+    for (byte b : subject) {
+      if (b < 32 || b > 126) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  private TransportFrameUtil() {}
+}
diff --git a/core/src/main/java/io/grpc/internal/TransportTracer.java b/core/src/main/java/io/grpc/internal/TransportTracer.java
new file mode 100644
index 0000000..f2b400b
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/TransportTracer.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static io.grpc.internal.TimeProvider.SYSTEM_TIME_PROVIDER;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import io.grpc.InternalChannelz.TransportStats;
+
+/**
+ * A class for gathering statistics about a transport. This is an experimental feature.
+ * Can only be called from the transport thread unless otherwise noted.
+ */
+public final class TransportTracer {
+  private static final Factory DEFAULT_FACTORY = new Factory(SYSTEM_TIME_PROVIDER);
+
+  private final TimeProvider timeProvider;
+  private long streamsStarted;
+  private long lastLocalStreamCreatedTimeNanos;
+  private long lastRemoteStreamCreatedTimeNanos;
+  private long streamsSucceeded;
+  private long streamsFailed;
+  private long keepAlivesSent;
+  private FlowControlReader flowControlWindowReader;
+
+  private long messagesSent;
+  private long lastMessageSentTimeNanos;
+  // deframing happens on the application thread, and there's no easy way to avoid synchronization
+  private final LongCounter messagesReceived = LongCounterFactory.create();
+  private volatile long lastMessageReceivedTimeNanos;
+
+  public TransportTracer() {
+    this.timeProvider = SYSTEM_TIME_PROVIDER;
+  }
+
+  private TransportTracer(TimeProvider timeProvider) {
+    this.timeProvider = timeProvider;
+  }
+
+  /**
+   * Returns a read only set of current stats.
+   */
+  public TransportStats getStats() {
+    long localFlowControlWindow =
+        flowControlWindowReader == null ? -1 : flowControlWindowReader.read().localBytes;
+    long remoteFlowControlWindow =
+        flowControlWindowReader == null ? -1 : flowControlWindowReader.read().remoteBytes;
+    return new TransportStats(
+        streamsStarted,
+        lastLocalStreamCreatedTimeNanos,
+        lastRemoteStreamCreatedTimeNanos,
+        streamsSucceeded,
+        streamsFailed,
+        messagesSent,
+        messagesReceived.value(),
+        keepAlivesSent,
+        lastMessageSentTimeNanos,
+        lastMessageReceivedTimeNanos,
+        localFlowControlWindow,
+        remoteFlowControlWindow);
+  }
+
+  /**
+   * Called by the client to report a stream has started.
+   */
+  public void reportLocalStreamStarted() {
+    streamsStarted++;
+    lastLocalStreamCreatedTimeNanos = timeProvider.currentTimeNanos();
+  }
+
+  /**
+   * Called by the server to report a stream has started.
+   */
+  public void reportRemoteStreamStarted() {
+    streamsStarted++;
+    lastRemoteStreamCreatedTimeNanos = timeProvider.currentTimeNanos();
+  }
+
+  /**
+   * Reports that a stream closed with the specified Status.
+   */
+  public void reportStreamClosed(boolean success) {
+    if (success) {
+      streamsSucceeded++;
+    } else {
+      streamsFailed++;
+    }
+  }
+
+  /**
+   * Reports that some messages were successfully sent. {@code numMessages} must be at least 0.
+   */
+  public void reportMessageSent(int numMessages) {
+    if (numMessages == 0) {
+      return;
+    }
+    messagesSent += numMessages;
+    lastMessageSentTimeNanos = timeProvider.currentTimeNanos();
+  }
+
+  /**
+   * Reports that a message was successfully received. This method is thread safe.
+   */
+  public void reportMessageReceived() {
+    messagesReceived.add(1);
+    lastMessageReceivedTimeNanos = timeProvider.currentTimeNanos();
+  }
+
+  /**
+   * Reports that a keep alive message was sent.
+   */
+  public void reportKeepAliveSent() {
+    keepAlivesSent++;
+  }
+
+  /**
+   * Registers a {@link FlowControlReader} that can be used to read the local and remote flow
+   * control window sizes.
+   */
+  public void setFlowControlWindowReader(FlowControlReader flowControlWindowReader) {
+    this.flowControlWindowReader = Preconditions.checkNotNull(flowControlWindowReader);
+  }
+
+  /**
+   * A container that holds the local and remote flow control window sizes.
+   */
+  public static final class FlowControlWindows {
+    public final long remoteBytes;
+    public final long localBytes;
+
+    public FlowControlWindows(long localBytes, long remoteBytes) {
+      this.localBytes = localBytes;
+      this.remoteBytes = remoteBytes;
+    }
+  }
+
+  /**
+   * An interface for reading the local and remote flow control windows of the transport.
+   */
+  public interface FlowControlReader {
+    FlowControlWindows read();
+  }
+
+  public static final class Factory {
+    private TimeProvider timeProvider;
+
+    @VisibleForTesting
+    public Factory(TimeProvider timeProvider) {
+      this.timeProvider = timeProvider;
+    }
+
+    public TransportTracer create() {
+      return new TransportTracer(timeProvider);
+    }
+  }
+
+  public static Factory getDefaultFactory() {
+    return DEFAULT_FACTORY;
+  }
+}
diff --git a/core/src/main/java/io/grpc/internal/WritableBuffer.java b/core/src/main/java/io/grpc/internal/WritableBuffer.java
new file mode 100644
index 0000000..8cb36ab
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/WritableBuffer.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+/**
+ * An interface for a byte buffer that can only be written to.
+ * {@link WritableBuffer}s are a generic way to transfer bytes to
+ * the concrete network transports, like Netty and OkHttp.
+ */
+public interface WritableBuffer {
+
+  /**
+   * Appends {@code length} bytes to the buffer from the source
+   * array starting at {@code srcIndex}.
+   *
+   * @throws IndexOutOfBoundsException
+   *         if the specified {@code srcIndex} is less than {@code 0},
+   *         if {@code srcIndex + length} is greater than
+   *            {@code src.length}, or
+   *         if {@code length} is greater than {@link #writableBytes()}
+   */
+  void write(byte[] src, int srcIndex, int length);
+
+  /**
+   * Appends a single byte to the buffer.  This is slow so don't call it.
+   */
+  void write(byte b);
+
+  /**
+   * Returns the number of bytes one can write to the buffer.
+   */
+  int writableBytes();
+
+  /**
+   * Returns the number of bytes one can read from the buffer.
+   */
+  int readableBytes();
+
+  /**
+   * Releases the buffer, indicating to the {@link WritableBufferAllocator} that
+   * this buffer is no longer used and its resources can be reused.
+   */
+  void release();
+}
diff --git a/core/src/main/java/io/grpc/internal/WritableBufferAllocator.java b/core/src/main/java/io/grpc/internal/WritableBufferAllocator.java
new file mode 100644
index 0000000..902b279
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/WritableBufferAllocator.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+/**
+ * An allocator of buffers provided by the transport implementation to {@link MessageFramer} so
+ * it can send chunks of data to the transport in a form that the transport can directly serialize.
+ */
+public interface WritableBufferAllocator {
+
+  /**
+   * Request a new {@link WritableBuffer} with the given {@code capacityHint}. The allocator is
+   * free to return a buffer with a greater or lesser capacity.
+   */
+  WritableBuffer allocate(int capacityHint);
+}
diff --git a/core/src/main/java/io/grpc/internal/package-info.java b/core/src/main/java/io/grpc/internal/package-info.java
new file mode 100644
index 0000000..bd8ff89
--- /dev/null
+++ b/core/src/main/java/io/grpc/internal/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Interfaces and implementations that are internal to gRPC.
+ *
+ * <p>All the content under this package and its subpackages are considered annotated with {@link
+ * io.grpc.Internal}.
+ */
+@io.grpc.Internal
+package io.grpc.internal;
diff --git a/core/src/main/java/io/grpc/package-info.java b/core/src/main/java/io/grpc/package-info.java
new file mode 100644
index 0000000..8663d75
--- /dev/null
+++ b/core/src/main/java/io/grpc/package-info.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/**
+ * The gRPC core public API.
+ *
+ * <p>gRPC is based on a client-server model of remote procedure calls. A client creates a channel
+ * which is connected to a server. RPCs are initiated from the client and sent to the server which
+ * then responds back to the client. When the client and server are done sending messages, they half
+ * close their respective connections. The RPC is complete as soon as the server closes.
+ *
+ * <p>To send an RPC, first create a {@link io.grpc.Channel} using {@link
+ * io.grpc.ManagedChannelBuilder#forTarget}. When using auto generate Protobuf stubs, the stub class
+ * will have constructors for wrapping the channel. These include {@code newBlockingStub}, {@code
+ * newStub}, and {@code newFutureStub} which you can use based on your design. The stub is the
+ * primary way a client interacts with a server.
+ *
+ * <p>To receive RPCs, create a {@link io.grpc.Server} using {@link io.grpc.ServerBuilder#forPort}.
+ * The Protobuf stub will contain an abstract class called AbstractFoo, where Foo is the name of
+ * your service. Extend this class, and pass an instance of it to {@link
+ * io.grpc.ServerBuilder#addService}. Once your server is built, call {@link io.grpc.Server#start}
+ * to begin accepting RPCs.
+ *
+ * <p>Both Clients and Servers should use a custom {@link java.util.concurrent.Executor}. The gRPC
+ * runtime includes a default executor that eases testing and examples, but is not ideal for use in
+ * a production environment. See the associated documentation in the respective builders.
+ *
+ * <p>Clients and Servers can also be shutdown gracefully using the {@code shutdown} method. The API
+ * to conduct an orderly shutdown is modeled from the {@link java.util.concurrent.ExecutorService}.
+ *
+ * <p>gRPC also includes support for more advanced features, such as name resolution, load
+ * balancing, bidirectional streaming, health checking, and more. See the relative methods in the
+ * client and server builders.
+ *
+ * <p>Development of gRPC is done primary on Github at <a
+ * href="https://github.com/grpc/grpc-java">https://github.com/grpc/grpc-java</a>, where the gRPC
+ * team welcomes contributions and bug reports. There is also a mailing list at <a
+ * href="https://groups.google.com/forum/#!forum/grpc-io">grpc-io</a> if you have questions about
+ * gRPC.
+ */
+package io.grpc;
diff --git a/core/src/main/java/io/grpc/util/MutableHandlerRegistry.java b/core/src/main/java/io/grpc/util/MutableHandlerRegistry.java
new file mode 100644
index 0000000..a068cdc
--- /dev/null
+++ b/core/src/main/java/io/grpc/util/MutableHandlerRegistry.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.util;
+
+import io.grpc.BindableService;
+import io.grpc.ExperimentalApi;
+import io.grpc.HandlerRegistry;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerMethodDefinition;
+import io.grpc.ServerServiceDefinition;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Default implementation of {@link MutableHandlerRegistry}.
+ *
+ * <p>Uses {@link ConcurrentHashMap} to avoid service registration excessively
+ * blocking method lookup.
+ */
+@ThreadSafe
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/933")
+public final class MutableHandlerRegistry extends HandlerRegistry {
+  private final ConcurrentMap<String, ServerServiceDefinition> services
+      = new ConcurrentHashMap<String, ServerServiceDefinition>();
+
+  /**
+   * Registers a service.
+   *
+   * @return the previously registered service with the same service descriptor name if exists,
+   *         otherwise {@code null}.
+   */
+  @Nullable
+  public ServerServiceDefinition addService(ServerServiceDefinition service) {
+    return services.put(service.getServiceDescriptor().getName(), service);
+  }
+
+  /**
+   * Registers a service.
+   *
+   * @return the previously registered service with the same service descriptor name if exists,
+   *         otherwise {@code null}.
+   */
+  @Nullable
+  public ServerServiceDefinition addService(BindableService bindableService) {
+    return addService(bindableService.bindService());
+  }
+
+  /**
+   * Removes a registered service
+   *
+   * @return true if the service was found to be removed.
+   */
+  public boolean removeService(ServerServiceDefinition service) {
+    return services.remove(service.getServiceDescriptor().getName(), service);
+  }
+
+  /**
+   *  Note: This does not necessarily return a consistent view of the map.
+   */
+  @Override
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2222")
+  public List<ServerServiceDefinition> getServices() {
+    return Collections.unmodifiableList(new ArrayList<>(services.values()));
+  }
+
+  /**
+   * Note: This does not actually honor the authority provided.  It will, eventually in the future.
+   */
+  @Override
+  @Nullable
+  public ServerMethodDefinition<?, ?> lookupMethod(String methodName, @Nullable String authority) {
+    String serviceName = MethodDescriptor.extractFullServiceName(methodName);
+    if (serviceName == null) {
+      return null;
+    }
+    ServerServiceDefinition service = services.get(serviceName);
+    if (service == null) {
+      return null;
+    }
+    return service.getMethod(methodName);
+  }
+}
diff --git a/core/src/main/java/io/grpc/util/RoundRobinLoadBalancerFactory.java b/core/src/main/java/io/grpc/util/RoundRobinLoadBalancerFactory.java
new file mode 100644
index 0000000..e46c5e5
--- /dev/null
+++ b/core/src/main/java/io/grpc/util/RoundRobinLoadBalancerFactory.java
@@ -0,0 +1,492 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.util;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.ConnectivityState.CONNECTING;
+import static io.grpc.ConnectivityState.IDLE;
+import static io.grpc.ConnectivityState.READY;
+import static io.grpc.ConnectivityState.SHUTDOWN;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+
+import io.grpc.Attributes;
+import io.grpc.ConnectivityState;
+import io.grpc.ConnectivityStateInfo;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.ExperimentalApi;
+import io.grpc.LoadBalancer;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.Metadata;
+import io.grpc.Metadata.Key;
+import io.grpc.NameResolver;
+import io.grpc.Status;
+import io.grpc.internal.GrpcAttributes;
+import io.grpc.internal.ServiceConfigUtil;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * A {@link LoadBalancer} that provides round-robin load balancing mechanism over the
+ * addresses from the {@link NameResolver}.  The sub-lists received from the name resolver
+ * are considered to be an {@link EquivalentAddressGroup} and each of these sub-lists is
+ * what is then balanced across.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
+public final class RoundRobinLoadBalancerFactory extends LoadBalancer.Factory {
+
+  private static final RoundRobinLoadBalancerFactory INSTANCE =
+      new RoundRobinLoadBalancerFactory();
+
+  private RoundRobinLoadBalancerFactory() {}
+
+  /**
+   * A lighter weight Reference than AtomicReference.
+   */
+  @VisibleForTesting
+  static final class Ref<T> {
+    T value;
+
+    Ref(T value) {
+      this.value = value;
+    }
+  }
+
+  /**
+   * Gets the singleton instance of this factory.
+   */
+  public static RoundRobinLoadBalancerFactory getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public LoadBalancer newLoadBalancer(LoadBalancer.Helper helper) {
+    return new RoundRobinLoadBalancer(helper);
+  }
+
+  @VisibleForTesting
+  static final class RoundRobinLoadBalancer extends LoadBalancer {
+    @VisibleForTesting
+    static final Attributes.Key<Ref<ConnectivityStateInfo>> STATE_INFO =
+        Attributes.Key.create("state-info");
+    // package-private to avoid synthetic access
+    static final Attributes.Key<Ref<Subchannel>> STICKY_REF = Attributes.Key.create("sticky-ref");
+
+    private static final Logger logger = Logger.getLogger(RoundRobinLoadBalancer.class.getName());
+
+    private final Helper helper;
+    private final Map<EquivalentAddressGroup, Subchannel> subchannels =
+        new HashMap<EquivalentAddressGroup, Subchannel>();
+    private final Random random;
+
+    private ConnectivityState currentState;
+    private RoundRobinPicker currentPicker = new EmptyPicker(EMPTY_OK);
+
+    @Nullable
+    private StickinessState stickinessState;
+
+    RoundRobinLoadBalancer(Helper helper) {
+      this.helper = checkNotNull(helper, "helper");
+      this.random = new Random();
+    }
+
+    @Override
+    public void handleResolvedAddressGroups(
+        List<EquivalentAddressGroup> servers, Attributes attributes) {
+      Set<EquivalentAddressGroup> currentAddrs = subchannels.keySet();
+      Set<EquivalentAddressGroup> latestAddrs = stripAttrs(servers);
+      Set<EquivalentAddressGroup> addedAddrs = setsDifference(latestAddrs, currentAddrs);
+      Set<EquivalentAddressGroup> removedAddrs = setsDifference(currentAddrs, latestAddrs);
+
+      Map<String, Object> serviceConfig =
+          attributes.get(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG);
+      if (serviceConfig != null) {
+        String stickinessMetadataKey =
+            ServiceConfigUtil.getStickinessMetadataKeyFromServiceConfig(serviceConfig);
+        if (stickinessMetadataKey != null) {
+          if (stickinessMetadataKey.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
+            logger.log(
+                Level.FINE,
+                "Binary stickiness header is not supported. The header '{0}' will be ignored",
+                stickinessMetadataKey);
+          } else if (stickinessState == null
+              || !stickinessState.key.name().equals(stickinessMetadataKey)) {
+            stickinessState = new StickinessState(stickinessMetadataKey);
+          }
+        }
+      }
+
+      // Create new subchannels for new addresses.
+      for (EquivalentAddressGroup addressGroup : addedAddrs) {
+        // NB(lukaszx0): we don't merge `attributes` with `subchannelAttr` because subchannel
+        // doesn't need them. They're describing the resolved server list but we're not taking
+        // any action based on this information.
+        Attributes.Builder subchannelAttrs = Attributes.newBuilder()
+            // NB(lukaszx0): because attributes are immutable we can't set new value for the key
+            // after creation but since we can mutate the values we leverage that and set
+            // AtomicReference which will allow mutating state info for given channel.
+            .set(STATE_INFO,
+                new Ref<ConnectivityStateInfo>(ConnectivityStateInfo.forNonError(IDLE)));
+
+        Ref<Subchannel> stickyRef = null;
+        if (stickinessState != null) {
+          subchannelAttrs.set(STICKY_REF, stickyRef = new Ref<Subchannel>(null));
+        }
+
+        Subchannel subchannel = checkNotNull(
+            helper.createSubchannel(addressGroup, subchannelAttrs.build()), "subchannel");
+        if (stickyRef != null) {
+          stickyRef.value = subchannel;
+        }
+        subchannels.put(addressGroup, subchannel);
+        subchannel.requestConnection();
+      }
+
+      // Shutdown subchannels for removed addresses.
+      for (EquivalentAddressGroup addressGroup : removedAddrs) {
+        Subchannel subchannel = subchannels.remove(addressGroup);
+        shutdownSubchannel(subchannel);
+      }
+
+      updateBalancingState();
+    }
+
+    @Override
+    public void handleNameResolutionError(Status error) {
+      // ready pickers aren't affected by status changes
+      updateBalancingState(TRANSIENT_FAILURE,
+          currentPicker instanceof ReadyPicker ? currentPicker : new EmptyPicker(error));
+    }
+
+    @Override
+    public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
+      if (subchannels.get(subchannel.getAddresses()) != subchannel) {
+        return;
+      }
+      if (stateInfo.getState() == SHUTDOWN && stickinessState != null) {
+        stickinessState.remove(subchannel);
+      }
+      if (stateInfo.getState() == IDLE) {
+        subchannel.requestConnection();
+      }
+      getSubchannelStateInfoRef(subchannel).value = stateInfo;
+      updateBalancingState();
+    }
+
+    private void shutdownSubchannel(Subchannel subchannel) {
+      subchannel.shutdown();
+      getSubchannelStateInfoRef(subchannel).value =
+          ConnectivityStateInfo.forNonError(SHUTDOWN);
+      if (stickinessState != null) {
+        stickinessState.remove(subchannel);
+      }
+    }
+
+    @Override
+    public void shutdown() {
+      for (Subchannel subchannel : getSubchannels()) {
+        shutdownSubchannel(subchannel);
+      }
+    }
+
+    private static final Status EMPTY_OK = Status.OK.withDescription("no subchannels ready");
+
+    /**
+     * Updates picker with the list of active subchannels (state == READY).
+     */
+    @SuppressWarnings("ReferenceEquality")
+    private void updateBalancingState() {
+      List<Subchannel> activeList = filterNonFailingSubchannels(getSubchannels());
+      if (activeList.isEmpty()) {
+        // No READY subchannels, determine aggregate state and error status
+        boolean isConnecting = false;
+        Status aggStatus = EMPTY_OK;
+        for (Subchannel subchannel : getSubchannels()) {
+          ConnectivityStateInfo stateInfo = getSubchannelStateInfoRef(subchannel).value;
+          // This subchannel IDLE is not because of channel IDLE_TIMEOUT,
+          // in which case LB is already shutdown.
+          // RRLB will request connection immediately on subchannel IDLE.
+          if (stateInfo.getState() == CONNECTING || stateInfo.getState() == IDLE) {
+            isConnecting = true;
+          }
+          if (aggStatus == EMPTY_OK || !aggStatus.isOk()) {
+            aggStatus = stateInfo.getStatus();
+          }
+        }
+        updateBalancingState(isConnecting ? CONNECTING : TRANSIENT_FAILURE,
+            // If all subchannels are TRANSIENT_FAILURE, return the Status associated with
+            // an arbitrary subchannel, otherwise return OK.
+            new EmptyPicker(aggStatus));
+      } else {
+        // initialize the Picker to a random start index to ensure that a high frequency of Picker
+        // churn does not skew subchannel selection.
+        int startIndex = random.nextInt(activeList.size());
+        updateBalancingState(READY, new ReadyPicker(activeList, startIndex, stickinessState));
+      }
+    }
+
+    private void updateBalancingState(ConnectivityState state, RoundRobinPicker picker) {
+      if (state != currentState || !picker.isEquivalentTo(currentPicker)) {
+        helper.updateBalancingState(state, picker);
+        currentState = state;
+        currentPicker = picker;
+      }
+    }
+
+    /**
+     * Filters out non-ready subchannels.
+     */
+    private static List<Subchannel> filterNonFailingSubchannels(
+        Collection<Subchannel> subchannels) {
+      List<Subchannel> readySubchannels = new ArrayList<>(subchannels.size());
+      for (Subchannel subchannel : subchannels) {
+        if (isReady(subchannel)) {
+          readySubchannels.add(subchannel);
+        }
+      }
+      return readySubchannels;
+    }
+
+    /**
+     * Converts list of {@link EquivalentAddressGroup} to {@link EquivalentAddressGroup} set and
+     * remove all attributes.
+     */
+    private static Set<EquivalentAddressGroup> stripAttrs(List<EquivalentAddressGroup> groupList) {
+      Set<EquivalentAddressGroup> addrs = new HashSet<EquivalentAddressGroup>(groupList.size());
+      for (EquivalentAddressGroup group : groupList) {
+        addrs.add(new EquivalentAddressGroup(group.getAddresses()));
+      }
+      return addrs;
+    }
+
+    @VisibleForTesting
+    Collection<Subchannel> getSubchannels() {
+      return subchannels.values();
+    }
+
+    private static Ref<ConnectivityStateInfo> getSubchannelStateInfoRef(
+        Subchannel subchannel) {
+      return checkNotNull(subchannel.getAttributes().get(STATE_INFO), "STATE_INFO");
+    }
+    
+    // package-private to avoid synthetic access
+    static boolean isReady(Subchannel subchannel) {
+      return getSubchannelStateInfoRef(subchannel).value.getState() == READY;
+    }
+
+    private static <T> Set<T> setsDifference(Set<T> a, Set<T> b) {
+      Set<T> aCopy = new HashSet<T>(a);
+      aCopy.removeAll(b);
+      return aCopy;
+    }
+
+    Map<String, Ref<Subchannel>> getStickinessMapForTest() {
+      if (stickinessState == null) {
+        return null;
+      }
+      return stickinessState.stickinessMap;
+    }
+
+    /**
+     * Holds stickiness related states: The stickiness key, a registry mapping stickiness values to
+     * the associated Subchannel Ref, and a map from Subchannel to Subchannel Ref.
+     */
+    @VisibleForTesting
+    static final class StickinessState {
+      static final int MAX_ENTRIES = 1000;
+
+      final Key<String> key;
+      final ConcurrentMap<String, Ref<Subchannel>> stickinessMap =
+          new ConcurrentHashMap<String, Ref<Subchannel>>();
+
+      final Queue<String> evictionQueue = new ConcurrentLinkedQueue<String>();
+
+      StickinessState(@Nonnull String stickinessKey) {
+        this.key = Key.of(stickinessKey, Metadata.ASCII_STRING_MARSHALLER);
+      }
+
+      /**
+       * Returns the subchannel associated to the stickiness value if available in both the
+       * registry and the round robin list, otherwise associates the given subchannel with the
+       * stickiness key in the registry and returns the given subchannel.
+       */
+      @Nonnull
+      Subchannel maybeRegister(
+          String stickinessValue, @Nonnull Subchannel subchannel) {
+        final Ref<Subchannel> newSubchannelRef = subchannel.getAttributes().get(STICKY_REF);
+        while (true) {
+          Ref<Subchannel> existingSubchannelRef =
+              stickinessMap.putIfAbsent(stickinessValue, newSubchannelRef);
+          if (existingSubchannelRef == null) {
+            // new entry
+            addToEvictionQueue(stickinessValue);
+            return subchannel;
+          } else {
+            // existing entry
+            Subchannel existingSubchannel = existingSubchannelRef.value;
+            if (existingSubchannel != null && isReady(existingSubchannel)) {
+              return existingSubchannel;
+            }
+          }
+          // existingSubchannelRef is not null but no longer valid, replace it
+          if (stickinessMap.replace(stickinessValue, existingSubchannelRef, newSubchannelRef)) {
+            return subchannel;
+          }
+          // another thread concurrently removed or updated the entry, try again
+        }
+      }
+
+      private void addToEvictionQueue(String value) {
+        String oldValue;
+        while (stickinessMap.size() >= MAX_ENTRIES && (oldValue = evictionQueue.poll()) != null) {
+          stickinessMap.remove(oldValue);
+        }
+        evictionQueue.add(value);
+      }
+
+      /**
+       * Unregister the subchannel from StickinessState.
+       */
+      void remove(Subchannel subchannel) {
+        subchannel.getAttributes().get(STICKY_REF).value = null;
+      }
+
+      /**
+       * Gets the subchannel associated with the stickiness value if there is.
+       */
+      @Nullable
+      Subchannel getSubchannel(String stickinessValue) {
+        Ref<Subchannel> subchannelRef = stickinessMap.get(stickinessValue);
+        if (subchannelRef != null) {
+          return subchannelRef.value;
+        }
+        return null;
+      }
+    }
+  }
+  
+  // Only subclasses are ReadyPicker or EmptyPicker
+  private abstract static class RoundRobinPicker extends SubchannelPicker {
+    abstract boolean isEquivalentTo(RoundRobinPicker picker);
+  }
+
+  @VisibleForTesting
+  static final class ReadyPicker extends RoundRobinPicker {
+    private static final AtomicIntegerFieldUpdater<ReadyPicker> indexUpdater =
+        AtomicIntegerFieldUpdater.newUpdater(ReadyPicker.class, "index");
+
+    private final List<Subchannel> list; // non-empty
+    @Nullable
+    private final RoundRobinLoadBalancer.StickinessState stickinessState;
+    @SuppressWarnings("unused")
+    private volatile int index;
+
+    ReadyPicker(List<Subchannel> list, int startIndex,
+        @Nullable RoundRobinLoadBalancer.StickinessState stickinessState) {
+      Preconditions.checkArgument(!list.isEmpty(), "empty list");
+      this.list = list;
+      this.stickinessState = stickinessState;
+      this.index = startIndex - 1;
+    }
+
+    @Override
+    public PickResult pickSubchannel(PickSubchannelArgs args) {
+      Subchannel subchannel = null;
+      if (stickinessState != null) {
+        String stickinessValue = args.getHeaders().get(stickinessState.key);
+        if (stickinessValue != null) {
+          subchannel = stickinessState.getSubchannel(stickinessValue);
+          if (subchannel == null || !RoundRobinLoadBalancer.isReady(subchannel)) {
+            subchannel = stickinessState.maybeRegister(stickinessValue, nextSubchannel());
+          }
+        }
+      }
+
+      return PickResult.withSubchannel(subchannel != null ? subchannel : nextSubchannel());
+    }
+
+    private Subchannel nextSubchannel() {
+      int size = list.size();
+      int i = indexUpdater.incrementAndGet(this);
+      if (i >= size) {
+        int oldi = i;
+        i %= size;
+        indexUpdater.compareAndSet(this, oldi, i);
+      }
+      return list.get(i);
+    }
+
+    @VisibleForTesting
+    List<Subchannel> getList() {
+      return list;
+    }
+
+    @Override
+    boolean isEquivalentTo(RoundRobinPicker picker) {
+      if (!(picker instanceof ReadyPicker)) {
+        return false;
+      }
+      ReadyPicker other = (ReadyPicker) picker;
+      // the lists cannot contain duplicate subchannels
+      return other == this || (stickinessState == other.stickinessState
+          && list.size() == other.list.size()
+          && new HashSet<Subchannel>(list).containsAll(other.list));
+    }
+  }
+
+  @VisibleForTesting
+  static final class EmptyPicker extends RoundRobinPicker {
+
+    private final Status status;
+
+    EmptyPicker(@Nonnull Status status) {
+      this.status = Preconditions.checkNotNull(status, "status");
+    }
+
+    @Override
+    public PickResult pickSubchannel(PickSubchannelArgs args) {
+      return status.isOk() ? PickResult.withNoResult() : PickResult.withError(status);
+    }
+
+    @Override
+    boolean isEquivalentTo(RoundRobinPicker picker) {
+      return picker instanceof EmptyPicker && (Objects.equal(status, ((EmptyPicker) picker).status)
+          || (status.isOk() && ((EmptyPicker) picker).status.isOk()));
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/util/TransmitStatusRuntimeExceptionInterceptor.java b/core/src/main/java/io/grpc/util/TransmitStatusRuntimeExceptionInterceptor.java
new file mode 100644
index 0000000..0b89065
--- /dev/null
+++ b/core/src/main/java/io/grpc/util/TransmitStatusRuntimeExceptionInterceptor.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.util;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.Attributes;
+import io.grpc.ExperimentalApi;
+import io.grpc.ForwardingServerCall;
+import io.grpc.ForwardingServerCallListener;
+import io.grpc.Metadata;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.internal.SerializingExecutor;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.Nullable;
+
+/**
+ * A class that intercepts uncaught exceptions of type {@link StatusRuntimeException} and handles
+ * them by closing the {@link ServerCall}, and transmitting the exception's status and metadata
+ * to the client.
+ *
+ * <p>Without this interceptor, gRPC will strip all details and close the {@link ServerCall} with
+ * a generic {@link Status#UNKNOWN} code.
+ *
+ * <p>Security warning: the {@link Status} and {@link Metadata} may contain sensitive server-side
+ * state information, and generally should not be sent to clients. Only install this interceptor
+ * if all clients are trusted.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2189")
+public final class TransmitStatusRuntimeExceptionInterceptor implements ServerInterceptor {
+  private TransmitStatusRuntimeExceptionInterceptor() {
+  }
+
+  public static ServerInterceptor instance() {
+    return new TransmitStatusRuntimeExceptionInterceptor();
+  }
+
+  @Override
+  public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+      ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
+    final ServerCall<ReqT, RespT> serverCall = new SerializingServerCall<ReqT, RespT>(call);
+    ServerCall.Listener<ReqT> listener = next.startCall(serverCall, headers);
+    return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(listener) {
+      @Override
+      public void onMessage(ReqT message) {
+        try {
+          super.onMessage(message);
+        } catch (StatusRuntimeException e) {
+          closeWithException(e);
+        }
+      }
+
+      @Override
+      public void onHalfClose() {
+        try {
+          super.onHalfClose();
+        } catch (StatusRuntimeException e) {
+          closeWithException(e);
+        }
+      }
+
+      @Override
+      public void onCancel() {
+        try {
+          super.onCancel();
+        } catch (StatusRuntimeException e) {
+          closeWithException(e);
+        }
+      }
+
+      @Override
+      public void onComplete() {
+        try {
+          super.onComplete();
+        } catch (StatusRuntimeException e) {
+          closeWithException(e);
+        }
+      }
+
+      @Override
+      public void onReady() {
+        try {
+          super.onReady();
+        } catch (StatusRuntimeException e) {
+          closeWithException(e);
+        }
+      }
+
+      private void closeWithException(StatusRuntimeException t) {
+        Metadata metadata = t.getTrailers();
+        if (metadata == null) {
+          metadata = new Metadata();
+        }
+        serverCall.close(t.getStatus(), metadata);
+      }
+    };
+  }
+
+  /**
+   * A {@link ServerCall} that wraps around a non thread safe delegate and provides thread safe
+   * access by serializing everything on an executor.
+   */
+  private static class SerializingServerCall<ReqT, RespT> extends
+      ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT> {
+    private static final String ERROR_MSG = "Encountered error during serialized access";
+    private final SerializingExecutor serializingExecutor =
+        new SerializingExecutor(MoreExecutors.directExecutor());
+    private boolean closeCalled = false;
+
+    SerializingServerCall(ServerCall<ReqT, RespT> delegate) {
+      super(delegate);
+    }
+
+    @Override
+    public void sendMessage(final RespT message) {
+      serializingExecutor.execute(new Runnable() {
+        @Override
+        public void run() {
+          SerializingServerCall.super.sendMessage(message);
+        }
+      });
+    }
+
+    @Override
+    public void request(final int numMessages) {
+      serializingExecutor.execute(new Runnable() {
+        @Override
+        public void run() {
+          SerializingServerCall.super.request(numMessages);
+        }
+      });
+    }
+
+    @Override
+    public void sendHeaders(final Metadata headers) {
+      serializingExecutor.execute(new Runnable() {
+        @Override
+        public void run() {
+          SerializingServerCall.super.sendHeaders(headers);
+        }
+      });
+    }
+
+    @Override
+    public void close(final Status status, final Metadata trailers) {
+      serializingExecutor.execute(new Runnable() {
+        @Override
+        public void run() {
+          if (!closeCalled) {
+            closeCalled = true;
+
+            SerializingServerCall.super.close(status, trailers);
+          }
+        }
+      });
+    }
+
+    @Override
+    public boolean isReady() {
+      final SettableFuture<Boolean> retVal = SettableFuture.create();
+      serializingExecutor.execute(new Runnable() {
+        @Override
+        public void run() {
+          retVal.set(SerializingServerCall.super.isReady());
+        }
+      });
+      try {
+        return retVal.get();
+      } catch (InterruptedException e) {
+        throw new RuntimeException(ERROR_MSG, e);
+      } catch (ExecutionException e) {
+        throw new RuntimeException(ERROR_MSG, e);
+      }
+    }
+
+    @Override
+    public boolean isCancelled() {
+      final SettableFuture<Boolean> retVal = SettableFuture.create();
+      serializingExecutor.execute(new Runnable() {
+        @Override
+        public void run() {
+          retVal.set(SerializingServerCall.super.isCancelled());
+        }
+      });
+      try {
+        return retVal.get();
+      } catch (InterruptedException e) {
+        throw new RuntimeException(ERROR_MSG, e);
+      } catch (ExecutionException e) {
+        throw new RuntimeException(ERROR_MSG, e);
+      }
+    }
+
+    @Override
+    public void setMessageCompression(final boolean enabled) {
+      serializingExecutor.execute(new Runnable() {
+        @Override
+        public void run() {
+          SerializingServerCall.super.setMessageCompression(enabled);
+        }
+      });
+    }
+
+    @Override
+    public void setCompression(final String compressor) {
+      serializingExecutor.execute(new Runnable() {
+        @Override
+        public void run() {
+          SerializingServerCall.super.setCompression(compressor);
+        }
+      });
+    }
+
+    @Override
+    public Attributes getAttributes() {
+      final SettableFuture<Attributes> retVal = SettableFuture.create();
+      serializingExecutor.execute(new Runnable() {
+        @Override
+        public void run() {
+          retVal.set(SerializingServerCall.super.getAttributes());
+        }
+      });
+      try {
+        return retVal.get();
+      } catch (InterruptedException e) {
+        throw new RuntimeException(ERROR_MSG, e);
+      } catch (ExecutionException e) {
+        throw new RuntimeException(ERROR_MSG, e);
+      }
+    }
+
+    @Nullable
+    @Override
+    public String getAuthority() {
+      final SettableFuture<String> retVal = SettableFuture.create();
+      serializingExecutor.execute(new Runnable() {
+        @Override
+        public void run() {
+          retVal.set(SerializingServerCall.super.getAuthority());
+        }
+      });
+      try {
+        return retVal.get();
+      } catch (InterruptedException e) {
+        throw new RuntimeException(ERROR_MSG, e);
+      } catch (ExecutionException e) {
+        throw new RuntimeException(ERROR_MSG, e);
+      }
+    }
+  }
+}
diff --git a/core/src/main/java/io/grpc/util/package-info.java b/core/src/main/java/io/grpc/util/package-info.java
new file mode 100644
index 0000000..b0e877f
--- /dev/null
+++ b/core/src/main/java/io/grpc/util/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Utilities with advanced features in the core layer that user can optionally use.
+ */
+package io.grpc.util;
diff --git a/core/src/main/resources/META-INF/services/io.grpc.NameResolverProvider b/core/src/main/resources/META-INF/services/io.grpc.NameResolverProvider
new file mode 100644
index 0000000..ba85009
--- /dev/null
+++ b/core/src/main/resources/META-INF/services/io.grpc.NameResolverProvider
@@ -0,0 +1 @@
+io.grpc.internal.DnsNameResolverProvider
diff --git a/core/src/test/java/io/grpc/AttributesTest.java b/core/src/test/java/io/grpc/AttributesTest.java
new file mode 100644
index 0000000..33f43f8
--- /dev/null
+++ b/core/src/test/java/io/grpc/AttributesTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Attributes}. */
+@RunWith(JUnit4.class)
+public class AttributesTest {
+  private static final Attributes.Key<String> YOLO_KEY = Attributes.Key.create("yolo");
+
+  @Test
+  public void buildAttributes() {
+    Attributes attrs = Attributes.newBuilder().set(YOLO_KEY, "To be, or not to be?").build();
+    assertSame("To be, or not to be?", attrs.get(YOLO_KEY));
+    assertThat(attrs.keysForTest()).hasSize(1);
+  }
+
+  @Test
+  public void duplicates() {
+    Attributes attrs = Attributes.newBuilder()
+        .set(YOLO_KEY, "To be?")
+        .set(YOLO_KEY, "Or not to be?")
+        .set(Attributes.Key.create("yolo"), "I'm not a duplicate")
+        .build();
+    assertThat(attrs.get(YOLO_KEY)).isEqualTo("Or not to be?");
+    assertThat(attrs.keysForTest()).hasSize(2);
+  }
+
+  @Test
+  public void toBuilder() {
+    Attributes attrs = Attributes.newBuilder()
+        .set(YOLO_KEY, "To be?")
+        .build()
+        .toBuilder()
+        .set(YOLO_KEY, "Or not to be?")
+        .set(Attributes.Key.create("yolo"), "I'm not a duplicate")
+        .build();
+    assertThat(attrs.get(YOLO_KEY)).isEqualTo("Or not to be?");
+    assertThat(attrs.keysForTest()).hasSize(2);
+  }
+
+  @Test
+  public void empty() {
+    assertThat(Attributes.EMPTY.keysForTest()).isEmpty();
+  }
+
+  @Test
+  public void valueEquality() {
+    class EqualObject {
+      @Override public boolean equals(Object o) {
+        return o instanceof EqualObject;
+      }
+
+      @Override public int hashCode() {
+        return 42;
+      }
+    }
+
+    Attributes.Key<EqualObject> key = Attributes.Key.create("ints");
+    EqualObject v1 = new EqualObject();
+    EqualObject v2 = new EqualObject();
+
+    assertNotSame(v1, v2);
+    assertEquals(v1, v2);
+
+    Attributes attr1 = Attributes.newBuilder().set(key, v1).build();
+    Attributes attr2 = Attributes.newBuilder().set(key, v2).build();
+
+    assertEquals(attr1, attr2);
+    assertEquals(attr1.hashCode(), attr2.hashCode());
+  }
+}
diff --git a/core/src/test/java/io/grpc/CallOptionsTest.java b/core/src/test/java/io/grpc/CallOptionsTest.java
new file mode 100644
index 0000000..7b31148
--- /dev/null
+++ b/core/src/test/java/io/grpc/CallOptionsTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+import static io.grpc.testing.DeadlineSubject.deadline;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import com.google.common.base.Objects;
+import io.grpc.internal.SerializingExecutor;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link CallOptions}. */
+@RunWith(JUnit4.class)
+public class CallOptionsTest {
+  private static final CallOptions.Key<String> OPTION_1
+      = CallOptions.Key.createWithDefault("option1", "default");
+  private static final CallOptions.Key<String> OPTION_2
+      = CallOptions.Key.createWithDefault("option2", "default");
+  private final String sampleAuthority = "authority";
+  private final String sampleCompressor = "compressor";
+  private final Deadline.Ticker ticker = new FakeTicker();
+  private final Deadline sampleDeadline = Deadline.after(1, NANOSECONDS, ticker);
+  private final CallCredentials sampleCreds = mock(CallCredentials.class);
+  private final ClientStreamTracer.Factory tracerFactory1 = new FakeTracerFactory("tracerFactory1");
+  private final ClientStreamTracer.Factory tracerFactory2 = new FakeTracerFactory("tracerFactory2");
+  private final CallOptions allSet = CallOptions.DEFAULT
+      .withAuthority(sampleAuthority)
+      .withDeadline(sampleDeadline)
+      .withCallCredentials(sampleCreds)
+      .withCompression(sampleCompressor)
+      .withWaitForReady()
+      .withExecutor(directExecutor())
+      .withOption(OPTION_1, "value1")
+      .withStreamTracerFactory(tracerFactory1)
+      .withOption(OPTION_2, "value2")
+      .withStreamTracerFactory(tracerFactory2);
+
+  @Test
+  public void defaultsAreAllNull() {
+    assertThat(CallOptions.DEFAULT.getDeadline()).isNull();
+    assertThat(CallOptions.DEFAULT.getAuthority()).isNull();
+    assertThat(CallOptions.DEFAULT.getExecutor()).isNull();
+    assertThat(CallOptions.DEFAULT.getCredentials()).isNull();
+    assertThat(CallOptions.DEFAULT.getCompressor()).isNull();
+    assertThat(CallOptions.DEFAULT.isWaitForReady()).isFalse();
+    assertThat(CallOptions.DEFAULT.getStreamTracerFactories()).isEmpty();
+  }
+
+  @Test
+  public void withAndWithoutWaitForReady() {
+    assertThat(CallOptions.DEFAULT.withWaitForReady().isWaitForReady()).isTrue();
+    assertThat(CallOptions.DEFAULT.withWaitForReady().withoutWaitForReady().isWaitForReady())
+        .isFalse();
+  }
+
+  @Test
+  public void allWiths() {
+    assertThat(allSet.getAuthority()).isSameAs(sampleAuthority);
+    assertThat(allSet.getDeadline()).isSameAs(sampleDeadline);
+    assertThat(allSet.getCredentials()).isSameAs(sampleCreds);
+    assertThat(allSet.getCompressor()).isSameAs(sampleCompressor);
+    assertThat(allSet.getExecutor()).isSameAs(directExecutor());
+    assertThat(allSet.getOption(OPTION_1)).isSameAs("value1");
+    assertThat(allSet.getOption(OPTION_2)).isSameAs("value2");
+    assertThat(allSet.isWaitForReady()).isTrue();
+  }
+
+  @Test
+  public void noStrayModifications() {
+    assertThat(equal(allSet, allSet.withAuthority("blah").withAuthority(sampleAuthority)))
+        .isTrue();
+    assertThat(
+        equal(allSet,
+            allSet.withDeadline(Deadline.after(314, NANOSECONDS)).withDeadline(sampleDeadline)))
+        .isTrue();
+    assertThat(
+        equal(allSet,
+            allSet.withCallCredentials(mock(CallCredentials.class))
+            .withCallCredentials(sampleCreds)))
+        .isTrue();
+  }
+
+  @Test
+  public void mutation() {
+    Deadline deadline = Deadline.after(10, SECONDS);
+    CallOptions options1 = CallOptions.DEFAULT.withDeadline(deadline);
+    assertThat(CallOptions.DEFAULT.getDeadline()).isNull();
+    assertThat(deadline).isSameAs(options1.getDeadline());
+
+    CallOptions options2 = options1.withDeadline(null);
+    assertThat(deadline).isSameAs(options1.getDeadline());
+    assertThat(options2.getDeadline()).isNull();
+  }
+
+  @Test
+  public void mutateExecutor() {
+    Executor executor = directExecutor();
+    CallOptions options1 = CallOptions.DEFAULT.withExecutor(executor);
+    assertThat(CallOptions.DEFAULT.getExecutor()).isNull();
+    assertThat(executor).isSameAs(options1.getExecutor());
+
+    CallOptions options2 = options1.withExecutor(null);
+    assertThat(executor).isSameAs(options1.getExecutor());
+    assertThat(options2.getExecutor()).isNull();
+  }
+
+  @Test
+  public void withDeadlineAfter() {
+    Deadline actual = CallOptions.DEFAULT.withDeadlineAfter(1, MINUTES).getDeadline();
+    Deadline expected = Deadline.after(1, MINUTES);
+
+    assertAbout(deadline()).that(actual).isWithin(10, MILLISECONDS).of(expected);
+  }
+
+  @Test
+  public void toStringMatches_noDeadline_default() {
+    String actual = allSet
+        .withDeadline(null)
+        .withExecutor(new SerializingExecutor(directExecutor()))
+        .withCallCredentials(null)
+        .withMaxInboundMessageSize(44)
+        .withMaxOutboundMessageSize(55)
+        .toString();
+
+    assertThat(actual).contains("deadline=null");
+    assertThat(actual).contains("authority=authority");
+    assertThat(actual).contains("callCredentials=null");
+    assertThat(actual).contains("executor=class io.grpc.internal.SerializingExecutor");
+    assertThat(actual).contains("compressorName=compressor");
+    assertThat(actual).contains("customOptions=[[option1, value1], [option2, value2]]");
+    assertThat(actual).contains("waitForReady=true");
+    assertThat(actual).contains("maxInboundMessageSize=44");
+    assertThat(actual).contains("maxOutboundMessageSize=55");
+    assertThat(actual).contains("streamTracerFactories=[tracerFactory1, tracerFactory2]");
+  }
+
+  @Test
+  public void toStringMatches_noDeadline() {
+    String actual = CallOptions.DEFAULT.toString();
+    assertThat(actual).contains("deadline=null");
+  }
+
+  @Test
+  public void toStringMatches_withDeadline() {
+    assertThat(allSet.toString()).contains("1 ns from now");
+  }
+
+  @Test
+  public void withCustomOptionDefault() {
+    CallOptions opts = CallOptions.DEFAULT;
+    assertThat(opts.getOption(OPTION_1)).isEqualTo("default");
+  }
+
+  @Test
+  public void withCustomOption() {
+    CallOptions opts = CallOptions.DEFAULT.withOption(OPTION_1, "v1");
+    assertThat(opts.getOption(OPTION_1)).isEqualTo("v1");
+  }
+
+  @Test
+  public void withCustomOptionLastOneWins() {
+    CallOptions opts = CallOptions.DEFAULT.withOption(OPTION_1, "v1").withOption(OPTION_1, "v2");
+    assertThat(opts.getOption(OPTION_1)).isEqualTo("v2");
+  }
+
+  @Test
+  public void withMultipleCustomOption() {
+    CallOptions opts = CallOptions.DEFAULT.withOption(OPTION_1, "v1").withOption(OPTION_2, "v2");
+    assertThat(opts.getOption(OPTION_1)).isEqualTo("v1");
+    assertThat(opts.getOption(OPTION_2)).isEqualTo("v2");
+  }
+
+  @Test
+  public void withStreamTracerFactory() {
+    CallOptions opts1 = CallOptions.DEFAULT.withStreamTracerFactory(tracerFactory1);
+    CallOptions opts2 = opts1.withStreamTracerFactory(tracerFactory2);
+    CallOptions opts3 = opts2.withStreamTracerFactory(tracerFactory2);
+
+    assertThat(opts1.getStreamTracerFactories()).containsExactly(tracerFactory1);
+    assertThat(opts2.getStreamTracerFactories()).containsExactly(tracerFactory1, tracerFactory2)
+        .inOrder();
+    assertThat(opts3.getStreamTracerFactories())
+        .containsExactly(tracerFactory1, tracerFactory2, tracerFactory2).inOrder();
+
+    try {
+      CallOptions.DEFAULT.getStreamTracerFactories().add(tracerFactory1);
+      fail("Should have thrown. The list should be unmodifiable.");
+    } catch (UnsupportedOperationException e) {
+      // Expected
+    }
+
+    try {
+      opts2.getStreamTracerFactories().clear();
+      fail("Should have thrown. The list should be unmodifiable.");
+    } catch (UnsupportedOperationException e) {
+      // Expected
+    }
+  }
+
+  // Only used in noStrayModifications()
+  // TODO(carl-mastrangelo): consider making a CallOptionsSubject for Truth.
+  private static boolean equal(CallOptions o1, CallOptions o2) {
+    return Objects.equal(o1.getDeadline(), o2.getDeadline())
+        && Objects.equal(o1.getAuthority(), o2.getAuthority())
+        && Objects.equal(o1.getCredentials(), o2.getCredentials());
+  }
+
+  private static class FakeTicker extends Deadline.Ticker {
+    private long time;
+
+    @Override
+    public long read() {
+      return time;
+    }
+
+    public void reset(long time) {
+      this.time = time;
+    }
+
+    public void increment(long period, TimeUnit unit) {
+      if (period < 0) {
+        throw new IllegalArgumentException();
+      }
+      this.time += unit.toNanos(period);
+    }
+  }
+
+  private static class FakeTracerFactory extends ClientStreamTracer.Factory {
+    final String name;
+
+    FakeTracerFactory(String name) {
+      this.name = name;
+    }
+
+    @Override
+    public ClientStreamTracer newClientStreamTracer(CallOptions callOptions, Metadata headers) {
+      return new ClientStreamTracer() {};
+    }
+
+    @Override
+    public String toString() {
+      return name;
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/ClientInterceptorsTest.java b/core/src/test/java/io/grpc/ClientInterceptorsTest.java
new file mode 100644
index 0000000..a71b6c7
--- /dev/null
+++ b/core/src/test/java/io/grpc/ClientInterceptorsTest.java
@@ -0,0 +1,480 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import io.grpc.ClientInterceptors.CheckedForwardingClientCall;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
+import io.grpc.testing.TestMethodDescriptors;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link ClientInterceptors}. */
+@RunWith(JUnit4.class)
+public class ClientInterceptorsTest {
+
+  @Mock
+  private Channel channel;
+
+  private BaseClientCall call = new BaseClientCall();
+
+  private final MethodDescriptor<Void, Void> method = TestMethodDescriptors.voidMethod();
+
+  /**
+   * Sets up mocks.
+   */
+  @Before public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    when(channel.newCall(
+        Mockito.<MethodDescriptor<String, Integer>>any(), any(CallOptions.class)))
+        .thenReturn(call);
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void npeForNullChannel() {
+    ClientInterceptors.intercept(null, Arrays.<ClientInterceptor>asList());
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void npeForNullInterceptorList() {
+    ClientInterceptors.intercept(channel, (List<ClientInterceptor>) null);
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void npeForNullInterceptor() {
+    ClientInterceptors.intercept(channel, (ClientInterceptor) null);
+  }
+
+  @Test
+  public void noop() {
+    assertSame(channel, ClientInterceptors.intercept(channel, Arrays.<ClientInterceptor>asList()));
+  }
+
+  @Test
+  public void channelAndInterceptorCalled() {
+    ClientInterceptor interceptor =
+        mock(ClientInterceptor.class, delegatesTo(new NoopInterceptor()));
+    Channel intercepted = ClientInterceptors.intercept(channel, interceptor);
+    CallOptions callOptions = CallOptions.DEFAULT;
+    // First call
+    assertSame(call, intercepted.newCall(method, callOptions));
+    verify(channel).newCall(same(method), same(callOptions));
+    verify(interceptor).interceptCall(same(method), same(callOptions), Mockito.<Channel>any());
+    verifyNoMoreInteractions(channel, interceptor);
+    // Second call
+    assertSame(call, intercepted.newCall(method, callOptions));
+    verify(channel, times(2)).newCall(same(method), same(callOptions));
+    verify(interceptor, times(2))
+        .interceptCall(same(method), same(callOptions), Mockito.<Channel>any());
+    verifyNoMoreInteractions(channel, interceptor);
+  }
+
+  @Test
+  public void callNextTwice() {
+    ClientInterceptor interceptor = new ClientInterceptor() {
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+          MethodDescriptor<ReqT, RespT> method,
+          CallOptions callOptions,
+          Channel next) {
+        // Calling next twice is permitted, although should only rarely be useful.
+        assertSame(call, next.newCall(method, callOptions));
+        return next.newCall(method, callOptions);
+      }
+    };
+    Channel intercepted = ClientInterceptors.intercept(channel, interceptor);
+    assertSame(call, intercepted.newCall(method, CallOptions.DEFAULT));
+    verify(channel, times(2)).newCall(same(method), same(CallOptions.DEFAULT));
+    verifyNoMoreInteractions(channel);
+  }
+
+  @Test
+  public void ordered() {
+    final List<String> order = new ArrayList<>();
+    channel = new Channel() {
+      @SuppressWarnings("unchecked")
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> newCall(
+          MethodDescriptor<ReqT, RespT> method, CallOptions callOptions) {
+        order.add("channel");
+        return (ClientCall<ReqT, RespT>) call;
+      }
+
+      @Override
+      public String authority() {
+        return null;
+      }
+    };
+    ClientInterceptor interceptor1 = new ClientInterceptor() {
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+          MethodDescriptor<ReqT, RespT> method,
+          CallOptions callOptions,
+          Channel next) {
+        order.add("i1");
+        return next.newCall(method, callOptions);
+      }
+    };
+    ClientInterceptor interceptor2 = new ClientInterceptor() {
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+          MethodDescriptor<ReqT, RespT> method,
+          CallOptions callOptions,
+          Channel next) {
+        order.add("i2");
+        return next.newCall(method, callOptions);
+      }
+    };
+    Channel intercepted = ClientInterceptors.intercept(channel, interceptor1, interceptor2);
+    assertSame(call, intercepted.newCall(method, CallOptions.DEFAULT));
+    assertEquals(Arrays.asList("i2", "i1", "channel"), order);
+  }
+
+  @Test
+  public void orderedForward() {
+    final List<String> order = new ArrayList<>();
+    channel = new Channel() {
+      @SuppressWarnings("unchecked")
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> newCall(
+          MethodDescriptor<ReqT, RespT> method, CallOptions callOptions) {
+        order.add("channel");
+        return (ClientCall<ReqT, RespT>) call;
+      }
+
+      @Override
+      public String authority() {
+        return null;
+      }
+    };
+    ClientInterceptor interceptor1 = new ClientInterceptor() {
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+          MethodDescriptor<ReqT, RespT> method,
+          CallOptions callOptions,
+          Channel next) {
+        order.add("i1");
+        return next.newCall(method, callOptions);
+      }
+    };
+    ClientInterceptor interceptor2 = new ClientInterceptor() {
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+          MethodDescriptor<ReqT, RespT> method,
+          CallOptions callOptions,
+          Channel next) {
+        order.add("i2");
+        return next.newCall(method, callOptions);
+      }
+    };
+    Channel intercepted = ClientInterceptors.interceptForward(channel, interceptor1, interceptor2);
+    assertSame(call, intercepted.newCall(method, CallOptions.DEFAULT));
+    assertEquals(Arrays.asList("i1", "i2", "channel"), order);
+  }
+
+  @Test
+  public void callOptions() {
+    final CallOptions initialCallOptions = CallOptions.DEFAULT.withDeadlineAfter(100, NANOSECONDS);
+    final CallOptions newCallOptions = initialCallOptions.withDeadlineAfter(300, NANOSECONDS);
+    assertNotSame(initialCallOptions, newCallOptions);
+    ClientInterceptor interceptor =
+        mock(ClientInterceptor.class, delegatesTo(new ClientInterceptor() {
+          @Override
+          public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+              MethodDescriptor<ReqT, RespT> method,
+              CallOptions callOptions,
+              Channel next) {
+            return next.newCall(method, newCallOptions);
+          }
+        }));
+    Channel intercepted = ClientInterceptors.intercept(channel, interceptor);
+    intercepted.newCall(method, initialCallOptions);
+    verify(interceptor).interceptCall(
+        same(method), same(initialCallOptions), Mockito.<Channel>any());
+    verify(channel).newCall(same(method), same(newCallOptions));
+  }
+
+  @Test
+  public void addOutboundHeaders() {
+    final Metadata.Key<String> credKey = Metadata.Key.of("Cred", Metadata.ASCII_STRING_MARSHALLER);
+    ClientInterceptor interceptor = new ClientInterceptor() {
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+          MethodDescriptor<ReqT, RespT> method,
+          CallOptions callOptions,
+          Channel next) {
+        ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);
+        return new SimpleForwardingClientCall<ReqT, RespT>(call) {
+          @Override
+          public void start(ClientCall.Listener<RespT> responseListener, Metadata headers) {
+            headers.put(credKey, "abcd");
+            super.start(responseListener, headers);
+          }
+        };
+      }
+    };
+    Channel intercepted = ClientInterceptors.intercept(channel, interceptor);
+    @SuppressWarnings("unchecked")
+    ClientCall.Listener<Void> listener = mock(ClientCall.Listener.class);
+    ClientCall<Void, Void> interceptedCall = intercepted.newCall(method, CallOptions.DEFAULT);
+    // start() on the intercepted call will eventually reach the call created by the real channel
+    interceptedCall.start(listener, new Metadata());
+    // The headers passed to the real channel call will contain the information inserted by the
+    // interceptor.
+    assertSame(listener, call.listener);
+    assertEquals("abcd", call.headers.get(credKey));
+  }
+
+  @Test
+  public void examineInboundHeaders() {
+    final List<Metadata> examinedHeaders = new ArrayList<>();
+    ClientInterceptor interceptor = new ClientInterceptor() {
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+          MethodDescriptor<ReqT, RespT> method,
+          CallOptions callOptions,
+          Channel next) {
+        ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);
+        return new SimpleForwardingClientCall<ReqT, RespT>(call) {
+          @Override
+          public void start(ClientCall.Listener<RespT> responseListener, Metadata headers) {
+            super.start(new SimpleForwardingClientCallListener<RespT>(responseListener) {
+              @Override
+              public void onHeaders(Metadata headers) {
+                examinedHeaders.add(headers);
+                super.onHeaders(headers);
+              }
+            }, headers);
+          }
+        };
+      }
+    };
+    Channel intercepted = ClientInterceptors.intercept(channel, interceptor);
+    @SuppressWarnings("unchecked")
+    ClientCall.Listener<Void> listener = mock(ClientCall.Listener.class);
+    ClientCall<Void, Void> interceptedCall = intercepted.newCall(method, CallOptions.DEFAULT);
+    interceptedCall.start(listener, new Metadata());
+    // Capture the underlying call listener that will receive headers from the transport.
+
+    Metadata inboundHeaders = new Metadata();
+    // Simulate that a headers arrives on the underlying call listener.
+    call.listener.onHeaders(inboundHeaders);
+    assertThat(examinedHeaders).contains(inboundHeaders);
+  }
+
+  @Test
+  public void normalCall() {
+    ClientInterceptor interceptor = new ClientInterceptor() {
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+          MethodDescriptor<ReqT, RespT> method,
+          CallOptions callOptions,
+          Channel next) {
+        ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);
+        return new SimpleForwardingClientCall<ReqT, RespT>(call) { };
+      }
+    };
+    Channel intercepted = ClientInterceptors.intercept(channel, interceptor);
+    ClientCall<Void, Void> interceptedCall = intercepted.newCall(method, CallOptions.DEFAULT);
+    assertNotSame(call, interceptedCall);
+    @SuppressWarnings("unchecked")
+    ClientCall.Listener<Void> listener = mock(ClientCall.Listener.class);
+    Metadata headers = new Metadata();
+    interceptedCall.start(listener, headers);
+    assertSame(listener, call.listener);
+    assertSame(headers, call.headers);
+    interceptedCall.sendMessage(null /*request*/);
+    assertThat(call.messages).containsExactly((Void) null /*request*/);
+    interceptedCall.halfClose();
+    assertTrue(call.halfClosed);
+    interceptedCall.request(1);
+    assertThat(call.requests).containsExactly(1);
+  }
+
+  @Test
+  public void exceptionInStart() {
+    final Exception error = new Exception("emulated error");
+    ClientInterceptor interceptor = new ClientInterceptor() {
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+          MethodDescriptor<ReqT, RespT> method,
+          CallOptions callOptions,
+          Channel next) {
+        ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);
+        return new CheckedForwardingClientCall<ReqT, RespT>(call) {
+          @Override
+          protected void checkedStart(ClientCall.Listener<RespT> responseListener, Metadata headers)
+              throws Exception {
+            throw error;
+            // delegate().start will not be called
+          }
+        };
+      }
+    };
+    Channel intercepted = ClientInterceptors.intercept(channel, interceptor);
+    @SuppressWarnings("unchecked")
+    ClientCall.Listener<Void> listener = mock(ClientCall.Listener.class);
+    ClientCall<Void, Void> interceptedCall = intercepted.newCall(method, CallOptions.DEFAULT);
+    assertNotSame(call, interceptedCall);
+    interceptedCall.start(listener, new Metadata());
+    interceptedCall.sendMessage(null /*request*/);
+    interceptedCall.halfClose();
+    interceptedCall.request(1);
+    call.done = true;
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
+    verify(listener).onClose(captor.capture(), any(Metadata.class));
+    assertSame(error, captor.getValue().getCause());
+
+    // Make sure nothing bad happens after the exception.
+    ClientCall<?, ?> noop = ((CheckedForwardingClientCall<?, ?>)interceptedCall).delegate();
+    // Should not throw, even on bad input
+    noop.cancel("Cancel for test", null);
+    noop.start(null, null);
+    noop.request(-1);
+    noop.halfClose();
+    noop.sendMessage(null);
+    assertFalse(noop.isReady());
+  }
+
+  @Test
+  public void authorityIsDelegated() {
+    ClientInterceptor interceptor = new ClientInterceptor() {
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+          MethodDescriptor<ReqT, RespT> method,
+          CallOptions callOptions,
+          Channel next) {
+        return next.newCall(method, callOptions);
+      }
+    };
+
+    when(channel.authority()).thenReturn("auth");
+    Channel intercepted = ClientInterceptors.intercept(channel, interceptor);
+    assertEquals("auth", intercepted.authority());
+  }
+
+  @Test
+  public void customOptionAccessible() {
+    CallOptions.Key<String> customOption = CallOptions.Key.create("custom");
+    CallOptions callOptions = CallOptions.DEFAULT.withOption(customOption, "value");
+    ArgumentCaptor<CallOptions> passedOptions = ArgumentCaptor.forClass(CallOptions.class);
+    ClientInterceptor interceptor =
+        mock(ClientInterceptor.class, delegatesTo(new NoopInterceptor()));
+
+    Channel intercepted = ClientInterceptors.intercept(channel, interceptor);
+
+    assertSame(call, intercepted.newCall(method, callOptions));
+    verify(channel).newCall(same(method), same(callOptions));
+
+    verify(interceptor).interceptCall(same(method), passedOptions.capture(), isA(Channel.class));
+    assertSame("value", passedOptions.getValue().getOption(customOption));
+  }
+
+  private static class NoopInterceptor implements ClientInterceptor {
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
+        CallOptions callOptions, Channel next) {
+      return next.newCall(method, callOptions);
+    }
+  }
+
+  private static class BaseClientCall extends ClientCall<String, Integer> {
+    private boolean started;
+    private boolean done;
+    private ClientCall.Listener<Integer> listener;
+    private Metadata headers;
+    private List<Integer> requests = new ArrayList<>();
+    private List<String> messages = new ArrayList<>();
+    private boolean halfClosed;
+    private Throwable cancelCause;
+    private String cancelMessage;
+
+    @Override
+    public void start(ClientCall.Listener<Integer> listener, Metadata headers) {
+      checkNotDone();
+      started = true;
+      this.listener = listener;
+      this.headers = headers;
+    }
+
+    @Override
+    public void request(int numMessages) {
+      checkNotDone();
+      checkStarted();
+      requests.add(numMessages);
+    }
+
+    @Override
+    public void cancel(String message, Throwable cause) {
+      checkNotDone();
+      this.cancelMessage = message;
+      this.cancelCause = cause;
+    }
+
+    @Override
+    public void halfClose() {
+      checkNotDone();
+      checkStarted();
+      this.halfClosed = true;
+    }
+
+    @Override
+    public void sendMessage(String message) {
+      checkNotDone();
+      checkStarted();
+      messages.add(message);
+    }
+
+    private void checkNotDone() {
+      if (done) {
+        throw new IllegalStateException("no more methods should be called");
+      }
+    }
+
+    private void checkStarted() {
+      if (!started) {
+        throw new IllegalStateException("should have called start");
+      }
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/ConnectivityStateInfoTest.java b/core/src/test/java/io/grpc/ConnectivityStateInfoTest.java
new file mode 100644
index 0000000..4ba755e
--- /dev/null
+++ b/core/src/test/java/io/grpc/ConnectivityStateInfoTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static io.grpc.ConnectivityState.CONNECTING;
+import static io.grpc.ConnectivityState.IDLE;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotSame;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ConnectivityStateInfo}. */
+@RunWith(JUnit4.class)
+public class ConnectivityStateInfoTest {
+  @Test
+  public void forNonError() {
+    ConnectivityStateInfo info = ConnectivityStateInfo.forNonError(IDLE);
+    assertEquals(IDLE, info.getState());
+    assertEquals(Status.OK, info.getStatus());
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void forNonErrorInvalid() {
+    ConnectivityStateInfo.forNonError(TRANSIENT_FAILURE);
+  }
+
+  @Test
+  public void forTransientFailure() {
+    ConnectivityStateInfo info = ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE);
+    assertEquals(TRANSIENT_FAILURE, info.getState());
+    assertEquals(Status.UNAVAILABLE, info.getStatus());
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void forTransientFailureInvalid() {
+    ConnectivityStateInfo.forTransientFailure(Status.OK);
+  }
+
+  @Test
+  public void equality() {
+    ConnectivityStateInfo info1 = ConnectivityStateInfo.forNonError(IDLE);
+    ConnectivityStateInfo info2 = ConnectivityStateInfo.forNonError(CONNECTING);
+    ConnectivityStateInfo info3 = ConnectivityStateInfo.forNonError(IDLE);
+    ConnectivityStateInfo info4 = ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE);
+    ConnectivityStateInfo info5 = ConnectivityStateInfo.forTransientFailure(Status.INTERNAL);
+    ConnectivityStateInfo info6 = ConnectivityStateInfo.forTransientFailure(Status.INTERNAL);
+
+    assertEquals(info1, info3);
+    assertNotSame(info1, info3);
+    assertEquals(info1.hashCode(), info3.hashCode());
+    assertEquals(info5, info6);
+    assertEquals(info5.hashCode(), info6.hashCode());
+    assertNotSame(info5, info6);
+
+    assertNotEquals(info1, info2);
+    assertNotEquals(info1, info4);
+    assertNotEquals(info4, info6);
+
+    assertFalse(info1.equals(null));
+    // Extra cast to avoid ErrorProne EqualsIncompatibleType failure
+    assertFalse(((Object) info1).equals(this));
+  }
+}
diff --git a/core/src/test/java/io/grpc/ContextsTest.java b/core/src/test/java/io/grpc/ContextsTest.java
new file mode 100644
index 0000000..4a4ae35
--- /dev/null
+++ b/core/src/test/java/io/grpc/ContextsTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static io.grpc.Contexts.interceptCall;
+import static io.grpc.Contexts.statusFromCancelled;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import io.grpc.internal.FakeClock;
+import io.grpc.internal.NoopServerCall;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link Contexts}.
+ */
+@RunWith(JUnit4.class)
+public class ContextsTest {
+  private static Context.Key<Object> contextKey = Context.key("key");
+  /** For use in comparing context by reference. */
+  private Context uniqueContext = Context.ROOT.withValue(contextKey, new Object());
+  @SuppressWarnings("unchecked")
+  private ServerCall<Object, Object> call = new NoopServerCall<Object, Object>();
+  private Metadata headers = new Metadata();
+
+  @Test
+  public void interceptCall_basic() {
+    Context origContext = Context.current();
+    final Object message = new Object();
+    final List<Integer> methodCalls = new ArrayList<>();
+    final ServerCall.Listener<Object> listener = new ServerCall.Listener<Object>() {
+      @Override public void onMessage(Object messageIn) {
+        assertSame(message, messageIn);
+        assertSame(uniqueContext, Context.current());
+        methodCalls.add(1);
+      }
+
+      @Override public void onHalfClose() {
+        assertSame(uniqueContext, Context.current());
+        methodCalls.add(2);
+      }
+
+      @Override public void onCancel() {
+        assertSame(uniqueContext, Context.current());
+        methodCalls.add(3);
+      }
+
+      @Override public void onComplete() {
+        assertSame(uniqueContext, Context.current());
+        methodCalls.add(4);
+      }
+
+      @Override public void onReady() {
+        assertSame(uniqueContext, Context.current());
+        methodCalls.add(5);
+      }
+    };
+    ServerCall.Listener<Object> wrapped = interceptCall(uniqueContext, call, headers,
+        new ServerCallHandler<Object, Object>() {
+          @Override
+          public ServerCall.Listener<Object> startCall(
+              ServerCall<Object, Object> call, Metadata headers) {
+            assertSame(ContextsTest.this.call, call);
+            assertSame(ContextsTest.this.headers, headers);
+            assertSame(uniqueContext, Context.current());
+            return listener;
+          }
+        });
+    assertSame(origContext, Context.current());
+
+    wrapped.onMessage(message);
+    wrapped.onHalfClose();
+    wrapped.onCancel();
+    wrapped.onComplete();
+    wrapped.onReady();
+    assertEquals(Arrays.asList(1, 2, 3, 4, 5), methodCalls);
+    assertSame(origContext, Context.current());
+  }
+
+  @Test
+  public void interceptCall_restoresIfNextThrows() {
+    Context origContext = Context.current();
+    try {
+      interceptCall(uniqueContext, call, headers, new ServerCallHandler<Object, Object>() {
+        @Override
+        public ServerCall.Listener<Object> startCall(
+            ServerCall<Object, Object> call, Metadata headers) {
+          throw new RuntimeException();
+        }
+      });
+      fail("Expected exception");
+    } catch (RuntimeException expected) {
+    }
+    assertSame(origContext, Context.current());
+  }
+
+  @Test
+  public void interceptCall_restoresIfListenerThrows() {
+    Context origContext = Context.current();
+    final ServerCall.Listener<Object> listener = new ServerCall.Listener<Object>() {
+      @Override public void onMessage(Object messageIn) {
+        throw new RuntimeException();
+      }
+
+      @Override public void onHalfClose() {
+        throw new RuntimeException();
+      }
+
+      @Override public void onCancel() {
+        throw new RuntimeException();
+      }
+
+      @Override public void onComplete() {
+        throw new RuntimeException();
+      }
+
+      @Override public void onReady() {
+        throw new RuntimeException();
+      }
+    };
+    ServerCall.Listener<Object> wrapped = interceptCall(uniqueContext, call, headers,
+        new ServerCallHandler<Object, Object>() {
+          @Override
+          public ServerCall.Listener<Object> startCall(
+              ServerCall<Object, Object> call, Metadata headers) {
+            return listener;
+          }
+        });
+
+    try {
+      wrapped.onMessage(new Object());
+      fail("Exception expected");
+    } catch (RuntimeException expected) {
+    }
+    try {
+      wrapped.onHalfClose();
+      fail("Exception expected");
+    } catch (RuntimeException expected) {
+    }
+    try {
+      wrapped.onCancel();
+      fail("Exception expected");
+    } catch (RuntimeException expected) {
+    }
+    try {
+      wrapped.onComplete();
+      fail("Exception expected");
+    } catch (RuntimeException expected) {
+    }
+    try {
+      wrapped.onReady();
+      fail("Exception expected");
+    } catch (RuntimeException expected) {
+    }
+    assertSame(origContext, Context.current());
+  }
+
+  @Test
+  public void statusFromCancelled_returnNullIfCtxNotCancelled() {
+    Context context = Context.current();
+    assertFalse(context.isCancelled());
+    assertNull(statusFromCancelled(context));
+  }
+
+  @Test
+  public void statusFromCancelled_returnStatusAsSetOnCtx() {
+    Context.CancellableContext cancellableContext = Context.current().withCancellation();
+    cancellableContext.cancel(Status.DEADLINE_EXCEEDED.withDescription("foo bar").asException());
+    Status status = statusFromCancelled(cancellableContext);
+    assertNotNull(status);
+    assertEquals(Status.Code.DEADLINE_EXCEEDED, status.getCode());
+    assertEquals("foo bar", status.getDescription());
+  }
+
+  @Test
+  public void statusFromCancelled_shouldReturnStatusWithCauseAttached() {
+    Context.CancellableContext cancellableContext = Context.current().withCancellation();
+    Throwable t = new Throwable();
+    cancellableContext.cancel(t);
+    Status status = statusFromCancelled(cancellableContext);
+    assertNotNull(status);
+    assertEquals(Status.Code.CANCELLED, status.getCode());
+    assertSame(t, status.getCause());
+  }
+
+  @Test
+  public void statusFromCancelled_TimeoutExceptionShouldMapToDeadlineExceeded() {
+    FakeClock fakeClock = new FakeClock();
+    Context.CancellableContext cancellableContext = Context.current()
+        .withDeadlineAfter(100, TimeUnit.NANOSECONDS, fakeClock.getScheduledExecutorService());
+    fakeClock.forwardTime(System.nanoTime(), TimeUnit.NANOSECONDS);
+    fakeClock.forwardNanos(100);
+
+    assertTrue(cancellableContext.isCancelled());
+    assertThat(cancellableContext.cancellationCause(), instanceOf(TimeoutException.class));
+
+    Status status = statusFromCancelled(cancellableContext);
+    assertNotNull(status);
+    assertEquals(Status.Code.DEADLINE_EXCEEDED, status.getCode());
+    assertEquals("context timed out", status.getDescription());
+  }
+
+  @Test
+  public void statusFromCancelled_returnCancelledIfCauseIsNull() {
+    Context.CancellableContext cancellableContext = Context.current().withCancellation();
+    cancellableContext.cancel(null);
+    assertTrue(cancellableContext.isCancelled());
+    Status status = statusFromCancelled(cancellableContext);
+    assertNotNull(status);
+    assertEquals(Status.Code.CANCELLED, status.getCode());
+  }
+
+  /** This is a whitebox test, to verify a special case of the implementation. */
+  @Test
+  public void statusFromCancelled_StatusUnknownShouldWork() {
+    Context.CancellableContext cancellableContext = Context.current().withCancellation();
+    Exception e = Status.UNKNOWN.asException();
+    cancellableContext.cancel(e);
+    assertTrue(cancellableContext.isCancelled());
+
+    Status status = statusFromCancelled(cancellableContext);
+    assertNotNull(status);
+    assertEquals(Status.Code.UNKNOWN, status.getCode());
+    assertSame(e, status.getCause());
+  }
+
+  @Test
+  public void statusFromCancelled_shouldThrowIfCtxIsNull() {
+    try {
+      statusFromCancelled(null);
+      fail("NPE expected");
+    } catch (NullPointerException npe) {
+      assertEquals("context must not be null", npe.getMessage());
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/DecompressorRegistryTest.java b/core/src/test/java/io/grpc/DecompressorRegistryTest.java
new file mode 100644
index 0000000..0430eb2
--- /dev/null
+++ b/core/src/test/java/io/grpc/DecompressorRegistryTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link DecompressorRegistry}.
+ */
+@RunWith(JUnit4.class)
+public class DecompressorRegistryTest {
+
+  private final Dummy dummyDecompressor = new Dummy();
+  private DecompressorRegistry registry = DecompressorRegistry.emptyInstance();
+
+  @Test
+  public void lookupDecompressor_checkDefaultMessageEncodingsExist() {
+    // Explicitly put the names in, rather than link against MessageEncoding
+    assertNotNull("Expected identity to be registered",
+        DecompressorRegistry.getDefaultInstance().lookupDecompressor("identity"));
+    assertNotNull("Expected gzip to be registered",
+        DecompressorRegistry.getDefaultInstance().lookupDecompressor("gzip"));
+  }
+
+  @Test
+  public void getKnownMessageEncodings_checkDefaultMessageEncodingsExist() {
+    Set<String> knownEncodings = new HashSet<String>();
+    knownEncodings.add("identity");
+    knownEncodings.add("gzip");
+
+    assertEquals(knownEncodings,
+        DecompressorRegistry.getDefaultInstance().getKnownMessageEncodings());
+  }
+
+  /*
+   * This test will likely change once encoders are advertised
+   */
+  @Test
+  public void getAdvertisedMessageEncodings_noEncodingsAdvertised() {
+    assertTrue(registry.getAdvertisedMessageEncodings().isEmpty());
+  }
+
+  @Test
+  public void registerDecompressor_advertisedDecompressor() {
+    registry = registry.with(dummyDecompressor, true);
+
+    assertTrue(registry.getAdvertisedMessageEncodings()
+        .contains(dummyDecompressor.getMessageEncoding()));
+  }
+
+  @Test
+  public void registerDecompressor_nonadvertisedDecompressor() {
+    registry = registry.with(dummyDecompressor, false);
+
+    assertFalse(registry.getAdvertisedMessageEncodings()
+        .contains(dummyDecompressor.getMessageEncoding()));
+  }
+
+  private static final class Dummy implements Decompressor {
+    @Override
+    public String getMessageEncoding() {
+      return "dummy";
+    }
+
+    @Override
+    public InputStream decompress(InputStream is) throws IOException {
+      return is;
+    }
+  }
+}
+
diff --git a/core/src/test/java/io/grpc/ForwardingChannelBuilderTest.java b/core/src/test/java/io/grpc/ForwardingChannelBuilderTest.java
new file mode 100644
index 0000000..1c5e1b3
--- /dev/null
+++ b/core/src/test/java/io/grpc/ForwardingChannelBuilderTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import com.google.common.base.Defaults;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link ForwardingChannelBuilder}.
+ */
+@RunWith(JUnit4.class)
+public class ForwardingChannelBuilderTest {
+  private final ManagedChannelBuilder<?> mockDelegate = mock(ManagedChannelBuilder.class);
+
+  private final ForwardingChannelBuilder<?> testChannelBuilder = new TestBuilder();
+
+  private final class TestBuilder extends ForwardingChannelBuilder<TestBuilder> {
+    @Override
+    protected ManagedChannelBuilder<?> delegate() {
+      return mockDelegate;
+    }
+  }
+
+  @Test
+  public void allMethodsForwarded() throws Exception {
+    ForwardingTestUtil.testMethodsForwarded(
+        ManagedChannelBuilder.class,
+        mockDelegate,
+        testChannelBuilder,
+        Collections.<Method>emptyList());
+  }
+
+  @Test
+  public void allBuilderMethodsReturnThis() throws Exception {
+    for (Method method : ManagedChannelBuilder.class.getDeclaredMethods()) {
+      if (Modifier.isStatic(method.getModifiers()) || Modifier.isPrivate(method.getModifiers())) {
+        continue;
+      }
+      if (method.getName().equals("build")) {
+        continue;
+      }
+      Class<?>[] argTypes = method.getParameterTypes();
+      Object[] args = new Object[argTypes.length];
+      for (int i = 0; i < argTypes.length; i++) {
+        args[i] = Defaults.defaultValue(argTypes[i]);
+      }
+
+      Object returnedValue = method.invoke(testChannelBuilder, args);
+
+      assertThat(returnedValue).isSameAs(testChannelBuilder);
+    }
+  }
+
+  @Test
+  public void buildReturnsDelegateBuildByDefualt() {
+    ManagedChannel mockChannel = mock(ManagedChannel.class);
+    doReturn(mockChannel).when(mockDelegate).build();
+
+    assertThat(testChannelBuilder.build()).isSameAs(mockChannel);
+  }
+}
diff --git a/core/src/test/java/io/grpc/ForwardingServerCallListenerTest.java b/core/src/test/java/io/grpc/ForwardingServerCallListenerTest.java
new file mode 100644
index 0000000..c252aad
--- /dev/null
+++ b/core/src/test/java/io/grpc/ForwardingServerCallListenerTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static org.mockito.Mockito.verify;
+
+import io.grpc.ForwardingServerCallListener.SimpleForwardingServerCallListener;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link ForwardingServerCallListener}.
+ */
+@RunWith(JUnit4.class)
+public class ForwardingServerCallListenerTest {
+
+  @Mock private ServerCall.Listener<Integer> serverCallListener;
+  private ForwardingServerCallListener<Integer> forwarder;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    forwarder = new SimpleForwardingServerCallListener<Integer>(serverCallListener) {};
+  }
+
+  @Test
+  public void allMethodsForwarded() throws Exception {
+    ForwardingTestUtil.testMethodsForwarded(
+        ServerCall.Listener.class, serverCallListener, forwarder, Collections.<Method>emptyList());
+  }
+
+  @Test
+  public void onMessage() {
+    forwarder.onMessage(12345);
+
+    verify(serverCallListener).onMessage(12345);
+  }
+}
+
diff --git a/core/src/test/java/io/grpc/ForwardingTestUtil.java b/core/src/test/java/io/grpc/ForwardingTestUtil.java
new file mode 100644
index 0000000..ce9ca5e
--- /dev/null
+++ b/core/src/test/java/io/grpc/ForwardingTestUtil.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static junit.framework.TestCase.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mockingDetails;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.base.Defaults;
+import com.google.common.base.MoreObjects;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Collection;
+
+/**
+ * A util class to help test forwarding classes.
+ */
+public final class ForwardingTestUtil {
+  /**
+   * Use reflection to perform a basic sanity test. The forwarding class should forward all public
+   * methods to the delegate, except for those in skippedMethods.
+   * This does NOT verify that arguments or return values are forwarded properly. It only alerts
+   * the developer if a forward method is missing.
+   *
+   * @param delegateClass The class whose methods should be forwarded.
+   * @param mockDelegate The mockito mock of the delegate class.
+   * @param forwarder The forwarder object that forwards to the mockDelegate.
+   * @param skippedMethods A collection of methods that are skipped by the test.
+   */
+  public static <T> void testMethodsForwarded(
+      Class<T> delegateClass,
+      T mockDelegate,
+      T forwarder,
+      Collection<Method> skippedMethods) throws Exception {
+    assertTrue(mockingDetails(mockDelegate).isMock());
+    assertFalse(mockingDetails(forwarder).isMock());
+
+    for (Method method : delegateClass.getDeclaredMethods()) {
+      if (Modifier.isStatic(method.getModifiers())
+          || Modifier.isPrivate(method.getModifiers())
+          || skippedMethods.contains(method)) {
+        continue;
+      }
+      Class<?>[] argTypes = method.getParameterTypes();
+      Object[] args = new Object[argTypes.length];
+      for (int i = 0; i < argTypes.length; i++) {
+        args[i] = Defaults.defaultValue(argTypes[i]);
+      }
+      method.invoke(forwarder, args);
+      try {
+        method.invoke(verify(mockDelegate), args);
+      } catch (InvocationTargetException e) {
+        throw new AssertionError(String.format("Method was not forwarded: %s", method));
+      }
+    }
+
+    boolean skipToString = false;
+    for (Method method : skippedMethods) {
+      if (method.getName().equals("toString")) {
+        skipToString = true;
+        break;
+      }
+    }
+    if (!skipToString) {
+      String actual = forwarder.toString();
+      String expected =
+          MoreObjects.toStringHelper(forwarder).add("delegate", mockDelegate).toString();
+      assertEquals("Method toString() was not forwarded properly", expected, actual);
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/IntegerMarshaller.java b/core/src/test/java/io/grpc/IntegerMarshaller.java
new file mode 100644
index 0000000..44b3786
--- /dev/null
+++ b/core/src/test/java/io/grpc/IntegerMarshaller.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.io.InputStream;
+
+/** Marshalls decimal-encoded integers. */
+public class IntegerMarshaller implements MethodDescriptor.Marshaller<Integer> {
+  public static final IntegerMarshaller INSTANCE = new IntegerMarshaller();
+
+  @Override
+  public InputStream stream(Integer value) {
+    return StringMarshaller.INSTANCE.stream(value.toString());
+  }
+
+  @Override
+  public Integer parse(InputStream stream) {
+    return Integer.valueOf(StringMarshaller.INSTANCE.parse(stream));
+  }
+}
diff --git a/core/src/test/java/io/grpc/InternalChannelzTest.java b/core/src/test/java/io/grpc/InternalChannelzTest.java
new file mode 100644
index 0000000..4607f68
--- /dev/null
+++ b/core/src/test/java/io/grpc/InternalChannelzTest.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.InternalChannelz.id;
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import io.grpc.InternalChannelz.ChannelStats;
+import io.grpc.InternalChannelz.RootChannelList;
+import io.grpc.InternalChannelz.ServerList;
+import io.grpc.InternalChannelz.ServerSocketsList;
+import io.grpc.InternalChannelz.ServerStats;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalChannelz.Tls;
+import java.security.cert.Certificate;
+import javax.net.ssl.SSLSession;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class InternalChannelzTest {
+
+  private final InternalChannelz channelz = new InternalChannelz();
+
+  @Test
+  public void getRootChannels_empty() {
+    RootChannelList rootChannels = channelz.getRootChannels(/*fromId=*/ 0, /*maxPageSize=*/ 1);
+    assertTrue(rootChannels.end);
+    assertThat(rootChannels.channels).isEmpty();
+  }
+
+  @Test
+  public void getRootChannels_onePage() {
+    InternalInstrumented<ChannelStats> root1 = create();
+    channelz.addRootChannel(root1);
+    RootChannelList page = channelz.getRootChannels(/*fromId=*/ 0, /*maxPageSize=*/ 1);
+    assertTrue(page.end);
+    assertThat(page.channels).containsExactly(root1);
+  }
+
+  @Test
+  public void getRootChannels_onePage_multi() {
+    InternalInstrumented<ChannelStats> root1 = create();
+    InternalInstrumented<ChannelStats> root2 = create();
+    channelz.addRootChannel(root1);
+    channelz.addRootChannel(root2);
+    RootChannelList page = channelz.getRootChannels(/*fromId=*/ 0, /*maxPageSize=*/ 2);
+    assertTrue(page.end);
+    assertThat(page.channels).containsExactly(root1, root2);
+  }
+
+  @Test
+  public void getRootChannels_paginate() {
+    InternalInstrumented<ChannelStats> root1 = create();
+    InternalInstrumented<ChannelStats> root2 = create();
+    channelz.addRootChannel(root1);
+    channelz.addRootChannel(root2);
+    RootChannelList page1 = channelz.getRootChannels(/*fromId=*/ 0, /*maxPageSize=*/ 1);
+    assertFalse(page1.end);
+    assertThat(page1.channels).containsExactly(root1);
+    RootChannelList page2
+        = channelz.getRootChannels(/*fromId=*/ id(root1) + 1, /*maxPageSize=*/ 1);
+    assertTrue(page2.end);
+    assertThat(page2.channels).containsExactly(root2);
+  }
+
+  @Test
+  public void getRootChannels_remove() {
+    InternalInstrumented<ChannelStats> root1 = create();
+    channelz.addRootChannel(root1);
+    channelz.removeRootChannel(root1);
+    RootChannelList page = channelz.getRootChannels(/*fromId=*/ 0, /*maxPageSize=*/ 1);
+    assertTrue(page.end);
+    assertThat(page.channels).isEmpty();
+  }
+
+  @Test
+  public void getRootChannels_addAfterLastPage() {
+    InternalInstrumented<ChannelStats> root1 = create();
+    {
+      channelz.addRootChannel(root1);
+      RootChannelList page1 = channelz.getRootChannels(/*fromId=*/ 0, /*maxPageSize=*/ 1);
+      assertTrue(page1.end);
+      assertThat(page1.channels).containsExactly(root1);
+    }
+
+    InternalInstrumented<ChannelStats> root2 = create();
+    {
+      channelz.addRootChannel(root2);
+      RootChannelList page2
+          = channelz.getRootChannels(/*fromId=*/ id(root1) + 1, /*maxPageSize=*/ 1);
+      assertTrue(page2.end);
+      assertThat(page2.channels).containsExactly(root2);
+    }
+  }
+
+  @Test
+  public void getServers_empty() {
+    ServerList servers = channelz.getServers(/*fromId=*/ 0, /*maxPageSize=*/ 1);
+    assertTrue(servers.end);
+    assertThat(servers.servers).isEmpty();
+  }
+
+  @Test
+  public void getServers_onePage() {
+    InternalInstrumented<ServerStats> server1 = create();
+    channelz.addServer(server1);
+    ServerList page = channelz.getServers(/*fromId=*/ 0, /*maxPageSize=*/ 1);
+    assertTrue(page.end);
+    assertThat(page.servers).containsExactly(server1);
+  }
+
+  @Test
+  public void getServers_onePage_multi() {
+    InternalInstrumented<ServerStats> server1 = create();
+    InternalInstrumented<ServerStats> server2 = create();
+    channelz.addServer(server1);
+    channelz.addServer(server2);
+    ServerList page = channelz.getServers(/*fromId=*/ 0, /*maxPageSize=*/ 2);
+    assertTrue(page.end);
+    assertThat(page.servers).containsExactly(server1, server2);
+  }
+
+  @Test
+  public void getServers_paginate() {
+    InternalInstrumented<ServerStats> server1 = create();
+    InternalInstrumented<ServerStats> server2 = create();
+    channelz.addServer(server1);
+    channelz.addServer(server2);
+    ServerList page1 = channelz.getServers(/*fromId=*/ 0, /*maxPageSize=*/ 1);
+    assertFalse(page1.end);
+    assertThat(page1.servers).containsExactly(server1);
+    ServerList page2
+        = channelz.getServers(/*fromId=*/ id(server1) + 1, /*maxPageSize=*/ 1);
+    assertTrue(page2.end);
+    assertThat(page2.servers).containsExactly(server2);
+  }
+
+  @Test
+  public void getServers_remove() {
+    InternalInstrumented<ServerStats> server1 = create();
+    channelz.addServer(server1);
+    channelz.removeServer(server1);
+    ServerList page = channelz.getServers(/*fromId=*/ 0, /*maxPageSize=*/ 1);
+    assertTrue(page.end);
+    assertThat(page.servers).isEmpty();
+  }
+
+  @Test
+  public void getServers_addAfterLastPage() {
+    InternalInstrumented<ServerStats> server1 = create();
+    {
+      channelz.addServer(server1);
+      ServerList page = channelz.getServers(/*fromId=*/ 0, /*maxPageSize=*/ 1);
+      assertTrue(page.end);
+      assertThat(page.servers).containsExactly(server1);
+    }
+
+    InternalInstrumented<ServerStats> server2 = create();
+    {
+      channelz.addServer(server2);
+      ServerList page
+          = channelz.getServers(/*fromId=*/ id(server1) + 1, /*maxPageSize=*/ 1);
+      assertTrue(page.end);
+      assertThat(page.servers).containsExactly(server2);
+    }
+  }
+
+  @Test
+  public void getChannel() {
+    InternalInstrumented<ChannelStats> root = create();
+    assertNull(channelz.getChannel(id(root)));
+
+    channelz.addRootChannel(root);
+    assertSame(root, channelz.getChannel(id(root)));
+    assertNull(channelz.getSubchannel(id(root)));
+
+    channelz.removeRootChannel(root);
+    assertNull(channelz.getRootChannel(id(root)));
+  }
+
+  @Test
+  public void getSubchannel() {
+    InternalInstrumented<ChannelStats> sub = create();
+    assertNull(channelz.getSubchannel(id(sub)));
+
+    channelz.addSubchannel(sub);
+    assertSame(sub, channelz.getSubchannel(id(sub)));
+    assertNull(channelz.getChannel(id(sub)));
+
+    channelz.removeSubchannel(sub);
+    assertNull(channelz.getSubchannel(id(sub)));
+  }
+
+  @Test
+  public void getSocket() {
+    InternalInstrumented<SocketStats> socket = create();
+    assertNull(channelz.getSocket(id(socket)));
+
+    channelz.addClientSocket(socket);
+    assertSame(socket, channelz.getSocket(id(socket)));
+
+    channelz.removeClientSocket(socket);
+    assertNull(channelz.getSocket(id(socket)));
+  }
+
+  @Test
+  public void serverSocket_noServer() {
+    assertNull(channelz.getServerSockets(/*serverId=*/ 1, /*fromId=*/0, /*maxPageSize=*/ 1));
+  }
+
+  @Test
+  public void serverSocket() {
+    InternalInstrumented<ServerStats> server = create();
+    channelz.addServer(server);
+
+    InternalInstrumented<SocketStats> socket = create();
+    assertEmptyServerSocketsPage(id(server), id(socket));
+
+    channelz.addServerSocket(server, socket);
+    ServerSocketsList page
+        = channelz.getServerSockets(id(server), id(socket), /*maxPageSize=*/ 1);
+    assertNotNull(page);
+    assertTrue(page.end);
+    assertThat(page.sockets).containsExactly(socket);
+
+    channelz.removeServerSocket(server, socket);
+    assertEmptyServerSocketsPage(id(server), id(socket));
+  }
+
+  @Test
+  public void serverSocket_eachServerSeparate() {
+    InternalInstrumented<ServerStats> server1 = create();
+    InternalInstrumented<ServerStats> server2 = create();
+
+    InternalInstrumented<SocketStats> socket1 = create();
+    InternalInstrumented<SocketStats> socket2 = create();
+
+    channelz.addServer(server1);
+    channelz.addServer(server2);
+    channelz.addServerSocket(server1, socket1);
+    channelz.addServerSocket(server2, socket2);
+
+    ServerSocketsList list1
+        = channelz.getServerSockets(id(server1), /*fromId=*/ 0, /*maxPageSize=*/ 2);
+    assertNotNull(list1);
+    assertTrue(list1.end);
+    assertThat(list1.sockets).containsExactly(socket1);
+
+    ServerSocketsList list2
+        = channelz.getServerSockets(id(server2), /*fromId=*/ 0, /*maxPageSize=*/2);
+    assertNotNull(list2);
+    assertTrue(list2.end);
+    assertThat(list2.sockets).containsExactly(socket2);
+  }
+
+  @Test
+  public void tlsSecurityInfo() throws Exception {
+    Certificate local = io.grpc.internal.testing.TestUtils.loadX509Cert("client.pem");
+    Certificate remote = io.grpc.internal.testing.TestUtils.loadX509Cert("server0.pem");
+    final SSLSession session = mock(SSLSession.class);
+    when(session.getCipherSuite()).thenReturn("TLS_NULL_WITH_NULL_NULL");
+    when(session.getLocalCertificates()).thenReturn(new Certificate[]{local});
+    when(session.getPeerCertificates()).thenReturn(new Certificate[]{remote});
+
+    Tls tls = new Tls(session);
+    assertEquals(local, tls.localCert);
+    assertEquals(remote, tls.remoteCert);
+    assertEquals("TLS_NULL_WITH_NULL_NULL", tls.cipherSuiteStandardName);
+  }
+
+  private void assertEmptyServerSocketsPage(long serverId, long socketId) {
+    ServerSocketsList emptyPage
+        = channelz.getServerSockets(serverId, socketId, /*maxPageSize=*/ 1);
+    assertNotNull(emptyPage);
+    assertTrue(emptyPage.end);
+    assertThat(emptyPage.sockets).isEmpty();
+  }
+
+  private static <T> InternalInstrumented<T> create() {
+    return new InternalInstrumented<T>() {
+      final InternalLogId id = InternalLogId.allocate("fake-tag");
+      @Override
+      public ListenableFuture<T> getStats() {
+        throw new UnsupportedOperationException();
+      }
+
+      @Override
+      public InternalLogId getLogId() {
+        return id;
+      }
+    };
+  }
+}
diff --git a/core/src/test/java/io/grpc/LoadBalancerTest.java b/core/src/test/java/io/grpc/LoadBalancerTest.java
new file mode 100644
index 0000000..ca239c7
--- /dev/null
+++ b/core/src/test/java/io/grpc/LoadBalancerTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+
+import io.grpc.ClientStreamTracer;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.Status;
+import java.net.SocketAddress;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for the inner classes in {@link LoadBalancer}. */
+@RunWith(JUnit4.class)
+public class LoadBalancerTest {
+  private final Subchannel subchannel = mock(Subchannel.class);
+  private final Subchannel subchannel2 = mock(Subchannel.class);
+  private final ClientStreamTracer.Factory tracerFactory = mock(ClientStreamTracer.Factory.class);
+  private final Status status = Status.UNAVAILABLE.withDescription("for test");
+  private final Status status2 = Status.UNAVAILABLE.withDescription("for test 2");
+  private final EquivalentAddressGroup eag = new EquivalentAddressGroup(new SocketAddress() {});
+  private final Attributes attrs = Attributes.newBuilder()
+      .set(Attributes.Key.create("trash"), new Object())
+      .build();
+  private final Subchannel emptySubchannel = new EmptySubchannel();
+
+  @Test
+  public void pickResult_withSubchannel() {
+    PickResult result = PickResult.withSubchannel(subchannel);
+    assertThat(result.getSubchannel()).isSameAs(subchannel);
+    assertThat(result.getStatus()).isSameAs(Status.OK);
+    assertThat(result.getStreamTracerFactory()).isNull();
+    assertThat(result.isDrop()).isFalse();
+  }
+
+  @Test
+  public void pickResult_withSubchannelAndTracer() {
+    PickResult result = PickResult.withSubchannel(subchannel, tracerFactory);
+    assertThat(result.getSubchannel()).isSameAs(subchannel);
+    assertThat(result.getStatus()).isSameAs(Status.OK);
+    assertThat(result.getStreamTracerFactory()).isSameAs(tracerFactory);
+    assertThat(result.isDrop()).isFalse();
+  }
+
+  @Test
+  public void pickResult_withNoResult() {
+    PickResult result = PickResult.withNoResult();
+    assertThat(result.getSubchannel()).isNull();
+    assertThat(result.getStatus()).isSameAs(Status.OK);
+    assertThat(result.getStreamTracerFactory()).isNull();
+    assertThat(result.isDrop()).isFalse();
+  }
+
+  @Test
+  public void pickResult_withError() {
+    PickResult result = PickResult.withError(status);
+    assertThat(result.getSubchannel()).isNull();
+    assertThat(result.getStatus()).isSameAs(status);
+    assertThat(result.getStreamTracerFactory()).isNull();
+    assertThat(result.isDrop()).isFalse();
+  }
+
+  @Test
+  public void pickResult_withDrop() {
+    PickResult result = PickResult.withDrop(status);
+    assertThat(result.getSubchannel()).isNull();
+    assertThat(result.getStatus()).isSameAs(status);
+    assertThat(result.getStreamTracerFactory()).isNull();
+    assertThat(result.isDrop()).isTrue();
+  }
+
+  @Test
+  public void pickResult_equals() {
+    PickResult sc1 = PickResult.withSubchannel(subchannel);
+    PickResult sc2 = PickResult.withSubchannel(subchannel);
+    PickResult sc3 = PickResult.withSubchannel(subchannel, tracerFactory);
+    PickResult sc4 = PickResult.withSubchannel(subchannel2);
+    PickResult nr = PickResult.withNoResult();
+    PickResult error1 = PickResult.withError(status);
+    PickResult error2 = PickResult.withError(status2);
+    PickResult error3 = PickResult.withError(status2);
+    PickResult drop1 = PickResult.withDrop(status);
+    PickResult drop2 = PickResult.withDrop(status);
+    PickResult drop3 = PickResult.withDrop(status2);
+
+    assertThat(sc1).isNotEqualTo(nr);
+    assertThat(sc1).isNotEqualTo(error1);
+    assertThat(sc1).isNotEqualTo(drop1);
+    assertThat(sc1).isEqualTo(sc2);
+    assertThat(sc1).isNotEqualTo(sc3);
+    assertThat(sc1).isNotEqualTo(sc4);
+
+    assertThat(error1).isNotEqualTo(error2);
+    assertThat(error2).isEqualTo(error3);
+
+    assertThat(drop1).isEqualTo(drop2);
+    assertThat(drop1).isNotEqualTo(drop3);
+
+    assertThat(error1.getStatus()).isEqualTo(drop1.getStatus());
+    assertThat(error1).isNotEqualTo(drop1);
+  }
+
+  @Test
+  public void helper_createSubchannel_delegates() {
+    class OverrideCreateSubchannel extends NoopHelper {
+      boolean ran;
+
+      @Override
+      public Subchannel createSubchannel(List<EquivalentAddressGroup> addrsIn, Attributes attrsIn) {
+        assertThat(addrsIn).hasSize(1);
+        assertThat(addrsIn.get(0)).isSameAs(eag);
+        assertThat(attrsIn).isSameAs(attrs);
+        ran = true;
+        return subchannel;
+      }
+    }
+
+    OverrideCreateSubchannel helper = new OverrideCreateSubchannel();
+    assertThat(helper.createSubchannel(eag, attrs)).isSameAs(subchannel);
+    assertThat(helper.ran).isTrue();
+  }
+
+  @Test(expected = UnsupportedOperationException.class)
+  public void helper_createSubchannelList_throws() {
+    new NoopHelper().createSubchannel(Arrays.asList(eag), attrs);
+  }
+
+  @Test
+  public void helper_updateSubchannelAddresses_delegates() {
+    class OverrideUpdateSubchannel extends NoopHelper {
+      boolean ran;
+
+      @Override
+      public void updateSubchannelAddresses(
+          Subchannel subchannelIn, List<EquivalentAddressGroup> addrsIn) {
+        assertThat(subchannelIn).isSameAs(emptySubchannel);
+        assertThat(addrsIn).hasSize(1);
+        assertThat(addrsIn.get(0)).isSameAs(eag);
+        ran = true;
+      }
+    }
+
+    OverrideUpdateSubchannel helper = new OverrideUpdateSubchannel();
+    helper.updateSubchannelAddresses(emptySubchannel, eag);
+    assertThat(helper.ran).isTrue();
+  }
+
+  @Test(expected = UnsupportedOperationException.class)
+  public void helper_updateSubchannelAddressesList_throws() {
+    new NoopHelper().updateSubchannelAddresses(null, Arrays.asList(eag));
+  }
+
+  @Test
+  public void subchannel_getAddresses_delegates() {
+    class OverrideGetAllAddresses extends EmptySubchannel {
+      boolean ran;
+
+      @Override public List<EquivalentAddressGroup> getAllAddresses() {
+        ran = true;
+        return Arrays.asList(eag);
+      }
+    }
+
+    OverrideGetAllAddresses subchannel = new OverrideGetAllAddresses();
+    assertThat(subchannel.getAddresses()).isEqualTo(eag);
+    assertThat(subchannel.ran).isTrue();
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void subchannel_getAddresses_throwsOnTwoAddrs() {
+    new EmptySubchannel() {
+      boolean ran;
+
+      @Override public List<EquivalentAddressGroup> getAllAddresses() {
+        ran = true;
+        // Doubling up eag is technically a bad idea, but nothing here cares
+        return Arrays.asList(eag, eag);
+      }
+    }.getAddresses();
+  }
+
+  private static class NoopHelper extends LoadBalancer.Helper {
+    @Override
+    public ManagedChannel createOobChannel(EquivalentAddressGroup eag, String authority) {
+      return null;
+    }
+
+    @Override
+    public void updateBalancingState(
+        ConnectivityState newState, LoadBalancer.SubchannelPicker newPicker) {}
+
+    @Override public void runSerialized(Runnable task) {}
+
+    @Override public NameResolver.Factory getNameResolverFactory() {
+      return null;
+    }
+
+    @Override public String getAuthority() {
+      return null;
+    }
+  }
+
+  private static class EmptySubchannel extends LoadBalancer.Subchannel {
+    @Override public void shutdown() {}
+
+    @Override public void requestConnection() {}
+
+    @Override public Attributes getAttributes() {
+      return null;
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/ManagedChannelProviderTest.java b/core/src/test/java/io/grpc/ManagedChannelProviderTest.java
new file mode 100644
index 0000000..edb4a70
--- /dev/null
+++ b/core/src/test/java/io/grpc/ManagedChannelProviderTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Iterator;
+import java.util.concurrent.Callable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ManagedChannelProvider}. */
+@RunWith(JUnit4.class)
+public class ManagedChannelProviderTest {
+
+  @Test
+  public void getCandidatesViaHardCoded_triesToLoadClasses() throws Exception {
+    ServiceProvidersTestUtil.testHardcodedClasses(
+        HardcodedClassesCallable.class.getName(),
+        getClass().getClassLoader(),
+        ImmutableSet.of(
+            "io.grpc.okhttp.OkHttpChannelProvider",
+            "io.grpc.netty.NettyChannelProvider"));
+  }
+
+  public static final class HardcodedClassesCallable implements Callable<Iterator<Class<?>>> {
+    @Override
+    public Iterator<Class<?>> call() {
+      return ManagedChannelProvider.HARDCODED_CLASSES.iterator();
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/MetadataTest.java b/core/src/test/java/io/grpc/MetadataTest.java
new file mode 100644
index 0000000..360f888
--- /dev/null
+++ b/core/src/test/java/io/grpc/MetadataTest.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Charsets.US_ASCII;
+import static com.google.common.base.Charsets.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.Lists;
+import io.grpc.Metadata.Key;
+import io.grpc.internal.GrpcUtil;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Locale;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link Metadata}.
+ */
+@RunWith(JUnit4.class)
+public class MetadataTest {
+
+  @Rule public final ExpectedException thrown = ExpectedException.none();
+
+  private static final Metadata.BinaryMarshaller<Fish> FISH_MARSHALLER =
+      new Metadata.BinaryMarshaller<Fish>() {
+    @Override
+    public byte[] toBytes(Fish fish) {
+      return fish.name.getBytes(UTF_8);
+    }
+
+    @Override
+    public Fish parseBytes(byte[] serialized) {
+      return new Fish(new String(serialized, UTF_8));
+    }
+  };
+
+  private static final String LANCE = "lance";
+  private static final byte[] LANCE_BYTES = LANCE.getBytes(US_ASCII);
+  private static final Metadata.Key<Fish> KEY = Metadata.Key.of("test-bin", FISH_MARSHALLER);
+
+  @Test
+  public void noPseudoHeaders() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Invalid character");
+
+    Metadata.Key.of(":test-bin", FISH_MARSHALLER);
+  }
+
+  @Test
+  public void testMutations() {
+    Fish lance = new Fish(LANCE);
+    Fish cat = new Fish("cat");
+    Metadata metadata = new Metadata();
+
+    assertEquals(null, metadata.get(KEY));
+    metadata.put(KEY, lance);
+    assertEquals(Arrays.asList(lance), Lists.newArrayList(metadata.getAll(KEY)));
+    assertEquals(lance, metadata.get(KEY));
+    metadata.put(KEY, lance);
+    assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.getAll(KEY)));
+    assertTrue(metadata.remove(KEY, lance));
+    assertEquals(Arrays.asList(lance), Lists.newArrayList(metadata.getAll(KEY)));
+
+    assertFalse(metadata.remove(KEY, cat));
+    metadata.put(KEY, cat);
+    assertEquals(cat, metadata.get(KEY));
+    metadata.put(KEY, lance);
+    assertEquals(Arrays.asList(lance, cat, lance), Lists.newArrayList(metadata.getAll(KEY)));
+    assertEquals(lance, metadata.get(KEY));
+    assertTrue(metadata.remove(KEY, lance));
+    assertEquals(Arrays.asList(cat, lance), Lists.newArrayList(metadata.getAll(KEY)));
+    metadata.put(KEY, lance);
+    assertTrue(metadata.remove(KEY, cat));
+    assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.getAll(KEY)));
+
+    assertEquals(Arrays.asList(lance, lance), Lists.newArrayList(metadata.removeAll(KEY)));
+    assertEquals(null, metadata.getAll(KEY));
+    assertEquals(null, metadata.get(KEY));
+  }
+
+  @Test
+  public void discardAll() {
+    Fish lance = new Fish(LANCE);
+    Metadata metadata = new Metadata();
+
+    metadata.put(KEY, lance);
+    metadata.discardAll(KEY);
+    assertEquals(null, metadata.getAll(KEY));
+    assertEquals(null, metadata.get(KEY));
+  }
+
+  @Test
+  public void discardAll_empty() {
+    Metadata metadata = new Metadata();
+    metadata.discardAll(KEY);
+    assertEquals(null, metadata.getAll(KEY));
+    assertEquals(null, metadata.get(KEY));
+  }
+
+  @Test
+  public void testGetAllNoRemove() {
+    Fish lance = new Fish(LANCE);
+    Metadata metadata = new Metadata();
+    metadata.put(KEY, lance);
+    Iterator<Fish> i = metadata.getAll(KEY).iterator();
+    assertEquals(lance, i.next());
+
+    thrown.expect(UnsupportedOperationException.class);
+    i.remove();
+  }
+
+  @Test
+  public void testWriteParsed() {
+    Fish lance = new Fish(LANCE);
+    Metadata metadata = new Metadata();
+    metadata.put(KEY, lance);
+    assertEquals(lance, metadata.get(KEY));
+    Iterator<Fish> fishes = metadata.<Fish>getAll(KEY).iterator();
+    assertTrue(fishes.hasNext());
+    assertEquals(fishes.next(), lance);
+    assertFalse(fishes.hasNext());
+    byte[][] serialized = metadata.serialize();
+    assertEquals(2, serialized.length);
+    assertEquals("test-bin", new String(serialized[0], US_ASCII));
+    assertArrayEquals(LANCE_BYTES, serialized[1]);
+    assertEquals(lance, metadata.get(KEY));
+    assertEquals(serialized[0], metadata.serialize()[0]);
+    assertEquals(serialized[1], metadata.serialize()[1]);
+  }
+
+  @Test
+  public void testWriteRaw() {
+    Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES);
+    Fish lance = raw.get(KEY);
+    assertEquals(lance, new Fish(LANCE));
+    // Reading again should return the same parsed instance
+    assertEquals(lance, raw.get(KEY));
+  }
+
+  @Test
+  public void testSerializeRaw() {
+    Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES);
+    byte[][] serialized = raw.serialize();
+    assertArrayEquals(serialized[0], KEY.asciiName());
+    assertArrayEquals(serialized[1], LANCE_BYTES);
+  }
+
+  @Test
+  public void testMergeByteConstructed() {
+    Metadata raw = new Metadata(KEY.asciiName(), LANCE_BYTES);
+    Metadata serializable = new Metadata();
+    serializable.merge(raw);
+
+    byte[][] serialized = serializable.serialize();
+    assertArrayEquals(serialized[0], KEY.asciiName());
+    assertArrayEquals(serialized[1], LANCE_BYTES);
+    assertEquals(new Fish(LANCE), serializable.get(KEY));
+  }
+
+  @Test
+  public void headerMergeShouldCopyValues() {
+    Fish lance = new Fish(LANCE);
+    Metadata h1 = new Metadata();
+
+    Metadata h2 = new Metadata();
+    h2.put(KEY, lance);
+
+    h1.merge(h2);
+
+    Iterator<Fish> fishes = h1.<Fish>getAll(KEY).iterator();
+    assertTrue(fishes.hasNext());
+    assertEquals(fishes.next(), lance);
+    assertFalse(fishes.hasNext());
+  }
+
+  @Test
+  public void mergeExpands() {
+    Fish lance = new Fish(LANCE);
+    Metadata h1 = new Metadata();
+    h1.put(KEY, lance);
+
+    Metadata h2 = new Metadata();
+    h2.put(KEY, lance);
+    h2.put(KEY, lance);
+    h2.put(KEY, lance);
+    h2.put(KEY, lance);
+
+    h1.merge(h2);
+  }
+
+  @Test
+  public void shortBinaryKeyName() {
+    thrown.expect(IllegalArgumentException.class);
+
+    Metadata.Key.of("-bin", FISH_MARSHALLER);
+  }
+
+  @Test
+  public void invalidSuffixBinaryKeyName() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Binary header is named");
+
+    Metadata.Key.of("nonbinary", FISH_MARSHALLER);
+  }
+
+  @Test
+  public void verifyToString() {
+    Metadata h = new Metadata();
+    h.put(KEY, new Fish("binary"));
+    h.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii");
+    assertEquals("Metadata(test-bin=YmluYXJ5,test=ascii)", h.toString());
+
+    Metadata t = new Metadata();
+    t.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii");
+    assertEquals("Metadata(test=ascii)", t.toString());
+
+    t = new Metadata("test".getBytes(US_ASCII), "ascii".getBytes(US_ASCII),
+        "test-bin".getBytes(US_ASCII), "binary".getBytes(US_ASCII));
+    assertEquals("Metadata(test=ascii,test-bin=YmluYXJ5)", t.toString());
+  }
+
+  @Test
+  public void verifyToString_usingBinary() {
+    Metadata h = new Metadata();
+    h.put(KEY, new Fish("binary"));
+    h.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii");
+    assertEquals("Metadata(test-bin=YmluYXJ5,test=ascii)", h.toString());
+
+    Metadata t = new Metadata();
+    t.put(Metadata.Key.of("test", Metadata.ASCII_STRING_MARSHALLER), "ascii");
+    assertEquals("Metadata(test=ascii)", t.toString());
+  }
+
+  @Test
+  public void testKeyCaseHandling() {
+    Locale originalLocale = Locale.getDefault();
+    Locale.setDefault(new Locale("tr", "TR"));
+    try {
+      // In Turkish, both I and i (which are in ASCII) change into non-ASCII characters when their
+      // case is changed as ı and İ, respectively.
+      assertEquals("İ", "i".toUpperCase());
+      assertEquals("ı", "I".toLowerCase());
+
+      Metadata.Key<String> keyTitleCase
+          = Metadata.Key.of("If-Modified-Since", Metadata.ASCII_STRING_MARSHALLER);
+      Metadata.Key<String> keyLowerCase
+          = Metadata.Key.of("if-modified-since", Metadata.ASCII_STRING_MARSHALLER);
+      Metadata.Key<String> keyUpperCase
+          = Metadata.Key.of("IF-MODIFIED-SINCE", Metadata.ASCII_STRING_MARSHALLER);
+
+      Metadata metadata = new Metadata();
+      metadata.put(keyTitleCase, "plain string");
+      assertEquals("plain string", metadata.get(keyTitleCase));
+      assertEquals("plain string", metadata.get(keyLowerCase));
+      assertEquals("plain string", metadata.get(keyUpperCase));
+
+      byte[][] bytes = metadata.serialize();
+      assertEquals(2, bytes.length);
+      assertArrayEquals("if-modified-since".getBytes(US_ASCII), bytes[0]);
+      assertArrayEquals("plain string".getBytes(US_ASCII), bytes[1]);
+    } finally {
+      Locale.setDefault(originalLocale);
+    }
+  }
+
+  @Test
+  public void removeIgnoresMissingValue() {
+    Metadata m = new Metadata();
+    // Any key will work.
+    Key<String> key = GrpcUtil.USER_AGENT_KEY;
+
+    boolean success = m.remove(key, "agent");
+    assertFalse(success);
+  }
+
+  @Test
+  public void removeAllIgnoresMissingValue() {
+    Metadata m = new Metadata();
+    // Any key will work.
+    Key<String> key = GrpcUtil.USER_AGENT_KEY;
+
+    Iterable<String> removed = m.removeAll(key);
+    assertNull(removed);
+  }
+
+  @Test
+  public void keyEqualsHashNameWorks() {
+    Key<?> k1 = Key.of("case", Metadata.ASCII_STRING_MARSHALLER);
+
+    Key<?> k2 = Key.of("CASE", Metadata.ASCII_STRING_MARSHALLER);
+    assertEquals(k1, k1);
+    assertNotEquals(k1, null);
+    assertNotEquals(k1, new Object(){});
+    assertEquals(k1, k2);
+
+    assertEquals(k1.hashCode(), k2.hashCode());
+    // Check that the casing is preserved.
+    assertEquals("CASE", k2.originalName());
+    assertEquals("case", k2.name());
+  }
+
+  @Test
+  public void invalidKeyName() {
+    try {
+      Key.of("io.grpc/key1", Metadata.ASCII_STRING_MARSHALLER);
+      fail("Should have thrown");
+    } catch (IllegalArgumentException e) {
+      assertEquals("Invalid character '/' in key name 'io.grpc/key1'", e.getMessage());
+    }
+  }
+
+  private static class Fish {
+    private String name;
+
+    private Fish(String name) {
+      this.name = name;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      Fish fish = (Fish) o;
+      if (name != null ? !name.equals(fish.name) : fish.name != null) {
+        return false;
+      }
+      return true;
+    }
+
+    @Override
+    public int hashCode() {
+      return name.hashCode();
+    }
+
+    @Override
+    public String toString() {
+      return "Fish(" + name + ")";
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/MethodDescriptorTest.java b/core/src/test/java/io/grpc/MethodDescriptorTest.java
new file mode 100644
index 0000000..47f13d3
--- /dev/null
+++ b/core/src/test/java/io/grpc/MethodDescriptorTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static junit.framework.TestCase.assertSame;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.MethodDescriptor.MethodType;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link MethodDescriptor}.
+ */
+@RunWith(JUnit4.class)
+public class MethodDescriptorTest {
+  @Rule
+  public final ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void createMethodDescriptor() {
+    MethodDescriptor<String, String> descriptor = MethodDescriptor.<String, String>newBuilder()
+        .setType(MethodType.CLIENT_STREAMING)
+        .setFullMethodName("package.service/method")
+        .setRequestMarshaller(new StringMarshaller())
+        .setResponseMarshaller(new StringMarshaller())
+        .build();
+
+    assertEquals(MethodType.CLIENT_STREAMING, descriptor.getType());
+    assertEquals("package.service/method", descriptor.getFullMethodName());
+    assertFalse(descriptor.isIdempotent());
+    assertFalse(descriptor.isSafe());
+  }
+
+  @Test
+  public void idempotent() {
+    MethodDescriptor<String, String> descriptor = MethodDescriptor.<String, String>newBuilder()
+        .setType(MethodType.SERVER_STREAMING)
+        .setFullMethodName("package.service/method")
+        .setRequestMarshaller(new StringMarshaller())
+        .setResponseMarshaller(new StringMarshaller())
+        .build();
+
+    assertFalse(descriptor.isIdempotent());
+
+    // Create a new desriptor by setting idempotent to true
+    MethodDescriptor<String, String> newDescriptor =
+        descriptor.toBuilder().setIdempotent(true).build();
+    assertTrue(newDescriptor.isIdempotent());
+    // All other fields should staty the same
+    assertEquals(MethodType.SERVER_STREAMING, newDescriptor.getType());
+    assertEquals("package.service/method", newDescriptor.getFullMethodName());
+  }
+
+  @Test
+  public void safe() {
+    MethodDescriptor<String, String> descriptor = MethodDescriptor.<String, String>newBuilder()
+        .setType(MethodType.UNARY)
+        .setFullMethodName("package.service/method")
+        .setRequestMarshaller(new StringMarshaller())
+        .setResponseMarshaller(new StringMarshaller())
+        .build();
+    assertFalse(descriptor.isSafe());
+
+    // Create a new desriptor by setting safe to true
+    MethodDescriptor<String, String> newDescriptor = descriptor.toBuilder().setSafe(true).build();
+    assertTrue(newDescriptor.isSafe());
+    // All other fields should staty the same
+    assertEquals(MethodType.UNARY, newDescriptor.getType());
+    assertEquals("package.service/method", newDescriptor.getFullMethodName());
+  }
+
+  @Test
+  public void safeAndNonUnary() {
+    MethodDescriptor<String, String> descriptor = MethodDescriptor.<String, String>newBuilder()
+        .setType(MethodType.SERVER_STREAMING)
+        .setFullMethodName("package.service/method")
+        .setRequestMarshaller(new StringMarshaller())
+        .setResponseMarshaller(new StringMarshaller())
+        .build();
+
+    thrown.expect(IllegalArgumentException.class);
+    MethodDescriptor<String, String> unused = descriptor.toBuilder().setSafe(true).build();
+  }
+
+  @Test
+  public void sampledToLocalTracing() {
+    MethodDescriptor<String, String> md1 = MethodDescriptor.<String, String>newBuilder()
+        .setType(MethodType.SERVER_STREAMING)
+        .setFullMethodName("package.service/method")
+        .setRequestMarshaller(new StringMarshaller())
+        .setResponseMarshaller(new StringMarshaller())
+        .setSampledToLocalTracing(true)
+        .build();
+    assertTrue(md1.isSampledToLocalTracing());
+
+    MethodDescriptor<String, String> md2 = md1.toBuilder()
+        .setFullMethodName("package.service/method2")
+        .build();
+    assertTrue(md2.isSampledToLocalTracing());
+
+    // Same method name as md1, but not setting sampledToLocalTracing
+    MethodDescriptor<String, String> md3 = MethodDescriptor.<String, String>newBuilder()
+        .setType(MethodType.SERVER_STREAMING)
+        .setFullMethodName("package.service/method")
+        .setRequestMarshaller(new StringMarshaller())
+        .setResponseMarshaller(new StringMarshaller())
+        .build();
+    assertFalse(md3.isSampledToLocalTracing());
+
+    MethodDescriptor<String, String> md4 = md3.toBuilder()
+        .setFullMethodName("package.service/method2")
+        .setSampledToLocalTracing(true)
+        .build();
+    assertTrue(md4.isSampledToLocalTracing());
+  }
+
+  @Test
+  public void toBuilderTest() {
+    MethodDescriptor<String, String> md1 = MethodDescriptor.<String, String>newBuilder()
+        .setType(MethodType.UNARY)
+        .setFullMethodName("package.service/method")
+        .setRequestMarshaller(StringMarshaller.INSTANCE)
+        .setResponseMarshaller(StringMarshaller.INSTANCE)
+        .setSampledToLocalTracing(true)
+        .setIdempotent(true)
+        .setSafe(true)
+        .setSchemaDescriptor(new Object())
+        .build();
+    // Verify that we are not using any default builder values, so if md1 and md2 matches,
+    // it's because toBuilder explicitly copied it.
+    MethodDescriptor<String, String> defaults = MethodDescriptor.<String, String>newBuilder()
+        .setType(MethodType.UNARY)
+        .setFullMethodName("package.service/method")
+        .setRequestMarshaller(StringMarshaller.INSTANCE)
+        .setResponseMarshaller(StringMarshaller.INSTANCE)
+        .build();
+    assertNotEquals(md1.isSampledToLocalTracing(), defaults.isSampledToLocalTracing());
+    assertNotEquals(md1.isIdempotent(), defaults.isIdempotent());
+    assertNotEquals(md1.isSafe(), defaults.isSafe());
+    assertNotEquals(md1.getSchemaDescriptor(), defaults.getSchemaDescriptor());
+
+    // Verify that the builder correctly copied over the values
+    MethodDescriptor<Integer, Integer> md2 = md1.toBuilder(
+        IntegerMarshaller.INSTANCE,
+        IntegerMarshaller.INSTANCE).build();
+    assertSame(md1.getType(), md2.getType());
+    assertSame(md1.getFullMethodName(), md2.getFullMethodName());
+    assertSame(IntegerMarshaller.INSTANCE, md2.getRequestMarshaller());
+    assertSame(IntegerMarshaller.INSTANCE, md2.getResponseMarshaller());
+    assertEquals(md1.isSampledToLocalTracing(), md2.isSampledToLocalTracing());
+    assertEquals(md1.isIdempotent(), md2.isIdempotent());
+    assertEquals(md1.isSafe(), md2.isSafe());
+    assertSame(md1.getSchemaDescriptor(), md2.getSchemaDescriptor());
+  }
+
+  @Test
+  public void toStringTest() {
+    MethodDescriptor<String, String> descriptor = MethodDescriptor.<String, String>newBuilder()
+        .setType(MethodType.UNARY)
+        .setFullMethodName("package.service/method")
+        .setRequestMarshaller(StringMarshaller.INSTANCE)
+        .setResponseMarshaller(StringMarshaller.INSTANCE)
+        .setSampledToLocalTracing(true)
+        .setIdempotent(true)
+        .setSafe(true)
+        .setSchemaDescriptor(new Object())
+        .build();
+
+    String toString = descriptor.toString();
+    assertTrue(toString.contains("MethodDescriptor"));
+    assertTrue(toString.contains("fullMethodName=package.service/method"));
+    assertTrue(toString.contains("type=UNARY"));
+    assertTrue(toString.contains("idempotent=true"));
+    assertTrue(toString.contains("safe=true"));
+    assertTrue(toString.contains("sampledToLocalTracing=true"));
+    assertTrue(toString.contains("requestMarshaller=io.grpc.StringMarshaller"));
+    assertTrue(toString.contains("responseMarshaller=io.grpc.StringMarshaller"));
+    assertTrue(toString.contains("schemaDescriptor=java.lang.Object"));
+  }
+}
diff --git a/core/src/test/java/io/grpc/NameResolverProviderTest.java b/core/src/test/java/io/grpc/NameResolverProviderTest.java
new file mode 100644
index 0000000..efe4e9e
--- /dev/null
+++ b/core/src/test/java/io/grpc/NameResolverProviderTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import io.grpc.internal.DnsNameResolverProvider;
+import java.net.URI;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Callable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link NameResolverProvider}. */
+@RunWith(JUnit4.class)
+public class NameResolverProviderTest {
+  private final URI uri = URI.create("dns:///localhost");
+  private final Attributes attributes = Attributes.EMPTY;
+
+  @Test
+  public void getDefaultScheme_noProvider() {
+    List<NameResolverProvider> providers = Collections.<NameResolverProvider>emptyList();
+    NameResolver.Factory factory = NameResolverProvider.asFactory(providers);
+    try {
+      factory.getDefaultScheme();
+      fail("Expected exception");
+    } catch (RuntimeException ex) {
+      assertTrue(ex.toString(), ex.getMessage().contains("No NameResolverProviders found"));
+    }
+  }
+
+  @Test
+  public void newNameResolver_providerReturnsNull() {
+    List<NameResolverProvider> providers = Collections.<NameResolverProvider>singletonList(
+        new BaseProvider(true, 5) {
+          @Override
+          public NameResolver newNameResolver(URI passedUri, Attributes passedAttributes) {
+            assertSame(uri, passedUri);
+            assertSame(attributes, passedAttributes);
+            return null;
+          }
+        });
+    assertNull(NameResolverProvider.asFactory(providers).newNameResolver(uri, attributes));
+  }
+
+  @Test
+  public void newNameResolver_noProvider() {
+    List<NameResolverProvider> providers = Collections.<NameResolverProvider>emptyList();
+    NameResolver.Factory factory = NameResolverProvider.asFactory(providers);
+    try {
+      factory.newNameResolver(uri, attributes);
+      fail("Expected exception");
+    } catch (RuntimeException ex) {
+      assertTrue(ex.toString(), ex.getMessage().contains("No NameResolverProviders found"));
+    }
+  }
+
+  @Test
+  public void baseProviders() {
+    List<NameResolverProvider> providers = NameResolverProvider.providers();
+    assertEquals(1, providers.size());
+    assertSame(DnsNameResolverProvider.class, providers.get(0).getClass());
+    assertEquals("dns", NameResolverProvider.asFactory().getDefaultScheme());
+  }
+
+  @Test
+  public void getClassesViaHardcoded_classesPresent() throws Exception {
+    List<Class<?>> classes = NameResolverProvider.getHardCodedClasses();
+    assertThat(classes).hasSize(1);
+    assertThat(classes.get(0).getName()).isEqualTo("io.grpc.internal.DnsNameResolverProvider");
+  }
+
+  @Test
+  public void provided() {
+    for (NameResolverProvider current
+        : InternalServiceProviders.getCandidatesViaServiceLoader(
+        NameResolverProvider.class, getClass().getClassLoader())) {
+      if (current instanceof DnsNameResolverProvider) {
+        return;
+      }
+    }
+    fail("DnsNameResolverProvider not registered");
+  }
+
+  @Test
+  public void providedHardCoded() {
+    for (NameResolverProvider current : InternalServiceProviders.getCandidatesViaHardCoded(
+        NameResolverProvider.class, NameResolverProvider.HARDCODED_CLASSES)) {
+      if (current instanceof DnsNameResolverProvider) {
+        return;
+      }
+    }
+    fail("DnsNameResolverProvider not registered");
+  }
+
+  public static final class HardcodedClassesCallable implements Callable<Iterator<Class<?>>> {
+    @Override
+    public Iterator<Class<?>> call() {
+      return NameResolverProvider.getHardCodedClasses().iterator();
+    }
+  }
+
+  private static class BaseProvider extends NameResolverProvider {
+    private final boolean isAvailable;
+    private final int priority;
+
+    public BaseProvider(boolean isAvailable, int priority) {
+      this.isAvailable = isAvailable;
+      this.priority = priority;
+    }
+
+    @Override
+    protected boolean isAvailable() {
+      return isAvailable;
+    }
+
+    @Override
+    protected int priority() {
+      return priority;
+    }
+
+    @Override
+    public NameResolver newNameResolver(URI targetUri, Attributes params) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getDefaultScheme() {
+      return "scheme" + getClass().getSimpleName();
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/PickFirstLoadBalancerTest.java b/core/src/test/java/io/grpc/PickFirstLoadBalancerTest.java
new file mode 100644
index 0000000..30c31e2
--- /dev/null
+++ b/core/src/test/java/io/grpc/PickFirstLoadBalancerTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static io.grpc.ConnectivityState.CONNECTING;
+import static io.grpc.ConnectivityState.IDLE;
+import static io.grpc.ConnectivityState.READY;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyListOf;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.Lists;
+import io.grpc.LoadBalancer.Helper;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.PickFirstBalancerFactory.PickFirstBalancer;
+import java.net.SocketAddress;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+/** Unit test for {@link PickFirstBalancerFactory}. */
+@RunWith(JUnit4.class)
+public class PickFirstLoadBalancerTest {
+  private PickFirstBalancer loadBalancer;
+  private List<EquivalentAddressGroup> servers = Lists.newArrayList();
+  private List<SocketAddress> socketAddresses = Lists.newArrayList();
+
+  private static final Attributes.Key<String> FOO = Attributes.Key.create("foo");
+  private Attributes affinity = Attributes.newBuilder().set(FOO, "bar").build();
+
+  @Captor
+  private ArgumentCaptor<SubchannelPicker> pickerCaptor;
+  @Captor
+  private ArgumentCaptor<Attributes> attrsCaptor;
+  @Mock
+  private Helper mockHelper;
+  @Mock
+  private Subchannel mockSubchannel;
+  @Mock // This LoadBalancer doesn't use any of the arg fields, as verified in tearDown().
+  private PickSubchannelArgs mockArgs;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    for (int i = 0; i < 3; i++) {
+      SocketAddress addr = new FakeSocketAddress("server" + i);
+      servers.add(new EquivalentAddressGroup(addr));
+      socketAddresses.add(addr);
+    }
+
+    when(mockSubchannel.getAddresses()).thenThrow(new UnsupportedOperationException());
+    when(mockHelper.createSubchannel(
+        anyListOf(EquivalentAddressGroup.class), any(Attributes.class)))
+        .thenReturn(mockSubchannel);
+
+    loadBalancer = (PickFirstBalancer) PickFirstBalancerFactory.getInstance().newLoadBalancer(
+        mockHelper);
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    verifyNoMoreInteractions(mockArgs);
+  }
+
+  @Test
+  public void pickAfterResolved() throws Exception {
+    loadBalancer.handleResolvedAddressGroups(servers, affinity);
+
+    verify(mockHelper).createSubchannel(eq(servers), attrsCaptor.capture());
+    verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
+    verify(mockSubchannel).requestConnection();
+
+    assertEquals(pickerCaptor.getValue().pickSubchannel(mockArgs),
+        pickerCaptor.getValue().pickSubchannel(mockArgs));
+
+    verifyNoMoreInteractions(mockHelper);
+  }
+
+  @Test
+  public void pickAfterResolvedAndUnchanged() throws Exception {
+    loadBalancer.handleResolvedAddressGroups(servers, affinity);
+    verify(mockSubchannel).requestConnection();
+    loadBalancer.handleResolvedAddressGroups(servers, affinity);
+    verifyNoMoreInteractions(mockSubchannel);
+
+    verify(mockHelper).createSubchannel(anyListOf(EquivalentAddressGroup.class),
+        any(Attributes.class));
+    verify(mockHelper)
+        .updateBalancingState(isA(ConnectivityState.class), isA(SubchannelPicker.class));
+    // Updating the subchannel addresses is unnecessary, but doesn't hurt anything
+    verify(mockHelper).updateSubchannelAddresses(
+        eq(mockSubchannel), anyListOf(EquivalentAddressGroup.class));
+
+    verifyNoMoreInteractions(mockHelper);
+  }
+
+  @Test
+  public void pickAfterResolvedAndChanged() throws Exception {
+    SocketAddress socketAddr = new FakeSocketAddress("newserver");
+    List<EquivalentAddressGroup> newServers =
+        Lists.newArrayList(new EquivalentAddressGroup(socketAddr));
+
+    InOrder inOrder = inOrder(mockHelper);
+
+    loadBalancer.handleResolvedAddressGroups(servers, affinity);
+    inOrder.verify(mockHelper).createSubchannel(eq(servers), any(Attributes.class));
+    inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
+    verify(mockSubchannel).requestConnection();
+    assertEquals(mockSubchannel, pickerCaptor.getValue().pickSubchannel(mockArgs).getSubchannel());
+
+    loadBalancer.handleResolvedAddressGroups(newServers, affinity);
+    inOrder.verify(mockHelper).updateSubchannelAddresses(eq(mockSubchannel), eq(newServers));
+
+    verifyNoMoreInteractions(mockSubchannel);
+    verifyNoMoreInteractions(mockHelper);
+  }
+
+  @Test
+  public void stateChangeBeforeResolution() throws Exception {
+    loadBalancer.handleSubchannelState(mockSubchannel, ConnectivityStateInfo.forNonError(READY));
+
+    verifyNoMoreInteractions(mockHelper);
+  }
+
+  @Test
+  public void pickAfterStateChangeAfterResolution() throws Exception {
+    loadBalancer.handleResolvedAddressGroups(servers, affinity);
+    verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
+    Subchannel subchannel = pickerCaptor.getValue().pickSubchannel(mockArgs).getSubchannel();
+    reset(mockHelper);
+
+    InOrder inOrder = inOrder(mockHelper);
+
+    Status error = Status.UNAVAILABLE.withDescription("boom!");
+    loadBalancer.handleSubchannelState(subchannel,
+        ConnectivityStateInfo.forTransientFailure(error));
+    inOrder.verify(mockHelper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture());
+    assertEquals(error, pickerCaptor.getValue().pickSubchannel(mockArgs).getStatus());
+
+    loadBalancer.handleSubchannelState(subchannel, ConnectivityStateInfo.forNonError(IDLE));
+    inOrder.verify(mockHelper).updateBalancingState(eq(IDLE), pickerCaptor.capture());
+    assertEquals(Status.OK, pickerCaptor.getValue().pickSubchannel(mockArgs).getStatus());
+
+    loadBalancer.handleSubchannelState(subchannel, ConnectivityStateInfo.forNonError(READY));
+    inOrder.verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture());
+    assertEquals(subchannel, pickerCaptor.getValue().pickSubchannel(mockArgs).getSubchannel());
+
+    verifyNoMoreInteractions(mockHelper);
+  }
+
+  @Test
+  public void nameResolutionError() throws Exception {
+    Status error = Status.NOT_FOUND.withDescription("nameResolutionError");
+    loadBalancer.handleNameResolutionError(error);
+    verify(mockHelper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture());
+    PickResult pickResult = pickerCaptor.getValue().pickSubchannel(mockArgs);
+    assertEquals(null, pickResult.getSubchannel());
+    assertEquals(error, pickResult.getStatus());
+    verify(mockSubchannel, never()).requestConnection();
+    verifyNoMoreInteractions(mockHelper);
+  }
+
+  @Test
+  public void nameResolutionSuccessAfterError() throws Exception {
+    InOrder inOrder = inOrder(mockHelper);
+
+    loadBalancer.handleNameResolutionError(Status.NOT_FOUND.withDescription("nameResolutionError"));
+    inOrder.verify(mockHelper)
+        .updateBalancingState(any(ConnectivityState.class), any(SubchannelPicker.class));
+    verify(mockSubchannel, never()).requestConnection();
+
+    loadBalancer.handleResolvedAddressGroups(servers, affinity);
+    inOrder.verify(mockHelper).createSubchannel(eq(servers), eq(Attributes.EMPTY));
+    inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
+    verify(mockSubchannel).requestConnection();
+
+    assertEquals(mockSubchannel, pickerCaptor.getValue().pickSubchannel(mockArgs)
+        .getSubchannel());
+
+    assertEquals(pickerCaptor.getValue().pickSubchannel(mockArgs),
+        pickerCaptor.getValue().pickSubchannel(mockArgs));
+
+    verifyNoMoreInteractions(mockHelper);
+  }
+
+  @Test
+  public void nameResolutionErrorWithStateChanges() throws Exception {
+    InOrder inOrder = inOrder(mockHelper);
+
+    loadBalancer.handleSubchannelState(mockSubchannel,
+        ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE));
+    Status error = Status.NOT_FOUND.withDescription("nameResolutionError");
+    loadBalancer.handleNameResolutionError(error);
+    inOrder.verify(mockHelper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture());
+
+    PickResult pickResult = pickerCaptor.getValue().pickSubchannel(mockArgs);
+    assertEquals(null, pickResult.getSubchannel());
+    assertEquals(error, pickResult.getStatus());
+
+    loadBalancer.handleSubchannelState(mockSubchannel, ConnectivityStateInfo.forNonError(READY));
+    Status error2 = Status.NOT_FOUND.withDescription("nameResolutionError2");
+    loadBalancer.handleNameResolutionError(error2);
+    inOrder.verify(mockHelper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture());
+
+    pickResult = pickerCaptor.getValue().pickSubchannel(mockArgs);
+    assertEquals(null, pickResult.getSubchannel());
+    assertEquals(error2, pickResult.getStatus());
+
+    verifyNoMoreInteractions(mockHelper);
+  }
+
+  @Test
+  public void requestConnection() {
+    loadBalancer.handleResolvedAddressGroups(servers, affinity);
+    loadBalancer.handleSubchannelState(mockSubchannel, ConnectivityStateInfo.forNonError(IDLE));
+    verify(mockHelper).updateBalancingState(eq(IDLE), pickerCaptor.capture());
+    SubchannelPicker picker = pickerCaptor.getValue();
+
+    verify(mockSubchannel).requestConnection();
+    picker.requestConnection();
+    verify(mockSubchannel, times(2)).requestConnection();
+  }
+
+  private static class FakeSocketAddress extends SocketAddress {
+    final String name;
+
+    FakeSocketAddress(String name) {
+      this.name = name;
+    }
+
+    @Override
+    public String toString() {
+      return "FakeSocketAddress-" + name;
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/ReplacingClassLoader.java b/core/src/test/java/io/grpc/ReplacingClassLoader.java
new file mode 100644
index 0000000..0cd34f8
--- /dev/null
+++ b/core/src/test/java/io/grpc/ReplacingClassLoader.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+
+/**
+ * A ClassLoader to help test service providers.
+ */
+public class ReplacingClassLoader extends ClassLoader {
+  private final String resource;
+  private final String replacement;
+
+  /**
+   * Construct an instance where {@code replacement} is loaded instead of {@code resource}.
+   */
+  public ReplacingClassLoader(ClassLoader parent, String resource, String replacement) {
+    super(parent);
+    this.resource = resource;
+    this.replacement = replacement;
+  }
+
+  @Override
+  public URL getResource(String name) {
+    if (resource.equals(name)) {
+      return getParent().getResource(replacement);
+    }
+    return super.getResource(name);
+  }
+
+  @Override
+  public Enumeration<URL> getResources(String name) throws IOException {
+    if (resource.equals(name)) {
+      return getParent().getResources(replacement);
+    }
+    return super.getResources(name);
+  }
+}
+
diff --git a/core/src/test/java/io/grpc/ServerInterceptorsTest.java b/core/src/test/java/io/grpc/ServerInterceptorsTest.java
new file mode 100644
index 0000000..e34c9df
--- /dev/null
+++ b/core/src/test/java/io/grpc/ServerInterceptorsTest.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.ServerCall.Listener;
+import io.grpc.internal.NoopServerCall;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link ServerInterceptors}. */
+@RunWith(JUnit4.class)
+public class ServerInterceptorsTest {
+  @Mock
+  private Marshaller<String> requestMarshaller;
+
+  @Mock
+  private Marshaller<Integer> responseMarshaller;
+
+  @Mock
+  private ServerCallHandler<String, Integer> handler;
+
+  @Mock
+  private ServerCall.Listener<String> listener;
+
+  private MethodDescriptor<String, Integer> flowMethod;
+
+  private ServerCall<String, Integer> call = new NoopServerCall<String, Integer>();
+
+  private ServerServiceDefinition serviceDefinition;
+
+  private final Metadata headers = new Metadata();
+
+  /** Set up for test. */
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    flowMethod = MethodDescriptor.<String, Integer>newBuilder()
+        .setType(MethodType.UNKNOWN)
+        .setFullMethodName("basic/flow")
+        .setRequestMarshaller(requestMarshaller)
+        .setResponseMarshaller(responseMarshaller)
+        .build();
+
+    Mockito.when(handler.startCall(
+        Mockito.<ServerCall<String, Integer>>any(), Mockito.<Metadata>any()))
+            .thenReturn(listener);
+
+    serviceDefinition = ServerServiceDefinition.builder(new ServiceDescriptor("basic", flowMethod))
+        .addMethod(flowMethod, handler).build();
+  }
+
+  /** Final checks for all tests. */
+  @After
+  public void makeSureExpectedMocksUnused() {
+    verifyZeroInteractions(requestMarshaller);
+    verifyZeroInteractions(responseMarshaller);
+    verifyZeroInteractions(listener);
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void npeForNullServiceDefinition() {
+    ServerServiceDefinition serviceDef = null;
+    ServerInterceptors.intercept(serviceDef, Arrays.<ServerInterceptor>asList());
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void npeForNullInterceptorList() {
+    ServerInterceptors.intercept(serviceDefinition, (List<ServerInterceptor>) null);
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void npeForNullInterceptor() {
+    ServerInterceptors.intercept(serviceDefinition, Arrays.asList((ServerInterceptor) null));
+  }
+
+  @Test
+  public void noop() {
+    assertSame(serviceDefinition,
+        ServerInterceptors.intercept(serviceDefinition, Arrays.<ServerInterceptor>asList()));
+  }
+
+  @Test
+  public void multipleInvocationsOfHandler() {
+    ServerInterceptor interceptor =
+        mock(ServerInterceptor.class, delegatesTo(new NoopInterceptor()));
+    ServerServiceDefinition intercepted
+        = ServerInterceptors.intercept(serviceDefinition, Arrays.asList(interceptor));
+    assertSame(listener,
+        getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers));
+    verify(interceptor).interceptCall(same(call), same(headers), anyCallHandler());
+    verify(handler).startCall(call, headers);
+    verifyNoMoreInteractions(interceptor, handler);
+
+    assertSame(listener,
+        getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers));
+    verify(interceptor, times(2))
+        .interceptCall(same(call), same(headers), anyCallHandler());
+    verify(handler, times(2)).startCall(call, headers);
+    verifyNoMoreInteractions(interceptor, handler);
+  }
+
+  @Test
+  public void correctHandlerCalled() {
+    @SuppressWarnings("unchecked")
+    ServerCallHandler<String, Integer> handler2 = mock(ServerCallHandler.class);
+    MethodDescriptor<String, Integer> flowMethod2 =
+        flowMethod.toBuilder().setFullMethodName("basic/flow2").build();
+    serviceDefinition = ServerServiceDefinition.builder(
+        new ServiceDescriptor("basic", flowMethod, flowMethod2))
+        .addMethod(flowMethod, handler)
+        .addMethod(flowMethod2, handler2).build();
+    ServerServiceDefinition intercepted = ServerInterceptors.intercept(
+        serviceDefinition, Arrays.<ServerInterceptor>asList(new NoopInterceptor()));
+    getMethod(intercepted, "basic/flow").getServerCallHandler().startCall(call, headers);
+    verify(handler).startCall(call, headers);
+    verifyNoMoreInteractions(handler);
+    verifyNoMoreInteractions(handler2);
+
+    getMethod(intercepted, "basic/flow2").getServerCallHandler().startCall(call, headers);
+    verify(handler2).startCall(call, headers);
+    verifyNoMoreInteractions(handler);
+    verifyNoMoreInteractions(handler2);
+  }
+
+  @Test
+  public void callNextTwice() {
+    ServerInterceptor interceptor = new ServerInterceptor() {
+      @Override
+      public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+          ServerCall<ReqT, RespT> call,
+          Metadata headers,
+          ServerCallHandler<ReqT, RespT> next) {
+        // Calling next twice is permitted, although should only rarely be useful.
+        assertSame(listener, next.startCall(call, headers));
+        return next.startCall(call, headers);
+      }
+    };
+    ServerServiceDefinition intercepted = ServerInterceptors.intercept(serviceDefinition,
+        interceptor);
+    assertSame(listener,
+        getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers));
+    verify(handler, times(2)).startCall(same(call), same(headers));
+    verifyNoMoreInteractions(handler);
+  }
+
+  @Test
+  public void ordered() {
+    final List<String> order = new ArrayList<>();
+    handler = new ServerCallHandler<String, Integer>() {
+          @Override
+          public ServerCall.Listener<String> startCall(
+              ServerCall<String, Integer> call,
+              Metadata headers) {
+            order.add("handler");
+            return listener;
+          }
+        };
+    ServerInterceptor interceptor1 = new ServerInterceptor() {
+          @Override
+          public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+              ServerCall<ReqT, RespT> call,
+              Metadata headers,
+              ServerCallHandler<ReqT, RespT> next) {
+            order.add("i1");
+            return next.startCall(call, headers);
+          }
+        };
+    ServerInterceptor interceptor2 = new ServerInterceptor() {
+          @Override
+          public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+              ServerCall<ReqT, RespT> call,
+              Metadata headers,
+              ServerCallHandler<ReqT, RespT> next) {
+            order.add("i2");
+            return next.startCall(call, headers);
+          }
+        };
+    ServerServiceDefinition serviceDefinition = ServerServiceDefinition.builder(
+        new ServiceDescriptor("basic", flowMethod))
+        .addMethod(flowMethod, handler).build();
+    ServerServiceDefinition intercepted = ServerInterceptors.intercept(
+        serviceDefinition, Arrays.asList(interceptor1, interceptor2));
+    assertSame(listener,
+        getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers));
+    assertEquals(Arrays.asList("i2", "i1", "handler"), order);
+  }
+
+  @Test
+  public void orderedForward() {
+    final List<String> order = new ArrayList<>();
+    handler = new ServerCallHandler<String, Integer>() {
+      @Override
+      public ServerCall.Listener<String> startCall(
+          ServerCall<String, Integer> call,
+          Metadata headers) {
+        order.add("handler");
+        return listener;
+      }
+    };
+    ServerInterceptor interceptor1 = new ServerInterceptor() {
+      @Override
+      public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+          ServerCall<ReqT, RespT> call,
+          Metadata headers,
+          ServerCallHandler<ReqT, RespT> next) {
+        order.add("i1");
+        return next.startCall(call, headers);
+      }
+    };
+    ServerInterceptor interceptor2 = new ServerInterceptor() {
+      @Override
+      public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+          ServerCall<ReqT, RespT> call,
+          Metadata headers,
+          ServerCallHandler<ReqT, RespT> next) {
+        order.add("i2");
+        return next.startCall(call, headers);
+      }
+    };
+    ServerServiceDefinition serviceDefinition = ServerServiceDefinition.builder(
+        new ServiceDescriptor("basic", flowMethod))
+        .addMethod(flowMethod, handler).build();
+    ServerServiceDefinition intercepted = ServerInterceptors.interceptForward(
+        serviceDefinition, interceptor1, interceptor2);
+    assertSame(listener,
+        getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers));
+    assertEquals(Arrays.asList("i1", "i2", "handler"), order);
+  }
+
+  @Test
+  public void argumentsPassed() {
+    @SuppressWarnings("unchecked")
+    final ServerCall<String, Integer> call2 = new NoopServerCall<String, Integer>();
+    @SuppressWarnings("unchecked")
+    final ServerCall.Listener<String> listener2 = mock(ServerCall.Listener.class);
+
+    ServerInterceptor interceptor = new ServerInterceptor() {
+        @SuppressWarnings("unchecked") // Lot's of casting for no benefit.  Not intended use.
+        @Override
+        public <R1, R2> ServerCall.Listener<R1> interceptCall(
+            ServerCall<R1, R2> call,
+            Metadata headers,
+            ServerCallHandler<R1, R2> next) {
+          assertSame(call, ServerInterceptorsTest.this.call);
+          assertSame(listener,
+              next.startCall((ServerCall<R1, R2>)call2, headers));
+          return (ServerCall.Listener<R1>) listener2;
+        }
+      };
+    ServerServiceDefinition intercepted = ServerInterceptors.intercept(
+        serviceDefinition, Arrays.asList(interceptor));
+    assertSame(listener2,
+        getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers));
+    verify(handler).startCall(call2, headers);
+  }
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void typedMarshalledMessages() {
+    final List<String> order = new ArrayList<>();
+    Marshaller<Holder> marshaller = new Marshaller<Holder>() {
+      @Override
+      public InputStream stream(Holder value) {
+        return value.get();
+      }
+
+      @Override
+      public Holder parse(InputStream stream) {
+        return new Holder(stream);
+      }
+    };
+
+    ServerCallHandler<Holder, Holder> handler2 = new ServerCallHandler<Holder, Holder>() {
+      @Override
+      public Listener<Holder> startCall(final ServerCall<Holder, Holder> call,
+                                        final Metadata headers) {
+        return new Listener<Holder>() {
+          @Override
+          public void onMessage(Holder message) {
+            order.add("handler");
+            call.sendMessage(message);
+          }
+        };
+      }
+    };
+
+    MethodDescriptor<Holder, Holder> wrappedMethod = MethodDescriptor.<Holder, Holder>newBuilder()
+        .setType(MethodType.UNKNOWN)
+        .setFullMethodName("basic/wrapped")
+        .setRequestMarshaller(marshaller)
+        .setResponseMarshaller(marshaller)
+        .build();
+    ServerServiceDefinition serviceDef = ServerServiceDefinition.builder(
+        new ServiceDescriptor("basic", wrappedMethod))
+        .addMethod(wrappedMethod, handler2).build();
+
+    ServerInterceptor interceptor1 = new ServerInterceptor() {
+      @Override
+      public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
+                                                        Metadata headers,
+                                                        ServerCallHandler<ReqT, RespT> next) {
+        ServerCall<ReqT, RespT> interceptedCall = new ForwardingServerCall
+            .SimpleForwardingServerCall<ReqT, RespT>(call) {
+          @Override
+          public void sendMessage(RespT message) {
+            order.add("i1sendMessage");
+            assertTrue(message instanceof Holder);
+            super.sendMessage(message);
+          }
+        };
+
+        ServerCall.Listener<ReqT> originalListener = next
+            .startCall(interceptedCall, headers);
+        return new ForwardingServerCallListener
+            .SimpleForwardingServerCallListener<ReqT>(originalListener) {
+          @Override
+          public void onMessage(ReqT message) {
+            order.add("i1onMessage");
+            assertTrue(message instanceof Holder);
+            super.onMessage(message);
+          }
+        };
+      }
+    };
+
+    ServerInterceptor interceptor2 = new ServerInterceptor() {
+      @Override
+      public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
+                                                        Metadata headers,
+                                                        ServerCallHandler<ReqT, RespT> next) {
+        ServerCall<ReqT, RespT> interceptedCall = new ForwardingServerCall
+            .SimpleForwardingServerCall<ReqT, RespT>(call) {
+          @Override
+          public void sendMessage(RespT message) {
+            order.add("i2sendMessage");
+            assertTrue(message instanceof InputStream);
+            super.sendMessage(message);
+          }
+        };
+
+        ServerCall.Listener<ReqT> originalListener = next
+            .startCall(interceptedCall, headers);
+        return new ForwardingServerCallListener
+            .SimpleForwardingServerCallListener<ReqT>(originalListener) {
+          @Override
+          public void onMessage(ReqT message) {
+            order.add("i2onMessage");
+            assertTrue(message instanceof InputStream);
+            super.onMessage(message);
+          }
+        };
+      }
+    };
+
+    ServerServiceDefinition intercepted = ServerInterceptors.intercept(serviceDef, interceptor1);
+    ServerServiceDefinition inputStreamMessageService = ServerInterceptors
+        .useInputStreamMessages(intercepted);
+    ServerServiceDefinition intercepted2 = ServerInterceptors
+        .intercept(inputStreamMessageService, interceptor2);
+    ServerMethodDefinition<InputStream, InputStream> serverMethod =
+        (ServerMethodDefinition<InputStream, InputStream>) intercepted2.getMethod("basic/wrapped");
+    ServerCall<InputStream, InputStream> call2 = new NoopServerCall<InputStream, InputStream>();
+    byte[] bytes = {};
+    serverMethod
+        .getServerCallHandler()
+        .startCall(call2, headers)
+        .onMessage(new ByteArrayInputStream(bytes));
+    assertEquals(
+        Arrays.asList("i2onMessage", "i1onMessage", "handler", "i1sendMessage", "i2sendMessage"),
+        order);
+  }
+
+  @SuppressWarnings("unchecked")
+  private static ServerMethodDefinition<String, Integer> getSoleMethod(
+      ServerServiceDefinition serviceDef) {
+    if (serviceDef.getMethods().size() != 1) {
+      throw new AssertionError("Not exactly one method present");
+    }
+    return (ServerMethodDefinition<String, Integer>) getOnlyElement(serviceDef.getMethods());
+  }
+
+  @SuppressWarnings("unchecked")
+  private static ServerMethodDefinition<String, Integer> getMethod(
+      ServerServiceDefinition serviceDef, String name) {
+    return (ServerMethodDefinition<String, Integer>) serviceDef.getMethod(name);
+  }
+
+  private ServerCallHandler<String, Integer> anyCallHandler() {
+    return Mockito.<ServerCallHandler<String, Integer>>any();
+  }
+
+  private static class NoopInterceptor implements ServerInterceptor {
+    @Override
+    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+        ServerCall<ReqT, RespT> call,
+        Metadata headers,
+        ServerCallHandler<ReqT, RespT> next) {
+      return next.startCall(call, headers);
+    }
+  }
+
+  private static class Holder {
+    private final InputStream inputStream;
+
+    Holder(InputStream inputStream) {
+      this.inputStream = inputStream;
+    }
+
+    public InputStream get() {
+      return inputStream;
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/ServerServiceDefinitionTest.java b/core/src/test/java/io/grpc/ServerServiceDefinitionTest.java
new file mode 100644
index 0000000..3250485
--- /dev/null
+++ b/core/src/test/java/io/grpc/ServerServiceDefinitionTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.util.Collections;
+import java.util.HashSet;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ServerServiceDefinition}. */
+@RunWith(JUnit4.class)
+public class ServerServiceDefinitionTest {
+  private String serviceName = "com.example.service";
+  private MethodDescriptor<String, Integer> method1 = MethodDescriptor.<String, Integer>newBuilder()
+      .setType(MethodDescriptor.MethodType.UNKNOWN)
+      .setFullMethodName(MethodDescriptor.generateFullMethodName(serviceName, "method1"))
+      .setRequestMarshaller(StringMarshaller.INSTANCE)
+      .setResponseMarshaller(IntegerMarshaller.INSTANCE)
+      .build();
+  private MethodDescriptor<String, Integer> diffMethod1 =
+      method1.toBuilder().setIdempotent(true).build();
+  private MethodDescriptor<String, Integer> method2 = method1.toBuilder()
+      .setFullMethodName(MethodDescriptor.generateFullMethodName(serviceName, "method2"))
+      .build();
+  private ServerCallHandler<String, Integer> methodHandler1
+      = new NoopServerCallHandler<String, Integer>();
+  private ServerCallHandler<String, Integer> methodHandler2
+      = new NoopServerCallHandler<String, Integer>();
+  private ServerMethodDefinition<String, Integer> methodDef1
+        = ServerMethodDefinition.create(method1, methodHandler1);
+  private ServerMethodDefinition<String, Integer> methodDef2
+        = ServerMethodDefinition.create(method2, methodHandler2);
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void noMethods() {
+    ServiceDescriptor sd = new ServiceDescriptor(serviceName);
+    ServerServiceDefinition ssd = ServerServiceDefinition.builder(sd)
+        .build();
+    assertSame(sd, ssd.getServiceDescriptor());
+    assertEquals(Collections.<MethodDescriptor<?, ?>>emptyList(),
+        ssd.getServiceDescriptor().getMethods());
+  }
+
+  @Test
+  public void addMethod_twoArg() {
+    ServiceDescriptor sd = new ServiceDescriptor(serviceName, method1, method2);
+    ServerServiceDefinition ssd = ServerServiceDefinition.builder(sd)
+        .addMethod(method1, methodHandler1)
+        .addMethod(method2, methodHandler2)
+        .build();
+    assertSame(sd, ssd.getServiceDescriptor());
+    for (ServerMethodDefinition<?, ?> serverMethod : ssd.getMethods()) {
+      MethodDescriptor<?, ?> method = serverMethod.getMethodDescriptor();
+      if (method1.equals(method)) {
+        assertSame(methodHandler1, serverMethod.getServerCallHandler());
+      } else if (method2.equals(method)) {
+        assertSame(methodHandler2, serverMethod.getServerCallHandler());
+      } else {
+        fail("Unexpected method descriptor: " + method.getFullMethodName());
+      }
+    }
+  }
+
+  @Test
+  public void addMethod_duplicateName() {
+    ServiceDescriptor sd = new ServiceDescriptor(serviceName, method1);
+    ServerServiceDefinition.Builder ssd = ServerServiceDefinition.builder(sd)
+        .addMethod(method1, methodHandler1);
+    thrown.expect(IllegalStateException.class);
+    ssd.addMethod(diffMethod1, methodHandler2)
+        .build();
+  }
+
+  @Test
+  public void buildMisaligned_extraMethod() {
+    ServiceDescriptor sd = new ServiceDescriptor(serviceName);
+    ServerServiceDefinition.Builder ssd = ServerServiceDefinition.builder(sd)
+        .addMethod(methodDef1);
+    thrown.expect(IllegalStateException.class);
+    ssd.build();
+  }
+
+  @Test
+  public void buildMisaligned_diffMethodInstance() {
+    ServiceDescriptor sd = new ServiceDescriptor(serviceName, method1);
+    ServerServiceDefinition.Builder ssd = ServerServiceDefinition.builder(sd)
+        .addMethod(diffMethod1, methodHandler1);
+    thrown.expect(IllegalStateException.class);
+    ssd.build();
+  }
+
+  @Test
+  public void buildMisaligned_missingMethod() {
+    ServiceDescriptor sd = new ServiceDescriptor(serviceName, method1);
+    ServerServiceDefinition.Builder ssd = ServerServiceDefinition.builder(sd);
+    thrown.expect(IllegalStateException.class);
+    ssd.build();
+  }
+
+  @Test
+  public void builderWithServiceName() {
+    ServerServiceDefinition ssd = ServerServiceDefinition.builder(serviceName)
+        .addMethod(methodDef1)
+        .addMethod(methodDef2)
+        .build();
+    assertEquals(serviceName, ssd.getServiceDescriptor().getName());
+
+    HashSet<MethodDescriptor<?, ?>> goldenMethods = new HashSet<MethodDescriptor<?, ?>>();
+    goldenMethods.add(method1);
+    goldenMethods.add(method2);
+    assertEquals(goldenMethods,
+        new HashSet<MethodDescriptor<?, ?>>(ssd.getServiceDescriptor().getMethods()));
+
+    HashSet<ServerMethodDefinition<?, ?>> goldenMethodDefs
+        = new HashSet<ServerMethodDefinition<?, ?>>();
+    goldenMethodDefs.add(methodDef1);
+    goldenMethodDefs.add(methodDef2);
+    assertEquals(goldenMethodDefs, new HashSet<ServerMethodDefinition<?, ?>>(ssd.getMethods()));
+  }
+
+  @Test
+  public void builderWithServiceName_noMethods() {
+    ServerServiceDefinition ssd = ServerServiceDefinition.builder(serviceName)
+        .build();
+    assertEquals(Collections.<MethodDescriptor<?, ?>>emptyList(),
+        ssd.getServiceDescriptor().getMethods());
+    assertEquals(Collections.<ServerMethodDefinition<?, ?>>emptySet(),
+        new HashSet<ServerMethodDefinition<?, ?>>(ssd.getMethods()));
+  }
+
+  private static class NoopServerCallHandler<ReqT, RespT>
+      implements ServerCallHandler<ReqT, RespT> {
+    @Override
+    public ServerCall.Listener<ReqT> startCall(ServerCall<ReqT, RespT> call, Metadata headers) {
+      throw new UnsupportedOperationException();
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/ServiceDescriptorTest.java b/core/src/test/java/io/grpc/ServiceDescriptorTest.java
new file mode 100644
index 0000000..f3ed69f
--- /dev/null
+++ b/core/src/test/java/io/grpc/ServiceDescriptorTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.testing.TestMethodDescriptors;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link ServiceDescriptor}.
+ */
+@RunWith(JUnit4.class)
+public class ServiceDescriptorTest {
+
+  @Rule
+  public final ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void failsOnNullName() {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("name");
+
+    new ServiceDescriptor(null, Collections.<MethodDescriptor<?, ?>>emptyList());
+  }
+
+  @Test
+  public void failsOnNullMethods() {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("methods");
+
+    new ServiceDescriptor("name", (Collection<MethodDescriptor<?, ?>>) null);
+  }
+
+  @Test
+  public void failsOnNullMethod() {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("method");
+
+    new ServiceDescriptor("name", (Collections.<MethodDescriptor<?, ?>>singletonList(null)));
+  }
+
+  @Test
+  public void failsOnNonMatchingNames() {
+    List<MethodDescriptor<?, ?>> descriptors = Collections.<MethodDescriptor<?, ?>>singletonList(
+        MethodDescriptor.<Void, Void>newBuilder()
+          .setType(MethodType.UNARY)
+          .setFullMethodName(MethodDescriptor.generateFullMethodName("wrongservice", "method"))
+          .setRequestMarshaller(TestMethodDescriptors.voidMarshaller())
+          .setResponseMarshaller(TestMethodDescriptors.voidMarshaller())
+          .build());
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("service names");
+
+    new ServiceDescriptor("name", descriptors);
+  }
+
+  @Test
+  public void failsOnNonDuplicateNames() {
+    List<MethodDescriptor<?, ?>> descriptors = Arrays.<MethodDescriptor<?, ?>>asList(
+        MethodDescriptor.<Void, Void>newBuilder()
+          .setType(MethodType.UNARY)
+          .setFullMethodName(MethodDescriptor.generateFullMethodName("name", "method"))
+          .setRequestMarshaller(TestMethodDescriptors.voidMarshaller())
+          .setResponseMarshaller(TestMethodDescriptors.voidMarshaller())
+          .build(),
+        MethodDescriptor.<Void, Void>newBuilder()
+          .setType(MethodType.UNARY)
+          .setFullMethodName(MethodDescriptor.generateFullMethodName("name", "method"))
+          .setRequestMarshaller(TestMethodDescriptors.voidMarshaller())
+          .setResponseMarshaller(TestMethodDescriptors.voidMarshaller())
+          .build());
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("duplicate");
+
+    new ServiceDescriptor("name", descriptors);
+  }
+
+  @Test
+  public void toStringTest() {
+    ServiceDescriptor descriptor = new ServiceDescriptor("package.service",
+        Arrays.<MethodDescriptor<?, ?>>asList(
+        MethodDescriptor.<Void, Void>newBuilder()
+          .setType(MethodType.UNARY)
+          .setFullMethodName(MethodDescriptor.generateFullMethodName("package.service",
+            "methodOne"))
+          .setRequestMarshaller(TestMethodDescriptors.voidMarshaller())
+          .setResponseMarshaller(TestMethodDescriptors.voidMarshaller())
+          .build(),
+        MethodDescriptor.<Void, Void>newBuilder()
+          .setType(MethodType.UNARY)
+          .setFullMethodName(MethodDescriptor.generateFullMethodName("package.service",
+            "methodTwo"))
+          .setRequestMarshaller(TestMethodDescriptors.voidMarshaller())
+          .setResponseMarshaller(TestMethodDescriptors.voidMarshaller())
+          .build()));
+
+    String toString = descriptor.toString();
+    assertTrue(toString.contains("ServiceDescriptor"));
+    assertTrue(toString.contains("name=package.service"));
+    assertTrue(toString.contains("package.service/methodOne"));
+    assertTrue(toString.contains("package.service/methodTwo"));
+  }
+}
diff --git a/core/src/test/java/io/grpc/ServiceProvidersTest.java b/core/src/test/java/io/grpc/ServiceProvidersTest.java
new file mode 100644
index 0000000..d069501
--- /dev/null
+++ b/core/src/test/java/io/grpc/ServiceProvidersTest.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import io.grpc.InternalServiceProviders.PriorityAccessor;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ServiceConfigurationError;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ServiceProviders}. */
+@RunWith(JUnit4.class)
+public class ServiceProvidersTest {
+  private static final List<Class<?>> NO_HARDCODED = Collections.emptyList();
+  private static final PriorityAccessor<ServiceProvidersTestAbstractProvider> ACCESSOR =
+      new PriorityAccessor<ServiceProvidersTestAbstractProvider>() {
+        @Override
+        public boolean isAvailable(ServiceProvidersTestAbstractProvider provider) {
+          return provider.isAvailable();
+        }
+
+        @Override
+        public int getPriority(ServiceProvidersTestAbstractProvider provider) {
+          return provider.priority();
+        }
+      };
+  private final String serviceFile =
+      "META-INF/services/io.grpc.ServiceProvidersTestAbstractProvider";
+
+  @Test
+  public void contextClassLoaderProvider() {
+    ClassLoader ccl = Thread.currentThread().getContextClassLoader();
+    try {
+      ClassLoader cl = new ReplacingClassLoader(
+          getClass().getClassLoader(),
+          serviceFile,
+          "io/grpc/ServiceProvidersTestAbstractProvider-multipleProvider.txt");
+
+      // test that the context classloader is used as fallback
+      ClassLoader rcll = new ReplacingClassLoader(
+          getClass().getClassLoader(),
+          serviceFile,
+          "io/grpc/ServiceProvidersTestAbstractProvider-empty.txt");
+      Thread.currentThread().setContextClassLoader(rcll);
+      assertEquals(
+          Available7Provider.class,
+          ServiceProviders.load(
+              ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR).getClass());
+    } finally {
+      Thread.currentThread().setContextClassLoader(ccl);
+    }
+  }
+
+  @Test
+  public void noProvider() {
+    ClassLoader ccl = Thread.currentThread().getContextClassLoader();
+    try {
+      ClassLoader cl = new ReplacingClassLoader(
+          getClass().getClassLoader(),
+          serviceFile,
+          "io/grpc/ServiceProvidersTestAbstractProvider-doesNotExist.txt");
+      Thread.currentThread().setContextClassLoader(cl);
+      assertNull(ServiceProviders.load(
+          ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR));
+    } finally {
+      Thread.currentThread().setContextClassLoader(ccl);
+    }
+  }
+
+  @Test
+  public void multipleProvider() throws Exception {
+    ClassLoader cl = new ReplacingClassLoader(getClass().getClassLoader(), serviceFile,
+        "io/grpc/ServiceProvidersTestAbstractProvider-multipleProvider.txt");
+    assertSame(
+        Available7Provider.class,
+        ServiceProviders.load(
+            ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR).getClass());
+
+    List<ServiceProvidersTestAbstractProvider> providers = ServiceProviders.loadAll(
+        ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR);
+    assertEquals(3, providers.size());
+    assertEquals(Available7Provider.class, providers.get(0).getClass());
+    assertEquals(Available5Provider.class, providers.get(1).getClass());
+    assertEquals(Available0Provider.class, providers.get(2).getClass());
+  }
+
+  @Test
+  public void unavailableProvider() {
+    // tries to load Available7 and UnavailableProvider, which has priority 10
+    ClassLoader cl = new ReplacingClassLoader(getClass().getClassLoader(), serviceFile,
+        "io/grpc/ServiceProvidersTestAbstractProvider-unavailableProvider.txt");
+    assertEquals(
+        Available7Provider.class,
+        ServiceProviders.load(
+            ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR).getClass());
+  }
+
+  @Test
+  public void unknownClassProvider() {
+    ClassLoader cl = new ReplacingClassLoader(getClass().getClassLoader(), serviceFile,
+        "io/grpc/ServiceProvidersTestAbstractProvider-unknownClassProvider.txt");
+    try {
+      ServiceProvidersTestAbstractProvider ignored = ServiceProviders.load(
+          ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR);
+      fail("Exception expected");
+    } catch (ServiceConfigurationError e) {
+      // noop
+    }
+  }
+
+  @Test
+  public void exceptionSurfacedToCaller_failAtInit() {
+    ClassLoader cl = new ReplacingClassLoader(getClass().getClassLoader(), serviceFile,
+        "io/grpc/ServiceProvidersTestAbstractProvider-failAtInitProvider.txt");
+    try {
+      // Even though there is a working provider, if any providers fail then we should fail
+      // completely to avoid returning something unexpected.
+      ServiceProvidersTestAbstractProvider ignored = ServiceProviders.load(
+          ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR);
+      fail("Expected exception");
+    } catch (ServiceConfigurationError expected) {
+      // noop
+    }
+  }
+
+  @Test
+  public void exceptionSurfacedToCaller_failAtPriority() {
+    ClassLoader cl = new ReplacingClassLoader(getClass().getClassLoader(), serviceFile,
+        "io/grpc/ServiceProvidersTestAbstractProvider-failAtPriorityProvider.txt");
+    try {
+      // The exception should be surfaced to the caller
+      ServiceProvidersTestAbstractProvider ignored = ServiceProviders.load(
+          ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR);
+      fail("Expected exception");
+    } catch (FailAtPriorityProvider.PriorityException expected) {
+      // noop
+    }
+  }
+
+  @Test
+  public void exceptionSurfacedToCaller_failAtAvailable() {
+    ClassLoader cl = new ReplacingClassLoader(getClass().getClassLoader(), serviceFile,
+        "io/grpc/ServiceProvidersTestAbstractProvider-failAtAvailableProvider.txt");
+    try {
+      // The exception should be surfaced to the caller
+      ServiceProvidersTestAbstractProvider ignored = ServiceProviders.load(
+          ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR);
+      fail("Expected exception");
+    } catch (FailAtAvailableProvider.AvailableException expected) {
+      // noop
+    }
+  }
+
+  @Test
+  public void getCandidatesViaHardCoded_multipleProvider() throws Exception {
+    Iterator<ServiceProvidersTestAbstractProvider> candidates =
+        ServiceProviders.getCandidatesViaHardCoded(
+            ServiceProvidersTestAbstractProvider.class,
+            ImmutableList.<Class<?>>of(
+                Available7Provider.class,
+                Available0Provider.class))
+            .iterator();
+    assertEquals(Available7Provider.class, candidates.next().getClass());
+    assertEquals(Available0Provider.class, candidates.next().getClass());
+    assertFalse(candidates.hasNext());
+  }
+
+  @Test
+  public void getCandidatesViaHardCoded_failAtInit() throws Exception {
+    try {
+      Iterable<ServiceProvidersTestAbstractProvider> ignored =
+          ServiceProviders.getCandidatesViaHardCoded(
+              ServiceProvidersTestAbstractProvider.class,
+              Collections.<Class<?>>singletonList(FailAtInitProvider.class));
+      fail("Expected exception");
+    } catch (ServiceConfigurationError expected) {
+      // noop
+    }
+  }
+
+  @Test
+  public void getCandidatesViaHardCoded_failAtInit_moreCandidates() throws Exception {
+    try {
+      Iterable<ServiceProvidersTestAbstractProvider> ignored =
+          ServiceProviders.getCandidatesViaHardCoded(
+              ServiceProvidersTestAbstractProvider.class,
+              ImmutableList.<Class<?>>of(FailAtInitProvider.class, Available0Provider.class));
+      fail("Expected exception");
+    } catch (ServiceConfigurationError expected) {
+      // noop
+    }
+  }
+
+  @Test
+  public void create_throwsErrorOnMisconfiguration() throws Exception {
+    class PrivateClass {}
+
+    try {
+      ServiceProvidersTestAbstractProvider ignored = ServiceProviders.create(
+          ServiceProvidersTestAbstractProvider.class, PrivateClass.class);
+      fail("Expected exception");
+    } catch (ServiceConfigurationError expected) {
+      assertTrue("Expected ClassCastException cause: " + expected.getCause(),
+          expected.getCause() instanceof ClassCastException);
+    }
+  }
+
+  private static class BaseProvider extends ServiceProvidersTestAbstractProvider {
+    private final boolean isAvailable;
+    private final int priority;
+
+    public BaseProvider(boolean isAvailable, int priority) {
+      this.isAvailable = isAvailable;
+      this.priority = priority;
+    }
+
+    @Override
+    public boolean isAvailable() {
+      return isAvailable;
+    }
+
+    @Override
+    public int priority() {
+      return priority;
+    }
+  }
+
+  public static final class Available0Provider extends BaseProvider {
+    public Available0Provider() {
+      super(true, 0);
+    }
+  }
+
+  public static final class Available5Provider extends BaseProvider {
+    public Available5Provider() {
+      super(true, 5);
+    }
+  }
+
+  public static final class Available7Provider extends BaseProvider {
+    public Available7Provider() {
+      super(true, 7);
+    }
+  }
+
+  public static final class UnavailableProvider extends BaseProvider {
+    public UnavailableProvider() {
+      super(false, 10);
+    }
+  }
+
+  public static final class FailAtInitProvider extends ServiceProvidersTestAbstractProvider {
+    public FailAtInitProvider() {
+      throw new RuntimeException("intentionally broken");
+    }
+
+    @Override
+    public boolean isAvailable() {
+      return true;
+    }
+
+    @Override
+    public int priority() {
+      return 0;
+    }
+  }
+
+  public static final class FailAtPriorityProvider extends ServiceProvidersTestAbstractProvider {
+    @Override
+    public boolean isAvailable() {
+      return true;
+    }
+
+    @Override
+    public int priority() {
+      throw new PriorityException();
+    }
+
+    public static final class PriorityException extends RuntimeException {}
+  }
+
+  public static final class FailAtAvailableProvider extends ServiceProvidersTestAbstractProvider {
+    @Override
+    public boolean isAvailable() {
+      throw new AvailableException();
+    }
+
+    @Override
+    public int priority() {
+      return 0;
+    }
+
+    public static final class AvailableException extends RuntimeException {}
+  }
+}
diff --git a/core/src/test/java/io/grpc/ServiceProvidersTestAbstractProvider.java b/core/src/test/java/io/grpc/ServiceProvidersTestAbstractProvider.java
new file mode 100644
index 0000000..cc99897
--- /dev/null
+++ b/core/src/test/java/io/grpc/ServiceProvidersTestAbstractProvider.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+/**
+ * A provider class for the {@link ServiceProvidersTest}.
+ */
+// Nesting the class inside the test leads to a class name that has a '$' in it, which causes
+// issues with our build pipeline.
+abstract class ServiceProvidersTestAbstractProvider {
+  abstract boolean isAvailable();
+
+  abstract int priority();
+}
diff --git a/core/src/test/java/io/grpc/ServiceProvidersTestUtil.java b/core/src/test/java/io/grpc/ServiceProvidersTestUtil.java
new file mode 100644
index 0000000..aec493d
--- /dev/null
+++ b/core/src/test/java/io/grpc/ServiceProvidersTestUtil.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.google.common.collect.Iterators;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+final class ServiceProvidersTestUtil {
+  /**
+   * Creates an iterator from the callable class via reflection, and checks that all expected
+   * classes were loaded.
+   *
+   * <p>{@code callableClassName} is a {@code Callable<Iterable<Class<?>>} rather than the iterable
+   * class name itself so that the iterable class can be non-public.
+   *
+   * <p>We accept class names as input so that we can test against classes not in the
+   * testing class path.
+   */
+  static void testHardcodedClasses(
+      String callableClassName,
+      ClassLoader cl,
+      Set<String> hardcodedClassNames) throws Exception {
+    final Set<String> notLoaded = new HashSet<String>(hardcodedClassNames);
+    cl = new ClassLoader(cl) {
+      @Override
+      public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        if (notLoaded.remove(name)) {
+          throw new ClassNotFoundException();
+        } else {
+          return super.loadClass(name, resolve);
+        }
+      }
+    };
+    cl = new StaticTestingClassLoader(cl, Pattern.compile("io\\.grpc\\.[^.]*"));
+    // Some classes fall back to the context class loader.
+    // Ensure that the context class loader is not an accidental backdoor.
+    ClassLoader ccl = Thread.currentThread().getContextClassLoader();
+    try {
+      Thread.currentThread().setContextClassLoader(cl);
+      Object[] results = Iterators.toArray(
+          invokeIteratorCallable(callableClassName, cl), Object.class);
+      assertWithMessage("The Iterable loaded a class that was not in hardcodedClassNames")
+          .that(results).isEmpty();
+      assertWithMessage(
+          "The Iterable did not attempt to load some classes from hardcodedClassNames")
+          .that(notLoaded).isEmpty();
+    } finally {
+      Thread.currentThread().setContextClassLoader(ccl);
+    }
+  }
+
+  private static Iterator<?> invokeIteratorCallable(
+      String callableClassName, ClassLoader cl) throws Exception {
+    Class<?> klass = Class.forName(callableClassName, true, cl);
+    Constructor<?> ctor = klass.getDeclaredConstructor();
+    Object instance = ctor.newInstance();
+    Method callMethod = klass.getMethod("call");
+    return (Iterator<?>) callMethod.invoke(instance);
+  }
+}
diff --git a/core/src/test/java/io/grpc/StatusExceptionTest.java b/core/src/test/java/io/grpc/StatusExceptionTest.java
new file mode 100644
index 0000000..dd0d12d
--- /dev/null
+++ b/core/src/test/java/io/grpc/StatusExceptionTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link StatusException}.
+ */
+@RunWith(JUnit4.class)
+public class StatusExceptionTest {
+
+  @Test
+  public void internalCtorRemovesStack() {
+    StackTraceElement[] trace =
+        new StatusException(Status.CANCELLED, null, false) {}.getStackTrace();
+
+    assertThat(trace).isEmpty();
+  }
+
+  @Test
+  public void normalCtorKeepsStack() {
+    StackTraceElement[] trace =
+        new StatusException(Status.CANCELLED, null) {}.getStackTrace();
+
+    assertThat(trace).isNotEmpty();
+  }
+
+  @Test
+  public void extendPreservesStack() {
+    StackTraceElement[] trace = new StatusException(Status.CANCELLED) {}.getStackTrace();
+
+    assertThat(trace).isNotEmpty();
+  }
+
+  @Test
+  public void extendAndOverridePreservesStack() {
+    final StackTraceElement element = new StackTraceElement("a", "b", "c", 4);
+    StatusException exception = new StatusException(Status.CANCELLED, new Metadata()) {
+
+      @Override
+      public synchronized Throwable fillInStackTrace() {
+        setStackTrace(new StackTraceElement[]{element});
+        return this;
+      }
+    };
+    assertThat(exception.getStackTrace()).asList().containsExactly(element);
+  }
+}
diff --git a/core/src/test/java/io/grpc/StatusRuntimeExceptionTest.java b/core/src/test/java/io/grpc/StatusRuntimeExceptionTest.java
new file mode 100644
index 0000000..2c3bf7e
--- /dev/null
+++ b/core/src/test/java/io/grpc/StatusRuntimeExceptionTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link StatusRuntimeException}.
+ */
+@RunWith(JUnit4.class)
+public class StatusRuntimeExceptionTest {
+
+  @Test
+  public void internalCtorRemovesStack() {
+    StackTraceElement[] trace =
+        new StatusRuntimeException(Status.CANCELLED, null, false) {}.getStackTrace();
+
+    assertThat(trace).isEmpty();
+  }
+
+  @Test
+  public void normalCtorKeepsStack() {
+    StackTraceElement[] trace =
+        new StatusRuntimeException(Status.CANCELLED, null) {}.getStackTrace();
+
+    assertThat(trace).isNotEmpty();
+  }
+
+  @Test
+  public void extendPreservesStack() {
+    StackTraceElement[] trace = new StatusRuntimeException(Status.CANCELLED) {}.getStackTrace();
+
+    assertThat(trace).isNotEmpty();
+  }
+
+  @Test
+  public void extendAndOverridePreservesStack() {
+    final StackTraceElement element = new StackTraceElement("a", "b", "c", 4);
+    StatusRuntimeException exception =
+        new StatusRuntimeException(Status.CANCELLED, new Metadata()) {
+
+      @Override
+      public synchronized Throwable fillInStackTrace() {
+        setStackTrace(new StackTraceElement[]{element});
+        return this;
+      }
+    };
+    assertThat(exception.getStackTrace()).asList().containsExactly(element);
+  }
+}
diff --git a/core/src/test/java/io/grpc/StatusTest.java b/core/src/test/java/io/grpc/StatusTest.java
new file mode 100644
index 0000000..9abeba4
--- /dev/null
+++ b/core/src/test/java/io/grpc/StatusTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import io.grpc.Status.Code;
+import java.nio.charset.Charset;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Status}. */
+@RunWith(JUnit4.class)
+public class StatusTest {
+  private final Charset ascii = Charset.forName("US-ASCII");
+
+  @Test
+  public void verifyExceptionMessage() {
+    assertEquals("UNKNOWN", Status.UNKNOWN.asRuntimeException().getMessage());
+    assertEquals("CANCELLED: This is a test",
+        Status.CANCELLED.withDescription("This is a test").asRuntimeException().getMessage());
+    assertEquals("UNKNOWN", Status.UNKNOWN.asException().getMessage());
+    assertEquals("CANCELLED: This is a test",
+        Status.CANCELLED.withDescription("This is a test").asException().getMessage());
+  }
+
+  @Test
+  public void impossibleCodeValue() {
+    assertEquals(Code.UNKNOWN, Status.fromCodeValue(-1).getCode());
+  }
+
+  @Test
+  public void sameCauseReturnsSelf() {
+    assertSame(Status.CANCELLED, Status.CANCELLED.withCause(null));
+  }
+
+  @Test
+  public void sameDescriptionReturnsSelf() {
+    assertSame(Status.CANCELLED, Status.CANCELLED.withDescription(null));
+    assertSame(Status.CANCELLED, Status.CANCELLED.augmentDescription(null));
+  }
+
+  @Test
+  public void useObjectHashCode() {
+    assertEquals(Status.CANCELLED.hashCode(), System.identityHashCode(Status.CANCELLED));
+  }
+
+  @Test
+  public void metadataEncode_lowAscii() {
+    byte[] b = Status.MESSAGE_KEY.toBytes("my favorite character is \u0000");
+    assertEquals("my favorite character is %00", new String(b, ascii));
+  }
+
+  @Test
+  public void metadataEncode_percent() {
+    byte[] b = Status.MESSAGE_KEY.toBytes("my favorite character is %");
+    assertEquals("my favorite character is %25", new String(b, ascii));
+  }
+
+  @Test
+  public void metadataEncode_surrogatePair() {
+    byte[] b = Status.MESSAGE_KEY.toBytes("my favorite character is 𐀁");
+    assertEquals("my favorite character is %F0%90%80%81", new String(b, ascii));
+  }
+
+  @Test
+  public void metadataEncode_unmatchedHighSurrogate() {
+    byte[] b = Status.MESSAGE_KEY.toBytes("my favorite character is " + ((char) 0xD801));
+    assertEquals("my favorite character is ?", new String(b, ascii));
+  }
+
+  @Test
+  public void metadataEncode_unmatchedLowSurrogate() {
+    byte[] b = Status.MESSAGE_KEY.toBytes("my favorite character is " + ((char)0xDC37));
+    assertEquals("my favorite character is ?", new String(b, ascii));
+  }
+
+  @Test
+  public void metadataEncode_maxSurrogatePair() {
+    byte[] b = Status.MESSAGE_KEY.toBytes(
+        "my favorite character is " + ((char)0xDBFF) + ((char)0xDFFF));
+    assertEquals("my favorite character is %F4%8F%BF%BF", new String(b, ascii));
+  }
+
+  @Test
+  public void metadataDecode_ascii() {
+    String s = Status.MESSAGE_KEY.parseBytes(new byte[]{'H', 'e', 'l', 'l', 'o'});
+    assertEquals("Hello", s);
+  }
+
+  @Test
+  public void metadataDecode_percent() {
+    String s = Status.MESSAGE_KEY.parseBytes(new byte[]{'H', '%', '6', '1', 'o'});
+    assertEquals("Hao", s);
+  }
+
+  @Test
+  public void metadataDecode_percentUnderflow() {
+    String s = Status.MESSAGE_KEY.parseBytes(new byte[]{'H', '%', '6'});
+    assertEquals("H%6", s);
+  }
+
+  @Test
+  public void metadataDecode_surrogate() {
+    String s = Status.MESSAGE_KEY.parseBytes(
+        new byte[]{'%', 'F', '0', '%', '9', '0', '%', '8', '0', '%', '8', '1'});
+    assertEquals("𐀁", s);
+  }
+
+  @Test
+  public void metadataDecode_badEncoding() {
+    String s = Status.MESSAGE_KEY.parseBytes(new byte[]{'%', 'G', '0'});
+    assertEquals("%G0", s);
+  }
+}
diff --git a/core/src/test/java/io/grpc/StringMarshaller.java b/core/src/test/java/io/grpc/StringMarshaller.java
new file mode 100644
index 0000000..af53d42
--- /dev/null
+++ b/core/src/test/java/io/grpc/StringMarshaller.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static com.google.common.base.Charsets.UTF_8;
+
+import com.google.common.io.ByteStreams;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Marshalls UTF-8 encoded strings. */
+public class StringMarshaller implements MethodDescriptor.Marshaller<String> {
+  public static StringMarshaller INSTANCE = new StringMarshaller();
+
+  @Override
+  public InputStream stream(String value) {
+    return new ByteArrayInputStream(value.getBytes(UTF_8));
+  }
+
+  @Override
+  public String parse(InputStream stream) {
+    try {
+      return new String(ByteStreams.toByteArray(stream), UTF_8);
+    } catch (IOException ex) {
+      throw new RuntimeException(ex);
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/inprocess/InProcessChannelBuilderTest.java b/core/src/test/java/io/grpc/inprocess/InProcessChannelBuilderTest.java
new file mode 100644
index 0000000..58efb7f
--- /dev/null
+++ b/core/src/test/java/io/grpc/inprocess/InProcessChannelBuilderTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.inprocess;
+
+import static io.grpc.internal.GrpcUtil.TIMER_SERVICE;
+import static org.junit.Assert.assertSame;
+
+import io.grpc.internal.ClientTransportFactory;
+import io.grpc.internal.FakeClock;
+import io.grpc.internal.SharedResourceHolder;
+import java.util.concurrent.ScheduledExecutorService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link InProcessChannelBuilder}.
+ */
+@RunWith(JUnit4.class)
+public class InProcessChannelBuilderTest {
+  @Test
+  public void scheduledExecutorService_default() {
+    InProcessChannelBuilder builder = InProcessChannelBuilder.forName("foo");
+    ClientTransportFactory clientTransportFactory = builder.buildTransportFactory();
+    assertSame(
+        SharedResourceHolder.get(TIMER_SERVICE),
+        clientTransportFactory.getScheduledExecutorService());
+
+    SharedResourceHolder.release(
+        TIMER_SERVICE, clientTransportFactory.getScheduledExecutorService());
+    clientTransportFactory.close();
+  }
+
+  @Test
+  public void scheduledExecutorService_custom() {
+    InProcessChannelBuilder builder = InProcessChannelBuilder.forName("foo");
+    ScheduledExecutorService scheduledExecutorService =
+        new FakeClock().getScheduledExecutorService();
+
+    InProcessChannelBuilder builder1 = builder.scheduledExecutorService(scheduledExecutorService);
+    assertSame(builder, builder1);
+
+    ClientTransportFactory clientTransportFactory = builder1.buildTransportFactory();
+
+    assertSame(scheduledExecutorService, clientTransportFactory.getScheduledExecutorService());
+
+    clientTransportFactory.close();
+  }
+}
diff --git a/core/src/test/java/io/grpc/inprocess/InProcessClientTransportFactoryTest.java b/core/src/test/java/io/grpc/inprocess/InProcessClientTransportFactoryTest.java
new file mode 100644
index 0000000..4dcd4b8
--- /dev/null
+++ b/core/src/test/java/io/grpc/inprocess/InProcessClientTransportFactoryTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.inprocess;
+
+import io.grpc.internal.ClientTransportFactory;
+import io.grpc.internal.testing.AbstractClientTransportFactoryTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class InProcessClientTransportFactoryTest extends AbstractClientTransportFactoryTest {
+
+  @Override
+  protected ClientTransportFactory newClientTransportFactory() {
+    return InProcessChannelBuilder.forName("test-transport").buildTransportFactory();
+  }
+}
diff --git a/core/src/test/java/io/grpc/inprocess/InProcessServerBuilderTest.java b/core/src/test/java/io/grpc/inprocess/InProcessServerBuilderTest.java
new file mode 100644
index 0000000..9485939
--- /dev/null
+++ b/core/src/test/java/io/grpc/inprocess/InProcessServerBuilderTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.inprocess;
+
+import static io.grpc.internal.GrpcUtil.TIMER_SERVICE;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+import io.grpc.ServerStreamTracer.Factory;
+import io.grpc.internal.FakeClock;
+import io.grpc.internal.ObjectPool;
+import io.grpc.internal.SharedResourcePool;
+import java.util.ArrayList;
+import java.util.concurrent.ScheduledExecutorService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link InProcessServerBuilder}.
+ */
+@RunWith(JUnit4.class)
+public class InProcessServerBuilderTest {
+
+  @Test
+  public void generateName() {
+    String name1 = InProcessServerBuilder.generateName();
+    assertNotNull(name1);
+    assertFalse(name1.isEmpty());
+
+    String name2 = InProcessServerBuilder.generateName();
+    assertNotNull(name2);
+    assertFalse(name2.isEmpty());
+
+    assertNotEquals(name1, name2);
+  }
+
+  @Test
+  public void scheduledExecutorService_default() {
+    InProcessServerBuilder builder = InProcessServerBuilder.forName("foo");
+    InProcessServer server = builder.buildTransportServer(new ArrayList<Factory>());
+
+    ObjectPool<ScheduledExecutorService> scheduledExecutorServicePool =
+        server.getScheduledExecutorServicePool();
+    ObjectPool<ScheduledExecutorService> expectedPool =
+        SharedResourcePool.forResource(TIMER_SERVICE);
+
+    ScheduledExecutorService expected = expectedPool.getObject();
+    ScheduledExecutorService actual = scheduledExecutorServicePool.getObject();
+    assertSame(expected, actual);
+
+    expectedPool.returnObject(expected);
+    scheduledExecutorServicePool.returnObject(actual);
+  }
+
+  @Test
+  public void scheduledExecutorService_custom() {
+    InProcessServerBuilder builder = InProcessServerBuilder.forName("foo");
+    ScheduledExecutorService scheduledExecutorService =
+        new FakeClock().getScheduledExecutorService();
+
+    InProcessServerBuilder builder1 = builder.scheduledExecutorService(scheduledExecutorService);
+    assertSame(builder, builder1);
+
+    InProcessServer server = builder1.buildTransportServer(new ArrayList<Factory>());
+    ObjectPool<ScheduledExecutorService> scheduledExecutorServicePool =
+        server.getScheduledExecutorServicePool();
+
+    assertSame(scheduledExecutorService, scheduledExecutorServicePool.getObject());
+
+    scheduledExecutorServicePool.returnObject(scheduledExecutorService);
+  }
+}
diff --git a/core/src/test/java/io/grpc/inprocess/InProcessServerTest.java b/core/src/test/java/io/grpc/inprocess/InProcessServerTest.java
new file mode 100644
index 0000000..2a45f15
--- /dev/null
+++ b/core/src/test/java/io/grpc/inprocess/InProcessServerTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.inprocess;
+
+import com.google.common.truth.Truth;
+import io.grpc.ServerStreamTracer;
+import io.grpc.internal.FakeClock;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.ObjectPool;
+import io.grpc.internal.ServerListener;
+import io.grpc.internal.ServerTransport;
+import io.grpc.internal.ServerTransportListener;
+import io.grpc.internal.SharedResourcePool;
+import java.util.Collections;
+import java.util.concurrent.ScheduledExecutorService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class InProcessServerTest {
+
+  @Test
+  public void getPort_notStarted() throws Exception {
+    InProcessServer s =
+        new InProcessServer("name", SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE),
+            Collections.<ServerStreamTracer.Factory>emptyList());
+
+    Truth.assertThat(s.getPort()).isEqualTo(-1);
+  }
+
+  @Test
+  public void serverHoldsRefToScheduler() throws Exception {
+    final ScheduledExecutorService ses = new FakeClock().getScheduledExecutorService();
+    class RefCountingObjectPool implements ObjectPool<ScheduledExecutorService> {
+      private int count;
+
+      @Override
+      public ScheduledExecutorService getObject() {
+        count++;
+        return ses;
+      }
+
+      @Override
+      public ScheduledExecutorService returnObject(Object returned) {
+        count--;
+        return null;
+      }
+    }
+
+    RefCountingObjectPool pool = new RefCountingObjectPool();
+    InProcessServer s =
+        new InProcessServer("name", pool, Collections.<ServerStreamTracer.Factory>emptyList());
+    Truth.assertThat(pool.count).isEqualTo(0);
+    s.start(new ServerListener() {
+      @Override public ServerTransportListener transportCreated(ServerTransport transport) {
+        throw new UnsupportedOperationException();
+      }
+
+      @Override public void serverShutdown() {}
+    });
+    Truth.assertThat(pool.count).isEqualTo(1);
+    s.shutdown();
+    Truth.assertThat(pool.count).isEqualTo(0);
+  }
+}
+
diff --git a/core/src/test/java/io/grpc/inprocess/InProcessSocketAddressTest.java b/core/src/test/java/io/grpc/inprocess/InProcessSocketAddressTest.java
new file mode 100644
index 0000000..fe1df95
--- /dev/null
+++ b/core/src/test/java/io/grpc/inprocess/InProcessSocketAddressTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.inprocess;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class InProcessSocketAddressTest {
+
+  @Test
+  public void equal() {
+    new EqualsTester()
+        .addEqualityGroup(
+            new InProcessSocketAddress("a"),
+            new InProcessSocketAddress(new String("a")))
+        .addEqualityGroup(
+            new InProcessSocketAddress("z"),
+            new InProcessSocketAddress("z"))
+        .addEqualityGroup(
+            new InProcessSocketAddress(""),
+            new InProcessSocketAddress("")
+        )
+        .testEquals();
+  }
+
+  @Test
+  public void hash() {
+    assertThat(new InProcessSocketAddress("a").hashCode())
+        .isEqualTo(new InProcessSocketAddress(new String("a")).hashCode());
+  }
+}
diff --git a/core/src/test/java/io/grpc/inprocess/InProcessTransportTest.java b/core/src/test/java/io/grpc/inprocess/InProcessTransportTest.java
new file mode 100644
index 0000000..5dc14df
--- /dev/null
+++ b/core/src/test/java/io/grpc/inprocess/InProcessTransportTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.inprocess;
+
+import io.grpc.ServerStreamTracer;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.InternalServer;
+import io.grpc.internal.ManagedClientTransport;
+import io.grpc.internal.SharedResourcePool;
+import io.grpc.internal.testing.AbstractTransportTest;
+import java.util.List;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link InProcessTransport}. */
+@RunWith(JUnit4.class)
+public class InProcessTransportTest extends AbstractTransportTest {
+  private static final String TRANSPORT_NAME = "perfect-for-testing";
+  private static final String AUTHORITY = "a-testing-authority";
+  private static final String USER_AGENT = "a-testing-user-agent";
+
+  @Override
+  protected InternalServer newServer(List<ServerStreamTracer.Factory> streamTracerFactories) {
+    return new InProcessServer(
+        TRANSPORT_NAME,
+        SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE), streamTracerFactories);
+  }
+
+  @Override
+  protected InternalServer newServer(
+      InternalServer server, List<ServerStreamTracer.Factory> streamTracerFactories) {
+    return newServer(streamTracerFactories);
+  }
+
+  @Override
+  protected String testAuthority(InternalServer server) {
+    return AUTHORITY;
+  }
+
+  @Override
+  protected ManagedClientTransport newClientTransport(InternalServer server) {
+    return new InProcessTransport(TRANSPORT_NAME, testAuthority(server), USER_AGENT);
+  }
+
+  @Override
+  protected boolean sizesReported() {
+    // TODO(zhangkun83): InProcessTransport doesn't record metrics for now
+    // (https://github.com/grpc/grpc-java/issues/2284)
+    return false;
+  }
+
+  @Test
+  @Ignore
+  @Override
+  public void socketStats() throws Exception {
+    // test does not apply to in-process
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/AbstractClientStreamTest.java b/core/src/test/java/io/grpc/internal/AbstractClientStreamTest.java
new file mode 100644
index 0000000..0d540a9
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/AbstractClientStreamTest.java
@@ -0,0 +1,552 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED;
+import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import io.grpc.Attributes;
+import io.grpc.Codec;
+import io.grpc.Deadline;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.Status.Code;
+import io.grpc.StreamTracer;
+import io.grpc.internal.AbstractClientStream.TransportState;
+import io.grpc.internal.ClientStreamListener.RpcProgress;
+import io.grpc.internal.MessageFramerTest.ByteWritableBuffer;
+import io.grpc.internal.testing.TestClientStreamTracer;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Test for {@link AbstractClientStream}.  This class tries to test functionality in
+ * AbstractClientStream, but not in any super classes.
+ */
+@RunWith(JUnit4.class)
+public class AbstractClientStreamTest {
+
+  @Rule public final ExpectedException thrown = ExpectedException.none();
+
+  private final StatsTraceContext statsTraceCtx = StatsTraceContext.NOOP;
+  private final TransportTracer transportTracer = new TransportTracer();
+  @Mock private ClientStreamListener mockListener;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+
+    doAnswer(new Answer<Void>() {
+      @Override
+      public Void answer(InvocationOnMock invocation) throws Throwable {
+        StreamListener.MessageProducer producer =
+            (StreamListener.MessageProducer) invocation.getArguments()[0];
+        while (producer.next() != null) {}
+        return null;
+      }
+    }).when(mockListener).messagesAvailable(Matchers.<StreamListener.MessageProducer>any());
+  }
+
+  private final WritableBufferAllocator allocator = new WritableBufferAllocator() {
+    @Override
+    public WritableBuffer allocate(int capacityHint) {
+      return new ByteWritableBuffer(capacityHint);
+    }
+  };
+
+  @Test
+  public void cancel_doNotAcceptOk() {
+    for (Code code : Code.values()) {
+      ClientStreamListener listener = new NoopClientStreamListener();
+      AbstractClientStream stream =
+          new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+      stream.start(listener);
+      if (code != Code.OK) {
+        stream.cancel(Status.fromCodeValue(code.value()));
+      } else {
+        try {
+          stream.cancel(Status.fromCodeValue(code.value()));
+          fail();
+        } catch (IllegalArgumentException e) {
+          // ignore
+        }
+      }
+    }
+  }
+
+  @Test
+  public void cancel_failsOnNull() {
+    ClientStreamListener listener = new NoopClientStreamListener();
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(listener);
+    thrown.expect(NullPointerException.class);
+
+    stream.cancel(null);
+  }
+
+  @Test
+  public void cancel_notifiesOnlyOnce() {
+    final BaseTransportState state = new BaseTransportState(statsTraceCtx, transportTracer);
+    AbstractClientStream stream = new BaseAbstractClientStream(allocator, state, new BaseSink() {
+      @Override
+      public void cancel(Status errorStatus) {
+        // Cancel should eventually result in a transportReportStatus on the transport thread
+        state.transportReportStatus(errorStatus, true/*stop delivery*/, new Metadata());
+      }
+    }, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+
+    stream.cancel(Status.DEADLINE_EXCEEDED);
+    stream.cancel(Status.DEADLINE_EXCEEDED);
+
+    verify(mockListener).closed(any(Status.class), same(PROCESSED), any(Metadata.class));
+  }
+
+  @Test
+  public void startFailsOnNullListener() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+
+    thrown.expect(NullPointerException.class);
+
+    stream.start(null);
+  }
+
+  @Test
+  public void cantCallStartTwice() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+    thrown.expect(IllegalStateException.class);
+
+    stream.start(mockListener);
+  }
+
+  @Test
+  public void inboundDataReceived_failsOnNullFrame() {
+    ClientStreamListener listener = new NoopClientStreamListener();
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(listener);
+
+    TransportState state = stream.transportState();
+
+    thrown.expect(NullPointerException.class);
+    state.inboundDataReceived(null);
+  }
+
+  @Test
+  public void inboundHeadersReceived_notifiesListener() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+    Metadata headers = new Metadata();
+
+    stream.transportState().inboundHeadersReceived(headers);
+    verify(mockListener).headersRead(headers);
+  }
+
+  @Test
+  public void inboundHeadersReceived_failsIfStatusReported() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+    stream.transportState().transportReportStatus(Status.CANCELLED, false, new Metadata());
+
+    TransportState state = stream.transportState();
+
+    thrown.expect(IllegalStateException.class);
+    state.inboundHeadersReceived(new Metadata());
+  }
+
+  @Test
+  public void inboundHeadersReceived_acceptsGzipContentEncoding() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(GrpcUtil.CONTENT_ENCODING_KEY, "gzip");
+
+    stream.setFullStreamDecompression(true);
+    stream.transportState().inboundHeadersReceived(headers);
+
+    verify(mockListener).headersRead(headers);
+  }
+
+  @Test
+  // https://tools.ietf.org/html/rfc7231#section-3.1.2.1
+  public void inboundHeadersReceived_contentEncodingIsCaseInsensitive() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(GrpcUtil.CONTENT_ENCODING_KEY, "gZIp");
+
+    stream.setFullStreamDecompression(true);
+    stream.transportState().inboundHeadersReceived(headers);
+
+    verify(mockListener).headersRead(headers);
+  }
+
+  @Test
+  public void inboundHeadersReceived_failsOnUnrecognizedContentEncoding() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(GrpcUtil.CONTENT_ENCODING_KEY, "not-a-real-compression-method");
+
+    stream.setFullStreamDecompression(true);
+    stream.transportState().inboundHeadersReceived(headers);
+
+    verifyNoMoreInteractions(mockListener);
+    Throwable t = ((BaseTransportState) stream.transportState()).getDeframeFailedCause();
+    assertEquals(Status.INTERNAL.getCode(), Status.fromThrowable(t).getCode());
+    assertTrue(
+        "unexpected deframe failed description",
+        Status.fromThrowable(t)
+            .getDescription()
+            .startsWith("Can't find full stream decompressor for"));
+  }
+
+  @Test
+  public void inboundHeadersReceived_disallowsContentAndMessageEncoding() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(GrpcUtil.CONTENT_ENCODING_KEY, "gzip");
+    headers.put(GrpcUtil.MESSAGE_ENCODING_KEY, new Codec.Gzip().getMessageEncoding());
+
+    stream.setFullStreamDecompression(true);
+    stream.transportState().inboundHeadersReceived(headers);
+
+    verifyNoMoreInteractions(mockListener);
+    Throwable t = ((BaseTransportState) stream.transportState()).getDeframeFailedCause();
+    assertEquals(Status.INTERNAL.getCode(), Status.fromThrowable(t).getCode());
+    assertTrue(
+        "unexpected deframe failed description",
+        Status.fromThrowable(t)
+            .getDescription()
+            .equals("Full stream and gRPC message encoding cannot both be set"));
+  }
+
+  @Test
+  public void inboundHeadersReceived_acceptsGzipMessageEncoding() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(GrpcUtil.MESSAGE_ENCODING_KEY, new Codec.Gzip().getMessageEncoding());
+
+    stream.transportState().inboundHeadersReceived(headers);
+    verify(mockListener).headersRead(headers);
+  }
+
+  @Test
+  public void inboundHeadersReceived_acceptsIdentityMessageEncoding() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(GrpcUtil.MESSAGE_ENCODING_KEY, Codec.Identity.NONE.getMessageEncoding());
+
+    stream.transportState().inboundHeadersReceived(headers);
+    verify(mockListener).headersRead(headers);
+  }
+
+  @Test
+  public void inboundHeadersReceived_failsOnUnrecognizedMessageEncoding() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(GrpcUtil.MESSAGE_ENCODING_KEY, "not-a-real-compression-method");
+
+    stream.transportState().inboundHeadersReceived(headers);
+
+    verifyNoMoreInteractions(mockListener);
+    Throwable t = ((BaseTransportState) stream.transportState()).getDeframeFailedCause();
+    assertEquals(Status.INTERNAL.getCode(), Status.fromThrowable(t).getCode());
+    assertTrue(
+        "unexpected deframe failed description",
+        Status.fromThrowable(t).getDescription().startsWith("Can't find decompressor for"));
+  }
+
+  @Test
+  public void rstStreamClosesStream() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+    // The application will call request when waiting for a message, which will in turn call this
+    // on the transport thread.
+    stream.transportState().requestMessagesFromDeframer(1);
+    // Send first byte of 2 byte message
+    stream.transportState().deframe(ReadableBuffers.wrap(new byte[] {0, 0, 0, 0, 2, 1}));
+    Status status = Status.INTERNAL.withDescription("rst___stream");
+    // Simulate getting a reset
+    stream.transportState().transportReportStatus(status, false /*stop delivery*/, new Metadata());
+
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(mockListener)
+        .closed(statusCaptor.capture(), any(RpcProgress.class), any(Metadata.class));
+    assertSame(Status.Code.INTERNAL, statusCaptor.getValue().getCode());
+    assertEquals("rst___stream", statusCaptor.getValue().getDescription());
+  }
+
+  @Test
+  public void trailerOkWithTruncatedMessage() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+
+    stream.transportState().requestMessagesFromDeframer(1);
+    stream.transportState().deframe(ReadableBuffers.wrap(new byte[] {0, 0, 0, 0, 2, 1}));
+    stream.transportState().inboundTrailersReceived(new Metadata(), Status.OK);
+
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(mockListener)
+        .closed(statusCaptor.capture(), any(RpcProgress.class), any(Metadata.class));
+    assertSame(Status.Code.INTERNAL, statusCaptor.getValue().getCode());
+    assertEquals("Encountered end-of-stream mid-frame", statusCaptor.getValue().getDescription());
+  }
+
+  @Test
+  public void trailerNotOkWithTruncatedMessage() {
+    AbstractClientStream stream =
+        new BaseAbstractClientStream(allocator, statsTraceCtx, transportTracer);
+    stream.start(mockListener);
+
+    stream.transportState().requestMessagesFromDeframer(1);
+    stream.transportState().deframe(ReadableBuffers.wrap(new byte[] {0, 0, 0, 0, 2, 1}));
+    stream.transportState().inboundTrailersReceived(
+        new Metadata(), Status.DATA_LOSS.withDescription("data___loss"));
+
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(mockListener)
+        .closed(statusCaptor.capture(), any(RpcProgress.class), any(Metadata.class));
+    assertSame(Status.Code.DATA_LOSS, statusCaptor.getValue().getCode());
+    assertEquals("data___loss", statusCaptor.getValue().getDescription());
+  }
+  
+  @Test
+  public void getRequest() {
+    AbstractClientStream.Sink sink = mock(AbstractClientStream.Sink.class);
+    final TestClientStreamTracer tracer = new TestClientStreamTracer();
+    StatsTraceContext statsTraceCtx = new StatsTraceContext(new StreamTracer[]{tracer});
+    AbstractClientStream stream = new BaseAbstractClientStream(
+        allocator,
+        new BaseTransportState(statsTraceCtx, transportTracer),
+        sink,
+        statsTraceCtx,
+        transportTracer,
+        true);
+    stream.start(mockListener);
+    stream.writeMessage(new ByteArrayInputStream(new byte[1]));
+    // writeHeaders will be delayed since we're sending a GET request.
+    verify(sink, never()).writeHeaders(any(Metadata.class), any(byte[].class));
+    // halfClose will trigger writeHeaders.
+    stream.halfClose();
+    ArgumentCaptor<byte[]> payloadCaptor = ArgumentCaptor.forClass(byte[].class);
+    verify(sink).writeHeaders(any(Metadata.class), payloadCaptor.capture());
+    assertTrue(payloadCaptor.getValue() != null);
+    // GET requests don't have BODY.
+    verify(sink, never())
+        .writeFrame(
+            any(WritableBuffer.class), any(Boolean.class), any(Boolean.class), any(Integer.class));
+    assertThat(tracer.nextOutboundEvent()).isEqualTo("outboundMessage(0)");
+    assertThat(tracer.nextOutboundEvent()).matches("outboundMessageSent\\(0, [0-9]+, [0-9]+\\)");
+    assertNull(tracer.nextOutboundEvent());
+    assertNull(tracer.nextInboundEvent());
+    assertEquals(1, tracer.getOutboundWireSize());
+    assertEquals(1, tracer.getOutboundUncompressedSize());
+  }
+
+  @Test
+  public void writeMessage_closesStream() throws IOException {
+    AbstractClientStream.Sink sink = mock(AbstractClientStream.Sink.class);
+    final TestClientStreamTracer tracer = new TestClientStreamTracer();
+    StatsTraceContext statsTraceCtx = new StatsTraceContext(new StreamTracer[] {tracer});
+    AbstractClientStream stream = new BaseAbstractClientStream(
+        allocator,
+        new BaseTransportState(statsTraceCtx, transportTracer),
+        sink,
+        statsTraceCtx,
+        transportTracer,
+        true);
+    stream.start(mockListener);
+    InputStream input = mock(InputStream.class, delegatesTo(new ByteArrayInputStream(new byte[1])));
+    stream.writeMessage(input);
+    verify(input).close();
+  }
+
+  @Test
+  public void deadlineTimeoutPopulatedToHeaders() {
+    AbstractClientStream.Sink sink = mock(AbstractClientStream.Sink.class);
+    ClientStream stream = new BaseAbstractClientStream(
+        allocator, new BaseTransportState(statsTraceCtx, transportTracer), sink, statsTraceCtx,
+        transportTracer);
+
+    stream.setDeadline(Deadline.after(1, TimeUnit.SECONDS));
+    stream.start(mockListener);
+
+    ArgumentCaptor<Metadata> headersCaptor = ArgumentCaptor.forClass(Metadata.class);
+    verify(sink).writeHeaders(headersCaptor.capture(), any(byte[].class));
+
+    Metadata headers = headersCaptor.getValue();
+    assertTrue(headers.containsKey(GrpcUtil.TIMEOUT_KEY));
+    assertThat(headers.get(GrpcUtil.TIMEOUT_KEY).longValue())
+        .isLessThan(TimeUnit.SECONDS.toNanos(1));
+    assertThat(headers.get(GrpcUtil.TIMEOUT_KEY).longValue())
+        .isGreaterThan(TimeUnit.MILLISECONDS.toNanos(600));
+  }
+
+  /**
+   * No-op base class for testing.
+   */
+  private static class BaseAbstractClientStream extends AbstractClientStream {
+    private final TransportState state;
+    private final Sink sink;
+
+    public BaseAbstractClientStream(
+        WritableBufferAllocator allocator,
+        StatsTraceContext statsTraceCtx,
+        TransportTracer transportTracer) {
+      this(
+          allocator,
+          new BaseTransportState(statsTraceCtx, transportTracer),
+          new BaseSink(),
+          statsTraceCtx,
+          transportTracer);
+    }
+
+    public BaseAbstractClientStream(
+        WritableBufferAllocator allocator,
+        TransportState state,
+        Sink sink,
+        StatsTraceContext statsTraceCtx,
+        TransportTracer transportTracer) {
+      this(allocator, state, sink, statsTraceCtx, transportTracer, false);
+    }
+
+    public BaseAbstractClientStream(
+        WritableBufferAllocator allocator,
+        TransportState state,
+        Sink sink,
+        StatsTraceContext statsTraceCtx,
+        TransportTracer transportTracer,
+        boolean useGet) {
+      super(allocator, statsTraceCtx, transportTracer, new Metadata(), useGet);
+      this.state = state;
+      this.sink = sink;
+    }
+
+    @Override
+    protected Sink abstractClientStreamSink() {
+      return sink;
+    }
+
+    @Override
+    public TransportState transportState() {
+      return state;
+    }
+
+    @Override
+    public void setAuthority(String authority) {}
+
+    @Override
+    public void setMaxInboundMessageSize(int maxSize) {}
+
+    @Override
+    public void setMaxOutboundMessageSize(int maxSize) {}
+
+    @Override
+    public Attributes getAttributes() {
+      return Attributes.EMPTY;
+    }
+  }
+
+  private static class BaseSink implements AbstractClientStream.Sink {
+    @Override
+    public void writeHeaders(Metadata headers, byte[] payload) {}
+
+    @Override
+    public void request(int numMessages) {}
+
+    @Override
+    public void writeFrame(
+        WritableBuffer frame, boolean endOfStream, boolean flush, int numMessages) {}
+
+    @Override
+    public void cancel(Status reason) {}
+  }
+
+  private static class BaseTransportState extends AbstractClientStream.TransportState {
+    private Throwable deframeFailedCause;
+
+    private Throwable getDeframeFailedCause() {
+      return deframeFailedCause;
+    }
+
+    public BaseTransportState(StatsTraceContext statsTraceCtx, TransportTracer transportTracer) {
+      super(DEFAULT_MAX_MESSAGE_SIZE, statsTraceCtx, transportTracer);
+    }
+
+    @Override
+    public void deframeFailed(Throwable cause) {
+      assertNull("deframeFailed already called", deframeFailedCause);
+      deframeFailedCause = cause;
+    }
+
+    @Override
+    public void bytesRead(int processedBytes) {}
+
+    @Override
+    public void runOnTransportThread(Runnable r) {
+      r.run();
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java b/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java
new file mode 100644
index 0000000..b6ab80e
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/AbstractManagedChannelImplBuilderTest.java
@@ -0,0 +1,436 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.TestCase.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.CompressorRegistry;
+import io.grpc.DecompressorRegistry;
+import io.grpc.LoadBalancer;
+import io.grpc.MethodDescriptor;
+import io.grpc.NameResolver;
+import io.grpc.internal.testing.StatsTestUtils.FakeStatsRecorder;
+import io.grpc.internal.testing.StatsTestUtils.FakeTagContextBinarySerializer;
+import io.grpc.internal.testing.StatsTestUtils.FakeTagger;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AbstractManagedChannelImplBuilder}. */
+@RunWith(JUnit4.class)
+public class AbstractManagedChannelImplBuilderTest {
+
+  @Rule
+  public final ExpectedException thrown = ExpectedException.none();
+
+  private static final ClientInterceptor DUMMY_USER_INTERCEPTOR =
+      new ClientInterceptor() {
+        @Override
+        public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+            MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+          return next.newCall(method, callOptions);
+        }
+      };
+
+  private Builder builder = new Builder("fake");
+  private Builder directAddressBuilder = new Builder(new SocketAddress(){}, "fake");
+
+  @Test
+  public void executor_default() {
+    assertNotNull(builder.executorPool);
+  }
+
+  @Test
+  public void executor_normal() {
+    Executor executor = mock(Executor.class);
+    assertEquals(builder, builder.executor(executor));
+    assertEquals(executor, builder.executorPool.getObject());
+  }
+
+  @Test
+  public void executor_null() {
+    ObjectPool<? extends Executor> defaultValue = builder.executorPool;
+    builder.executor(mock(Executor.class));
+    assertEquals(builder, builder.executor(null));
+    assertEquals(defaultValue, builder.executorPool);
+  }
+
+  @Test
+  public void directExecutor() {
+    assertEquals(builder, builder.directExecutor());
+    assertEquals(MoreExecutors.directExecutor(), builder.executorPool.getObject());
+  }
+
+  @Test
+  public void nameResolverFactory_default() {
+    assertNotNull(builder.getNameResolverFactory());
+  }
+
+  @Test
+  public void nameResolverFactory_normal() {
+    NameResolver.Factory nameResolverFactory = mock(NameResolver.Factory.class);
+    assertEquals(builder, builder.nameResolverFactory(nameResolverFactory));
+    assertEquals(nameResolverFactory, builder.getNameResolverFactory());
+  }
+
+  @Test
+  public void nameResolverFactory_null() {
+    NameResolver.Factory defaultValue = builder.getNameResolverFactory();
+    builder.nameResolverFactory(mock(NameResolver.Factory.class));
+    assertEquals(builder, builder.nameResolverFactory(null));
+    assertEquals(defaultValue, builder.getNameResolverFactory());
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void nameResolverFactory_notAllowedWithDirectAddress() {
+    directAddressBuilder.nameResolverFactory(mock(NameResolver.Factory.class));
+  }
+
+  @Test
+  public void loadBalancerFactory_default() {
+    assertNull(builder.loadBalancerFactory);
+  }
+
+  @Test
+  public void loadBalancerFactory_normal() {
+    LoadBalancer.Factory loadBalancerFactory = mock(LoadBalancer.Factory.class);
+    assertEquals(builder, builder.loadBalancerFactory(loadBalancerFactory));
+    assertEquals(loadBalancerFactory, builder.loadBalancerFactory);
+  }
+
+  @Test
+  public void loadBalancerFactory_null() {
+    LoadBalancer.Factory defaultValue = builder.loadBalancerFactory;
+    builder.loadBalancerFactory(mock(LoadBalancer.Factory.class));
+    assertEquals(builder, builder.loadBalancerFactory(null));
+    assertEquals(defaultValue, builder.loadBalancerFactory);
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void loadBalancerFactory_notAllowedWithDirectAddress() {
+    directAddressBuilder.loadBalancerFactory(mock(LoadBalancer.Factory.class));
+  }
+
+  @Test
+  public void fullStreamDecompression_default() {
+    assertFalse(builder.fullStreamDecompression);
+  }
+
+  @Test
+  public void fullStreamDecompression_enabled() {
+    assertEquals(builder, builder.enableFullStreamDecompression());
+    assertTrue(builder.fullStreamDecompression);
+  }
+
+  @Test
+  public void decompressorRegistry_default() {
+    assertNotNull(builder.decompressorRegistry);
+  }
+
+  @Test
+  public void decompressorRegistry_normal() {
+    DecompressorRegistry decompressorRegistry = DecompressorRegistry.emptyInstance();
+    assertNotEquals(decompressorRegistry, builder.decompressorRegistry);
+    assertEquals(builder, builder.decompressorRegistry(decompressorRegistry));
+    assertEquals(decompressorRegistry, builder.decompressorRegistry);
+  }
+
+  @Test
+  public void decompressorRegistry_null() {
+    DecompressorRegistry defaultValue = builder.decompressorRegistry;
+    assertEquals(builder, builder.decompressorRegistry(DecompressorRegistry.emptyInstance()));
+    assertNotEquals(defaultValue, builder.decompressorRegistry);
+    builder.decompressorRegistry(null);
+    assertEquals(defaultValue, builder.decompressorRegistry);
+  }
+
+  @Test
+  public void compressorRegistry_default() {
+    assertNotNull(builder.compressorRegistry);
+  }
+
+  @Test
+  public void compressorRegistry_normal() {
+    CompressorRegistry compressorRegistry = CompressorRegistry.newEmptyInstance();
+    assertNotEquals(compressorRegistry, builder.compressorRegistry);
+    assertEquals(builder, builder.compressorRegistry(compressorRegistry));
+    assertEquals(compressorRegistry, builder.compressorRegistry);
+  }
+
+  @Test
+  public void compressorRegistry_null() {
+    CompressorRegistry defaultValue = builder.compressorRegistry;
+    builder.compressorRegistry(CompressorRegistry.newEmptyInstance());
+    assertNotEquals(defaultValue, builder.compressorRegistry);
+    assertEquals(builder, builder.compressorRegistry(null));
+    assertEquals(defaultValue, builder.compressorRegistry);
+  }
+
+  @Test
+  public void userAgent_default() {
+    assertNull(builder.userAgent);
+  }
+
+  @Test
+  public void userAgent_normal() {
+    String userAgent = "user-agent/1";
+    assertEquals(builder, builder.userAgent(userAgent));
+    assertEquals(userAgent, builder.userAgent);
+  }
+
+  @Test
+  public void userAgent_null() {
+    assertEquals(builder, builder.userAgent(null));
+    assertNull(builder.userAgent);
+
+    builder.userAgent("user-agent/1");
+    builder.userAgent(null);
+    assertNull(builder.userAgent);
+  }
+
+  @Test
+  public void overrideAuthority_default() {
+    assertNull(builder.authorityOverride);
+  }
+
+  @Test
+  public void overrideAuthority_normal() {
+    String overrideAuthority = "best-authority";
+    assertEquals(builder, builder.overrideAuthority(overrideAuthority));
+    assertEquals(overrideAuthority, builder.authorityOverride);
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void overrideAuthority_null() {
+    builder.overrideAuthority(null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void overrideAuthority_invalid() {
+    builder.overrideAuthority("not_allowed");
+  }
+
+  @Test
+  public void overrideAuthority_getNameResolverFactory() {
+    Builder builder = new Builder("target");
+    assertNull(builder.authorityOverride);
+    assertFalse(builder.getNameResolverFactory() instanceof OverrideAuthorityNameResolverFactory);
+    builder.overrideAuthority("google.com");
+    assertTrue(builder.getNameResolverFactory() instanceof OverrideAuthorityNameResolverFactory);
+  }
+
+  @Test
+  public void makeTargetStringForDirectAddress_scopedIpv6() throws Exception {
+    InetSocketAddress address = new InetSocketAddress("0:0:0:0:0:0:0:0%0", 10005);
+    assertEquals("/0:0:0:0:0:0:0:0%0:10005", address.toString());
+    String target = AbstractManagedChannelImplBuilder.makeTargetStringForDirectAddress(address);
+    URI uri = new URI(target);
+    assertEquals("directaddress:////0:0:0:0:0:0:0:0%250:10005", target);
+    assertEquals(target, uri.toString());
+  }
+
+  @Test
+  public void getEffectiveInterceptors_default() {
+    builder.intercept(DUMMY_USER_INTERCEPTOR);
+    List<ClientInterceptor> effectiveInterceptors = builder.getEffectiveInterceptors();
+    assertEquals(3, effectiveInterceptors.size());
+    assertThat(effectiveInterceptors.get(0))
+        .isInstanceOf(CensusTracingModule.TracingClientInterceptor.class);
+    assertThat(effectiveInterceptors.get(1))
+        .isInstanceOf(CensusStatsModule.StatsClientInterceptor.class);
+    assertThat(effectiveInterceptors.get(2)).isSameAs(DUMMY_USER_INTERCEPTOR);
+  }
+
+  @Test
+  public void getEffectiveInterceptors_disableStats() {
+    builder.intercept(DUMMY_USER_INTERCEPTOR);
+    builder.setStatsEnabled(false);
+    List<ClientInterceptor> effectiveInterceptors = builder.getEffectiveInterceptors();
+    assertEquals(2, effectiveInterceptors.size());
+    assertThat(effectiveInterceptors.get(0))
+        .isInstanceOf(CensusTracingModule.TracingClientInterceptor.class);
+    assertThat(effectiveInterceptors.get(1)).isSameAs(DUMMY_USER_INTERCEPTOR);
+  }
+
+  @Test
+  public void getEffectiveInterceptors_disableTracing() {
+    builder.intercept(DUMMY_USER_INTERCEPTOR);
+    builder.setTracingEnabled(false);
+    List<ClientInterceptor> effectiveInterceptors = builder.getEffectiveInterceptors();
+    assertEquals(2, effectiveInterceptors.size());
+    assertThat(effectiveInterceptors.get(0))
+        .isInstanceOf(CensusStatsModule.StatsClientInterceptor.class);
+    assertThat(effectiveInterceptors.get(1)).isSameAs(DUMMY_USER_INTERCEPTOR);
+  }
+
+  @Test
+  public void getEffectiveInterceptors_disableBoth() {
+    builder.intercept(DUMMY_USER_INTERCEPTOR);
+    builder.setStatsEnabled(false);
+    builder.setTracingEnabled(false);
+    List<ClientInterceptor> effectiveInterceptors = builder.getEffectiveInterceptors();
+    assertThat(effectiveInterceptors).containsExactly(DUMMY_USER_INTERCEPTOR);
+  }
+
+  @Test
+  public void idleTimeout() {
+    Builder builder = new Builder("target");
+
+    assertEquals(AbstractManagedChannelImplBuilder.IDLE_MODE_DEFAULT_TIMEOUT_MILLIS,
+        builder.getIdleTimeoutMillis());
+
+    builder.idleTimeout(Long.MAX_VALUE, TimeUnit.DAYS);
+    assertEquals(ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE, builder.getIdleTimeoutMillis());
+
+    builder.idleTimeout(AbstractManagedChannelImplBuilder.IDLE_MODE_MAX_TIMEOUT_DAYS,
+        TimeUnit.DAYS);
+    assertEquals(ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE, builder.getIdleTimeoutMillis());
+
+    try {
+      builder.idleTimeout(0, TimeUnit.SECONDS);
+      fail("Should throw");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    builder.idleTimeout(1, TimeUnit.NANOSECONDS);
+    assertEquals(AbstractManagedChannelImplBuilder.IDLE_MODE_MIN_TIMEOUT_MILLIS,
+        builder.getIdleTimeoutMillis());
+
+    builder.idleTimeout(30, TimeUnit.SECONDS);
+    assertEquals(TimeUnit.SECONDS.toMillis(30), builder.getIdleTimeoutMillis());
+  }
+
+  @Test
+  public void maxRetryAttempts() {
+    Builder builder = new Builder("target");
+    assertEquals(5, builder.maxRetryAttempts);
+
+    builder.maxRetryAttempts(3);
+    assertEquals(3, builder.maxRetryAttempts);
+  }
+
+  @Test
+  public void maxHedgedAttempts() {
+    Builder builder = new Builder("target");
+    assertEquals(5, builder.maxHedgedAttempts);
+
+    builder.maxHedgedAttempts(3);
+    assertEquals(3, builder.maxHedgedAttempts);
+  }
+
+  @Test
+  public void retryBufferSize() {
+    Builder builder = new Builder("target");
+    assertEquals(1L << 24, builder.retryBufferSize);
+
+    builder.retryBufferSize(3456L);
+    assertEquals(3456L, builder.retryBufferSize);
+  }
+
+  @Test
+  public void perRpcBufferLimit() {
+    Builder builder = new Builder("target");
+    assertEquals(1L << 20, builder.perRpcBufferLimit);
+
+    builder.perRpcBufferLimit(3456L);
+    assertEquals(3456L, builder.perRpcBufferLimit);
+  }
+
+  @Test
+  public void retryBufferSizeInvalidArg() {
+    Builder builder = new Builder("target");
+
+    thrown.expect(IllegalArgumentException.class);
+    builder.retryBufferSize(0L);
+  }
+
+  @Test
+  public void perRpcBufferLimitInvalidArg() {
+    Builder builder = new Builder("target");
+
+    thrown.expect(IllegalArgumentException.class);
+    builder.perRpcBufferLimit(0L);
+  }
+
+  @Test
+  public void disableRetry() {
+    Builder builder = new Builder("target");
+
+    builder.enableRetry();
+    assertTrue(builder.retryEnabled);
+
+    builder.disableRetry();
+    assertFalse(builder.retryEnabled);
+
+    builder.enableRetry();
+    assertTrue(builder.retryEnabled);
+
+    builder.disableRetry();
+    assertFalse(builder.retryEnabled);
+  }
+
+  static class Builder extends AbstractManagedChannelImplBuilder<Builder> {
+    Builder(String target) {
+      super(target);
+      overrideCensusStatsModule(
+          new CensusStatsModule(
+              new FakeTagger(),
+              new FakeTagContextBinarySerializer(),
+              new FakeStatsRecorder(),
+              GrpcUtil.STOPWATCH_SUPPLIER,
+              true));
+    }
+
+    Builder(SocketAddress directServerAddress, String authority) {
+      super(directServerAddress, authority);
+      overrideCensusStatsModule(
+          new CensusStatsModule(
+              new FakeTagger(),
+              new FakeTagContextBinarySerializer(),
+              new FakeStatsRecorder(),
+              GrpcUtil.STOPWATCH_SUPPLIER,
+              true));
+    }
+
+    @Override
+    protected ClientTransportFactory buildTransportFactory() {
+      throw new UnsupportedOperationException();
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/AbstractReadableBufferTest.java b/core/src/test/java/io/grpc/internal/AbstractReadableBufferTest.java
new file mode 100644
index 0000000..46a6658
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/AbstractReadableBufferTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.OngoingStubbing;
+
+/**
+ * Tests for {@link AbstractReadableBuffer}.
+ */
+@RunWith(JUnit4.class)
+public class AbstractReadableBufferTest {
+
+  @Mock
+  private AbstractReadableBuffer buffer;
+
+  @Before
+  public void setup() {
+    MockitoAnnotations.initMocks(this);
+  }
+
+  @Test
+  public void readPositiveIntShouldSucceed() {
+    mockBytes(0x7F, 0xEE, 0xDD, 0xCC);
+    assertEquals(0x7FEEDDCC, buffer.readInt());
+  }
+
+  @Test
+  public void readNegativeIntShouldSucceed() {
+    mockBytes(0xFF, 0xEE, 0xDD, 0xCC);
+    assertEquals(0xFFEEDDCC, buffer.readInt());
+  }
+
+  private void mockBytes(int... bytes) {
+    when(buffer.readableBytes()).thenReturn(bytes.length);
+    OngoingStubbing<Integer> stub = when(buffer.readUnsignedByte());
+    for (int b : bytes) {
+      stub = stub.thenReturn(b);
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/AbstractServerImplBuilderTest.java b/core/src/test/java/io/grpc/internal/AbstractServerImplBuilderTest.java
new file mode 100644
index 0000000..db4def2
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/AbstractServerImplBuilderTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import io.grpc.Metadata;
+import io.grpc.ServerStreamTracer;
+import io.grpc.internal.testing.StatsTestUtils.FakeStatsRecorder;
+import io.grpc.internal.testing.StatsTestUtils.FakeTagContextBinarySerializer;
+import io.grpc.internal.testing.StatsTestUtils.FakeTagger;
+import java.io.File;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AbstractServerImplBuilder}. */
+@RunWith(JUnit4.class)
+public class AbstractServerImplBuilderTest {
+
+  private static final ServerStreamTracer.Factory DUMMY_USER_TRACER =
+      new ServerStreamTracer.Factory() {
+        @Override
+        public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata headers) {
+          throw new UnsupportedOperationException();
+        }
+      };
+
+  private Builder builder = new Builder();
+
+  @Test
+  public void getTracerFactories_default() {
+    builder.addStreamTracerFactory(DUMMY_USER_TRACER);
+    List<ServerStreamTracer.Factory> factories = builder.getTracerFactories();
+    assertEquals(3, factories.size());
+    assertThat(factories.get(0)).isInstanceOf(CensusStatsModule.ServerTracerFactory.class);
+    assertThat(factories.get(1)).isInstanceOf(CensusTracingModule.ServerTracerFactory.class);
+    assertThat(factories.get(2)).isSameAs(DUMMY_USER_TRACER);
+  }
+
+  @Test
+  public void getTracerFactories_disableStats() {
+    builder.addStreamTracerFactory(DUMMY_USER_TRACER);
+    builder.setStatsEnabled(false);
+    List<ServerStreamTracer.Factory> factories = builder.getTracerFactories();
+    assertEquals(2, factories.size());
+    assertThat(factories.get(0)).isInstanceOf(CensusTracingModule.ServerTracerFactory.class);
+    assertThat(factories.get(1)).isSameAs(DUMMY_USER_TRACER);
+  }
+
+  @Test
+  public void getTracerFactories_disableTracing() {
+    builder.addStreamTracerFactory(DUMMY_USER_TRACER);
+    builder.setTracingEnabled(false);
+    List<ServerStreamTracer.Factory> factories = builder.getTracerFactories();
+    assertEquals(2, factories.size());
+    assertThat(factories.get(0)).isInstanceOf(CensusStatsModule.ServerTracerFactory.class);
+    assertThat(factories.get(1)).isSameAs(DUMMY_USER_TRACER);
+  }
+
+  @Test
+  public void getTracerFactories_disableBoth() {
+    builder.addStreamTracerFactory(DUMMY_USER_TRACER);
+    builder.setTracingEnabled(false);
+    builder.setStatsEnabled(false);
+    List<ServerStreamTracer.Factory> factories = builder.getTracerFactories();
+    assertThat(factories).containsExactly(DUMMY_USER_TRACER);
+  }
+
+  static class Builder extends AbstractServerImplBuilder<Builder> {
+    Builder() {
+      overrideCensusStatsModule(
+          new CensusStatsModule(
+              new FakeTagger(),
+              new FakeTagContextBinarySerializer(),
+              new FakeStatsRecorder(),
+              GrpcUtil.STOPWATCH_SUPPLIER,
+              true));
+    }
+
+    @Override
+    protected io.grpc.internal.InternalServer buildTransportServer(
+        List<ServerStreamTracer.Factory> streamTracerFactories) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Builder useTransportSecurity(File certChain, File privateKey) {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+}
diff --git a/core/src/test/java/io/grpc/internal/AbstractServerStreamTest.java b/core/src/test/java/io/grpc/internal/AbstractServerStreamTest.java
new file mode 100644
index 0000000..49ac5bd
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/AbstractServerStreamTest.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.InternalStatus;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.AbstractServerStream.TransportState;
+import io.grpc.internal.MessageFramerTest.ByteWritableBuffer;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Tests for {@link AbstractServerStream}.
+ */
+@RunWith(JUnit4.class)
+public class AbstractServerStreamTest {
+  private static final int TIMEOUT_MS = 1000;
+  private static final int MAX_MESSAGE_SIZE = 100;
+
+  @Rule public final ExpectedException thrown = ExpectedException.none();
+
+  private final WritableBufferAllocator allocator = new WritableBufferAllocator() {
+    @Override
+    public WritableBuffer allocate(int capacityHint) {
+      return new ByteWritableBuffer(capacityHint);
+    }
+  };
+
+  private AbstractServerStream.Sink sink = mock(AbstractServerStream.Sink.class);
+  private TransportTracer transportTracer;
+  private AbstractServerStreamBase stream;
+  private final ArgumentCaptor<Metadata> metadataCaptor = ArgumentCaptor.forClass(Metadata.class);
+
+  @Before
+  public void setUp() {
+    transportTracer = new TransportTracer();
+    stream = new AbstractServerStreamBase(
+        allocator,
+        sink,
+        new AbstractServerStreamBase.TransportState(MAX_MESSAGE_SIZE, transportTracer));
+  }
+
+  /**
+   * Test for issue https://github.com/grpc/grpc-java/issues/1795
+   */
+  @Test
+  public void frameShouldBeIgnoredAfterDeframerClosed() {
+    final Queue<InputStream> streamListenerMessageQueue = new LinkedList<InputStream>();
+    stream.transportState().setListener(new ServerStreamListenerBase() {
+      @Override
+      public void messagesAvailable(MessageProducer producer) {
+        InputStream message;
+        while ((message = producer.next()) != null) {
+          streamListenerMessageQueue.add(message);
+        }
+      }
+    });
+    ReadableBuffer buffer = mock(ReadableBuffer.class);
+
+    // Close the deframer
+    stream.close(Status.OK, new Metadata());
+    stream.transportState().complete();
+    // Frame received after deframer closed, should be ignored and not trigger an exception
+    stream.transportState().inboundDataReceived(buffer, true);
+
+    verify(buffer).close();
+    assertNull("no message expected", streamListenerMessageQueue.poll());
+  }
+
+  @Test
+  public void queuedBytesInDeframerShouldNotBlockComplete() throws Exception {
+    final SettableFuture<Status> closedFuture = SettableFuture.create();
+    stream
+        .transportState()
+        .setListener(
+            new ServerStreamListenerBase() {
+              @Override
+              public void closed(Status status) {
+                closedFuture.set(status);
+              }
+            });
+
+    // Queue bytes in deframer
+    stream.transportState().inboundDataReceived(ReadableBuffers.wrap(new byte[] {1}), false);
+    stream.close(Status.OK, new Metadata());
+    stream.transportState().complete();
+
+    assertEquals(Status.OK, closedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+  }
+
+  @Test
+  public void queuedBytesInDeframerShouldNotBlockTransportReportStatus() throws Exception {
+    final SettableFuture<Status> closedFuture = SettableFuture.create();
+    stream
+        .transportState()
+        .setListener(
+            new ServerStreamListenerBase() {
+              @Override
+              public void closed(Status status) {
+                closedFuture.set(status);
+              }
+            });
+
+    // Queue bytes in deframer
+    stream.transportState().inboundDataReceived(ReadableBuffers.wrap(new byte[] {1}), false);
+    stream.transportState().transportReportStatus(Status.CANCELLED);
+
+    assertEquals(Status.CANCELLED, closedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+  }
+
+  @Test
+  public void partialMessageAtEndOfStreamShouldFail() throws Exception {
+    final SettableFuture<Status> closedFuture = SettableFuture.create();
+    stream
+        .transportState()
+        .setListener(
+            new ServerStreamListenerBase() {
+              @Override
+              public void closed(Status status) {
+                closedFuture.set(status);
+              }
+            });
+
+    // Queue a partial message in the deframer
+    stream.transportState().inboundDataReceived(ReadableBuffers.wrap(new byte[] {1}), true);
+    stream.transportState().requestMessagesFromDeframer(1);
+
+    Status status = closedFuture.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertEquals(Status.INTERNAL.getCode(), status.getCode());
+    assertEquals("Encountered end-of-stream mid-frame", status.getDescription());
+  }
+
+  /**
+   * Test for issue https://github.com/grpc/grpc-java/issues/615
+   */
+  @Test
+  public void completeWithoutClose() {
+    stream.transportState().setListener(new ServerStreamListenerBase());
+    // Test that it doesn't throw an exception
+    stream.close(Status.OK, new Metadata());
+    stream.transportState().complete();
+  }
+
+  @Test
+  public void setListener_setOnlyOnce() {
+    TransportState state = stream.transportState();
+    state.setListener(new ServerStreamListenerBase());
+    thrown.expect(IllegalStateException.class);
+
+    state.setListener(new ServerStreamListenerBase());
+  }
+
+  @Test
+  public void listenerReady_onlyOnce() {
+    stream.transportState().setListener(new ServerStreamListenerBase());
+    stream.transportState().onStreamAllocated();
+
+    TransportState state = stream.transportState();
+
+    thrown.expect(IllegalStateException.class);
+    state.onStreamAllocated();
+  }
+
+  @Test
+  public void listenerReady_readyCalled() {
+    ServerStreamListener streamListener = mock(ServerStreamListener.class);
+    stream.transportState().setListener(streamListener);
+    stream.transportState().onStreamAllocated();
+
+    verify(streamListener).onReady();
+  }
+
+  @Test
+  public void setListener_failsOnNull() {
+    TransportState state = stream.transportState();
+
+    thrown.expect(NullPointerException.class);
+    state.setListener(null);
+  }
+
+  // TODO(ericgribkoff) This test is only valid if deframeInTransportThread=true, as otherwise the
+  // message is queued.
+  /*
+  @Test
+  public void messageRead_listenerCalled() {
+    final Queue<InputStream> streamListenerMessageQueue = new LinkedList<InputStream>();
+    stream.transportState().setListener(new ServerStreamListenerBase() {
+      @Override
+      public void messagesAvailable(MessageProducer producer) {
+        InputStream message;
+        while ((message = producer.next()) != null) {
+          streamListenerMessageQueue.add(message);
+        }
+      }
+    });
+
+    // Normally called by a deframe event.
+    stream.transportState().messageRead(new ByteArrayInputStream(new byte[]{}));
+
+    assertNotNull(streamListenerMessageQueue.poll());
+  }
+  */
+
+  @Test
+  public void writeHeaders_failsOnNullHeaders() {
+    thrown.expect(NullPointerException.class);
+
+    stream.writeHeaders(null);
+  }
+
+  @Test
+  public void writeHeaders() {
+    Metadata headers = new Metadata();
+    stream.writeHeaders(headers);
+    verify(sink).writeHeaders(same(headers));
+  }
+
+  @Test
+  public void writeMessage_dontWriteDuplicateHeaders() {
+    stream.writeHeaders(new Metadata());
+    stream.writeMessage(new ByteArrayInputStream(new byte[]{}));
+
+    // Make sure it wasn't called twice
+    verify(sink).writeHeaders(any(Metadata.class));
+  }
+
+  @Test
+  public void writeMessage_ignoreIfFramerClosed() {
+    stream.writeHeaders(new Metadata());
+    stream.endOfMessages();
+    reset(sink);
+
+    stream.writeMessage(new ByteArrayInputStream(new byte[]{}));
+
+    verify(sink, never())
+        .writeFrame(any(WritableBuffer.class), any(Boolean.class), any(Integer.class));
+  }
+
+  @Test
+  public void writeMessage() {
+    stream.writeHeaders(new Metadata());
+
+    stream.writeMessage(new ByteArrayInputStream(new byte[]{}));
+    stream.flush();
+
+    verify(sink).writeFrame(any(WritableBuffer.class), eq(true), eq(1));
+  }
+
+  @Test
+  public void writeMessage_closesStream() throws Exception {
+    stream.writeHeaders(new Metadata());
+    InputStream input = mock(InputStream.class, delegatesTo(new ByteArrayInputStream(new byte[1])));
+    stream.writeMessage(input);
+    verify(input).close();
+  }
+
+  @Test
+  public void close_failsOnNullStatus() {
+    thrown.expect(NullPointerException.class);
+
+    stream.close(null, new Metadata());
+  }
+
+  @Test
+  public void close_failsOnNullMetadata() {
+    thrown.expect(NullPointerException.class);
+
+    stream.close(Status.INTERNAL, null);
+  }
+
+  @Test
+  public void close_sendsTrailers() {
+    Metadata trailers = new Metadata();
+    stream.close(Status.INTERNAL, trailers);
+    verify(sink).writeTrailers(any(Metadata.class), eq(false), eq(Status.INTERNAL));
+  }
+
+  @Test
+  public void close_sendTrailersClearsReservedFields() {
+    // stream actually mutates trailers, so we can't check that the fields here are the same as
+    // the captured ones.
+    Metadata trailers = new Metadata();
+    trailers.put(InternalStatus.CODE_KEY, Status.OK);
+    trailers.put(InternalStatus.MESSAGE_KEY, "Everything's super.");
+
+    Status closeStatus = Status.INTERNAL.withDescription("bad");
+    stream.close(closeStatus, trailers);
+
+    verify(sink).writeTrailers(metadataCaptor.capture(), eq(false), eq(closeStatus));
+    assertEquals(
+        Status.Code.INTERNAL, metadataCaptor.getValue().get(InternalStatus.CODE_KEY).getCode());
+    assertEquals("bad", metadataCaptor.getValue().get(InternalStatus.MESSAGE_KEY));
+  }
+
+  private static class ServerStreamListenerBase implements ServerStreamListener {
+    @Override
+    public void messagesAvailable(MessageProducer producer) {
+      InputStream message;
+      while ((message = producer.next()) != null) {
+        try {
+          message.close();
+        } catch (IOException e) {
+          // Continue to close other messages
+        }
+      }
+    }
+
+    @Override
+    public void onReady() {}
+
+    @Override
+    public void halfClosed() {}
+
+    @Override
+    public void closed(Status status) {}
+  }
+
+  private static class AbstractServerStreamBase extends AbstractServerStream {
+    private final Sink sink;
+    private final AbstractServerStream.TransportState state;
+
+    protected AbstractServerStreamBase(WritableBufferAllocator bufferAllocator, Sink sink,
+        AbstractServerStream.TransportState state) {
+      super(bufferAllocator, StatsTraceContext.NOOP);
+      this.sink = sink;
+      this.state = state;
+    }
+
+    @Override
+    protected Sink abstractServerStreamSink() {
+      return sink;
+    }
+
+    @Override
+    protected AbstractServerStream.TransportState transportState() {
+      return state;
+    }
+
+    static class TransportState extends AbstractServerStream.TransportState {
+      protected TransportState(int maxMessageSize, TransportTracer transportTracer) {
+        super(maxMessageSize, StatsTraceContext.NOOP, transportTracer);
+      }
+
+      @Override
+      public void deframeFailed(Throwable cause) {
+        Status status = Status.fromThrowable(cause);
+        transportReportStatus(status);
+      }
+
+      @Override
+      public void bytesRead(int processedBytes) {}
+
+      @Override
+      public void runOnTransportThread(Runnable r) {
+        r.run();
+      }
+    }
+  }
+}
+
diff --git a/core/src/test/java/io/grpc/internal/ApplicationThreadDeframerTest.java b/core/src/test/java/io/grpc/internal/ApplicationThreadDeframerTest.java
new file mode 100644
index 0000000..68f2ac1
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ApplicationThreadDeframerTest.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import com.google.common.io.ByteStreams;
+import com.google.common.primitives.Bytes;
+import io.grpc.internal.StreamListener.MessageProducer;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.Queue;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ApplicationThreadDeframer}. */
+@RunWith(JUnit4.class)
+public class ApplicationThreadDeframerTest {
+  private MessageDeframer mockDeframer = mock(MessageDeframer.class);
+  private DeframerListener listener = new DeframerListener();
+  private TransportExecutor transportExecutor = new TransportExecutor();
+  private ApplicationThreadDeframer applicationThreadDeframer =
+      new ApplicationThreadDeframer(listener, transportExecutor, mockDeframer);
+
+  @Before
+  public void setUp() {
+    // ApplicationThreadDeframer constructor injects itself as the wrapped deframer's listener.
+    verify(mockDeframer).setListener(applicationThreadDeframer);
+  }
+
+  @Test
+  public void requestInvokesMessagesAvailableOnListener() {
+    applicationThreadDeframer.request(1);
+    verifyZeroInteractions(mockDeframer);
+    listener.runStoredProducer();
+    verify(mockDeframer).request(1);
+  }
+
+  @Test
+  public void deframeInvokesMessagesAvailableOnListener() {
+    ReadableBuffer frame = ReadableBuffers.wrap(new byte[1]);
+    applicationThreadDeframer.deframe(frame);
+    verifyZeroInteractions(mockDeframer);
+    listener.runStoredProducer();
+    verify(mockDeframer).deframe(frame);
+  }
+
+  @Test
+  public void closeWhenCompleteInvokesMessagesAvailableOnListener() {
+    applicationThreadDeframer.closeWhenComplete();
+    verifyZeroInteractions(mockDeframer);
+    listener.runStoredProducer();
+    verify(mockDeframer).closeWhenComplete();
+  }
+
+  @Test
+  public void closeInvokesMessagesAvailableOnListener() {
+    applicationThreadDeframer.close();
+    verify(mockDeframer).stopDelivery();
+    verifyNoMoreInteractions(mockDeframer);
+    listener.runStoredProducer();
+    verify(mockDeframer).close();
+  }
+
+  @Test
+  public void bytesReadInvokesTransportExecutor() {
+    applicationThreadDeframer.bytesRead(1);
+    assertEquals(0, listener.bytesRead);
+    transportExecutor.runStoredRunnable();
+    assertEquals(1, listener.bytesRead);
+  }
+
+  @Test
+  public void deframerClosedInvokesTransportExecutor() {
+    applicationThreadDeframer.deframerClosed(true);
+    assertFalse(listener.deframerClosedWithPartialMessage);
+    transportExecutor.runStoredRunnable();
+    assertTrue(listener.deframerClosedWithPartialMessage);
+  }
+
+  @Test
+  public void deframeFailedInvokesTransportExecutor() {
+    Throwable cause = new Throwable("error");
+    applicationThreadDeframer.deframeFailed(cause);
+    assertNull(listener.deframeFailedCause);
+    transportExecutor.runStoredRunnable();
+    assertEquals(cause, listener.deframeFailedCause);
+  }
+
+  @Test
+  public void messagesAvailableDrainsToMessageReadQueue_returnedByInitializingMessageProducer()
+      throws Exception {
+    byte[][] messageBytes = {{1, 2, 3}, {4}, {5, 6}};
+    Queue<InputStream> messages = new LinkedList<InputStream>();
+    for (int i = 0; i < messageBytes.length; i++) {
+      messages.add(new ByteArrayInputStream(messageBytes[i]));
+    }
+    MultiMessageProducer messageProducer = new MultiMessageProducer(messages);
+    applicationThreadDeframer.messagesAvailable(messageProducer);
+    applicationThreadDeframer.request(1 /* value is ignored */);
+    for (int i = 0; i < messageBytes.length; i++) {
+      InputStream message = listener.storedProducer.next();
+      assertNotNull(message);
+      assertEquals(Bytes.asList(messageBytes[i]), Bytes.asList(ByteStreams.toByteArray(message)));
+    }
+    assertNull(listener.storedProducer.next());
+  }
+
+  private static class DeframerListener implements MessageDeframer.Listener {
+    private MessageProducer storedProducer;
+    private int bytesRead;
+    private boolean deframerClosedWithPartialMessage;
+    private Throwable deframeFailedCause;
+
+    private void runStoredProducer() {
+      assertNotNull(storedProducer);
+      storedProducer.next();
+    }
+
+    @Override
+    public void bytesRead(int numBytes) {
+      assertEquals(0, bytesRead);
+      bytesRead = numBytes;
+    }
+
+    @Override
+    public void messagesAvailable(MessageProducer producer) {
+      assertNull(storedProducer);
+      storedProducer = producer;
+    }
+
+    @Override
+    public void deframerClosed(boolean hasPartialMessage) {
+      assertFalse(deframerClosedWithPartialMessage);
+      deframerClosedWithPartialMessage = hasPartialMessage;
+    }
+
+    @Override
+    public void deframeFailed(Throwable cause) {
+      assertNull(deframeFailedCause);
+      deframeFailedCause = cause;
+    }
+  }
+
+  private static class TransportExecutor implements ApplicationThreadDeframer.TransportExecutor {
+    private Runnable storedRunnable;
+
+    private void runStoredRunnable() {
+      assertNotNull(storedRunnable);
+      storedRunnable.run();
+    }
+
+    @Override
+    public void runOnTransportThread(Runnable r) {
+      assertNull(storedRunnable);
+      storedRunnable = r;
+    }
+  }
+
+  private static class MultiMessageProducer implements StreamListener.MessageProducer {
+    private final Queue<InputStream> messages;
+
+    private MultiMessageProducer(Queue<InputStream> messages) {
+      this.messages = messages;
+    }
+
+    @Nullable
+    @Override
+    public InputStream next() {
+      return messages.poll();
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/AtomicBackoffTest.java b/core/src/test/java/io/grpc/internal/AtomicBackoffTest.java
new file mode 100644
index 0000000..780db52
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/AtomicBackoffTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AtomicBackoff}. */
+@RunWith(JUnit4.class)
+public class AtomicBackoffTest {
+  @Test(expected = IllegalArgumentException.class)
+  public void mustBePositive() {
+    new AtomicBackoff("test", 0);
+  }
+
+  @Test
+  public void backoff_doesNotChangeStateGet() {
+    AtomicBackoff backoff = new AtomicBackoff("test", 8);
+    AtomicBackoff.State state = backoff.getState();
+    assertEquals(8, state.get());
+    state.backoff();
+    assertEquals(8, state.get());
+  }
+
+  @Test
+  public void backoff_doubles() {
+    AtomicBackoff backoff = new AtomicBackoff("test", 3);
+    backoff.getState().backoff();
+    assertEquals(6, backoff.getState().get());
+
+    backoff.getState().backoff();
+    assertEquals(12, backoff.getState().get());
+
+    backoff.getState().backoff();
+    assertEquals(24, backoff.getState().get());
+
+    backoff = new AtomicBackoff("test", 13);
+    backoff.getState().backoff();
+    assertEquals(26, backoff.getState().get());
+  }
+
+  @Test
+  public void backoff_oncePerState() {
+    AtomicBackoff backoff = new AtomicBackoff("test", 8);
+    AtomicBackoff.State state = backoff.getState();
+
+    state.backoff();
+    assertEquals(8, state.get());
+    assertEquals(16, backoff.getState().get());
+    state.backoff(); // Nothing happens the second time
+    assertEquals(8, state.get());
+    assertEquals(16, backoff.getState().get());
+  }
+
+  @Test
+  public void backoff_twiceForEquivalentState_noChange() {
+    AtomicBackoff backoff = new AtomicBackoff("test", 8);
+    AtomicBackoff.State state1 = backoff.getState();
+    AtomicBackoff.State state2 = backoff.getState();
+    state1.backoff();
+    state2.backoff();
+    assertEquals(16, backoff.getState().get());
+  }
+
+  @Test
+  public void backoff_delayed() {
+    AtomicBackoff backoff = new AtomicBackoff("test", 8);
+    AtomicBackoff.State state = backoff.getState();
+    backoff.getState().backoff();
+    backoff.getState().backoff();
+    assertEquals(32, backoff.getState().get());
+
+    // Shouldn't decrease value
+    state.backoff();
+    assertEquals(32, backoff.getState().get());
+  }
+
+  @Test
+  public void largeLong() {
+    // Don't use MAX_VALUE because it is easy to check for explicitly
+    long tooLarge = Long.MAX_VALUE - 100;
+    AtomicBackoff backoff = new AtomicBackoff("test", tooLarge);
+    backoff.getState().backoff();
+    // It would also be okay if it became MAX_VALUE
+    assertEquals(tooLarge, backoff.getState().get());
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java b/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java
new file mode 100644
index 0000000..1542a6f
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/AutoConfiguredLoadBalancerFactoryTest.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import io.grpc.Attributes;
+import io.grpc.ConnectivityState;
+import io.grpc.ConnectivityStateInfo;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.LoadBalancer;
+import io.grpc.LoadBalancer.Helper;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.ManagedChannel;
+import io.grpc.NameResolver.Factory;
+import io.grpc.PickFirstBalancerFactory;
+import io.grpc.Status;
+import io.grpc.grpclb.GrpclbLoadBalancerFactory;
+import io.grpc.internal.AutoConfiguredLoadBalancerFactory.AutoConfiguredLoadBalancer;
+import io.grpc.util.RoundRobinLoadBalancerFactory;
+import java.net.SocketAddress;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.annotation.Nonnull;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link AutoConfiguredLoadBalancerFactory}.
+ */
+@RunWith(JUnit4.class)
+public class AutoConfiguredLoadBalancerFactoryTest {
+  private final AutoConfiguredLoadBalancerFactory lbf = new AutoConfiguredLoadBalancerFactory();
+
+  @Test
+  public void newLoadBalancer_isAuto() {
+    LoadBalancer lb = lbf.newLoadBalancer(new TestHelper());
+
+    assertThat(lb).isInstanceOf(AutoConfiguredLoadBalancer.class);
+  }
+
+  @Test
+  public void defaultIsPickFirst() {
+    AutoConfiguredLoadBalancer lb =
+        (AutoConfiguredLoadBalancer) lbf.newLoadBalancer(new TestHelper());
+
+    assertThat(lb.getDelegateFactory()).isInstanceOf(PickFirstBalancerFactory.class);
+    assertThat(lb.getDelegate().getClass().getName()).contains("PickFirst");
+  }
+
+  @Test
+  public void forwardsCalls() {
+    AutoConfiguredLoadBalancer lb =
+        (AutoConfiguredLoadBalancer) lbf.newLoadBalancer(new TestHelper());
+
+    final AtomicInteger calls = new AtomicInteger();
+    TestLoadBalancer testlb = new TestLoadBalancer() {
+
+      @Override
+      public void handleNameResolutionError(Status error) {
+        calls.getAndSet(1);
+      }
+
+      @Override
+      public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
+        calls.getAndSet(2);
+      }
+
+      @Override
+      public void shutdown() {
+        calls.getAndSet(3);
+      }
+    };
+
+    lb.setDelegate(testlb);
+
+    lb.handleNameResolutionError(Status.RESOURCE_EXHAUSTED);
+    assertThat(calls.getAndSet(0)).isEqualTo(1);
+
+    lb.handleSubchannelState(null, null);
+    assertThat(calls.getAndSet(0)).isEqualTo(2);
+
+    lb.shutdown();
+    assertThat(calls.getAndSet(0)).isEqualTo(3);
+  }
+
+  @Test
+  public void handleResolvedAddressGroups_keepOldBalancer() {
+    final List<EquivalentAddressGroup> servers =
+        Collections.singletonList(
+            new EquivalentAddressGroup(new SocketAddress(){}, Attributes.EMPTY));
+    Helper helper = new TestHelper() {
+      @Override
+      public Subchannel createSubchannel(List<EquivalentAddressGroup> addrs, Attributes attrs) {
+        assertThat(addrs).isEqualTo(servers);
+        return new TestSubchannel(addrs, attrs);
+      }
+
+      @Override
+      public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
+        // noop
+      }
+    };
+    AutoConfiguredLoadBalancer lb =
+        (AutoConfiguredLoadBalancer) lbf.newLoadBalancer(helper);
+    LoadBalancer oldDelegate = lb.getDelegate();
+
+    lb.handleResolvedAddressGroups(servers, Attributes.EMPTY);
+
+    assertThat(lb.getDelegate()).isSameAs(oldDelegate);
+  }
+
+  @Test
+  public void handleResolvedAddressGroups_shutsDownOldBalancer() {
+    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+    serviceConfig.put("loadBalancingPolicy", "round_robin");
+    Attributes serviceConfigAttrs =
+        Attributes.newBuilder()
+            .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
+            .build();
+    final List<EquivalentAddressGroup> servers =
+        Collections.singletonList(
+            new EquivalentAddressGroup(
+                new SocketAddress(){},
+                Attributes.EMPTY));
+    Helper helper = new TestHelper() {
+      @Override
+      public Subchannel createSubchannel(List<EquivalentAddressGroup> addrs, Attributes attrs) {
+        assertThat(addrs).isEqualTo(servers);
+        return new TestSubchannel(addrs, attrs);
+      }
+
+      @Override
+      public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
+        // noop
+      }
+    };
+    AutoConfiguredLoadBalancer lb =
+        (AutoConfiguredLoadBalancer) lbf.newLoadBalancer(helper);
+    final AtomicBoolean shutdown = new AtomicBoolean();
+    TestLoadBalancer testlb = new TestLoadBalancer() {
+
+      @Override
+      public void handleNameResolutionError(Status error) {
+        // noop
+      }
+
+      @Override
+      public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
+        // noop
+      }
+
+      @Override
+      public void shutdown() {
+        shutdown.set(true);
+      }
+    };
+    lb.setDelegate(testlb);
+
+    lb.handleResolvedAddressGroups(servers, serviceConfigAttrs);
+
+    assertThat(lb.getDelegateFactory()).isEqualTo(RoundRobinLoadBalancerFactory.getInstance());
+    assertTrue(shutdown.get());
+  }
+
+  @Test
+  public void decideLoadBalancerFactory_noBalancerAddresses_noServiceConfig_pickFirst() {
+    Map<String, Object> serviceConfig = null;
+    List<EquivalentAddressGroup> servers =
+        Collections.singletonList(
+            new EquivalentAddressGroup(new SocketAddress(){}, Attributes.EMPTY));
+    LoadBalancer.Factory factory =
+        AutoConfiguredLoadBalancer.decideLoadBalancerFactory(servers, serviceConfig);
+
+    assertThat(factory).isInstanceOf(PickFirstBalancerFactory.class);
+  }
+
+  @Test
+  public void decideLoadBalancerFactory_oneBalancer_noServiceConfig_grpclb() {
+    Map<String, Object> serviceConfig = null;
+    List<EquivalentAddressGroup> servers =
+        Collections.singletonList(
+            new EquivalentAddressGroup(
+                new SocketAddress(){},
+                Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build()));
+    LoadBalancer.Factory factory = AutoConfiguredLoadBalancer.decideLoadBalancerFactory(
+        servers, serviceConfig);
+
+    assertThat(factory).isInstanceOf(GrpclbLoadBalancerFactory.class);
+  }
+
+  @Test
+  public void decideLoadBalancerFactory_grpclbOverridesServiceConfig() {
+    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+    serviceConfig.put("loadBalancingPolicy", "round_robin");
+    List<EquivalentAddressGroup> servers =
+        Collections.singletonList(
+            new EquivalentAddressGroup(
+                new SocketAddress(){},
+                Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, "ok").build()));
+    LoadBalancer.Factory factory = AutoConfiguredLoadBalancer.decideLoadBalancerFactory(
+        servers, serviceConfig);
+
+    assertThat(factory).isInstanceOf(GrpclbLoadBalancerFactory.class);
+  }
+
+  @Test
+  public void decideLoadBalancerFactory_serviceConfigOverridesDefault() {
+    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+    serviceConfig.put("loadBalancingPolicy", "round_robin");
+    List<EquivalentAddressGroup> servers =
+        Collections.singletonList(
+            new EquivalentAddressGroup(
+                new SocketAddress(){},
+                Attributes.EMPTY));
+    LoadBalancer.Factory factory = AutoConfiguredLoadBalancer.decideLoadBalancerFactory(
+        servers, serviceConfig);
+
+    assertThat(factory).isInstanceOf(RoundRobinLoadBalancerFactory.class);
+  }
+
+  @Test
+  public void decideLoadBalancerFactory_serviceConfigFailsOnUnknown() {
+    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+    serviceConfig.put("loadBalancingPolicy", "MAGIC_BALANCER");
+    List<EquivalentAddressGroup> servers =
+        Collections.singletonList(
+            new EquivalentAddressGroup(
+                new SocketAddress(){},
+                Attributes.EMPTY));
+    try {
+      AutoConfiguredLoadBalancer.decideLoadBalancerFactory(servers, serviceConfig);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+  }
+
+  public static class ForwardingLoadBalancer extends LoadBalancer {
+    private final LoadBalancer delegate;
+
+    public ForwardingLoadBalancer(LoadBalancer delegate) {
+      this.delegate = delegate;
+    }
+
+    protected LoadBalancer delegate() {
+      return delegate;
+    }
+
+    @Override
+    public void handleResolvedAddressGroups(
+        List<EquivalentAddressGroup> servers, Attributes attributes) {
+      delegate().handleResolvedAddressGroups(servers, attributes);
+    }
+
+    @Override
+    public void handleNameResolutionError(Status error) {
+      delegate().handleNameResolutionError(error);
+    }
+
+    @Override
+    public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
+      delegate().handleSubchannelState(subchannel, stateInfo);
+    }
+
+    @Override
+    public void shutdown() {
+      delegate().shutdown();
+    }
+  }
+
+  public static class ForwardingLoadBalancerHelper extends Helper {
+
+    private final Helper delegate;
+
+    public ForwardingLoadBalancerHelper(Helper delegate) {
+      this.delegate = delegate;
+    }
+
+    protected Helper delegate() {
+      return delegate;
+    }
+
+    @Override
+    public Subchannel createSubchannel(List<EquivalentAddressGroup> addrs, Attributes attrs) {
+      return delegate().createSubchannel(addrs, attrs);
+    }
+
+    @Override
+    public ManagedChannel createOobChannel(EquivalentAddressGroup eag, String authority) {
+      return delegate().createOobChannel(eag, authority);
+    }
+
+    @Override
+    public void updateBalancingState(
+        @Nonnull ConnectivityState newState, @Nonnull SubchannelPicker newPicker) {
+      delegate().updateBalancingState(newState, newPicker);
+    }
+
+    @Override
+    public void runSerialized(Runnable task) {
+      delegate().runSerialized(task);
+    }
+
+    @Override
+    public Factory getNameResolverFactory() {
+      return delegate().getNameResolverFactory();
+    }
+
+    @Override
+    public String getAuthority() {
+      return delegate().getAuthority();
+    }
+  }
+
+  private static class TestLoadBalancer extends ForwardingLoadBalancer {
+    TestLoadBalancer() {
+      super(null);
+    }
+  }
+
+  private static class TestHelper extends ForwardingLoadBalancerHelper {
+    TestHelper() {
+      super(null);
+    }
+  }
+
+  private static class TestSubchannel extends Subchannel {
+    TestSubchannel(List<EquivalentAddressGroup> addrs, Attributes attrs) {
+      this.addrs = addrs;
+      this.attrs = attrs;
+    }
+
+    final List<EquivalentAddressGroup> addrs;
+    final Attributes attrs;
+
+    @Override
+    public void shutdown() {
+    }
+
+    @Override
+    public void requestConnection() {
+    }
+
+    @Override
+    public List<EquivalentAddressGroup> getAllAddresses() {
+      return addrs;
+    }
+
+    @Override
+    public Attributes getAttributes() {
+      return attrs;
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ByteWritableBufferTest.java b/core/src/test/java/io/grpc/internal/ByteWritableBufferTest.java
new file mode 100644
index 0000000..cb73031
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ByteWritableBufferTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import java.util.Arrays;
+import org.junit.Before;
+
+public class ByteWritableBufferTest extends WritableBufferTestBase {
+
+  private MessageFramerTest.ByteWritableBuffer buffer;
+
+  @Before
+  public void setup() {
+    buffer = new MessageFramerTest.ByteWritableBuffer(100);
+  }
+
+  @Override
+  protected WritableBuffer buffer() {
+    return buffer;
+  }
+
+  @Override
+  protected byte[] writtenBytes() {
+    return Arrays.copyOf(buffer.data, buffer.readableBytes());
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java b/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java
new file mode 100644
index 0000000..c6a9bfb
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/CallCredentialsApplyingTest.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.grpc.Attributes;
+import io.grpc.CallCredentials;
+import io.grpc.CallCredentials.MetadataApplier;
+import io.grpc.CallOptions;
+import io.grpc.IntegerMarshaller;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.SecurityLevel;
+import io.grpc.Status;
+import io.grpc.StringMarshaller;
+import java.net.SocketAddress;
+import java.util.concurrent.Executor;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Unit test for {@link CallCredentials} applying functionality implemented by {@link
+ * CallCredentialsApplyingTransportFactory} and {@link MetadataApplierImpl}.
+ */
+@RunWith(JUnit4.class)
+public class CallCredentialsApplyingTest {
+  @Mock
+  private ClientTransportFactory mockTransportFactory;
+
+  @Mock
+  private ConnectionClientTransport mockTransport;
+
+  @Mock
+  private ClientStream mockStream;
+
+  @Mock
+  private CallCredentials mockCreds;
+
+  @Mock
+  private Executor mockExecutor;
+
+  @Mock
+  private SocketAddress address;
+
+  private static final String AUTHORITY = "testauthority";
+  private static final String USER_AGENT = "testuseragent";
+  private static final Attributes.Key<String> ATTR_KEY = Attributes.Key.create("somekey");
+  private static final String ATTR_VALUE = "somevalue";
+  private static final MethodDescriptor<String, Integer> method =
+      MethodDescriptor.<String, Integer>newBuilder()
+          .setType(MethodDescriptor.MethodType.UNKNOWN)
+          .setFullMethodName("service/method")
+          .setRequestMarshaller(new StringMarshaller())
+          .setResponseMarshaller(new IntegerMarshaller())
+          .build();
+  private static final Metadata.Key<String> ORIG_HEADER_KEY =
+      Metadata.Key.of("header1", Metadata.ASCII_STRING_MARSHALLER);
+  private static final String ORIG_HEADER_VALUE = "some original header value";
+  private static final Metadata.Key<String> CREDS_KEY =
+      Metadata.Key.of("test-creds", Metadata.ASCII_STRING_MARSHALLER);
+  private static final String CREDS_VALUE = "some credentials";
+
+  private final Metadata origHeaders = new Metadata();
+  private ForwardingConnectionClientTransport transport;
+  private CallOptions callOptions;
+
+  @Before
+  public void setUp() {
+    ClientTransportFactory.ClientTransportOptions clientTransportOptions =
+        new ClientTransportFactory.ClientTransportOptions()
+          .setAuthority(AUTHORITY)
+          .setUserAgent(USER_AGENT);
+
+    MockitoAnnotations.initMocks(this);
+    origHeaders.put(ORIG_HEADER_KEY, ORIG_HEADER_VALUE);
+    when(mockTransportFactory.newClientTransport(address, clientTransportOptions))
+        .thenReturn(mockTransport);
+    when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class)))
+        .thenReturn(mockStream);
+    ClientTransportFactory transportFactory = new CallCredentialsApplyingTransportFactory(
+        mockTransportFactory, mockExecutor);
+    transport = (ForwardingConnectionClientTransport)
+        transportFactory.newClientTransport(address, clientTransportOptions);
+    callOptions = CallOptions.DEFAULT.withCallCredentials(mockCreds);
+    verify(mockTransportFactory).newClientTransport(address, clientTransportOptions);
+    assertSame(mockTransport, transport.delegate());
+  }
+
+  @Test
+  public void parameterPropagation_base() {
+    Attributes transportAttrs = Attributes.newBuilder().set(ATTR_KEY, ATTR_VALUE).build();
+    when(mockTransport.getAttributes()).thenReturn(transportAttrs);
+
+    transport.newStream(method, origHeaders, callOptions);
+
+    ArgumentCaptor<Attributes> attrsCaptor = ArgumentCaptor.forClass(null);
+    verify(mockCreds).applyRequestMetadata(same(method), attrsCaptor.capture(), same(mockExecutor),
+        any(MetadataApplier.class));
+    Attributes attrs = attrsCaptor.getValue();
+    assertSame(ATTR_VALUE, attrs.get(ATTR_KEY));
+    assertSame(AUTHORITY, attrs.get(CallCredentials.ATTR_AUTHORITY));
+    assertSame(SecurityLevel.NONE, attrs.get(CallCredentials.ATTR_SECURITY_LEVEL));
+  }
+
+  @Test
+  public void parameterPropagation_overrideByTransport() {
+    Attributes transportAttrs = Attributes.newBuilder()
+        .set(ATTR_KEY, ATTR_VALUE)
+        .set(CallCredentials.ATTR_AUTHORITY, "transport-override-authority")
+        .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.INTEGRITY)
+        .build();
+    when(mockTransport.getAttributes()).thenReturn(transportAttrs);
+
+    transport.newStream(method, origHeaders, callOptions);
+
+    ArgumentCaptor<Attributes> attrsCaptor = ArgumentCaptor.forClass(null);
+    verify(mockCreds).applyRequestMetadata(same(method), attrsCaptor.capture(), same(mockExecutor),
+        any(MetadataApplier.class));
+    Attributes attrs = attrsCaptor.getValue();
+    assertSame(ATTR_VALUE, attrs.get(ATTR_KEY));
+    assertEquals("transport-override-authority", attrs.get(CallCredentials.ATTR_AUTHORITY));
+    assertSame(SecurityLevel.INTEGRITY, attrs.get(CallCredentials.ATTR_SECURITY_LEVEL));
+  }
+
+  @Test
+  public void parameterPropagation_overrideByCallOptions() {
+    Attributes transportAttrs = Attributes.newBuilder()
+        .set(ATTR_KEY, ATTR_VALUE)
+        .set(CallCredentials.ATTR_AUTHORITY, "transport-override-authority")
+        .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.INTEGRITY)
+        .build();
+    when(mockTransport.getAttributes()).thenReturn(transportAttrs);
+    Executor anotherExecutor = mock(Executor.class);
+
+    transport.newStream(method, origHeaders,
+        callOptions.withAuthority("calloptions-authority").withExecutor(anotherExecutor));
+
+    ArgumentCaptor<Attributes> attrsCaptor = ArgumentCaptor.forClass(null);
+    verify(mockCreds).applyRequestMetadata(same(method), attrsCaptor.capture(),
+        same(anotherExecutor), any(MetadataApplier.class));
+    Attributes attrs = attrsCaptor.getValue();
+    assertSame(ATTR_VALUE, attrs.get(ATTR_KEY));
+    assertEquals("calloptions-authority", attrs.get(CallCredentials.ATTR_AUTHORITY));
+    assertSame(SecurityLevel.INTEGRITY, attrs.get(CallCredentials.ATTR_SECURITY_LEVEL));
+  }
+
+  @Test
+  public void credentialThrows() {
+    final RuntimeException ex = new RuntimeException();
+    when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
+    doThrow(ex).when(mockCreds).applyRequestMetadata(
+        same(method), any(Attributes.class), same(mockExecutor), any(MetadataApplier.class));
+
+    FailingClientStream stream =
+        (FailingClientStream) transport.newStream(method, origHeaders, callOptions);
+
+    verify(mockTransport, never()).newStream(method, origHeaders, callOptions);
+    assertEquals(Status.Code.UNAUTHENTICATED, stream.getError().getCode());
+    assertSame(ex, stream.getError().getCause());
+  }
+
+  @Test
+  public void applyMetadata_inline() {
+    when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
+    doAnswer(new Answer<Void>() {
+        @Override
+        public Void answer(InvocationOnMock invocation) throws Throwable {
+          MetadataApplier applier = (MetadataApplier) invocation.getArguments()[3];
+          Metadata headers = new Metadata();
+          headers.put(CREDS_KEY, CREDS_VALUE);
+          applier.apply(headers);
+          return null;
+        }
+      }).when(mockCreds).applyRequestMetadata(same(method), any(Attributes.class),
+          same(mockExecutor), any(MetadataApplier.class));
+
+    ClientStream stream = transport.newStream(method, origHeaders, callOptions);
+
+    verify(mockTransport).newStream(method, origHeaders, callOptions);
+    assertSame(mockStream, stream);
+    assertEquals(CREDS_VALUE, origHeaders.get(CREDS_KEY));
+    assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY));
+  }
+
+  @Test
+  public void fail_inline() {
+    final Status error = Status.FAILED_PRECONDITION.withDescription("channel not secure for creds");
+    when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
+    doAnswer(new Answer<Void>() {
+        @Override
+        public Void answer(InvocationOnMock invocation) throws Throwable {
+          MetadataApplier applier = (MetadataApplier) invocation.getArguments()[3];
+          applier.fail(error);
+          return null;
+        }
+      }).when(mockCreds).applyRequestMetadata(same(method), any(Attributes.class),
+          same(mockExecutor), any(MetadataApplier.class));
+
+    FailingClientStream stream =
+        (FailingClientStream) transport.newStream(method, origHeaders, callOptions);
+
+    verify(mockTransport, never()).newStream(method, origHeaders, callOptions);
+    assertSame(error, stream.getError());
+  }
+
+  @Test
+  public void applyMetadata_delayed() {
+    when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
+
+    // Will call applyRequestMetadata(), which is no-op.
+    DelayedStream stream = (DelayedStream) transport.newStream(method, origHeaders, callOptions);
+
+    ArgumentCaptor<MetadataApplier> applierCaptor = ArgumentCaptor.forClass(null);
+    verify(mockCreds).applyRequestMetadata(same(method), any(Attributes.class),
+        same(mockExecutor), applierCaptor.capture());
+    verify(mockTransport, never()).newStream(method, origHeaders, callOptions);
+
+    Metadata headers = new Metadata();
+    headers.put(CREDS_KEY, CREDS_VALUE);
+    applierCaptor.getValue().apply(headers);
+
+    verify(mockTransport).newStream(method, origHeaders, callOptions);
+    assertSame(mockStream, stream.getRealStream());
+    assertEquals(CREDS_VALUE, origHeaders.get(CREDS_KEY));
+    assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY));
+  }
+
+  @Test
+  public void fail_delayed() {
+    when(mockTransport.getAttributes()).thenReturn(Attributes.EMPTY);
+
+    // Will call applyRequestMetadata(), which is no-op.
+    DelayedStream stream = (DelayedStream) transport.newStream(method, origHeaders, callOptions);
+
+    ArgumentCaptor<MetadataApplier> applierCaptor = ArgumentCaptor.forClass(null);
+    verify(mockCreds).applyRequestMetadata(same(method), any(Attributes.class),
+        same(mockExecutor), applierCaptor.capture());
+
+    Status error = Status.FAILED_PRECONDITION.withDescription("channel not secure for creds");
+    applierCaptor.getValue().fail(error);
+
+    verify(mockTransport, never()).newStream(method, origHeaders, callOptions);
+    FailingClientStream failingStream = (FailingClientStream) stream.getRealStream();
+    assertSame(error, failingStream.getError());
+  }
+
+  @Test
+  public void noCreds() {
+    callOptions = callOptions.withCallCredentials(null);
+    ClientStream stream = transport.newStream(method, origHeaders, callOptions);
+
+    verify(mockTransport).newStream(method, origHeaders, callOptions);
+    assertSame(mockStream, stream);
+    assertNull(origHeaders.get(CREDS_KEY));
+    assertEquals(ORIG_HEADER_VALUE, origHeaders.get(ORIG_HEADER_KEY));
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/CensusModulesTest.java b/core/src/test/java/io/grpc/internal/CensusModulesTest.java
new file mode 100644
index 0000000..29c59a0
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/CensusModulesTest.java
@@ -0,0 +1,1020 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.opencensus.tags.unsafe.ContextUtils.TAG_CONTEXT_KEY;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import io.grpc.Attributes;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ClientInterceptors;
+import io.grpc.ClientStreamTracer;
+import io.grpc.Context;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.ServerStreamTracer;
+import io.grpc.Status;
+import io.grpc.internal.testing.StatsTestUtils;
+import io.grpc.internal.testing.StatsTestUtils.FakeStatsRecorder;
+import io.grpc.internal.testing.StatsTestUtils.FakeTagContextBinarySerializer;
+import io.grpc.internal.testing.StatsTestUtils.FakeTagger;
+import io.grpc.internal.testing.StatsTestUtils.MockableSpan;
+import io.grpc.testing.GrpcServerRule;
+import io.opencensus.contrib.grpc.metrics.RpcMeasureConstants;
+import io.opencensus.tags.TagContext;
+import io.opencensus.tags.TagValue;
+import io.opencensus.trace.BlankSpan;
+import io.opencensus.trace.EndSpanOptions;
+import io.opencensus.trace.MessageEvent;
+import io.opencensus.trace.MessageEvent.Type;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanBuilder;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.Tracer;
+import io.opencensus.trace.propagation.BinaryFormat;
+import io.opencensus.trace.propagation.SpanContextParseException;
+import io.opencensus.trace.unsafe.ContextUtils;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test for {@link CensusStatsModule} and {@link CensusTracingModule}.
+ */
+@RunWith(JUnit4.class)
+public class CensusModulesTest {
+  private static final CallOptions.Key<String> CUSTOM_OPTION =
+      CallOptions.Key.createWithDefault("option1", "default");
+  private static final CallOptions CALL_OPTIONS =
+      CallOptions.DEFAULT.withOption(CUSTOM_OPTION, "customvalue");
+
+  private static class StringInputStream extends InputStream {
+    final String string;
+
+    StringInputStream(String string) {
+      this.string = string;
+    }
+
+    @Override
+    public int read() {
+      // InProcessTransport doesn't actually read bytes from the InputStream.  The InputStream is
+      // passed to the InProcess server and consumed by MARSHALLER.parse().
+      throw new UnsupportedOperationException("Should not be called");
+    }
+  }
+
+  private static final MethodDescriptor.Marshaller<String> MARSHALLER =
+      new MethodDescriptor.Marshaller<String>() {
+        @Override
+        public InputStream stream(String value) {
+          return new StringInputStream(value);
+        }
+
+        @Override
+        public String parse(InputStream stream) {
+          return ((StringInputStream) stream).string;
+        }
+      };
+
+  private final MethodDescriptor<String, String> method =
+      MethodDescriptor.<String, String>newBuilder()
+          .setType(MethodDescriptor.MethodType.UNKNOWN)
+          .setRequestMarshaller(MARSHALLER)
+          .setResponseMarshaller(MARSHALLER)
+          .setFullMethodName("package1.service2/method3")
+          .build();
+  private final MethodDescriptor<String, String> sampledMethod =
+      method.toBuilder().setSampledToLocalTracing(true).build();
+
+  private final FakeClock fakeClock = new FakeClock();
+  private final FakeTagger tagger = new FakeTagger();
+  private final FakeTagContextBinarySerializer tagCtxSerializer =
+      new FakeTagContextBinarySerializer();
+  private final FakeStatsRecorder statsRecorder = new FakeStatsRecorder();
+  private final Random random = new Random(1234);
+  private final Span fakeClientParentSpan = MockableSpan.generateRandomSpan(random);
+  private final Span spyClientSpan = spy(MockableSpan.generateRandomSpan(random));
+  private final SpanContext fakeClientSpanContext = spyClientSpan.getContext();
+  private final Span spyServerSpan = spy(MockableSpan.generateRandomSpan(random));
+  private final byte[] binarySpanContext = new byte[]{3, 1, 5};
+  private final SpanBuilder spyClientSpanBuilder = spy(new MockableSpan.Builder());
+  private final SpanBuilder spyServerSpanBuilder = spy(new MockableSpan.Builder());
+
+  @Rule
+  public final GrpcServerRule grpcServerRule = new GrpcServerRule().directExecutor();
+
+  @Mock
+  private Tracer tracer;
+  @Mock
+  private BinaryFormat mockTracingPropagationHandler;
+  @Mock
+  private ClientCall.Listener<String> mockClientCallListener;
+  @Mock
+  private ServerCall.Listener<String> mockServerCallListener;
+  @Captor
+  private ArgumentCaptor<Status> statusCaptor;
+  @Captor
+  private ArgumentCaptor<MessageEvent> messageEventCaptor;
+
+  private CensusStatsModule censusStats;
+  private CensusTracingModule censusTracing;
+
+  @Before
+  @SuppressWarnings("unchecked")
+  public void setUp() throws Exception {
+    MockitoAnnotations.initMocks(this);
+    when(spyClientSpanBuilder.startSpan()).thenReturn(spyClientSpan);
+    when(tracer.spanBuilderWithExplicitParent(anyString(), any(Span.class)))
+        .thenReturn(spyClientSpanBuilder);
+    when(spyServerSpanBuilder.startSpan()).thenReturn(spyServerSpan);
+    when(tracer.spanBuilderWithRemoteParent(anyString(), any(SpanContext.class)))
+        .thenReturn(spyServerSpanBuilder);
+    when(mockTracingPropagationHandler.toByteArray(any(SpanContext.class)))
+        .thenReturn(binarySpanContext);
+    when(mockTracingPropagationHandler.fromByteArray(any(byte[].class)))
+        .thenReturn(fakeClientSpanContext);
+    censusStats =
+        new CensusStatsModule(
+            tagger, tagCtxSerializer, statsRecorder, fakeClock.getStopwatchSupplier(), true);
+    censusTracing = new CensusTracingModule(tracer, mockTracingPropagationHandler);
+  }
+
+  @After
+  public void wrapUp() {
+    assertNull(statsRecorder.pollRecord());
+  }
+
+  @Test
+  public void clientInterceptorNoCustomTag() {
+    testClientInterceptors(false);
+  }
+
+  @Test
+  public void clientInterceptorCustomTag() {
+    testClientInterceptors(true);
+  }
+
+  // Test that Census ClientInterceptors uses the TagContext and Span out of the current Context
+  // to create the ClientCallTracer, and that it intercepts ClientCall.Listener.onClose() to call
+  // ClientCallTracer.callEnded().
+  private void testClientInterceptors(boolean nonDefaultContext) {
+    grpcServerRule.getServiceRegistry().addService(
+        ServerServiceDefinition.builder("package1.service2").addMethod(
+            method, new ServerCallHandler<String, String>() {
+                @Override
+                public ServerCall.Listener<String> startCall(
+                    ServerCall<String, String> call, Metadata headers) {
+                  call.sendHeaders(new Metadata());
+                  call.sendMessage("Hello");
+                  call.close(
+                      Status.PERMISSION_DENIED.withDescription("No you don't"), new Metadata());
+                  return mockServerCallListener;
+                }
+              }).build());
+
+    final AtomicReference<CallOptions> capturedCallOptions = new AtomicReference<CallOptions>();
+    ClientInterceptor callOptionsCaptureInterceptor = new ClientInterceptor() {
+        @Override
+        public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+            MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+          capturedCallOptions.set(callOptions);
+          return next.newCall(method, callOptions);
+        }
+      };
+    Channel interceptedChannel =
+        ClientInterceptors.intercept(
+            grpcServerRule.getChannel(), callOptionsCaptureInterceptor,
+            censusStats.getClientInterceptor(true, true), censusTracing.getClientInterceptor());
+    ClientCall<String, String> call;
+    if (nonDefaultContext) {
+      Context ctx =
+          Context.ROOT.withValues(
+              TAG_CONTEXT_KEY,
+              tagger.emptyBuilder().put(
+                  StatsTestUtils.EXTRA_TAG, TagValue.create("extra value")).build(),
+              ContextUtils.CONTEXT_SPAN_KEY,
+              fakeClientParentSpan);
+      Context origCtx = ctx.attach();
+      try {
+        call = interceptedChannel.newCall(method, CALL_OPTIONS);
+      } finally {
+        ctx.detach(origCtx);
+      }
+    } else {
+      assertEquals(TAG_CONTEXT_KEY.get(Context.ROOT), TAG_CONTEXT_KEY.get());
+      assertNull(ContextUtils.CONTEXT_SPAN_KEY.get());
+      call = interceptedChannel.newCall(method, CALL_OPTIONS);
+    }
+
+    // The interceptor adds tracer factory to CallOptions
+    assertEquals("customvalue", capturedCallOptions.get().getOption(CUSTOM_OPTION));
+    assertEquals(2, capturedCallOptions.get().getStreamTracerFactories().size());
+    assertTrue(
+        capturedCallOptions.get().getStreamTracerFactories().get(0)
+        instanceof CensusTracingModule.ClientCallTracer);
+    assertTrue(
+        capturedCallOptions.get().getStreamTracerFactories().get(1)
+        instanceof CensusStatsModule.ClientCallTracer);
+
+    // Make the call
+    Metadata headers = new Metadata();
+    call.start(mockClientCallListener, headers);
+
+    StatsTestUtils.MetricsRecord record = statsRecorder.pollRecord();
+    assertNotNull(record);
+    TagValue methodTag = record.tags.get(RpcMeasureConstants.RPC_METHOD);
+    assertEquals(method.getFullMethodName(), methodTag.asString());
+    if (nonDefaultContext) {
+      TagValue extraTag = record.tags.get(StatsTestUtils.EXTRA_TAG);
+      assertEquals("extra value", extraTag.asString());
+      assertEquals(2, record.tags.size());
+    } else {
+      assertNull(record.tags.get(StatsTestUtils.EXTRA_TAG));
+      assertEquals(1, record.tags.size());
+    }
+
+    if (nonDefaultContext) {
+      verify(tracer).spanBuilderWithExplicitParent(
+          eq("Sent.package1.service2.method3"), same(fakeClientParentSpan));
+      verify(spyClientSpanBuilder).setRecordEvents(eq(true));
+    } else {
+      verify(tracer).spanBuilderWithExplicitParent(
+          eq("Sent.package1.service2.method3"), isNull(Span.class));
+      verify(spyClientSpanBuilder).setRecordEvents(eq(true));
+    }
+    verify(spyClientSpan, never()).end(any(EndSpanOptions.class));
+
+    // End the call
+    call.halfClose();
+    call.request(1);
+
+    verify(mockClientCallListener).onClose(statusCaptor.capture(), any(Metadata.class));
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.Code.PERMISSION_DENIED, status.getCode());
+    assertEquals("No you don't", status.getDescription());
+
+    // The intercepting listener calls callEnded() on ClientCallTracer, which records to Census.
+    record = statsRecorder.pollRecord();
+    assertNotNull(record);
+    methodTag = record.tags.get(RpcMeasureConstants.RPC_METHOD);
+    assertEquals(method.getFullMethodName(), methodTag.asString());
+    TagValue statusTag = record.tags.get(RpcMeasureConstants.RPC_STATUS);
+    assertEquals(Status.Code.PERMISSION_DENIED.toString(), statusTag.asString());
+    if (nonDefaultContext) {
+      TagValue extraTag = record.tags.get(StatsTestUtils.EXTRA_TAG);
+      assertEquals("extra value", extraTag.asString());
+    } else {
+      assertNull(record.tags.get(StatsTestUtils.EXTRA_TAG));
+    }
+    verify(spyClientSpan).end(
+        EndSpanOptions.builder()
+            .setStatus(
+                io.opencensus.trace.Status.PERMISSION_DENIED
+                    .withDescription("No you don't"))
+            .setSampleToLocalSpanStore(false)
+            .build());
+    verify(spyClientSpan, never()).end();
+  }
+
+  @Test
+  public void clientBasicStatsDefaultContext_startsAndFinishes() {
+    subtestClientBasicStatsDefaultContext(true, true);
+  }
+
+  @Test
+  public void clientBasicStatsDefaultContext_startsOnly() {
+    subtestClientBasicStatsDefaultContext(true, false);
+  }
+
+  @Test
+  public void clientBasicStatsDefaultContext_finishesOnly() {
+    subtestClientBasicStatsDefaultContext(false, true);
+  }
+
+  @Test
+  public void clientBasicStatsDefaultContext_neither() {
+    subtestClientBasicStatsDefaultContext(false, true);
+  }
+
+  private void subtestClientBasicStatsDefaultContext(boolean recordStarts, boolean recordFinishes) {
+    CensusStatsModule.ClientCallTracer callTracer =
+        censusStats.newClientCallTracer(
+            tagger.empty(), method.getFullMethodName(), recordStarts, recordFinishes);
+    Metadata headers = new Metadata();
+    ClientStreamTracer tracer = callTracer.newClientStreamTracer(CallOptions.DEFAULT, headers);
+
+    if (recordStarts) {
+      StatsTestUtils.MetricsRecord record = statsRecorder.pollRecord();
+      assertNotNull(record);
+      assertNoServerContent(record);
+      assertEquals(1, record.tags.size());
+      TagValue methodTag = record.tags.get(RpcMeasureConstants.RPC_METHOD);
+      assertEquals(method.getFullMethodName(), methodTag.asString());
+      assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_STARTED_COUNT));
+    } else {
+      assertNull(statsRecorder.pollRecord());
+    }
+
+    fakeClock.forwardTime(30, MILLISECONDS);
+    tracer.outboundHeaders();
+
+    fakeClock.forwardTime(100, MILLISECONDS);
+    tracer.outboundMessage(0);
+    tracer.outboundWireSize(1028);
+    tracer.outboundUncompressedSize(1128);
+
+    fakeClock.forwardTime(16, MILLISECONDS);
+    tracer.inboundMessage(0);
+    tracer.inboundWireSize(33);
+    tracer.inboundUncompressedSize(67);
+    tracer.outboundMessage(1);
+    tracer.outboundWireSize(99);
+    tracer.outboundUncompressedSize(865);
+
+    fakeClock.forwardTime(24, MILLISECONDS);
+    tracer.inboundMessage(1);
+    tracer.inboundWireSize(154);
+    tracer.inboundUncompressedSize(552);
+    tracer.streamClosed(Status.OK);
+    callTracer.callEnded(Status.OK);
+
+    if (recordFinishes) {
+      StatsTestUtils.MetricsRecord record = statsRecorder.pollRecord();
+      assertNotNull(record);
+      assertNoServerContent(record);
+      TagValue methodTag = record.tags.get(RpcMeasureConstants.RPC_METHOD);
+      assertEquals(method.getFullMethodName(), methodTag.asString());
+      TagValue statusTag = record.tags.get(RpcMeasureConstants.RPC_STATUS);
+      assertEquals(Status.Code.OK.toString(), statusTag.asString());
+      assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_FINISHED_COUNT));
+      assertNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_ERROR_COUNT));
+      assertEquals(2, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_REQUEST_COUNT));
+      assertEquals(
+          1028 + 99, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_REQUEST_BYTES));
+      assertEquals(
+          1128 + 865,
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
+      assertEquals(2, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_RESPONSE_COUNT));
+      assertEquals(
+          33 + 154, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_RESPONSE_BYTES));
+      assertEquals(67 + 552,
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
+      assertEquals(30 + 100 + 16 + 24,
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
+    } else {
+      assertNull(statsRecorder.pollRecord());
+    }
+  }
+
+  @Test
+  public void clientBasicTracingDefaultSpan() {
+    CensusTracingModule.ClientCallTracer callTracer =
+        censusTracing.newClientCallTracer(null, method);
+    Metadata headers = new Metadata();
+    ClientStreamTracer clientStreamTracer =
+        callTracer.newClientStreamTracer(CallOptions.DEFAULT, headers);
+    verify(tracer).spanBuilderWithExplicitParent(
+        eq("Sent.package1.service2.method3"), isNull(Span.class));
+    verify(spyClientSpan, never()).end(any(EndSpanOptions.class));
+
+    clientStreamTracer.outboundMessage(0);
+    clientStreamTracer.outboundMessageSent(0, 882, -1);
+    clientStreamTracer.inboundMessage(0);
+    clientStreamTracer.outboundMessage(1);
+    clientStreamTracer.outboundMessageSent(1, -1, 27);
+    clientStreamTracer.inboundMessageRead(0, 255, 90);
+
+    clientStreamTracer.streamClosed(Status.OK);
+    callTracer.callEnded(Status.OK);
+
+    InOrder inOrder = inOrder(spyClientSpan);
+    inOrder.verify(spyClientSpan, times(3)).addMessageEvent(messageEventCaptor.capture());
+    List<MessageEvent> events = messageEventCaptor.getAllValues();
+    assertEquals(
+        MessageEvent.builder(Type.SENT, 0).setCompressedMessageSize(882).build(), events.get(0));
+    assertEquals(
+        MessageEvent.builder(Type.SENT, 1).setUncompressedMessageSize(27).build(), events.get(1));
+    assertEquals(
+        MessageEvent.builder(Type.RECEIVED, 0)
+            .setCompressedMessageSize(255)
+            .setUncompressedMessageSize(90)
+            .build(),
+        events.get(2));
+    inOrder.verify(spyClientSpan).end(
+        EndSpanOptions.builder()
+            .setStatus(io.opencensus.trace.Status.OK)
+            .setSampleToLocalSpanStore(false)
+            .build());
+    verifyNoMoreInteractions(spyClientSpan);
+    verifyNoMoreInteractions(tracer);
+  }
+
+  @Test
+  public void clientTracingSampledToLocalSpanStore() {
+    CensusTracingModule.ClientCallTracer callTracer =
+        censusTracing.newClientCallTracer(null, sampledMethod);
+    callTracer.callEnded(Status.OK);
+
+    verify(spyClientSpan).end(
+        EndSpanOptions.builder()
+            .setStatus(io.opencensus.trace.Status.OK)
+            .setSampleToLocalSpanStore(true)
+            .build());
+  }
+
+  @Test
+  public void clientStreamNeverCreatedStillRecordStats() {
+    CensusStatsModule.ClientCallTracer callTracer =
+        censusStats.newClientCallTracer(
+            tagger.empty(), method.getFullMethodName(), true, true);
+
+    fakeClock.forwardTime(3000, MILLISECONDS);
+    callTracer.callEnded(Status.DEADLINE_EXCEEDED.withDescription("3 seconds"));
+
+    // Upstart record
+    StatsTestUtils.MetricsRecord record = statsRecorder.pollRecord();
+    assertNotNull(record);
+    assertNoServerContent(record);
+    assertEquals(1, record.tags.size());
+    TagValue methodTag = record.tags.get(RpcMeasureConstants.RPC_METHOD);
+    assertEquals(method.getFullMethodName(), methodTag.asString());
+    assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_STARTED_COUNT));
+
+    // Completion record
+    record = statsRecorder.pollRecord();
+    assertNotNull(record);
+    assertNoServerContent(record);
+    methodTag = record.tags.get(RpcMeasureConstants.RPC_METHOD);
+    assertEquals(method.getFullMethodName(), methodTag.asString());
+    TagValue statusTag = record.tags.get(RpcMeasureConstants.RPC_STATUS);
+    assertEquals(Status.Code.DEADLINE_EXCEEDED.toString(), statusTag.asString());
+    assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_FINISHED_COUNT));
+    assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_ERROR_COUNT));
+    assertEquals(0, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_REQUEST_COUNT));
+    assertEquals(0, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_REQUEST_BYTES));
+    assertEquals(0,
+        record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
+    assertEquals(0, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_RESPONSE_COUNT));
+    assertEquals(0, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_RESPONSE_BYTES));
+    assertEquals(0,
+        record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
+    assertEquals(
+        3000, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_SERVER_ELAPSED_TIME));
+  }
+
+  @Test
+  public void clientStreamNeverCreatedStillRecordTracing() {
+    CensusTracingModule.ClientCallTracer callTracer =
+        censusTracing.newClientCallTracer(fakeClientParentSpan, method);
+    verify(tracer).spanBuilderWithExplicitParent(
+        eq("Sent.package1.service2.method3"), same(fakeClientParentSpan));
+    verify(spyClientSpanBuilder).setRecordEvents(eq(true));
+
+    callTracer.callEnded(Status.DEADLINE_EXCEEDED.withDescription("3 seconds"));
+    verify(spyClientSpan).end(
+        EndSpanOptions.builder()
+            .setStatus(
+                io.opencensus.trace.Status.DEADLINE_EXCEEDED
+                    .withDescription("3 seconds"))
+            .setSampleToLocalSpanStore(false)
+            .build());
+    verifyNoMoreInteractions(spyClientSpan);
+  }
+
+  @Test
+  public void statsHeadersPropagateTags_record() {
+    subtestStatsHeadersPropagateTags(true, true);
+  }
+
+  @Test
+  public void statsHeadersPropagateTags_notRecord() {
+    subtestStatsHeadersPropagateTags(true, false);
+  }
+
+  @Test
+  public void statsHeadersNotPropagateTags_record() {
+    subtestStatsHeadersPropagateTags(false, true);
+  }
+
+  @Test
+  public void statsHeadersNotPropagateTags_notRecord() {
+    subtestStatsHeadersPropagateTags(false, false);
+  }
+
+  private void subtestStatsHeadersPropagateTags(boolean propagate, boolean recordStats) {
+    // EXTRA_TAG is propagated by the FakeStatsContextFactory. Note that not all tags are
+    // propagated.  The StatsContextFactory decides which tags are to propagated.  gRPC facilitates
+    // the propagation by putting them in the headers.
+    TagContext clientCtx = tagger.emptyBuilder().put(
+        StatsTestUtils.EXTRA_TAG, TagValue.create("extra-tag-value-897")).build();
+    CensusStatsModule census =
+        new CensusStatsModule(
+            tagger,
+            tagCtxSerializer,
+            statsRecorder,
+            fakeClock.getStopwatchSupplier(),
+            propagate);
+    Metadata headers = new Metadata();
+    CensusStatsModule.ClientCallTracer callTracer =
+        census.newClientCallTracer(clientCtx, method.getFullMethodName(), recordStats, recordStats);
+    // This propagates clientCtx to headers if propagates==true
+    callTracer.newClientStreamTracer(CallOptions.DEFAULT, headers);
+    if (recordStats) {
+      // Client upstart record
+      StatsTestUtils.MetricsRecord clientRecord = statsRecorder.pollRecord();
+      assertNotNull(clientRecord);
+      assertNoServerContent(clientRecord);
+      assertEquals(2, clientRecord.tags.size());
+      TagValue clientMethodTag = clientRecord.tags.get(RpcMeasureConstants.RPC_METHOD);
+      assertEquals(method.getFullMethodName(), clientMethodTag.asString());
+      TagValue clientPropagatedTag = clientRecord.tags.get(StatsTestUtils.EXTRA_TAG);
+      assertEquals("extra-tag-value-897", clientPropagatedTag.asString());
+    }
+
+    if (propagate) {
+      assertTrue(headers.containsKey(census.statsHeader));
+    } else {
+      assertFalse(headers.containsKey(census.statsHeader));
+      return;
+    }
+
+    ServerStreamTracer serverTracer =
+        census.getServerTracerFactory(recordStats, recordStats).newServerStreamTracer(
+            method.getFullMethodName(), headers);
+    // Server tracer deserializes clientCtx from the headers, so that it records stats with the
+    // propagated tags.
+    Context serverContext = serverTracer.filterContext(Context.ROOT);
+    // It also put clientCtx in the Context seen by the call handler
+    assertEquals(
+        tagger.toBuilder(clientCtx).put(
+            RpcMeasureConstants.RPC_METHOD,
+            TagValue.create(method.getFullMethodName())).build(),
+        TAG_CONTEXT_KEY.get(serverContext));
+
+    // Verifies that the server tracer records the status with the propagated tag
+    serverTracer.streamClosed(Status.OK);
+
+    if (recordStats) {
+      // Server upstart record
+      StatsTestUtils.MetricsRecord serverRecord = statsRecorder.pollRecord();
+      assertNotNull(serverRecord);
+      assertNoClientContent(serverRecord);
+      assertEquals(2, serverRecord.tags.size());
+      TagValue serverMethodTag = serverRecord.tags.get(RpcMeasureConstants.RPC_METHOD);
+      assertEquals(method.getFullMethodName(), serverMethodTag.asString());
+      TagValue serverPropagatedTag = serverRecord.tags.get(StatsTestUtils.EXTRA_TAG);
+      assertEquals("extra-tag-value-897", serverPropagatedTag.asString());
+
+      // Server completion record
+      serverRecord = statsRecorder.pollRecord();
+      assertNotNull(serverRecord);
+      assertNoClientContent(serverRecord);
+      serverMethodTag = serverRecord.tags.get(RpcMeasureConstants.RPC_METHOD);
+      assertEquals(method.getFullMethodName(), serverMethodTag.asString());
+      TagValue serverStatusTag = serverRecord.tags.get(RpcMeasureConstants.RPC_STATUS);
+      assertEquals(Status.Code.OK.toString(), serverStatusTag.asString());
+      assertNull(serverRecord.getMetric(RpcMeasureConstants.RPC_SERVER_ERROR_COUNT));
+      serverPropagatedTag = serverRecord.tags.get(StatsTestUtils.EXTRA_TAG);
+      assertEquals("extra-tag-value-897", serverPropagatedTag.asString());
+    }
+
+    // Verifies that the client tracer factory uses clientCtx, which includes the custom tags, to
+    // record stats.
+    callTracer.callEnded(Status.OK);
+
+    if (recordStats) {
+      // Client completion record
+      StatsTestUtils.MetricsRecord clientRecord = statsRecorder.pollRecord();
+      assertNotNull(clientRecord);
+      assertNoServerContent(clientRecord);
+      TagValue clientMethodTag = clientRecord.tags.get(RpcMeasureConstants.RPC_METHOD);
+      assertEquals(method.getFullMethodName(), clientMethodTag.asString());
+      TagValue clientStatusTag = clientRecord.tags.get(RpcMeasureConstants.RPC_STATUS);
+      assertEquals(Status.Code.OK.toString(), clientStatusTag.asString());
+      assertNull(clientRecord.getMetric(RpcMeasureConstants.RPC_CLIENT_ERROR_COUNT));
+      TagValue clientPropagatedTag = clientRecord.tags.get(StatsTestUtils.EXTRA_TAG);
+      assertEquals("extra-tag-value-897", clientPropagatedTag.asString());
+    }
+
+    if (!recordStats) {
+      assertNull(statsRecorder.pollRecord());
+    }
+  }
+
+  @Test
+  public void statsHeadersNotPropagateDefaultContext() {
+    CensusStatsModule.ClientCallTracer callTracer =
+        censusStats.newClientCallTracer(tagger.empty(), method.getFullMethodName(), false, false);
+    Metadata headers = new Metadata();
+    callTracer.newClientStreamTracer(CallOptions.DEFAULT, headers);
+    assertFalse(headers.containsKey(censusStats.statsHeader));
+  }
+
+  @Test
+  public void statsHeaderMalformed() {
+    // Construct a malformed header and make sure parsing it will throw
+    byte[] statsHeaderValue = new byte[]{1};
+    Metadata.Key<byte[]> arbitraryStatsHeader =
+        Metadata.Key.of("grpc-tags-bin", Metadata.BINARY_BYTE_MARSHALLER);
+    try {
+      tagCtxSerializer.fromByteArray(statsHeaderValue);
+      fail("Should have thrown");
+    } catch (Exception e) {
+      // Expected
+    }
+
+    // But the header key will return a default context for it
+    Metadata headers = new Metadata();
+    assertNull(headers.get(censusStats.statsHeader));
+    headers.put(arbitraryStatsHeader, statsHeaderValue);
+    assertSame(tagger.empty(), headers.get(censusStats.statsHeader));
+  }
+
+  @Test
+  public void traceHeadersPropagateSpanContext() throws Exception {
+    CensusTracingModule.ClientCallTracer callTracer =
+        censusTracing.newClientCallTracer(fakeClientParentSpan, method);
+    Metadata headers = new Metadata();
+    callTracer.newClientStreamTracer(CallOptions.DEFAULT, headers);
+
+    verify(mockTracingPropagationHandler).toByteArray(same(fakeClientSpanContext));
+    verifyNoMoreInteractions(mockTracingPropagationHandler);
+    verify(tracer).spanBuilderWithExplicitParent(
+        eq("Sent.package1.service2.method3"), same(fakeClientParentSpan));
+    verify(spyClientSpanBuilder).setRecordEvents(eq(true));
+    verifyNoMoreInteractions(tracer);
+    assertTrue(headers.containsKey(censusTracing.tracingHeader));
+
+    ServerStreamTracer serverTracer =
+        censusTracing.getServerTracerFactory().newServerStreamTracer(
+            method.getFullMethodName(), headers);
+    verify(mockTracingPropagationHandler).fromByteArray(same(binarySpanContext));
+    verify(tracer).spanBuilderWithRemoteParent(
+        eq("Recv.package1.service2.method3"), same(spyClientSpan.getContext()));
+    verify(spyServerSpanBuilder).setRecordEvents(eq(true));
+
+    Context filteredContext = serverTracer.filterContext(Context.ROOT);
+    assertSame(spyServerSpan, ContextUtils.CONTEXT_SPAN_KEY.get(filteredContext));
+  }
+
+  @Test
+  public void traceHeaders_propagateSpanContext() throws Exception {
+    CensusTracingModule.ClientCallTracer callTracer =
+        censusTracing.newClientCallTracer(fakeClientParentSpan, method);
+    Metadata headers = new Metadata();
+
+    callTracer.newClientStreamTracer(CallOptions.DEFAULT, headers);
+
+    assertThat(headers.keys()).isNotEmpty();
+  }
+
+  @Test
+  public void traceHeaders_missingCensusImpl_notPropagateSpanContext()
+      throws Exception {
+    reset(spyClientSpanBuilder);
+    when(spyClientSpanBuilder.startSpan()).thenReturn(BlankSpan.INSTANCE);
+    Metadata headers = new Metadata();
+
+    CensusTracingModule.ClientCallTracer callTracer =
+        censusTracing.newClientCallTracer(BlankSpan.INSTANCE, method);
+    callTracer.newClientStreamTracer(CallOptions.DEFAULT, headers);
+
+    assertThat(headers.keys()).isEmpty();
+  }
+
+  @Test
+  public void traceHeaders_clientMissingCensusImpl_preservingHeaders() throws Exception {
+    reset(spyClientSpanBuilder);
+    when(spyClientSpanBuilder.startSpan()).thenReturn(BlankSpan.INSTANCE);
+    Metadata headers = new Metadata();
+    headers.put(
+        Metadata.Key.of("never-used-key-bin", Metadata.BINARY_BYTE_MARSHALLER),
+        new byte[] {});
+    Set<String> originalHeaderKeys = new HashSet<String>(headers.keys());
+
+    CensusTracingModule.ClientCallTracer callTracer =
+        censusTracing.newClientCallTracer(BlankSpan.INSTANCE, method);
+    callTracer.newClientStreamTracer(CallOptions.DEFAULT, headers);
+
+    assertThat(headers.keys()).containsExactlyElementsIn(originalHeaderKeys);
+  }
+
+  @Test
+  public void traceHeaderMalformed() throws Exception {
+    // As comparison, normal header parsing
+    Metadata headers = new Metadata();
+    headers.put(censusTracing.tracingHeader, fakeClientSpanContext);
+    // mockTracingPropagationHandler was stubbed to always return fakeServerParentSpanContext
+    assertSame(spyClientSpan.getContext(), headers.get(censusTracing.tracingHeader));
+
+    // Make BinaryPropagationHandler always throw when parsing the header
+    when(mockTracingPropagationHandler.fromByteArray(any(byte[].class)))
+        .thenThrow(new SpanContextParseException("Malformed header"));
+
+    headers = new Metadata();
+    assertNull(headers.get(censusTracing.tracingHeader));
+    headers.put(censusTracing.tracingHeader, fakeClientSpanContext);
+    assertSame(SpanContext.INVALID, headers.get(censusTracing.tracingHeader));
+    assertNotSame(spyClientSpan.getContext(), SpanContext.INVALID);
+
+    // A null Span is used as the parent in this case
+    censusTracing.getServerTracerFactory().newServerStreamTracer(
+        method.getFullMethodName(), headers);
+    verify(tracer).spanBuilderWithRemoteParent(
+        eq("Recv.package1.service2.method3"), isNull(SpanContext.class));
+    verify(spyServerSpanBuilder).setRecordEvents(eq(true));
+  }
+
+  @Test
+  public void serverBasicStatsNoHeaders_startsAndFinishes() {
+    subtestServerBasicStatsNoHeaders(true, true);
+  }
+
+  @Test
+  public void serverBasicStatsNoHeaders_startsOnly() {
+    subtestServerBasicStatsNoHeaders(true, false);
+  }
+
+  @Test
+  public void serverBasicStatsNoHeaders_finishesOnly() {
+    subtestServerBasicStatsNoHeaders(false, true);
+  }
+
+  @Test
+  public void serverBasicStatsNoHeaders_neither() {
+    subtestServerBasicStatsNoHeaders(false, false);
+  }
+
+  private void subtestServerBasicStatsNoHeaders(boolean recordStarts, boolean recordFinishes) {
+    ServerStreamTracer.Factory tracerFactory =
+        censusStats.getServerTracerFactory(recordStarts, recordFinishes);
+    ServerStreamTracer tracer =
+        tracerFactory.newServerStreamTracer(method.getFullMethodName(), new Metadata());
+
+    if (recordStarts) {
+      StatsTestUtils.MetricsRecord record = statsRecorder.pollRecord();
+      assertNotNull(record);
+      assertNoClientContent(record);
+      assertEquals(1, record.tags.size());
+      TagValue methodTag = record.tags.get(RpcMeasureConstants.RPC_METHOD);
+      assertEquals(method.getFullMethodName(), methodTag.asString());
+      assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_STARTED_COUNT));
+    } else {
+      assertNull(statsRecorder.pollRecord());
+    }
+
+    Context filteredContext = tracer.filterContext(Context.ROOT);
+    TagContext statsCtx = TAG_CONTEXT_KEY.get(filteredContext);
+    assertEquals(
+        tagger
+            .emptyBuilder()
+            .put(
+                RpcMeasureConstants.RPC_METHOD,
+                TagValue.create(method.getFullMethodName()))
+            .build(),
+        statsCtx);
+
+    tracer.inboundMessage(0);
+    tracer.inboundWireSize(34);
+    tracer.inboundUncompressedSize(67);
+
+    fakeClock.forwardTime(100, MILLISECONDS);
+    tracer.outboundMessage(0);
+    tracer.outboundWireSize(1028);
+    tracer.outboundUncompressedSize(1128);
+
+    fakeClock.forwardTime(16, MILLISECONDS);
+    tracer.inboundMessage(1);
+    tracer.inboundWireSize(154);
+    tracer.inboundUncompressedSize(552);
+    tracer.outboundMessage(1);
+    tracer.outboundWireSize(99);
+    tracer.outboundUncompressedSize(865);
+
+    fakeClock.forwardTime(24, MILLISECONDS);
+
+    tracer.streamClosed(Status.CANCELLED);
+
+    if (recordFinishes) {
+      StatsTestUtils.MetricsRecord record = statsRecorder.pollRecord();
+      assertNotNull(record);
+      assertNoClientContent(record);
+      TagValue methodTag = record.tags.get(RpcMeasureConstants.RPC_METHOD);
+      assertEquals(method.getFullMethodName(), methodTag.asString());
+      TagValue statusTag = record.tags.get(RpcMeasureConstants.RPC_STATUS);
+      assertEquals(Status.Code.CANCELLED.toString(), statusTag.asString());
+      assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_FINISHED_COUNT));
+      assertEquals(1, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_ERROR_COUNT));
+      assertEquals(2, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_RESPONSE_COUNT));
+      assertEquals(
+          1028 + 99, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_RESPONSE_BYTES));
+      assertEquals(
+          1128 + 865,
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES));
+      assertEquals(2, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_REQUEST_COUNT));
+      assertEquals(
+          34 + 154, record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_REQUEST_BYTES));
+      assertEquals(67 + 552,
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES));
+      assertEquals(100 + 16 + 24,
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_SERVER_LATENCY));
+    } else {
+      assertNull(statsRecorder.pollRecord());
+    }
+  }
+
+  @Test
+  public void serverBasicTracingNoHeaders() {
+    ServerStreamTracer.Factory tracerFactory = censusTracing.getServerTracerFactory();
+    ServerStreamTracer serverStreamTracer =
+        tracerFactory.newServerStreamTracer(method.getFullMethodName(), new Metadata());
+    verifyZeroInteractions(mockTracingPropagationHandler);
+    verify(tracer).spanBuilderWithRemoteParent(
+        eq("Recv.package1.service2.method3"), isNull(SpanContext.class));
+    verify(spyServerSpanBuilder).setRecordEvents(eq(true));
+
+    Context filteredContext = serverStreamTracer.filterContext(Context.ROOT);
+    assertSame(spyServerSpan, ContextUtils.CONTEXT_SPAN_KEY.get(filteredContext));
+
+    serverStreamTracer.serverCallStarted(
+        new ServerCallInfoImpl<String, String>(method, Attributes.EMPTY, null));
+
+    verify(spyServerSpan, never()).end(any(EndSpanOptions.class));
+
+    serverStreamTracer.outboundMessage(0);
+    serverStreamTracer.outboundMessageSent(0, 882, -1);
+    serverStreamTracer.inboundMessage(0);
+    serverStreamTracer.outboundMessage(1);
+    serverStreamTracer.outboundMessageSent(1, -1, 27);
+    serverStreamTracer.inboundMessageRead(0, 255, 90);
+
+    serverStreamTracer.streamClosed(Status.CANCELLED);
+
+    InOrder inOrder = inOrder(spyServerSpan);
+    inOrder.verify(spyServerSpan, times(3)).addMessageEvent(messageEventCaptor.capture());
+    List<MessageEvent> events = messageEventCaptor.getAllValues();
+    assertEquals(
+        MessageEvent.builder(Type.SENT, 0).setCompressedMessageSize(882).build(), events.get(0));
+    assertEquals(
+        MessageEvent.builder(Type.SENT, 1).setUncompressedMessageSize(27).build(), events.get(1));
+    assertEquals(
+        MessageEvent.builder(Type.RECEIVED, 0)
+            .setCompressedMessageSize(255)
+            .setUncompressedMessageSize(90)
+            .build(),
+        events.get(2));
+    inOrder.verify(spyServerSpan).end(
+        EndSpanOptions.builder()
+            .setStatus(io.opencensus.trace.Status.CANCELLED)
+            .setSampleToLocalSpanStore(false)
+            .build());
+    verifyNoMoreInteractions(spyServerSpan);
+  }
+
+  @Test
+  public void serverTracingSampledToLocalSpanStore() {
+    ServerStreamTracer.Factory tracerFactory = censusTracing.getServerTracerFactory();
+    ServerStreamTracer serverStreamTracer =
+        tracerFactory.newServerStreamTracer(sampledMethod.getFullMethodName(), new Metadata());
+
+    serverStreamTracer.filterContext(Context.ROOT);
+
+    serverStreamTracer.serverCallStarted(
+        new ServerCallInfoImpl<String, String>(sampledMethod, Attributes.EMPTY, null));
+
+    serverStreamTracer.streamClosed(Status.CANCELLED);
+
+    verify(spyServerSpan).end(
+        EndSpanOptions.builder()
+            .setStatus(io.opencensus.trace.Status.CANCELLED)
+            .setSampleToLocalSpanStore(true)
+            .build());
+  }
+
+  @Test
+  public void serverTracingNotSampledToLocalSpanStore_whenServerCallNotCreated() {
+    ServerStreamTracer.Factory tracerFactory = censusTracing.getServerTracerFactory();
+    ServerStreamTracer serverStreamTracer =
+        tracerFactory.newServerStreamTracer(sampledMethod.getFullMethodName(), new Metadata());
+
+    serverStreamTracer.streamClosed(Status.CANCELLED);
+
+    verify(spyServerSpan).end(
+        EndSpanOptions.builder()
+            .setStatus(io.opencensus.trace.Status.CANCELLED)
+            .setSampleToLocalSpanStore(false)
+            .build());
+  }
+
+  @Test
+  public void convertToTracingStatus() {
+    // Without description
+    for (Status.Code grpcCode : Status.Code.values()) {
+      Status grpcStatus = Status.fromCode(grpcCode);
+      io.opencensus.trace.Status tracingStatus =
+          CensusTracingModule.convertStatus(grpcStatus);
+      assertEquals(grpcCode.toString(), tracingStatus.getCanonicalCode().toString());
+      assertNull(tracingStatus.getDescription());
+    }
+
+    // With description
+    for (Status.Code grpcCode : Status.Code.values()) {
+      Status grpcStatus = Status.fromCode(grpcCode).withDescription("This is my description");
+      io.opencensus.trace.Status tracingStatus =
+          CensusTracingModule.convertStatus(grpcStatus);
+      assertEquals(grpcCode.toString(), tracingStatus.getCanonicalCode().toString());
+      assertEquals(grpcStatus.getDescription(), tracingStatus.getDescription());
+    }
+  }
+
+
+  @Test
+  public void generateTraceSpanName() {
+    assertEquals(
+        "Sent.io.grpc.Foo", CensusTracingModule.generateTraceSpanName(false, "io.grpc/Foo"));
+    assertEquals(
+        "Recv.io.grpc.Bar", CensusTracingModule.generateTraceSpanName(true, "io.grpc/Bar"));
+  }
+
+  private static void assertNoServerContent(StatsTestUtils.MetricsRecord record) {
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_SERVER_ERROR_COUNT));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_SERVER_REQUEST_COUNT));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_SERVER_RESPONSE_COUNT));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_SERVER_REQUEST_BYTES));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_SERVER_RESPONSE_BYTES));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_SERVER_SERVER_ELAPSED_TIME));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_SERVER_SERVER_LATENCY));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES));
+  }
+
+  private static void assertNoClientContent(StatsTestUtils.MetricsRecord record) {
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_ERROR_COUNT));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_REQUEST_COUNT));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_RESPONSE_COUNT));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_REQUEST_BYTES));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_RESPONSE_BYTES));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_SERVER_ELAPSED_TIME));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
+    assertNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ChannelExecutorTest.java b/core/src/test/java/io/grpc/internal/ChannelExecutorTest.java
new file mode 100644
index 0000000..db18366
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ChannelExecutorTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Unit tests for {@link ChannelExecutor}.
+ */
+@RunWith(JUnit4.class)
+public class ChannelExecutorTest {
+  private final BlockingQueue<Throwable> uncaughtErrors = new LinkedBlockingQueue<Throwable>();
+  private final ChannelExecutor executor = new ChannelExecutor() {
+      @Override
+      void handleUncaughtThrowable(Throwable t) {
+        uncaughtErrors.add(t);
+      }
+    };
+
+  @Mock
+  private Runnable task1;
+
+  @Mock
+  private Runnable task2;
+
+  @Mock
+  private Runnable task3;
+
+  @Before public void setUp() {
+    MockitoAnnotations.initMocks(this);
+  }
+
+  @After public void tearDown() {
+    assertThat(uncaughtErrors).isEmpty();
+  }
+
+  @Test
+  public void singleThread() {
+    executor.executeLater(task1);
+    executor.executeLater(task2);
+    InOrder inOrder = inOrder(task1, task2, task3);
+    inOrder.verifyNoMoreInteractions();
+    executor.drain();
+    inOrder.verify(task1).run();
+    inOrder.verify(task2).run();
+
+    executor.executeLater(task3);
+    inOrder.verifyNoMoreInteractions();
+    executor.drain();
+    inOrder.verify(task3).run();
+  }
+
+  @Test
+  public void multiThread() throws Exception {
+    InOrder inOrder = inOrder(task1, task2);
+
+    final CountDownLatch task1Added = new CountDownLatch(1);
+    final CountDownLatch task1Running = new CountDownLatch(1);
+    final CountDownLatch task1Proceed = new CountDownLatch(1);
+    final CountDownLatch sideThreadDone = new CountDownLatch(1);
+    final AtomicReference<Thread> task1Thread = new AtomicReference<Thread>();
+    final AtomicReference<Thread> task2Thread = new AtomicReference<Thread>();
+
+    doAnswer(new Answer<Void>() {
+        @Override
+        public Void answer(InvocationOnMock invocation) {
+          task1Thread.set(Thread.currentThread());
+          task1Running.countDown();
+          try {
+            assertTrue(task1Proceed.await(5, TimeUnit.SECONDS));
+          } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+          }
+          return null;
+        }
+      }).when(task1).run();
+
+    doAnswer(new Answer<Void>() {
+        @Override
+        public Void answer(InvocationOnMock invocation) {
+          task2Thread.set(Thread.currentThread());
+          return null;
+        }
+      }).when(task2).run();
+
+    Thread sideThread = new Thread() {
+        @Override
+        public void run() {
+          executor.executeLater(task1);
+          task1Added.countDown();
+          executor.drain();
+          sideThreadDone.countDown();
+        }
+      };
+    sideThread.start();
+
+    assertTrue(task1Added.await(5, TimeUnit.SECONDS));
+    executor.executeLater(task2);
+    assertTrue(task1Running.await(5, TimeUnit.SECONDS));
+    // This will do nothing because task1 is running until task1Proceed is set
+    executor.drain();
+
+    inOrder.verify(task1).run();
+    inOrder.verifyNoMoreInteractions();
+
+    task1Proceed.countDown();
+    // drain() on the side thread has returned, which runs task2
+    assertTrue(sideThreadDone.await(5, TimeUnit.SECONDS));
+    inOrder.verify(task2).run();
+
+    assertSame(sideThread, task1Thread.get());
+    assertSame(sideThread, task2Thread.get());
+  }
+
+  @Test
+  public void taskThrows() {
+    InOrder inOrder = inOrder(task1, task2, task3);
+    final RuntimeException e = new RuntimeException("Simulated");
+    doAnswer(new Answer<Void>() {
+        @Override
+        public Void answer(InvocationOnMock invocation) {
+          throw e;
+        }
+      }).when(task2).run();
+    executor.executeLater(task1);
+    executor.executeLater(task2);
+    executor.executeLater(task3);
+    executor.drain();
+    inOrder.verify(task1).run();
+    inOrder.verify(task2).run();
+    inOrder.verify(task3).run();
+    assertThat(uncaughtErrors).containsExactly(e);
+    uncaughtErrors.clear();
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ChannelTracerTest.java b/core/src/test/java/io/grpc/internal/ChannelTracerTest.java
new file mode 100644
index 0000000..1603a54
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ChannelTracerTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.grpc.InternalChannelz.ChannelStats;
+import io.grpc.InternalChannelz.ChannelTrace.Event;
+import io.grpc.InternalChannelz.ChannelTrace.Event.Severity;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link ChannelTracer}.
+ */
+@RunWith(JUnit4.class)
+public class ChannelTracerTest {
+  @Rule
+  public final ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void channelTracerWithZeroMaxEventsShouldThrow() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("maxEvents must be greater than zero");
+
+    new ChannelTracer(/* maxEvents= */ 0, /* channelCreationTimeNanos= */ 3L, "fooType");
+  }
+
+  @Test
+  public void reportEvents() {
+    ChannelTracer channelTracer =
+        new ChannelTracer(/* maxEvents= */ 2, /* channelCreationTimeNanos= */ 3L, "fooType");
+    ChannelStats.Builder builder = new ChannelStats.Builder();
+    Event e1 = new Event.Builder()
+        .setDescription("e1").setSeverity(Severity.CT_ERROR).setTimestampNanos(1001).build();
+    Event e2 = new Event.Builder()
+        .setDescription("e2").setSeverity(Severity.CT_INFO).setTimestampNanos(1002).build();
+    Event e3 = new Event.Builder()
+        .setDescription("e3").setSeverity(Severity.CT_WARNING).setTimestampNanos(1003).build();
+    Event e4 = new Event.Builder()
+        .setDescription("e4").setSeverity(Severity.CT_UNKNOWN).setTimestampNanos(1004).build();
+
+    channelTracer.updateBuilder(builder);
+    ChannelStats stats = builder.build();
+    assertThat(stats.channelTrace.events).hasSize(1);
+    Event creationEvent = stats.channelTrace.events.get(0);
+    assertThat(stats.channelTrace.numEventsLogged).isEqualTo(1);
+
+
+    channelTracer.reportEvent(e1);
+    channelTracer.updateBuilder(builder);
+    stats = builder.build();
+
+    assertThat(stats.channelTrace.events).containsExactly(creationEvent, e1);
+    assertThat(stats.channelTrace.numEventsLogged).isEqualTo(2);
+
+    channelTracer.reportEvent(e2);
+    channelTracer.updateBuilder(builder);
+    stats = builder.build();
+
+    assertThat(stats.channelTrace.events).containsExactly(e1, e2);
+    assertThat(stats.channelTrace.numEventsLogged).isEqualTo(3);
+
+    channelTracer.reportEvent(e3);
+    channelTracer.updateBuilder(builder);
+    stats = builder.build();
+
+    assertThat(stats.channelTrace.events).containsExactly(e2, e3);
+    assertThat(stats.channelTrace.numEventsLogged).isEqualTo(4);
+
+    channelTracer.reportEvent(e4);
+    channelTracer.updateBuilder(builder);
+    stats = builder.build();
+
+    assertThat(stats.channelTrace.events).containsExactly(e3, e4);
+    assertThat(stats.channelTrace.numEventsLogged).isEqualTo(5);
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ClientCallImplTest.java b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java
new file mode 100644
index 0000000..c8f7254
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ClientCallImplTest.java
@@ -0,0 +1,968 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.internal.GrpcUtil.ACCEPT_ENCODING_SPLITTER;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.Attributes;
+import io.grpc.Attributes.Key;
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.ClientStreamTracer;
+import io.grpc.Codec;
+import io.grpc.Context;
+import io.grpc.Deadline;
+import io.grpc.Decompressor;
+import io.grpc.DecompressorRegistry;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.Status;
+import io.grpc.internal.ClientCallImpl.ClientTransportProvider;
+import io.grpc.internal.testing.SingleMessageProducer;
+import io.grpc.testing.TestMethodDescriptors;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test for {@link ClientCallImpl}.
+ */
+@RunWith(JUnit4.class)
+public class ClientCallImplTest {
+
+  private final FakeClock fakeClock = new FakeClock();
+  private final ScheduledExecutorService deadlineCancellationExecutor =
+      fakeClock.getScheduledExecutorService();
+  private final CallTracer channelCallTracer = CallTracer.getDefaultFactory().create();
+  private final DecompressorRegistry decompressorRegistry =
+      DecompressorRegistry.getDefaultInstance().with(new Codec.Gzip(), true);
+  private final MethodDescriptor<Void, Void> method = MethodDescriptor.<Void, Void>newBuilder()
+      .setType(MethodType.UNARY)
+      .setFullMethodName("service/method")
+      .setRequestMarshaller(TestMethodDescriptors.voidMarshaller())
+      .setResponseMarshaller(TestMethodDescriptors.voidMarshaller())
+      .build();
+
+  @Mock private ClientStreamListener streamListener;
+  @Mock private ClientTransport clientTransport;
+  @Captor private ArgumentCaptor<Status> statusCaptor;
+
+  @Mock
+  private ClientStreamTracer.Factory streamTracerFactory;
+
+  @Mock
+  private ClientTransport transport;
+
+  @Mock
+  private ClientTransportProvider provider;
+
+  @Mock
+  private ClientStream stream;
+
+  @Mock
+  private ClientCall.Listener<Void> callListener;
+
+  @Captor
+  private ArgumentCaptor<ClientStreamListener> listenerArgumentCaptor;
+
+  @Captor
+  private ArgumentCaptor<Status> statusArgumentCaptor;
+
+  private CallOptions baseCallOptions;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    when(provider.get(any(PickSubchannelArgsImpl.class))).thenReturn(transport);
+    when(transport.newStream(
+            any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)))
+        .thenReturn(stream);
+    baseCallOptions = CallOptions.DEFAULT.withStreamTracerFactory(streamTracerFactory);
+  }
+
+  @After
+  public void tearDown() {
+    verifyZeroInteractions(streamTracerFactory);
+  }
+
+  @Test
+  public void statusPropagatedFromStreamToCallListener() {
+    DelayedExecutor executor = new DelayedExecutor();
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        executor,
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+    call.start(callListener, new Metadata());
+    verify(stream).start(listenerArgumentCaptor.capture());
+    final ClientStreamListener streamListener = listenerArgumentCaptor.getValue();
+    streamListener.headersRead(new Metadata());
+    Status status = Status.RESOURCE_EXHAUSTED.withDescription("simulated");
+    streamListener.closed(status , new Metadata());
+    executor.release();
+
+    verify(callListener).onClose(same(status), Matchers.isA(Metadata.class));
+  }
+
+  @Test
+  public void exceptionInOnMessageTakesPrecedenceOverServer() {
+    DelayedExecutor executor = new DelayedExecutor();
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        executor,
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+    call.start(callListener, new Metadata());
+    verify(stream).start(listenerArgumentCaptor.capture());
+    final ClientStreamListener streamListener = listenerArgumentCaptor.getValue();
+    streamListener.headersRead(new Metadata());
+
+    RuntimeException failure = new RuntimeException("bad");
+    doThrow(failure).when(callListener).onMessage(Matchers.<Void>any());
+
+    /*
+     * In unary calls, the server closes the call right after responding, so the onClose call is
+     * queued to run.  When messageRead is called, an exception will occur and attempt to cancel the
+     * stream.  However, since the server closed it "first" the second exception is lost leading to
+     * the call being counted as successful.
+     */
+    streamListener
+        .messagesAvailable(new SingleMessageProducer(new ByteArrayInputStream(new byte[]{})));
+    streamListener.closed(Status.OK, new Metadata());
+    executor.release();
+
+    verify(callListener).onClose(statusArgumentCaptor.capture(), Matchers.isA(Metadata.class));
+    Status callListenerStatus = statusArgumentCaptor.getValue();
+    assertThat(callListenerStatus.getCode()).isEqualTo(Status.Code.CANCELLED);
+    assertThat(callListenerStatus.getCause()).isSameAs(failure);
+    verify(stream).cancel(same(callListenerStatus));
+  }
+
+  @Test
+  public void exceptionInOnHeadersTakesPrecedenceOverServer() {
+    DelayedExecutor executor = new DelayedExecutor();
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        executor,
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+    call.start(callListener, new Metadata());
+    verify(stream).start(listenerArgumentCaptor.capture());
+    final ClientStreamListener streamListener = listenerArgumentCaptor.getValue();
+
+    RuntimeException failure = new RuntimeException("bad");
+    doThrow(failure).when(callListener).onHeaders(any(Metadata.class));
+
+    /*
+     * In unary calls, the server closes the call right after responding, so the onClose call is
+     * queued to run.  When headersRead is called, an exception will occur and attempt to cancel the
+     * stream.  However, since the server closed it "first" the second exception is lost leading to
+     * the call being counted as successful.
+     */
+    streamListener.headersRead(new Metadata());
+    streamListener.closed(Status.OK, new Metadata());
+    executor.release();
+
+    verify(callListener).onClose(statusArgumentCaptor.capture(), Matchers.isA(Metadata.class));
+    Status callListenerStatus = statusArgumentCaptor.getValue();
+    assertThat(callListenerStatus.getCode()).isEqualTo(Status.Code.CANCELLED);
+    assertThat(callListenerStatus.getCause()).isSameAs(failure);
+    verify(stream).cancel(same(callListenerStatus));
+  }
+
+  @Test
+  public void exceptionInOnReadyTakesPrecedenceOverServer() {
+    DelayedExecutor executor = new DelayedExecutor();
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        executor,
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+    call.start(callListener, new Metadata());
+    verify(stream).start(listenerArgumentCaptor.capture());
+    final ClientStreamListener streamListener = listenerArgumentCaptor.getValue();
+
+    RuntimeException failure = new RuntimeException("bad");
+    doThrow(failure).when(callListener).onReady();
+
+    /*
+     * In unary calls, the server closes the call right after responding, so the onClose call is
+     * queued to run.  When onReady is called, an exception will occur and attempt to cancel the
+     * stream.  However, since the server closed it "first" the second exception is lost leading to
+     * the call being counted as successful.
+     */
+    streamListener.onReady();
+    streamListener.closed(Status.OK, new Metadata());
+    executor.release();
+
+    verify(callListener).onClose(statusArgumentCaptor.capture(), Matchers.isA(Metadata.class));
+    Status callListenerStatus = statusArgumentCaptor.getValue();
+    assertThat(callListenerStatus.getCode()).isEqualTo(Status.Code.CANCELLED);
+    assertThat(callListenerStatus.getCause()).isSameAs(failure);
+    verify(stream).cancel(same(callListenerStatus));
+  }
+
+  @Test
+  public void advertisedEncodingsAreSent() {
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */)
+            .setDecompressorRegistry(decompressorRegistry);
+
+    call.start(callListener, new Metadata());
+
+    ArgumentCaptor<Metadata> metadataCaptor = ArgumentCaptor.forClass(Metadata.class);
+    verify(transport).newStream(eq(method), metadataCaptor.capture(), same(baseCallOptions));
+    Metadata actual = metadataCaptor.getValue();
+
+    // there should only be one.
+    Set<String> acceptedEncodings = ImmutableSet.of(
+        new String(actual.get(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY), GrpcUtil.US_ASCII));
+    assertEquals(decompressorRegistry.getAdvertisedMessageEncodings(), acceptedEncodings);
+  }
+
+  @Test
+  public void authorityPropagatedToStream() {
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        baseCallOptions.withAuthority("overridden-authority"),
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */)
+            .setDecompressorRegistry(decompressorRegistry);
+
+    call.start(callListener, new Metadata());
+    verify(stream).setAuthority("overridden-authority");
+  }
+
+  @Test
+  public void callOptionsPropagatedToTransport() {
+    final CallOptions callOptions = baseCallOptions.withAuthority("dummy_value");
+    final ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        callOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */)
+        .setDecompressorRegistry(decompressorRegistry);
+    final Metadata metadata = new Metadata();
+
+    call.start(callListener, metadata);
+
+    verify(transport).newStream(same(method), same(metadata), same(callOptions));
+  }
+
+  @Test
+  public void authorityNotPropagatedToStream() {
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        // Don't provide an authority
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */)
+            .setDecompressorRegistry(decompressorRegistry);
+
+    call.start(callListener, new Metadata());
+    verify(stream, never()).setAuthority(any(String.class));
+  }
+
+  @Test
+  public void prepareHeaders_userAgentIgnored() {
+    Metadata m = new Metadata();
+    m.put(GrpcUtil.USER_AGENT_KEY, "batmobile");
+    ClientCallImpl.prepareHeaders(m, decompressorRegistry, Codec.Identity.NONE, false);
+
+    // User Agent is removed and set by the transport
+    assertThat(m.get(GrpcUtil.USER_AGENT_KEY)).isNotNull();
+  }
+
+  @Test
+  public void prepareHeaders_ignoreIdentityEncoding() {
+    Metadata m = new Metadata();
+    ClientCallImpl.prepareHeaders(m, decompressorRegistry, Codec.Identity.NONE, false);
+
+    assertNull(m.get(GrpcUtil.MESSAGE_ENCODING_KEY));
+  }
+
+  @Test
+  public void prepareHeaders_acceptedMessageEncodingsAdded() {
+    Metadata m = new Metadata();
+    DecompressorRegistry customRegistry = DecompressorRegistry.emptyInstance()
+        .with(new Decompressor() {
+          @Override
+          public String getMessageEncoding() {
+            return "a";
+          }
+
+          @Override
+          public InputStream decompress(InputStream is) throws IOException {
+            return null;
+          }
+        }, true)
+        .with(new Decompressor() {
+          @Override
+          public String getMessageEncoding() {
+            return "b";
+          }
+
+          @Override
+          public InputStream decompress(InputStream is) throws IOException {
+            return null;
+          }
+        }, true)
+        .with(new Decompressor() {
+          @Override
+          public String getMessageEncoding() {
+            return "c";
+          }
+
+          @Override
+          public InputStream decompress(InputStream is) throws IOException {
+            return null;
+          }
+        }, false); // not advertised
+
+    ClientCallImpl.prepareHeaders(m, customRegistry, Codec.Identity.NONE, false);
+
+    Iterable<String> acceptedEncodings = ACCEPT_ENCODING_SPLITTER.split(
+        new String(m.get(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY), GrpcUtil.US_ASCII));
+
+    // Order may be different, since decoder priorities have not yet been implemented.
+    assertEquals(ImmutableSet.of("b", "a"), ImmutableSet.copyOf(acceptedEncodings));
+  }
+
+  @Test
+  public void prepareHeaders_noAcceptedContentEncodingsWithoutFullStreamDecompressionEnabled() {
+    Metadata m = new Metadata();
+    ClientCallImpl.prepareHeaders(m, decompressorRegistry, Codec.Identity.NONE, false);
+
+    assertNull(m.get(GrpcUtil.CONTENT_ACCEPT_ENCODING_KEY));
+  }
+
+  @Test
+  public void prepareHeaders_acceptedMessageAndContentEncodingsAdded() {
+    Metadata m = new Metadata();
+    DecompressorRegistry customRegistry =
+        DecompressorRegistry.emptyInstance()
+            .with(
+                new Decompressor() {
+                  @Override
+                  public String getMessageEncoding() {
+                    return "a";
+                  }
+
+                  @Override
+                  public InputStream decompress(InputStream is) throws IOException {
+                    return null;
+                  }
+                },
+                true)
+            .with(
+                new Decompressor() {
+                  @Override
+                  public String getMessageEncoding() {
+                    return "b";
+                  }
+
+                  @Override
+                  public InputStream decompress(InputStream is) throws IOException {
+                    return null;
+                  }
+                },
+                true)
+            .with(
+                new Decompressor() {
+                  @Override
+                  public String getMessageEncoding() {
+                    return "c";
+                  }
+
+                  @Override
+                  public InputStream decompress(InputStream is) throws IOException {
+                    return null;
+                  }
+                },
+                false); // not advertised
+
+    ClientCallImpl.prepareHeaders(m, customRegistry, Codec.Identity.NONE, true);
+
+    Iterable<String> acceptedMessageEncodings =
+        ACCEPT_ENCODING_SPLITTER.split(
+            new String(m.get(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY), GrpcUtil.US_ASCII));
+    // Order may be different, since decoder priorities have not yet been implemented.
+    assertEquals(ImmutableSet.of("b", "a"), ImmutableSet.copyOf(acceptedMessageEncodings));
+
+    Iterable<String> acceptedContentEncodings =
+        ACCEPT_ENCODING_SPLITTER.split(
+            new String(m.get(GrpcUtil.CONTENT_ACCEPT_ENCODING_KEY), GrpcUtil.US_ASCII));
+    assertEquals(
+        ImmutableSet.of("gzip"), ImmutableSet.copyOf(acceptedContentEncodings));
+  }
+
+  @Test
+  public void prepareHeaders_removeReservedHeaders() {
+    Metadata m = new Metadata();
+    m.put(GrpcUtil.MESSAGE_ENCODING_KEY, "gzip");
+    m.put(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY, "gzip".getBytes(GrpcUtil.US_ASCII));
+    m.put(GrpcUtil.CONTENT_ENCODING_KEY, "gzip");
+    m.put(GrpcUtil.CONTENT_ACCEPT_ENCODING_KEY, "gzip".getBytes(GrpcUtil.US_ASCII));
+
+    ClientCallImpl.prepareHeaders(
+        m, DecompressorRegistry.emptyInstance(), Codec.Identity.NONE, false);
+
+    assertNull(m.get(GrpcUtil.MESSAGE_ENCODING_KEY));
+    assertNull(m.get(GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY));
+    assertNull(m.get(GrpcUtil.CONTENT_ENCODING_KEY));
+    assertNull(m.get(GrpcUtil.CONTENT_ACCEPT_ENCODING_KEY));
+  }
+
+  @Test
+  public void callerContextPropagatedToListener() throws Exception {
+    // Attach the context which is recorded when the call is created
+    final Context.Key<String> testKey = Context.key("testing");
+    Context context = Context.current().withValue(testKey, "testValue");
+    Context previous = context.attach();
+
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        new SerializingExecutor(Executors.newSingleThreadExecutor()),
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */)
+            .setDecompressorRegistry(decompressorRegistry);
+
+    context.detach(previous);
+
+    // Override the value after creating the call, this should not be seen by callbacks
+    context = Context.current().withValue(testKey, "badValue");
+    previous = context.attach();
+
+    final AtomicBoolean onHeadersCalled = new AtomicBoolean();
+    final AtomicBoolean onMessageCalled = new AtomicBoolean();
+    final AtomicBoolean onReadyCalled = new AtomicBoolean();
+    final AtomicBoolean observedIncorrectContext = new AtomicBoolean();
+    final CountDownLatch latch = new CountDownLatch(1);
+
+    call.start(new ClientCall.Listener<Void>() {
+      @Override
+      public void onHeaders(Metadata headers) {
+        onHeadersCalled.set(true);
+        checkContext();
+      }
+
+      @Override
+      public void onMessage(Void message) {
+        onMessageCalled.set(true);
+        checkContext();
+      }
+
+      @Override
+      public void onClose(Status status, Metadata trailers) {
+        checkContext();
+        latch.countDown();
+      }
+
+      @Override
+      public void onReady() {
+        onReadyCalled.set(true);
+        checkContext();
+      }
+
+      private void checkContext() {
+        if (!"testValue".equals(testKey.get())) {
+          observedIncorrectContext.set(true);
+        }
+      }
+    }, new Metadata());
+
+    context.detach(previous);
+
+    verify(stream).start(listenerArgumentCaptor.capture());
+    ClientStreamListener listener = listenerArgumentCaptor.getValue();
+    listener.onReady();
+    listener.headersRead(new Metadata());
+    listener.messagesAvailable(new SingleMessageProducer(new ByteArrayInputStream(new byte[0])));
+    listener.messagesAvailable(new SingleMessageProducer(new ByteArrayInputStream(new byte[0])));
+    listener.closed(Status.OK, new Metadata());
+
+    assertTrue(latch.await(5, TimeUnit.SECONDS));
+
+    assertTrue(onHeadersCalled.get());
+    assertTrue(onMessageCalled.get());
+    assertTrue(onReadyCalled.get());
+    assertFalse(observedIncorrectContext.get());
+  }
+
+  @Test
+  public void contextCancellationCancelsStream() throws Exception {
+    // Attach the context which is recorded when the call is created
+    Context.CancellableContext cancellableContext = Context.current().withCancellation();
+    Context previous = cancellableContext.attach();
+
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        new SerializingExecutor(Executors.newSingleThreadExecutor()),
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */)
+            .setDecompressorRegistry(decompressorRegistry);
+
+    cancellableContext.detach(previous);
+
+    call.start(callListener, new Metadata());
+
+    Throwable t = new Throwable();
+    cancellableContext.cancel(t);
+
+    verify(stream, times(1)).cancel(statusArgumentCaptor.capture());
+    Status streamStatus = statusArgumentCaptor.getValue();
+    assertEquals(Status.Code.CANCELLED, streamStatus.getCode());
+  }
+
+  @Test
+  public void contextAlreadyCancelledNotifiesImmediately() throws Exception {
+    // Attach the context which is recorded when the call is created
+    Context.CancellableContext cancellableContext = Context.current().withCancellation();
+    Throwable cause = new Throwable();
+    cancellableContext.cancel(cause);
+    Context previous = cancellableContext.attach();
+
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        new SerializingExecutor(Executors.newSingleThreadExecutor()),
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */)
+        .setDecompressorRegistry(decompressorRegistry);
+
+    cancellableContext.detach(previous);
+
+    final SettableFuture<Status> statusFuture = SettableFuture.create();
+    call.start(new ClientCall.Listener<Void>() {
+      @Override
+      public void onClose(Status status, Metadata trailers) {
+        statusFuture.set(status);
+      }
+    }, new Metadata());
+
+    // Caller should receive onClose callback.
+    Status status = statusFuture.get(5, TimeUnit.SECONDS);
+    assertEquals(Status.Code.CANCELLED, status.getCode());
+    assertSame(cause, status.getCause());
+
+    // Following operations should be no-op.
+    call.request(1);
+    call.sendMessage(null);
+    call.halfClose();
+
+    // Stream should never be created.
+    verifyZeroInteractions(transport);
+
+    try {
+      call.sendMessage(null);
+      fail("Call has been cancelled");
+    } catch (IllegalStateException ise) {
+      // expected
+    }
+  }
+
+  @Test
+  public void deadlineExceededBeforeCallStarted() {
+    CallOptions callOptions = baseCallOptions.withDeadlineAfter(0, TimeUnit.SECONDS);
+    fakeClock.forwardTime(System.nanoTime(), TimeUnit.NANOSECONDS);
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        new SerializingExecutor(Executors.newSingleThreadExecutor()),
+        callOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */)
+            .setDecompressorRegistry(decompressorRegistry);
+    call.start(callListener, new Metadata());
+    verify(transport, times(0))
+        .newStream(any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+    verify(callListener, timeout(1000)).onClose(statusCaptor.capture(), any(Metadata.class));
+    assertEquals(Status.Code.DEADLINE_EXCEEDED, statusCaptor.getValue().getCode());
+    verifyZeroInteractions(provider);
+  }
+
+  @Test
+  public void contextDeadlineShouldBePropagatedToStream() {
+    Context context = Context.current()
+        .withDeadlineAfter(1000, TimeUnit.MILLISECONDS, deadlineCancellationExecutor);
+    Context origContext = context.attach();
+
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+    call.start(callListener, new Metadata());
+
+    context.detach(origContext);
+
+    ArgumentCaptor<Deadline> deadlineCaptor = ArgumentCaptor.forClass(Deadline.class);
+    verify(stream).setDeadline(deadlineCaptor.capture());
+
+    assertTimeoutBetween(deadlineCaptor.getValue().timeRemaining(TimeUnit.MILLISECONDS), 600, 1000);
+  }
+
+  @Test
+  public void contextDeadlineShouldOverrideLargerCallOptionsDeadline() {
+    Context context = Context.current()
+        .withDeadlineAfter(1000, TimeUnit.MILLISECONDS, deadlineCancellationExecutor);
+    Context origContext = context.attach();
+
+    CallOptions callOpts = baseCallOptions.withDeadlineAfter(2000, TimeUnit.MILLISECONDS);
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        callOpts,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+    call.start(callListener, new Metadata());
+
+    context.detach(origContext);
+
+    ArgumentCaptor<Deadline> deadlineCaptor = ArgumentCaptor.forClass(Deadline.class);
+    verify(stream).setDeadline(deadlineCaptor.capture());
+
+    assertTimeoutBetween(deadlineCaptor.getValue().timeRemaining(TimeUnit.MILLISECONDS), 600, 1000);
+  }
+
+  @Test
+  public void contextDeadlineShouldNotOverrideSmallerCallOptionsDeadline() {
+    Context context = Context.current()
+        .withDeadlineAfter(2000, TimeUnit.MILLISECONDS, deadlineCancellationExecutor);
+    Context origContext = context.attach();
+
+    CallOptions callOpts = baseCallOptions.withDeadlineAfter(1000, TimeUnit.MILLISECONDS);
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        callOpts,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+    call.start(callListener, new Metadata());
+
+    context.detach(origContext);
+
+    ArgumentCaptor<Deadline> deadlineCaptor = ArgumentCaptor.forClass(Deadline.class);
+    verify(stream).setDeadline(deadlineCaptor.capture());
+
+    assertTimeoutBetween(deadlineCaptor.getValue().timeRemaining(TimeUnit.MILLISECONDS), 600, 1000);
+  }
+
+  @Test
+  public void callOptionsDeadlineShouldBePropagatedToStream() {
+    CallOptions callOpts = baseCallOptions.withDeadlineAfter(1000, TimeUnit.MILLISECONDS);
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        callOpts,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+    call.start(callListener, new Metadata());
+
+    ArgumentCaptor<Deadline> deadlineCaptor = ArgumentCaptor.forClass(Deadline.class);
+    verify(stream).setDeadline(deadlineCaptor.capture());
+
+    assertTimeoutBetween(deadlineCaptor.getValue().timeRemaining(TimeUnit.MILLISECONDS), 600, 1000);
+  }
+
+  @Test
+  public void noDeadlineShouldBePropagatedToStream() {
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+    call.start(callListener, new Metadata());
+
+    verify(stream, never()).setDeadline(any(Deadline.class));
+  }
+
+  @Test
+  public void expiredDeadlineCancelsStream_CallOptions() {
+    fakeClock.forwardTime(System.nanoTime(), TimeUnit.NANOSECONDS);
+    // The deadline needs to be a number large enough to get encompass the call to start, otherwise
+    // the scheduled cancellation won't be created, and the call will fail early.
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        baseCallOptions.withDeadline(Deadline.after(1, TimeUnit.SECONDS)),
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+
+    call.start(callListener, new Metadata());
+
+    fakeClock.forwardNanos(TimeUnit.SECONDS.toNanos(1) + 1);
+
+    verify(stream, times(1)).cancel(statusCaptor.capture());
+    assertEquals(Status.Code.DEADLINE_EXCEEDED, statusCaptor.getValue().getCode());
+  }
+
+  @Test
+  public void expiredDeadlineCancelsStream_Context() {
+    fakeClock.forwardTime(System.nanoTime(), TimeUnit.NANOSECONDS);
+
+    Context context = Context.current()
+        .withDeadlineAfter(1, TimeUnit.SECONDS, deadlineCancellationExecutor);
+    Context origContext = context.attach();
+
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+
+    context.detach(origContext);
+
+    call.start(callListener, new Metadata());
+
+    fakeClock.forwardNanos(TimeUnit.SECONDS.toNanos(1) + 1);
+
+    verify(stream, times(1)).cancel(statusCaptor.capture());
+    assertEquals(Status.Code.DEADLINE_EXCEEDED, statusCaptor.getValue().getCode());
+  }
+
+  @Test
+  public void streamCancelAbortsDeadlineTimer() {
+    fakeClock.forwardTime(System.nanoTime(), TimeUnit.NANOSECONDS);
+
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        baseCallOptions.withDeadline(Deadline.after(1, TimeUnit.SECONDS)),
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+    call.start(callListener, new Metadata());
+    call.cancel("canceled", null);
+
+    // Run the deadline timer, which should have been cancelled by the previous call to cancel()
+    fakeClock.forwardNanos(TimeUnit.SECONDS.toNanos(1) + 1);
+
+    verify(stream, times(1)).cancel(statusCaptor.capture());
+
+    assertEquals(Status.CANCELLED.getCode(), statusCaptor.getValue().getCode());
+  }
+
+  /**
+   * Without a context or call options deadline,
+   * a timeout should not be set in metadata.
+   */
+  @Test
+  public void timeoutShouldNotBeSet() {
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+
+    Metadata headers = new Metadata();
+
+    call.start(callListener, headers);
+
+    assertFalse(headers.containsKey(GrpcUtil.TIMEOUT_KEY));
+  }
+
+  @Test
+  public void cancelInOnMessageShouldInvokeStreamCancel() throws Exception {
+    final ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        MoreExecutors.directExecutor(),
+        baseCallOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */);
+    final Exception cause = new Exception();
+    ClientCall.Listener<Void> callListener =
+        new ClientCall.Listener<Void>() {
+          @Override
+          public void onMessage(Void message) {
+            call.cancel("foo", cause);
+          }
+        };
+
+    call.start(callListener, new Metadata());
+    call.halfClose();
+    call.request(1);
+
+    verify(stream).start(listenerArgumentCaptor.capture());
+    ClientStreamListener streamListener = listenerArgumentCaptor.getValue();
+    streamListener.onReady();
+    streamListener.headersRead(new Metadata());
+    streamListener
+        .messagesAvailable(new SingleMessageProducer(new ByteArrayInputStream(new byte[0])));
+    verify(stream).cancel(statusCaptor.capture());
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.CANCELLED.getCode(), status.getCode());
+    assertEquals("foo", status.getDescription());
+    assertSame(cause, status.getCause());
+  }
+
+  @Test
+  public void startAddsMaxSize() {
+    CallOptions callOptions =
+        baseCallOptions.withMaxInboundMessageSize(1).withMaxOutboundMessageSize(2);
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method,
+        new SerializingExecutor(Executors.newSingleThreadExecutor()),
+        callOptions,
+        provider,
+        deadlineCancellationExecutor,
+        channelCallTracer,
+        false /* retryEnabled */)
+            .setDecompressorRegistry(decompressorRegistry);
+
+    call.start(callListener, new Metadata());
+
+    verify(stream).setMaxInboundMessageSize(1);
+    verify(stream).setMaxOutboundMessageSize(2);
+  }
+
+  @Test
+  public void getAttributes() {
+    ClientCallImpl<Void, Void> call = new ClientCallImpl<Void, Void>(
+        method, MoreExecutors.directExecutor(), baseCallOptions, provider,
+        deadlineCancellationExecutor, channelCallTracer, false /* retryEnabled */);
+    Attributes attrs =
+        Attributes.newBuilder().set(Key.<String>create("fake key"), "fake value").build();
+    when(stream.getAttributes()).thenReturn(attrs);
+
+    assertNotEquals(attrs, call.getAttributes());
+
+    call.start(callListener, new Metadata());
+
+    assertEquals(attrs, call.getAttributes());
+  }
+
+  private static void assertTimeoutBetween(long timeout, long from, long to) {
+    assertTrue("timeout: " + timeout + " ns", timeout <= to);
+    assertTrue("timeout: " + timeout + " ns", timeout >= from);
+  }
+
+  private static final class DelayedExecutor implements Executor {
+    private final BlockingQueue<Runnable> commands = new LinkedBlockingQueue<Runnable>();
+
+    @Override
+    public void execute(Runnable command) {
+      commands.add(command);
+    }
+
+    void release() {
+      while (!commands.isEmpty()) {
+        commands.poll().run();
+      }
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ClientTransportFactoryTest.java b/core/src/test/java/io/grpc/internal/ClientTransportFactoryTest.java
new file mode 100644
index 0000000..279d544
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ClientTransportFactoryTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.testing.EqualsTester;
+import io.grpc.Attributes;
+import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
+import java.net.InetSocketAddress;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ClientTransportFactoryTest {
+  private String authority = "testing123";
+  private Attributes eagAttributes =
+      Attributes.newBuilder().set(Attributes.Key.create("fake key"), "fake value").build();
+  private String userAgent = "best-ua/3.14";
+  private ProxyParameters proxyParameters =
+      new ProxyParameters(new InetSocketAddress(0), null, null);
+
+  @Test
+  public void clientTransportOptions_init_checkNotNulls() {
+    ClientTransportOptions cto = new ClientTransportOptions();
+    assertThat(cto.getAuthority()).isNotNull();
+    assertThat(cto.getEagAttributes()).isEqualTo(Attributes.EMPTY);
+  }
+
+  @Test
+  public void clientTransportOptions_getsMatchSets() {
+    ClientTransportOptions cto = new ClientTransportOptions()
+        .setAuthority(authority)
+        .setEagAttributes(eagAttributes)
+        .setUserAgent(userAgent)
+        .setProxyParameters(proxyParameters);
+    assertThat(cto.getAuthority()).isEqualTo(authority);
+    assertThat(cto.getEagAttributes()).isEqualTo(eagAttributes);
+    assertThat(cto.getUserAgent()).isEqualTo(userAgent);
+    assertThat(cto.getProxyParameters()).isSameAs(proxyParameters);
+  }
+
+  @Test
+  public void clientTransportOptions_equals() {
+    new EqualsTester()
+        .addEqualityGroup(new ClientTransportOptions())
+        .addEqualityGroup(
+            new ClientTransportOptions()
+              .setAuthority(authority),
+            new ClientTransportOptions()
+              .setAuthority(authority)
+              .setEagAttributes(Attributes.EMPTY))
+        .addEqualityGroup(
+            new ClientTransportOptions()
+              .setAuthority(authority)
+              .setEagAttributes(eagAttributes))
+        .addEqualityGroup(
+            new ClientTransportOptions()
+              .setAuthority(authority)
+              .setEagAttributes(eagAttributes)
+              .setUserAgent(userAgent))
+        .addEqualityGroup(
+            new ClientTransportOptions()
+              .setAuthority(authority)
+              .setEagAttributes(eagAttributes)
+              .setUserAgent(userAgent)
+              .setProxyParameters(proxyParameters),
+            new ClientTransportOptions()
+              .setAuthority(authority)
+              .setEagAttributes(eagAttributes)
+              .setUserAgent(userAgent)
+              .setProxyParameters(proxyParameters))
+        .testEquals();
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/CompositeReadableBufferTest.java b/core/src/test/java/io/grpc/internal/CompositeReadableBufferTest.java
new file mode 100644
index 0000000..a73df72
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/CompositeReadableBufferTest.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link CompositeReadableBuffer}.
+ */
+@RunWith(JUnit4.class)
+public class CompositeReadableBufferTest {
+  private static final String EXPECTED_VALUE = "hello world";
+
+  private CompositeReadableBuffer composite;
+
+  @Before
+  public void setup() {
+    composite = new CompositeReadableBuffer();
+    splitAndAdd(EXPECTED_VALUE);
+  }
+
+  @After
+  public void teardown() {
+    composite.close();
+  }
+
+  @Test
+  public void singleBufferShouldSucceed() {
+    composite = new CompositeReadableBuffer();
+    composite.addBuffer(ReadableBuffers.wrap(EXPECTED_VALUE.getBytes(UTF_8)));
+    assertEquals(EXPECTED_VALUE.length(), composite.readableBytes());
+    assertEquals(EXPECTED_VALUE, ReadableBuffers.readAsStringUtf8(composite));
+    assertEquals(0, composite.readableBytes());
+  }
+
+  @Test
+  public void readUnsignedByteShouldSucceed() {
+    for (int ix = 0; ix < EXPECTED_VALUE.length(); ++ix) {
+      int c = composite.readUnsignedByte();
+      assertEquals(EXPECTED_VALUE.charAt(ix), (char) c);
+    }
+    assertEquals(0, composite.readableBytes());
+  }
+
+  @Test
+  public void readUnsignedByteShouldSkipZeroLengthBuffer() {
+    composite = new CompositeReadableBuffer();
+    composite.addBuffer(ReadableBuffers.wrap(new byte[0]));
+    byte[] in = {1};
+    composite.addBuffer(ReadableBuffers.wrap(in));
+    assertEquals(1, composite.readUnsignedByte());
+    assertEquals(0, composite.readableBytes());
+  }
+
+  @Test
+  public void skipBytesShouldSucceed() {
+    int remaining = EXPECTED_VALUE.length();
+    composite.skipBytes(1);
+    remaining--;
+    assertEquals(remaining, composite.readableBytes());
+
+    composite.skipBytes(5);
+    remaining -= 5;
+    assertEquals(remaining, composite.readableBytes());
+
+    composite.skipBytes(remaining);
+    assertEquals(0, composite.readableBytes());
+  }
+
+  @Test
+  public void readByteArrayShouldSucceed() {
+    byte[] bytes = new byte[composite.readableBytes()];
+    int writeIndex = 0;
+
+    composite.readBytes(bytes, writeIndex, 1);
+    writeIndex++;
+    assertEquals(EXPECTED_VALUE.length() - writeIndex, composite.readableBytes());
+
+    composite.readBytes(bytes, writeIndex, 5);
+    writeIndex += 5;
+    assertEquals(EXPECTED_VALUE.length() - writeIndex, composite.readableBytes());
+
+    int remaining = composite.readableBytes();
+    composite.readBytes(bytes, writeIndex, remaining);
+    writeIndex += remaining;
+    assertEquals(0, composite.readableBytes());
+    assertEquals(bytes.length, writeIndex);
+    assertEquals(EXPECTED_VALUE, new String(bytes, UTF_8));
+  }
+
+  @Test
+  public void readByteBufferShouldSucceed() {
+    ByteBuffer byteBuffer = ByteBuffer.allocate(EXPECTED_VALUE.length());
+    int remaining = EXPECTED_VALUE.length();
+
+    byteBuffer.limit(1);
+    composite.readBytes(byteBuffer);
+    remaining--;
+    assertEquals(remaining, composite.readableBytes());
+
+    byteBuffer.limit(byteBuffer.limit() + 5);
+    composite.readBytes(byteBuffer);
+    remaining -= 5;
+    assertEquals(remaining, composite.readableBytes());
+
+    byteBuffer.limit(byteBuffer.limit() + remaining);
+    composite.readBytes(byteBuffer);
+    assertEquals(0, composite.readableBytes());
+    assertEquals(EXPECTED_VALUE, new String(byteBuffer.array(), UTF_8));
+  }
+
+  @Test
+  public void readStreamShouldSucceed() throws IOException {
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    int remaining = EXPECTED_VALUE.length();
+
+    composite.readBytes(bos, 1);
+    remaining--;
+    assertEquals(remaining, composite.readableBytes());
+
+    composite.readBytes(bos, 5);
+    remaining -= 5;
+    assertEquals(remaining, composite.readableBytes());
+
+    composite.readBytes(bos, remaining);
+    assertEquals(0, composite.readableBytes());
+    assertEquals(EXPECTED_VALUE, new String(bos.toByteArray(), UTF_8));
+  }
+
+  @Test
+  public void closeShouldCloseBuffers() {
+    composite = new CompositeReadableBuffer();
+    ReadableBuffer mock1 = mock(ReadableBuffer.class);
+    ReadableBuffer mock2 = mock(ReadableBuffer.class);
+    composite.addBuffer(mock1);
+    composite.addBuffer(mock2);
+
+    composite.close();
+    verify(mock1).close();
+    verify(mock2).close();
+  }
+
+  private void splitAndAdd(String value) {
+    int partLength = Math.max(1, value.length() / 4);
+    for (int startIndex = 0, endIndex = 0; startIndex < value.length(); startIndex = endIndex) {
+      endIndex = Math.min(value.length(), startIndex + partLength);
+      String part = value.substring(startIndex, endIndex);
+      composite.addBuffer(ReadableBuffers.wrap(part.getBytes(UTF_8)));
+    }
+
+    assertEquals(value.length(), composite.readableBytes());
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ConnectivityStateManagerTest.java b/core/src/test/java/io/grpc/internal/ConnectivityStateManagerTest.java
new file mode 100644
index 0000000..1f88a31
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ConnectivityStateManagerTest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static io.grpc.ConnectivityState.CONNECTING;
+import static io.grpc.ConnectivityState.IDLE;
+import static io.grpc.ConnectivityState.READY;
+import static io.grpc.ConnectivityState.SHUTDOWN;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.ConnectivityState;
+import java.util.LinkedList;
+import java.util.concurrent.Executor;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link ConnectivityStateManager}.
+ */
+@RunWith(JUnit4.class)
+public class ConnectivityStateManagerTest {
+  @Rule
+  public final ExpectedException thrown = ExpectedException.none();
+
+  private final FakeClock executor = new FakeClock();
+  private final ConnectivityStateManager state = new ConnectivityStateManager();
+  private final LinkedList<ConnectivityState> sink = new LinkedList<ConnectivityState>();
+
+  @Test
+  public void noCallback() {
+    state.gotoState(CONNECTING);
+    assertEquals(CONNECTING, state.getState());
+    state.gotoState(TRANSIENT_FAILURE);
+    assertEquals(TRANSIENT_FAILURE, state.getState());
+  }
+
+  @Test
+  public void registerCallbackBeforeStateChanged() {
+    state.gotoState(CONNECTING);
+    assertEquals(CONNECTING, state.getState());
+
+    state.notifyWhenStateChanged(new Runnable() {
+        @Override
+        public void run() {
+          sink.add(state.getState());
+        }
+      }, executor.getScheduledExecutorService(), CONNECTING);
+
+    assertEquals(0, executor.numPendingTasks());
+    state.gotoState(TRANSIENT_FAILURE);
+    // Make sure the callback is run in the executor
+    assertEquals(0, sink.size());
+    assertEquals(1, executor.runDueTasks());
+    assertEquals(0, executor.numPendingTasks());
+    assertEquals(1, sink.size());
+    assertEquals(TRANSIENT_FAILURE, sink.poll());
+  }
+  
+  @Test
+  public void registerCallbackAfterStateChanged() {
+    state.gotoState(CONNECTING);
+    assertEquals(CONNECTING, state.getState());
+
+    state.notifyWhenStateChanged(new Runnable() {
+        @Override
+        public void run() {
+          sink.add(state.getState());
+        }
+      }, executor.getScheduledExecutorService(), IDLE);
+
+    // Make sure the callback is run in the executor
+    assertEquals(0, sink.size());
+    assertEquals(1, executor.runDueTasks());
+    assertEquals(0, executor.numPendingTasks());
+    assertEquals(1, sink.size());
+    assertEquals(CONNECTING, sink.poll());
+  }
+
+  @Test
+  public void callbackOnlyCalledOnTransition() {
+    state.notifyWhenStateChanged(new Runnable() {
+        @Override
+        public void run() {
+          sink.add(state.getState());
+        }
+      }, executor.getScheduledExecutorService(), IDLE);
+
+    state.gotoState(IDLE);
+    assertEquals(0, executor.numPendingTasks());
+    assertEquals(0, sink.size());
+  }
+
+  @Test
+  public void callbacksAreOneShot() {
+    Runnable callback = new Runnable() {
+        @Override
+        public void run() {
+          sink.add(state.getState());
+        }
+      };
+
+    state.notifyWhenStateChanged(callback, executor.getScheduledExecutorService(), IDLE);
+    // First transition triggers the callback
+    state.gotoState(CONNECTING);
+    assertEquals(1, executor.runDueTasks());
+    assertEquals(1, sink.size());
+    assertEquals(CONNECTING, sink.poll());
+    assertEquals(0, executor.numPendingTasks());
+
+    // Second transition doesn't trigger the callback
+    state.gotoState(TRANSIENT_FAILURE);
+    assertEquals(0, sink.size());
+    assertEquals(0, executor.numPendingTasks());
+
+    // Register another callback
+    state.notifyWhenStateChanged(
+        callback, executor.getScheduledExecutorService(), TRANSIENT_FAILURE);
+
+    state.gotoState(READY);
+    state.gotoState(IDLE);
+    // Callback loses the race with the second stage change
+    assertEquals(1, executor.runDueTasks());
+    assertEquals(1, sink.size());
+    // It will see the second state
+    assertEquals(IDLE, sink.poll());
+    assertEquals(0, executor.numPendingTasks());
+  }
+
+  @Test
+  public void multipleCallbacks() {
+    final LinkedList<String> callbackRuns = new LinkedList<String>();
+    state.notifyWhenStateChanged(new Runnable() {
+        @Override
+        public void run() {
+          sink.add(state.getState());
+          callbackRuns.add("callback1");
+        }
+      }, executor.getScheduledExecutorService(), IDLE);
+    state.notifyWhenStateChanged(new Runnable() {
+        @Override
+        public void run() {
+          sink.add(state.getState());
+          callbackRuns.add("callback2");
+        }
+      }, executor.getScheduledExecutorService(), IDLE);
+    state.notifyWhenStateChanged(new Runnable() {
+        @Override
+        public void run() {
+          sink.add(state.getState());
+          callbackRuns.add("callback3");
+        }
+      }, executor.getScheduledExecutorService(), READY);
+
+    // callback3 is run immediately because the source state is already different from the current
+    // state.
+    assertEquals(1, executor.runDueTasks());
+    assertEquals(1, callbackRuns.size());
+    assertEquals("callback3", callbackRuns.poll());
+    assertEquals(1, sink.size());
+    assertEquals(IDLE, sink.poll());
+
+    // Now change the state.
+    state.gotoState(CONNECTING);
+    assertEquals(2, executor.runDueTasks());
+    assertEquals(2, callbackRuns.size());
+    assertEquals("callback1", callbackRuns.poll());
+    assertEquals("callback2", callbackRuns.poll());
+    assertEquals(2, sink.size());
+    assertEquals(CONNECTING, sink.poll());
+    assertEquals(CONNECTING, sink.poll());
+
+    assertEquals(0, executor.numPendingTasks());
+  }
+
+  private Runnable newRecursiveCallback(final Executor executor) {
+    return new Runnable() {
+      @Override
+      public void run() {
+        sink.add(state.getState());
+        state.notifyWhenStateChanged(newRecursiveCallback(executor), executor, state.getState());
+      }
+    };
+  }
+
+  @Test
+  public void registerCallbackFromCallback() {
+    state.notifyWhenStateChanged(newRecursiveCallback(executor.getScheduledExecutorService()),
+        executor.getScheduledExecutorService(), state.getState());
+
+    state.gotoState(CONNECTING);
+    assertEquals(1, executor.runDueTasks());
+    assertEquals(0, executor.numPendingTasks());
+    assertEquals(1, sink.size());
+    assertEquals(CONNECTING, sink.poll());
+
+    state.gotoState(READY);
+    assertEquals(1, executor.runDueTasks());
+    assertEquals(0, executor.numPendingTasks());
+    assertEquals(1, sink.size());
+    assertEquals(READY, sink.poll());
+  }
+
+  @Test
+  public void registerCallbackFromCallbackDirectExecutor() {
+    state.notifyWhenStateChanged(newRecursiveCallback(MoreExecutors.directExecutor()),
+        MoreExecutors.directExecutor(), state.getState());
+
+    state.gotoState(CONNECTING);
+    assertEquals(1, sink.size());
+    assertEquals(CONNECTING, sink.poll());
+
+    state.gotoState(READY);
+    assertEquals(1, sink.size());
+    assertEquals(READY, sink.poll());
+  }
+
+  @Test
+  public void shutdownThenReady() {
+    state.gotoState(SHUTDOWN);
+    assertEquals(SHUTDOWN, state.getState());
+
+    state.gotoState(READY);
+    assertEquals(SHUTDOWN, state.getState());
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java b/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java
new file mode 100644
index 0000000..fe2e144
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/DelayedClientTransportTest.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import io.grpc.CallOptions;
+import io.grpc.IntegerMarshaller;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.Status;
+import io.grpc.StringMarshaller;
+import io.grpc.internal.ClientStreamListener.RpcProgress;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Unit tests for {@link DelayedClientTransport}.
+ */
+@RunWith(JUnit4.class)
+public class DelayedClientTransportTest {
+  @Mock private ManagedClientTransport.Listener transportListener;
+  @Mock private SubchannelPicker mockPicker;
+  @Mock private AbstractSubchannel mockSubchannel;
+  @Mock private ClientTransport mockRealTransport;
+  @Mock private ClientTransport mockRealTransport2;
+  @Mock private ClientStream mockRealStream;
+  @Mock private ClientStream mockRealStream2;
+  @Mock private ClientStreamListener streamListener;
+  @Mock private Executor mockExecutor;
+  @Captor private ArgumentCaptor<Status> statusCaptor;
+  @Captor private ArgumentCaptor<ClientStreamListener> listenerCaptor;
+
+  private static final CallOptions.Key<Integer> SHARD_ID
+      = CallOptions.Key.createWithDefault("shard-id", -1);
+  private static final Status SHUTDOWN_STATUS =
+      Status.UNAVAILABLE.withDescription("shutdown called");
+
+  private final MethodDescriptor<String, Integer> method =
+      MethodDescriptor.<String, Integer>newBuilder()
+          .setType(MethodType.UNKNOWN)
+          .setFullMethodName("service/method")
+          .setRequestMarshaller(new StringMarshaller())
+          .setResponseMarshaller(new IntegerMarshaller())
+          .build();
+  private final MethodDescriptor<String, Integer> method2 =
+      method.toBuilder().setFullMethodName("service/method").build();
+  private final Metadata headers = new Metadata();
+  private final Metadata headers2 = new Metadata();
+
+  private final CallOptions callOptions = CallOptions.DEFAULT.withAuthority("dummy_value");
+  private final CallOptions callOptions2 = CallOptions.DEFAULT.withAuthority("dummy_value2");
+
+  private final FakeClock fakeExecutor = new FakeClock();
+
+  private final DelayedClientTransport delayedTransport = new DelayedClientTransport(
+      fakeExecutor.getScheduledExecutorService(), new ChannelExecutor());
+
+  @Before public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withSubchannel(mockSubchannel));
+    when(mockSubchannel.obtainActiveTransport()).thenReturn(mockRealTransport);
+    when(mockRealTransport.newStream(same(method), same(headers), same(callOptions)))
+        .thenReturn(mockRealStream);
+    when(mockRealTransport2.newStream(same(method2), same(headers2), same(callOptions2)))
+        .thenReturn(mockRealStream2);
+    delayedTransport.start(transportListener);
+  }
+
+  @After public void noMorePendingTasks() {
+    assertEquals(0, fakeExecutor.numPendingTasks());
+  }
+
+  @Test public void streamStartThenAssignTransport() {
+    assertFalse(delayedTransport.hasPendingStreams());
+    ClientStream stream = delayedTransport.newStream(method, headers, callOptions);
+    stream.start(streamListener);
+    assertEquals(1, delayedTransport.getPendingStreamsCount());
+    assertTrue(delayedTransport.hasPendingStreams());
+    assertTrue(stream instanceof DelayedStream);
+    assertEquals(0, fakeExecutor.numPendingTasks());
+    delayedTransport.reprocess(mockPicker);
+    assertEquals(0, delayedTransport.getPendingStreamsCount());
+    assertFalse(delayedTransport.hasPendingStreams());
+    assertEquals(1, fakeExecutor.runDueTasks());
+    verify(mockRealTransport).newStream(same(method), same(headers), same(callOptions));
+    verify(mockRealStream).start(listenerCaptor.capture());
+    verifyNoMoreInteractions(streamListener);
+    listenerCaptor.getValue().onReady();
+    verify(streamListener).onReady();
+    verifyNoMoreInteractions(streamListener);
+  }
+
+  @Test public void newStreamThenAssignTransportThenShutdown() {
+    ClientStream stream = delayedTransport.newStream(method, headers, callOptions);
+    assertEquals(1, delayedTransport.getPendingStreamsCount());
+    assertTrue(stream instanceof DelayedStream);
+    delayedTransport.reprocess(mockPicker);
+    assertEquals(0, delayedTransport.getPendingStreamsCount());
+    delayedTransport.shutdown(SHUTDOWN_STATUS);
+    verify(transportListener).transportShutdown(same(SHUTDOWN_STATUS));
+    verify(transportListener).transportTerminated();
+    assertEquals(1, fakeExecutor.runDueTasks());
+    verify(mockRealTransport).newStream(same(method), same(headers), same(callOptions));
+    stream.start(streamListener);
+    verify(mockRealStream).start(same(streamListener));
+  }
+
+  @Test public void transportTerminatedThenAssignTransport() {
+    delayedTransport.shutdown(SHUTDOWN_STATUS);
+    verify(transportListener).transportShutdown(same(SHUTDOWN_STATUS));
+    verify(transportListener).transportTerminated();
+    delayedTransport.reprocess(mockPicker);
+    verifyNoMoreInteractions(transportListener);
+  }
+
+  @Test public void assignTransportThenShutdownThenNewStream() {
+    delayedTransport.reprocess(mockPicker);
+    delayedTransport.shutdown(SHUTDOWN_STATUS);
+    verify(transportListener).transportShutdown(same(SHUTDOWN_STATUS));
+    verify(transportListener).transportTerminated();
+    ClientStream stream = delayedTransport.newStream(method, headers, callOptions);
+    assertEquals(0, delayedTransport.getPendingStreamsCount());
+    assertTrue(stream instanceof FailingClientStream);
+    verify(mockRealTransport, never()).newStream(
+        any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+  }
+
+  @Test public void assignTransportThenShutdownNowThenNewStream() {
+    delayedTransport.reprocess(mockPicker);
+    delayedTransport.shutdownNow(Status.UNAVAILABLE);
+    verify(transportListener).transportShutdown(any(Status.class));
+    verify(transportListener).transportTerminated();
+    ClientStream stream = delayedTransport.newStream(method, headers, callOptions);
+    assertEquals(0, delayedTransport.getPendingStreamsCount());
+    assertTrue(stream instanceof FailingClientStream);
+    verify(mockRealTransport, never()).newStream(
+        any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+  }
+
+  @Test public void cancelStreamWithoutSetTransport() {
+    ClientStream stream = delayedTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    assertEquals(1, delayedTransport.getPendingStreamsCount());
+    stream.cancel(Status.CANCELLED);
+    assertEquals(0, delayedTransport.getPendingStreamsCount());
+    verifyNoMoreInteractions(mockRealTransport);
+    verifyNoMoreInteractions(mockRealStream);
+  }
+
+  @Test public void startThenCancelStreamWithoutSetTransport() {
+    ClientStream stream = delayedTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(streamListener);
+    assertEquals(1, delayedTransport.getPendingStreamsCount());
+    stream.cancel(Status.CANCELLED);
+    assertEquals(0, delayedTransport.getPendingStreamsCount());
+    verify(streamListener).closed(same(Status.CANCELLED), any(Metadata.class));
+    verifyNoMoreInteractions(mockRealTransport);
+    verifyNoMoreInteractions(mockRealStream);
+  }
+
+  @Test public void newStreamThenShutdownTransportThenAssignTransport() {
+    ClientStream stream = delayedTransport.newStream(method, headers, callOptions);
+    stream.start(streamListener);
+    delayedTransport.shutdown(SHUTDOWN_STATUS);
+
+    // Stream is still buffered
+    verify(transportListener).transportShutdown(same(SHUTDOWN_STATUS));
+    verify(transportListener, times(0)).transportTerminated();
+    assertEquals(1, delayedTransport.getPendingStreamsCount());
+
+    // ... and will proceed if a real transport is available
+    delayedTransport.reprocess(mockPicker);
+    fakeExecutor.runDueTasks();
+    verify(mockRealTransport).newStream(method, headers, callOptions);
+    verify(mockRealStream).start(any(ClientStreamListener.class));
+
+    // Since no more streams are pending, delayed transport is now terminated
+    assertEquals(0, delayedTransport.getPendingStreamsCount());
+    verify(transportListener).transportTerminated();
+
+    // Further newStream() will return a failing stream
+    stream = delayedTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    verify(streamListener, never()).closed(any(Status.class), any(Metadata.class));
+    stream.start(streamListener);
+    verify(streamListener).closed(
+        statusCaptor.capture(), any(RpcProgress.class), any(Metadata.class));
+    assertEquals(Status.Code.UNAVAILABLE, statusCaptor.getValue().getCode());
+
+    assertEquals(0, delayedTransport.getPendingStreamsCount());
+    verifyNoMoreInteractions(mockRealTransport);
+    verifyNoMoreInteractions(mockRealStream);
+  }
+
+  @Test public void newStreamThenShutdownTransportThenCancelStream() {
+    ClientStream stream = delayedTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    delayedTransport.shutdown(SHUTDOWN_STATUS);
+    verify(transportListener).transportShutdown(same(SHUTDOWN_STATUS));
+    verify(transportListener, times(0)).transportTerminated();
+    assertEquals(1, delayedTransport.getPendingStreamsCount());
+    stream.cancel(Status.CANCELLED);
+    verify(transportListener).transportTerminated();
+    assertEquals(0, delayedTransport.getPendingStreamsCount());
+    verifyNoMoreInteractions(mockRealTransport);
+    verifyNoMoreInteractions(mockRealStream);
+  }
+
+  @Test public void shutdownThenNewStream() {
+    delayedTransport.shutdown(SHUTDOWN_STATUS);
+    verify(transportListener).transportShutdown(same(SHUTDOWN_STATUS));
+    verify(transportListener).transportTerminated();
+    ClientStream stream = delayedTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(streamListener);
+    verify(streamListener).closed(
+        statusCaptor.capture(), any(RpcProgress.class), any(Metadata.class));
+    assertEquals(Status.Code.UNAVAILABLE, statusCaptor.getValue().getCode());
+  }
+
+  @Test public void startStreamThenShutdownNow() {
+    ClientStream stream = delayedTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(streamListener);
+    delayedTransport.shutdownNow(Status.UNAVAILABLE);
+    verify(transportListener).transportShutdown(any(Status.class));
+    verify(transportListener).transportTerminated();
+    verify(streamListener).closed(statusCaptor.capture(), any(Metadata.class));
+    assertEquals(Status.Code.UNAVAILABLE, statusCaptor.getValue().getCode());
+  }
+
+  @Test public void shutdownNowThenNewStream() {
+    delayedTransport.shutdownNow(Status.UNAVAILABLE);
+    verify(transportListener).transportShutdown(any(Status.class));
+    verify(transportListener).transportTerminated();
+    ClientStream stream = delayedTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(streamListener);
+    verify(streamListener).closed(
+        statusCaptor.capture(), any(RpcProgress.class), any(Metadata.class));
+    assertEquals(Status.Code.UNAVAILABLE, statusCaptor.getValue().getCode());
+  }
+
+  @Test public void reprocessSemantics() {
+    CallOptions failFastCallOptions = CallOptions.DEFAULT.withOption(SHARD_ID, 1);
+    CallOptions waitForReadyCallOptions = CallOptions.DEFAULT.withOption(SHARD_ID, 2)
+        .withWaitForReady();
+
+    AbstractSubchannel subchannel1 = mock(AbstractSubchannel.class);
+    AbstractSubchannel subchannel2 = mock(AbstractSubchannel.class);
+    AbstractSubchannel subchannel3 = mock(AbstractSubchannel.class);
+    when(mockRealTransport.newStream(any(MethodDescriptor.class), any(Metadata.class),
+        any(CallOptions.class))).thenReturn(mockRealStream);
+    when(mockRealTransport2.newStream(any(MethodDescriptor.class), any(Metadata.class),
+        any(CallOptions.class))).thenReturn(mockRealStream2);
+    when(subchannel1.obtainActiveTransport()).thenReturn(mockRealTransport);
+    when(subchannel2.obtainActiveTransport()).thenReturn(mockRealTransport2);
+    when(subchannel3.obtainActiveTransport()).thenReturn(null);
+
+    // Fail-fast streams
+    DelayedStream ff1 = (DelayedStream) delayedTransport.newStream(
+        method, headers, failFastCallOptions);
+    PickSubchannelArgsImpl ff1args = new PickSubchannelArgsImpl(method, headers,
+        failFastCallOptions);
+    verify(transportListener).transportInUse(true);
+    DelayedStream ff2 = (DelayedStream) delayedTransport.newStream(
+        method2, headers2, failFastCallOptions);
+    PickSubchannelArgsImpl ff2args = new PickSubchannelArgsImpl(method2, headers2,
+        failFastCallOptions);
+    DelayedStream ff3 = (DelayedStream) delayedTransport.newStream(
+        method, headers, failFastCallOptions);
+    PickSubchannelArgsImpl ff3args = new PickSubchannelArgsImpl(method, headers,
+        failFastCallOptions);
+    DelayedStream ff4 = (DelayedStream) delayedTransport.newStream(
+        method2, headers2, failFastCallOptions);
+    PickSubchannelArgsImpl ff4args = new PickSubchannelArgsImpl(method2, headers2,
+        failFastCallOptions);
+
+    // Wait-for-ready streams
+    FakeClock wfr3Executor = new FakeClock();
+    DelayedStream wfr1 = (DelayedStream) delayedTransport.newStream(
+        method, headers, waitForReadyCallOptions);
+    PickSubchannelArgsImpl wfr1args = new PickSubchannelArgsImpl(method, headers,
+        waitForReadyCallOptions);
+    DelayedStream wfr2 = (DelayedStream) delayedTransport.newStream(
+        method2, headers2, waitForReadyCallOptions);
+    PickSubchannelArgsImpl wfr2args = new PickSubchannelArgsImpl(method2, headers2,
+        waitForReadyCallOptions);
+    CallOptions wfr3callOptions = waitForReadyCallOptions.withExecutor(
+        wfr3Executor.getScheduledExecutorService());
+    DelayedStream wfr3 = (DelayedStream) delayedTransport.newStream(
+        method, headers, wfr3callOptions);
+    PickSubchannelArgsImpl wfr3args = new PickSubchannelArgsImpl(method, headers,
+        wfr3callOptions);
+    DelayedStream wfr4 = (DelayedStream) delayedTransport.newStream(
+        method2, headers2, waitForReadyCallOptions);
+    PickSubchannelArgsImpl wfr4args = new PickSubchannelArgsImpl(method2, headers2,
+        waitForReadyCallOptions);
+
+    assertEquals(8, delayedTransport.getPendingStreamsCount());
+
+    // First reprocess(). Some will proceed, some will fail and the rest will stay buffered.
+    SubchannelPicker picker = mock(SubchannelPicker.class);
+    when(picker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(
+        // For the fail-fast streams
+        PickResult.withSubchannel(subchannel1),    // ff1: proceed
+        PickResult.withError(Status.UNAVAILABLE),  // ff2: fail
+        PickResult.withSubchannel(subchannel3),    // ff3: stay
+        PickResult.withNoResult(),                 // ff4: stay
+        // For the wait-for-ready streams
+        PickResult.withSubchannel(subchannel2),           // wfr1: proceed
+        PickResult.withError(Status.RESOURCE_EXHAUSTED),  // wfr2: stay
+        PickResult.withSubchannel(subchannel3));          // wfr3: stay
+    InOrder inOrder = inOrder(picker);
+    delayedTransport.reprocess(picker);
+
+    assertEquals(5, delayedTransport.getPendingStreamsCount());
+    inOrder.verify(picker).pickSubchannel(ff1args);
+    inOrder.verify(picker).pickSubchannel(ff2args);
+    inOrder.verify(picker).pickSubchannel(ff3args);
+    inOrder.verify(picker).pickSubchannel(ff4args);
+    inOrder.verify(picker).pickSubchannel(wfr1args);
+    inOrder.verify(picker).pickSubchannel(wfr2args);
+    inOrder.verify(picker).pickSubchannel(wfr3args);
+    inOrder.verify(picker).pickSubchannel(wfr4args);
+
+    inOrder.verifyNoMoreInteractions();
+    // Make sure that real transport creates streams in the executor
+    verify(mockRealTransport, never()).newStream(
+        any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+    verify(mockRealTransport2, never()).newStream(
+        any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+    fakeExecutor.runDueTasks();
+    assertEquals(0, fakeExecutor.numPendingTasks());
+    // ff1 and wfr1 went through
+    verify(mockRealTransport).newStream(method, headers, failFastCallOptions);
+    verify(mockRealTransport2).newStream(method, headers, waitForReadyCallOptions);
+    assertSame(mockRealStream, ff1.getRealStream());
+    assertSame(mockRealStream2, wfr1.getRealStream());
+    // The ff2 has failed due to picker returning an error
+    assertSame(Status.UNAVAILABLE, ((FailingClientStream) ff2.getRealStream()).getError());
+    // Other streams are still buffered
+    assertNull(ff3.getRealStream());
+    assertNull(ff4.getRealStream());
+    assertNull(wfr2.getRealStream());
+    assertNull(wfr3.getRealStream());
+    assertNull(wfr4.getRealStream());
+
+    // Second reprocess(). All existing streams will proceed.
+    picker = mock(SubchannelPicker.class);
+    when(picker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(
+        PickResult.withSubchannel(subchannel1),  // ff3
+        PickResult.withSubchannel(subchannel2),  // ff4
+        PickResult.withSubchannel(subchannel2),  // wfr2
+        PickResult.withSubchannel(subchannel1),  // wfr3
+        PickResult.withSubchannel(subchannel2),  // wfr4
+        PickResult.withNoResult());              // wfr5 (not yet created)
+    inOrder = inOrder(picker);
+    assertEquals(0, wfr3Executor.numPendingTasks());
+    verify(transportListener, never()).transportInUse(false);
+
+    delayedTransport.reprocess(picker);
+    assertEquals(0, delayedTransport.getPendingStreamsCount());
+    verify(transportListener).transportInUse(false);
+    inOrder.verify(picker).pickSubchannel(ff3args);  // ff3
+    inOrder.verify(picker).pickSubchannel(ff4args);  // ff4
+    inOrder.verify(picker).pickSubchannel(wfr2args);  // wfr2
+    inOrder.verify(picker).pickSubchannel(wfr3args);  // wfr3
+    inOrder.verify(picker).pickSubchannel(wfr4args);  // wfr4
+    inOrder.verifyNoMoreInteractions();
+    fakeExecutor.runDueTasks();
+    assertEquals(0, fakeExecutor.numPendingTasks());
+    assertSame(mockRealStream, ff3.getRealStream());
+    assertSame(mockRealStream2, ff4.getRealStream());
+    assertSame(mockRealStream2, wfr2.getRealStream());
+    assertSame(mockRealStream2, wfr4.getRealStream());
+
+    // If there is an executor in the CallOptions, it will be used to create the real stream.
+    assertNull(wfr3.getRealStream());
+    wfr3Executor.runDueTasks();
+    assertSame(mockRealStream, wfr3.getRealStream());
+
+    // New streams will use the last picker
+    DelayedStream wfr5 = (DelayedStream) delayedTransport.newStream(
+        method, headers, waitForReadyCallOptions);
+    assertNull(wfr5.getRealStream());
+    inOrder.verify(picker).pickSubchannel(
+        new PickSubchannelArgsImpl(method, headers, waitForReadyCallOptions));
+    inOrder.verifyNoMoreInteractions();
+    assertEquals(1, delayedTransport.getPendingStreamsCount());
+
+    // wfr5 will stop delayed transport from terminating
+    delayedTransport.shutdown(SHUTDOWN_STATUS);
+    verify(transportListener).transportShutdown(same(SHUTDOWN_STATUS));
+    verify(transportListener, never()).transportTerminated();
+    // ... until it's gone
+    picker = mock(SubchannelPicker.class);
+    when(picker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(
+        PickResult.withSubchannel(subchannel1));
+    delayedTransport.reprocess(picker);
+    verify(picker).pickSubchannel(
+        new PickSubchannelArgsImpl(method, headers, waitForReadyCallOptions));
+    fakeExecutor.runDueTasks();
+    assertSame(mockRealStream, wfr5.getRealStream());
+    assertEquals(0, delayedTransport.getPendingStreamsCount());
+    verify(transportListener).transportTerminated();
+  }
+
+  @Test
+  public void reprocess_NoPendingStream() {
+    SubchannelPicker picker = mock(SubchannelPicker.class);
+    AbstractSubchannel subchannel = mock(AbstractSubchannel.class);
+    when(subchannel.obtainActiveTransport()).thenReturn(mockRealTransport);
+    when(picker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(
+        PickResult.withSubchannel(subchannel));
+    when(mockRealTransport.newStream(any(MethodDescriptor.class), any(Metadata.class),
+            any(CallOptions.class))).thenReturn(mockRealStream);
+    delayedTransport.reprocess(picker);
+    verifyNoMoreInteractions(picker);
+    verifyNoMoreInteractions(transportListener);
+
+    // Though picker was not originally used, it will be saved and serve future streams.
+    ClientStream stream = delayedTransport.newStream(method, headers, CallOptions.DEFAULT);
+    verify(picker).pickSubchannel(new PickSubchannelArgsImpl(method, headers, CallOptions.DEFAULT));
+    verify(subchannel).obtainActiveTransport();
+    assertSame(mockRealStream, stream);
+  }
+
+  @Test
+  public void reprocess_newStreamRacesWithReprocess() throws Exception {
+    final CyclicBarrier barrier = new CyclicBarrier(2);
+    // In both phases, we only expect the first pickSubchannel() call to block on the barrier.
+    final AtomicBoolean nextPickShouldWait = new AtomicBoolean(true);
+    ///////// Phase 1: reprocess() twice with the same picker
+    SubchannelPicker picker = mock(SubchannelPicker.class);
+
+    doAnswer(new Answer<PickResult>() {
+        @Override
+        public PickResult answer(InvocationOnMock invocation) throws Throwable {
+          if (nextPickShouldWait.compareAndSet(true, false)) {
+            try {
+              barrier.await();
+              return PickResult.withNoResult();
+            } catch (Exception e) {
+              e.printStackTrace();
+            }
+          }
+          return PickResult.withNoResult();
+        }
+    }).when(picker).pickSubchannel(any(PickSubchannelArgs.class));
+
+    // Because there is no pending stream yet, it will do nothing but save the picker.
+    delayedTransport.reprocess(picker);
+    verify(picker, never()).pickSubchannel(any(PickSubchannelArgs.class));
+
+    Thread sideThread = new Thread("sideThread") {
+        @Override
+        public void run() {
+          // Will call pickSubchannel and wait on barrier
+          delayedTransport.newStream(method, headers, callOptions);
+        }
+      };
+    sideThread.start();
+
+    PickSubchannelArgsImpl args = new PickSubchannelArgsImpl(method, headers, callOptions);
+    PickSubchannelArgsImpl args2 = new PickSubchannelArgsImpl(method, headers2, callOptions);
+
+    // Is called from sideThread
+    verify(picker, timeout(5000)).pickSubchannel(args);
+
+    // Because stream has not been buffered (it's still stuck in newStream()), this will do nothing,
+    // but incrementing the picker version.
+    delayedTransport.reprocess(picker);
+    verify(picker).pickSubchannel(args);
+
+    // Now let the stuck newStream() through
+    barrier.await(5, TimeUnit.SECONDS);
+
+    sideThread.join(5000);
+    assertFalse("sideThread should've exited", sideThread.isAlive());
+    // newStream() detects that there has been a new picker while it's stuck, thus will pick again.
+    verify(picker, times(2)).pickSubchannel(args);
+
+    barrier.reset();
+    nextPickShouldWait.set(true);
+
+    ////////// Phase 2: reprocess() with a different picker
+    // Create the second stream
+    Thread sideThread2 = new Thread("sideThread2") {
+        @Override
+        public void run() {
+          // Will call pickSubchannel and wait on barrier
+          delayedTransport.newStream(method, headers2, callOptions);
+        }
+      };
+    sideThread2.start();
+    // The second stream will see the first picker
+    verify(picker, timeout(5000)).pickSubchannel(args2);
+    // While the first stream won't use the first picker any more.
+    verify(picker, times(2)).pickSubchannel(args);
+
+    // Now use a different picker
+    SubchannelPicker picker2 = mock(SubchannelPicker.class);
+    when(picker2.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withNoResult());
+    delayedTransport.reprocess(picker2);
+    // The pending first stream uses the new picker
+    verify(picker2).pickSubchannel(args);
+    // The second stream is still pending in creation, doesn't use the new picker.
+    verify(picker2, never()).pickSubchannel(args2);
+
+    // Now let the second stream finish creation
+    barrier.await(5, TimeUnit.SECONDS);
+
+    sideThread2.join(5000);
+    assertFalse("sideThread2 should've exited", sideThread2.isAlive());
+    // The second stream should see the new picker
+    verify(picker2, timeout(5000)).pickSubchannel(args2);
+
+    // Wrapping up
+    verify(picker, times(2)).pickSubchannel(args);
+    verify(picker).pickSubchannel(args2);
+    verify(picker2).pickSubchannel(args);
+    verify(picker2).pickSubchannel(args);
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/DelayedStreamTest.java b/core/src/test/java/io/grpc/internal/DelayedStreamTest.java
new file mode 100644
index 0000000..482dbdc
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/DelayedStreamTest.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import io.grpc.Attributes;
+import io.grpc.Attributes.Key;
+import io.grpc.Codec;
+import io.grpc.DecompressorRegistry;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.testing.SingleMessageProducer;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link DelayedStream}.  Most of the state checking is enforced by
+ * {@link ClientCallImpl} so we don't check it here.
+ */
+@RunWith(JUnit4.class)
+public class DelayedStreamTest {
+  @Mock private ClientStreamListener listener;
+  @Mock private ClientStream realStream;
+  @Captor private ArgumentCaptor<ClientStreamListener> listenerCaptor;
+  private DelayedStream stream = new DelayedStream();
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+  }
+
+  @Test
+  public void setStream_setAuthority() {
+    final String authority = "becauseIsaidSo";
+    stream.setAuthority(authority);
+    stream.start(listener);
+    stream.setStream(realStream);
+    InOrder inOrder = inOrder(realStream);
+    inOrder.verify(realStream).setAuthority(authority);
+    inOrder.verify(realStream).start(any(ClientStreamListener.class));
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void setAuthority_afterStart() {
+    stream.start(listener);
+    stream.setAuthority("notgonnawork");
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void start_afterStart() {
+    stream.start(listener);
+    stream.start(mock(ClientStreamListener.class));
+  }
+
+  @Test
+  public void setStream_sendsAllMessages() {
+    stream.start(listener);
+    stream.setCompressor(Codec.Identity.NONE);
+    stream.setDecompressorRegistry(DecompressorRegistry.getDefaultInstance());
+
+    stream.setMessageCompression(true);
+    InputStream message = new ByteArrayInputStream(new byte[]{'a'});
+    stream.writeMessage(message);
+    stream.setMessageCompression(false);
+    stream.writeMessage(message);
+
+    stream.setStream(realStream);
+
+    verify(realStream).setCompressor(Codec.Identity.NONE);
+    verify(realStream).setDecompressorRegistry(DecompressorRegistry.getDefaultInstance());
+
+    verify(realStream).setMessageCompression(true);
+    verify(realStream).setMessageCompression(false);
+
+    verify(realStream, times(2)).writeMessage(message);
+    verify(realStream).start(listenerCaptor.capture());
+
+    stream.writeMessage(message);
+    verify(realStream, times(3)).writeMessage(message);
+
+    verifyNoMoreInteractions(listener);
+    listenerCaptor.getValue().onReady();
+    verify(listener).onReady();
+  }
+
+  @Test
+  public void setStream_halfClose() {
+    stream.start(listener);
+    stream.halfClose();
+    stream.setStream(realStream);
+
+    verify(realStream).halfClose();
+  }
+
+  @Test
+  public void setStream_flush() {
+    stream.start(listener);
+    stream.flush();
+    stream.setStream(realStream);
+    verify(realStream).flush();
+
+    stream.flush();
+    verify(realStream, times(2)).flush();
+  }
+
+  @Test
+  public void setStream_flowControl() {
+    stream.start(listener);
+    stream.request(1);
+    stream.request(2);
+    stream.setStream(realStream);
+    verify(realStream).request(1);
+    verify(realStream).request(2);
+
+    stream.request(3);
+    verify(realStream).request(3);
+  }
+
+  @Test
+  public void setStream_setMessageCompression() {
+    stream.start(listener);
+    stream.setMessageCompression(false);
+    stream.setStream(realStream);
+    verify(realStream).setMessageCompression(false);
+
+    stream.setMessageCompression(true);
+    verify(realStream).setMessageCompression(true);
+  }
+
+  @Test
+  public void setStream_isReady() {
+    stream.start(listener);
+    assertFalse(stream.isReady());
+    stream.setStream(realStream);
+    verify(realStream, never()).isReady();
+
+    assertFalse(stream.isReady());
+    verify(realStream).isReady();
+
+    when(realStream.isReady()).thenReturn(true);
+    assertTrue(stream.isReady());
+    verify(realStream, times(2)).isReady();
+  }
+
+  @Test
+  public void setStream_getAttributes() {
+    Attributes attributes =
+        Attributes.newBuilder().set(Key.<String>create("fakeKey"), "fakeValue").build();
+    when(realStream.getAttributes()).thenReturn(attributes);
+
+    stream.start(listener);
+
+    try {
+      stream.getAttributes(); // expect to throw IllegalStateException, otherwise fail()
+      fail();
+    } catch (IllegalStateException expected) {
+      // ignore
+    }
+
+    stream.setStream(realStream);
+    assertEquals(attributes, stream.getAttributes());
+  }
+
+  @Test
+  public void startThenCancelled() {
+    stream.start(listener);
+    stream.cancel(Status.CANCELLED);
+    verify(listener).closed(eq(Status.CANCELLED), any(Metadata.class));
+  }
+
+  @Test
+  public void startThenSetStreamThenCancelled() {
+    stream.start(listener);
+    stream.setStream(realStream);
+    stream.cancel(Status.CANCELLED);
+    verify(realStream).start(any(ClientStreamListener.class));
+    verify(realStream).cancel(same(Status.CANCELLED));
+  }
+
+  @Test
+  public void setStreamThenStartThenCancelled() {
+    stream.setStream(realStream);
+    stream.start(listener);
+    stream.cancel(Status.CANCELLED);
+    verify(realStream).start(same(listener));
+    verify(realStream).cancel(same(Status.CANCELLED));
+  }
+
+  @Test
+  public void setStreamThenCancelled() {
+    stream.setStream(realStream);
+    stream.cancel(Status.CANCELLED);
+    verify(realStream).cancel(same(Status.CANCELLED));
+  }
+
+  @Test
+  public void setStreamTwice() {
+    stream.start(listener);
+    stream.setStream(realStream);
+    verify(realStream).start(any(ClientStreamListener.class));
+    stream.setStream(mock(ClientStream.class));
+    stream.flush();
+    verify(realStream).flush();
+  }
+
+  @Test
+  public void cancelThenSetStream() {
+    stream.cancel(Status.CANCELLED);
+    stream.setStream(realStream);
+    stream.start(listener);
+    stream.isReady();
+    verifyNoMoreInteractions(realStream);
+  }
+
+  @Test
+  public void cancel_beforeStart() {
+    Status status = Status.CANCELLED.withDescription("that was quick");
+    stream.cancel(status);
+    stream.start(listener);
+    verify(listener).closed(same(status), any(Metadata.class));
+  }
+
+  @Test
+  public void cancelledThenStart() {
+    stream.cancel(Status.CANCELLED);
+    stream.start(listener);
+    verify(listener).closed(eq(Status.CANCELLED), any(Metadata.class));
+  }
+
+  @Test
+  public void listener_onReadyDelayedUntilPassthrough() {
+    class IsReadyListener extends NoopClientStreamListener {
+      boolean onReadyCalled;
+
+      @Override
+      public void onReady() {
+        // If onReady was not delayed, then passthrough==false and isReady will return false.
+        assertTrue(stream.isReady());
+        onReadyCalled = true;
+      }
+    }
+
+    IsReadyListener isReadyListener = new IsReadyListener();
+    stream.start(isReadyListener);
+    stream.setStream(new NoopClientStream() {
+      @Override
+      public void start(ClientStreamListener listener) {
+        // This call to the listener should end up being delayed.
+        listener.onReady();
+      }
+
+      @Override
+      public boolean isReady() {
+        return true;
+      }
+    });
+    assertTrue(isReadyListener.onReadyCalled);
+  }
+
+  @Test
+  public void listener_allQueued() {
+    final Metadata headers = new Metadata();
+    final InputStream message1 = mock(InputStream.class);
+    final InputStream message2 = mock(InputStream.class);
+    final SingleMessageProducer producer1 = new SingleMessageProducer(message1);
+    final SingleMessageProducer producer2 = new SingleMessageProducer(message2);
+    final Metadata trailers = new Metadata();
+    final Status status = Status.UNKNOWN.withDescription("unique status");
+
+    final InOrder inOrder = inOrder(listener);
+    stream.start(listener);
+    stream.setStream(new NoopClientStream() {
+      @Override
+      public void start(ClientStreamListener passedListener) {
+        passedListener.onReady();
+        passedListener.headersRead(headers);
+        passedListener.messagesAvailable(producer1);
+        passedListener.onReady();
+        passedListener.messagesAvailable(producer2);
+        passedListener.closed(status, trailers);
+
+        verifyNoMoreInteractions(listener);
+      }
+    });
+    inOrder.verify(listener).onReady();
+    inOrder.verify(listener).headersRead(headers);
+    inOrder.verify(listener).messagesAvailable(producer1);
+    inOrder.verify(listener).onReady();
+    inOrder.verify(listener).messagesAvailable(producer2);
+    inOrder.verify(listener).closed(status, trailers);
+  }
+
+  @Test
+  public void listener_noQueued() {
+    final Metadata headers = new Metadata();
+    final InputStream message = mock(InputStream.class);
+    final SingleMessageProducer producer = new SingleMessageProducer(message);
+    final Metadata trailers = new Metadata();
+    final Status status = Status.UNKNOWN.withDescription("unique status");
+
+    stream.start(listener);
+    stream.setStream(realStream);
+    verify(realStream).start(listenerCaptor.capture());
+    ClientStreamListener delayedListener = listenerCaptor.getValue();
+    delayedListener.onReady();
+    verify(listener).onReady();
+    delayedListener.headersRead(headers);
+    verify(listener).headersRead(headers);
+    delayedListener.messagesAvailable(producer);
+    verify(listener).messagesAvailable(producer);
+    delayedListener.closed(status, trailers);
+    verify(listener).closed(status, trailers);
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/DnsNameResolverProviderTest.java b/core/src/test/java/io/grpc/internal/DnsNameResolverProviderTest.java
new file mode 100644
index 0000000..9e771aa
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/DnsNameResolverProviderTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.Attributes;
+import java.net.URI;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link DnsNameResolverProvider}. */
+@RunWith(JUnit4.class)
+public class DnsNameResolverProviderTest {
+  private DnsNameResolverProvider provider = new DnsNameResolverProvider();
+
+  @Test
+  public void isAvailable() {
+    assertTrue(provider.isAvailable());
+  }
+
+  @Test
+  public void newNameResolver() {
+    assertSame(DnsNameResolver.class,
+        provider.newNameResolver(URI.create("dns:///localhost:443"), Attributes.EMPTY).getClass());
+    assertNull(
+        provider.newNameResolver(URI.create("notdns:///localhost:443"), Attributes.EMPTY));
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java
new file mode 100644
index 0000000..44bde22
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/DnsNameResolverTest.java
@@ -0,0 +1,794 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.Iterables;
+import com.google.common.net.InetAddresses;
+import com.google.common.testing.FakeTicker;
+import io.grpc.Attributes;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.NameResolver;
+import io.grpc.internal.DnsNameResolver.AddressResolver;
+import io.grpc.internal.DnsNameResolver.ResolutionResults;
+import io.grpc.internal.DnsNameResolver.ResourceResolver;
+import io.grpc.internal.DnsNameResolver.ResourceResolverFactory;
+import io.grpc.internal.SharedResourceHolder.Resource;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link DnsNameResolver}. */
+@RunWith(JUnit4.class)
+public class DnsNameResolverTest {
+
+  @Rule public final TestRule globalTimeout = new DisableOnDebug(Timeout.seconds(10));
+
+  @Rule
+  public final ExpectedException thrown = ExpectedException.none();
+
+  private final Map<String, Object> serviceConfig = new LinkedHashMap<String, Object>();
+
+  private static final int DEFAULT_PORT = 887;
+  private static final Attributes NAME_RESOLVER_PARAMS =
+      Attributes.newBuilder().set(NameResolver.Factory.PARAMS_DEFAULT_PORT, DEFAULT_PORT).build();
+
+  private final DnsNameResolverProvider provider = new DnsNameResolverProvider();
+  private final FakeClock fakeClock = new FakeClock();
+  private final FakeClock fakeExecutor = new FakeClock();
+
+  private final Resource<ExecutorService> fakeExecutorResource =
+      new Resource<ExecutorService>() {
+        @Override
+        public ExecutorService create() {
+          return fakeExecutor.getScheduledExecutorService();
+        }
+
+        @Override
+        public void close(ExecutorService instance) {
+        }
+      };
+
+  @Mock
+  private NameResolver.Listener mockListener;
+  @Captor
+  private ArgumentCaptor<List<EquivalentAddressGroup>> resultCaptor;
+  @Nullable
+  private String networkaddressCacheTtlPropertyValue;
+
+  private DnsNameResolver newResolver(String name, int port) {
+    return newResolver(name, port, GrpcUtil.NOOP_PROXY_DETECTOR, Stopwatch.createUnstarted());
+  }
+
+  private DnsNameResolver newResolver(
+      String name,
+      int port,
+      ProxyDetector proxyDetector,
+      Stopwatch stopwatch) {
+    DnsNameResolver dnsResolver = new DnsNameResolver(
+        null,
+        name,
+        Attributes.newBuilder().set(NameResolver.Factory.PARAMS_DEFAULT_PORT, port).build(),
+        fakeExecutorResource,
+        proxyDetector,
+        stopwatch);
+    return dnsResolver;
+  }
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    DnsNameResolver.enableJndi = true;
+    networkaddressCacheTtlPropertyValue =
+        System.getProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY);
+  }
+
+  @After
+  public void restoreSystemProperty() {
+    if (networkaddressCacheTtlPropertyValue == null) {
+      System.clearProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY);
+    } else {
+      System.setProperty(
+          DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY,
+          networkaddressCacheTtlPropertyValue);
+    }
+  }
+
+  @After
+  public void noMorePendingTasks() {
+    assertEquals(0, fakeClock.numPendingTasks());
+    assertEquals(0, fakeExecutor.numPendingTasks());
+  }
+
+  @Test
+  public void invalidDnsName() throws Exception {
+    testInvalidUri(new URI("dns", null, "/[invalid]", null));
+  }
+
+  @Test
+  public void validIpv6() throws Exception {
+    testValidUri(new URI("dns", null, "/[::1]", null), "[::1]", DEFAULT_PORT);
+  }
+
+  @Test
+  public void validDnsNameWithoutPort() throws Exception {
+    testValidUri(new URI("dns", null, "/foo.googleapis.com", null),
+        "foo.googleapis.com", DEFAULT_PORT);
+  }
+
+  @Test
+  public void validDnsNameWithPort() throws Exception {
+    testValidUri(new URI("dns", null, "/foo.googleapis.com:456", null),
+        "foo.googleapis.com:456", 456);
+  }
+
+  @Test
+  public void nullDnsName() {
+    try {
+      newResolver(null, DEFAULT_PORT);
+      fail("Expected NullPointerException");
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  @Test
+  public void invalidDnsName_containsUnderscore() {
+    try {
+      newResolver("host_1", DEFAULT_PORT);
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+  }
+
+  @Test
+  public void resolve_neverCache() throws Exception {
+    System.setProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY, "0");
+    final List<InetAddress> answer1 = createAddressList(2);
+    final List<InetAddress> answer2 = createAddressList(1);
+    String name = "foo.googleapis.com";
+
+    DnsNameResolver resolver = newResolver(name, 81);
+    AddressResolver mockResolver = mock(AddressResolver.class);
+    when(mockResolver.resolveAddress(Matchers.anyString())).thenReturn(answer1).thenReturn(answer2);
+    resolver.setAddressResolver(mockResolver);
+
+    resolver.start(mockListener);
+    assertEquals(1, fakeExecutor.runDueTasks());
+    verify(mockListener).onAddresses(resultCaptor.capture(), any(Attributes.class));
+    assertAnswerMatches(answer1, 81, resultCaptor.getValue());
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    resolver.refresh();
+    assertEquals(1, fakeExecutor.runDueTasks());
+    verify(mockListener, times(2)).onAddresses(resultCaptor.capture(), any(Attributes.class));
+    assertAnswerMatches(answer2, 81, resultCaptor.getValue());
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    resolver.shutdown();
+
+    verify(mockResolver, times(2)).resolveAddress(Matchers.anyString());
+  }
+
+  @Test
+  public void resolve_cacheForever() throws Exception {
+    System.setProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY, "-1");
+    final List<InetAddress> answer1 = createAddressList(2);
+    String name = "foo.googleapis.com";
+    FakeTicker fakeTicker = new FakeTicker();
+
+    DnsNameResolver resolver =
+        newResolver(name, 81, GrpcUtil.NOOP_PROXY_DETECTOR, Stopwatch.createUnstarted(fakeTicker));
+    AddressResolver mockResolver = mock(AddressResolver.class);
+    when(mockResolver.resolveAddress(Matchers.anyString()))
+        .thenReturn(answer1)
+        .thenThrow(new AssertionError("should not called twice"));
+    resolver.setAddressResolver(mockResolver);
+
+    resolver.start(mockListener);
+    assertEquals(1, fakeExecutor.runDueTasks());
+    verify(mockListener).onAddresses(resultCaptor.capture(), any(Attributes.class));
+    assertAnswerMatches(answer1, 81, resultCaptor.getValue());
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    fakeTicker.advance(1, TimeUnit.DAYS);
+    resolver.refresh();
+    assertEquals(1, fakeExecutor.runDueTasks());
+    verifyNoMoreInteractions(mockListener);
+    assertAnswerMatches(answer1, 81, resultCaptor.getValue());
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    resolver.shutdown();
+
+    verify(mockResolver).resolveAddress(Matchers.anyString());
+  }
+
+  @Test
+  public void resolve_usingCache() throws Exception {
+    long ttl = 60;
+    System.setProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY, Long.toString(ttl));
+    final List<InetAddress> answer = createAddressList(2);
+    String name = "foo.googleapis.com";
+    FakeTicker fakeTicker = new FakeTicker();
+
+    DnsNameResolver resolver =
+        newResolver(name, 81, GrpcUtil.NOOP_PROXY_DETECTOR, Stopwatch.createUnstarted(fakeTicker));
+    AddressResolver mockResolver = mock(AddressResolver.class);
+    when(mockResolver.resolveAddress(Matchers.anyString()))
+        .thenReturn(answer)
+        .thenThrow(new AssertionError("should not reach here."));
+    resolver.setAddressResolver(mockResolver);
+
+    resolver.start(mockListener);
+    assertEquals(1, fakeExecutor.runDueTasks());
+    verify(mockListener).onAddresses(resultCaptor.capture(), any(Attributes.class));
+    assertAnswerMatches(answer, 81, resultCaptor.getValue());
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    // this refresh should return cached result
+    fakeTicker.advance(ttl - 1, TimeUnit.SECONDS);
+    resolver.refresh();
+    assertEquals(1, fakeExecutor.runDueTasks());
+    verifyNoMoreInteractions(mockListener);
+    assertAnswerMatches(answer, 81, resultCaptor.getValue());
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    resolver.shutdown();
+
+    verify(mockResolver).resolveAddress(Matchers.anyString());
+  }
+
+  @Test
+  public void resolve_cacheExpired() throws Exception {
+    long ttl = 60;
+    System.setProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY, Long.toString(ttl));
+    final List<InetAddress> answer1 = createAddressList(2);
+    final List<InetAddress> answer2 = createAddressList(1);
+    String name = "foo.googleapis.com";
+    FakeTicker fakeTicker = new FakeTicker();
+
+    DnsNameResolver resolver =
+        newResolver(name, 81, GrpcUtil.NOOP_PROXY_DETECTOR, Stopwatch.createUnstarted(fakeTicker));
+    AddressResolver mockResolver = mock(AddressResolver.class);
+    when(mockResolver.resolveAddress(Matchers.anyString())).thenReturn(answer1).thenReturn(answer2);
+    resolver.setAddressResolver(mockResolver);
+
+    resolver.start(mockListener);
+    assertEquals(1, fakeExecutor.runDueTasks());
+    verify(mockListener).onAddresses(resultCaptor.capture(), any(Attributes.class));
+    assertAnswerMatches(answer1, 81, resultCaptor.getValue());
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    fakeTicker.advance(ttl + 1, TimeUnit.SECONDS);
+    resolver.refresh();
+    assertEquals(1, fakeExecutor.runDueTasks());
+    verify(mockListener, times(2)).onAddresses(resultCaptor.capture(), any(Attributes.class));
+    assertAnswerMatches(answer2, 81, resultCaptor.getValue());
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    resolver.shutdown();
+
+    verify(mockResolver, times(2)).resolveAddress(Matchers.anyString());
+  }
+
+  @Test
+  public void resolve_invalidTtlPropertyValue() throws Exception {
+    System.setProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY, "not_a_number");
+    resolveDefaultValue();
+  }
+
+  @Test
+  public void resolve_noPropertyValue() throws Exception {
+    System.clearProperty(DnsNameResolver.NETWORKADDRESS_CACHE_TTL_PROPERTY);
+    resolveDefaultValue();
+  }
+
+  private void resolveDefaultValue() throws Exception {
+    final List<InetAddress> answer1 = createAddressList(2);
+    final List<InetAddress> answer2 = createAddressList(1);
+    String name = "foo.googleapis.com";
+    FakeTicker fakeTicker = new FakeTicker();
+
+    DnsNameResolver resolver =
+        newResolver(name, 81, GrpcUtil.NOOP_PROXY_DETECTOR, Stopwatch.createUnstarted(fakeTicker));
+    AddressResolver mockResolver = mock(AddressResolver.class);
+    when(mockResolver.resolveAddress(Matchers.anyString())).thenReturn(answer1).thenReturn(answer2);
+    resolver.setAddressResolver(mockResolver);
+
+    resolver.start(mockListener);
+    assertEquals(1, fakeExecutor.runDueTasks());
+    verify(mockListener).onAddresses(resultCaptor.capture(), any(Attributes.class));
+    assertAnswerMatches(answer1, 81, resultCaptor.getValue());
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    fakeTicker.advance(DnsNameResolver.DEFAULT_NETWORK_CACHE_TTL_SECONDS, TimeUnit.SECONDS);
+    resolver.refresh();
+    assertEquals(1, fakeExecutor.runDueTasks());
+    verifyNoMoreInteractions(mockListener);
+    assertAnswerMatches(answer1, 81, resultCaptor.getValue());
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    fakeTicker.advance(1, TimeUnit.SECONDS);
+    resolver.refresh();
+    assertEquals(1, fakeExecutor.runDueTasks());
+    verify(mockListener, times(2)).onAddresses(resultCaptor.capture(), any(Attributes.class));
+    assertAnswerMatches(answer2, 81, resultCaptor.getValue());
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    resolver.shutdown();
+
+    verify(mockResolver, times(2)).resolveAddress(Matchers.anyString());
+  }
+
+  @Test
+  public void resolveAll_nullResourceResolver() throws Exception {
+    final String hostname = "addr.fake";
+    final Inet4Address backendAddr = InetAddresses.fromInteger(0x7f000001);
+
+    AddressResolver mockResolver = mock(AddressResolver.class);
+    when(mockResolver.resolveAddress(Matchers.anyString()))
+        .thenReturn(Collections.<InetAddress>singletonList(backendAddr));
+    ResourceResolver resourceResolver = null;
+    boolean resovleSrv = true;
+    boolean resolveTxt = true;
+
+    ResolutionResults res = DnsNameResolver.resolveAll(
+        mockResolver, resourceResolver, resovleSrv, resolveTxt, hostname);
+    assertThat(res.addresses).containsExactly(backendAddr);
+    assertThat(res.balancerAddresses).isEmpty();
+    assertThat(res.txtRecords).isEmpty();
+    verify(mockResolver).resolveAddress(hostname);
+  }
+
+  @Test
+  public void resolveAll_presentResourceResolver() throws Exception {
+    final String hostname = "addr.fake";
+    final Inet4Address backendAddr = InetAddresses.fromInteger(0x7f000001);
+    final EquivalentAddressGroup balancerAddr = new EquivalentAddressGroup(new SocketAddress() {});
+
+    AddressResolver mockAddressResolver = mock(AddressResolver.class);
+    when(mockAddressResolver.resolveAddress(Matchers.anyString()))
+        .thenReturn(Collections.<InetAddress>singletonList(backendAddr));
+    ResourceResolver mockResourceResolver = mock(ResourceResolver.class);
+    when(mockResourceResolver.resolveTxt(Matchers.anyString()))
+        .thenReturn(Collections.singletonList("service config"));
+    when(mockResourceResolver.resolveSrv(Matchers.any(AddressResolver.class), Matchers.anyString()))
+        .thenReturn(Collections.singletonList(balancerAddr));
+    boolean resovleSrv = true;
+    boolean resolveTxt = true;
+
+    ResolutionResults res = DnsNameResolver.resolveAll(
+        mockAddressResolver, mockResourceResolver, resovleSrv, resolveTxt, hostname);
+    assertThat(res.addresses).containsExactly(backendAddr);
+    assertThat(res.balancerAddresses).containsExactly(balancerAddr);
+    assertThat(res.txtRecords).containsExactly("service config");
+    verify(mockAddressResolver).resolveAddress(hostname);
+    verify(mockResourceResolver).resolveTxt("_grpc_config." + hostname);
+    verify(mockResourceResolver).resolveSrv(mockAddressResolver, "_grpclb._tcp." + hostname);
+  }
+
+  @Test
+  public void resolveAll_onlyBalancers() throws Exception {
+    String hostname = "addr.fake";
+    EquivalentAddressGroup balancerAddr = new EquivalentAddressGroup(new SocketAddress() {});
+
+    AddressResolver mockAddressResolver = mock(AddressResolver.class);
+    when(mockAddressResolver.resolveAddress(Matchers.anyString()))
+        .thenThrow(new UnknownHostException("I really tried"));
+    ResourceResolver mockResourceResolver = mock(ResourceResolver.class);
+    when(mockResourceResolver.resolveTxt(Matchers.anyString()))
+        .thenReturn(Collections.<String>emptyList());
+    when(mockResourceResolver.resolveSrv(Matchers.any(AddressResolver.class), Matchers.anyString()))
+        .thenReturn(Collections.singletonList(balancerAddr));
+    boolean resovleSrv = true;
+    boolean resolveTxt = true;
+
+    ResolutionResults res = DnsNameResolver.resolveAll(
+        mockAddressResolver, mockResourceResolver, resovleSrv, resolveTxt, hostname);
+    assertThat(res.addresses).isEmpty();
+    assertThat(res.balancerAddresses).containsExactly(balancerAddr);
+    assertThat(res.txtRecords).isEmpty();
+    verify(mockAddressResolver).resolveAddress(hostname);
+    verify(mockResourceResolver).resolveTxt("_grpc_config." + hostname);
+    verify(mockResourceResolver).resolveSrv(mockAddressResolver, "_grpclb._tcp." + hostname);
+  }
+
+  @Test
+  public void resolveAll_balancerLookupFails() throws Exception {
+    final String hostname = "addr.fake";
+    final Inet4Address backendAddr = InetAddresses.fromInteger(0x7f000001);
+    AddressResolver mockAddressResolver = mock(AddressResolver.class);
+    when(mockAddressResolver.resolveAddress(Matchers.anyString()))
+        .thenReturn(Collections.<InetAddress>singletonList(backendAddr));
+    ResourceResolver mockResourceResolver = mock(ResourceResolver.class);
+    when(mockResourceResolver.resolveTxt(Matchers.anyString()))
+        .thenReturn(Collections.singletonList("service config"));
+    when(mockResourceResolver.resolveSrv(Matchers.any(AddressResolver.class), Matchers.anyString()))
+        .thenThrow(new Exception("something like javax.naming.NamingException"));
+    boolean resovleSrv = true;
+    boolean resolveTxt = true;
+
+    ResolutionResults res = DnsNameResolver.resolveAll(
+        mockAddressResolver, mockResourceResolver, resovleSrv, resolveTxt, hostname);
+    assertThat(res.addresses).containsExactly(backendAddr);
+    assertThat(res.balancerAddresses).isEmpty();
+    assertThat(res.txtRecords).containsExactly("service config");
+    verify(mockAddressResolver).resolveAddress(hostname);
+    verify(mockResourceResolver).resolveTxt("_grpc_config." + hostname);
+    verify(mockResourceResolver).resolveSrv(mockAddressResolver, "_grpclb._tcp." + hostname);
+  }
+
+  @Test
+  public void skipMissingJndiResolverResolver() throws Exception {
+    ClassLoader cl = new ClassLoader() {
+      @Override
+      protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        if ("io.grpc.internal.JndiResourceResolverFactory".equals(name)) {
+          throw new ClassNotFoundException();
+        }
+        return super.loadClass(name, resolve);
+      }
+    };
+
+    ResourceResolverFactory factory = DnsNameResolver.getResourceResolverFactory(cl);
+
+    assertThat(factory).isNull();
+  }
+
+  @Test
+  public void doNotResolveWhenProxyDetected() throws Exception {
+    final String name = "foo.googleapis.com";
+    final int port = 81;
+    ProxyDetector alwaysDetectProxy = mock(ProxyDetector.class);
+    ProxyParameters proxyParameters = new ProxyParameters(
+        new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 1000),
+        "username",
+        "password");
+    when(alwaysDetectProxy.proxyFor(any(SocketAddress.class)))
+        .thenReturn(proxyParameters);
+    DnsNameResolver resolver =
+        newResolver(name, port, alwaysDetectProxy, Stopwatch.createUnstarted());
+    AddressResolver mockAddressResolver = mock(AddressResolver.class);
+    when(mockAddressResolver.resolveAddress(Matchers.anyString())).thenThrow(new AssertionError());
+    resolver.setAddressResolver(mockAddressResolver);
+    resolver.start(mockListener);
+    assertEquals(1, fakeExecutor.runDueTasks());
+
+    verify(mockListener).onAddresses(resultCaptor.capture(), any(Attributes.class));
+    List<EquivalentAddressGroup> result = resultCaptor.getValue();
+    assertThat(result).hasSize(1);
+    EquivalentAddressGroup eag = result.get(0);
+    assertThat(eag.getAddresses()).hasSize(1);
+
+    ProxySocketAddress socketAddress = (ProxySocketAddress) eag.getAddresses().get(0);
+    assertSame(proxyParameters, socketAddress.getProxyParameters());
+    assertTrue(((InetSocketAddress) socketAddress.getAddress()).isUnresolved());
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_failsOnMisspelling() {
+    Map<String, Object> bad = new LinkedHashMap<String, Object>();
+    bad.put("parcentage", 1.0);
+    thrown.expectMessage("Bad key");
+
+    DnsNameResolver.maybeChooseServiceConfig(bad, new Random(), "host");
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_clientLanguageMatchesJava() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    List<Object> langs = new ArrayList<>();
+    langs.add("java");
+    choice.put("clientLanguage", langs);
+    choice.put("serviceConfig", serviceConfig);
+
+    assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_clientLanguageDoesntMatchGo() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    List<Object> langs = new ArrayList<>();
+    langs.add("go");
+    choice.put("clientLanguage", langs);
+    choice.put("serviceConfig", serviceConfig);
+
+    assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_clientLanguageCaseInsensitive() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    List<Object> langs = new ArrayList<>();
+    langs.add("JAVA");
+    choice.put("clientLanguage", langs);
+    choice.put("serviceConfig", serviceConfig);
+
+    assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_clientLanguageMatchesEmtpy() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    List<Object> langs = new ArrayList<>();
+    choice.put("clientLanguage", langs);
+    choice.put("serviceConfig", serviceConfig);
+
+    assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_clientLanguageMatchesMulti() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    List<Object> langs = new ArrayList<>();
+    langs.add("go");
+    langs.add("java");
+    choice.put("clientLanguage", langs);
+    choice.put("serviceConfig", serviceConfig);
+
+    assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_percentageZeroAlwaysFails() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    choice.put("percentage", 0D);
+    choice.put("serviceConfig", serviceConfig);
+
+    assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_percentageHundredAlwaysSucceeds() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    choice.put("percentage", 100D);
+    choice.put("serviceConfig", serviceConfig);
+
+    assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_percentageAboveMatches50() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    choice.put("percentage", 50D);
+    choice.put("serviceConfig", serviceConfig);
+
+    Random r = new Random() {
+      @Override
+      public int nextInt(int bound) {
+        return 49;
+      }
+    };
+
+    assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, r, "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_percentageAtFails50() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    choice.put("percentage", 50D);
+    choice.put("serviceConfig", serviceConfig);
+
+    Random r = new Random() {
+      @Override
+      public int nextInt(int bound) {
+        return 50;
+      }
+    };
+
+    assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, r, "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_percentageAboveMatches99() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    choice.put("percentage", 99D);
+    choice.put("serviceConfig", serviceConfig);
+
+    Random r = new Random() {
+      @Override
+      public int nextInt(int bound) {
+        return 98;
+      }
+    };
+
+    assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, r, "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_percentageAtFails99() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    choice.put("percentage", 99D);
+    choice.put("serviceConfig", serviceConfig);
+
+    Random r = new Random() {
+      @Override
+      public int nextInt(int bound) {
+        return 99;
+      }
+    };
+
+    assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, r, "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_percentageAboveMatches1() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    choice.put("percentage", 1D);
+    choice.put("serviceConfig", serviceConfig);
+
+    Random r = new Random() {
+      @Override
+      public int nextInt(int bound) {
+        return 0;
+      }
+    };
+
+    assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, r, "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_percentageAtFails1() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    choice.put("percentage", 1D);
+    choice.put("serviceConfig", serviceConfig);
+
+    Random r = new Random() {
+      @Override
+      public int nextInt(int bound) {
+        return 1;
+      }
+    };
+
+    assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, r, "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_hostnameMatches() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    List<Object> hosts = new ArrayList<>();
+    hosts.add("localhost");
+    choice.put("clientHostname", hosts);
+    choice.put("serviceConfig", serviceConfig);
+
+    assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "localhost"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_hostnameDoesntMatch() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    List<Object> hosts = new ArrayList<>();
+    hosts.add("localhorse");
+    choice.put("clientHostname", hosts);
+    choice.put("serviceConfig", serviceConfig);
+
+    assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "localhost"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_clientLanguageCaseSensitive() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    List<Object> hosts = new ArrayList<>();
+    hosts.add("LOCALHOST");
+    choice.put("clientHostname", hosts);
+    choice.put("serviceConfig", serviceConfig);
+
+    assertNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "localhost"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_hostnameMatchesEmtpy() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    List<Object> hosts = new ArrayList<>();
+    choice.put("clientHostname", hosts);
+    choice.put("serviceConfig", serviceConfig);
+
+    assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "host"));
+  }
+
+  @Test
+  public void maybeChooseServiceConfig_hostnameMatchesMulti() {
+    Map<String, Object> choice = new LinkedHashMap<String, Object>();
+    List<Object> hosts = new ArrayList<>();
+    hosts.add("localhorse");
+    hosts.add("localhost");
+    choice.put("clientHostname", hosts);
+    choice.put("serviceConfig", serviceConfig);
+
+    assertNotNull(DnsNameResolver.maybeChooseServiceConfig(choice, new Random(), "localhost"));
+  }
+
+  private void testInvalidUri(URI uri) {
+    try {
+      provider.newNameResolver(uri, NAME_RESOLVER_PARAMS);
+      fail("Should have failed");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+  }
+
+  private void testValidUri(URI uri, String exportedAuthority, int expectedPort) {
+    DnsNameResolver resolver = provider.newNameResolver(uri, NAME_RESOLVER_PARAMS);
+    assertNotNull(resolver);
+    assertEquals(expectedPort, resolver.getPort());
+    assertEquals(exportedAuthority, resolver.getServiceAuthority());
+  }
+
+  private byte lastByte = 0;
+
+  private List<InetAddress> createAddressList(int n) throws UnknownHostException {
+    List<InetAddress> list = new ArrayList<>(n);
+    for (int i = 0; i < n; i++) {
+      list.add(InetAddress.getByAddress(new byte[] {127, 0, 0, ++lastByte}));
+    }
+    return list;
+  }
+
+  private static void assertAnswerMatches(
+      List<InetAddress> addrs, int port, List<EquivalentAddressGroup> results) {
+    assertEquals(addrs.size(), results.size());
+    for (int i = 0; i < addrs.size(); i++) {
+      EquivalentAddressGroup addrGroup = results.get(i);
+      InetSocketAddress socketAddr =
+          (InetSocketAddress) Iterables.getOnlyElement(addrGroup.getAddresses());
+      assertEquals("Addr " + i, port, socketAddr.getPort());
+      assertEquals("Addr " + i, addrs.get(i), socketAddr.getAddress());
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ExponentialBackoffPolicyTest.java b/core/src/test/java/io/grpc/internal/ExponentialBackoffPolicyTest.java
new file mode 100644
index 0000000..ca597a8
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ExponentialBackoffPolicyTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.Random;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test for {@link ExponentialBackoffPolicy}.
+ */
+@RunWith(JUnit4.class)
+public class ExponentialBackoffPolicyTest {
+  private ExponentialBackoffPolicy policy = new ExponentialBackoffPolicy();
+  private Random notRandom = new Random() {
+    @Override
+    public double nextDouble() {
+      return .5;
+    }
+  };
+
+  @Test
+  public void maxDelayReached() {
+    long maxBackoffNanos = 120 * 1000;
+    policy.setMaxBackoffNanos(maxBackoffNanos)
+        .setJitter(0)
+        .setRandom(notRandom);
+    for (int i = 0; i < 50; i++) {
+      if (maxBackoffNanos == policy.nextBackoffNanos()) {
+        return; // Success
+      }
+    }
+    assertEquals("max delay not reached", maxBackoffNanos, policy.nextBackoffNanos());
+  }
+
+  @Test public void canProvide() {
+    assertNotNull(new ExponentialBackoffPolicy.Provider().get());
+  }
+}
+
diff --git a/core/src/test/java/io/grpc/internal/FailingClientStreamTest.java b/core/src/test/java/io/grpc/internal/FailingClientStreamTest.java
new file mode 100644
index 0000000..aceed19
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/FailingClientStreamTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.ClientStreamListener.RpcProgress;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link FailingClientStream}.
+ */
+@RunWith(JUnit4.class)
+public class FailingClientStreamTest {
+
+  @Test
+  public void processedRpcProgressPopulatedToListener() {
+    ClientStreamListener listener = mock(ClientStreamListener.class);
+    Status status = Status.UNAVAILABLE;
+
+    ClientStream stream = new FailingClientStream(status);
+    stream.start(listener);
+    verify(listener).closed(eq(status), eq(RpcProgress.PROCESSED), any(Metadata.class));
+  }
+
+  @Test
+  public void droppedRpcProgressPopulatedToListener() {
+    ClientStreamListener listener = mock(ClientStreamListener.class);
+    Status status = Status.UNAVAILABLE;
+
+    ClientStream stream = new FailingClientStream(status, RpcProgress.DROPPED);
+    stream.start(listener);
+    verify(listener).closed(eq(status), eq(RpcProgress.DROPPED), any(Metadata.class));
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/FailingClientTransportTest.java b/core/src/test/java/io/grpc/internal/FailingClientTransportTest.java
new file mode 100644
index 0000000..8ea161e
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/FailingClientTransportTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.CallOptions;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.ClientStreamListener.RpcProgress;
+import io.grpc.testing.TestMethodDescriptors;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link FailingClientTransport}.
+ */
+@RunWith(JUnit4.class)
+public class FailingClientTransportTest {
+
+  @Test
+  public void newStreamStart() {
+    Status error = Status.UNAVAILABLE;
+    RpcProgress rpcProgress = RpcProgress.DROPPED;
+    FailingClientTransport transport = new FailingClientTransport(error, rpcProgress);
+    ClientStream stream = transport
+        .newStream(TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT);
+    ClientStreamListener listener = mock(ClientStreamListener.class);
+    stream.start(listener);
+
+    verify(listener).closed(eq(error), eq(rpcProgress), any(Metadata.class));
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/FakeClock.java b/core/src/test/java/io/grpc/internal/FakeClock.java
new file mode 100644
index 0000000..38936e0
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/FakeClock.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Supplier;
+import com.google.common.base.Ticker;
+import com.google.common.util.concurrent.AbstractFuture;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.Future;
+import java.util.concurrent.PriorityBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A manipulated clock that exports a {@link Ticker} and a {@link ScheduledExecutorService}.
+ *
+ * <p>To simulate the locking scenario of using real executors, it never runs tasks within {@code
+ * schedule()} or {@code execute()}. Instead, you should call {@link #runDueTasks} in your test
+ * method to run all due tasks. {@link #forwardTime} and {@link #forwardNanos} call {@link
+ * #runDueTasks} automatically.
+ */
+public final class FakeClock {
+
+  private static final TaskFilter ACCEPT_ALL_FILTER = new TaskFilter() {
+      @Override
+      public boolean shouldAccept(Runnable command) {
+        return true;
+      }
+    };
+
+  private final ScheduledExecutorService scheduledExecutorService = new ScheduledExecutorImpl();
+
+  private final PriorityBlockingQueue<ScheduledTask> tasks =
+      new PriorityBlockingQueue<ScheduledTask>();
+
+  private final Ticker ticker =
+      new Ticker() {
+        @Override public long read() {
+          return currentTimeNanos;
+        }
+      };
+
+  private final Supplier<Stopwatch> stopwatchSupplier =
+      new Supplier<Stopwatch>() {
+        @Override public Stopwatch get() {
+          return Stopwatch.createUnstarted(ticker);
+        }
+      };
+
+  private long currentTimeNanos;
+
+  public class ScheduledTask extends AbstractFuture<Void> implements ScheduledFuture<Void> {
+    public final Runnable command;
+    public final long dueTimeNanos;
+
+    ScheduledTask(long dueTimeNanos, Runnable command) {
+      this.dueTimeNanos = dueTimeNanos;
+      this.command = command;
+    }
+
+    @Override public boolean cancel(boolean mayInterruptIfRunning) {
+      tasks.remove(this);
+      return super.cancel(mayInterruptIfRunning);
+    }
+
+    @Override public long getDelay(TimeUnit unit) {
+      return unit.convert(dueTimeNanos - currentTimeNanos, TimeUnit.NANOSECONDS);
+    }
+
+    @Override public int compareTo(Delayed other) {
+      ScheduledTask otherTask = (ScheduledTask) other;
+      if (dueTimeNanos > otherTask.dueTimeNanos) {
+        return 1;
+      } else if (dueTimeNanos < otherTask.dueTimeNanos) {
+        return -1;
+      } else {
+        return 0;
+      }
+    }
+
+    void complete() {
+      set(null);
+    }
+
+    @Override
+    public String toString() {
+      return "[due=" + dueTimeNanos + ", task=" + command + "]";
+    }
+  }
+
+  private class ScheduledExecutorImpl implements ScheduledExecutorService {
+    @Override public <V> ScheduledFuture<V> schedule(
+        Callable<V> callable, long delay, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public ScheduledFuture<?> schedule(Runnable cmd, long delay, TimeUnit unit) {
+      ScheduledTask task = new ScheduledTask(currentTimeNanos + unit.toNanos(delay), cmd);
+      tasks.add(task);
+      return task;
+    }
+
+    @Override public ScheduledFuture<?> scheduleAtFixedRate(
+        Runnable command, long initialDelay, long period, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public ScheduledFuture<?> scheduleWithFixedDelay(
+        Runnable command, long initialDelay, long delay, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public boolean awaitTermination(long timeout, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public <T> List<Future<T>> invokeAll(
+        Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public <T> T invokeAny(Collection<? extends Callable<T>> tasks) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public <T> T invokeAny(
+        Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public boolean isShutdown() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public boolean isTerminated() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public void shutdown() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public List<Runnable> shutdownNow() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public <T> Future<T> submit(Callable<T> task) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public Future<?> submit(Runnable task) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public <T> Future<T> submit(Runnable task, T result) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public void execute(Runnable command) {
+      // Since it is being enqueued immediately, no point in tracing the future for cancellation.
+      Future<?> unused = schedule(command, 0, TimeUnit.NANOSECONDS);
+    }
+  }
+
+  /**
+   * Provides a partially implemented instance of {@link ScheduledExecutorService} that uses the
+   * fake clock ticker for testing.
+   */
+  public ScheduledExecutorService getScheduledExecutorService() {
+    return scheduledExecutorService;
+  }
+
+  /**
+   * Provides a stopwatch instance that uses the fake clock ticker.
+   */
+  public Supplier<Stopwatch> getStopwatchSupplier() {
+    return stopwatchSupplier;
+  }
+
+  /**
+   * Ticker of the FakeClock.
+   */
+  public Ticker getTicker() {
+    return ticker;
+  }
+
+  /**
+   * Run all due tasks.
+   *
+   * @return the number of tasks run by this call
+   */
+  public int runDueTasks() {
+    int count = 0;
+    while (true) {
+      ScheduledTask task = tasks.peek();
+      if (task == null || task.dueTimeNanos > currentTimeNanos) {
+        break;
+      }
+      if (tasks.remove(task)) {
+        task.command.run();
+        task.complete();
+        count++;
+      }
+    }
+    return count;
+  }
+
+  /**
+   * Return all due tasks.
+   */
+  public Collection<ScheduledTask> getDueTasks() {
+    ArrayList<ScheduledTask> result = new ArrayList<>();
+    for (ScheduledTask task : tasks) {
+      if (task.dueTimeNanos > currentTimeNanos) {
+        continue;
+      }
+      result.add(task);
+    }
+    return result;
+  }
+
+  /**
+   * Return all unrun tasks.
+   */
+  public Collection<ScheduledTask> getPendingTasks() {
+    return getPendingTasks(ACCEPT_ALL_FILTER);
+  }
+
+  /**
+   * Return all unrun tasks accepted by the given filter.
+   */
+  public Collection<ScheduledTask> getPendingTasks(TaskFilter filter) {
+    ArrayList<ScheduledTask> result = new ArrayList<>();
+    for (ScheduledTask task : tasks) {
+      if (filter.shouldAccept(task.command)) {
+        result.add(task);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Forward the time by the given duration and run all due tasks.
+   *
+   * @return the number of tasks run by this call
+   */
+  public int forwardTime(long value, TimeUnit unit) {
+    currentTimeNanos += unit.toNanos(value);
+    return runDueTasks();
+  }
+
+  /**
+   * Forward the time by the given nanoseconds and run all due tasks.
+   *
+   * @return the number of tasks run by this call
+   */
+  public int forwardNanos(long nanos) {
+    return forwardTime(nanos, TimeUnit.NANOSECONDS);
+  }
+
+  /**
+   * Return the number of queued tasks.
+   */
+  public int numPendingTasks() {
+    return tasks.size();
+  }
+
+  /**
+   * Return the number of queued tasks accepted by the given filter.
+   */
+  public int numPendingTasks(TaskFilter filter) {
+    int count = 0;
+    for (ScheduledTask task : tasks) {
+      if (filter.shouldAccept(task.command)) {
+        count++;
+      }
+    }
+    return count;
+  }
+
+  public long currentTimeMillis() {
+    // Normally millis and nanos are of different epochs. Add an offset to simulate that.
+    return TimeUnit.NANOSECONDS.toMillis(currentTimeNanos + 123456789L);
+  }
+
+  /**
+   * A filter that allows us to have fine grained control over which tasks are accepted for certain
+   * operation.
+   */
+  public interface TaskFilter {
+    /**
+     * Inspect the Runnable and returns true if it should be accepted.
+     */
+    boolean shouldAccept(Runnable runnable);
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/FakeClockTest.java b/core/src/test/java/io/grpc/internal/FakeClockTest.java
new file mode 100644
index 0000000..a92cd49
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/FakeClockTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Stopwatch;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link FakeClock}. */
+@RunWith(JUnit4.class)
+public class FakeClockTest {
+
+  @Test
+  public void testScheduledExecutorService_sameInstance() {
+    FakeClock fakeClock = new FakeClock();
+    ScheduledExecutorService scheduledExecutorService1 = fakeClock.getScheduledExecutorService();
+    ScheduledExecutorService scheduledExecutorService2 = fakeClock.getScheduledExecutorService();
+    assertTrue(scheduledExecutorService1 == scheduledExecutorService2);
+  }
+
+  @Test
+  public void testScheduledExecutorService_isDone() {
+    FakeClock fakeClock = new FakeClock();
+    ScheduledFuture<?> future = fakeClock.getScheduledExecutorService()
+        .schedule(newRunnable(), 100L, TimeUnit.NANOSECONDS);
+
+    fakeClock.forwardNanos(99L);
+    assertFalse(future.isDone());
+
+    fakeClock.forwardNanos(2L);
+    assertTrue(future.isDone());
+  }
+
+  @Test
+  public void testScheduledExecutorService_cancel() {
+    FakeClock fakeClock = new FakeClock();
+    ScheduledFuture<?> future = fakeClock.getScheduledExecutorService()
+        .schedule(newRunnable(), 100L, TimeUnit.NANOSECONDS);
+
+    fakeClock.forwardNanos(99L);
+    future.cancel(false);
+
+    fakeClock.forwardNanos(2);
+    assertTrue(future.isCancelled());
+  }
+
+  @Test
+  public void testScheduledExecutorService_getDelay() {
+    FakeClock fakeClock = new FakeClock();
+    ScheduledFuture<?> future = fakeClock.getScheduledExecutorService()
+        .schedule(newRunnable(), 100L, TimeUnit.NANOSECONDS);
+
+    fakeClock.forwardNanos(90L);
+    assertEquals(10L, future.getDelay(TimeUnit.NANOSECONDS));
+  }
+
+  @Test
+  public void testScheduledExecutorService_result() {
+    FakeClock fakeClock = new FakeClock();
+    final boolean[] result = new boolean[]{false};
+    ScheduledFuture<?> unused = fakeClock.getScheduledExecutorService().schedule(
+        new Runnable() {
+          @Override
+          public void run() {
+            result[0] = true;
+          }
+        },
+        100L,
+        TimeUnit.NANOSECONDS);
+
+    fakeClock.forwardNanos(100L);
+    assertTrue(result[0]);
+  }
+
+  @Test
+  public void testStopWatch() {
+    FakeClock fakeClock = new FakeClock();
+    Stopwatch stopwatch = fakeClock.getStopwatchSupplier().get();
+    long expectedElapsedNanos = 0L;
+
+    stopwatch.start();
+
+    fakeClock.forwardNanos(100L);
+    expectedElapsedNanos += 100L;
+    assertEquals(expectedElapsedNanos, stopwatch.elapsed(TimeUnit.NANOSECONDS));
+
+    fakeClock.forwardTime(10L, TimeUnit.MINUTES);
+    expectedElapsedNanos += TimeUnit.MINUTES.toNanos(10L);
+    assertEquals(expectedElapsedNanos, stopwatch.elapsed(TimeUnit.NANOSECONDS));
+
+    stopwatch.stop();
+
+    fakeClock.forwardNanos(1000L);
+    assertEquals(expectedElapsedNanos, stopwatch.elapsed(TimeUnit.NANOSECONDS));
+
+    stopwatch.reset();
+
+    expectedElapsedNanos = 0L;
+    assertEquals(expectedElapsedNanos, stopwatch.elapsed(TimeUnit.NANOSECONDS));
+  }
+
+  @Test
+  @SuppressWarnings("FutureReturnValueIgnored")
+  public void testPendingAndDueTasks() {
+    FakeClock fakeClock = new FakeClock();
+    ScheduledExecutorService scheduledExecutorService = fakeClock.getScheduledExecutorService();
+
+    scheduledExecutorService.schedule(newRunnable(), 200L, TimeUnit.NANOSECONDS);
+    scheduledExecutorService.execute(newRunnable());
+    scheduledExecutorService.schedule(newRunnable(), 0L, TimeUnit.NANOSECONDS);
+    scheduledExecutorService.schedule(newRunnable(), 80L, TimeUnit.NANOSECONDS);
+    scheduledExecutorService.schedule(newRunnable(), 90L, TimeUnit.NANOSECONDS);
+    scheduledExecutorService.schedule(newRunnable(), 100L, TimeUnit.NANOSECONDS);
+    scheduledExecutorService.schedule(newRunnable(), 110L, TimeUnit.NANOSECONDS);
+    scheduledExecutorService.schedule(newRunnable(), 120L, TimeUnit.NANOSECONDS);
+
+
+    assertEquals(8, fakeClock.numPendingTasks());
+    assertEquals(2, fakeClock.getDueTasks().size());
+
+    fakeClock.runDueTasks();
+
+    assertEquals(6, fakeClock.numPendingTasks());
+    assertEquals(0, fakeClock.getDueTasks().size());
+
+    fakeClock.forwardNanos(90L);
+
+    assertEquals(4, fakeClock.numPendingTasks());
+    assertEquals(0, fakeClock.getDueTasks().size());
+
+    fakeClock.forwardNanos(20L);
+
+    assertEquals(2, fakeClock.numPendingTasks());
+    assertEquals(0, fakeClock.getDueTasks().size());
+  }
+
+  @Test
+  public void testTaskFilter() {
+    FakeClock fakeClock = new FakeClock();
+    ScheduledExecutorService scheduledExecutorService = fakeClock.getScheduledExecutorService();
+    final AtomicBoolean selectedDone = new AtomicBoolean();
+    final AtomicBoolean ignoredDone = new AtomicBoolean();
+    final Runnable selectedRunnable = new Runnable() {
+      @Override
+      public void run() {
+        selectedDone.set(true);
+      }
+    };
+    Runnable ignoredRunnable = new Runnable() {
+      @Override
+      public void run() {
+        ignoredDone.set(true);
+      }
+    };
+    FakeClock.TaskFilter filter = new FakeClock.TaskFilter() {
+        @Override
+        public boolean shouldAccept(Runnable runnable) {
+          return runnable == selectedRunnable;
+        }
+      };
+    scheduledExecutorService.execute(selectedRunnable);
+    scheduledExecutorService.execute(ignoredRunnable);
+    assertEquals(2, fakeClock.numPendingTasks());
+    assertEquals(1, fakeClock.numPendingTasks(filter));
+    assertEquals(2, fakeClock.getPendingTasks().size());
+    assertEquals(1, fakeClock.getPendingTasks(filter).size());
+    assertSame(selectedRunnable, fakeClock.getPendingTasks(filter).iterator().next().command);
+    assertEquals(2, fakeClock.runDueTasks());
+    assertTrue(selectedDone.get());
+    assertTrue(ignoredDone.get());
+  }
+
+  private Runnable newRunnable() {
+    return new Runnable() {
+      @Override
+      public void run() {
+      }
+    };
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ForwardingClientStreamListenerTest.java b/core/src/test/java/io/grpc/internal/ForwardingClientStreamListenerTest.java
new file mode 100644
index 0000000..5d1fbe1
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ForwardingClientStreamListenerTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.ForwardingTestUtil;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.StreamListener.MessageProducer;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ForwardingClientStreamListenerTest {
+  private ClientStreamListener mock = mock(ClientStreamListener.class);
+  private ForwardingClientStreamListener forward = new ForwardingClientStreamListener() {
+    @Override
+    protected ClientStreamListener delegate() {
+      return mock;
+    }
+  };
+
+  @Test
+  public void allMethodsForwarded() throws Exception {
+    ForwardingTestUtil.testMethodsForwarded(
+        ClientStreamListener.class,
+        mock,
+        forward,
+        Collections.<Method>emptyList());
+  }
+
+
+  @Test
+  public void headersReadTest() {
+    Metadata headers = new Metadata();
+    forward.headersRead(headers);
+    verify(mock).headersRead(same(headers));
+  }
+
+  @Test
+  public void closedTest() {
+    Status status = Status.UNKNOWN;
+    Metadata trailers = new Metadata();
+    forward.closed(status, trailers);
+    verify(mock).closed(same(status), same(trailers));
+  }
+
+  @Test
+  public void messagesAvailableTest() {
+    MessageProducer producer = mock(MessageProducer.class);
+    forward.messagesAvailable(producer);
+    verify(mock).messagesAvailable(same(producer));
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ForwardingClientStreamTest.java b/core/src/test/java/io/grpc/internal/ForwardingClientStreamTest.java
new file mode 100644
index 0000000..130bb1b
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ForwardingClientStreamTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.grpc.Attributes;
+import io.grpc.Compressor;
+import io.grpc.Decompressor;
+import io.grpc.DecompressorRegistry;
+import io.grpc.ForwardingTestUtil;
+import io.grpc.Status;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ForwardingClientStreamTest {
+  private ClientStream mock = mock(ClientStream.class);
+  private ForwardingClientStream forward = new ForwardingClientStream() {
+    @Override
+    protected ClientStream delegate() {
+      return mock;
+    }
+  };
+
+  @Test
+  public void allMethodsForwarded() throws Exception {
+    ForwardingTestUtil.testMethodsForwarded(
+        ClientStream.class,
+        mock,
+        forward,
+        Collections.<Method>emptyList());
+  }
+
+  @Test
+  public void requestTest() {
+    forward.request(1234);
+    verify(mock).request(1234);
+  }
+
+  @Test
+  public void writeMessageTest() {
+    InputStream is = mock(InputStream.class); 
+    forward.writeMessage(is);
+    verify(mock).writeMessage(same(is));
+  }
+
+  @Test
+  public void isReadyTest() {
+    when(mock.isReady()).thenReturn(true);
+    assertEquals(true, forward.isReady());
+  }
+
+  @Test
+  public void setCompressorTest() {
+    Compressor compressor = mock(Compressor.class);
+    forward.setCompressor(compressor);
+    verify(mock).setCompressor(same(compressor));
+  }
+
+  @Test
+  public void setMessageCompressionTest() {
+    forward.setMessageCompression(true);
+    verify(mock).setMessageCompression(true);
+  }
+
+  @Test
+  public void cancelTest() {
+    Status reason = Status.UNKNOWN;
+    forward.cancel(reason);
+    verify(mock).cancel(same(reason));
+  }
+
+  @Test
+  public void setAuthorityTest() {
+    String authority = "authority";
+    forward.setAuthority(authority);
+    verify(mock).setAuthority(authority);
+  }
+
+  @Test
+  public void setFullStreamDecompressionTest() {
+    forward.setFullStreamDecompression(true);
+    verify(mock).setFullStreamDecompression(true);
+  }
+
+  @Test
+  public void setDecompressorRegistryTest() {
+    DecompressorRegistry decompressor =
+        DecompressorRegistry.emptyInstance().with(new Decompressor() {
+          @Override
+          public String getMessageEncoding() {
+            return "some-encoding";
+          }
+
+          @Override
+          public InputStream decompress(InputStream is) throws IOException {
+            return is;
+          }
+        }, true);
+    forward.setDecompressorRegistry(decompressor);
+    verify(mock).setDecompressorRegistry(same(decompressor));
+  }
+
+  @Test
+  public void startTest() {
+    ClientStreamListener listener = mock(ClientStreamListener.class);
+    forward.start(listener);
+    verify(mock).start(same(listener));
+  }
+
+  @Test
+  public void setMaxInboundMessageSizeTest() {
+    int size = 4567;
+    forward.setMaxInboundMessageSize(size);
+    verify(mock).setMaxInboundMessageSize(size);
+  }
+
+  @Test
+  public void setMaxOutboundMessageSizeTest() {
+    int size = 6789;
+    forward.setMaxOutboundMessageSize(size);
+    verify(mock).setMaxOutboundMessageSize(size);
+  }
+
+  @Test
+  public void getAttributesTest() {
+    Attributes attr = Attributes.newBuilder().build();
+    when(mock.getAttributes()).thenReturn(attr);
+    assertSame(attr, forward.getAttributes());
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ForwardingManagedChannelTest.java b/core/src/test/java/io/grpc/internal/ForwardingManagedChannelTest.java
new file mode 100644
index 0000000..6e192e9
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ForwardingManagedChannelTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.grpc.CallOptions;
+import io.grpc.ConnectivityState;
+import io.grpc.ForwardingTestUtil;
+import io.grpc.ManagedChannel;
+import io.grpc.MethodDescriptor;
+import io.grpc.testing.TestMethodDescriptors;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ForwardingManagedChannelTest {
+  private final ManagedChannel mock = mock(ManagedChannel.class);
+  private final ForwardingManagedChannel forward = new ForwardingManagedChannel(mock) {};
+
+  @Test
+  public void allMethodsForwarded() throws Exception {
+    ForwardingTestUtil.testMethodsForwarded(
+        ManagedChannel.class,
+        mock,
+        forward,
+        Collections.<Method>emptyList());
+  }
+
+  @Test
+  public void shutdown() {
+    ManagedChannel ret = mock(ManagedChannel.class);
+    when(mock.shutdown()).thenReturn(ret);
+    assertSame(ret, forward.shutdown());
+  }
+
+  @Test
+  public void isShutdown() {
+    when(mock.isShutdown()).thenReturn(true);
+    assertSame(true, forward.isShutdown());
+  }
+
+  @Test
+  public void isTerminated() {
+    when(mock.isTerminated()).thenReturn(true);
+    assertSame(true, forward.isTerminated());
+  }
+
+  @Test
+  public void shutdownNow() {
+    ManagedChannel ret = mock(ManagedChannel.class);
+    when(mock.shutdownNow()).thenReturn(ret);
+    assertSame(ret, forward.shutdownNow());
+  }
+
+  @Test
+  public void awaitTermination() throws Exception {
+    long timeout = 1234;
+    TimeUnit unit = TimeUnit.MILLISECONDS;
+    forward.awaitTermination(timeout, unit);
+    verify(mock).awaitTermination(eq(timeout), eq(unit));
+  }
+
+  @Test
+  public void newCall() {
+    NoopClientCall<Void, Void> clientCall = new NoopClientCall<Void, Void>();
+    CallOptions callOptions = CallOptions.DEFAULT.withoutWaitForReady();
+    MethodDescriptor<Void, Void> method = TestMethodDescriptors.voidMethod();
+    when(mock.newCall(same(method), same(callOptions))).thenReturn(clientCall);
+    assertSame(clientCall, forward.newCall(method, callOptions));
+  }
+
+  @Test
+  public void authority() {
+    String authority = "authority5678";
+    when(mock.authority()).thenReturn(authority);
+    assertSame(authority, forward.authority());
+  }
+
+  @Test
+  public void getState() {
+    when(mock.getState(true)).thenReturn(ConnectivityState.READY);
+    assertSame(ConnectivityState.READY, forward.getState(true));
+  }
+
+  @Test
+  public void notifyWhenStateChanged() {
+    Runnable callback = new Runnable() {
+      @Override
+      public void run() { }
+    };
+    forward.notifyWhenStateChanged(ConnectivityState.READY, callback);
+    verify(mock).notifyWhenStateChanged(same(ConnectivityState.READY), same(callback));
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ForwardingNameResolverTest.java b/core/src/test/java/io/grpc/internal/ForwardingNameResolverTest.java
new file mode 100644
index 0000000..45223e0
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ForwardingNameResolverTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.grpc.Attributes;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.ForwardingTestUtil;
+import io.grpc.NameResolver;
+import io.grpc.Status;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link ForwardingNameResolver}.
+ */
+@RunWith(JUnit4.class)
+public class ForwardingNameResolverTest {
+  private final NameResolver delegate = mock(NameResolver.class);
+  private final NameResolver forwarder = new ForwardingNameResolver(delegate) {
+  };
+
+  @Test
+  public void allMethodsForwarded() throws Exception {
+    ForwardingTestUtil.testMethodsForwarded(
+        NameResolver.class,
+        delegate,
+        forwarder,
+        Collections.<Method>emptyList());
+  }
+
+  @Test
+  public void getServiceAuthority() {
+    String auth = "example.com";
+    when(delegate.getServiceAuthority()).thenReturn(auth);
+
+    assertEquals(auth, forwarder.getServiceAuthority());
+  }
+
+  @Test
+  public void start() {
+    NameResolver.Listener listener = new NameResolver.Listener() {
+      @Override
+      public void onAddresses(List<EquivalentAddressGroup> servers, Attributes attributes) { }
+
+      @Override
+      public void onError(Status error) { }
+    };
+
+    forwarder.start(listener);
+    verify(delegate).start(listener);
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ForwardingReadableBufferTest.java b/core/src/test/java/io/grpc/internal/ForwardingReadableBufferTest.java
new file mode 100644
index 0000000..1f33d59
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ForwardingReadableBufferTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.grpc.ForwardingTestUtil;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link ForwardingReadableBuffer}.
+ */
+@RunWith(JUnit4.class)
+public class ForwardingReadableBufferTest {
+
+  @Mock private ReadableBuffer delegate;
+  private ForwardingReadableBuffer buffer;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    buffer = new ForwardingReadableBuffer(delegate) {};
+  }
+
+  @Test
+  public void allMethodsForwarded() throws Exception {
+    ForwardingTestUtil.testMethodsForwarded(
+        ReadableBuffer.class,
+        delegate,
+        buffer,
+        Collections.<Method>emptyList());
+  }
+
+  @Test
+  public void readableBytes() {
+    when(delegate.readableBytes()).thenReturn(1);
+
+    assertEquals(1, buffer.readableBytes());
+  }
+
+  @Test
+  public void readUnsignedByte() {
+    when(delegate.readUnsignedByte()).thenReturn(1);
+
+    assertEquals(1, buffer.readUnsignedByte());
+  }
+
+  @Test
+  public void readInt() {
+    when(delegate.readInt()).thenReturn(1);
+
+    assertEquals(1, buffer.readInt());
+  }
+
+  @Test
+  public void skipBytes() {
+    buffer.skipBytes(1);
+
+    verify(delegate).skipBytes(1);
+  }
+
+  @Test
+  public void readBytes() {
+    byte[] dest = new byte[1];
+    buffer.readBytes(dest, 1, 2);
+
+    verify(delegate).readBytes(dest, 1, 2);
+  }
+
+  @Test
+  public void readBytes_overload1() {
+    ByteBuffer dest = mock(ByteBuffer.class);
+    buffer.readBytes(dest);
+
+    verify(delegate).readBytes(dest);
+  }
+
+  @Test
+  public void readBytes_overload2() throws IOException {
+    OutputStream dest = mock(OutputStream.class);
+    buffer.readBytes(dest, 1);
+
+    verify(delegate).readBytes(dest, 1);
+  }
+
+  @Test
+  public void readBytes_overload3() {
+    buffer.readBytes(1);
+
+    verify(delegate).readBytes(1);
+  }
+
+  @Test
+  public void hasArray() {
+    when(delegate.hasArray()).thenReturn(true);
+
+    assertEquals(true, buffer.hasArray());
+  }
+
+  @Test
+  public void array() {
+    byte[] array = new byte[1];
+    when(delegate.array()).thenReturn(array);
+
+    assertEquals(array, buffer.array());
+  }
+
+  @Test
+  public void arrayOffset() {
+    when(delegate.arrayOffset()).thenReturn(1);
+
+    assertEquals(1, buffer.arrayOffset());
+  }
+
+  @Test
+  public void close() {
+    buffer.close();
+
+    verify(delegate).close();
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/GrpcUtilTest.java b/core/src/test/java/io/grpc/internal/GrpcUtilTest.java
new file mode 100644
index 0000000..f369453
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/GrpcUtilTest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.CallOptions;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.ClientStreamListener.RpcProgress;
+import io.grpc.internal.GrpcUtil.Http2Error;
+import io.grpc.testing.TestMethodDescriptors;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link GrpcUtil}. */
+@RunWith(JUnit4.class)
+public class GrpcUtilTest {
+
+  @Rule public final ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void http2ErrorForCode() {
+    // Try edge cases manually, to make the test obviously correct for important cases.
+    assertNull(Http2Error.forCode(-1));
+    assertSame(Http2Error.NO_ERROR, Http2Error.forCode(0));
+    assertSame(Http2Error.HTTP_1_1_REQUIRED, Http2Error.forCode(0xD));
+    assertNull(Http2Error.forCode(0xD + 1));
+  }
+
+  @Test
+  public void http2ErrorRoundTrip() {
+    for (Http2Error error : Http2Error.values()) {
+      assertSame(error, Http2Error.forCode(error.code()));
+    }
+  }
+
+  @Test
+  public void http2ErrorStatus() {
+    // Nothing special about this particular error, except that it is slightly distinctive.
+    assertSame(Status.Code.CANCELLED, Http2Error.CANCEL.status().getCode());
+  }
+
+  @Test
+  public void http2ErrorStatusForCode() {
+    assertSame(Status.Code.INTERNAL, Http2Error.statusForCode(-1).getCode());
+    assertSame(Http2Error.NO_ERROR.status(), Http2Error.statusForCode(0));
+    assertSame(Http2Error.HTTP_1_1_REQUIRED.status(), Http2Error.statusForCode(0xD));
+    assertSame(Status.Code.INTERNAL, Http2Error.statusForCode(0xD + 1).getCode());
+  }
+
+  @Test
+  public void timeoutTest() {
+    GrpcUtil.TimeoutMarshaller marshaller =
+            new GrpcUtil.TimeoutMarshaller();
+    // nanos
+    assertEquals("0n", marshaller.toAsciiString(0L));
+    assertEquals(0L, (long) marshaller.parseAsciiString("0n"));
+
+    assertEquals("99999999n", marshaller.toAsciiString(99999999L));
+    assertEquals(99999999L, (long) marshaller.parseAsciiString("99999999n"));
+
+    // micros
+    assertEquals("100000u", marshaller.toAsciiString(100000000L));
+    assertEquals(100000000L, (long) marshaller.parseAsciiString("100000u"));
+
+    assertEquals("99999999u", marshaller.toAsciiString(99999999999L));
+    assertEquals(99999999000L, (long) marshaller.parseAsciiString("99999999u"));
+
+    // millis
+    assertEquals("100000m", marshaller.toAsciiString(100000000000L));
+    assertEquals(100000000000L, (long) marshaller.parseAsciiString("100000m"));
+
+    assertEquals("99999999m", marshaller.toAsciiString(99999999999999L));
+    assertEquals(99999999000000L, (long) marshaller.parseAsciiString("99999999m"));
+
+    // seconds
+    assertEquals("100000S", marshaller.toAsciiString(100000000000000L));
+    assertEquals(100000000000000L, (long) marshaller.parseAsciiString("100000S"));
+
+    assertEquals("99999999S", marshaller.toAsciiString(99999999999999999L));
+    assertEquals(99999999000000000L, (long) marshaller.parseAsciiString("99999999S"));
+
+    // minutes
+    assertEquals("1666666M", marshaller.toAsciiString(100000000000000000L));
+    assertEquals(99999960000000000L, (long) marshaller.parseAsciiString("1666666M"));
+
+    assertEquals("99999999M", marshaller.toAsciiString(5999999999999999999L));
+    assertEquals(5999999940000000000L, (long) marshaller.parseAsciiString("99999999M"));
+
+    // hours
+    assertEquals("1666666H", marshaller.toAsciiString(6000000000000000000L));
+    assertEquals(5999997600000000000L, (long) marshaller.parseAsciiString("1666666H"));
+
+    assertEquals("2562047H", marshaller.toAsciiString(Long.MAX_VALUE));
+    assertEquals(9223369200000000000L, (long) marshaller.parseAsciiString("2562047H"));
+
+    assertEquals(Long.MAX_VALUE, (long) marshaller.parseAsciiString("2562048H"));
+  }
+
+  @Test
+  public void grpcUserAgent() {
+    assertTrue(GrpcUtil.getGrpcUserAgent("netty", null).startsWith("grpc-java-netty/"));
+    assertTrue(GrpcUtil.getGrpcUserAgent("okhttp", "libfoo/1.0")
+        .startsWith("libfoo/1.0 grpc-java-okhttp"));
+  }
+
+  @Test
+  public void contentTypeShouldBeValid() {
+    assertTrue(GrpcUtil.isGrpcContentType(GrpcUtil.CONTENT_TYPE_GRPC));
+    assertTrue(GrpcUtil.isGrpcContentType(GrpcUtil.CONTENT_TYPE_GRPC + "+blaa"));
+    assertTrue(GrpcUtil.isGrpcContentType(GrpcUtil.CONTENT_TYPE_GRPC + ";blaa"));
+  }
+
+  @Test
+  public void contentTypeShouldNotBeValid() {
+    assertFalse(GrpcUtil.isGrpcContentType("application/bad"));
+  }
+
+  @Test
+  public void checkAuthority_failsOnNull() {
+    thrown.expect(NullPointerException.class);
+
+    GrpcUtil.checkAuthority(null);
+  }
+
+  @Test
+  public void checkAuthority_succeedsOnHostAndPort() {
+    String actual = GrpcUtil.checkAuthority("valid:1234");
+
+    assertEquals("valid:1234", actual);
+  }
+
+  @Test
+  public void checkAuthority_succeedsOnHost() {
+    String actual = GrpcUtil.checkAuthority("valid");
+
+    assertEquals("valid", actual);
+  }
+
+  @Test
+  public void checkAuthority_succeedsOnIpV6() {
+    String actual = GrpcUtil.checkAuthority("[::1]");
+
+    assertEquals("[::1]", actual);
+  }
+
+  @Test
+  public void checkAuthority_failsOnInvalidAuthority() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Invalid authority");
+
+    GrpcUtil.checkAuthority("[ : : 1]");
+  }
+
+  @Test
+  public void checkAuthority_failsOnInvalidHost() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("No host in authority");
+
+    GrpcUtil.checkAuthority("bad_host");
+  }
+
+  @Test
+  public void checkAuthority_userInfoNotAllowed() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Userinfo");
+
+    GrpcUtil.checkAuthority("foo@valid");
+  }
+
+  @Test
+  public void httpStatusToGrpcStatus_messageContainsHttpStatus() {
+    assertTrue(GrpcUtil.httpStatusToGrpcStatus(500).getDescription().contains("500"));
+  }
+
+  @Test
+  public void httpStatusToGrpcStatus_checkAgainstSpec() {
+    assertEquals(Status.Code.INTERNAL, GrpcUtil.httpStatusToGrpcStatus(400).getCode());
+    assertEquals(Status.Code.UNAUTHENTICATED, GrpcUtil.httpStatusToGrpcStatus(401).getCode());
+    assertEquals(Status.Code.PERMISSION_DENIED, GrpcUtil.httpStatusToGrpcStatus(403).getCode());
+    assertEquals(Status.Code.UNIMPLEMENTED, GrpcUtil.httpStatusToGrpcStatus(404).getCode());
+    assertEquals(Status.Code.UNAVAILABLE, GrpcUtil.httpStatusToGrpcStatus(429).getCode());
+    assertEquals(Status.Code.UNAVAILABLE, GrpcUtil.httpStatusToGrpcStatus(502).getCode());
+    assertEquals(Status.Code.UNAVAILABLE, GrpcUtil.httpStatusToGrpcStatus(503).getCode());
+    assertEquals(Status.Code.UNAVAILABLE, GrpcUtil.httpStatusToGrpcStatus(504).getCode());
+    // Some other code
+    assertEquals(Status.Code.UNKNOWN, GrpcUtil.httpStatusToGrpcStatus(500).getCode());
+
+    // If transport is doing it's job, 1xx should never happen. But it may not do its job.
+    assertEquals(Status.Code.INTERNAL, GrpcUtil.httpStatusToGrpcStatus(100).getCode());
+    assertEquals(Status.Code.INTERNAL, GrpcUtil.httpStatusToGrpcStatus(101).getCode());
+  }
+
+  @Test
+  public void httpStatusToGrpcStatus_neverOk() {
+    for (int i = -1; i < 800; i++) {
+      assertFalse(GrpcUtil.httpStatusToGrpcStatus(i).isOk());
+    }
+  }
+
+  @Test
+  public void getTransportFromPickResult_errorPickResult_waitForReady() {
+    Status status = Status.UNAVAILABLE;
+    PickResult pickResult = PickResult.withError(status);
+    ClientTransport transport = GrpcUtil.getTransportFromPickResult(pickResult, true);
+
+    assertNull(transport);
+  }
+
+  @Test
+  public void getTransportFromPickResult_errorPickResult_failFast() {
+    Status status = Status.UNAVAILABLE;
+    PickResult pickResult = PickResult.withError(status);
+    ClientTransport transport = GrpcUtil.getTransportFromPickResult(pickResult, false);
+
+    assertNotNull(transport);
+
+    ClientStream stream = transport
+        .newStream(TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT);
+    ClientStreamListener listener = mock(ClientStreamListener.class);
+    stream.start(listener);
+
+    verify(listener).closed(eq(status), eq(RpcProgress.PROCESSED), any(Metadata.class));
+  }
+
+  @Test
+  public void getTransportFromPickResult_dropPickResult_waitForReady() {
+    Status status = Status.UNAVAILABLE;
+    PickResult pickResult = PickResult.withDrop(status);
+    ClientTransport transport = GrpcUtil.getTransportFromPickResult(pickResult, true);
+
+    assertNotNull(transport);
+
+    ClientStream stream = transport
+        .newStream(TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT);
+    ClientStreamListener listener = mock(ClientStreamListener.class);
+    stream.start(listener);
+
+    verify(listener).closed(eq(status), eq(RpcProgress.DROPPED), any(Metadata.class));
+  }
+
+  @Test
+  public void getTransportFromPickResult_dropPickResult_failFast() {
+    Status status = Status.UNAVAILABLE;
+    PickResult pickResult = PickResult.withDrop(status);
+    ClientTransport transport = GrpcUtil.getTransportFromPickResult(pickResult, false);
+
+    assertNotNull(transport);
+
+    ClientStream stream = transport
+        .newStream(TestMethodDescriptors.voidMethod(), new Metadata(), CallOptions.DEFAULT);
+    ClientStreamListener listener = mock(ClientStreamListener.class);
+    stream.start(listener);
+
+    verify(listener).closed(eq(status), eq(RpcProgress.DROPPED), any(Metadata.class));
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/GzipInflatingBufferTest.java b/core/src/test/java/io/grpc/internal/GzipInflatingBufferTest.java
new file mode 100644
index 0000000..06d431d
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/GzipInflatingBufferTest.java
@@ -0,0 +1,762 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.io.ByteStreams;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.zip.CRC32;
+import java.util.zip.DataFormatException;
+import java.util.zip.GZIPOutputStream;
+import java.util.zip.ZipException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link GzipInflatingBuffer}. */
+@RunWith(JUnit4.class)
+public class GzipInflatingBufferTest {
+  private static final String UNCOMPRESSABLE_FILE = "/io/grpc/internal/uncompressable.bin";
+
+  private static final int GZIP_HEADER_MIN_SIZE = 10;
+  private static final int GZIP_TRAILER_SIZE = 8;
+  private static final int GZIP_HEADER_FLAG_INDEX = 3;
+
+  public static final int GZIP_MAGIC = 0x8b1f;
+
+  private static final int FTEXT = 1;
+  private static final int FHCRC = 2;
+  private static final int FEXTRA = 4;
+  private static final int FNAME = 8;
+  private static final int FCOMMENT = 16;
+
+  private static final int TRUNCATED_DATA_SIZE = 10;
+
+  private byte[] originalData;
+  private byte[] gzippedData;
+  private byte[] gzipHeader;
+  private byte[] deflatedBytes;
+  private byte[] gzipTrailer;
+  private byte[] truncatedData;
+  private byte[] gzippedTruncatedData;
+
+  private GzipInflatingBuffer gzipInflatingBuffer;
+
+  @Before
+  public void setUp() {
+    gzipInflatingBuffer = new GzipInflatingBuffer();
+    try {
+      originalData = ByteStreams.toByteArray(getClass().getResourceAsStream(UNCOMPRESSABLE_FILE));
+      truncatedData = Arrays.copyOf(originalData, TRUNCATED_DATA_SIZE);
+
+      ByteArrayOutputStream gzippedOutputStream = new ByteArrayOutputStream();
+      OutputStream gzippingOutputStream = new GZIPOutputStream(gzippedOutputStream);
+      gzippingOutputStream.write(originalData);
+      gzippingOutputStream.close();
+      gzippedData = gzippedOutputStream.toByteArray();
+      gzippedOutputStream.close();
+
+      gzipHeader = Arrays.copyOf(gzippedData, GZIP_HEADER_MIN_SIZE);
+      deflatedBytes =
+          Arrays.copyOfRange(
+              gzippedData, GZIP_HEADER_MIN_SIZE, gzippedData.length - GZIP_TRAILER_SIZE);
+      gzipTrailer =
+          Arrays.copyOfRange(
+              gzippedData, gzippedData.length - GZIP_TRAILER_SIZE, gzippedData.length);
+
+      ByteArrayOutputStream truncatedGzippedOutputStream = new ByteArrayOutputStream();
+      OutputStream smallerGzipCompressingStream =
+          new GZIPOutputStream(truncatedGzippedOutputStream);
+      smallerGzipCompressingStream.write(truncatedData);
+      smallerGzipCompressingStream.close();
+      gzippedTruncatedData = truncatedGzippedOutputStream.toByteArray();
+      truncatedGzippedOutputStream.close();
+    } catch (Exception e) {
+      throw new RuntimeException("Failed to set up compressed data", e);
+    }
+  }
+
+  @After
+  public void tearDown() {
+    gzipInflatingBuffer.close();
+  }
+
+  @Test
+  public void gzipInflateWorks() throws Exception {
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+  }
+
+  @Test
+  public void splitGzipStreamWorks() throws Exception {
+    int initialBytes = 100;
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData, 0, initialBytes));
+
+    byte[] b = new byte[originalData.length];
+    int n = gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
+    assertTrue("inflated bytes expected", n > 0);
+    assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
+    assertEquals(initialBytes, gzipInflatingBuffer.getAndResetBytesConsumed());
+
+    gzipInflatingBuffer.addGzippedBytes(
+        ReadableBuffers.wrap(gzippedData, initialBytes, gzippedData.length - initialBytes));
+    int bytesRemaining = originalData.length - n;
+    assertEquals(bytesRemaining, gzipInflatingBuffer.inflateBytes(b, n, bytesRemaining));
+    assertEquals(gzippedData.length - initialBytes, gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+  }
+
+  @Test
+  public void inflateBytesObeysOffsetAndLength() throws Exception {
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
+
+    int offset = 10;
+    int length = 100;
+    byte[] b = new byte[offset + length + offset];
+    assertEquals(length, gzipInflatingBuffer.inflateBytes(b, offset, length));
+    assertTrue(
+        "bytes written before offset",
+        Arrays.equals(new byte[offset], Arrays.copyOfRange(b, 0, offset)));
+    assertTrue(
+        "inflated data does not match",
+        Arrays.equals(
+            Arrays.copyOfRange(originalData, 0, length),
+            Arrays.copyOfRange(b, offset, offset + length)));
+    assertTrue(
+        "bytes written beyond length",
+        Arrays.equals(
+            new byte[offset], Arrays.copyOfRange(b, offset + length, offset + length + offset)));
+  }
+
+  @Test
+  public void concatenatedStreamsWorks() throws Exception {
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData));
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+
+    assertEquals(
+        truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length));
+    assertEquals(gzippedTruncatedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+
+    assertEquals(
+        truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length));
+    assertEquals(gzippedTruncatedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+  }
+
+  @Test
+  public void requestingTooManyBytesStillReturnsEndOfBlock() throws Exception {
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
+
+    int len = 2 * originalData.length;
+    byte[] b = new byte[len];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, len));
+    assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue(gzipInflatingBuffer.isStalled());
+    assertTrue(
+        "inflated data does not match",
+        Arrays.equals(originalData, Arrays.copyOf(b, originalData.length)));
+  }
+
+  @Test
+  public void closeStopsDecompression() throws Exception {
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
+
+    byte[] b = new byte[1];
+    gzipInflatingBuffer.inflateBytes(b, 0, 1);
+    gzipInflatingBuffer.close();
+    try {
+      gzipInflatingBuffer.inflateBytes(b, 0, 1);
+      fail("Expected IllegalStateException");
+    } catch (IllegalStateException expectedException) {
+      assertEquals("GzipInflatingBuffer is closed", expectedException.getMessage());
+    }
+  }
+
+  @Test
+  public void isStalledReturnsTrueAtEndOfStream() throws Exception {
+    int bytesToWithhold = 10;
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
+
+    byte[] b = new byte[originalData.length];
+    gzipInflatingBuffer.inflateBytes(b, 0, originalData.length - bytesToWithhold);
+    assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled());
+
+    gzipInflatingBuffer.inflateBytes(b, originalData.length - bytesToWithhold, bytesToWithhold);
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+    assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
+  }
+
+  @Test
+  public void isStalledReturnsFalseBetweenStreams() throws Exception {
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+    assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled());
+
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+    assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
+  }
+
+  @Test
+  public void isStalledReturnsFalseBetweenSmallStreams() throws Exception {
+    // Use small streams to make sure that they all fit in the inflater buffer
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData));
+
+    byte[] b = new byte[truncatedData.length];
+    assertEquals(
+        truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length));
+    assertTrue("inflated data does not match", Arrays.equals(truncatedData, b));
+    assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled());
+
+    assertEquals(
+        truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length));
+    assertTrue("inflated data does not match", Arrays.equals(truncatedData, b));
+    assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
+  }
+
+  @Test
+  public void isStalledReturnsTrueWithPartialNextHeaderAvailable() throws Exception {
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(new byte[1]));
+
+    byte[] b = new byte[truncatedData.length];
+    assertEquals(
+        truncatedData.length, gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length));
+    assertTrue("inflated data does not match", Arrays.equals(truncatedData, b));
+    assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
+    assertTrue("partial data expected", gzipInflatingBuffer.hasPartialData());
+  }
+
+  @Test
+  public void isStalledWorksWithAllHeaderFlags() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] =
+        (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FTEXT | FHCRC | FEXTRA | FNAME | FCOMMENT);
+    int len = 1025;
+    byte[] fExtraLen = {(byte) len, (byte) (len >> 8)};
+    byte[] fExtra = new byte[len];
+    byte[] zeroTerminatedBytes = new byte[len];
+    for (int i = 0; i < len - 1; i++) {
+      zeroTerminatedBytes[i] = 1;
+    }
+    ByteArrayOutputStream newHeader = new ByteArrayOutputStream();
+    newHeader.write(gzipHeader);
+    newHeader.write(fExtraLen);
+    newHeader.write(fExtra);
+    newHeader.write(zeroTerminatedBytes); // FNAME
+    newHeader.write(zeroTerminatedBytes); // FCOMMENT
+    byte[] headerCrc16 = getHeaderCrc16Bytes(newHeader.toByteArray());
+
+    assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
+
+    addInTwoChunksAndVerifyIsStalled(gzipHeader);
+    addInTwoChunksAndVerifyIsStalled(fExtraLen);
+    addInTwoChunksAndVerifyIsStalled(fExtra);
+    addInTwoChunksAndVerifyIsStalled(zeroTerminatedBytes);
+    addInTwoChunksAndVerifyIsStalled(zeroTerminatedBytes);
+    addInTwoChunksAndVerifyIsStalled(headerCrc16);
+
+    byte[] b = new byte[originalData.length];
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+
+    addInTwoChunksAndVerifyIsStalled(gzipTrailer);
+  }
+
+  @Test
+  public void hasPartialData() throws Exception {
+    assertFalse("no partial data expected", gzipInflatingBuffer.hasPartialData());
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(new byte[1]));
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+    assertTrue("partial data expected", gzipInflatingBuffer.hasPartialData());
+  }
+
+  @Test
+  public void hasPartialDataWithoutGzipTrailer() throws Exception {
+    assertFalse("no partial data expected", gzipInflatingBuffer.hasPartialData());
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+    assertTrue("partial data expected", gzipInflatingBuffer.hasPartialData());
+  }
+
+  @Test
+  public void inflatingCompleteGzipStreamConsumesTrailer() throws Exception {
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+    assertFalse("no partial data expected", gzipInflatingBuffer.hasPartialData());
+  }
+
+  @Test
+  public void bytesConsumedForPartiallyInflatedBlock() throws Exception {
+    int bytesToWithhold = 1;
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedTruncatedData));
+
+    byte[] b = new byte[truncatedData.length];
+    assertEquals(
+        truncatedData.length - bytesToWithhold,
+        gzipInflatingBuffer.inflateBytes(b, 0, truncatedData.length - bytesToWithhold));
+    assertEquals(
+        gzippedTruncatedData.length - bytesToWithhold - GZIP_TRAILER_SIZE,
+        gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertEquals(
+        bytesToWithhold,
+        gzipInflatingBuffer.inflateBytes(
+            b, truncatedData.length - bytesToWithhold, bytesToWithhold));
+    assertEquals(
+        bytesToWithhold + GZIP_TRAILER_SIZE, gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue("inflated data does not match", Arrays.equals(truncatedData, b));
+  }
+
+  @Test
+  public void getAndResetCompressedBytesConsumedReportsHeaderFlagBytes() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] =
+        (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FTEXT | FHCRC | FEXTRA | FNAME | FCOMMENT);
+    int len = 1025;
+    byte[] fExtraLen = {(byte) len, (byte) (len >> 8)};
+    byte[] fExtra = new byte[len];
+    byte[] zeroTerminatedBytes = new byte[len];
+    for (int i = 0; i < len - 1; i++) {
+      zeroTerminatedBytes[i] = 1;
+    }
+    ByteArrayOutputStream newHeader = new ByteArrayOutputStream();
+    newHeader.write(gzipHeader);
+    newHeader.write(fExtraLen);
+    newHeader.write(fExtra);
+    newHeader.write(zeroTerminatedBytes); // FNAME
+    newHeader.write(zeroTerminatedBytes); // FCOMMENT
+    byte[] headerCrc16 = getHeaderCrc16Bytes(newHeader.toByteArray());
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
+    assertEquals(0, gzipInflatingBuffer.getAndResetBytesConsumed());
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
+    assertEquals(gzipHeader.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtraLen));
+    assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
+    assertEquals(fExtraLen.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtra));
+    assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
+    assertEquals(fExtra.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(zeroTerminatedBytes));
+    assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
+    assertEquals(zeroTerminatedBytes.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(zeroTerminatedBytes));
+    assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
+    assertEquals(zeroTerminatedBytes.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(headerCrc16));
+    assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
+    assertEquals(headerCrc16.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertEquals(deflatedBytes.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+    assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
+    assertEquals(gzipTrailer.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+  }
+
+  @Test
+  public void getAndResetDeflatedBytesConsumedExcludesGzipMetadata() throws Exception {
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzippedData));
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertEquals(
+        gzippedData.length - GZIP_HEADER_MIN_SIZE - GZIP_TRAILER_SIZE,
+        gzipInflatingBuffer.getAndResetDeflatedBytesConsumed());
+  }
+
+  @Test
+  public void wrongHeaderMagicShouldFail() throws Exception {
+    gzipHeader[1] = (byte) ~(GZIP_MAGIC >> 8);
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    try {
+      byte[] b = new byte[1];
+      gzipInflatingBuffer.inflateBytes(b, 0, 1);
+      fail("Expected ZipException");
+    } catch (ZipException expectedException) {
+      assertEquals("Not in GZIP format", expectedException.getMessage());
+    }
+  }
+
+  @Test
+  public void wrongHeaderCompressionMethodShouldFail() throws Exception {
+    gzipHeader[2] = 7; // Should be 8
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    try {
+      byte[] b = new byte[1];
+      gzipInflatingBuffer.inflateBytes(b, 0, 1);
+      fail("Expected ZipException");
+    } catch (ZipException expectedException) {
+      assertEquals("Unsupported compression method", expectedException.getMessage());
+    }
+  }
+
+  @Test
+  public void allHeaderFlagsWork() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] =
+        (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FTEXT | FHCRC | FEXTRA | FNAME | FCOMMENT);
+    int len = 1025;
+    byte[] fExtraLen = {(byte) len, (byte) (len >> 8)};
+    byte[] fExtra = new byte[len];
+    byte[] zeroTerminatedBytes = new byte[len];
+    for (int i = 0; i < len - 1; i++) {
+      zeroTerminatedBytes[i] = 1;
+    }
+    ByteArrayOutputStream newHeader = new ByteArrayOutputStream();
+    newHeader.write(gzipHeader);
+    newHeader.write(fExtraLen);
+    newHeader.write(fExtra);
+    newHeader.write(zeroTerminatedBytes); // FNAME
+    newHeader.write(zeroTerminatedBytes); // FCOMMENT
+    byte[] headerCrc16 = getHeaderCrc16Bytes(newHeader.toByteArray());
+    newHeader.write(headerCrc16);
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(newHeader.toByteArray()));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+
+    byte[] b = new byte[originalData.length];
+    gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+  }
+
+  @Test
+  public void headerFTextFlagIsIgnored() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FTEXT);
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertEquals(gzippedData.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+  }
+
+  @Test
+  public void headerFhcrcFlagWorks() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FHCRC);
+
+    byte[] headerCrc16 = getHeaderCrc16Bytes(gzipHeader);
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(headerCrc16));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertEquals(
+        gzippedData.length + headerCrc16.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+  }
+
+  @Test
+  public void headerInvalidFhcrcFlagFails() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FHCRC);
+
+    byte[] headerCrc16 = getHeaderCrc16Bytes(gzipHeader);
+    headerCrc16[0] = (byte) ~headerCrc16[0];
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(headerCrc16));
+    try {
+      byte[] b = new byte[1];
+      gzipInflatingBuffer.inflateBytes(b, 0, 1);
+      fail("Expected ZipException");
+    } catch (ZipException expectedException) {
+      assertEquals("Corrupt GZIP header", expectedException.getMessage());
+    }
+  }
+
+  @Test
+  public void headerFExtraFlagWorks() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FEXTRA);
+
+    int len = 1025;
+    byte[] fExtraLen = {(byte) len, (byte) (len >> 8)};
+    byte[] fExtra = new byte[len];
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtraLen));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtra));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertEquals(
+        gzippedData.length + fExtraLen.length + fExtra.length,
+        gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+  }
+
+  @Test
+  public void headerFExtraFlagWithZeroLenWorks() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FEXTRA);
+    byte[] fExtraLen = new byte[2];
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtraLen));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertEquals(
+        gzippedData.length + fExtraLen.length, gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+  }
+
+  @Test
+  public void headerFExtraFlagWithMissingExtraLenFails() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FEXTRA);
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+
+    try {
+      byte[] b = new byte[originalData.length];
+      gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
+      fail("Expected DataFormatException");
+    } catch (DataFormatException expectedException) {
+      assertTrue(
+          "wrong exception message",
+          expectedException.getMessage().startsWith("Inflater data format exception:"));
+    }
+  }
+
+  @Test
+  public void headerFExtraFlagWithMissingExtraBytesFails() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FEXTRA);
+
+    int len = 5;
+    byte[] fExtraLen = {(byte) len, (byte) (len >> 8)};
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(fExtraLen));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+
+    try {
+      byte[] b = new byte[originalData.length];
+      gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
+      fail("Expected DataFormatException");
+    } catch (DataFormatException expectedException) {
+      assertTrue(
+          "wrong exception message",
+          expectedException.getMessage().startsWith("Inflater data format exception:"));
+    }
+  }
+
+  @Test
+  public void headerFNameFlagWorks() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FNAME);
+    int len = 1025;
+    byte[] zeroTerminatedBytes = new byte[len];
+    for (int i = 0; i < len - 1; i++) {
+      zeroTerminatedBytes[i] = 1;
+    }
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(zeroTerminatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertEquals(gzippedData.length + len, gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+  }
+
+  @Test
+  public void headerFNameFlagWithMissingBytesFail() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FNAME);
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+
+    try {
+      byte[] b = new byte[originalData.length];
+      gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
+      fail("Expected DataFormatException");
+    } catch (DataFormatException expectedException) {
+      assertTrue(
+          "wrong exception message",
+          expectedException.getMessage().startsWith("Inflater data format exception:"));
+    }
+  }
+
+  @Test
+  public void headerFCommentFlagWorks() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FCOMMENT);
+    int len = 1025;
+    byte[] zeroTerminatedBytes = new byte[len];
+    for (int i = 0; i < len - 1; i++) {
+      zeroTerminatedBytes[i] = 1;
+    }
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(zeroTerminatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+
+    byte[] b = new byte[originalData.length];
+    assertEquals(originalData.length, gzipInflatingBuffer.inflateBytes(b, 0, originalData.length));
+    assertEquals(gzippedData.length + len, gzipInflatingBuffer.getAndResetBytesConsumed());
+    assertTrue("inflated data does not match", Arrays.equals(originalData, b));
+  }
+
+  @Test
+  public void headerFCommentFlagWithMissingBytesFail() throws Exception {
+    gzipHeader[GZIP_HEADER_FLAG_INDEX] = (byte) (gzipHeader[GZIP_HEADER_FLAG_INDEX] | FCOMMENT);
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+    try {
+      byte[] b = new byte[originalData.length];
+      gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
+      fail("Expected DataFormatException");
+    } catch (DataFormatException expectedException) {
+      assertTrue(
+          "wrong exception message",
+          expectedException.getMessage().startsWith("Inflater data format exception:"));
+    }
+  }
+
+  @Test
+  public void wrongTrailerCrcShouldFail() throws Exception {
+    gzipTrailer[0] = (byte) ~gzipTrailer[0];
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+
+    try {
+      byte[] b = new byte[originalData.length];
+      gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
+      fail("Expected ZipException");
+    } catch (ZipException expectedException) {
+      assertEquals("Corrupt GZIP trailer", expectedException.getMessage());
+    }
+  }
+
+  @Test
+  public void wrongTrailerISizeShouldFail() throws Exception {
+    gzipTrailer[GZIP_TRAILER_SIZE - 1] = (byte) ~gzipTrailer[GZIP_TRAILER_SIZE - 1];
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(deflatedBytes));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipTrailer));
+
+    try {
+      byte[] b = new byte[originalData.length];
+      gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
+      fail("Expected ZipException");
+    } catch (ZipException expectedException) {
+      assertEquals("Corrupt GZIP trailer", expectedException.getMessage());
+    }
+  }
+
+  @Test
+  public void invalidDeflateBlockShouldFail() throws Exception {
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(gzipHeader));
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(new byte[10]));
+
+    try {
+      byte[] b = new byte[originalData.length];
+      gzipInflatingBuffer.inflateBytes(b, 0, originalData.length);
+      fail("Expected DataFormatException");
+    } catch (DataFormatException expectedException) {
+      assertTrue(
+          "wrong exception message",
+          expectedException.getMessage().startsWith("Inflater data format exception:"));
+    }
+  }
+
+  private void addInTwoChunksAndVerifyIsStalled(byte[] input) throws Exception {
+    byte[] b = new byte[1];
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(input, 0, input.length - 1));
+    assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled());
+
+    assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
+    assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
+
+    gzipInflatingBuffer.addGzippedBytes(ReadableBuffers.wrap(input, input.length - 1, 1));
+    assertFalse("gzipInflatingBuffer is stalled", gzipInflatingBuffer.isStalled());
+
+    assertEquals(0, gzipInflatingBuffer.inflateBytes(b, 0, 1));
+    assertTrue("gzipInflatingBuffer is not stalled", gzipInflatingBuffer.isStalled());
+  }
+
+  private byte[] getHeaderCrc16Bytes(byte[] headerBytes) {
+    CRC32 crc = new CRC32();
+    crc.update(headerBytes);
+    byte[] headerCrc16 = {(byte) crc.getValue(), (byte) (crc.getValue() >> 8)};
+    return headerCrc16;
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/HedgingPolicyTest.java b/core/src/test/java/io/grpc/internal/HedgingPolicyTest.java
new file mode 100644
index 0000000..37e82af
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/HedgingPolicyTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.internal.ServiceConfigInterceptor.HEDGING_POLICY_KEY;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.collect.ImmutableSet;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status.Code;
+import io.grpc.testing.TestMethodDescriptors;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+/** Unit tests for HedgingPolicy. */
+@RunWith(JUnit4.class)
+public class HedgingPolicyTest {
+  @Test
+  public void getHedgingPolicies() throws Exception {
+    BufferedReader reader = null;
+    try {
+      reader = new BufferedReader(new InputStreamReader(RetryPolicyTest.class.getResourceAsStream(
+          "/io/grpc/internal/test_hedging_service_config.json"), "UTF-8"));
+      StringBuilder sb = new StringBuilder();
+      String line;
+      while ((line = reader.readLine()) != null) {
+        sb.append(line).append('\n');
+      }
+      Object serviceConfigObj = JsonParser.parse(sb.toString());
+      assertTrue(serviceConfigObj instanceof Map);
+
+      @SuppressWarnings("unchecked")
+      Map<String, Object> serviceConfig = (Map<String, Object>) serviceConfigObj;
+
+      ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor(
+          /* retryEnabled = */ true, /* maxRetryAttemptsLimit = */ 3,
+          /* maxHedgedAttemptsLimit = */ 4);
+      serviceConfigInterceptor.handleUpdate(serviceConfig);
+
+      MethodDescriptor.Builder<Void, Void> builder = TestMethodDescriptors.voidMethod().toBuilder();
+
+      MethodDescriptor<Void, Void> method = builder.setFullMethodName("not/exist").build();
+      assertEquals(
+          HedgingPolicy.DEFAULT,
+          serviceConfigInterceptor.getHedgingPolicyFromConfig(method));
+
+      method = builder.setFullMethodName("not_exist/Foo1").build();
+      assertEquals(
+          HedgingPolicy.DEFAULT,
+          serviceConfigInterceptor.getHedgingPolicyFromConfig(method));
+
+      method = builder.setFullMethodName("SimpleService1/not_exist").build();
+
+      assertEquals(
+          new HedgingPolicy(
+              3,
+              TimeUnit.MILLISECONDS.toNanos(2100),
+              ImmutableSet.of(Code.UNAVAILABLE, Code.RESOURCE_EXHAUSTED)),
+          serviceConfigInterceptor.getHedgingPolicyFromConfig(method));
+
+      method = builder.setFullMethodName("SimpleService1/Foo1").build();
+      assertEquals(
+          new HedgingPolicy(
+              4,
+              TimeUnit.MILLISECONDS.toNanos(100),
+              ImmutableSet.of(Code.UNAVAILABLE)),
+          serviceConfigInterceptor.getHedgingPolicyFromConfig(method));
+
+      method = builder.setFullMethodName("SimpleService2/not_exist").build();
+      assertEquals(
+          HedgingPolicy.DEFAULT,
+          serviceConfigInterceptor.getHedgingPolicyFromConfig(method));
+
+      method = builder.setFullMethodName("SimpleService2/Foo2").build();
+      assertEquals(
+          new HedgingPolicy(
+              4,
+              TimeUnit.MILLISECONDS.toNanos(100),
+              ImmutableSet.of(Code.UNAVAILABLE)),
+          serviceConfigInterceptor.getHedgingPolicyFromConfig(method));
+    } finally {
+      if (reader != null) {
+        reader.close();
+      }
+    }
+  }
+
+  @Test
+  public void getRetryPolicies_hedgingDisabled() throws Exception {
+    Channel channel = mock(Channel.class);
+    ArgumentCaptor<CallOptions> callOptionsCap = ArgumentCaptor.forClass(CallOptions.class);
+    BufferedReader reader = null;
+    try {
+      reader = new BufferedReader(new InputStreamReader(RetryPolicyTest.class.getResourceAsStream(
+          "/io/grpc/internal/test_hedging_service_config.json"), "UTF-8"));
+      StringBuilder sb = new StringBuilder();
+      String line;
+      while ((line = reader.readLine()) != null) {
+        sb.append(line).append('\n');
+      }
+      Object serviceConfigObj = JsonParser.parse(sb.toString());
+      assertTrue(serviceConfigObj instanceof Map);
+
+      @SuppressWarnings("unchecked")
+      Map<String, Object> serviceConfig = (Map<String, Object>) serviceConfigObj;
+
+      ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor(
+          /* retryEnabled = */ false, /* maxRetryAttemptsLimit = */ 3,
+          /* maxHedgedAttemptsLimit = */ 4);
+      serviceConfigInterceptor.handleUpdate(serviceConfig);
+
+      MethodDescriptor.Builder<Void, Void> builder = TestMethodDescriptors.voidMethod().toBuilder();
+
+      MethodDescriptor<Void, Void> method =
+          builder.setFullMethodName("SimpleService1/Foo1").build();
+
+      serviceConfigInterceptor.interceptCall(method, CallOptions.DEFAULT, channel);
+      verify(channel).newCall(eq(method), callOptionsCap.capture());
+      assertThat(callOptionsCap.getValue().getOption(HEDGING_POLICY_KEY)).isNull();
+    } finally {
+      if (reader != null) {
+        reader.close();
+      }
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/Http2ClientStreamTransportStateTest.java b/core/src/test/java/io/grpc/internal/Http2ClientStreamTransportStateTest.java
new file mode 100644
index 0000000..3e4f4b9
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/Http2ClientStreamTransportStateTest.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Charsets.US_ASCII;
+import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED;
+import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.InternalMetadata;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.Status.Code;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/** Unit tests for {@link Http2ClientStreamTransportState}. */
+@RunWith(JUnit4.class)
+public class Http2ClientStreamTransportStateTest {
+
+  private final Metadata.Key<String> testStatusMashaller =
+      InternalMetadata.keyOf(":status", Metadata.ASCII_STRING_MARSHALLER);
+
+  private TransportTracer transportTracer;
+  @Mock private ClientStreamListener mockListener;
+  @Captor private ArgumentCaptor<Status> statusCaptor;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    transportTracer = new TransportTracer();
+
+    doAnswer(new Answer<Void>() {
+      @Override
+      public Void answer(InvocationOnMock invocation) throws Throwable {
+        StreamListener.MessageProducer producer =
+            (StreamListener.MessageProducer) invocation.getArguments()[0];
+        while (producer.next() != null) {}
+        return null;
+      }
+    }).when(mockListener).messagesAvailable(Matchers.<StreamListener.MessageProducer>any());
+  }
+
+  @Test
+  public void transportHeadersReceived_notifiesListener() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(testStatusMashaller, "200");
+    headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
+        "application/grpc");
+    state.transportHeadersReceived(headers);
+
+    verify(mockListener, never()).closed(any(Status.class), same(PROCESSED), any(Metadata.class));
+    verify(mockListener).headersRead(headers);
+  }
+
+  @Test
+  public void transportHeadersReceived_doesntRequire200() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(testStatusMashaller, "500");
+    headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
+        "application/grpc");
+    state.transportHeadersReceived(headers);
+
+    verify(mockListener, never()).closed(any(Status.class), same(PROCESSED), any(Metadata.class));
+    verify(mockListener).headersRead(headers);
+  }
+
+  @Test
+  public void transportHeadersReceived_noHttpStatus() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
+        "application/grpc");
+    state.transportHeadersReceived(headers);
+    state.transportDataReceived(ReadableBuffers.empty(), true);
+
+    verify(mockListener, never()).headersRead(any(Metadata.class));
+    verify(mockListener).closed(statusCaptor.capture(), same(PROCESSED), same(headers));
+    assertEquals(Code.INTERNAL, statusCaptor.getValue().getCode());
+  }
+
+  @Test
+  public void transportHeadersReceived_wrongContentType_200() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(testStatusMashaller, "200");
+    headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html");
+    state.transportHeadersReceived(headers);
+    state.transportDataReceived(ReadableBuffers.empty(), true);
+
+    verify(mockListener, never()).headersRead(any(Metadata.class));
+    verify(mockListener).closed(statusCaptor.capture(), same(PROCESSED), same(headers));
+    assertEquals(Code.UNKNOWN, statusCaptor.getValue().getCode());
+    assertTrue(statusCaptor.getValue().getDescription().contains("200"));
+  }
+
+  @Test
+  public void transportHeadersReceived_wrongContentType_401() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(testStatusMashaller, "401");
+    headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html");
+    state.transportHeadersReceived(headers);
+    state.transportDataReceived(ReadableBuffers.empty(), true);
+
+    verify(mockListener, never()).headersRead(any(Metadata.class));
+    verify(mockListener).closed(statusCaptor.capture(), same(PROCESSED), same(headers));
+    assertEquals(Code.UNAUTHENTICATED, statusCaptor.getValue().getCode());
+    assertTrue(statusCaptor.getValue().getDescription().contains("401"));
+    assertTrue(statusCaptor.getValue().getDescription().contains("text/html"));
+  }
+
+  @Test
+  public void transportHeadersReceived_handles_1xx() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+
+    Metadata infoHeaders = new Metadata();
+    infoHeaders.put(testStatusMashaller, "100");
+    state.transportHeadersReceived(infoHeaders);
+    Metadata infoHeaders2 = new Metadata();
+    infoHeaders2.put(testStatusMashaller, "101");
+    state.transportHeadersReceived(infoHeaders2);
+
+    Metadata headers = new Metadata();
+    headers.put(testStatusMashaller, "200");
+    headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
+        "application/grpc");
+    state.transportHeadersReceived(headers);
+
+    verify(mockListener, never()).closed(any(Status.class), same(PROCESSED), any(Metadata.class));
+    verify(mockListener).headersRead(headers);
+  }
+
+  @Test
+  public void transportHeadersReceived_twice() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(testStatusMashaller, "200");
+    headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
+        "application/grpc");
+    state.transportHeadersReceived(headers);
+    Metadata headersAgain = new Metadata();
+    state.transportHeadersReceived(headersAgain);
+    state.transportDataReceived(ReadableBuffers.empty(), true);
+
+    verify(mockListener).headersRead(headers);
+    verify(mockListener).closed(statusCaptor.capture(), same(PROCESSED), same(headersAgain));
+    assertEquals(Code.INTERNAL, statusCaptor.getValue().getCode());
+    assertTrue(statusCaptor.getValue().getDescription().contains("twice"));
+  }
+
+  @Test
+  public void transportHeadersReceived_unknownAndTwiceLogsSecondHeaders() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(testStatusMashaller, "200");
+    headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html");
+    state.transportHeadersReceived(headers);
+    Metadata headersAgain = new Metadata();
+    String testString = "This is a test";
+    headersAgain.put(Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER), testString);
+    state.transportHeadersReceived(headersAgain);
+    state.transportDataReceived(ReadableBuffers.empty(), true);
+
+    verify(mockListener, never()).headersRead(any(Metadata.class));
+    verify(mockListener).closed(statusCaptor.capture(), same(PROCESSED), same(headers));
+    assertEquals(Code.UNKNOWN, statusCaptor.getValue().getCode());
+    assertTrue(statusCaptor.getValue().getDescription().contains(testString));
+  }
+
+  @Test
+  public void transportDataReceived_noHeaderReceived() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    String testString = "This is a test";
+    state.transportDataReceived(ReadableBuffers.wrap(testString.getBytes(US_ASCII)), true);
+
+    verify(mockListener).closed(statusCaptor.capture(), same(PROCESSED), any(Metadata.class));
+    assertEquals(Code.INTERNAL, statusCaptor.getValue().getCode());
+  }
+
+  @Test
+  public void transportDataReceived_debugData() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(testStatusMashaller, "200");
+    headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER), "text/html");
+    state.transportHeadersReceived(headers);
+    String testString = "This is a test";
+    state.transportDataReceived(ReadableBuffers.wrap(testString.getBytes(US_ASCII)), true);
+
+    verify(mockListener).closed(statusCaptor.capture(), same(PROCESSED), same(headers));
+    assertTrue(statusCaptor.getValue().getDescription().contains(testString));
+  }
+
+  @Test
+  public void transportTrailersReceived_notifiesListener() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata trailers = new Metadata();
+    trailers.put(testStatusMashaller, "200");
+    trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
+        "application/grpc");
+    trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "0");
+    state.transportTrailersReceived(trailers);
+
+    verify(mockListener, never()).headersRead(any(Metadata.class));
+    verify(mockListener).closed(Status.OK, PROCESSED, trailers);
+  }
+
+  @Test
+  public void transportTrailersReceived_afterHeaders() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(testStatusMashaller, "200");
+    headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
+        "application/grpc");
+    state.transportHeadersReceived(headers);
+    Metadata trailers = new Metadata();
+    trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "0");
+    state.transportTrailersReceived(trailers);
+
+    verify(mockListener).headersRead(headers);
+    verify(mockListener).closed(Status.OK, PROCESSED, trailers);
+  }
+
+  @Test
+  public void transportTrailersReceived_observesStatus() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata trailers = new Metadata();
+    trailers.put(testStatusMashaller, "200");
+    trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
+        "application/grpc");
+    trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "1");
+    state.transportTrailersReceived(trailers);
+
+    verify(mockListener, never()).headersRead(any(Metadata.class));
+    verify(mockListener).closed(Status.CANCELLED, PROCESSED, trailers);
+  }
+
+  @Test
+  public void transportTrailersReceived_missingStatusUsesHttpStatus() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata trailers = new Metadata();
+    trailers.put(testStatusMashaller, "401");
+    trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
+        "application/grpc");
+    state.transportTrailersReceived(trailers);
+
+    verify(mockListener, never()).headersRead(any(Metadata.class));
+    verify(mockListener).closed(statusCaptor.capture(), same(PROCESSED), same(trailers));
+    assertEquals(Code.UNAUTHENTICATED, statusCaptor.getValue().getCode());
+    assertTrue(statusCaptor.getValue().getDescription().contains("401"));
+  }
+
+  @Test
+  public void transportTrailersReceived_missingHttpStatus() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata trailers = new Metadata();
+    trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
+        "application/grpc");
+    trailers.put(Metadata.Key.of("grpc-status", Metadata.ASCII_STRING_MARSHALLER), "0");
+    state.transportTrailersReceived(trailers);
+
+    verify(mockListener, never()).headersRead(any(Metadata.class));
+    verify(mockListener).closed(statusCaptor.capture(), same(PROCESSED), same(trailers));
+    assertEquals(Code.INTERNAL, statusCaptor.getValue().getCode());
+  }
+
+  @Test
+  public void transportTrailersReceived_missingStatusAndMissingHttpStatus() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata trailers = new Metadata();
+    trailers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
+        "application/grpc");
+    state.transportTrailersReceived(trailers);
+
+    verify(mockListener, never()).headersRead(any(Metadata.class));
+    verify(mockListener).closed(statusCaptor.capture(), same(PROCESSED), same(trailers));
+    assertEquals(Code.INTERNAL, statusCaptor.getValue().getCode());
+  }
+
+  @Test
+  public void transportTrailersReceived_missingStatusAfterHeadersIgnoresHttpStatus() {
+    BaseTransportState state = new BaseTransportState(transportTracer);
+    state.setListener(mockListener);
+    Metadata headers = new Metadata();
+    headers.put(testStatusMashaller, "200");
+    headers.put(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER),
+        "application/grpc");
+    state.transportHeadersReceived(headers);
+    Metadata trailers = new Metadata();
+    trailers.put(testStatusMashaller, "401");
+    state.transportTrailersReceived(trailers);
+
+    verify(mockListener).headersRead(headers);
+    verify(mockListener).closed(statusCaptor.capture(), same(PROCESSED), same(trailers));
+    assertEquals(Code.UNKNOWN, statusCaptor.getValue().getCode());
+  }
+
+  private static class BaseTransportState extends Http2ClientStreamTransportState {
+    public BaseTransportState(TransportTracer transportTracer) {
+      super(DEFAULT_MAX_MESSAGE_SIZE, StatsTraceContext.NOOP, transportTracer);
+    }
+
+    @Override
+    protected void http2ProcessingFailed(Status status, boolean stopDelivery, Metadata trailers) {
+      transportReportStatus(status, stopDelivery, trailers);
+    }
+
+    @Override
+    public void deframeFailed(Throwable cause) {}
+
+    @Override
+    public void bytesRead(int processedBytes) {}
+
+    @Override
+    public void runOnTransportThread(Runnable r) {
+      r.run();
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java b/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java
new file mode 100644
index 0000000..8319ddc
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/InternalSubchannelTest.java
@@ -0,0 +1,1136 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.ConnectivityState.CONNECTING;
+import static io.grpc.ConnectivityState.IDLE;
+import static io.grpc.ConnectivityState.READY;
+import static io.grpc.ConnectivityState.SHUTDOWN;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.Iterables;
+import io.grpc.Attributes;
+import io.grpc.ConnectivityStateInfo;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalWithLogId;
+import io.grpc.Status;
+import io.grpc.internal.InternalSubchannel.CallTracingTransport;
+import io.grpc.internal.InternalSubchannel.Index;
+import io.grpc.internal.TestUtils.MockClientTransportInfo;
+import java.net.SocketAddress;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link InternalSubchannel}.
+ */
+@RunWith(JUnit4.class)
+public class InternalSubchannelTest {
+
+  @Rule
+  public final ExpectedException thrown = ExpectedException.none();
+
+  private static final String AUTHORITY = "fakeauthority";
+  private static final String USER_AGENT = "mosaic";
+  private static final ConnectivityStateInfo UNAVAILABLE_STATE =
+      ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE);
+  private static final ConnectivityStateInfo RESOURCE_EXHAUSTED_STATE =
+      ConnectivityStateInfo.forTransientFailure(Status.RESOURCE_EXHAUSTED);
+  private static final Status SHUTDOWN_REASON = Status.UNAVAILABLE.withDescription("for test");
+
+  // For scheduled executor
+  private final FakeClock fakeClock = new FakeClock();
+  // For channelExecutor
+  private final FakeClock fakeExecutor = new FakeClock();
+  private final ChannelExecutor channelExecutor = new ChannelExecutor();
+
+  private final InternalChannelz channelz = new InternalChannelz();
+
+  @Mock private BackoffPolicy mockBackoffPolicy1;
+  @Mock private BackoffPolicy mockBackoffPolicy2;
+  @Mock private BackoffPolicy mockBackoffPolicy3;
+  @Mock private BackoffPolicy.Provider mockBackoffPolicyProvider;
+  @Mock private ClientTransportFactory mockTransportFactory;
+
+  private final LinkedList<String> callbackInvokes = new LinkedList<String>();
+  private final InternalSubchannel.Callback mockInternalSubchannelCallback =
+      new InternalSubchannel.Callback() {
+        @Override
+        protected void onTerminated(InternalSubchannel is) {
+          assertSame(internalSubchannel, is);
+          callbackInvokes.add("onTerminated");
+        }
+
+        @Override
+        protected void onStateChange(InternalSubchannel is, ConnectivityStateInfo newState) {
+          assertSame(internalSubchannel, is);
+          callbackInvokes.add("onStateChange:" + newState);
+        }
+
+        @Override
+        protected void onInUse(InternalSubchannel is) {
+          assertSame(internalSubchannel, is);
+          callbackInvokes.add("onInUse");
+        }
+
+        @Override
+        protected void onNotInUse(InternalSubchannel is) {
+          assertSame(internalSubchannel, is);
+          callbackInvokes.add("onNotInUse");
+        }
+      };
+
+  private InternalSubchannel internalSubchannel;
+  private BlockingQueue<MockClientTransportInfo> transports;
+
+  @Before public void setUp() {
+    MockitoAnnotations.initMocks(this);
+
+    when(mockBackoffPolicyProvider.get())
+        .thenReturn(mockBackoffPolicy1, mockBackoffPolicy2, mockBackoffPolicy3);
+    when(mockBackoffPolicy1.nextBackoffNanos()).thenReturn(10L, 100L);
+    when(mockBackoffPolicy2.nextBackoffNanos()).thenReturn(10L, 100L);
+    when(mockBackoffPolicy3.nextBackoffNanos()).thenReturn(10L, 100L);
+    transports = TestUtils.captureTransports(mockTransportFactory);
+  }
+
+  @After public void noMorePendingTasks() {
+    assertEquals(0, fakeClock.numPendingTasks());
+    assertEquals(0, fakeExecutor.numPendingTasks());
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void constructor_emptyEagList_throws() {
+    createInternalSubchannel(new EquivalentAddressGroup[0]);
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void constructor_eagListWithNull_throws() {
+    createInternalSubchannel(new EquivalentAddressGroup[] {null});
+  }
+
+  @Test public void eagAttribute_propagatesToTransport() {
+    SocketAddress addr = new SocketAddress() {};
+    Attributes attr = Attributes.newBuilder().set(Attributes.Key.create("some-key"), "1").build();
+    createInternalSubchannel(new EquivalentAddressGroup(Arrays.asList(addr), attr));
+
+    // First attempt
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertEquals(CONNECTING, internalSubchannel.getState());
+    verify(mockTransportFactory)
+        .newClientTransport(addr, createClientTransportOptions().setEagAttributes(attr));
+  }
+
+  @Test public void singleAddressReconnect() {
+    SocketAddress addr = mock(SocketAddress.class);
+    createInternalSubchannel(addr);
+    assertEquals(IDLE, internalSubchannel.getState());
+
+    // Invocation counters
+    int transportsCreated = 0;
+    int backoff1Consulted = 0;
+    int backoff2Consulted = 0;
+    int backoffReset = 0;
+
+    // First attempt
+    assertEquals(IDLE, internalSubchannel.getState());
+    assertNoCallbackInvoke();
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    assertEquals(CONNECTING, internalSubchannel.getState());
+    verify(mockTransportFactory, times(++transportsCreated))
+        .newClientTransport(addr, createClientTransportOptions());
+
+    // Fail this one. Because there is only one address to try, enter TRANSIENT_FAILURE.
+    assertNoCallbackInvoke();
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertEquals(TRANSIENT_FAILURE, internalSubchannel.getState());
+    assertExactCallbackInvokes("onStateChange:" + UNAVAILABLE_STATE);
+    // Backoff reset and using first back-off value interval
+    verify(mockBackoffPolicy1, times(++backoff1Consulted)).nextBackoffNanos();
+    verify(mockBackoffPolicyProvider, times(++backoffReset)).get();
+
+    // Second attempt
+    // Transport creation doesn't happen until time is due
+    fakeClock.forwardNanos(9);
+    assertNull(internalSubchannel.obtainActiveTransport());
+    verify(mockTransportFactory, times(transportsCreated))
+        .newClientTransport(addr, createClientTransportOptions());
+    assertEquals(TRANSIENT_FAILURE, internalSubchannel.getState());
+
+    assertNoCallbackInvoke();
+    fakeClock.forwardNanos(1);
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    assertEquals(CONNECTING, internalSubchannel.getState());
+    verify(mockTransportFactory, times(++transportsCreated))
+        .newClientTransport(addr, createClientTransportOptions());
+    // Fail this one too
+    assertNoCallbackInvoke();
+    // Here we use a different status from the first failure, and verify that it's passed to
+    // the callback.
+    transports.poll().listener.transportShutdown(Status.RESOURCE_EXHAUSTED);
+    assertEquals(TRANSIENT_FAILURE, internalSubchannel.getState());
+    assertExactCallbackInvokes("onStateChange:" + RESOURCE_EXHAUSTED_STATE);
+    // Second back-off interval
+    verify(mockBackoffPolicy1, times(++backoff1Consulted)).nextBackoffNanos();
+    verify(mockBackoffPolicyProvider, times(backoffReset)).get();
+
+    // Third attempt
+    // Transport creation doesn't happen until time is due
+    fakeClock.forwardNanos(99);
+    assertNull(internalSubchannel.obtainActiveTransport());
+    verify(mockTransportFactory, times(transportsCreated))
+        .newClientTransport(addr, createClientTransportOptions());
+    assertEquals(TRANSIENT_FAILURE, internalSubchannel.getState());
+    assertNoCallbackInvoke();
+    fakeClock.forwardNanos(1);
+    assertEquals(CONNECTING, internalSubchannel.getState());
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    assertNull(internalSubchannel.obtainActiveTransport());
+    verify(mockTransportFactory, times(++transportsCreated))
+        .newClientTransport(addr, createClientTransportOptions());
+    // Let this one succeed, will enter READY state.
+    assertNoCallbackInvoke();
+    transports.peek().listener.transportReady();
+    assertExactCallbackInvokes("onStateChange:READY");
+    assertEquals(READY, internalSubchannel.getState());
+    assertSame(
+        transports.peek().transport,
+        ((CallTracingTransport) internalSubchannel.obtainActiveTransport()).delegate());
+
+    // Close the READY transport, will enter IDLE state.
+    assertNoCallbackInvoke();
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertEquals(IDLE, internalSubchannel.getState());
+    assertExactCallbackInvokes("onStateChange:IDLE");
+
+    // Back-off is reset, and the next attempt will happen immediately
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertEquals(CONNECTING, internalSubchannel.getState());
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    verify(mockBackoffPolicyProvider, times(backoffReset)).get();
+    verify(mockTransportFactory, times(++transportsCreated))
+        .newClientTransport(addr, createClientTransportOptions());
+
+    // Final checks for consultations on back-off policies
+    verify(mockBackoffPolicy1, times(backoff1Consulted)).nextBackoffNanos();
+    verify(mockBackoffPolicy2, times(backoff2Consulted)).nextBackoffNanos();
+  }
+
+  @Test public void twoAddressesReconnect() {
+    SocketAddress addr1 = mock(SocketAddress.class);
+    SocketAddress addr2 = mock(SocketAddress.class);
+    createInternalSubchannel(addr1, addr2);
+    assertEquals(IDLE, internalSubchannel.getState());
+    // Invocation counters
+    int transportsAddr1 = 0;
+    int transportsAddr2 = 0;
+    int backoff1Consulted = 0;
+    int backoff2Consulted = 0;
+    int backoff3Consulted = 0;
+    int backoffReset = 0;
+
+    // First attempt
+    assertNoCallbackInvoke();
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    assertEquals(CONNECTING, internalSubchannel.getState());
+    verify(mockTransportFactory, times(++transportsAddr1))
+        .newClientTransport(addr1, createClientTransportOptions());
+
+    // Let this one fail without success
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    // Still in CONNECTING
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertNoCallbackInvoke();
+    assertEquals(CONNECTING, internalSubchannel.getState());
+
+    // Second attempt will start immediately. Still no back-off policy.
+    verify(mockBackoffPolicyProvider, times(backoffReset)).get();
+    verify(mockTransportFactory, times(++transportsAddr2))
+        .newClientTransport(addr2, createClientTransportOptions());
+    assertNull(internalSubchannel.obtainActiveTransport());
+    // Fail this one too
+    assertNoCallbackInvoke();
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    // All addresses have failed. Delayed transport will be in back-off interval.
+    assertEquals(TRANSIENT_FAILURE, internalSubchannel.getState());
+    assertExactCallbackInvokes("onStateChange:" + UNAVAILABLE_STATE);
+    // Backoff reset and first back-off interval begins
+    verify(mockBackoffPolicy1, times(++backoff1Consulted)).nextBackoffNanos();
+    verify(mockBackoffPolicyProvider, times(++backoffReset)).get();
+
+    // No reconnect during TRANSIENT_FAILURE even when requested.
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertNoCallbackInvoke();
+    assertEquals(TRANSIENT_FAILURE, internalSubchannel.getState());
+
+    // Third attempt is the first address, thus controlled by the first back-off interval.
+    fakeClock.forwardNanos(9);
+    verify(mockTransportFactory, times(transportsAddr1))
+        .newClientTransport(addr1, createClientTransportOptions());
+    assertEquals(TRANSIENT_FAILURE, internalSubchannel.getState());
+    assertNoCallbackInvoke();
+    fakeClock.forwardNanos(1);
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    assertEquals(CONNECTING, internalSubchannel.getState());
+    verify(mockTransportFactory, times(++transportsAddr1))
+        .newClientTransport(addr1, createClientTransportOptions());
+    // Fail this one too
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertEquals(CONNECTING, internalSubchannel.getState());
+
+    // Forth attempt will start immediately. Keep back-off policy.
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertEquals(CONNECTING, internalSubchannel.getState());
+    verify(mockBackoffPolicyProvider, times(backoffReset)).get();
+    verify(mockTransportFactory, times(++transportsAddr2))
+        .newClientTransport(addr2, createClientTransportOptions());
+    // Fail this one too
+    assertNoCallbackInvoke();
+    transports.poll().listener.transportShutdown(Status.RESOURCE_EXHAUSTED);
+    // All addresses have failed again. Delayed transport will be in back-off interval.
+    assertExactCallbackInvokes("onStateChange:" + RESOURCE_EXHAUSTED_STATE);
+    assertEquals(TRANSIENT_FAILURE, internalSubchannel.getState());
+    // Second back-off interval begins
+    verify(mockBackoffPolicy1, times(++backoff1Consulted)).nextBackoffNanos();
+    verify(mockBackoffPolicyProvider, times(backoffReset)).get();
+
+    // Fifth attempt for the first address, thus controlled by the second back-off interval.
+    assertEquals(TRANSIENT_FAILURE, internalSubchannel.getState());
+    fakeClock.forwardNanos(99);
+    verify(mockTransportFactory, times(transportsAddr1))
+        .newClientTransport(addr1, createClientTransportOptions());
+    assertEquals(TRANSIENT_FAILURE, internalSubchannel.getState());
+    assertNoCallbackInvoke();
+    fakeClock.forwardNanos(1);
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    assertEquals(CONNECTING, internalSubchannel.getState());
+    verify(mockTransportFactory, times(++transportsAddr1))
+        .newClientTransport(addr1, createClientTransportOptions());
+    // Let it through
+    assertNoCallbackInvoke();
+    transports.peek().listener.transportReady();
+    assertExactCallbackInvokes("onStateChange:READY");
+    assertEquals(READY, internalSubchannel.getState());
+
+    assertSame(
+        transports.peek().transport,
+        ((CallTracingTransport) internalSubchannel.obtainActiveTransport()).delegate());
+    // Then close it.
+    assertNoCallbackInvoke();
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertExactCallbackInvokes("onStateChange:IDLE");
+    assertEquals(IDLE, internalSubchannel.getState());
+
+    // First attempt after a successful connection. Old back-off policy should be ignored, but there
+    // is not yet a need for a new one. Start from the first address.
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertEquals(CONNECTING, internalSubchannel.getState());
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    verify(mockBackoffPolicyProvider, times(backoffReset)).get();
+    verify(mockTransportFactory, times(++transportsAddr1))
+        .newClientTransport(addr1, createClientTransportOptions());
+    // Fail the transport
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertEquals(CONNECTING, internalSubchannel.getState());
+
+    // Second attempt will start immediately. Still no new back-off policy.
+    verify(mockBackoffPolicyProvider, times(backoffReset)).get();
+    verify(mockTransportFactory, times(++transportsAddr2))
+        .newClientTransport(addr2, createClientTransportOptions());
+    // Fail this one too
+    assertEquals(CONNECTING, internalSubchannel.getState());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    // All addresses have failed. Enter TRANSIENT_FAILURE. Back-off in effect.
+    assertExactCallbackInvokes("onStateChange:" + UNAVAILABLE_STATE);
+    assertEquals(TRANSIENT_FAILURE, internalSubchannel.getState());
+    // Back-off reset and first back-off interval begins
+    verify(mockBackoffPolicy2, times(++backoff2Consulted)).nextBackoffNanos();
+    verify(mockBackoffPolicyProvider, times(++backoffReset)).get();
+
+    // Third attempt is the first address, thus controlled by the first back-off interval.
+    fakeClock.forwardNanos(9);
+    verify(mockTransportFactory, times(transportsAddr1))
+        .newClientTransport(addr1, createClientTransportOptions());
+    assertEquals(TRANSIENT_FAILURE, internalSubchannel.getState());
+    assertNoCallbackInvoke();
+    fakeClock.forwardNanos(1);
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    assertEquals(CONNECTING, internalSubchannel.getState());
+    verify(mockTransportFactory, times(++transportsAddr1))
+        .newClientTransport(addr1, createClientTransportOptions());
+
+    // Final checks on invocations on back-off policies
+    verify(mockBackoffPolicy1, times(backoff1Consulted)).nextBackoffNanos();
+    verify(mockBackoffPolicy2, times(backoff2Consulted)).nextBackoffNanos();
+    verify(mockBackoffPolicy3, times(backoff3Consulted)).nextBackoffNanos();
+  }
+
+  @Test
+  public void updateAddresses_emptyEagList_throws() {
+    SocketAddress addr = new FakeSocketAddress();
+    createInternalSubchannel(addr);
+    thrown.expect(IllegalArgumentException.class);
+    internalSubchannel.updateAddresses(Arrays.<EquivalentAddressGroup>asList());
+  }
+
+  @Test
+  public void updateAddresses_eagListWithNull_throws() {
+    SocketAddress addr = new FakeSocketAddress();
+    createInternalSubchannel(addr);
+    List<EquivalentAddressGroup> eags = Arrays.asList((EquivalentAddressGroup) null);
+    thrown.expect(NullPointerException.class);
+    internalSubchannel.updateAddresses(eags);
+  }
+
+  @Test public void updateAddresses_intersecting_ready() {
+    SocketAddress addr1 = mock(SocketAddress.class);
+    SocketAddress addr2 = mock(SocketAddress.class);
+    SocketAddress addr3 = mock(SocketAddress.class);
+    createInternalSubchannel(addr1, addr2);
+    assertEquals(IDLE, internalSubchannel.getState());
+
+    // First address fails
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    verify(mockTransportFactory).newClientTransport(addr1, createClientTransportOptions());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertEquals(CONNECTING, internalSubchannel.getState());
+
+    // Second address connects
+    verify(mockTransportFactory).newClientTransport(addr2, createClientTransportOptions());
+    transports.peek().listener.transportReady();
+    assertExactCallbackInvokes("onStateChange:READY");
+    assertEquals(READY, internalSubchannel.getState());
+
+    // Update addresses
+    internalSubchannel.updateAddresses(
+        Arrays.asList(new EquivalentAddressGroup(Arrays.asList(addr2, addr3))));
+    assertNoCallbackInvoke();
+    assertEquals(READY, internalSubchannel.getState());
+    verify(transports.peek().transport, never()).shutdown(any(Status.class));
+    verify(transports.peek().transport, never()).shutdownNow(any(Status.class));
+
+    // And new addresses chosen when re-connecting
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertExactCallbackInvokes("onStateChange:IDLE");
+
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(mockTransportFactory, times(2))
+        .newClientTransport(addr2, createClientTransportOptions());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    verify(mockTransportFactory).newClientTransport(addr3, createClientTransportOptions());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    verifyNoMoreInteractions(mockTransportFactory);
+
+    fakeClock.forwardNanos(10); // Drain retry, but don't care about result
+  }
+
+  @Test public void updateAddresses_intersecting_connecting() {
+    SocketAddress addr1 = mock(SocketAddress.class);
+    SocketAddress addr2 = mock(SocketAddress.class);
+    SocketAddress addr3 = mock(SocketAddress.class);
+    createInternalSubchannel(addr1, addr2);
+    assertEquals(IDLE, internalSubchannel.getState());
+
+    // First address fails
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    verify(mockTransportFactory).newClientTransport(addr1, createClientTransportOptions());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertEquals(CONNECTING, internalSubchannel.getState());
+
+    // Second address connecting
+    verify(mockTransportFactory).newClientTransport(addr2, createClientTransportOptions());
+    assertNoCallbackInvoke();
+    assertEquals(CONNECTING, internalSubchannel.getState());
+
+    // Update addresses
+    internalSubchannel.updateAddresses(
+        Arrays.asList(new EquivalentAddressGroup(Arrays.asList(addr2, addr3))));
+    assertNoCallbackInvoke();
+    assertEquals(CONNECTING, internalSubchannel.getState());
+    verify(transports.peek().transport, never()).shutdown(any(Status.class));
+    verify(transports.peek().transport, never()).shutdownNow(any(Status.class));
+
+    // And new addresses chosen when re-connecting
+    transports.peek().listener.transportReady();
+    assertExactCallbackInvokes("onStateChange:READY");
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertExactCallbackInvokes("onStateChange:IDLE");
+
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(mockTransportFactory, times(2))
+        .newClientTransport(addr2, createClientTransportOptions());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    verify(mockTransportFactory).newClientTransport(addr3, createClientTransportOptions());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    verifyNoMoreInteractions(mockTransportFactory);
+
+    fakeClock.forwardNanos(10); // Drain retry, but don't care about result
+  }
+
+  @Test public void updateAddresses_disjoint_idle() {
+    SocketAddress addr1 = mock(SocketAddress.class);
+    SocketAddress addr2 = mock(SocketAddress.class);
+
+    createInternalSubchannel(addr1);
+    internalSubchannel.updateAddresses(Arrays.asList(new EquivalentAddressGroup(addr2)));
+
+    // Nothing happened on address update
+    verify(mockTransportFactory, never())
+        .newClientTransport(addr1, createClientTransportOptions());
+    verify(mockTransportFactory, never())
+        .newClientTransport(addr2, createClientTransportOptions());
+    verifyNoMoreInteractions(mockTransportFactory);
+    assertNoCallbackInvoke();
+    assertEquals(IDLE, internalSubchannel.getState());
+
+    // But new address chosen when connecting
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    verify(mockTransportFactory).newClientTransport(addr2, createClientTransportOptions());
+
+    // And no other addresses attempted
+    assertEquals(0, fakeClock.numPendingTasks());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertExactCallbackInvokes("onStateChange:" + UNAVAILABLE_STATE);
+    assertEquals(TRANSIENT_FAILURE, internalSubchannel.getState());
+    verifyNoMoreInteractions(mockTransportFactory);
+
+    fakeClock.forwardNanos(10); // Drain retry, but don't care about result
+  }
+
+  @Test public void updateAddresses_disjoint_ready() {
+    SocketAddress addr1 = mock(SocketAddress.class);
+    SocketAddress addr2 = mock(SocketAddress.class);
+    SocketAddress addr3 = mock(SocketAddress.class);
+    SocketAddress addr4 = mock(SocketAddress.class);
+    createInternalSubchannel(addr1, addr2);
+    assertEquals(IDLE, internalSubchannel.getState());
+
+    // First address fails
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    verify(mockTransportFactory).newClientTransport(addr1, createClientTransportOptions());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertEquals(CONNECTING, internalSubchannel.getState());
+
+    // Second address connects
+    verify(mockTransportFactory).newClientTransport(addr2, createClientTransportOptions());
+    transports.peek().listener.transportReady();
+    assertExactCallbackInvokes("onStateChange:READY");
+    assertEquals(READY, internalSubchannel.getState());
+
+    // Update addresses
+    internalSubchannel.updateAddresses(
+        Arrays.asList(new EquivalentAddressGroup(Arrays.asList(addr3, addr4))));
+    assertExactCallbackInvokes("onStateChange:IDLE");
+    assertEquals(IDLE, internalSubchannel.getState());
+    verify(transports.peek().transport).shutdown(any(Status.class));
+
+    // And new addresses chosen when re-connecting
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertNoCallbackInvoke();
+    assertEquals(IDLE, internalSubchannel.getState());
+
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(mockTransportFactory).newClientTransport(addr3, createClientTransportOptions());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    verify(mockTransportFactory).newClientTransport(addr4, createClientTransportOptions());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    verifyNoMoreInteractions(mockTransportFactory);
+
+    fakeClock.forwardNanos(10); // Drain retry, but don't care about result
+  }
+
+  @Test public void updateAddresses_disjoint_connecting() {
+    SocketAddress addr1 = mock(SocketAddress.class);
+    SocketAddress addr2 = mock(SocketAddress.class);
+    SocketAddress addr3 = mock(SocketAddress.class);
+    SocketAddress addr4 = mock(SocketAddress.class);
+    createInternalSubchannel(addr1, addr2);
+    assertEquals(IDLE, internalSubchannel.getState());
+
+    // First address fails
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    verify(mockTransportFactory).newClientTransport(addr1, createClientTransportOptions());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertEquals(CONNECTING, internalSubchannel.getState());
+
+    // Second address connecting
+    verify(mockTransportFactory).newClientTransport(addr2, createClientTransportOptions());
+    assertNoCallbackInvoke();
+    assertEquals(CONNECTING, internalSubchannel.getState());
+
+    // Update addresses
+    internalSubchannel.updateAddresses(
+        Arrays.asList(new EquivalentAddressGroup(Arrays.asList(addr3, addr4))));
+    assertNoCallbackInvoke();
+    assertEquals(CONNECTING, internalSubchannel.getState());
+
+    // And new addresses chosen immediately
+    verify(transports.poll().transport).shutdown(any(Status.class));
+    assertNoCallbackInvoke();
+    assertEquals(CONNECTING, internalSubchannel.getState());
+
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(mockTransportFactory).newClientTransport(addr3, createClientTransportOptions());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    verify(mockTransportFactory).newClientTransport(addr4, createClientTransportOptions());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    verifyNoMoreInteractions(mockTransportFactory);
+
+    fakeClock.forwardNanos(10); // Drain retry, but don't care about result
+  }
+
+  @Test
+  public void connectIsLazy() {
+    SocketAddress addr = mock(SocketAddress.class);
+    createInternalSubchannel(addr);
+
+    // Invocation counters
+    int transportsCreated = 0;
+
+    // Won't connect until requested
+    verify(mockTransportFactory, times(transportsCreated))
+        .newClientTransport(addr, createClientTransportOptions());
+
+    // First attempt
+    internalSubchannel.obtainActiveTransport();
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    verify(mockTransportFactory, times(++transportsCreated))
+        .newClientTransport(addr, createClientTransportOptions());
+
+    // Fail this one
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertExactCallbackInvokes("onStateChange:" + UNAVAILABLE_STATE);
+
+    // Will always reconnect after back-off
+    fakeClock.forwardNanos(10);
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    verify(mockTransportFactory, times(++transportsCreated))
+        .newClientTransport(addr, createClientTransportOptions());
+
+    // Make this one proceed
+    transports.peek().listener.transportReady();
+    assertExactCallbackInvokes("onStateChange:READY");
+    // Then go-away
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertExactCallbackInvokes("onStateChange:IDLE");
+
+    // No scheduled tasks that would ever try to reconnect ...
+    assertEquals(0, fakeClock.numPendingTasks());
+    assertEquals(0, fakeExecutor.numPendingTasks());
+
+    // ... until it's requested.
+    internalSubchannel.obtainActiveTransport();
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    verify(mockTransportFactory, times(++transportsCreated))
+        .newClientTransport(addr, createClientTransportOptions());
+  }
+
+  @Test
+  public void shutdownWhenReady() throws Exception {
+    SocketAddress addr = mock(SocketAddress.class);
+    createInternalSubchannel(addr);
+
+    internalSubchannel.obtainActiveTransport();
+    MockClientTransportInfo transportInfo = transports.poll();
+    transportInfo.listener.transportReady();
+    assertExactCallbackInvokes("onStateChange:CONNECTING", "onStateChange:READY");
+
+    internalSubchannel.shutdown(SHUTDOWN_REASON);
+    verify(transportInfo.transport).shutdown(same(SHUTDOWN_REASON));
+    assertExactCallbackInvokes("onStateChange:SHUTDOWN");
+
+    transportInfo.listener.transportTerminated();
+    assertExactCallbackInvokes("onTerminated");
+    verify(transportInfo.transport, never()).shutdownNow(any(Status.class));
+  }
+
+  @Test
+  public void shutdownBeforeTransportCreated() throws Exception {
+    SocketAddress addr = mock(SocketAddress.class);
+    createInternalSubchannel(addr);
+
+    // First transport is created immediately
+    internalSubchannel.obtainActiveTransport();
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    verify(mockTransportFactory).newClientTransport(addr, createClientTransportOptions());
+
+    // Fail this one
+    MockClientTransportInfo transportInfo = transports.poll();
+    transportInfo.listener.transportShutdown(Status.UNAVAILABLE);
+    transportInfo.listener.transportTerminated();
+
+    // Entering TRANSIENT_FAILURE, waiting for back-off
+    assertExactCallbackInvokes("onStateChange:" + UNAVAILABLE_STATE);
+
+    // Save the reconnectTask before shutting down
+    FakeClock.ScheduledTask reconnectTask = null;
+    for (FakeClock.ScheduledTask task : fakeClock.getPendingTasks()) {
+      if (task.command.toString().contains("EndOfCurrentBackoff")) {
+        assertNull("There shouldn't be more than one reconnectTask", reconnectTask);
+        assertFalse(task.isDone());
+        reconnectTask = task;
+      }
+    }
+    assertNotNull("There should be at least one reconnectTask", reconnectTask);
+
+    // Shut down InternalSubchannel before the transport is created.
+    internalSubchannel.shutdown(SHUTDOWN_REASON);
+    assertTrue(reconnectTask.isCancelled());
+    // InternalSubchannel terminated promptly.
+    assertExactCallbackInvokes("onStateChange:SHUTDOWN", "onTerminated");
+
+    // Simulate a race between reconnectTask cancellation and execution -- the task runs anyway.
+    // This should not lead to the creation of a new transport.
+    reconnectTask.command.run();
+
+    // Futher call to obtainActiveTransport() is no-op.
+    assertNull(internalSubchannel.obtainActiveTransport());
+    assertEquals(SHUTDOWN, internalSubchannel.getState());
+    assertNoCallbackInvoke();
+
+    // No more transports will be created.
+    fakeClock.forwardNanos(10000);
+    assertEquals(SHUTDOWN, internalSubchannel.getState());
+    verifyNoMoreInteractions(mockTransportFactory);
+    assertEquals(0, transports.size());
+    assertNoCallbackInvoke();
+  }
+
+  @Test
+  public void shutdownBeforeTransportReady() throws Exception {
+    SocketAddress addr = mock(SocketAddress.class);
+    createInternalSubchannel(addr);
+
+    internalSubchannel.obtainActiveTransport();
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    MockClientTransportInfo transportInfo = transports.poll();
+
+    // Shutdown the InternalSubchannel before the pending transport is ready
+    assertNull(internalSubchannel.obtainActiveTransport());
+    internalSubchannel.shutdown(SHUTDOWN_REASON);
+    assertExactCallbackInvokes("onStateChange:SHUTDOWN");
+
+    // The transport should've been shut down even though it's not the active transport yet.
+    verify(transportInfo.transport).shutdown(same(SHUTDOWN_REASON));
+    transportInfo.listener.transportShutdown(Status.UNAVAILABLE);
+    assertNoCallbackInvoke();
+    transportInfo.listener.transportTerminated();
+    assertExactCallbackInvokes("onTerminated");
+    assertEquals(SHUTDOWN, internalSubchannel.getState());
+  }
+
+  @Test
+  public void shutdownNow() throws Exception {
+    SocketAddress addr = mock(SocketAddress.class);
+    createInternalSubchannel(addr);
+
+    internalSubchannel.obtainActiveTransport();
+    MockClientTransportInfo t1 = transports.poll();
+    t1.listener.transportReady();
+    assertExactCallbackInvokes("onStateChange:CONNECTING", "onStateChange:READY");
+    t1.listener.transportShutdown(Status.UNAVAILABLE);
+    assertExactCallbackInvokes("onStateChange:IDLE");
+
+    internalSubchannel.obtainActiveTransport();
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    MockClientTransportInfo t2 = transports.poll();
+
+    Status status = Status.UNAVAILABLE.withDescription("Requested");
+    internalSubchannel.shutdownNow(status);
+
+    verify(t1.transport).shutdownNow(same(status));
+    verify(t2.transport).shutdownNow(same(status));
+    assertExactCallbackInvokes("onStateChange:SHUTDOWN");
+  }
+
+  @Test
+  public void obtainTransportAfterShutdown() throws Exception {
+    SocketAddress addr = mock(SocketAddress.class);
+    createInternalSubchannel(addr);
+
+    internalSubchannel.shutdown(SHUTDOWN_REASON);
+    assertExactCallbackInvokes("onStateChange:SHUTDOWN", "onTerminated");
+    assertEquals(SHUTDOWN, internalSubchannel.getState());
+    assertNull(internalSubchannel.obtainActiveTransport());
+    verify(mockTransportFactory, times(0))
+        .newClientTransport(addr, createClientTransportOptions());
+    assertNoCallbackInvoke();
+    assertEquals(SHUTDOWN, internalSubchannel.getState());
+  }
+
+  @Test
+  public void logId() {
+    createInternalSubchannel(mock(SocketAddress.class));
+
+    assertNotNull(internalSubchannel.getLogId());
+  }
+
+  @Test
+  public void inUseState() {
+    SocketAddress addr = mock(SocketAddress.class);
+    createInternalSubchannel(addr);
+
+    internalSubchannel.obtainActiveTransport();
+    MockClientTransportInfo t0 = transports.poll();
+    t0.listener.transportReady();
+    assertExactCallbackInvokes("onStateChange:CONNECTING", "onStateChange:READY");
+    t0.listener.transportInUse(true);
+    assertExactCallbackInvokes("onInUse");
+
+    t0.listener.transportInUse(false);
+    assertExactCallbackInvokes("onNotInUse");
+
+    t0.listener.transportInUse(true);
+    assertExactCallbackInvokes("onInUse");
+    t0.listener.transportShutdown(Status.UNAVAILABLE);
+    assertExactCallbackInvokes("onStateChange:IDLE");
+
+    assertNull(internalSubchannel.obtainActiveTransport());
+    MockClientTransportInfo t1 = transports.poll();
+    t1.listener.transportReady();
+    assertExactCallbackInvokes("onStateChange:CONNECTING", "onStateChange:READY");
+    t1.listener.transportInUse(true);
+    // InternalSubchannel is already in-use, thus doesn't call the callback
+    assertNoCallbackInvoke();
+
+    t1.listener.transportInUse(false);
+    // t0 is still in-use
+    assertNoCallbackInvoke();
+
+    t0.listener.transportInUse(false);
+    assertExactCallbackInvokes("onNotInUse");
+  }
+
+  @Test
+  public void transportTerminateWithoutExitingInUse() {
+    // An imperfect transport that terminates without going out of in-use. InternalSubchannel will
+    // clear the in-use bit for it.
+    SocketAddress addr = mock(SocketAddress.class);
+    createInternalSubchannel(addr);
+
+    internalSubchannel.obtainActiveTransport();
+    MockClientTransportInfo t0 = transports.poll();
+    t0.listener.transportReady();
+    assertExactCallbackInvokes("onStateChange:CONNECTING", "onStateChange:READY");
+    t0.listener.transportInUse(true);
+    assertExactCallbackInvokes("onInUse");
+
+    t0.listener.transportShutdown(Status.UNAVAILABLE);
+    assertExactCallbackInvokes("onStateChange:IDLE");
+    t0.listener.transportTerminated();
+    assertExactCallbackInvokes("onNotInUse");
+  }
+
+  @Test
+  public void transportStartReturnsRunnable() {
+    SocketAddress addr1 = mock(SocketAddress.class);
+    SocketAddress addr2 = mock(SocketAddress.class);
+    createInternalSubchannel(addr1, addr2);
+    final AtomicInteger runnableInvokes = new AtomicInteger(0);
+    Runnable startRunnable = new Runnable() {
+        @Override
+        public void run() {
+          runnableInvokes.incrementAndGet();
+        }
+      };
+    transports = TestUtils.captureTransports(mockTransportFactory, startRunnable);
+
+    assertEquals(0, runnableInvokes.get());
+    internalSubchannel.obtainActiveTransport();
+    assertEquals(1, runnableInvokes.get());
+    internalSubchannel.obtainActiveTransport();
+    assertEquals(1, runnableInvokes.get());
+
+    MockClientTransportInfo t0 = transports.poll();
+    t0.listener.transportShutdown(Status.UNAVAILABLE);
+    assertEquals(2, runnableInvokes.get());
+
+    // 2nd address: reconnect immediatly
+    MockClientTransportInfo t1 = transports.poll();
+    t1.listener.transportShutdown(Status.UNAVAILABLE);
+
+    // Addresses exhausted, waiting for back-off.
+    assertEquals(2, runnableInvokes.get());
+    // Run out the back-off period
+    fakeClock.forwardNanos(10);
+    assertEquals(3, runnableInvokes.get());
+
+    // This test doesn't care about scheduled InternalSubchannel callbacks.  Clear it up so that
+    // noMorePendingTasks() won't fail.
+    fakeExecutor.runDueTasks();
+    assertEquals(3, runnableInvokes.get());
+  }
+
+  @Test
+  public void resetConnectBackoff() throws Exception {
+    SocketAddress addr = mock(SocketAddress.class);
+    createInternalSubchannel(addr);
+
+    // Move into TRANSIENT_FAILURE to schedule reconnect
+    internalSubchannel.obtainActiveTransport();
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    verify(mockTransportFactory).newClientTransport(addr, createClientTransportOptions());
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertExactCallbackInvokes("onStateChange:" + UNAVAILABLE_STATE);
+
+    // Save the reconnectTask
+    FakeClock.ScheduledTask reconnectTask = null;
+    for (FakeClock.ScheduledTask task : fakeClock.getPendingTasks()) {
+      if (task.command.toString().contains("EndOfCurrentBackoff")) {
+        assertNull("There shouldn't be more than one reconnectTask", reconnectTask);
+        assertFalse(task.isDone());
+        reconnectTask = task;
+      }
+    }
+    assertNotNull("There should be at least one reconnectTask", reconnectTask);
+
+    internalSubchannel.resetConnectBackoff();
+
+    verify(mockTransportFactory, times(2))
+        .newClientTransport(addr, createClientTransportOptions());
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    assertTrue(reconnectTask.isCancelled());
+
+    // Simulate a race between cancel and the task scheduler. Should be a no-op.
+    reconnectTask.command.run();
+    assertNoCallbackInvoke();
+    verify(mockTransportFactory, times(2))
+        .newClientTransport(addr, createClientTransportOptions());
+    verify(mockBackoffPolicyProvider, times(1)).get();
+
+    // Fail the reconnect attempt to verify that a fresh reconnect policy is generated after
+    // invoking resetConnectBackoff()
+    transports.poll().listener.transportShutdown(Status.UNAVAILABLE);
+    assertExactCallbackInvokes("onStateChange:" + UNAVAILABLE_STATE);
+    verify(mockBackoffPolicyProvider, times(2)).get();
+    fakeClock.forwardNanos(10);
+    assertExactCallbackInvokes("onStateChange:CONNECTING");
+    assertEquals(CONNECTING, internalSubchannel.getState());
+  }
+
+  @Test
+  public void resetConnectBackoff_noopOnIdleTransport() throws Exception {
+    SocketAddress addr = mock(SocketAddress.class);
+    createInternalSubchannel(addr);
+    assertEquals(IDLE, internalSubchannel.getState());
+
+    internalSubchannel.resetConnectBackoff();
+
+    assertNoCallbackInvoke();
+  }
+
+  @Test
+  public void channelzMembership() throws Exception {
+    SocketAddress addr1 = mock(SocketAddress.class);
+    createInternalSubchannel(addr1);
+    internalSubchannel.obtainActiveTransport();
+
+    MockClientTransportInfo t0 = transports.poll();
+    assertTrue(channelz.containsClientSocket(t0.transport.getLogId()));
+    t0.listener.transportTerminated();
+    assertFalse(channelz.containsClientSocket(t0.transport.getLogId()));
+  }
+
+  @Test
+  public void channelzStatContainsTransport() throws Exception {
+    SocketAddress addr = new SocketAddress() {};
+    assertThat(transports).isEmpty();
+    createInternalSubchannel(addr);
+    internalSubchannel.obtainActiveTransport();
+
+    InternalWithLogId registeredTransport
+        = Iterables.getOnlyElement(internalSubchannel.getStats().get().sockets);
+    MockClientTransportInfo actualTransport = Iterables.getOnlyElement(transports);
+    assertEquals(actualTransport.transport.getLogId(), registeredTransport.getLogId());
+  }
+
+  @Test public void index_looping() {
+    Attributes.Key<String> key = Attributes.Key.create("some-key");
+    Attributes attr1 = Attributes.newBuilder().set(key, "1").build();
+    Attributes attr2 = Attributes.newBuilder().set(key, "2").build();
+    Attributes attr3 = Attributes.newBuilder().set(key, "3").build();
+    SocketAddress addr1 = new FakeSocketAddress();
+    SocketAddress addr2 = new FakeSocketAddress();
+    SocketAddress addr3 = new FakeSocketAddress();
+    SocketAddress addr4 = new FakeSocketAddress();
+    SocketAddress addr5 = new FakeSocketAddress();
+    Index index = new Index(Arrays.asList(
+        new EquivalentAddressGroup(Arrays.asList(addr1, addr2), attr1),
+        new EquivalentAddressGroup(Arrays.asList(addr3), attr2),
+        new EquivalentAddressGroup(Arrays.asList(addr4, addr5), attr3)));
+    assertThat(index.getCurrentAddress()).isSameAs(addr1);
+    assertThat(index.getCurrentEagAttributes()).isSameAs(attr1);
+    assertThat(index.isAtBeginning()).isTrue();
+    assertThat(index.isValid()).isTrue();
+
+    index.increment();
+    assertThat(index.getCurrentAddress()).isSameAs(addr2);
+    assertThat(index.getCurrentEagAttributes()).isSameAs(attr1);
+    assertThat(index.isAtBeginning()).isFalse();
+    assertThat(index.isValid()).isTrue();
+
+    index.increment();
+    assertThat(index.getCurrentAddress()).isSameAs(addr3);
+    assertThat(index.getCurrentEagAttributes()).isSameAs(attr2);
+    assertThat(index.isAtBeginning()).isFalse();
+    assertThat(index.isValid()).isTrue();
+
+    index.increment();
+    assertThat(index.getCurrentAddress()).isSameAs(addr4);
+    assertThat(index.getCurrentEagAttributes()).isSameAs(attr3);
+    assertThat(index.isAtBeginning()).isFalse();
+    assertThat(index.isValid()).isTrue();
+
+    index.increment();
+    assertThat(index.getCurrentAddress()).isSameAs(addr5);
+    assertThat(index.getCurrentEagAttributes()).isSameAs(attr3);
+    assertThat(index.isAtBeginning()).isFalse();
+    assertThat(index.isValid()).isTrue();
+
+    index.increment();
+    assertThat(index.isAtBeginning()).isFalse();
+    assertThat(index.isValid()).isFalse();
+
+    index.reset();
+    assertThat(index.getCurrentAddress()).isSameAs(addr1);
+    assertThat(index.getCurrentEagAttributes()).isSameAs(attr1);
+    assertThat(index.isAtBeginning()).isTrue();
+    assertThat(index.isValid()).isTrue();
+
+    // We want to make sure both groupIndex and addressIndex are reset
+    index.increment();
+    index.increment();
+    index.increment();
+    index.increment();
+    assertThat(index.getCurrentAddress()).isSameAs(addr5);
+    assertThat(index.getCurrentEagAttributes()).isSameAs(attr3);
+    index.reset();
+    assertThat(index.getCurrentAddress()).isSameAs(addr1);
+    assertThat(index.getCurrentEagAttributes()).isSameAs(attr1);
+  }
+
+  @Test public void index_updateGroups_resets() {
+    SocketAddress addr1 = new FakeSocketAddress();
+    SocketAddress addr2 = new FakeSocketAddress();
+    SocketAddress addr3 = new FakeSocketAddress();
+    Index index = new Index(Arrays.asList(
+        new EquivalentAddressGroup(Arrays.asList(addr1)),
+        new EquivalentAddressGroup(Arrays.asList(addr2, addr3))));
+    index.increment();
+    index.increment();
+    // We want to make sure both groupIndex and addressIndex are reset
+    index.updateGroups(Arrays.asList(
+        new EquivalentAddressGroup(Arrays.asList(addr1)),
+        new EquivalentAddressGroup(Arrays.asList(addr2, addr3))));
+    assertThat(index.getCurrentAddress()).isSameAs(addr1);
+  }
+
+  @Test public void index_seekTo() {
+    SocketAddress addr1 = new FakeSocketAddress();
+    SocketAddress addr2 = new FakeSocketAddress();
+    SocketAddress addr3 = new FakeSocketAddress();
+    Index index = new Index(Arrays.asList(
+        new EquivalentAddressGroup(Arrays.asList(addr1, addr2)),
+        new EquivalentAddressGroup(Arrays.asList(addr3))));
+    assertThat(index.seekTo(addr3)).isTrue();
+    assertThat(index.getCurrentAddress()).isSameAs(addr3);
+    assertThat(index.seekTo(addr1)).isTrue();
+    assertThat(index.getCurrentAddress()).isSameAs(addr1);
+    assertThat(index.seekTo(addr2)).isTrue();
+    assertThat(index.getCurrentAddress()).isSameAs(addr2);
+    index.seekTo(new FakeSocketAddress());
+    // Failed seekTo doesn't change the index
+    assertThat(index.getCurrentAddress()).isSameAs(addr2);
+  }
+
+  /** Create ClientTransportOptions. Should not be reused if it may be mutated. */
+  private ClientTransportFactory.ClientTransportOptions createClientTransportOptions() {
+    return new ClientTransportFactory.ClientTransportOptions()
+        .setAuthority(AUTHORITY)
+        .setUserAgent(USER_AGENT);
+  }
+
+  private void createInternalSubchannel(SocketAddress ... addrs) {
+    createInternalSubchannel(new EquivalentAddressGroup(Arrays.asList(addrs)));
+  }
+
+  private void createInternalSubchannel(EquivalentAddressGroup ... addrs) {
+    List<EquivalentAddressGroup> addressGroups = Arrays.asList(addrs);
+    internalSubchannel = new InternalSubchannel(addressGroups, AUTHORITY, USER_AGENT,
+        mockBackoffPolicyProvider, mockTransportFactory, fakeClock.getScheduledExecutorService(),
+        fakeClock.getStopwatchSupplier(), channelExecutor, mockInternalSubchannelCallback,
+        channelz, CallTracer.getDefaultFactory().create(), null,
+        new TimeProvider() {
+          @Override
+          public long currentTimeNanos() {
+            return fakeClock.getTicker().read();
+          }
+        });
+  }
+
+  private void assertNoCallbackInvoke() {
+    while (fakeExecutor.runDueTasks() > 0) {}
+    assertEquals(0, callbackInvokes.size());
+  }
+
+  private void assertExactCallbackInvokes(String ... expectedInvokes) {
+    assertEquals(0, channelExecutor.numPendingTasks());
+    assertEquals(Arrays.asList(expectedInvokes), callbackInvokes);
+    callbackInvokes.clear();
+  }
+
+  private static class FakeSocketAddress extends SocketAddress {}
+}
diff --git a/core/src/test/java/io/grpc/internal/IoUtilsTest.java b/core/src/test/java/io/grpc/internal/IoUtilsTest.java
new file mode 100644
index 0000000..fc51985
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/IoUtilsTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit test for IoUtils. */
+@RunWith(JUnit4.class)
+public class IoUtilsTest {
+
+  @Test
+  public void testRoundTrip() throws Exception {
+    byte[] bytes = { 1, 2, 3, -127, 100, 127};
+    InputStream is = new ByteArrayInputStream(bytes);
+    byte[] bytes2 = IoUtils.toByteArray(is);
+    
+    assertNotSame(bytes2, bytes);
+    assertEquals(bytes.length, bytes2.length);
+    for (int i = 0; i < bytes.length; ++i) {
+      assertEquals(bytes[i], bytes2[i]);
+    }
+  }
+
+  @Test
+  public void testEmpty() throws Exception {
+    InputStream is = new ByteArrayInputStream(new byte[0]);
+    byte[] bytes = IoUtils.toByteArray(is);
+
+    assertEquals(0, bytes.length);
+  }
+}
\ No newline at end of file
diff --git a/core/src/test/java/io/grpc/internal/JndiResourceResolverTest.java b/core/src/test/java/io/grpc/internal/JndiResourceResolverTest.java
new file mode 100644
index 0000000..51ae7c5
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/JndiResourceResolverTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import io.grpc.internal.DnsNameResolver.AddressResolver;
+import io.grpc.internal.JndiResourceResolverFactory.JndiResourceResolver;
+import io.grpc.internal.JndiResourceResolverFactory.JndiResourceResolver.SrvRecord;
+import java.net.InetAddress;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link JndiResourceResolverFactory}.
+ */
+@RunWith(JUnit4.class)
+public class JndiResourceResolverTest {
+
+  @Test
+  public void normalizeDataRemovesJndiFormattingForTxtRecords() {
+    assertEquals("blah", JndiResourceResolver.unquote("blah"));
+    assertEquals("", JndiResourceResolver.unquote("\"\""));
+    assertEquals("blahblah", JndiResourceResolver.unquote("blah blah"));
+    assertEquals("blahfoo blah", JndiResourceResolver.unquote("blah \"foo blah\""));
+    assertEquals("blah blah", JndiResourceResolver.unquote("\"blah blah\""));
+    assertEquals("blah\"blah", JndiResourceResolver.unquote("\"blah\\\"blah\""));
+    assertEquals("blah\\blah", JndiResourceResolver.unquote("\"blah\\\\blah\""));
+  }
+
+  @Test
+  public void jndiResolverWorks() throws Exception {
+    Assume.assumeNoException(new JndiResourceResolverFactory().unavailabilityCause());
+
+    AddressResolver addressResolver = new AddressResolver() {
+      @Override
+      public List<InetAddress> resolveAddress(String host) throws Exception {
+        return null;
+      }
+    };
+    JndiResourceResolver resolver = new JndiResourceResolver();
+    try {
+      resolver.resolveSrv(addressResolver, "localhost");
+    } catch (javax.naming.CommunicationException e) {
+      Assume.assumeNoException(e);
+    } catch (javax.naming.NameNotFoundException e) {
+      Assume.assumeNoException(e);
+    }
+  }
+
+  @Test
+  public void parseSrvRecord() {
+    SrvRecord record = JndiResourceResolver.parseSrvRecord("0 0 1234 foo.bar.com");
+    assertThat(record.host).isEqualTo("foo.bar.com");
+    assertThat(record.port).isEqualTo(1234);
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/JsonParserTest.java b/core/src/test/java/io/grpc/internal/JsonParserTest.java
new file mode 100644
index 0000000..7ffeeb8
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/JsonParserTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gson.stream.MalformedJsonException;
+import java.io.EOFException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link JsonParser}.
+ */
+@RunWith(JUnit4.class)
+public class JsonParserTest {
+
+  @Rule
+  public final ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void emptyObject() throws IOException {
+    assertEquals(new LinkedHashMap<String, Object>(), JsonParser.parse("{}"));
+  }
+
+  @Test
+  public void emptyArray() throws IOException {
+    assertEquals(new ArrayList<>(), JsonParser.parse("[]"));
+  }
+
+  @Test
+  public void intNumber() throws IOException {
+    assertEquals(Double.valueOf("1"), JsonParser.parse("1"));
+  }
+
+  @Test
+  public void doubleNumber() throws IOException {
+    assertEquals(Double.valueOf("1.2"), JsonParser.parse("1.2"));
+  }
+
+  @Test
+  public void longNumber() throws IOException {
+    assertEquals(Double.valueOf("9999999999"), JsonParser.parse("9999999999"));
+  }
+
+  @Test
+  public void booleanValue() throws IOException {
+    assertEquals(true, JsonParser.parse("true"));
+  }
+
+  @Test
+  public void nullValue() throws IOException {
+    assertEquals(null, JsonParser.parse("null"));
+  }
+
+  @Test
+  public void nanFails() throws IOException {
+    thrown.expect(MalformedJsonException.class);
+
+    JsonParser.parse("NaN");
+  }
+
+  @Test
+  public void objectEarlyEnd() throws IOException {
+    thrown.expect(MalformedJsonException.class);
+
+    JsonParser.parse("{foo:}");
+  }
+
+  @Test
+  public void earlyEndArray() throws IOException {
+    thrown.expect(EOFException.class);
+
+    JsonParser.parse("[1, 2, ");
+  }
+
+  @Test
+  public void arrayMissingElement() throws IOException {
+    thrown.expect(MalformedJsonException.class);
+
+    JsonParser.parse("[1, 2, ]");
+  }
+
+  @Test
+  public void objectMissingElement() throws IOException {
+    thrown.expect(MalformedJsonException.class);
+
+    JsonParser.parse("{1: ");
+  }
+
+  @Test
+  public void objectNoName() throws IOException {
+    thrown.expect(MalformedJsonException.class);
+
+    JsonParser.parse("{: 1");
+  }
+
+  @Test
+  public void objectStringName() throws IOException {
+    LinkedHashMap<String, Object> expected = new LinkedHashMap<String, Object>();
+    expected.put("hi", Double.valueOf("2"));
+
+    assertEquals(expected, JsonParser.parse("{\"hi\": 2}"));
+  }
+}
\ No newline at end of file
diff --git a/core/src/test/java/io/grpc/internal/KeepAliveManagerTest.java b/core/src/test/java/io/grpc/internal/KeepAliveManagerTest.java
new file mode 100644
index 0000000..2567c93
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/KeepAliveManagerTest.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.Status;
+import io.grpc.internal.KeepAliveManager.ClientKeepAlivePinger;
+import io.grpc.internal.KeepAliveManager.KeepAlivePinger;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public final class KeepAliveManagerTest {
+  private final FakeTicker ticker = new FakeTicker();
+  private KeepAliveManager keepAliveManager;
+  @Mock private KeepAlivePinger keepAlivePinger;
+  @Mock private ConnectionClientTransport transport;
+  @Mock private ScheduledExecutorService scheduler;
+  @Captor
+  private ArgumentCaptor<Status> statusCaptor;
+
+  static class FakeTicker extends KeepAliveManager.Ticker {
+    long time;
+
+    @Override
+    public long read() {
+      return time;
+    }
+  }
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    keepAliveManager = new KeepAliveManager(keepAlivePinger, scheduler, ticker, 1000, 2000, false);
+  }
+
+  @Test
+  public void sendKeepAlivePings() {
+    ticker.time = 1;
+    // Transport becomes active. We should schedule keepalive pings.
+    keepAliveManager.onTransportActive();
+    ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
+    ArgumentCaptor<Runnable> sendPingCaptor = ArgumentCaptor.forClass(Runnable.class);
+    verify(scheduler, times(1)).schedule(sendPingCaptor.capture(), delayCaptor.capture(),
+        isA(TimeUnit.class));
+    Runnable sendPing = sendPingCaptor.getValue();
+    Long delay = delayCaptor.getValue();
+    assertEquals(1000 - 1, delay.longValue());
+
+    ScheduledFuture<?> shutdownFuture = mock(ScheduledFuture.class);
+    doReturn(shutdownFuture)
+        .when(scheduler).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+    // Mannually running the Runnable will send the ping. Shutdown task should be scheduled.
+    ticker.time = 1000;
+    sendPing.run();
+    verify(keepAlivePinger).ping();
+    verify(scheduler, times(2)).schedule(isA(Runnable.class), delayCaptor.capture(),
+        isA(TimeUnit.class));
+    delay = delayCaptor.getValue();
+    // Keepalive timeout is 2000.
+    assertEquals(2000, delay.longValue());
+
+    // Ping succeeds. Reschedule another ping.
+    ticker.time = 1100;
+    keepAliveManager.onDataReceived();
+    verify(scheduler, times(3)).schedule(isA(Runnable.class), delayCaptor.capture(),
+        isA(TimeUnit.class));
+    // Shutdown task has been cancelled.
+    verify(shutdownFuture).cancel(isA(Boolean.class));
+    delay = delayCaptor.getValue();
+    // Next ping should be exactly 1000 nanoseconds later.
+    assertEquals(1000, delay.longValue());
+  }
+
+  @Test
+  public void keepAlivePingDelayedByIncomingData() {
+    ScheduledFuture<?> future = mock(ScheduledFuture.class);
+    doReturn(future)
+        .when(scheduler).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+
+    // Transport becomes active. We should schedule keepalive pings.
+    keepAliveManager.onTransportActive();
+    ArgumentCaptor<Runnable> sendPingCaptor = ArgumentCaptor.forClass(Runnable.class);
+    verify(scheduler, times(1)).schedule(sendPingCaptor.capture(), isA(Long.class),
+        isA(TimeUnit.class));
+    Runnable sendPing = sendPingCaptor.getValue();
+
+    // We receive some data. We may need to delay the ping.
+    ticker.time = 1500;
+    keepAliveManager.onDataReceived();
+    ticker.time = 1600;
+    sendPing.run();
+    // We didn't send the ping.
+    verify(transport, times(0)).ping(isA(ClientTransport.PingCallback.class),
+        isA(Executor.class));
+    // Instead we reschedule.
+    ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
+    verify(scheduler, times(2)).schedule(isA(Runnable.class), delayCaptor.capture(),
+        isA(TimeUnit.class));
+    Long delay = delayCaptor.getValue();
+    assertEquals(1500 + 1000 - 1600, delay.longValue());
+  }
+
+  @Test
+  public void clientKeepAlivePinger_pingTimeout() {
+    keepAlivePinger = new ClientKeepAlivePinger(transport);
+
+    keepAlivePinger.onPingTimeout();
+
+    verify(transport).shutdownNow(statusCaptor.capture());
+    Status status = statusCaptor.getValue();
+    assertThat(status.getCode()).isEqualTo(Status.Code.UNAVAILABLE);
+    assertThat(status.getDescription()).isEqualTo(
+        "Keepalive failed. The connection is likely gone");
+  }
+
+  @Test
+  public void clientKeepAlivePinger_pingFailure() {
+    keepAlivePinger = new ClientKeepAlivePinger(transport);
+    keepAlivePinger.ping();
+    ArgumentCaptor<ClientTransport.PingCallback> pingCallbackCaptor =
+        ArgumentCaptor.forClass(ClientTransport.PingCallback.class);
+    verify(transport).ping(pingCallbackCaptor.capture(), isA(Executor.class));
+    ClientTransport.PingCallback pingCallback = pingCallbackCaptor.getValue();
+
+    pingCallback.onFailure(new Throwable());
+
+    verify(transport).shutdownNow(statusCaptor.capture());
+    Status status = statusCaptor.getValue();
+    assertThat(status.getCode()).isEqualTo(Status.Code.UNAVAILABLE);
+    assertThat(status.getDescription()).isEqualTo(
+        "Keepalive failed. The connection is likely gone");
+  }
+
+
+  @Test
+  public void onTransportTerminationCancelsShutdownFuture() {
+    // Transport becomes active. We should schedule keepalive pings.
+    keepAliveManager.onTransportActive();
+    ArgumentCaptor<Runnable> sendPingCaptor = ArgumentCaptor.forClass(Runnable.class);
+    verify(scheduler, times(1))
+        .schedule(sendPingCaptor.capture(), isA(Long.class), isA(TimeUnit.class));
+    Runnable sendPing = sendPingCaptor.getValue();
+
+    ScheduledFuture<?> shutdownFuture = mock(ScheduledFuture.class);
+    doReturn(shutdownFuture)
+        .when(scheduler).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+    // Mannually running the Runnable will send the ping. Shutdown task should be scheduled.
+    ticker.time = 1000;
+    sendPing.run();
+
+    keepAliveManager.onTransportTermination();
+
+    // Shutdown task has been cancelled.
+    verify(shutdownFuture).cancel(isA(Boolean.class));
+  }
+
+  @Test
+  public void keepAlivePingTimesOut() {
+    // Transport becomes active. We should schedule keepalive pings.
+    keepAliveManager.onTransportActive();
+    ArgumentCaptor<Runnable> sendPingCaptor = ArgumentCaptor.forClass(Runnable.class);
+    verify(scheduler, times(1)).schedule(sendPingCaptor.capture(), isA(Long.class),
+        isA(TimeUnit.class));
+    Runnable sendPing = sendPingCaptor.getValue();
+
+    ScheduledFuture<?> shutdownFuture = mock(ScheduledFuture.class);
+    doReturn(shutdownFuture)
+        .when(scheduler).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+    // Mannually running the Runnable will send the ping. Shutdown task should be scheduled.
+    ticker.time = 1000;
+    sendPing.run();
+    verify(keepAlivePinger).ping();
+    ArgumentCaptor<Runnable> shutdownCaptor = ArgumentCaptor.forClass(Runnable.class);
+    verify(scheduler, times(2)).schedule(shutdownCaptor.capture(), isA(Long.class),
+        isA(TimeUnit.class));
+    Runnable shutdown = shutdownCaptor.getValue();
+
+    // We do not receive the ping response. Shutdown runnable runs.
+    // TODO(zdapeng): use FakeClock.ScheduledExecutorService
+    ticker.time = 3000;
+    shutdown.run();
+    verify(keepAlivePinger).onPingTimeout();
+
+    // We receive the ping response too late.
+    keepAliveManager.onDataReceived();
+    // No more ping should be scheduled.
+    verify(scheduler, times(2)).schedule(isA(Runnable.class), isA(Long.class),
+        isA(TimeUnit.class));
+  }
+
+  @Test
+  public void transportGoesIdle() {
+    ScheduledFuture<?> pingFuture = mock(ScheduledFuture.class);
+    doReturn(pingFuture)
+        .when(scheduler).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+
+    // Transport becomes active. We should schedule keepalive pings.
+    keepAliveManager.onTransportActive();
+    ArgumentCaptor<Runnable> sendPingCaptor = ArgumentCaptor.forClass(Runnable.class);
+    verify(scheduler, times(1)).schedule(sendPingCaptor.capture(), isA(Long.class),
+        isA(TimeUnit.class));
+    Runnable sendPing = sendPingCaptor.getValue();
+
+    // Transport becomes idle. Nothing should happen when ping runnable runs.
+    keepAliveManager.onTransportIdle();
+    sendPing.run();
+    // Ping was not sent.
+    verify(transport, times(0)).ping(isA(ClientTransport.PingCallback.class), isA(Executor.class));
+    // No new ping got scheduled.
+    verify(scheduler, times(1)).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+
+    // But when transport goes back to active
+    keepAliveManager.onTransportActive();
+    // Then we do schedule another ping
+    verify(scheduler, times(2)).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+  }
+
+  @Test
+  public void transportGoesIdle_doesntCauseIdleWhenEnabled() {
+    keepAliveManager.onTransportTermination();
+    keepAliveManager = new KeepAliveManager(keepAlivePinger, scheduler, ticker, 1000, 2000, true);
+    keepAliveManager.onTransportStarted();
+
+    // Keepalive scheduling should have started immediately.
+    ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+    verify(scheduler).schedule(runnableCaptor.capture(), isA(Long.class),
+        isA(TimeUnit.class));
+    Runnable sendPing = runnableCaptor.getValue();
+
+    keepAliveManager.onTransportActive();
+
+    // Transport becomes idle. Should not impact the sending of the ping.
+    keepAliveManager.onTransportIdle();
+    sendPing.run();
+    // Ping was sent.
+    verify(keepAlivePinger).ping();
+    // Shutdown is scheduled.
+    verify(scheduler, times(2)).schedule(runnableCaptor.capture(), isA(Long.class),
+        isA(TimeUnit.class));
+    // Shutdown is triggered.
+    runnableCaptor.getValue().run();
+    verify(keepAlivePinger).onPingTimeout();
+  }
+
+  @Test
+  public void transportGoesIdleAfterPingSent() {
+    // Transport becomes active. We should schedule keepalive pings.
+    keepAliveManager.onTransportActive();
+    ArgumentCaptor<Runnable> sendPingCaptor = ArgumentCaptor.forClass(Runnable.class);
+    verify(scheduler, times(1)).schedule(sendPingCaptor.capture(), isA(Long.class),
+        isA(TimeUnit.class));
+    Runnable sendPing = sendPingCaptor.getValue();
+
+    ScheduledFuture<?> shutdownFuture = mock(ScheduledFuture.class);
+    doReturn(shutdownFuture)
+        .when(scheduler).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+    // Mannually running the Runnable will send the ping. Shutdown task should be scheduled.
+    // TODO(zdapeng): user FakeClock.ScheduledExecutorService
+    ticker.time = 1000;
+    sendPing.run();
+    verify(keepAlivePinger).ping();
+    verify(scheduler, times(2)).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+
+    // Transport becomes idle. No more ping should be scheduled after we receive a ping response.
+    keepAliveManager.onTransportIdle();
+    ticker.time = 1100;
+    keepAliveManager.onDataReceived();
+    verify(scheduler, times(2)).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+    // Shutdown task has been cancelled.
+    verify(shutdownFuture).cancel(isA(Boolean.class));
+
+    // Transport becomes active again. Another ping is scheduled.
+    keepAliveManager.onTransportActive();
+    verify(scheduler, times(3)).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+  }
+
+  @Test
+  public void transportGoesIdleBeforePingSent() {
+    // Transport becomes active. We should schedule keepalive pings.
+    ScheduledFuture<?> pingFuture = mock(ScheduledFuture.class);
+    doReturn(pingFuture)
+        .when(scheduler).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+    keepAliveManager.onTransportActive();
+    verify(scheduler, times(1)).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+
+    // Data is received, and we go to ping delayed
+    keepAliveManager.onDataReceived();
+
+    // Transport becomes idle while the 1st ping is still scheduled
+    keepAliveManager.onTransportIdle();
+
+    // Transport becomes active again, we don't need to reschedule another ping
+    keepAliveManager.onTransportActive();
+    verify(scheduler, times(1)).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+  }
+
+  @Test
+  public void transportShutsdownAfterPingScheduled() {
+    ScheduledFuture<?> pingFuture = mock(ScheduledFuture.class);
+    doReturn(pingFuture)
+        .when(scheduler).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+    // Ping will be scheduled.
+    keepAliveManager.onTransportActive();
+    verify(scheduler, times(1)).schedule(isA(Runnable.class), isA(Long.class),
+        isA(TimeUnit.class));
+    // Transport is shutting down.
+    keepAliveManager.onTransportTermination();
+    // Ping future should have been cancelled.
+    verify(pingFuture).cancel(isA(Boolean.class));
+  }
+
+  @Test
+  public void transportShutsdownAfterPingSent() {
+    keepAliveManager.onTransportActive();
+    ArgumentCaptor<Runnable> sendPingCaptor = ArgumentCaptor.forClass(Runnable.class);
+    verify(scheduler, times(1)).schedule(sendPingCaptor.capture(), isA(Long.class),
+        isA(TimeUnit.class));
+    Runnable sendPing = sendPingCaptor.getValue();
+
+    ScheduledFuture<?> shutdownFuture = mock(ScheduledFuture.class);
+    doReturn(shutdownFuture)
+        .when(scheduler).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+    // Mannually running the Runnable will send the ping. Shutdown task should be scheduled.
+    ticker.time = 1000;
+    sendPing.run();
+    verify(keepAlivePinger).ping();
+    verify(scheduler, times(2)).schedule(isA(Runnable.class), isA(Long.class),
+        isA(TimeUnit.class));
+
+    // Transport is shutting down.
+    keepAliveManager.onTransportTermination();
+    // Shutdown task has been cancelled.
+    verify(shutdownFuture).cancel(isA(Boolean.class));
+  }
+
+  @Test
+  public void pingSentThenIdleThenActiveThenAck() {
+    keepAliveManager.onTransportActive();
+    ArgumentCaptor<Runnable> sendPingCaptor = ArgumentCaptor.forClass(Runnable.class);
+    verify(scheduler, times(1))
+        .schedule(sendPingCaptor.capture(), isA(Long.class), isA(TimeUnit.class));
+    Runnable sendPing = sendPingCaptor.getValue();
+    ScheduledFuture<?> shutdownFuture = mock(ScheduledFuture.class);
+    // ping scheduled
+    doReturn(shutdownFuture)
+        .when(scheduler).schedule(isA(Runnable.class), isA(Long.class), isA(TimeUnit.class));
+
+    // Mannually running the Runnable will send the ping.
+    ticker.time = 1000;
+    sendPing.run();
+
+    // shutdown scheduled
+    verify(scheduler, times(2))
+        .schedule(sendPingCaptor.capture(), isA(Long.class), isA(TimeUnit.class));
+    verify(keepAlivePinger).ping();
+
+    keepAliveManager.onTransportIdle();
+
+    keepAliveManager.onTransportActive();
+
+    keepAliveManager.onDataReceived();
+
+    // another ping scheduled
+    verify(scheduler, times(3))
+        .schedule(sendPingCaptor.capture(), isA(Long.class), isA(TimeUnit.class));
+  }
+}
+
diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplGetNameResolverTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplGetNameResolverTest.java
new file mode 100644
index 0000000..2fd3b9b
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplGetNameResolverTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import io.grpc.Attributes;
+import io.grpc.NameResolver;
+import io.grpc.NameResolver.Factory;
+import java.net.URI;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ManagedChannelImpl#getNameResolver}. */
+@RunWith(JUnit4.class)
+public class ManagedChannelImplGetNameResolverTest {
+  private static final Attributes NAME_RESOLVER_PARAMS =
+      Attributes.newBuilder().set(NameResolver.Factory.PARAMS_DEFAULT_PORT, 447).build();
+
+  @Test
+  public void invalidUriTarget() {
+    testInvalidTarget("defaultscheme:///[invalid]");
+  }
+
+  @Test
+  public void validTargetWithInvalidDnsName() throws Exception {
+    testValidTarget("[valid]", "defaultscheme:///%5Bvalid%5D",
+        new URI("defaultscheme", "", "/[valid]", null));
+  }
+
+  @Test
+  public void validAuthorityTarget() throws Exception {
+    testValidTarget("foo.googleapis.com:8080", "defaultscheme:///foo.googleapis.com:8080",
+        new URI("defaultscheme", "", "/foo.googleapis.com:8080", null));
+  }
+
+  @Test
+  public void validUriTarget() throws Exception {
+    testValidTarget("scheme:///foo.googleapis.com:8080", "scheme:///foo.googleapis.com:8080",
+        new URI("scheme", "", "/foo.googleapis.com:8080", null));
+  }
+
+  @Test
+  public void validIpv4AuthorityTarget() throws Exception {
+    testValidTarget("127.0.0.1:1234", "defaultscheme:///127.0.0.1:1234",
+        new URI("defaultscheme", "", "/127.0.0.1:1234", null));
+  }
+
+  @Test
+  public void validIpv4UriTarget() throws Exception {
+    testValidTarget("dns:///127.0.0.1:1234", "dns:///127.0.0.1:1234",
+        new URI("dns", "", "/127.0.0.1:1234", null));
+  }
+
+  @Test
+  public void validIpv6AuthorityTarget() throws Exception {
+    testValidTarget("[::1]:1234", "defaultscheme:///%5B::1%5D:1234",
+        new URI("defaultscheme", "", "/[::1]:1234", null));
+  }
+
+  @Test
+  public void invalidIpv6UriTarget() throws Exception {
+    testInvalidTarget("dns:///[::1]:1234");
+  }
+
+  @Test
+  public void validIpv6UriTarget() throws Exception {
+    testValidTarget("dns:///%5B::1%5D:1234", "dns:///%5B::1%5D:1234",
+        new URI("dns", "", "/[::1]:1234", null));
+  }
+
+  @Test
+  public void validTargetStartingWithSlash() throws Exception {
+    testValidTarget("/target", "defaultscheme:////target",
+        new URI("defaultscheme", "", "//target", null));
+  }
+
+  @Test
+  public void validTargetNoResovler() {
+    Factory nameResolverFactory = new NameResolver.Factory() {
+      @Override
+      public NameResolver newNameResolver(URI targetUri, Attributes params) {
+        return null;
+      }
+
+      @Override
+      public String getDefaultScheme() {
+        return "defaultscheme";
+      }
+    };
+    try {
+      ManagedChannelImpl.getNameResolver(
+          "foo.googleapis.com:8080", nameResolverFactory, NAME_RESOLVER_PARAMS);
+      fail("Should fail");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+  }
+
+  private void testValidTarget(String target, String expectedUriString, URI expectedUri) {
+    Factory nameResolverFactory = new FakeNameResolverFactory(expectedUri.getScheme());
+    FakeNameResolver nameResolver = (FakeNameResolver) ManagedChannelImpl.getNameResolver(
+        target, nameResolverFactory, NAME_RESOLVER_PARAMS);
+    assertNotNull(nameResolver);
+    assertEquals(expectedUri, nameResolver.uri);
+    assertEquals(expectedUriString, nameResolver.uri.toString());
+  }
+
+  private void testInvalidTarget(String target) {
+    Factory nameResolverFactory = new FakeNameResolverFactory("dns");
+
+    try {
+      FakeNameResolver nameResolver = (FakeNameResolver) ManagedChannelImpl.getNameResolver(
+          target, nameResolverFactory, NAME_RESOLVER_PARAMS);
+      fail("Should have failed, but got resolver with " + nameResolver.uri);
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+  }
+
+  private static class FakeNameResolverFactory extends NameResolver.Factory {
+    final String expectedScheme;
+
+    FakeNameResolverFactory(String expectedScheme) {
+      this.expectedScheme = expectedScheme;
+    }
+
+    @Override
+    public NameResolver newNameResolver(URI targetUri, Attributes params) {
+      if (expectedScheme.equals(targetUri.getScheme())) {
+        return new FakeNameResolver(targetUri);
+      }
+      return null;
+    }
+
+    @Override
+    public String getDefaultScheme() {
+      return expectedScheme;
+    }
+  }
+
+  private static class FakeNameResolver extends NameResolver {
+    final URI uri;
+
+    FakeNameResolver(URI uri) {
+      this.uri = uri;
+    }
+
+    @Override public String getServiceAuthority() {
+      return uri.getAuthority();
+    }
+
+    @Override public void start(final Listener listener) {}
+
+    @Override public void shutdown() {}
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java
new file mode 100644
index 0000000..d0ced5f
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplIdlenessTest.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static io.grpc.ConnectivityState.READY;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.Lists;
+import io.grpc.Attributes;
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.IntegerMarshaller;
+import io.grpc.LoadBalancer;
+import io.grpc.LoadBalancer.Helper;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.NameResolver;
+import io.grpc.Status;
+import io.grpc.StringMarshaller;
+import io.grpc.internal.FakeClock.ScheduledTask;
+import io.grpc.internal.TestUtils.MockClientTransportInfo;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link ManagedChannelImpl}'s idle mode.
+ */
+@RunWith(JUnit4.class)
+public class ManagedChannelImplIdlenessTest {
+  private final FakeClock timer = new FakeClock();
+  private final FakeClock executor = new FakeClock();
+  private final FakeClock oobExecutor = new FakeClock();
+  private static final String AUTHORITY = "fakeauthority";
+  private static final String USER_AGENT = "fakeagent";
+  private static final ProxyParameters NO_PROXY = null;
+  private static final long IDLE_TIMEOUT_SECONDS = 30;
+  private ManagedChannelImpl channel;
+
+  private final MethodDescriptor<String, Integer> method =
+      MethodDescriptor.<String, Integer>newBuilder()
+          .setType(MethodType.UNKNOWN)
+          .setFullMethodName("service/method")
+          .setRequestMarshaller(new StringMarshaller())
+          .setResponseMarshaller(new IntegerMarshaller())
+          .build();
+
+  private final List<EquivalentAddressGroup> servers = Lists.newArrayList();
+  private final ObjectPool<Executor> executorPool =
+      new FixedObjectPool<Executor>(executor.getScheduledExecutorService());
+  private final ObjectPool<Executor> oobExecutorPool =
+      new FixedObjectPool<Executor>(oobExecutor.getScheduledExecutorService());
+
+  @Mock private ClientTransportFactory mockTransportFactory;
+  @Mock private LoadBalancer mockLoadBalancer;
+  @Mock private LoadBalancer.Factory mockLoadBalancerFactory;
+  @Mock private NameResolver mockNameResolver;
+  @Mock private NameResolver.Factory mockNameResolverFactory;
+  @Mock private ClientCall.Listener<Integer> mockCallListener;
+  @Mock private ClientCall.Listener<Integer> mockCallListener2;
+  @Captor private ArgumentCaptor<NameResolver.Listener> nameResolverListenerCaptor;
+  private BlockingQueue<MockClientTransportInfo> newTransports;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    when(mockLoadBalancerFactory.newLoadBalancer(any(Helper.class))).thenReturn(mockLoadBalancer);
+    when(mockNameResolver.getServiceAuthority()).thenReturn(AUTHORITY);
+    when(mockNameResolverFactory
+        .newNameResolver(any(URI.class), any(Attributes.class)))
+        .thenReturn(mockNameResolver);
+    when(mockTransportFactory.getScheduledExecutorService())
+        .thenReturn(timer.getScheduledExecutorService());
+
+    class Builder extends AbstractManagedChannelImplBuilder<Builder> {
+      Builder(String target) {
+        super(target);
+      }
+
+      @Override protected ClientTransportFactory buildTransportFactory() {
+        throw new UnsupportedOperationException();
+      }
+
+      @Override public Builder usePlaintext() {
+        throw new UnsupportedOperationException();
+      }
+    }
+
+    Builder builder = new Builder("fake://target")
+        .nameResolverFactory(mockNameResolverFactory)
+        .loadBalancerFactory(mockLoadBalancerFactory)
+        .idleTimeout(IDLE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
+        .userAgent(USER_AGENT);
+    builder.executorPool = executorPool;
+    channel = new ManagedChannelImpl(
+        builder, mockTransportFactory, new FakeBackoffPolicyProvider(),
+        oobExecutorPool, timer.getStopwatchSupplier(),
+        Collections.<ClientInterceptor>emptyList(),
+        TimeProvider.SYSTEM_TIME_PROVIDER);
+    newTransports = TestUtils.captureTransports(mockTransportFactory);
+
+    for (int i = 0; i < 2; i++) {
+      ArrayList<SocketAddress> addrs = Lists.newArrayList();
+      for (int j = 0; j < 2; j++) {
+        addrs.add(new FakeSocketAddress("servergroup" + i + "server" + j));
+      }
+      servers.add(new EquivalentAddressGroup(addrs));
+    }
+    verify(mockNameResolverFactory).newNameResolver(any(URI.class), any(Attributes.class));
+    // Verify the initial idleness
+    verify(mockLoadBalancerFactory, never()).newLoadBalancer(any(Helper.class));
+    verify(mockTransportFactory, never()).newClientTransport(
+        any(SocketAddress.class),
+        any(ClientTransportFactory.ClientTransportOptions.class));
+    verify(mockNameResolver, never()).start(any(NameResolver.Listener.class));
+  }
+
+  @After
+  public void allPendingTasksAreRun() {
+    Collection<ScheduledTask> pendingTimerTasks = timer.getPendingTasks();
+    for (ScheduledTask a : pendingTimerTasks) {
+      assertFalse(Rescheduler.isEnabled(a.command));
+    }
+    assertEquals(executor.getPendingTasks() + " should be empty", 0, executor.numPendingTasks());
+  }
+
+  @Test
+  public void newCallExitsIdleness() throws Exception {
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata());
+
+    verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
+
+    verify(mockNameResolver).start(nameResolverListenerCaptor.capture());
+    // Simulate new address resolved to make sure the LoadBalancer is correctly linked to
+    // the NameResolver.
+    nameResolverListenerCaptor.getValue().onAddresses(servers, Attributes.EMPTY);
+    verify(mockLoadBalancer).handleResolvedAddressGroups(servers, Attributes.EMPTY);
+  }
+
+  @Test
+  public void newCallRefreshesIdlenessTimer() throws Exception {
+    // First call to exit the initial idleness, then immediately cancel the call.
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata());
+    call.cancel("For testing", null);
+
+    // Verify that we have exited the idle mode
+    verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
+    assertFalse(channel.inUseStateAggregator.isInUse());
+
+    // Move closer to idleness, but not yet.
+    timer.forwardTime(IDLE_TIMEOUT_SECONDS - 1, TimeUnit.SECONDS);
+    verify(mockLoadBalancer, never()).shutdown();
+    assertFalse(channel.inUseStateAggregator.isInUse());
+
+    // A new call would refresh the timer
+    call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata());
+    call.cancel("For testing", null);
+    assertFalse(channel.inUseStateAggregator.isInUse());
+
+    // ... so that passing the same length of time will not trigger idle mode
+    timer.forwardTime(IDLE_TIMEOUT_SECONDS - 1, TimeUnit.SECONDS);
+    verify(mockLoadBalancer, never()).shutdown();
+    assertFalse(channel.inUseStateAggregator.isInUse());
+
+    // ... until the time since last call has reached the timeout
+    timer.forwardTime(1, TimeUnit.SECONDS);
+    verify(mockLoadBalancer).shutdown();
+    assertFalse(channel.inUseStateAggregator.isInUse());
+
+    // Drain the app executor, which runs the call listeners
+    verify(mockCallListener, never()).onClose(any(Status.class), any(Metadata.class));
+    assertEquals(2, executor.runDueTasks());
+    verify(mockCallListener, times(2)).onClose(any(Status.class), any(Metadata.class));
+  }
+
+  @Test
+  public void delayedTransportHoldsOffIdleness() throws Exception {
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata());
+    assertTrue(channel.inUseStateAggregator.isInUse());
+
+    // As long as the delayed transport is in-use (by the pending RPC), the channel won't go idle.
+    timer.forwardTime(IDLE_TIMEOUT_SECONDS * 2, TimeUnit.SECONDS);
+    assertTrue(channel.inUseStateAggregator.isInUse());
+
+    // Cancelling the only RPC will reset the in-use state.
+    assertEquals(0, executor.numPendingTasks());
+    call.cancel("In test", null);
+    assertEquals(1, executor.runDueTasks());
+    assertFalse(channel.inUseStateAggregator.isInUse());
+    // And allow the channel to go idle.
+    timer.forwardTime(IDLE_TIMEOUT_SECONDS - 1, TimeUnit.SECONDS);
+    verify(mockLoadBalancer, never()).shutdown();
+    timer.forwardTime(1, TimeUnit.SECONDS);
+    verify(mockLoadBalancer).shutdown();
+  }
+
+  @Test
+  public void realTransportsHoldsOffIdleness() throws Exception {
+    final EquivalentAddressGroup addressGroup = servers.get(1);
+
+    // Start a call, which goes to delayed transport
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata());
+
+    // Verify that we have exited the idle mode
+    ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
+    verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
+    Helper helper = helperCaptor.getValue();
+    assertTrue(channel.inUseStateAggregator.isInUse());
+
+    // Assume LoadBalancer has received an address, then create a subchannel.
+    Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    subchannel.requestConnection();
+    MockClientTransportInfo t0 = newTransports.poll();
+    t0.listener.transportReady();
+
+    SubchannelPicker mockPicker = mock(SubchannelPicker.class);
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withSubchannel(subchannel));
+    helper.updateBalancingState(READY, mockPicker);
+    // Delayed transport creates real streams in the app executor
+    executor.runDueTasks();
+
+    // Delayed transport exits in-use, while real transport has not entered in-use yet.
+    assertFalse(channel.inUseStateAggregator.isInUse());
+
+    // Now it's in-use
+    t0.listener.transportInUse(true);
+    assertTrue(channel.inUseStateAggregator.isInUse());
+
+    // As long as the transport is in-use, the channel won't go idle.
+    timer.forwardTime(IDLE_TIMEOUT_SECONDS * 2, TimeUnit.SECONDS);
+    assertTrue(channel.inUseStateAggregator.isInUse());
+
+    t0.listener.transportInUse(false);
+    assertFalse(channel.inUseStateAggregator.isInUse());
+    // And allow the channel to go idle.
+    timer.forwardTime(IDLE_TIMEOUT_SECONDS - 1, TimeUnit.SECONDS);
+    verify(mockLoadBalancer, never()).shutdown();
+    timer.forwardTime(1, TimeUnit.SECONDS);
+    verify(mockLoadBalancer).shutdown();
+  }
+
+  @Test
+  public void updateSubchannelAddresses_newAddressConnects() {
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata()); // Create LB
+    ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
+    verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
+    Helper helper = helperCaptor.getValue();
+    Subchannel subchannel = helper.createSubchannel(servers.get(0), Attributes.EMPTY);
+
+    subchannel.requestConnection();
+    MockClientTransportInfo t0 = newTransports.poll();
+    t0.listener.transportReady();
+
+    helper.updateSubchannelAddresses(subchannel, servers.get(1));
+
+    subchannel.requestConnection();
+    MockClientTransportInfo t1 = newTransports.poll();
+    t1.listener.transportReady();
+  }
+
+  @Test
+  public void updateSubchannelAddresses_existingAddressDoesNotConnect() {
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata()); // Create LB
+    ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
+    verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
+    Helper helper = helperCaptor.getValue();
+    Subchannel subchannel = helper.createSubchannel(servers.get(0), Attributes.EMPTY);
+
+    subchannel.requestConnection();
+    MockClientTransportInfo t0 = newTransports.poll();
+    t0.listener.transportReady();
+
+    List<SocketAddress> changedList = new ArrayList<>(servers.get(0).getAddresses());
+    changedList.add(new FakeSocketAddress("aDifferentServer"));
+    helper.updateSubchannelAddresses(subchannel, new EquivalentAddressGroup(changedList));
+
+    subchannel.requestConnection();
+    assertNull(newTransports.poll());
+  }
+
+  @Test
+  public void oobTransportDoesNotAffectIdleness() {
+    // Start a call, which goes to delayed transport
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata());
+
+    // Verify that we have exited the idle mode
+    ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
+    verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
+    Helper helper = helperCaptor.getValue();
+
+    // Fail the RPC
+    SubchannelPicker failingPicker = mock(SubchannelPicker.class);
+    when(failingPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withError(Status.UNAVAILABLE));
+    helper.updateBalancingState(TRANSIENT_FAILURE, failingPicker);
+    executor.runDueTasks();
+    verify(mockCallListener).onClose(same(Status.UNAVAILABLE), any(Metadata.class));
+
+    // ... so that the channel resets its in-use state
+    assertFalse(channel.inUseStateAggregator.isInUse());
+
+    // Now make an RPC on an OOB channel
+    ManagedChannel oob = helper.createOobChannel(servers.get(0), "oobauthority");
+    verify(mockTransportFactory, never())
+        .newClientTransport(
+            any(SocketAddress.class),
+            eq(new ClientTransportFactory.ClientTransportOptions()
+              .setAuthority("oobauthority")
+              .setUserAgent(USER_AGENT)));
+    ClientCall<String, Integer> oobCall = oob.newCall(method, CallOptions.DEFAULT);
+    oobCall.start(mockCallListener2, new Metadata());
+    verify(mockTransportFactory)
+        .newClientTransport(
+            any(SocketAddress.class),
+            eq(new ClientTransportFactory.ClientTransportOptions()
+              .setAuthority("oobauthority")
+              .setUserAgent(USER_AGENT)));
+    MockClientTransportInfo oobTransportInfo = newTransports.poll();
+    assertEquals(0, newTransports.size());
+    // The OOB transport reports in-use state
+    oobTransportInfo.listener.transportInUse(true);
+
+    // But it won't stop the channel from going idle
+    verify(mockLoadBalancer, never()).shutdown();
+    timer.forwardTime(IDLE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+    verify(mockLoadBalancer).shutdown();
+  }
+
+  @Test
+  public void updateOobChannelAddresses_newAddressConnects() {
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata()); // Create LB
+    ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
+    verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
+    Helper helper = helperCaptor.getValue();
+    ManagedChannel oobChannel = helper.createOobChannel(servers.get(0), "localhost");
+
+    oobChannel.newCall(method, CallOptions.DEFAULT).start(mockCallListener, new Metadata());
+    MockClientTransportInfo t0 = newTransports.poll();
+    t0.listener.transportReady();
+
+    helper.updateOobChannelAddresses(oobChannel, servers.get(1));
+
+    oobChannel.newCall(method, CallOptions.DEFAULT).start(mockCallListener, new Metadata());
+    MockClientTransportInfo t1 = newTransports.poll();
+    t1.listener.transportReady();
+  }
+
+  @Test
+  public void updateOobChannelAddresses_existingAddressDoesNotConnect() {
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata()); // Create LB
+    ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
+    verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
+    Helper helper = helperCaptor.getValue();
+    ManagedChannel oobChannel = helper.createOobChannel(servers.get(0), "localhost");
+
+    oobChannel.newCall(method, CallOptions.DEFAULT).start(mockCallListener, new Metadata());
+    MockClientTransportInfo t0 = newTransports.poll();
+    t0.listener.transportReady();
+
+    List<SocketAddress> changedList = new ArrayList<>(servers.get(0).getAddresses());
+    changedList.add(new FakeSocketAddress("aDifferentServer"));
+    helper.updateOobChannelAddresses(oobChannel, new EquivalentAddressGroup(changedList));
+
+    oobChannel.newCall(method, CallOptions.DEFAULT).start(mockCallListener, new Metadata());
+    assertNull(newTransports.poll());
+  }
+
+  private static class FakeBackoffPolicyProvider implements BackoffPolicy.Provider {
+    @Override
+    public BackoffPolicy get() {
+      return new BackoffPolicy() {
+        @Override
+        public long nextBackoffNanos() {
+          return 1;
+        }
+      };
+    }
+  }
+
+  private static class FakeSocketAddress extends SocketAddress {
+    final String name;
+
+    FakeSocketAddress(String name) {
+      this.name = name;
+    }
+
+    @Override
+    public String toString() {
+      return "FakeSocketAddress-" + name;
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java
new file mode 100644
index 0000000..ffc92b1
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ManagedChannelImplTest.java
@@ -0,0 +1,2960 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.ConnectivityState.CONNECTING;
+import static io.grpc.ConnectivityState.IDLE;
+import static io.grpc.ConnectivityState.READY;
+import static io.grpc.ConnectivityState.SHUTDOWN;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+import static junit.framework.TestCase.assertNotSame;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.Attributes;
+import io.grpc.BinaryLog;
+import io.grpc.CallCredentials;
+import io.grpc.CallCredentials.MetadataApplier;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ClientInterceptors;
+import io.grpc.ClientStreamTracer;
+import io.grpc.ConnectivityState;
+import io.grpc.ConnectivityStateInfo;
+import io.grpc.Context;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.IntegerMarshaller;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.ChannelStats;
+import io.grpc.InternalChannelz.ChannelTrace;
+import io.grpc.InternalInstrumented;
+import io.grpc.LoadBalancer;
+import io.grpc.LoadBalancer.Helper;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.NameResolver;
+import io.grpc.SecurityLevel;
+import io.grpc.ServerMethodDefinition;
+import io.grpc.Status;
+import io.grpc.StringMarshaller;
+import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
+import io.grpc.internal.TestUtils.MockClientTransportInfo;
+import io.grpc.stub.ClientCalls;
+import io.grpc.testing.TestMethodDescriptors;
+import java.io.IOException;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.Nullable;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/** Unit tests for {@link ManagedChannelImpl}. */
+@RunWith(JUnit4.class)
+public class ManagedChannelImplTest {
+  private static final Attributes NAME_RESOLVER_PARAMS =
+      Attributes.newBuilder().set(NameResolver.Factory.PARAMS_DEFAULT_PORT, 447).build();
+
+  private static final MethodDescriptor<String, Integer> method =
+      MethodDescriptor.<String, Integer>newBuilder()
+          .setType(MethodType.UNKNOWN)
+          .setFullMethodName("service/method")
+          .setRequestMarshaller(new StringMarshaller())
+          .setResponseMarshaller(new IntegerMarshaller())
+          .build();
+  private static final Attributes.Key<String> SUBCHANNEL_ATTR_KEY =
+      Attributes.Key.create("subchannel-attr-key");
+  private static final long RECONNECT_BACKOFF_INTERVAL_NANOS = 10;
+  private static final String SERVICE_NAME = "fake.example.com";
+  private static final String AUTHORITY = SERVICE_NAME;
+  private static final String USER_AGENT = "userAgent";
+  private static final ClientTransportOptions clientTransportOptions =
+      new ClientTransportOptions()
+          .setAuthority(AUTHORITY)
+          .setUserAgent(USER_AGENT);
+  private static final String TARGET = "fake://" + SERVICE_NAME;
+  private URI expectedUri;
+  private final SocketAddress socketAddress = new SocketAddress() {};
+  private final EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(socketAddress);
+  private final FakeClock timer = new FakeClock();
+  private final FakeClock executor = new FakeClock();
+  private final FakeClock oobExecutor = new FakeClock();
+  private static final FakeClock.TaskFilter NAME_RESOLVER_REFRESH_TASK_FILTER =
+      new FakeClock.TaskFilter() {
+        @Override
+        public boolean shouldAccept(Runnable command) {
+          return command instanceof ManagedChannelImpl.NameResolverRefresh;
+        }
+      };
+
+  private final InternalChannelz channelz = new InternalChannelz();
+
+  @Rule public final ExpectedException thrown = ExpectedException.none();
+
+  private ManagedChannelImpl channel;
+  private Helper helper;
+  @Captor
+  private ArgumentCaptor<Status> statusCaptor;
+  @Captor
+  private ArgumentCaptor<CallOptions> callOptionsCaptor;
+  @Mock
+  private LoadBalancer.Factory mockLoadBalancerFactory;
+  @Mock
+  private LoadBalancer mockLoadBalancer;
+
+  @Captor
+  private ArgumentCaptor<ConnectivityStateInfo> stateInfoCaptor;
+  @Mock
+  private SubchannelPicker mockPicker;
+  @Mock
+  private ClientTransportFactory mockTransportFactory;
+  @Mock
+  private ClientCall.Listener<Integer> mockCallListener;
+  @Mock
+  private ClientCall.Listener<Integer> mockCallListener2;
+  @Mock
+  private ClientCall.Listener<Integer> mockCallListener3;
+  @Mock
+  private ClientCall.Listener<Integer> mockCallListener4;
+  @Mock
+  private ClientCall.Listener<Integer> mockCallListener5;
+  @Mock
+  private ObjectPool<Executor> executorPool;
+  @Mock
+  private ObjectPool<Executor> oobExecutorPool;
+  @Mock
+  private CallCredentials creds;
+  private ChannelBuilder channelBuilder;
+  private boolean requestConnection = true;
+  private BlockingQueue<MockClientTransportInfo> transports;
+
+  private ArgumentCaptor<ClientStreamListener> streamListenerCaptor =
+      ArgumentCaptor.forClass(ClientStreamListener.class);
+
+  private void createChannel(ClientInterceptor... interceptors) {
+    checkState(channel == null);
+    TimeProvider fakeClockTimeProvider = new TimeProvider() {
+      @Override
+      public long currentTimeNanos() {
+        return timer.getTicker().read();
+      }
+    };
+
+    channel = new ManagedChannelImpl(
+        channelBuilder, mockTransportFactory, new FakeBackoffPolicyProvider(),
+        oobExecutorPool, timer.getStopwatchSupplier(), Arrays.asList(interceptors),
+        fakeClockTimeProvider);
+
+    if (requestConnection) {
+      int numExpectedTasks = 0;
+
+      // Force-exit the initial idle-mode
+      channel.exitIdleMode();
+      if (channelBuilder.idleTimeoutMillis != ManagedChannelImpl.IDLE_TIMEOUT_MILLIS_DISABLE) {
+        numExpectedTasks += 1;
+      }
+
+      if (getNameResolverRefresh() != null) {
+        numExpectedTasks += 1;
+      }
+
+      assertEquals(numExpectedTasks, timer.numPendingTasks());
+
+      ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
+      verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
+      helper = helperCaptor.getValue();
+    }
+  }
+
+  @Before
+  public void setUp() throws Exception {
+    MockitoAnnotations.initMocks(this);
+    expectedUri = new URI(TARGET);
+    when(mockLoadBalancerFactory.newLoadBalancer(any(Helper.class))).thenReturn(mockLoadBalancer);
+    transports = TestUtils.captureTransports(mockTransportFactory);
+    when(mockTransportFactory.getScheduledExecutorService())
+        .thenReturn(timer.getScheduledExecutorService());
+    when(executorPool.getObject()).thenReturn(executor.getScheduledExecutorService());
+    when(oobExecutorPool.getObject()).thenReturn(oobExecutor.getScheduledExecutorService());
+
+    channelBuilder = new ChannelBuilder()
+        .nameResolverFactory(new FakeNameResolverFactory.Builder(expectedUri).build())
+        .loadBalancerFactory(mockLoadBalancerFactory)
+        .userAgent(USER_AGENT)
+        .idleTimeout(AbstractManagedChannelImplBuilder.IDLE_MODE_MAX_TIMEOUT_DAYS, TimeUnit.DAYS);
+    channelBuilder.executorPool = executorPool;
+    channelBuilder.binlog = null;
+    channelBuilder.channelz = channelz;
+  }
+
+  @After
+  public void allPendingTasksAreRun() throws Exception {
+    // The "never" verifications in the tests only hold up if all due tasks are done.
+    // As for timer, although there may be scheduled tasks in a future time, since we don't test
+    // any time-related behavior in this test suite, we only care the tasks that are due. This
+    // would ignore any time-sensitive tasks, e.g., back-off and the idle timer.
+    assertTrue(timer.getDueTasks() + " should be empty", timer.getDueTasks().isEmpty());
+    assertEquals(executor.getPendingTasks() + " should be empty", 0, executor.numPendingTasks());
+    if (channel != null) {
+      channel.shutdownNow();
+      channel = null;
+    }
+  }
+
+  @Test
+  @SuppressWarnings("unchecked")
+  public void idleModeDisabled() {
+    channelBuilder.nameResolverFactory(
+        new FakeNameResolverFactory.Builder(expectedUri)
+            .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
+            .build());
+    createChannel();
+
+    // In this test suite, the channel is always created with idle mode disabled.
+    // No task is scheduled to enter idle mode
+    assertEquals(0, timer.numPendingTasks());
+    assertEquals(0, executor.numPendingTasks());
+  }
+
+  @Test
+  public void immediateDeadlineExceeded() {
+    createChannel();
+    ClientCall<String, Integer> call =
+        channel.newCall(method, CallOptions.DEFAULT.withDeadlineAfter(0, TimeUnit.NANOSECONDS));
+    call.start(mockCallListener, new Metadata());
+    assertEquals(1, executor.runDueTasks());
+
+    verify(mockCallListener).onClose(statusCaptor.capture(), any(Metadata.class));
+    Status status = statusCaptor.getValue();
+    assertSame(Status.DEADLINE_EXCEEDED.getCode(), status.getCode());
+  }
+
+  @Test
+  public void shutdownWithNoTransportsEverCreated() {
+    channelBuilder.nameResolverFactory(
+        new FakeNameResolverFactory.Builder(expectedUri)
+            .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
+            .build());
+    createChannel();
+    verify(executorPool).getObject();
+    verify(executorPool, never()).returnObject(anyObject());
+    verify(mockTransportFactory).getScheduledExecutorService();
+    verifyNoMoreInteractions(mockTransportFactory);
+    channel.shutdown();
+    assertTrue(channel.isShutdown());
+    assertTrue(channel.isTerminated());
+    verify(executorPool).returnObject(executor.getScheduledExecutorService());
+  }
+
+  @Test
+  public void channelzMembership() throws Exception {
+    createChannel();
+    assertNotNull(channelz.getRootChannel(channel.getLogId().getId()));
+    assertFalse(channelz.containsSubchannel(channel.getLogId()));
+    channel.shutdownNow();
+    channel.awaitTermination(5, TimeUnit.SECONDS);
+    assertNull(channelz.getRootChannel(channel.getLogId().getId()));
+    assertFalse(channelz.containsSubchannel(channel.getLogId()));
+  }
+
+  @Test
+  public void channelzMembership_subchannel() throws Exception {
+    createChannel();
+    assertNotNull(channelz.getRootChannel(channel.getLogId().getId()));
+
+    AbstractSubchannel subchannel =
+        (AbstractSubchannel) helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    // subchannels are not root channels
+    assertNull(channelz.getRootChannel(subchannel.getInternalSubchannel().getLogId().getId()));
+    assertTrue(channelz.containsSubchannel(subchannel.getInternalSubchannel().getLogId()));
+    assertThat(getStats(channel).subchannels)
+        .containsExactly(subchannel.getInternalSubchannel());
+
+    subchannel.requestConnection();
+    MockClientTransportInfo transportInfo = transports.poll();
+    assertNotNull(transportInfo);
+    assertTrue(channelz.containsClientSocket(transportInfo.transport.getLogId()));
+
+    // terminate transport
+    transportInfo.listener.transportTerminated();
+    assertFalse(channelz.containsClientSocket(transportInfo.transport.getLogId()));
+
+    // terminate subchannel
+    assertTrue(channelz.containsSubchannel(subchannel.getInternalSubchannel().getLogId()));
+    subchannel.shutdown();
+    timer.forwardTime(ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, TimeUnit.SECONDS);
+    timer.runDueTasks();
+    assertFalse(channelz.containsSubchannel(subchannel.getInternalSubchannel().getLogId()));
+    assertThat(getStats(channel).subchannels).isEmpty();
+
+    // channel still appears
+    assertNotNull(channelz.getRootChannel(channel.getLogId().getId()));
+  }
+
+  @Test
+  public void channelzMembership_oob() throws Exception {
+    createChannel();
+    OobChannel oob = (OobChannel) helper.createOobChannel(addressGroup, AUTHORITY);
+    // oob channels are not root channels
+    assertNull(channelz.getRootChannel(oob.getLogId().getId()));
+    assertTrue(channelz.containsSubchannel(oob.getLogId()));
+    assertThat(getStats(channel).subchannels).containsExactly(oob);
+    assertTrue(channelz.containsSubchannel(oob.getLogId()));
+
+    AbstractSubchannel subchannel = (AbstractSubchannel) oob.getSubchannel();
+    assertTrue(channelz.containsSubchannel(subchannel.getInternalSubchannel().getLogId()));
+    assertThat(getStats(oob).subchannels)
+        .containsExactly(subchannel.getInternalSubchannel());
+    assertTrue(channelz.containsSubchannel(subchannel.getInternalSubchannel().getLogId()));
+
+    oob.getSubchannel().requestConnection();
+    MockClientTransportInfo transportInfo = transports.poll();
+    assertNotNull(transportInfo);
+    assertTrue(channelz.containsClientSocket(transportInfo.transport.getLogId()));
+
+    // terminate transport
+    transportInfo.listener.transportTerminated();
+    assertFalse(channelz.containsClientSocket(transportInfo.transport.getLogId()));
+
+    // terminate oobchannel
+    oob.shutdown();
+    assertFalse(channelz.containsSubchannel(oob.getLogId()));
+    assertThat(getStats(channel).subchannels).isEmpty();
+    assertFalse(channelz.containsSubchannel(subchannel.getInternalSubchannel().getLogId()));
+
+    // channel still appears
+    assertNotNull(channelz.getRootChannel(channel.getLogId().getId()));
+  }
+
+  @Test
+  public void callsAndShutdown() {
+    subtestCallsAndShutdown(false, false);
+  }
+
+  @Test
+  public void callsAndShutdownNow() {
+    subtestCallsAndShutdown(true, false);
+  }
+
+  /** Make sure shutdownNow() after shutdown() has an effect. */
+  @Test
+  public void callsAndShutdownAndShutdownNow() {
+    subtestCallsAndShutdown(false, true);
+  }
+
+  private void subtestCallsAndShutdown(boolean shutdownNow, boolean shutdownNowAfterShutdown) {
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri).build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    createChannel();
+    verify(executorPool).getObject();
+    ClientStream mockStream = mock(ClientStream.class);
+    ClientStream mockStream2 = mock(ClientStream.class);
+    Metadata headers = new Metadata();
+    Metadata headers2 = new Metadata();
+
+    // Configure the picker so that first RPC goes to delayed transport, and second RPC goes to
+    // real transport.
+    Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    subchannel.requestConnection();
+    verify(mockTransportFactory)
+        .newClientTransport(any(SocketAddress.class), any(ClientTransportOptions.class));
+    MockClientTransportInfo transportInfo = transports.poll();
+    ConnectionClientTransport mockTransport = transportInfo.transport;
+    verify(mockTransport).start(any(ManagedClientTransport.Listener.class));
+    ManagedClientTransport.Listener transportListener = transportInfo.listener;
+    when(mockTransport.newStream(same(method), same(headers), same(CallOptions.DEFAULT)))
+        .thenReturn(mockStream);
+    when(mockTransport.newStream(same(method), same(headers2), same(CallOptions.DEFAULT)))
+        .thenReturn(mockStream2);
+    transportListener.transportReady();
+    when(mockPicker.pickSubchannel(
+        new PickSubchannelArgsImpl(method, headers, CallOptions.DEFAULT))).thenReturn(
+        PickResult.withNoResult());
+    when(mockPicker.pickSubchannel(
+        new PickSubchannelArgsImpl(method, headers2, CallOptions.DEFAULT))).thenReturn(
+        PickResult.withSubchannel(subchannel));
+    helper.updateBalancingState(READY, mockPicker);
+
+    // First RPC, will be pending
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    verify(mockTransportFactory)
+        .newClientTransport(any(SocketAddress.class), any(ClientTransportOptions.class));
+    call.start(mockCallListener, headers);
+
+    verify(mockTransport, never())
+        .newStream(same(method), same(headers), same(CallOptions.DEFAULT));
+
+    // Second RPC, will be assigned to the real transport
+    ClientCall<String, Integer> call2 = channel.newCall(method, CallOptions.DEFAULT);
+    call2.start(mockCallListener2, headers2);
+    verify(mockTransport).newStream(same(method), same(headers2), same(CallOptions.DEFAULT));
+    verify(mockTransport).newStream(same(method), same(headers2), same(CallOptions.DEFAULT));
+    verify(mockStream2).start(any(ClientStreamListener.class));
+
+    // Shutdown
+    if (shutdownNow) {
+      channel.shutdownNow();
+    } else {
+      channel.shutdown();
+      if (shutdownNowAfterShutdown) {
+        channel.shutdownNow();
+        shutdownNow = true;
+      }
+    }
+    assertTrue(channel.isShutdown());
+    assertFalse(channel.isTerminated());
+    assertEquals(1, nameResolverFactory.resolvers.size());
+    verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
+
+    // Further calls should fail without going to the transport
+    ClientCall<String, Integer> call3 = channel.newCall(method, CallOptions.DEFAULT);
+    call3.start(mockCallListener3, headers2);
+    timer.runDueTasks();
+    executor.runDueTasks();
+
+    verify(mockCallListener3).onClose(statusCaptor.capture(), any(Metadata.class));
+    assertSame(Status.Code.UNAVAILABLE, statusCaptor.getValue().getCode());
+
+    if (shutdownNow) {
+      // LoadBalancer and NameResolver are shut down as soon as delayed transport is terminated.
+      verify(mockLoadBalancer).shutdown();
+      assertTrue(nameResolverFactory.resolvers.get(0).shutdown);
+      // call should have been aborted by delayed transport
+      executor.runDueTasks();
+      verify(mockCallListener).onClose(same(ManagedChannelImpl.SHUTDOWN_NOW_STATUS),
+          any(Metadata.class));
+    } else {
+      // LoadBalancer and NameResolver are still running.
+      verify(mockLoadBalancer, never()).shutdown();
+      assertFalse(nameResolverFactory.resolvers.get(0).shutdown);
+      // call and call2 are still alive, and can still be assigned to a real transport
+      SubchannelPicker picker2 = mock(SubchannelPicker.class);
+      when(picker2.pickSubchannel(new PickSubchannelArgsImpl(method, headers, CallOptions.DEFAULT)))
+          .thenReturn(PickResult.withSubchannel(subchannel));
+      helper.updateBalancingState(READY, picker2);
+      executor.runDueTasks();
+      verify(mockTransport).newStream(same(method), same(headers), same(CallOptions.DEFAULT));
+      verify(mockStream).start(any(ClientStreamListener.class));
+    }
+
+    // After call is moved out of delayed transport, LoadBalancer, NameResolver and the transports
+    // will be shutdown.
+    verify(mockLoadBalancer).shutdown();
+    assertTrue(nameResolverFactory.resolvers.get(0).shutdown);
+
+    if (shutdownNow) {
+      // Channel shutdownNow() all subchannels after shutting down LoadBalancer
+      verify(mockTransport).shutdownNow(ManagedChannelImpl.SHUTDOWN_NOW_STATUS);
+    } else {
+      verify(mockTransport, never()).shutdownNow(any(Status.class));
+    }
+    // LoadBalancer should shutdown the subchannel
+    subchannel.shutdown();
+    if (shutdownNow) {
+      verify(mockTransport).shutdown(same(ManagedChannelImpl.SHUTDOWN_NOW_STATUS));
+    } else {
+      verify(mockTransport).shutdown(same(ManagedChannelImpl.SHUTDOWN_STATUS));
+    }
+
+    // Killing the remaining real transport will terminate the channel
+    transportListener.transportShutdown(Status.UNAVAILABLE);
+    assertFalse(channel.isTerminated());
+    verify(executorPool, never()).returnObject(anyObject());
+    transportListener.transportTerminated();
+    assertTrue(channel.isTerminated());
+    verify(executorPool).returnObject(executor.getScheduledExecutorService());
+    verifyNoMoreInteractions(oobExecutorPool);
+
+    verify(mockTransportFactory)
+        .newClientTransport(any(SocketAddress.class), any(ClientTransportOptions.class));
+    verify(mockTransportFactory).close();
+    verify(mockTransport, atLeast(0)).getLogId();
+    verifyNoMoreInteractions(mockTransport);
+  }
+
+  @Test
+  public void noMoreCallbackAfterLoadBalancerShutdown() {
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri)
+            .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
+            .build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    Status resolutionError = Status.UNAVAILABLE.withDescription("Resolution failed");
+    createChannel();
+
+    FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0);
+    verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
+    verify(mockLoadBalancer).handleResolvedAddressGroups(
+        eq(Arrays.asList(addressGroup)), eq(Attributes.EMPTY));
+
+    Subchannel subchannel1 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    Subchannel subchannel2 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    subchannel1.requestConnection();
+    subchannel2.requestConnection();
+    verify(mockTransportFactory, times(2))
+        .newClientTransport(any(SocketAddress.class), any(ClientTransportOptions.class));
+    MockClientTransportInfo transportInfo1 = transports.poll();
+    MockClientTransportInfo transportInfo2 = transports.poll();
+
+    // LoadBalancer receives all sorts of callbacks
+    transportInfo1.listener.transportReady();
+    verify(mockLoadBalancer, times(2))
+        .handleSubchannelState(same(subchannel1), stateInfoCaptor.capture());
+    assertSame(CONNECTING, stateInfoCaptor.getAllValues().get(0).getState());
+    assertSame(READY, stateInfoCaptor.getAllValues().get(1).getState());
+
+    verify(mockLoadBalancer)
+        .handleSubchannelState(same(subchannel2), stateInfoCaptor.capture());
+    assertSame(CONNECTING, stateInfoCaptor.getValue().getState());
+
+    resolver.listener.onError(resolutionError);
+    verify(mockLoadBalancer).handleNameResolutionError(resolutionError);
+
+    verifyNoMoreInteractions(mockLoadBalancer);
+
+    channel.shutdown();
+    verify(mockLoadBalancer).shutdown();
+
+    // No more callback should be delivered to LoadBalancer after it's shut down
+    transportInfo2.listener.transportReady();
+    resolver.listener.onError(resolutionError);
+    resolver.resolved();
+    verifyNoMoreInteractions(mockLoadBalancer);
+  }
+
+  @Test
+  public void interceptor() throws Exception {
+    final AtomicLong atomic = new AtomicLong();
+    ClientInterceptor interceptor = new ClientInterceptor() {
+      @Override
+      public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> interceptCall(
+          MethodDescriptor<RequestT, ResponseT> method, CallOptions callOptions,
+          Channel next) {
+        atomic.set(1);
+        return next.newCall(method, callOptions);
+      }
+    };
+    createChannel(interceptor);
+    assertNotNull(channel.newCall(method, CallOptions.DEFAULT));
+    assertEquals(1, atomic.get());
+  }
+
+  @Test
+  public void callOptionsExecutor() {
+    Metadata headers = new Metadata();
+    ClientStream mockStream = mock(ClientStream.class);
+    FakeClock callExecutor = new FakeClock();
+    createChannel();
+
+    // Start a call with a call executor
+    CallOptions options =
+        CallOptions.DEFAULT.withExecutor(callExecutor.getScheduledExecutorService());
+    ClientCall<String, Integer> call = channel.newCall(method, options);
+    call.start(mockCallListener, headers);
+
+    // Make the transport available
+    Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    verify(mockTransportFactory, never())
+        .newClientTransport(any(SocketAddress.class), any(ClientTransportOptions.class));
+    subchannel.requestConnection();
+    verify(mockTransportFactory)
+        .newClientTransport(any(SocketAddress.class), any(ClientTransportOptions.class));
+    MockClientTransportInfo transportInfo = transports.poll();
+    ConnectionClientTransport mockTransport = transportInfo.transport;
+    ManagedClientTransport.Listener transportListener = transportInfo.listener;
+    when(mockTransport.newStream(same(method), same(headers), any(CallOptions.class)))
+        .thenReturn(mockStream);
+    transportListener.transportReady();
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withSubchannel(subchannel));
+    assertEquals(0, callExecutor.numPendingTasks());
+    helper.updateBalancingState(READY, mockPicker);
+
+    // Real streams are started in the call executor if they were previously buffered.
+    assertEquals(1, callExecutor.runDueTasks());
+    verify(mockTransport).newStream(same(method), same(headers), same(options));
+    verify(mockStream).start(streamListenerCaptor.capture());
+
+    // Call listener callbacks are also run in the call executor
+    ClientStreamListener streamListener = streamListenerCaptor.getValue();
+    Metadata trailers = new Metadata();
+    assertEquals(0, callExecutor.numPendingTasks());
+    streamListener.closed(Status.CANCELLED, trailers);
+    verify(mockCallListener, never()).onClose(same(Status.CANCELLED), same(trailers));
+    assertEquals(1, callExecutor.runDueTasks());
+    verify(mockCallListener).onClose(same(Status.CANCELLED), same(trailers));
+
+
+    transportListener.transportShutdown(Status.UNAVAILABLE);
+    transportListener.transportTerminated();
+
+    // Clean up as much as possible to allow the channel to terminate.
+    subchannel.shutdown();
+    timer.forwardNanos(
+        TimeUnit.SECONDS.toNanos(ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS));
+  }
+
+  @Test
+  public void nameResolutionFailed() {
+    Status error = Status.UNAVAILABLE.withCause(new Throwable("fake name resolution error"));
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri)
+            .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
+            .setError(error)
+            .build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    // Name resolution is started as soon as channel is created.
+    createChannel();
+    FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0);
+    verify(mockLoadBalancer).handleNameResolutionError(same(error));
+    assertEquals(1, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER));
+
+    timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS - 1);
+    assertEquals(0, resolver.refreshCalled);
+
+    timer.forwardNanos(1);
+    assertEquals(1, resolver.refreshCalled);
+    verify(mockLoadBalancer, times(2)).handleNameResolutionError(same(error));
+
+    // Verify an additional name resolution failure does not schedule another timer
+    resolver.refresh();
+    verify(mockLoadBalancer, times(3)).handleNameResolutionError(same(error));
+    assertEquals(1, timer.numPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER));
+
+    // Allow the next refresh attempt to succeed
+    resolver.error = null;
+
+    // For the second attempt, the backoff should occur at RECONNECT_BACKOFF_INTERVAL_NANOS * 2
+    timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS * 2 - 1);
+    assertEquals(2, resolver.refreshCalled);
+    timer.forwardNanos(1);
+    assertEquals(3, resolver.refreshCalled);
+    assertEquals(0, timer.numPendingTasks());
+
+    // Verify that the successful resolution reset the backoff policy
+    resolver.listener.onError(error);
+    timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS - 1);
+    assertEquals(3, resolver.refreshCalled);
+    timer.forwardNanos(1);
+    assertEquals(4, resolver.refreshCalled);
+    assertEquals(0, timer.numPendingTasks());
+  }
+
+  @Test
+  public void nameResolutionFailed_delayedTransportShutdownCancelsBackoff() {
+    Status error = Status.UNAVAILABLE.withCause(new Throwable("fake name resolution error"));
+
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri).setError(error).build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    // Name resolution is started as soon as channel is created.
+    createChannel();
+    verify(mockLoadBalancer).handleNameResolutionError(same(error));
+
+    FakeClock.ScheduledTask nameResolverBackoff = getNameResolverRefresh();
+    assertNotNull(nameResolverBackoff);
+    assertFalse(nameResolverBackoff.isCancelled());
+
+    // Add a pending call to the delayed transport
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    Metadata headers = new Metadata();
+    call.start(mockCallListener, headers);
+
+    // The pending call on the delayed transport stops the name resolver backoff from cancelling
+    channel.shutdown();
+    assertFalse(nameResolverBackoff.isCancelled());
+
+    // Notify that a subchannel is ready, which drains the delayed transport
+    SubchannelPicker picker = mock(SubchannelPicker.class);
+    Status status = Status.UNAVAILABLE.withDescription("for test");
+    when(picker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withDrop(status));
+    helper.updateBalancingState(READY, picker);
+    executor.runDueTasks();
+    verify(mockCallListener).onClose(same(status), any(Metadata.class));
+
+    assertTrue(nameResolverBackoff.isCancelled());
+  }
+
+  @Test
+  public void nameResolverReturnsEmptySubLists() {
+    String errorDescription = "NameResolver returned an empty list";
+
+    // Pass a FakeNameResolverFactory with an empty list
+    createChannel();
+
+    // LoadBalancer received the error
+    verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
+    verify(mockLoadBalancer).handleNameResolutionError(statusCaptor.capture());
+    Status status = statusCaptor.getValue();
+    assertSame(Status.Code.UNAVAILABLE, status.getCode());
+    assertEquals(errorDescription, status.getDescription());
+  }
+
+  @Test
+  public void loadBalancerThrowsInHandleResolvedAddresses() {
+    RuntimeException ex = new RuntimeException("simulated");
+    // Delay the success of name resolution until allResolved() is called
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri)
+            .setResolvedAtStart(false)
+            .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
+            .build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    createChannel();
+
+    verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
+    doThrow(ex).when(mockLoadBalancer).handleResolvedAddressGroups(
+        Matchers.<List<EquivalentAddressGroup>>anyObject(), any(Attributes.class));
+
+    // NameResolver returns addresses.
+    nameResolverFactory.allResolved();
+
+    // Exception thrown from balancer is caught by ChannelExecutor, making channel enter panic mode.
+    verifyPanicMode(ex);
+  }
+
+  @Test
+  public void nameResolvedAfterChannelShutdown() {
+    // Delay the success of name resolution until allResolved() is called.
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    createChannel();
+
+    channel.shutdown();
+
+    assertTrue(channel.isShutdown());
+    assertTrue(channel.isTerminated());
+    verify(mockLoadBalancer).shutdown();
+    // Name resolved after the channel is shut down, which is possible if the name resolution takes
+    // time and is not cancellable. The resolved address will be dropped.
+    nameResolverFactory.allResolved();
+    verifyNoMoreInteractions(mockLoadBalancer);
+  }
+
+  /**
+   * Verify that if the first resolved address points to a server that cannot be connected, the call
+   * will end up with the second address which works.
+   */
+  @Test
+  public void firstResolvedServerFailedToConnect() throws Exception {
+    final SocketAddress goodAddress = new SocketAddress() {
+        @Override public String toString() {
+          return "goodAddress";
+        }
+      };
+    final SocketAddress badAddress = new SocketAddress() {
+        @Override public String toString() {
+          return "badAddress";
+        }
+      };
+    InOrder inOrder = inOrder(mockLoadBalancer);
+
+    List<SocketAddress> resolvedAddrs = Arrays.asList(badAddress, goodAddress);
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri)
+            .setServers(Collections.singletonList(new EquivalentAddressGroup(resolvedAddrs)))
+            .build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    createChannel();
+
+    // Start the call
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    Metadata headers = new Metadata();
+    call.start(mockCallListener, headers);
+    executor.runDueTasks();
+
+    // Simulate name resolution results
+    EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(resolvedAddrs);
+    inOrder.verify(mockLoadBalancer).handleResolvedAddressGroups(
+        eq(Arrays.asList(addressGroup)), eq(Attributes.EMPTY));
+    Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withSubchannel(subchannel));
+    subchannel.requestConnection();
+    inOrder.verify(mockLoadBalancer).handleSubchannelState(
+        same(subchannel), stateInfoCaptor.capture());
+    assertEquals(CONNECTING, stateInfoCaptor.getValue().getState());
+
+    // The channel will starts with the first address (badAddress)
+    verify(mockTransportFactory)
+        .newClientTransport(same(badAddress), any(ClientTransportOptions.class));
+    verify(mockTransportFactory, times(0))
+        .newClientTransport(same(goodAddress), any(ClientTransportOptions.class));
+
+    MockClientTransportInfo badTransportInfo = transports.poll();
+    // Which failed to connect
+    badTransportInfo.listener.transportShutdown(Status.UNAVAILABLE);
+    inOrder.verifyNoMoreInteractions();
+
+    // The channel then try the second address (goodAddress)
+    verify(mockTransportFactory)
+        .newClientTransport(same(goodAddress), any(ClientTransportOptions.class));
+    MockClientTransportInfo goodTransportInfo = transports.poll();
+    when(goodTransportInfo.transport.newStream(
+            any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)))
+        .thenReturn(mock(ClientStream.class));
+
+    goodTransportInfo.listener.transportReady();
+    inOrder.verify(mockLoadBalancer).handleSubchannelState(
+        same(subchannel), stateInfoCaptor.capture());
+    assertEquals(READY, stateInfoCaptor.getValue().getState());
+
+    // A typical LoadBalancer will call this once the subchannel becomes READY
+    helper.updateBalancingState(READY, mockPicker);
+    // Delayed transport uses the app executor to create real streams.
+    executor.runDueTasks();
+
+    verify(goodTransportInfo.transport).newStream(same(method), same(headers),
+        same(CallOptions.DEFAULT));
+    // The bad transport was never used.
+    verify(badTransportInfo.transport, times(0)).newStream(any(MethodDescriptor.class),
+        any(Metadata.class), any(CallOptions.class));
+  }
+
+  @Test
+  public void failFastRpcFailFromErrorFromBalancer() {
+    subtestFailRpcFromBalancer(false, false, true);
+  }
+
+  @Test
+  public void failFastRpcFailFromDropFromBalancer() {
+    subtestFailRpcFromBalancer(false, true, true);
+  }
+
+  @Test
+  public void waitForReadyRpcImmuneFromErrorFromBalancer() {
+    subtestFailRpcFromBalancer(true, false, false);
+  }
+
+  @Test
+  public void waitForReadyRpcFailFromDropFromBalancer() {
+    subtestFailRpcFromBalancer(true, true, true);
+  }
+
+  private void subtestFailRpcFromBalancer(boolean waitForReady, boolean drop, boolean shouldFail) {
+    createChannel();
+
+    // This call will be buffered by the channel, thus involve delayed transport
+    CallOptions callOptions = CallOptions.DEFAULT;
+    if (waitForReady) {
+      callOptions = callOptions.withWaitForReady();
+    } else {
+      callOptions = callOptions.withoutWaitForReady();
+    }
+    ClientCall<String, Integer> call1 = channel.newCall(method, callOptions);
+    call1.start(mockCallListener, new Metadata());
+
+    SubchannelPicker picker = mock(SubchannelPicker.class);
+    Status status = Status.UNAVAILABLE.withDescription("for test");
+
+    when(picker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(drop ? PickResult.withDrop(status) : PickResult.withError(status));
+    helper.updateBalancingState(READY, picker);
+
+    executor.runDueTasks();
+    if (shouldFail) {
+      verify(mockCallListener).onClose(same(status), any(Metadata.class));
+    } else {
+      verifyZeroInteractions(mockCallListener);
+    }
+
+    // This call doesn't involve delayed transport
+    ClientCall<String, Integer> call2 = channel.newCall(method, callOptions);
+    call2.start(mockCallListener2, new Metadata());
+
+    executor.runDueTasks();
+    if (shouldFail) {
+      verify(mockCallListener2).onClose(same(status), any(Metadata.class));
+    } else {
+      verifyZeroInteractions(mockCallListener2);
+    }
+  }
+
+  /**
+   * Verify that if all resolved addresses failed to connect, a fail-fast call will fail, while a
+   * wait-for-ready call will still be buffered.
+   */
+  @Test
+  public void allServersFailedToConnect() throws Exception {
+    final SocketAddress addr1 = new SocketAddress() {
+        @Override public String toString() {
+          return "addr1";
+        }
+      };
+    final SocketAddress addr2 = new SocketAddress() {
+        @Override public String toString() {
+          return "addr2";
+        }
+      };
+    InOrder inOrder = inOrder(mockLoadBalancer);
+
+    List<SocketAddress> resolvedAddrs = Arrays.asList(addr1, addr2);
+
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri)
+            .setServers(Collections.singletonList(new EquivalentAddressGroup(resolvedAddrs)))
+            .build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    createChannel();
+
+    // Start a wait-for-ready call
+    ClientCall<String, Integer> call =
+        channel.newCall(method, CallOptions.DEFAULT.withWaitForReady());
+    Metadata headers = new Metadata();
+    call.start(mockCallListener, headers);
+    // ... and a fail-fast call
+    ClientCall<String, Integer> call2 =
+        channel.newCall(method, CallOptions.DEFAULT.withoutWaitForReady());
+    call2.start(mockCallListener2, headers);
+    executor.runDueTasks();
+
+    // Simulate name resolution results
+    EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(resolvedAddrs);
+    inOrder.verify(mockLoadBalancer).handleResolvedAddressGroups(
+        eq(Arrays.asList(addressGroup)), eq(Attributes.EMPTY));
+    Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withSubchannel(subchannel));
+    subchannel.requestConnection();
+
+    inOrder.verify(mockLoadBalancer).handleSubchannelState(
+        same(subchannel), stateInfoCaptor.capture());
+    assertEquals(CONNECTING, stateInfoCaptor.getValue().getState());
+
+    // Connecting to server1, which will fail
+    verify(mockTransportFactory)
+        .newClientTransport(same(addr1), any(ClientTransportOptions.class));
+    verify(mockTransportFactory, times(0))
+        .newClientTransport(same(addr2), any(ClientTransportOptions.class));
+    MockClientTransportInfo transportInfo1 = transports.poll();
+    transportInfo1.listener.transportShutdown(Status.UNAVAILABLE);
+
+    // Connecting to server2, which will fail too
+    verify(mockTransportFactory)
+        .newClientTransport(same(addr2), any(ClientTransportOptions.class));
+    MockClientTransportInfo transportInfo2 = transports.poll();
+    Status server2Error = Status.UNAVAILABLE.withDescription("Server2 failed to connect");
+    transportInfo2.listener.transportShutdown(server2Error);
+
+    // ... which makes the subchannel enter TRANSIENT_FAILURE. The last error Status is propagated
+    // to LoadBalancer.
+    inOrder.verify(mockLoadBalancer).handleSubchannelState(
+        same(subchannel), stateInfoCaptor.capture());
+    assertEquals(TRANSIENT_FAILURE, stateInfoCaptor.getValue().getState());
+    assertSame(server2Error, stateInfoCaptor.getValue().getStatus());
+
+    // A typical LoadBalancer would create a picker with error
+    SubchannelPicker picker2 = mock(SubchannelPicker.class);
+    when(picker2.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withError(server2Error));
+    helper.updateBalancingState(TRANSIENT_FAILURE, picker2);
+    executor.runDueTasks();
+
+    // ... which fails the fail-fast call
+    verify(mockCallListener2).onClose(same(server2Error), any(Metadata.class));
+    // ... while the wait-for-ready call stays
+    verifyNoMoreInteractions(mockCallListener);
+    // No real stream was ever created
+    verify(transportInfo1.transport, times(0))
+        .newStream(any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+    verify(transportInfo2.transport, times(0))
+        .newStream(any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+  }
+
+  @Test
+  public void subchannels() {
+    createChannel();
+
+    // createSubchannel() always return a new Subchannel
+    Attributes attrs1 = Attributes.newBuilder().set(SUBCHANNEL_ATTR_KEY, "attr1").build();
+    Attributes attrs2 = Attributes.newBuilder().set(SUBCHANNEL_ATTR_KEY, "attr2").build();
+    Subchannel sub1 = helper.createSubchannel(addressGroup, attrs1);
+    Subchannel sub2 = helper.createSubchannel(addressGroup, attrs2);
+    assertNotSame(sub1, sub2);
+    assertNotSame(attrs1, attrs2);
+    assertSame(attrs1, sub1.getAttributes());
+    assertSame(attrs2, sub2.getAttributes());
+    assertSame(addressGroup, sub1.getAddresses());
+    assertSame(addressGroup, sub2.getAddresses());
+
+    // requestConnection()
+    verify(mockTransportFactory, never())
+        .newClientTransport(any(SocketAddress.class), any(ClientTransportOptions.class));
+    sub1.requestConnection();
+    verify(mockTransportFactory).newClientTransport(socketAddress, clientTransportOptions);
+    MockClientTransportInfo transportInfo1 = transports.poll();
+    assertNotNull(transportInfo1);
+
+    sub2.requestConnection();
+    verify(mockTransportFactory, times(2))
+        .newClientTransport(socketAddress, clientTransportOptions);
+    MockClientTransportInfo transportInfo2 = transports.poll();
+    assertNotNull(transportInfo2);
+
+    sub1.requestConnection();
+    sub2.requestConnection();
+    verify(mockTransportFactory, times(2))
+        .newClientTransport(socketAddress, clientTransportOptions);
+
+    // shutdown() has a delay
+    sub1.shutdown();
+    timer.forwardTime(ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS - 1, TimeUnit.SECONDS);
+    sub1.shutdown();
+    verify(transportInfo1.transport, never()).shutdown(any(Status.class));
+    timer.forwardTime(1, TimeUnit.SECONDS);
+    verify(transportInfo1.transport).shutdown(same(ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_STATUS));
+
+    // ... but not after Channel is terminating
+    verify(mockLoadBalancer, never()).shutdown();
+    channel.shutdown();
+    verify(mockLoadBalancer).shutdown();
+    verify(transportInfo2.transport, never()).shutdown(any(Status.class));
+
+    sub2.shutdown();
+    verify(transportInfo2.transport).shutdown(same(ManagedChannelImpl.SHUTDOWN_STATUS));
+
+    // Cleanup
+    transportInfo1.listener.transportShutdown(Status.UNAVAILABLE);
+    transportInfo1.listener.transportTerminated();
+    transportInfo2.listener.transportShutdown(Status.UNAVAILABLE);
+    transportInfo2.listener.transportTerminated();
+    timer.forwardTime(ManagedChannelImpl.SUBCHANNEL_SHUTDOWN_DELAY_SECONDS, TimeUnit.SECONDS);
+  }
+
+  @Test
+  public void subchannelsWhenChannelShutdownNow() {
+    createChannel();
+    Subchannel sub1 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    Subchannel sub2 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    sub1.requestConnection();
+    sub2.requestConnection();
+
+    assertEquals(2, transports.size());
+    MockClientTransportInfo ti1 = transports.poll();
+    MockClientTransportInfo ti2 = transports.poll();
+
+    ti1.listener.transportReady();
+    ti2.listener.transportReady();
+
+    channel.shutdownNow();
+    verify(ti1.transport).shutdownNow(any(Status.class));
+    verify(ti2.transport).shutdownNow(any(Status.class));
+
+    ti1.listener.transportShutdown(Status.UNAVAILABLE.withDescription("shutdown now"));
+    ti2.listener.transportShutdown(Status.UNAVAILABLE.withDescription("shutdown now"));
+    ti1.listener.transportTerminated();
+
+    assertFalse(channel.isTerminated());
+    ti2.listener.transportTerminated();
+    assertTrue(channel.isTerminated());
+  }
+
+  @Test
+  public void subchannelsNoConnectionShutdown() {
+    createChannel();
+    Subchannel sub1 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    Subchannel sub2 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+
+    channel.shutdown();
+    verify(mockLoadBalancer).shutdown();
+    sub1.shutdown();
+    assertFalse(channel.isTerminated());
+    sub2.shutdown();
+    assertTrue(channel.isTerminated());
+    verify(mockTransportFactory, never())
+        .newClientTransport(any(SocketAddress.class), any(ClientTransportOptions.class));
+  }
+
+  @Test
+  public void subchannelsNoConnectionShutdownNow() {
+    createChannel();
+    helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    channel.shutdownNow();
+
+    verify(mockLoadBalancer).shutdown();
+    // Channel's shutdownNow() will call shutdownNow() on all subchannels and oobchannels.
+    // Therefore, channel is terminated without relying on LoadBalancer to shutdown subchannels.
+    assertTrue(channel.isTerminated());
+    verify(mockTransportFactory, never())
+        .newClientTransport(any(SocketAddress.class), any(ClientTransportOptions.class));
+  }
+
+  @Test
+  public void oobchannels() {
+    createChannel();
+
+    ManagedChannel oob1 = helper.createOobChannel(addressGroup, "oob1authority");
+    ManagedChannel oob2 = helper.createOobChannel(addressGroup, "oob2authority");
+    verify(oobExecutorPool, times(2)).getObject();
+
+    assertEquals("oob1authority", oob1.authority());
+    assertEquals("oob2authority", oob2.authority());
+
+    // OOB channels create connections lazily.  A new call will initiate the connection.
+    Metadata headers = new Metadata();
+    ClientCall<String, Integer> call = oob1.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, headers);
+    verify(mockTransportFactory)
+        .newClientTransport(
+            socketAddress,
+            new ClientTransportOptions().setAuthority("oob1authority").setUserAgent(USER_AGENT));
+    MockClientTransportInfo transportInfo = transports.poll();
+    assertNotNull(transportInfo);
+
+    assertEquals(0, oobExecutor.numPendingTasks());
+    transportInfo.listener.transportReady();
+    assertEquals(1, oobExecutor.runDueTasks());
+    verify(transportInfo.transport).newStream(same(method), same(headers),
+        same(CallOptions.DEFAULT));
+
+    // The transport goes away
+    transportInfo.listener.transportShutdown(Status.UNAVAILABLE);
+    transportInfo.listener.transportTerminated();
+
+    // A new call will trigger a new transport
+    ClientCall<String, Integer> call2 = oob1.newCall(method, CallOptions.DEFAULT);
+    call2.start(mockCallListener2, headers);
+    ClientCall<String, Integer> call3 =
+        oob1.newCall(method, CallOptions.DEFAULT.withWaitForReady());
+    call3.start(mockCallListener3, headers);
+    verify(mockTransportFactory, times(2)).newClientTransport(
+        socketAddress,
+        new ClientTransportOptions().setAuthority("oob1authority").setUserAgent(USER_AGENT));
+    transportInfo = transports.poll();
+    assertNotNull(transportInfo);
+
+    // This transport fails
+    Status transportError = Status.UNAVAILABLE.withDescription("Connection refused");
+    assertEquals(0, oobExecutor.numPendingTasks());
+    transportInfo.listener.transportShutdown(transportError);
+    assertTrue(oobExecutor.runDueTasks() > 0);
+
+    // Fail-fast RPC will fail, while wait-for-ready RPC will still be pending
+    verify(mockCallListener2).onClose(same(transportError), any(Metadata.class));
+    verify(mockCallListener3, never()).onClose(any(Status.class), any(Metadata.class));
+
+    // Shutdown
+    assertFalse(oob1.isShutdown());
+    assertFalse(oob2.isShutdown());
+    oob1.shutdown();
+    verify(oobExecutorPool, never()).returnObject(anyObject());
+    oob2.shutdownNow();
+    assertTrue(oob1.isShutdown());
+    assertTrue(oob2.isShutdown());
+    assertTrue(oob2.isTerminated());
+    verify(oobExecutorPool).returnObject(oobExecutor.getScheduledExecutorService());
+
+    // New RPCs will be rejected.
+    assertEquals(0, oobExecutor.numPendingTasks());
+    ClientCall<String, Integer> call4 = oob1.newCall(method, CallOptions.DEFAULT);
+    ClientCall<String, Integer> call5 = oob2.newCall(method, CallOptions.DEFAULT);
+    call4.start(mockCallListener4, headers);
+    call5.start(mockCallListener5, headers);
+    assertTrue(oobExecutor.runDueTasks() > 0);
+    verify(mockCallListener4).onClose(statusCaptor.capture(), any(Metadata.class));
+    Status status4 = statusCaptor.getValue();
+    assertEquals(Status.Code.UNAVAILABLE, status4.getCode());
+    verify(mockCallListener5).onClose(statusCaptor.capture(), any(Metadata.class));
+    Status status5 = statusCaptor.getValue();
+    assertEquals(Status.Code.UNAVAILABLE, status5.getCode());
+
+    // The pending RPC will still be pending
+    verify(mockCallListener3, never()).onClose(any(Status.class), any(Metadata.class));
+
+    // This will shutdownNow() the delayed transport, terminating the pending RPC
+    assertEquals(0, oobExecutor.numPendingTasks());
+    oob1.shutdownNow();
+    assertTrue(oobExecutor.runDueTasks() > 0);
+    verify(mockCallListener3).onClose(any(Status.class), any(Metadata.class));
+
+    // Shut down the channel, and it will not terminated because OOB channel has not.
+    channel.shutdown();
+    assertFalse(channel.isTerminated());
+    // Delayed transport has already terminated.  Terminating the transport terminates the
+    // subchannel, which in turn terimates the OOB channel, which terminates the channel.
+    assertFalse(oob1.isTerminated());
+    verify(oobExecutorPool).returnObject(oobExecutor.getScheduledExecutorService());
+    transportInfo.listener.transportTerminated();
+    assertTrue(oob1.isTerminated());
+    assertTrue(channel.isTerminated());
+    verify(oobExecutorPool, times(2)).returnObject(oobExecutor.getScheduledExecutorService());
+  }
+
+  @Test
+  public void oobChannelsWhenChannelShutdownNow() {
+    createChannel();
+    ManagedChannel oob1 = helper.createOobChannel(addressGroup, "oob1Authority");
+    ManagedChannel oob2 = helper.createOobChannel(addressGroup, "oob2Authority");
+
+    oob1.newCall(method, CallOptions.DEFAULT).start(mockCallListener, new Metadata());
+    oob2.newCall(method, CallOptions.DEFAULT).start(mockCallListener2, new Metadata());
+
+    assertEquals(2, transports.size());
+    MockClientTransportInfo ti1 = transports.poll();
+    MockClientTransportInfo ti2 = transports.poll();
+
+    ti1.listener.transportReady();
+    ti2.listener.transportReady();
+
+    channel.shutdownNow();
+    verify(ti1.transport).shutdownNow(any(Status.class));
+    verify(ti2.transport).shutdownNow(any(Status.class));
+
+    ti1.listener.transportShutdown(Status.UNAVAILABLE.withDescription("shutdown now"));
+    ti2.listener.transportShutdown(Status.UNAVAILABLE.withDescription("shutdown now"));
+    ti1.listener.transportTerminated();
+
+    assertFalse(channel.isTerminated());
+    ti2.listener.transportTerminated();
+    assertTrue(channel.isTerminated());
+  }
+
+  @Test
+  public void oobChannelsNoConnectionShutdown() {
+    createChannel();
+    ManagedChannel oob1 = helper.createOobChannel(addressGroup, "oob1Authority");
+    ManagedChannel oob2 = helper.createOobChannel(addressGroup, "oob2Authority");
+    channel.shutdown();
+
+    verify(mockLoadBalancer).shutdown();
+    oob1.shutdown();
+    assertTrue(oob1.isTerminated());
+    assertFalse(channel.isTerminated());
+    oob2.shutdown();
+    assertTrue(oob2.isTerminated());
+    assertTrue(channel.isTerminated());
+    verify(mockTransportFactory, never())
+        .newClientTransport(any(SocketAddress.class), any(ClientTransportOptions.class));
+  }
+
+  @Test
+  public void oobChannelsNoConnectionShutdownNow() {
+    createChannel();
+    helper.createOobChannel(addressGroup, "oob1Authority");
+    helper.createOobChannel(addressGroup, "oob2Authority");
+    channel.shutdownNow();
+
+    verify(mockLoadBalancer).shutdown();
+    assertTrue(channel.isTerminated());
+    // Channel's shutdownNow() will call shutdownNow() on all subchannels and oobchannels.
+    // Therefore, channel is terminated without relying on LoadBalancer to shutdown oobchannels.
+    verify(mockTransportFactory, never())
+        .newClientTransport(any(SocketAddress.class), any(ClientTransportOptions.class));
+  }
+
+  @Test
+  public void refreshNameResolutionWhenSubchannelConnectionFailed() {
+    subtestRefreshNameResolutionWhenConnectionFailed(false);
+  }
+
+  @Test
+  public void refreshNameResolutionWhenOobChannelConnectionFailed() {
+    subtestRefreshNameResolutionWhenConnectionFailed(true);
+  }
+
+  private void subtestRefreshNameResolutionWhenConnectionFailed(boolean isOobChannel) {
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri)
+            .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
+            .build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    createChannel();
+    FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0);
+
+    if (isOobChannel) {
+      OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "oobAuthority");
+      oobChannel.getSubchannel().requestConnection();
+    } else {
+      Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+      subchannel.requestConnection();
+    }
+    
+    MockClientTransportInfo transportInfo = transports.poll();
+    assertNotNull(transportInfo);
+
+    // Transport closed when connecting
+    assertEquals(0, resolver.refreshCalled);
+    transportInfo.listener.transportShutdown(Status.UNAVAILABLE);
+    assertEquals(1, resolver.refreshCalled);
+
+    timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS);
+    transportInfo = transports.poll();
+    assertNotNull(transportInfo);
+
+    transportInfo.listener.transportReady();
+
+    // Transport closed when ready
+    assertEquals(1, resolver.refreshCalled);
+    transportInfo.listener.transportShutdown(Status.UNAVAILABLE);
+    assertEquals(2, resolver.refreshCalled);
+  }
+
+  @Test
+  public void uriPattern() {
+    assertTrue(ManagedChannelImpl.URI_PATTERN.matcher("a:/").matches());
+    assertTrue(ManagedChannelImpl.URI_PATTERN.matcher("Z019+-.:/!@ #~ ").matches());
+    assertFalse(ManagedChannelImpl.URI_PATTERN.matcher("a/:").matches()); // "/:" not matched
+    assertFalse(ManagedChannelImpl.URI_PATTERN.matcher("0a:/").matches()); // '0' not matched
+    assertFalse(ManagedChannelImpl.URI_PATTERN.matcher("a,:/").matches()); // ',' not matched
+    assertFalse(ManagedChannelImpl.URI_PATTERN.matcher(" a:/").matches()); // space not matched
+  }
+
+  /**
+   * Test that information such as the Call's context, MethodDescriptor, authority, executor are
+   * propagated to newStream() and applyRequestMetadata().
+   */
+  @Test
+  public void informationPropagatedToNewStreamAndCallCredentials() {
+    createChannel();
+    CallOptions callOptions = CallOptions.DEFAULT.withCallCredentials(creds);
+    final Context.Key<String> testKey = Context.key("testing");
+    Context ctx = Context.current().withValue(testKey, "testValue");
+    final LinkedList<Context> credsApplyContexts = new LinkedList<Context>();
+    final LinkedList<Context> newStreamContexts = new LinkedList<Context>();
+    doAnswer(new Answer<Void>() {
+        @Override
+        public Void answer(InvocationOnMock in) throws Throwable {
+          credsApplyContexts.add(Context.current());
+          return null;
+        }
+      }).when(creds).applyRequestMetadata(
+          any(MethodDescriptor.class), any(Attributes.class), any(Executor.class),
+          any(MetadataApplier.class));
+
+    // First call will be on delayed transport.  Only newCall() is run within the expected context,
+    // so that we can verify that the context is explicitly attached before calling newStream() and
+    // applyRequestMetadata(), which happens after we detach the context from the thread.
+    Context origCtx = ctx.attach();
+    assertEquals("testValue", testKey.get());
+    ClientCall<String, Integer> call = channel.newCall(method, callOptions);
+    ctx.detach(origCtx);
+    assertNull(testKey.get());
+    call.start(mockCallListener, new Metadata());
+
+    // Simulate name resolution results
+    EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(socketAddress);
+    Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    subchannel.requestConnection();
+    verify(mockTransportFactory)
+        .newClientTransport(same(socketAddress), eq(clientTransportOptions));
+    MockClientTransportInfo transportInfo = transports.poll();
+    final ConnectionClientTransport transport = transportInfo.transport;
+    when(transport.getAttributes()).thenReturn(Attributes.EMPTY);
+    doAnswer(new Answer<ClientStream>() {
+        @Override
+        public ClientStream answer(InvocationOnMock in) throws Throwable {
+          newStreamContexts.add(Context.current());
+          return mock(ClientStream.class);
+        }
+      }).when(transport).newStream(
+          any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+
+    verify(creds, never()).applyRequestMetadata(
+        any(MethodDescriptor.class), any(Attributes.class), any(Executor.class),
+        any(MetadataApplier.class));
+
+    // applyRequestMetadata() is called after the transport becomes ready.
+    transportInfo.listener.transportReady();
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withSubchannel(subchannel));
+    helper.updateBalancingState(READY, mockPicker);
+    executor.runDueTasks();
+    ArgumentCaptor<Attributes> attrsCaptor = ArgumentCaptor.forClass(Attributes.class);
+    ArgumentCaptor<MetadataApplier> applierCaptor = ArgumentCaptor.forClass(MetadataApplier.class);
+    verify(creds).applyRequestMetadata(same(method), attrsCaptor.capture(),
+        same(executor.getScheduledExecutorService()), applierCaptor.capture());
+    assertEquals("testValue", testKey.get(credsApplyContexts.poll()));
+    assertEquals(AUTHORITY, attrsCaptor.getValue().get(CallCredentials.ATTR_AUTHORITY));
+    assertEquals(SecurityLevel.NONE,
+        attrsCaptor.getValue().get(CallCredentials.ATTR_SECURITY_LEVEL));
+    verify(transport, never()).newStream(
+        any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+
+    // newStream() is called after apply() is called
+    applierCaptor.getValue().apply(new Metadata());
+    verify(transport).newStream(same(method), any(Metadata.class), same(callOptions));
+    assertEquals("testValue", testKey.get(newStreamContexts.poll()));
+    // The context should not live beyond the scope of newStream() and applyRequestMetadata()
+    assertNull(testKey.get());
+
+
+    // Second call will not be on delayed transport
+    origCtx = ctx.attach();
+    call = channel.newCall(method, callOptions);
+    ctx.detach(origCtx);
+    call.start(mockCallListener, new Metadata());
+
+    verify(creds, times(2)).applyRequestMetadata(same(method), attrsCaptor.capture(),
+        same(executor.getScheduledExecutorService()), applierCaptor.capture());
+    assertEquals("testValue", testKey.get(credsApplyContexts.poll()));
+    assertEquals(AUTHORITY, attrsCaptor.getValue().get(CallCredentials.ATTR_AUTHORITY));
+    assertEquals(SecurityLevel.NONE,
+        attrsCaptor.getValue().get(CallCredentials.ATTR_SECURITY_LEVEL));
+    // This is from the first call
+    verify(transport).newStream(
+        any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+
+    // Still, newStream() is called after apply() is called
+    applierCaptor.getValue().apply(new Metadata());
+    verify(transport, times(2)).newStream(same(method), any(Metadata.class), same(callOptions));
+    assertEquals("testValue", testKey.get(newStreamContexts.poll()));
+
+    assertNull(testKey.get());
+  }
+
+  @Test
+  public void pickerReturnsStreamTracer_noDelay() {
+    ClientStream mockStream = mock(ClientStream.class);
+    ClientStreamTracer.Factory factory1 = mock(ClientStreamTracer.Factory.class);
+    ClientStreamTracer.Factory factory2 = mock(ClientStreamTracer.Factory.class);
+    createChannel();
+    Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    subchannel.requestConnection();
+    MockClientTransportInfo transportInfo = transports.poll();
+    transportInfo.listener.transportReady();
+    ClientTransport mockTransport = transportInfo.transport;
+    when(mockTransport.newStream(
+            any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)))
+        .thenReturn(mockStream);
+
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(
+        PickResult.withSubchannel(subchannel, factory2));
+    helper.updateBalancingState(READY, mockPicker);
+
+    CallOptions callOptions = CallOptions.DEFAULT.withStreamTracerFactory(factory1);
+    ClientCall<String, Integer> call = channel.newCall(method, callOptions);
+    call.start(mockCallListener, new Metadata());
+
+    verify(mockPicker).pickSubchannel(any(PickSubchannelArgs.class));
+    verify(mockTransport).newStream(same(method), any(Metadata.class), callOptionsCaptor.capture());
+    assertEquals(
+        Arrays.asList(factory1, factory2),
+        callOptionsCaptor.getValue().getStreamTracerFactories());
+    // The factories are safely not stubbed because we do not expect any usage of them.
+    verifyZeroInteractions(factory1);
+    verifyZeroInteractions(factory2);
+  }
+
+  @Test
+  public void pickerReturnsStreamTracer_delayed() {
+    ClientStream mockStream = mock(ClientStream.class);
+    ClientStreamTracer.Factory factory1 = mock(ClientStreamTracer.Factory.class);
+    ClientStreamTracer.Factory factory2 = mock(ClientStreamTracer.Factory.class);
+    createChannel();
+
+    CallOptions callOptions = CallOptions.DEFAULT.withStreamTracerFactory(factory1);
+    ClientCall<String, Integer> call = channel.newCall(method, callOptions);
+    call.start(mockCallListener, new Metadata());
+
+    Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    subchannel.requestConnection();
+    MockClientTransportInfo transportInfo = transports.poll();
+    transportInfo.listener.transportReady();
+    ClientTransport mockTransport = transportInfo.transport;
+    when(mockTransport.newStream(
+            any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)))
+        .thenReturn(mockStream);
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(
+        PickResult.withSubchannel(subchannel, factory2));
+
+    helper.updateBalancingState(READY, mockPicker);
+    assertEquals(1, executor.runDueTasks());
+
+    verify(mockPicker).pickSubchannel(any(PickSubchannelArgs.class));
+    verify(mockTransport).newStream(same(method), any(Metadata.class), callOptionsCaptor.capture());
+    assertEquals(
+        Arrays.asList(factory1, factory2),
+        callOptionsCaptor.getValue().getStreamTracerFactories());
+    // The factories are safely not stubbed because we do not expect any usage of them.
+    verifyZeroInteractions(factory1);
+    verifyZeroInteractions(factory2);
+  }
+
+  @Test
+  public void getState_loadBalancerSupportsChannelState() {
+    channelBuilder.nameResolverFactory(
+        new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build());
+    createChannel();
+    assertEquals(IDLE, channel.getState(false));
+
+    helper.updateBalancingState(TRANSIENT_FAILURE, mockPicker);
+    assertEquals(TRANSIENT_FAILURE, channel.getState(false));
+  }
+
+  @Test
+  public void getState_withRequestConnect() {
+    channelBuilder.nameResolverFactory(
+        new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build());
+    requestConnection = false;
+    createChannel();
+
+    assertEquals(IDLE, channel.getState(false));
+    verify(mockLoadBalancerFactory, never()).newLoadBalancer(any(Helper.class));
+
+    // call getState() with requestConnection = true
+    assertEquals(IDLE, channel.getState(true));
+    ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
+    verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
+    helper = helperCaptor.getValue();
+
+    helper.updateBalancingState(CONNECTING, mockPicker);
+    assertEquals(CONNECTING, channel.getState(false));
+    assertEquals(CONNECTING, channel.getState(true));
+    verifyNoMoreInteractions(mockLoadBalancerFactory);
+  }
+
+  @Test
+  public void getState_withRequestConnect_IdleWithLbRunning() {
+    channelBuilder.nameResolverFactory(
+        new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build());
+    createChannel();
+    verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
+
+    helper.updateBalancingState(IDLE, mockPicker);
+
+    assertEquals(IDLE, channel.getState(true));
+    verifyNoMoreInteractions(mockLoadBalancerFactory);
+    verify(mockPicker).requestConnection();
+  }
+
+  @Test
+  public void notifyWhenStateChanged() {
+    final AtomicBoolean stateChanged = new AtomicBoolean();
+    Runnable onStateChanged = new Runnable() {
+      @Override
+      public void run() {
+        stateChanged.set(true);
+      }
+    };
+
+    channelBuilder.nameResolverFactory(
+        new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build());
+    createChannel();
+    assertEquals(IDLE, channel.getState(false));
+
+    channel.notifyWhenStateChanged(IDLE, onStateChanged);
+    executor.runDueTasks();
+    assertFalse(stateChanged.get());
+
+    // state change from IDLE to CONNECTING
+    helper.updateBalancingState(CONNECTING, mockPicker);
+    // onStateChanged callback should run
+    executor.runDueTasks();
+    assertTrue(stateChanged.get());
+
+    // clear and test form CONNECTING
+    stateChanged.set(false);
+    channel.notifyWhenStateChanged(IDLE, onStateChanged);
+    // onStateChanged callback should run immediately
+    executor.runDueTasks();
+    assertTrue(stateChanged.get());
+  }
+
+  @Test
+  public void channelStateWhenChannelShutdown() {
+    final AtomicBoolean stateChanged = new AtomicBoolean();
+    Runnable onStateChanged = new Runnable() {
+      @Override
+      public void run() {
+        stateChanged.set(true);
+      }
+    };
+
+    channelBuilder.nameResolverFactory(
+        new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build());
+    createChannel();
+    assertEquals(IDLE, channel.getState(false));
+    channel.notifyWhenStateChanged(IDLE, onStateChanged);
+    executor.runDueTasks();
+    assertFalse(stateChanged.get());
+
+    channel.shutdown();
+    assertEquals(SHUTDOWN, channel.getState(false));
+    executor.runDueTasks();
+    assertTrue(stateChanged.get());
+
+    stateChanged.set(false);
+    channel.notifyWhenStateChanged(SHUTDOWN, onStateChanged);
+    helper.updateBalancingState(CONNECTING, mockPicker);
+
+    assertEquals(SHUTDOWN, channel.getState(false));
+    executor.runDueTasks();
+    assertFalse(stateChanged.get());
+  }
+
+  @Test
+  public void stateIsIdleOnIdleTimeout() {
+    long idleTimeoutMillis = 2000L;
+    channelBuilder.idleTimeout(idleTimeoutMillis, TimeUnit.MILLISECONDS);
+    createChannel();
+    assertEquals(IDLE, channel.getState(false));
+
+    helper.updateBalancingState(CONNECTING, mockPicker);
+    assertEquals(CONNECTING, channel.getState(false));
+
+    timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis));
+    assertEquals(IDLE, channel.getState(false));
+  }
+
+  @Test
+  public void panic_whenIdle() {
+    subtestPanic(IDLE);
+  }
+
+  @Test
+  public void panic_whenConnecting() {
+    subtestPanic(CONNECTING);
+  }
+
+  @Test
+  public void panic_whenTransientFailure() {
+    subtestPanic(TRANSIENT_FAILURE);
+  }
+
+  @Test
+  public void panic_whenReady() {
+    subtestPanic(READY);
+  }
+
+  private void subtestPanic(ConnectivityState initialState) {
+    assertNotEquals("We don't test panic mode if it's already SHUTDOWN", SHUTDOWN, initialState);
+    long idleTimeoutMillis = 2000L;
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri).build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    channelBuilder.idleTimeout(idleTimeoutMillis, TimeUnit.MILLISECONDS);
+    createChannel();
+
+    verify(mockLoadBalancerFactory).newLoadBalancer(any(Helper.class));
+    assertEquals(1, nameResolverFactory.resolvers.size());
+    FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.remove(0);
+
+    Throwable panicReason = new Exception("Simulated uncaught exception");
+    if (initialState == IDLE) {
+      timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis));
+    } else {
+      helper.updateBalancingState(initialState, mockPicker);
+    }
+    assertEquals(initialState, channel.getState(false));
+
+    if (initialState == IDLE) {
+      // IDLE mode will shutdown resolver and balancer
+      verify(mockLoadBalancer).shutdown();
+      assertTrue(resolver.shutdown);
+      // A new resolver is created
+      assertEquals(1, nameResolverFactory.resolvers.size());
+      resolver = nameResolverFactory.resolvers.remove(0);
+      assertFalse(resolver.shutdown);
+    } else {
+      verify(mockLoadBalancer, never()).shutdown();
+      assertFalse(resolver.shutdown);
+    }
+
+    // Make channel panic!
+    channel.panic(panicReason);
+
+    // Calls buffered in delayedTransport will fail
+
+    // Resolver and balancer are shutdown
+    verify(mockLoadBalancer).shutdown();
+    assertTrue(resolver.shutdown);
+
+    // Channel will stay in TRANSIENT_FAILURE. getState(true) will not revive it.
+    assertEquals(TRANSIENT_FAILURE, channel.getState(true));
+    assertEquals(TRANSIENT_FAILURE, channel.getState(true));
+    verifyPanicMode(panicReason);
+
+    // No new resolver or balancer are created
+    verifyNoMoreInteractions(mockLoadBalancerFactory);
+    assertEquals(0, nameResolverFactory.resolvers.size());
+
+    // A misbehaving balancer that calls updateBalancingState() after it's shut down will not be
+    // able to revive it.
+    helper.updateBalancingState(READY, mockPicker);
+    verifyPanicMode(panicReason);
+
+    // Cannot be revived by exitIdleMode()
+    channel.exitIdleMode();
+    verifyPanicMode(panicReason);
+
+    // Can still shutdown normally
+    channel.shutdown();
+    assertTrue(channel.isShutdown());
+    assertTrue(channel.isTerminated());
+    assertEquals(SHUTDOWN, channel.getState(false));
+
+    // We didn't stub mockPicker, because it should have never been called in this test.
+    verifyZeroInteractions(mockPicker);
+  }
+
+  @Test
+  public void panic_bufferedCallsWillFail() {
+    createChannel();
+
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withNoResult());
+    helper.updateBalancingState(CONNECTING, mockPicker);
+
+    // Start RPCs that will be buffered in delayedTransport
+    ClientCall<String, Integer> call =
+        channel.newCall(method, CallOptions.DEFAULT.withoutWaitForReady());
+    call.start(mockCallListener, new Metadata());
+
+    ClientCall<String, Integer> call2 =
+        channel.newCall(method, CallOptions.DEFAULT.withWaitForReady());
+    call2.start(mockCallListener2, new Metadata());
+
+    executor.runDueTasks();
+    verifyZeroInteractions(mockCallListener, mockCallListener2);
+
+    // Enter panic
+    Throwable panicReason = new Exception("Simulated uncaught exception");
+    channel.panic(panicReason);
+
+    // Buffered RPCs fail immediately
+    executor.runDueTasks();
+    verifyCallListenerClosed(mockCallListener, Status.Code.INTERNAL, panicReason);
+    verifyCallListenerClosed(mockCallListener2, Status.Code.INTERNAL, panicReason);
+  }
+
+  private void verifyPanicMode(Throwable cause) {
+    Assume.assumeTrue("Panic mode disabled to resolve issues with some tests. See #3293", false);
+
+    @SuppressWarnings("unchecked")
+    ClientCall.Listener<Integer> mockListener =
+        (ClientCall.Listener<Integer>) mock(ClientCall.Listener.class);
+    assertEquals(TRANSIENT_FAILURE, channel.getState(false));
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockListener, new Metadata());
+    executor.runDueTasks();
+    verifyCallListenerClosed(mockListener, Status.Code.INTERNAL, cause);
+
+    // Channel is dead.  No more pending task to possibly revive it.
+    assertEquals(0, timer.numPendingTasks());
+    assertEquals(0, executor.numPendingTasks());
+    assertEquals(0, oobExecutor.numPendingTasks());
+  }
+
+  private void verifyCallListenerClosed(
+      ClientCall.Listener<Integer> listener, Status.Code code, Throwable cause) {
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(null);
+    verify(listener).onClose(captor.capture(), any(Metadata.class));
+    Status rpcStatus = captor.getValue();
+    assertEquals(code, rpcStatus.getCode());
+    assertSame(cause, rpcStatus.getCause());
+    verifyNoMoreInteractions(listener);
+  }
+
+  @Test
+  public void idleTimeoutAndReconnect() {
+    long idleTimeoutMillis = 2000L;
+    channelBuilder.idleTimeout(idleTimeoutMillis, TimeUnit.MILLISECONDS);
+    createChannel();
+
+    timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis));
+    assertEquals(IDLE, channel.getState(true /* request connection */));
+
+    ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(Helper.class);
+    // Two times of requesting connection will create loadBalancer twice.
+    verify(mockLoadBalancerFactory, times(2)).newLoadBalancer(helperCaptor.capture());
+    Helper helper2 = helperCaptor.getValue();
+
+    // Updating on the old helper (whose balancer has been shutdown) does not change the channel
+    // state.
+    helper.updateBalancingState(CONNECTING, mockPicker);
+    assertEquals(IDLE, channel.getState(false));
+
+    helper2.updateBalancingState(CONNECTING, mockPicker);
+    assertEquals(CONNECTING, channel.getState(false));
+  }
+
+  @Test
+  public void idleMode_resetsDelayedTransportPicker() {
+    ClientStream mockStream = mock(ClientStream.class);
+    Status pickError = Status.UNAVAILABLE.withDescription("pick result error");
+    long idleTimeoutMillis = 1000L;
+    channelBuilder.idleTimeout(idleTimeoutMillis, TimeUnit.MILLISECONDS);
+    channelBuilder.nameResolverFactory(
+        new FakeNameResolverFactory.Builder(expectedUri)
+            .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
+            .build());
+    createChannel();
+    assertEquals(IDLE, channel.getState(false));
+
+    // This call will be buffered in delayedTransport
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata());
+
+    // Move channel into TRANSIENT_FAILURE, which will fail the pending call
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withError(pickError));
+    helper.updateBalancingState(TRANSIENT_FAILURE, mockPicker);
+    assertEquals(TRANSIENT_FAILURE, channel.getState(false));
+    executor.runDueTasks();
+    verify(mockCallListener).onClose(same(pickError), any(Metadata.class));
+
+    // Move channel to idle
+    timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis));
+    assertEquals(IDLE, channel.getState(false));
+
+    // This call should be buffered, but will move the channel out of idle
+    ClientCall<String, Integer> call2 = channel.newCall(method, CallOptions.DEFAULT);
+    call2.start(mockCallListener2, new Metadata());
+    executor.runDueTasks();
+    verifyNoMoreInteractions(mockCallListener2);
+
+    // Get the helper created on exiting idle
+    ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(Helper.class);
+    verify(mockLoadBalancerFactory, times(2)).newLoadBalancer(helperCaptor.capture());
+    Helper helper2 = helperCaptor.getValue();
+
+    // Establish a connection
+    Subchannel subchannel = helper2.createSubchannel(addressGroup, Attributes.EMPTY);
+    subchannel.requestConnection();
+    MockClientTransportInfo transportInfo = transports.poll();
+    ConnectionClientTransport mockTransport = transportInfo.transport;
+    ManagedClientTransport.Listener transportListener = transportInfo.listener;
+    when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class)))
+        .thenReturn(mockStream);
+    transportListener.transportReady();
+
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withSubchannel(subchannel));
+    helper2.updateBalancingState(READY, mockPicker);
+    assertEquals(READY, channel.getState(false));
+    executor.runDueTasks();
+
+    // Verify the buffered call was drained
+    verify(mockTransport).newStream(same(method), any(Metadata.class), any(CallOptions.class));
+    verify(mockStream).start(any(ClientStreamListener.class));
+  }
+
+  @Test
+  public void enterIdleEntersIdle() {
+    createChannel();
+    helper.updateBalancingState(READY, mockPicker);
+    assertEquals(READY, channel.getState(false));
+
+    channel.enterIdle();
+
+    assertEquals(IDLE, channel.getState(false));
+  }
+
+  @Test
+  public void enterIdleAfterIdleTimerIsNoOp() {
+    long idleTimeoutMillis = 2000L;
+    channelBuilder.idleTimeout(idleTimeoutMillis, TimeUnit.MILLISECONDS);
+    createChannel();
+    timer.forwardNanos(TimeUnit.MILLISECONDS.toNanos(idleTimeoutMillis));
+    assertEquals(IDLE, channel.getState(false));
+
+    channel.enterIdle();
+
+    assertEquals(IDLE, channel.getState(false));
+  }
+
+  @Test
+  public void enterIdle_exitsIdleIfDelayedStreamPending() {
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri)
+            .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
+            .build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    createChannel();
+
+    // Start a call that will be buffered in delayedTransport
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata());
+
+    // enterIdle() will shut down the name resolver and lb policy used to get a pick for the delayed
+    // call
+    channel.enterIdle();
+    assertEquals(IDLE, channel.getState(false));
+
+    // enterIdle() will restart the delayed call by exiting idle. This creates a new helper.
+    ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(Helper.class);
+    verify(mockLoadBalancerFactory, times(2)).newLoadBalancer(helperCaptor.capture());
+    Helper helper2 = helperCaptor.getValue();
+
+    // Establish a connection
+    Subchannel subchannel = helper2.createSubchannel(addressGroup, Attributes.EMPTY);
+    subchannel.requestConnection();
+    ClientStream mockStream = mock(ClientStream.class);
+    MockClientTransportInfo transportInfo = transports.poll();
+    ConnectionClientTransport mockTransport = transportInfo.transport;
+    ManagedClientTransport.Listener transportListener = transportInfo.listener;
+    when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class)))
+        .thenReturn(mockStream);
+    transportListener.transportReady();
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withSubchannel(subchannel));
+    helper2.updateBalancingState(READY, mockPicker);
+    assertEquals(READY, channel.getState(false));
+
+    // Verify the original call was drained
+    executor.runDueTasks();
+    verify(mockTransport).newStream(same(method), any(Metadata.class), any(CallOptions.class));
+    verify(mockStream).start(any(ClientStreamListener.class));
+  }
+
+  @Test
+  public void updateBalancingStateDoesUpdatePicker() {
+    ClientStream mockStream = mock(ClientStream.class);
+    createChannel();
+
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata());
+
+    // Make the transport available with subchannel2
+    Subchannel subchannel1 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    Subchannel subchannel2 = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    subchannel2.requestConnection();
+
+    MockClientTransportInfo transportInfo = transports.poll();
+    ConnectionClientTransport mockTransport = transportInfo.transport;
+    ManagedClientTransport.Listener transportListener = transportInfo.listener;
+    when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class)))
+        .thenReturn(mockStream);
+    transportListener.transportReady();
+
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withSubchannel(subchannel1));
+    helper.updateBalancingState(READY, mockPicker);
+
+    executor.runDueTasks();
+    verify(mockTransport, never())
+        .newStream(any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class));
+    verify(mockStream, never()).start(any(ClientStreamListener.class));
+
+
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withSubchannel(subchannel2));
+    helper.updateBalancingState(READY, mockPicker);
+
+    executor.runDueTasks();
+    verify(mockTransport).newStream(same(method), any(Metadata.class), any(CallOptions.class));
+    verify(mockStream).start(any(ClientStreamListener.class));
+  }
+
+  @Test
+  public void updateBalancingStateWithShutdownShouldBeIgnored() {
+    channelBuilder.nameResolverFactory(
+        new FakeNameResolverFactory.Builder(expectedUri).setResolvedAtStart(false).build());
+    createChannel();
+    assertEquals(IDLE, channel.getState(false));
+
+    Runnable onStateChanged = mock(Runnable.class);
+    channel.notifyWhenStateChanged(IDLE, onStateChanged);
+
+    helper.updateBalancingState(SHUTDOWN, mockPicker);
+
+    assertEquals(IDLE, channel.getState(false));
+    executor.runDueTasks();
+    verify(onStateChanged, never()).run();
+  }
+
+  @Test
+  public void resetConnectBackoff() {
+    // Start with a name resolution failure to trigger backoff attempts
+    Status error = Status.UNAVAILABLE.withCause(new Throwable("fake name resolution error"));
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri).setError(error).build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    // Name resolution is started as soon as channel is created.
+    createChannel();
+    FakeNameResolverFactory.FakeNameResolver resolver = nameResolverFactory.resolvers.get(0);
+    verify(mockLoadBalancer).handleNameResolutionError(same(error));
+
+    FakeClock.ScheduledTask nameResolverBackoff = getNameResolverRefresh();
+    assertNotNull("There should be a name resolver backoff task", nameResolverBackoff);
+    assertEquals(0, resolver.refreshCalled);
+
+    // Verify resetConnectBackoff() calls refresh and cancels the scheduled backoff
+    channel.resetConnectBackoff();
+    assertEquals(1, resolver.refreshCalled);
+    assertTrue(nameResolverBackoff.isCancelled());
+
+    // Simulate a race between cancel and the task scheduler. Should be a no-op.
+    nameResolverBackoff.command.run();
+    assertEquals(1, resolver.refreshCalled);
+
+    // Verify that the reconnect policy was recreated and the backoff multiplier reset to 1
+    timer.forwardNanos(RECONNECT_BACKOFF_INTERVAL_NANOS);
+    assertEquals(2, resolver.refreshCalled);
+  }
+
+  @Test
+  public void resetConnectBackoff_noOpWithoutPendingResolverBackoff() {
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri)
+            .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
+            .build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    createChannel();
+    FakeNameResolverFactory.FakeNameResolver nameResolver = nameResolverFactory.resolvers.get(0);
+    assertEquals(0, nameResolver.refreshCalled);
+
+    channel.resetConnectBackoff();
+
+    assertEquals(0, nameResolver.refreshCalled);
+  }
+
+  @Test
+  public void resetConnectBackoff_noOpWhenChannelShutdown() {
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri).build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    createChannel();
+
+    channel.shutdown();
+    assertTrue(channel.isShutdown());
+    channel.resetConnectBackoff();
+
+    FakeNameResolverFactory.FakeNameResolver nameResolver = nameResolverFactory.resolvers.get(0);
+    assertEquals(0, nameResolver.refreshCalled);
+  }
+
+  @Test
+  public void resetConnectBackoff_noOpWhenNameResolverNotStarted() {
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri).build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    requestConnection = false;
+    createChannel();
+
+    channel.resetConnectBackoff();
+
+    FakeNameResolverFactory.FakeNameResolver nameResolver = nameResolverFactory.resolvers.get(0);
+    assertEquals(0, nameResolver.refreshCalled);
+  }
+
+  @Test
+  public void channelsAndSubchannels_instrumented_name() throws Exception {
+    createChannel();
+    assertEquals(TARGET, getStats(channel).target);
+
+    Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    assertEquals(Collections.singletonList(addressGroup).toString(),
+        getStats((AbstractSubchannel) subchannel).target);
+  }
+
+  @Test
+  public void channelTracing_channelCreationEvent() throws Exception {
+    timer.forwardNanos(1234);
+    channelBuilder.maxTraceEvents(10);
+    createChannel();
+    assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder()
+        .setDescription("Channel created")
+        .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+        .setTimestampNanos(timer.getTicker().read())
+        .build());
+  }
+
+  @Test
+  public void channelTracing_subchannelCreationEvents() throws Exception {
+    channelBuilder.maxTraceEvents(10);
+    createChannel();
+    timer.forwardNanos(1234);
+    AbstractSubchannel subchannel =
+        (AbstractSubchannel) helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder()
+        .setDescription("Child channel created")
+        .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+        .setTimestampNanos(timer.getTicker().read())
+        .setSubchannelRef(subchannel.getInternalSubchannel())
+        .build());
+    assertThat(getStats(subchannel).channelTrace.events).contains(new ChannelTrace.Event.Builder()
+        .setDescription("Subchannel created")
+        .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+        .setTimestampNanos(timer.getTicker().read())
+        .build());
+  }
+
+  @Test
+  public void channelTracing_nameResolvingErrorEvent() throws Exception {
+    timer.forwardNanos(1234);
+    channelBuilder.maxTraceEvents(10);
+    createChannel();
+    assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder()
+        .setDescription("Failed to resolve name")
+        .setSeverity(ChannelTrace.Event.Severity.CT_WARNING)
+        .setTimestampNanos(timer.getTicker().read())
+        .build());
+  }
+
+  @Test
+  public void channelTracing_nameResolvedEvent() throws Exception {
+    timer.forwardNanos(1234);
+    channelBuilder.maxTraceEvents(10);
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri)
+            .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
+            .build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    createChannel();
+    assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder()
+        .setDescription("Address resolved: "
+            + Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
+        .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+        .setTimestampNanos(timer.getTicker().read())
+        .build());
+  }
+
+  @Test
+  public void channelTracing_nameResolvedEvent_zeorAndNonzeroBackends() throws Exception {
+    timer.forwardNanos(1234);
+    channelBuilder.maxTraceEvents(10);
+    List<EquivalentAddressGroup> servers = new ArrayList<>();
+    servers.add(new EquivalentAddressGroup(socketAddress));
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri).setServers(servers).build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    createChannel();
+
+    int prevSize = getStats(channel).channelTrace.events.size();
+    nameResolverFactory.resolvers.get(0).listener.onAddresses(
+        Collections.singletonList(new EquivalentAddressGroup(
+            Arrays.asList(new SocketAddress() {}, new SocketAddress() {}))),
+        Attributes.EMPTY);
+    assertThat(getStats(channel).channelTrace.events).hasSize(prevSize);
+
+    prevSize = getStats(channel).channelTrace.events.size();
+    nameResolverFactory.resolvers.get(0).listener.onError(Status.INTERNAL);
+    assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1);
+
+    prevSize = getStats(channel).channelTrace.events.size();
+    nameResolverFactory.resolvers.get(0).listener.onError(Status.INTERNAL);
+    assertThat(getStats(channel).channelTrace.events).hasSize(prevSize);
+
+    prevSize = getStats(channel).channelTrace.events.size();
+    nameResolverFactory.resolvers.get(0).listener.onAddresses(
+        Collections.singletonList(new EquivalentAddressGroup(
+            Arrays.asList(new SocketAddress() {}, new SocketAddress() {}))),
+        Attributes.EMPTY);
+    assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1);
+  }
+
+  @Test
+  public void channelTracing_serviceConfigChange() throws Exception {
+    timer.forwardNanos(1234);
+    channelBuilder.maxTraceEvents(10);
+    List<EquivalentAddressGroup> servers = new ArrayList<>();
+    servers.add(new EquivalentAddressGroup(socketAddress));
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri).setServers(servers).build();
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    createChannel();
+
+    int prevSize = getStats(channel).channelTrace.events.size();
+    Attributes attributes =
+        Attributes.newBuilder()
+            .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, new HashMap<String, Object>())
+            .build();
+    nameResolverFactory.resolvers.get(0).listener.onAddresses(
+        Collections.singletonList(new EquivalentAddressGroup(
+            Arrays.asList(new SocketAddress() {}, new SocketAddress() {}))),
+        attributes);
+    assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1);
+    assertThat(getStats(channel).channelTrace.events.get(prevSize))
+        .isEqualTo(new ChannelTrace.Event.Builder()
+            .setDescription("Service config changed")
+            .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+            .setTimestampNanos(timer.getTicker().read())
+            .build());
+
+    prevSize = getStats(channel).channelTrace.events.size();
+    nameResolverFactory.resolvers.get(0).listener.onAddresses(
+        Collections.singletonList(new EquivalentAddressGroup(
+            Arrays.asList(new SocketAddress() {}, new SocketAddress() {}))),
+        attributes);
+    assertThat(getStats(channel).channelTrace.events).hasSize(prevSize);
+
+    prevSize = getStats(channel).channelTrace.events.size();
+    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+    serviceConfig.put("methodConfig", new HashMap<String, Object>());
+    attributes =
+        Attributes.newBuilder()
+            .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig)
+            .build();
+    timer.forwardNanos(1234);
+    nameResolverFactory.resolvers.get(0).listener.onAddresses(
+        Collections.singletonList(new EquivalentAddressGroup(
+            Arrays.asList(new SocketAddress() {}, new SocketAddress() {}))),
+        attributes);
+    assertThat(getStats(channel).channelTrace.events).hasSize(prevSize + 1);
+    assertThat(getStats(channel).channelTrace.events.get(prevSize))
+        .isEqualTo(new ChannelTrace.Event.Builder()
+            .setDescription("Service config changed")
+            .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+            .setTimestampNanos(timer.getTicker().read())
+            .build());
+  }
+
+  @Test
+  public void channelTracing_stateChangeEvent() throws Exception {
+    channelBuilder.maxTraceEvents(10);
+    createChannel();
+    timer.forwardNanos(1234);
+    helper.updateBalancingState(CONNECTING, mockPicker);
+    assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder()
+        .setDescription("Entering CONNECTING state")
+        .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+        .setTimestampNanos(timer.getTicker().read())
+        .build());
+  }
+
+  @Test
+  public void channelTracing_subchannelStateChangeEvent() throws Exception {
+    channelBuilder.maxTraceEvents(10);
+    createChannel();
+    AbstractSubchannel subchannel =
+        (AbstractSubchannel) helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    timer.forwardNanos(1234);
+    subchannel.obtainActiveTransport();
+    assertThat(getStats(subchannel).channelTrace.events).contains(new ChannelTrace.Event.Builder()
+        .setDescription("Entering CONNECTING state")
+        .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+        .setTimestampNanos(timer.getTicker().read())
+        .build());
+  }
+
+  @Test
+  public void channelTracing_oobChannelStateChangeEvent() throws Exception {
+    channelBuilder.maxTraceEvents(10);
+    createChannel();
+    OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "authority");
+    timer.forwardNanos(1234);
+    oobChannel.handleSubchannelStateChange(
+        ConnectivityStateInfo.forNonError(ConnectivityState.CONNECTING));
+    assertThat(getStats(oobChannel).channelTrace.events).contains(new ChannelTrace.Event.Builder()
+        .setDescription("Entering CONNECTING state")
+        .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+        .setTimestampNanos(timer.getTicker().read())
+        .build());
+  }
+
+  @Test
+  public void channelTracing_oobChannelCreationEvents() throws Exception {
+    channelBuilder.maxTraceEvents(10);
+    createChannel();
+    timer.forwardNanos(1234);
+    OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "authority");
+    assertThat(getStats(channel).channelTrace.events).contains(new ChannelTrace.Event.Builder()
+        .setDescription("Child channel created")
+        .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+        .setTimestampNanos(timer.getTicker().read())
+        .setChannelRef(oobChannel)
+        .build());
+    assertThat(getStats(oobChannel).channelTrace.events).contains(new ChannelTrace.Event.Builder()
+        .setDescription("OobChannel created")
+        .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+        .setTimestampNanos(timer.getTicker().read())
+        .build());
+    assertThat(getStats(oobChannel.getInternalSubchannel()).channelTrace.events).contains(
+        new ChannelTrace.Event.Builder()
+            .setDescription("Subchannel created")
+            .setSeverity(ChannelTrace.Event.Severity.CT_INFO)
+            .setTimestampNanos(timer.getTicker().read())
+            .build());
+  }
+
+  @Test
+  public void channelsAndSubchannels_instrumented_state() throws Exception {
+    createChannel();
+
+    ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(null);
+    verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
+    helper = helperCaptor.getValue();
+
+    assertEquals(IDLE, getStats(channel).state);
+    helper.updateBalancingState(CONNECTING, mockPicker);
+    assertEquals(CONNECTING, getStats(channel).state);
+
+    AbstractSubchannel subchannel =
+        (AbstractSubchannel) helper.createSubchannel(addressGroup, Attributes.EMPTY);
+
+    assertEquals(IDLE, getStats(subchannel).state);
+    subchannel.requestConnection();
+    assertEquals(CONNECTING, getStats(subchannel).state);
+
+    MockClientTransportInfo transportInfo = transports.poll();
+
+    assertEquals(CONNECTING, getStats(subchannel).state);
+    transportInfo.listener.transportReady();
+    assertEquals(READY, getStats(subchannel).state);
+
+    assertEquals(CONNECTING, getStats(channel).state);
+    helper.updateBalancingState(READY, mockPicker);
+    assertEquals(READY, getStats(channel).state);
+
+    channel.shutdownNow();
+    assertEquals(SHUTDOWN, getStats(channel).state);
+    assertEquals(SHUTDOWN, getStats(subchannel).state);
+  }
+
+  @Test
+  public void channelStat_callStarted() throws Exception {
+    createChannel();
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    assertEquals(0, getStats(channel).callsStarted);
+    call.start(mockCallListener, new Metadata());
+    assertEquals(1, getStats(channel).callsStarted);
+    assertEquals(executor.getTicker().read(), getStats(channel).lastCallStartedNanos);
+  }
+
+  @Test
+  public void channelsAndSubChannels_instrumented_success() throws Exception {
+    channelsAndSubchannels_instrumented0(true);
+  }
+
+  @Test
+  public void channelsAndSubChannels_instrumented_fail() throws Exception {
+    channelsAndSubchannels_instrumented0(false);
+  }
+
+  private void channelsAndSubchannels_instrumented0(boolean success) throws Exception {
+    createChannel();
+
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+
+    // Channel stat bumped when ClientCall.start() called
+    assertEquals(0, getStats(channel).callsStarted);
+    call.start(mockCallListener, new Metadata());
+    assertEquals(1, getStats(channel).callsStarted);
+
+    ClientStream mockStream = mock(ClientStream.class);
+    ClientStreamTracer.Factory factory = mock(ClientStreamTracer.Factory.class);
+    AbstractSubchannel subchannel =
+        (AbstractSubchannel) helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    subchannel.requestConnection();
+    MockClientTransportInfo transportInfo = transports.poll();
+    transportInfo.listener.transportReady();
+    ClientTransport mockTransport = transportInfo.transport;
+    when(mockTransport.newStream(
+            any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)))
+        .thenReturn(mockStream);
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class))).thenReturn(
+        PickResult.withSubchannel(subchannel, factory));
+
+    // subchannel stat bumped when call gets assigned to it
+    assertEquals(0, getStats(subchannel).callsStarted);
+    helper.updateBalancingState(READY, mockPicker);
+    assertEquals(1, executor.runDueTasks());
+    verify(mockStream).start(streamListenerCaptor.capture());
+    assertEquals(1, getStats(subchannel).callsStarted);
+
+    ClientStreamListener streamListener = streamListenerCaptor.getValue();
+    call.halfClose();
+
+    // closing stream listener affects subchannel stats immediately
+    assertEquals(0, getStats(subchannel).callsSucceeded);
+    assertEquals(0, getStats(subchannel).callsFailed);
+    streamListener.closed(success ? Status.OK : Status.UNKNOWN, new Metadata());
+    if (success) {
+      assertEquals(1, getStats(subchannel).callsSucceeded);
+      assertEquals(0, getStats(subchannel).callsFailed);
+    } else {
+      assertEquals(0, getStats(subchannel).callsSucceeded);
+      assertEquals(1, getStats(subchannel).callsFailed);
+    }
+
+    // channel stats bumped when the ClientCall.Listener is notified
+    assertEquals(0, getStats(channel).callsSucceeded);
+    assertEquals(0, getStats(channel).callsFailed);
+    executor.runDueTasks();
+    if (success) {
+      assertEquals(1, getStats(channel).callsSucceeded);
+      assertEquals(0, getStats(channel).callsFailed);
+    } else {
+      assertEquals(0, getStats(channel).callsSucceeded);
+      assertEquals(1, getStats(channel).callsFailed);
+    }
+  }
+
+  @Test
+  public void channelsAndSubchannels_oob_instrumented_success() throws Exception {
+    channelsAndSubchannels_oob_instrumented0(true);
+  }
+
+  @Test
+  public void channelsAndSubchannels_oob_instrumented_fail() throws Exception {
+    channelsAndSubchannels_oob_instrumented0(false);
+  }
+
+  private void channelsAndSubchannels_oob_instrumented0(boolean success) throws Exception {
+    // set up
+    ClientStream mockStream = mock(ClientStream.class);
+    createChannel();
+
+    OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "oobauthority");
+    AbstractSubchannel oobSubchannel = (AbstractSubchannel) oobChannel.getSubchannel();
+    FakeClock callExecutor = new FakeClock();
+    CallOptions options =
+        CallOptions.DEFAULT.withExecutor(callExecutor.getScheduledExecutorService());
+    ClientCall<String, Integer> call = oobChannel.newCall(method, options);
+    Metadata headers = new Metadata();
+
+    // Channel stat bumped when ClientCall.start() called
+    assertEquals(0, getStats(oobChannel).callsStarted);
+    call.start(mockCallListener, headers);
+    assertEquals(1, getStats(oobChannel).callsStarted);
+
+    MockClientTransportInfo transportInfo = transports.poll();
+    ConnectionClientTransport mockTransport = transportInfo.transport;
+    ManagedClientTransport.Listener transportListener = transportInfo.listener;
+    when(mockTransport.newStream(same(method), same(headers), any(CallOptions.class)))
+        .thenReturn(mockStream);
+
+    // subchannel stat bumped when call gets assigned to it
+    assertEquals(0, getStats(oobSubchannel).callsStarted);
+    transportListener.transportReady();
+    callExecutor.runDueTasks();
+    verify(mockStream).start(streamListenerCaptor.capture());
+    assertEquals(1, getStats(oobSubchannel).callsStarted);
+
+    ClientStreamListener streamListener = streamListenerCaptor.getValue();
+    call.halfClose();
+
+    // closing stream listener affects subchannel stats immediately
+    assertEquals(0, getStats(oobSubchannel).callsSucceeded);
+    assertEquals(0, getStats(oobSubchannel).callsFailed);
+    streamListener.closed(success ? Status.OK : Status.UNKNOWN, new Metadata());
+    if (success) {
+      assertEquals(1, getStats(oobSubchannel).callsSucceeded);
+      assertEquals(0, getStats(oobSubchannel).callsFailed);
+    } else {
+      assertEquals(0, getStats(oobSubchannel).callsSucceeded);
+      assertEquals(1, getStats(oobSubchannel).callsFailed);
+    }
+
+    // channel stats bumped when the ClientCall.Listener is notified
+    assertEquals(0, getStats(oobChannel).callsSucceeded);
+    assertEquals(0, getStats(oobChannel).callsFailed);
+    callExecutor.runDueTasks();
+    if (success) {
+      assertEquals(1, getStats(oobChannel).callsSucceeded);
+      assertEquals(0, getStats(oobChannel).callsFailed);
+    } else {
+      assertEquals(0, getStats(oobChannel).callsSucceeded);
+      assertEquals(1, getStats(oobChannel).callsFailed);
+    }
+    // oob channel is separate from the original channel
+    assertEquals(0, getStats(channel).callsSucceeded);
+    assertEquals(0, getStats(channel).callsFailed);
+  }
+
+  @Test
+  public void channelsAndSubchannels_oob_instrumented_name() throws Exception {
+    createChannel();
+
+    String authority = "oobauthority";
+    OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, authority);
+    assertEquals(authority, getStats(oobChannel).target);
+  }
+
+  @Test
+  public void channelsAndSubchannels_oob_instrumented_state() throws Exception {
+    createChannel();
+
+    OobChannel oobChannel = (OobChannel) helper.createOobChannel(addressGroup, "oobauthority");
+    assertEquals(IDLE, getStats(oobChannel).state);
+
+    oobChannel.getSubchannel().requestConnection();
+    assertEquals(CONNECTING, getStats(oobChannel).state);
+
+    MockClientTransportInfo transportInfo = transports.poll();
+    ManagedClientTransport.Listener transportListener = transportInfo.listener;
+
+    transportListener.transportReady();
+    assertEquals(READY, getStats(oobChannel).state);
+
+    // oobchannel state is separate from the ManagedChannel
+    assertEquals(IDLE, getStats(channel).state);
+    channel.shutdownNow();
+    assertEquals(SHUTDOWN, getStats(channel).state);
+    assertEquals(SHUTDOWN, getStats(oobChannel).state);
+  }
+
+  @Test
+  public void binaryLogInstalled() throws Exception {
+    final SettableFuture<Boolean> intercepted = SettableFuture.create();
+    channelBuilder.binlog = new BinaryLog() {
+      @Override
+      public void close() throws IOException {
+        // noop
+      }
+
+      @Override
+      public <ReqT, RespT> ServerMethodDefinition<?, ?> wrapMethodDefinition(
+          ServerMethodDefinition<ReqT, RespT> oMethodDef) {
+        return oMethodDef;
+      }
+
+      @Override
+      public Channel wrapChannel(Channel channel) {
+        return ClientInterceptors.intercept(channel,
+            new ClientInterceptor() {
+              @Override
+              public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+                  MethodDescriptor<ReqT, RespT> method,
+                  CallOptions callOptions,
+                  Channel next) {
+                intercepted.set(true);
+                return next.newCall(method, callOptions);
+              }
+            });
+      }
+    };
+
+    createChannel();
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata());
+    assertTrue(intercepted.get());
+  }
+
+  @Test
+  public void retryBackoffThenChannelShutdown_retryShouldStillHappen_newCallShouldFail() {
+    Map<String, Object> retryPolicy = new HashMap<String, Object>();
+    retryPolicy.put("maxAttempts", 3D);
+    retryPolicy.put("initialBackoff", "10s");
+    retryPolicy.put("maxBackoff", "30s");
+    retryPolicy.put("backoffMultiplier", 2D);
+    retryPolicy.put("retryableStatusCodes", Arrays.<Object>asList("UNAVAILABLE"));
+    Map<String, Object> methodConfig = new HashMap<String, Object>();
+    Map<String, Object> name = new HashMap<String, Object>();
+    name.put("service", "service");
+    methodConfig.put("name", Arrays.<Object>asList(name));
+    methodConfig.put("retryPolicy", retryPolicy);
+    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+    serviceConfig.put("methodConfig", Arrays.<Object>asList(methodConfig));
+    Attributes attributesWithRetryPolicy = Attributes
+        .newBuilder().set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build();
+
+    FakeNameResolverFactory nameResolverFactory =
+        new FakeNameResolverFactory.Builder(expectedUri)
+            .setServers(Collections.singletonList(new EquivalentAddressGroup(socketAddress)))
+            .build();
+    nameResolverFactory.nextResolvedAttributes.set(attributesWithRetryPolicy);
+    channelBuilder.nameResolverFactory(nameResolverFactory);
+    channelBuilder.executor(MoreExecutors.directExecutor());
+    channelBuilder.enableRetry();
+    RetriableStream.setRandom(
+        // not random
+        new Random() {
+          @Override
+          public double nextDouble() {
+            return 1D; // fake random
+          }
+        });
+
+    requestConnection = false;
+    createChannel();
+
+    ClientCall<String, Integer> call = channel.newCall(method, CallOptions.DEFAULT);
+    call.start(mockCallListener, new Metadata());
+    ArgumentCaptor<Helper> helperCaptor = ArgumentCaptor.forClass(Helper.class);
+    verify(mockLoadBalancerFactory).newLoadBalancer(helperCaptor.capture());
+    helper = helperCaptor.getValue();
+    verify(mockLoadBalancer)
+        .handleResolvedAddressGroups(nameResolverFactory.servers, attributesWithRetryPolicy);
+
+    // simulating request connection and then transport ready after resolved address
+    Subchannel subchannel = helper.createSubchannel(addressGroup, Attributes.EMPTY);
+    when(mockPicker.pickSubchannel(any(PickSubchannelArgs.class)))
+        .thenReturn(PickResult.withSubchannel(subchannel));
+    subchannel.requestConnection();
+    MockClientTransportInfo transportInfo = transports.poll();
+    ConnectionClientTransport mockTransport = transportInfo.transport;
+    ClientStream mockStream = mock(ClientStream.class);
+    ClientStream mockStream2 = mock(ClientStream.class);
+    when(mockTransport.newStream(same(method), any(Metadata.class), any(CallOptions.class)))
+        .thenReturn(mockStream).thenReturn(mockStream2);
+    transportInfo.listener.transportReady();
+    helper.updateBalancingState(READY, mockPicker);
+
+    ArgumentCaptor<ClientStreamListener> streamListenerCaptor =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream).start(streamListenerCaptor.capture());
+    assertThat(timer.getPendingTasks()).isEmpty();
+
+    // trigger retry
+    streamListenerCaptor.getValue().closed(Status.UNAVAILABLE, new Metadata());
+
+    // in backoff
+    timer.forwardTime(5, TimeUnit.SECONDS);
+    assertThat(timer.getPendingTasks()).hasSize(1);
+    verify(mockStream2, never()).start(any(ClientStreamListener.class));
+
+    // shutdown during backoff period
+    channel.shutdown();
+
+    assertThat(timer.getPendingTasks()).hasSize(1);
+    verify(mockCallListener, never()).onClose(any(Status.class), any(Metadata.class));
+
+    ClientCall<String, Integer> call2 = channel.newCall(method, CallOptions.DEFAULT);
+    call2.start(mockCallListener2, new Metadata());
+
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(mockCallListener2).onClose(statusCaptor.capture(), any(Metadata.class));
+    assertSame(Status.Code.UNAVAILABLE, statusCaptor.getValue().getCode());
+    assertEquals("Channel shutdown invoked", statusCaptor.getValue().getDescription());
+
+    // backoff ends
+    timer.forwardTime(5, TimeUnit.SECONDS);
+    assertThat(timer.getPendingTasks()).isEmpty();
+    verify(mockStream2).start(streamListenerCaptor.capture());
+    verify(mockLoadBalancer, never()).shutdown();
+    assertFalse(
+        "channel.isTerminated() is expected to be false but was true",
+        channel.isTerminated());
+
+    streamListenerCaptor.getValue().closed(Status.INTERNAL, new Metadata());
+    verify(mockLoadBalancer).shutdown();
+    // simulating the shutdown of load balancer triggers the shutdown of subchannel
+    subchannel.shutdown();
+    transportInfo.listener.transportTerminated(); // simulating transport terminated
+    assertTrue(
+        "channel.isTerminated() is expected to be true but was false",
+        channel.isTerminated());
+  }
+
+  @Test
+  public void badServiceConfigIsRecoverable() throws Exception {
+    final List<EquivalentAddressGroup> addresses =
+        ImmutableList.of(new EquivalentAddressGroup(new SocketAddress() {}));
+    final class FakeNameResolver extends NameResolver {
+      Listener listener;
+
+      @Override
+      public String getServiceAuthority() {
+        return "also fake";
+      }
+
+      @Override
+      public void start(Listener listener) {
+        this.listener = listener;
+        listener.onAddresses(addresses,
+            Attributes.newBuilder()
+                .set(
+                    GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG,
+                    ImmutableMap.<String, Object>of("loadBalancingPolicy", "kaboom"))
+                .build());
+      }
+
+      @Override
+      public void shutdown() {}
+    }
+    
+    final class FakeNameResolverFactory extends NameResolver.Factory {
+      FakeNameResolver resolver;
+
+      @Nullable
+      @Override
+      public NameResolver newNameResolver(URI targetUri, Attributes params) {
+        return (resolver = new FakeNameResolver());
+      }
+
+      @Override
+      public String getDefaultScheme() {
+        return "fake";
+      }
+    }
+    
+    FakeNameResolverFactory factory = new FakeNameResolverFactory();
+    final class CustomBuilder extends AbstractManagedChannelImplBuilder<CustomBuilder> {
+
+      CustomBuilder() {
+        super(TARGET);
+        this.executorPool = ManagedChannelImplTest.this.executorPool;
+        this.channelz = ManagedChannelImplTest.this.channelz;
+      }
+
+      @Override
+      protected ClientTransportFactory buildTransportFactory() {
+        return mockTransportFactory;
+      }
+    }
+
+    ManagedChannel mychannel = new CustomBuilder()
+        .nameResolverFactory(factory)
+        .loadBalancerFactory(new AutoConfiguredLoadBalancerFactory()).build();
+
+    ClientCall<Void, Void> call1 =
+        mychannel.newCall(TestMethodDescriptors.voidMethod(), CallOptions.DEFAULT);
+    ListenableFuture<Void> future1 = ClientCalls.futureUnaryCall(call1, null);
+    executor.runDueTasks();
+    try {
+      future1.get();
+      Assert.fail();
+    } catch (ExecutionException e) {
+      assertThat(Throwables.getStackTraceAsString(e.getCause())).contains("kaboom");
+    }
+
+    // ok the service config is bad, let's fix it.
+
+    factory.resolver.listener.onAddresses(addresses,
+        Attributes.newBuilder()
+        .set(
+            GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG,
+            ImmutableMap.<String, Object>of("loadBalancingPolicy", "round_robin"))
+        .build());
+
+    ClientCall<Void, Void> call2 = mychannel.newCall(
+        TestMethodDescriptors.voidMethod(),
+        CallOptions.DEFAULT.withDeadlineAfter(5, TimeUnit.SECONDS));
+    ListenableFuture<Void> future2 = ClientCalls.futureUnaryCall(call2, null);
+
+    timer.forwardTime(1234, TimeUnit.SECONDS);
+
+    executor.runDueTasks();
+    try {
+      future2.get();
+      Assert.fail();
+    } catch (ExecutionException e) {
+      assertThat(Throwables.getStackTraceAsString(e.getCause())).contains("deadline");
+    }
+
+    mychannel.shutdownNow();
+  }
+
+  private static final class ChannelBuilder
+      extends AbstractManagedChannelImplBuilder<ChannelBuilder> {
+
+    ChannelBuilder() {
+      super(TARGET);
+    }
+
+    @Override protected ClientTransportFactory buildTransportFactory() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override protected Attributes getNameResolverParams() {
+      return NAME_RESOLVER_PARAMS;
+    }
+  }
+
+  private static final class FakeBackoffPolicyProvider implements BackoffPolicy.Provider {
+    @Override
+    public BackoffPolicy get() {
+      return new BackoffPolicy() {
+        int multiplier = 1;
+
+        @Override
+        public long nextBackoffNanos() {
+          return RECONNECT_BACKOFF_INTERVAL_NANOS * multiplier++;
+        }
+      };
+    }
+  }
+
+  private static final class FakeNameResolverFactory extends NameResolver.Factory {
+    final URI expectedUri;
+    final List<EquivalentAddressGroup> servers;
+    final boolean resolvedAtStart;
+    final Status error;
+    final ArrayList<FakeNameResolver> resolvers = new ArrayList<>();
+    // The Attributes argument of the next invocation of listener.onAddresses(servers, attrs)
+    final AtomicReference<Attributes> nextResolvedAttributes =
+        new AtomicReference<Attributes>(Attributes.EMPTY);
+
+    FakeNameResolverFactory(
+        URI expectedUri,
+        List<EquivalentAddressGroup> servers,
+        boolean resolvedAtStart,
+        Status error) {
+      this.expectedUri = expectedUri;
+      this.servers = servers;
+      this.resolvedAtStart = resolvedAtStart;
+      this.error = error;
+    }
+
+    @Override
+    public NameResolver newNameResolver(final URI targetUri, Attributes params) {
+      if (!expectedUri.equals(targetUri)) {
+        return null;
+      }
+      assertSame(NAME_RESOLVER_PARAMS, params);
+      FakeNameResolver resolver = new FakeNameResolver(error);
+      resolvers.add(resolver);
+      return resolver;
+    }
+
+    @Override
+    public String getDefaultScheme() {
+      return "fake";
+    }
+
+    void allResolved() {
+      for (FakeNameResolver resolver : resolvers) {
+        resolver.resolved();
+      }
+    }
+
+    final class FakeNameResolver extends NameResolver {
+      Listener listener;
+      boolean shutdown;
+      int refreshCalled;
+      Status error;
+
+      FakeNameResolver(Status error) {
+        this.error = error;
+      }
+
+      @Override public String getServiceAuthority() {
+        return expectedUri.getAuthority();
+      }
+
+      @Override public void start(final Listener listener) {
+        this.listener = listener;
+        if (resolvedAtStart) {
+          resolved();
+        }
+      }
+
+      @Override public void refresh() {
+        assertNotNull(listener);
+        refreshCalled++;
+        resolved();
+      }
+
+      void resolved() {
+        if (error != null) {
+          listener.onError(error);
+          return;
+        }
+        listener.onAddresses(servers, nextResolvedAttributes.get());
+      }
+
+      @Override public void shutdown() {
+        shutdown = true;
+      }
+    }
+
+    static final class Builder {
+      final URI expectedUri;
+      List<EquivalentAddressGroup> servers = ImmutableList.<EquivalentAddressGroup>of();
+      boolean resolvedAtStart = true;
+      Status error = null;
+
+      Builder(URI expectedUri) {
+        this.expectedUri = expectedUri;
+      }
+
+      Builder setServers(List<EquivalentAddressGroup> servers) {
+        this.servers = servers;
+        return this;
+      }
+
+      Builder setResolvedAtStart(boolean resolvedAtStart) {
+        this.resolvedAtStart = resolvedAtStart;
+        return this;
+      }
+
+      Builder setError(Status error) {
+        this.error = error;
+        return this;
+      }
+
+      FakeNameResolverFactory build() {
+        return new FakeNameResolverFactory(expectedUri, servers, resolvedAtStart, error);
+      }
+    }
+  }
+
+  private static ChannelStats getStats(AbstractSubchannel subchannel) throws Exception {
+    return subchannel.getInternalSubchannel().getStats().get();
+  }
+
+  private static ChannelStats getStats(
+      InternalInstrumented<ChannelStats> instrumented) throws Exception {
+    return instrumented.getStats().get();
+  }
+
+  private FakeClock.ScheduledTask getNameResolverRefresh() {
+    return Iterables.getOnlyElement(timer.getPendingTasks(NAME_RESOLVER_REFRESH_TASK_FILTER), null);
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ManagedChannelOrphanWrapperTest.java b/core/src/test/java/io/grpc/internal/ManagedChannelOrphanWrapperTest.java
new file mode 100644
index 0000000..9f9dc27
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ManagedChannelOrphanWrapperTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.ManagedChannel;
+import io.grpc.MethodDescriptor;
+import io.grpc.internal.ManagedChannelOrphanWrapper.ManagedChannelReference;
+import java.lang.ref.ReferenceQueue;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Filter;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ManagedChannelOrphanWrapperTest {
+  @Test
+  public void orphanedChannelsAreLogged() throws Exception {
+    ManagedChannel mc = mock(ManagedChannel.class);
+    String channelString = mc.toString();
+    ReferenceQueue<ManagedChannelOrphanWrapper> refqueue =
+        new ReferenceQueue<ManagedChannelOrphanWrapper>();
+    ConcurrentMap<ManagedChannelReference, ManagedChannelReference> refs =
+        new ConcurrentHashMap<ManagedChannelReference, ManagedChannelReference>();
+
+    assertEquals(0, refs.size());
+    ManagedChannelOrphanWrapper channel = new ManagedChannelOrphanWrapper(mc, refqueue, refs);
+    assertEquals(1, refs.size());
+
+    // Try to capture the log output but without causing terminal noise.  Adding the filter must
+    // be done before clearing the ref or else it might be missed.
+    final List<LogRecord> records = new ArrayList<>(1);
+    Logger orphanLogger = Logger.getLogger(ManagedChannelOrphanWrapper.class.getName());
+    Filter oldFilter = orphanLogger.getFilter();
+    orphanLogger.setFilter(new Filter() {
+
+      @Override
+      public boolean isLoggable(LogRecord record) {
+        synchronized (records) {
+          records.add(record);
+        }
+        return false;
+      }
+    });
+
+    // TODO(carl-mastrangelo): consider using com.google.common.testing.GcFinalization instead.
+    try {
+      channel = null;
+      boolean success = false;
+      for (int retry = 0; retry < 3; retry++) {
+        System.gc();
+        System.runFinalization();
+        int orphans = ManagedChannelReference.cleanQueue(refqueue);
+        if (orphans == 1) {
+          success = true;
+          break;
+        }
+        assertEquals("unexpected extra orphans", 0, orphans);
+        Thread.sleep(100L * (1L << retry));
+      }
+      assertTrue("Channel was not garbage collected", success);
+
+      LogRecord lr;
+      synchronized (records) {
+        assertEquals(1, records.size());
+        lr = records.get(0);
+      }
+      assertThat(lr.getMessage()).contains("shutdown");
+      assertThat(lr.getParameters()).asList().containsExactly(channelString).inOrder();
+      assertEquals(Level.SEVERE, lr.getLevel());
+      assertEquals(0, refs.size());
+    } finally {
+      orphanLogger.setFilter(oldFilter);
+    }
+  }
+
+  private static final class TestManagedChannel extends ManagedChannel {
+    @Override
+    public ManagedChannel shutdown() {
+      return null;
+    }
+
+    @Override
+    public boolean isShutdown() {
+      return false;
+    }
+
+    @Override
+    public boolean isTerminated() {
+      return false;
+    }
+
+    @Override
+    public ManagedChannel shutdownNow() {
+      return null;
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+      return false;
+    }
+
+    @Override
+    public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
+        MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) {
+      return null;
+    }
+
+    @Override
+    public String authority() {
+      return null;
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/MessageDeframerTest.java b/core/src/test/java/io/grpc/internal/MessageDeframerTest.java
new file mode 100644
index 0000000..8b6fc1d
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/MessageDeframerTest.java
@@ -0,0 +1,546 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteStreams;
+import com.google.common.primitives.Bytes;
+import io.grpc.Codec;
+import io.grpc.InternalChannelz.TransportStats;
+import io.grpc.StatusRuntimeException;
+import io.grpc.StreamTracer;
+import io.grpc.internal.MessageDeframer.Listener;
+import io.grpc.internal.MessageDeframer.SizeEnforcingInputStream;
+import io.grpc.internal.testing.TestStreamTracer.TestBaseStreamTracer;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.GZIPOutputStream;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Tests for {@link MessageDeframer}.
+ */
+@RunWith(Enclosed.class)
+public class MessageDeframerTest {
+
+  @RunWith(Parameterized.class)
+  public static class WithAndWithoutFullStreamCompressionTests {
+
+    /**
+     * Auto called by test.
+     */
+    @Parameters(name = "{index}: useGzipInflatingBuffer={0}")
+    public static Collection<Object[]> data() {
+      return Arrays.asList(new Object[][]{
+              {false}, {true}
+      });
+    }
+
+    @Parameter // Automatically set by test runner, must be public
+    public boolean useGzipInflatingBuffer;
+
+    private Listener listener = mock(Listener.class);
+    private TestBaseStreamTracer tracer = new TestBaseStreamTracer();
+    private StatsTraceContext statsTraceCtx = new StatsTraceContext(new StreamTracer[]{tracer});
+    private TransportTracer transportTracer = new TransportTracer();
+
+    private MessageDeframer deframer = new MessageDeframer(listener, Codec.Identity.NONE,
+            DEFAULT_MAX_MESSAGE_SIZE, statsTraceCtx, transportTracer);
+
+    private ArgumentCaptor<StreamListener.MessageProducer> producer =
+            ArgumentCaptor.forClass(StreamListener.MessageProducer.class);
+
+    @Before
+    public void setUp() {
+      if (useGzipInflatingBuffer) {
+        deframer.setFullStreamDecompressor(new GzipInflatingBuffer() {
+          @Override
+          public void addGzippedBytes(ReadableBuffer buffer) {
+            try {
+              ByteArrayOutputStream gzippedOutputStream = new ByteArrayOutputStream();
+              OutputStream gzipCompressingStream = new GZIPOutputStream(
+                      gzippedOutputStream);
+              buffer.readBytes(gzipCompressingStream, buffer.readableBytes());
+              gzipCompressingStream.close();
+              super.addGzippedBytes(ReadableBuffers.wrap(gzippedOutputStream.toByteArray()));
+            } catch (IOException e) {
+              throw new RuntimeException(e);
+            }
+          }
+        });
+      }
+    }
+
+    @Test
+    public void simplePayload() {
+      deframer.request(1);
+      deframer.deframe(buffer(new byte[]{0, 0, 0, 0, 2, 3, 14}));
+      verify(listener).messagesAvailable(producer.capture());
+      assertEquals(Bytes.asList(new byte[]{3, 14}), bytes(producer.getValue().next()));
+      verify(listener, atLeastOnce()).bytesRead(anyInt());
+      verifyNoMoreInteractions(listener);
+      checkStats(tracer, transportTracer.getStats(), 2, 2);
+    }
+
+    @Test
+    public void smallCombinedPayloads() {
+      deframer.request(2);
+      deframer.deframe(buffer(new byte[]{0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 2, 14, 15}));
+      verify(listener, times(2)).messagesAvailable(producer.capture());
+      List<StreamListener.MessageProducer> streams = producer.getAllValues();
+      assertEquals(2, streams.size());
+      assertEquals(Bytes.asList(new byte[]{3}), bytes(streams.get(0).next()));
+      verify(listener, atLeastOnce()).bytesRead(anyInt());
+      assertEquals(Bytes.asList(new byte[]{14, 15}), bytes(streams.get(1).next()));
+      verifyNoMoreInteractions(listener);
+      checkStats(tracer, transportTracer.getStats(), 1, 1, 2, 2);
+    }
+
+    @Test
+    public void endOfStreamWithPayloadShouldNotifyEndOfStream() {
+      deframer.request(1);
+      deframer.deframe(buffer(new byte[]{0, 0, 0, 0, 1, 3}));
+      deframer.closeWhenComplete();
+      verify(listener).messagesAvailable(producer.capture());
+      assertEquals(Bytes.asList(new byte[]{3}), bytes(producer.getValue().next()));
+      verify(listener).deframerClosed(false);
+      verify(listener, atLeastOnce()).bytesRead(anyInt());
+      verifyNoMoreInteractions(listener);
+      checkStats(tracer, transportTracer.getStats(), 1, 1);
+    }
+
+    @Test
+    public void endOfStreamShouldNotifyEndOfStream() {
+      deframer.deframe(buffer(new byte[0]));
+      deframer.closeWhenComplete();
+      deframer.request(1);
+      if (useGzipInflatingBuffer) {
+        deframer.request(1); // process the 20-byte empty GZIP stream, to get stalled=true
+        verify(listener, atLeast(1)).bytesRead(anyInt());
+      }
+      verify(listener).deframerClosed(false);
+      verifyNoMoreInteractions(listener);
+      checkStats(tracer, transportTracer.getStats());
+    }
+
+    @Test
+    public void endOfStreamWithPartialMessageShouldNotifyDeframerClosedWithPartialMessage() {
+      deframer.request(1);
+      deframer.deframe(buffer(new byte[1]));
+      deframer.closeWhenComplete();
+      verify(listener, atLeastOnce()).bytesRead(anyInt());
+      verify(listener).deframerClosed(true);
+      verifyNoMoreInteractions(listener);
+      checkStats(tracer, transportTracer.getStats());
+    }
+
+    @Test
+    public void endOfStreamWithInvalidGzipBlockShouldNotifyDeframerClosedWithPartialMessage() {
+      assumeTrue("test only valid for full-stream compression", useGzipInflatingBuffer);
+
+      // Create new deframer to allow writing bytes directly to the GzipInflatingBuffer
+      MessageDeframer deframer = new MessageDeframer(listener, Codec.Identity.NONE,
+              DEFAULT_MAX_MESSAGE_SIZE, statsTraceCtx, transportTracer);
+      deframer.setFullStreamDecompressor(new GzipInflatingBuffer());
+      deframer.request(1);
+      deframer.deframe(buffer(new byte[1]));
+      deframer.closeWhenComplete();
+      verify(listener).deframerClosed(true);
+      verifyNoMoreInteractions(listener);
+      checkStats(tracer, transportTracer.getStats());
+    }
+
+    @Test
+    public void payloadSplitBetweenBuffers() {
+      deframer.request(1);
+      deframer.deframe(buffer(new byte[]{0, 0, 0, 0, 7, 3, 14, 1, 5, 9}));
+      verify(listener, atLeastOnce()).bytesRead(anyInt());
+      verifyNoMoreInteractions(listener);
+      deframer.deframe(buffer(new byte[]{2, 6}));
+      verify(listener).messagesAvailable(producer.capture());
+      assertEquals(
+              Bytes.asList(new byte[]{3, 14, 1, 5, 9, 2, 6}), bytes(producer.getValue().next()));
+      verify(listener, atLeastOnce()).bytesRead(anyInt());
+      verifyNoMoreInteractions(listener);
+
+      if (useGzipInflatingBuffer) {
+        checkStats(
+            tracer,
+            transportTracer.getStats(), 
+            7 /* msg size */ + 2 /* second buffer adds two bytes of overhead in deflate block */,
+            7);
+      } else {
+        checkStats(tracer, transportTracer.getStats(), 7, 7);
+      }
+    }
+
+    @Test
+    public void frameHeaderSplitBetweenBuffers() {
+      deframer.request(1);
+
+      deframer.deframe(buffer(new byte[]{0, 0}));
+      verify(listener, atLeastOnce()).bytesRead(anyInt());
+      verifyNoMoreInteractions(listener);
+      deframer.deframe(buffer(new byte[]{0, 0, 1, 3}));
+      verify(listener).messagesAvailable(producer.capture());
+      assertEquals(Bytes.asList(new byte[]{3}), bytes(producer.getValue().next()));
+      verify(listener, atLeastOnce()).bytesRead(anyInt());
+      verifyNoMoreInteractions(listener);
+      checkStats(tracer, transportTracer.getStats(), 1, 1);
+    }
+
+    @Test
+    public void emptyPayload() {
+      deframer.request(1);
+      deframer.deframe(buffer(new byte[]{0, 0, 0, 0, 0}));
+      verify(listener).messagesAvailable(producer.capture());
+      assertEquals(Bytes.asList(), bytes(producer.getValue().next()));
+      verify(listener, atLeastOnce()).bytesRead(anyInt());
+      verifyNoMoreInteractions(listener);
+      checkStats(tracer, transportTracer.getStats(), 0, 0);
+    }
+
+    @Test
+    public void largerFrameSize() {
+      deframer.request(1);
+      deframer.deframe(ReadableBuffers.wrap(
+              Bytes.concat(new byte[]{0, 0, 0, 3, (byte) 232}, new byte[1000])));
+      verify(listener).messagesAvailable(producer.capture());
+      assertEquals(Bytes.asList(new byte[1000]), bytes(producer.getValue().next()));
+      verify(listener, atLeastOnce()).bytesRead(anyInt());
+      verifyNoMoreInteractions(listener);
+      if (useGzipInflatingBuffer) {
+        checkStats(tracer, transportTracer.getStats(), 8 /* compressed size */, 1000);
+      } else {
+        checkStats(tracer, transportTracer.getStats(), 1000, 1000);
+      }
+    }
+
+    @Test
+    public void endOfStreamCallbackShouldWaitForMessageDelivery() {
+      deframer.deframe(buffer(new byte[]{0, 0, 0, 0, 1, 3}));
+      deframer.closeWhenComplete();
+      verifyNoMoreInteractions(listener);
+
+      deframer.request(1);
+      verify(listener).messagesAvailable(producer.capture());
+      assertEquals(Bytes.asList(new byte[]{3}), bytes(producer.getValue().next()));
+      verify(listener).deframerClosed(false);
+      verify(listener, atLeastOnce()).bytesRead(anyInt());
+      verifyNoMoreInteractions(listener);
+      checkStats(tracer, transportTracer.getStats(), 1, 1);
+    }
+
+    @Test
+    public void compressed() {
+      deframer = new MessageDeframer(listener, new Codec.Gzip(), DEFAULT_MAX_MESSAGE_SIZE,
+              statsTraceCtx, transportTracer);
+      deframer.request(1);
+
+      byte[] payload = compress(new byte[1000]);
+      assertTrue(payload.length < 100);
+      byte[] header = new byte[]{1, 0, 0, 0, (byte) payload.length};
+      deframer.deframe(buffer(Bytes.concat(header, payload)));
+      verify(listener).messagesAvailable(producer.capture());
+      assertEquals(Bytes.asList(new byte[1000]), bytes(producer.getValue().next()));
+      verify(listener, atLeastOnce()).bytesRead(anyInt());
+      verifyNoMoreInteractions(listener);
+    }
+
+    @Test
+    public void deliverIsReentrantSafe() {
+      doAnswer(
+          new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+              deframer.request(1);
+              return null;
+            }
+          })
+          .when(listener)
+          .messagesAvailable(Matchers.<StreamListener.MessageProducer>any());
+      deframer.deframe(buffer(new byte[]{0, 0, 0, 0, 1, 3}));
+      deframer.closeWhenComplete();
+      verifyNoMoreInteractions(listener);
+
+      deframer.request(1);
+      verify(listener).messagesAvailable(producer.capture());
+      assertEquals(Bytes.asList(new byte[]{3}), bytes(producer.getValue().next()));
+      verify(listener).deframerClosed(false);
+      verify(listener, atLeastOnce()).bytesRead(anyInt());
+      verifyNoMoreInteractions(listener);
+    }
+  }
+
+  @RunWith(JUnit4.class)
+  public static class SizeEnforcingInputStreamTests {
+    @Rule
+    public final ExpectedException thrown = ExpectedException.none();
+
+    private TestBaseStreamTracer tracer = new TestBaseStreamTracer();
+    private StatsTraceContext statsTraceCtx = new StatsTraceContext(new StreamTracer[]{tracer});
+
+    @Test
+    public void sizeEnforcingInputStream_readByteBelowLimit() throws IOException {
+      ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8));
+      SizeEnforcingInputStream stream =
+              new MessageDeframer.SizeEnforcingInputStream(in, 4, statsTraceCtx);
+
+      while (stream.read() != -1) {
+      }
+
+      stream.close();
+      checkSizeEnforcingInputStreamStats(tracer, 3);
+    }
+
+    @Test
+    public void sizeEnforcingInputStream_readByteAtLimit() throws IOException {
+      ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8));
+      SizeEnforcingInputStream stream =
+              new MessageDeframer.SizeEnforcingInputStream(in, 3, statsTraceCtx);
+
+      while (stream.read() != -1) {
+      }
+
+      stream.close();
+      checkSizeEnforcingInputStreamStats(tracer, 3);
+    }
+
+    @Test
+    public void sizeEnforcingInputStream_readByteAboveLimit() throws IOException {
+      ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8));
+      SizeEnforcingInputStream stream =
+              new MessageDeframer.SizeEnforcingInputStream(in, 2, statsTraceCtx);
+
+      try {
+        thrown.expect(StatusRuntimeException.class);
+        thrown.expectMessage("RESOURCE_EXHAUSTED: Compressed gRPC message exceeds");
+
+        while (stream.read() != -1) {
+        }
+      } finally {
+        stream.close();
+      }
+    }
+
+    @Test
+    public void sizeEnforcingInputStream_readBelowLimit() throws IOException {
+      ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8));
+      SizeEnforcingInputStream stream =
+              new MessageDeframer.SizeEnforcingInputStream(in, 4, statsTraceCtx);
+      byte[] buf = new byte[10];
+
+      int read = stream.read(buf, 0, buf.length);
+
+      assertEquals(3, read);
+      stream.close();
+      checkSizeEnforcingInputStreamStats(tracer, 3);
+    }
+
+    @Test
+    public void sizeEnforcingInputStream_readAtLimit() throws IOException {
+      ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8));
+      SizeEnforcingInputStream stream =
+              new MessageDeframer.SizeEnforcingInputStream(in, 3, statsTraceCtx);
+      byte[] buf = new byte[10];
+
+      int read = stream.read(buf, 0, buf.length);
+
+      assertEquals(3, read);
+      stream.close();
+      checkSizeEnforcingInputStreamStats(tracer, 3);
+    }
+
+    @Test
+    public void sizeEnforcingInputStream_readAboveLimit() throws IOException {
+      ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8));
+      SizeEnforcingInputStream stream =
+              new MessageDeframer.SizeEnforcingInputStream(in, 2, statsTraceCtx);
+      byte[] buf = new byte[10];
+
+      try {
+        thrown.expect(StatusRuntimeException.class);
+        thrown.expectMessage("RESOURCE_EXHAUSTED: Compressed gRPC message exceeds");
+
+        stream.read(buf, 0, buf.length);
+      } finally {
+        stream.close();
+      }
+    }
+
+    @Test
+    public void sizeEnforcingInputStream_skipBelowLimit() throws IOException {
+      ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8));
+      SizeEnforcingInputStream stream =
+              new MessageDeframer.SizeEnforcingInputStream(in, 4, statsTraceCtx);
+
+      long skipped = stream.skip(4);
+
+      assertEquals(3, skipped);
+
+      stream.close();
+      checkSizeEnforcingInputStreamStats(tracer, 3);
+    }
+
+    @Test
+    public void sizeEnforcingInputStream_skipAtLimit() throws IOException {
+      ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8));
+      SizeEnforcingInputStream stream =
+              new MessageDeframer.SizeEnforcingInputStream(in, 3, statsTraceCtx);
+
+      long skipped = stream.skip(4);
+
+      assertEquals(3, skipped);
+      stream.close();
+      checkSizeEnforcingInputStreamStats(tracer, 3);
+    }
+
+    @Test
+    public void sizeEnforcingInputStream_skipAboveLimit() throws IOException {
+      ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8));
+      SizeEnforcingInputStream stream =
+              new MessageDeframer.SizeEnforcingInputStream(in, 2, statsTraceCtx);
+
+      try {
+        thrown.expect(StatusRuntimeException.class);
+        thrown.expectMessage("RESOURCE_EXHAUSTED: Compressed gRPC message exceeds");
+
+        stream.skip(4);
+      } finally {
+        stream.close();
+      }
+    }
+
+    @Test
+    public void sizeEnforcingInputStream_markReset() throws IOException {
+      ByteArrayInputStream in = new ByteArrayInputStream("foo".getBytes(Charsets.UTF_8));
+      SizeEnforcingInputStream stream =
+              new MessageDeframer.SizeEnforcingInputStream(in, 3, statsTraceCtx);
+      // stream currently looks like: |foo
+      stream.skip(1); // f|oo
+      stream.mark(10); // any large number will work.
+      stream.skip(2); // foo|
+      stream.reset(); // f|oo
+      long skipped = stream.skip(2); // foo|
+
+      assertEquals(2, skipped);
+      stream.close();
+      checkSizeEnforcingInputStreamStats(tracer, 3);
+    }
+  }
+
+  /**
+   * @param transportStats the transport level stats counters
+   * @param sizes in the format {wire0, uncompressed0, wire1, uncompressed1, ...}
+   */
+  private static void checkStats(
+      TestBaseStreamTracer tracer, TransportStats transportStats, long... sizes) {
+    assertEquals(0, sizes.length % 2);
+    int count = sizes.length / 2;
+    long expectedWireSize = 0;
+    long expectedUncompressedSize = 0;
+    for (int i = 0; i < count; i++) {
+      assertEquals("inboundMessage(" + i + ")", tracer.nextInboundEvent());
+      assertEquals(
+          String.format("inboundMessageRead(%d, %d, -1)", i, sizes[i * 2]),
+          tracer.nextInboundEvent());
+      expectedWireSize += sizes[i * 2];
+      expectedUncompressedSize += sizes[i * 2 + 1];
+    }
+    assertNull(tracer.nextInboundEvent());
+    assertNull(tracer.nextOutboundEvent());
+    assertEquals(expectedWireSize, tracer.getInboundWireSize());
+    assertEquals(expectedUncompressedSize, tracer.getInboundUncompressedSize());
+
+    assertEquals(count, transportStats.messagesReceived);
+    long transportReceiveMsgMs = TimeUnit.NANOSECONDS.toMillis(
+        transportStats.lastMessageReceivedTimeNanos);
+    if (count > 0) {
+      assertThat(System.currentTimeMillis() - transportReceiveMsgMs).isAtMost(50L);
+    } else {
+      assertEquals(0, transportReceiveMsgMs);
+    }
+  }
+
+  private static void checkSizeEnforcingInputStreamStats(
+      TestBaseStreamTracer tracer, long uncompressedSize) {
+    assertNull(tracer.nextInboundEvent());
+    assertNull(tracer.nextOutboundEvent());
+    assertEquals(0, tracer.getInboundWireSize());
+    // SizeEnforcingInputStream only reports uncompressed bytes
+    assertEquals(uncompressedSize, tracer.getInboundUncompressedSize());
+  }
+
+  private static List<Byte> bytes(InputStream in) {
+    try {
+      return Bytes.asList(ByteStreams.toByteArray(in));
+    } catch (IOException ex) {
+      throw new AssertionError(ex);
+    }
+  }
+
+  private static ReadableBuffer buffer(byte[] bytes) {
+    return ReadableBuffers.wrap(bytes);
+  }
+
+  private static byte[] compress(byte[] bytes) {
+    try {
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      GZIPOutputStream zip = new GZIPOutputStream(baos);
+      zip.write(bytes);
+      zip.close();
+      return baos.toByteArray();
+    } catch (IOException ex) {
+      throw new RuntimeException(ex);
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/MessageFramerTest.java b/core/src/test/java/io/grpc/internal/MessageFramerTest.java
new file mode 100644
index 0000000..4f6efdb
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/MessageFramerTest.java
@@ -0,0 +1,470 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import io.grpc.Codec;
+import io.grpc.StreamTracer;
+import io.grpc.internal.testing.TestStreamTracer.TestBaseStreamTracer;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link MessageFramer}.
+ */
+@RunWith(JUnit4.class)
+public class MessageFramerTest {
+  @Mock
+  private MessageFramer.Sink sink;
+
+  private final TestBaseStreamTracer tracer = new TestBaseStreamTracer();
+  private MessageFramer framer;
+
+  @Captor
+  private ArgumentCaptor<ByteWritableBuffer> frameCaptor;
+  @Captor
+  private ArgumentCaptor<Long> wireSizeCaptor;
+  @Captor
+  private ArgumentCaptor<Long> uncompressedSizeCaptor;
+  private BytesWritableBufferAllocator allocator =
+      new BytesWritableBufferAllocator(1000, 1000);
+  private StatsTraceContext statsTraceCtx;
+
+  /** Set up for test. */
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    // MessageDeframerTest tests with a client-side StatsTraceContext, so here we test with a
+    // server-side StatsTraceContext.
+    statsTraceCtx = new StatsTraceContext(new StreamTracer[]{tracer});
+    framer = new MessageFramer(sink, allocator, statsTraceCtx);
+  }
+
+  @Test
+  public void simplePayload() {
+    writeKnownLength(framer, new byte[]{3, 14});
+    verifyNoMoreInteractions(sink);
+    framer.flush();
+
+    verify(sink).deliverFrame(toWriteBuffer(new byte[] {0, 0, 0, 0, 2, 3, 14}), false, true, 1);
+    assertEquals(1, allocator.allocCount);
+    verifyNoMoreInteractions(sink);
+    checkStats(2, 2);
+  }
+
+  @Test
+  public void simpleUnknownLengthPayload() {
+    writeUnknownLength(framer, new byte[]{3, 14});
+    framer.flush();
+    // Header is written first, then payload
+    verify(sink).deliverFrame(toWriteBuffer(new byte[] {0, 0, 0, 0, 2}), false, false, 0);
+    verify(sink).deliverFrame(toWriteBuffer(new byte[] {3, 14}), false, true, 1);
+    assertEquals(2, allocator.allocCount);
+    verifyNoMoreInteractions(sink);
+    checkStats(2, 2);
+  }
+
+  @Test
+  public void smallPayloadsShouldBeCombined() {
+    writeKnownLength(framer, new byte[]{3});
+    verifyNoMoreInteractions(sink);
+    writeKnownLength(framer, new byte[]{14});
+    verifyNoMoreInteractions(sink);
+    framer.flush();
+    verify(sink).deliverFrame(
+        toWriteBuffer(new byte[] {0, 0, 0, 0, 1, 3, 0, 0, 0, 0, 1, 14}), false, true, 2);
+    verifyNoMoreInteractions(sink);
+    assertEquals(1, allocator.allocCount);
+    checkStats(1, 1, 1, 1);
+  }
+
+  @Test
+  public void closeCombinedWithFullSink() {
+    writeKnownLength(framer, new byte[]{3, 14, 1, 5, 9, 2, 6});
+    verifyNoMoreInteractions(sink);
+    framer.close();
+    verify(sink).deliverFrame(
+        toWriteBuffer(new byte[] {0, 0, 0, 0, 7, 3, 14, 1, 5, 9, 2, 6}), true, true, 1);
+    verifyNoMoreInteractions(sink);
+    assertEquals(1, allocator.allocCount);
+    checkStats(7, 7);
+  }
+
+  @Test
+  public void closeWithoutBufferedFrameGivesNullBuffer() {
+    framer.close();
+    verify(sink).deliverFrame(null, true, true, 0);
+    verifyNoMoreInteractions(sink);
+    assertEquals(0, allocator.allocCount);
+    checkStats();
+  }
+
+  @Test
+  public void payloadSplitBetweenSinks() {
+    allocator = new BytesWritableBufferAllocator(12, 12);
+    framer = new MessageFramer(sink, allocator, statsTraceCtx);
+    writeKnownLength(framer, new byte[]{3, 14, 1, 5, 9, 2, 6, 5});
+    verify(sink).deliverFrame(
+        toWriteBuffer(new byte[] {0, 0, 0, 0, 8, 3, 14, 1, 5, 9, 2, 6}), false, false, 1);
+    verifyNoMoreInteractions(sink);
+
+    framer.flush();
+    verify(sink).deliverFrame(toWriteBuffer(new byte[] {5}), false, true, 0);
+    verifyNoMoreInteractions(sink);
+    assertEquals(2, allocator.allocCount);
+    checkStats(8, 8);
+  }
+
+  @Test
+  public void frameHeaderSplitBetweenSinks() {
+    allocator = new BytesWritableBufferAllocator(12, 12);
+    framer = new MessageFramer(sink, allocator, statsTraceCtx);
+    writeKnownLength(framer, new byte[]{3, 14, 1});
+    writeKnownLength(framer, new byte[]{3});
+    verify(sink).deliverFrame(
+            toWriteBuffer(new byte[] {0, 0, 0, 0, 3, 3, 14, 1, 0, 0, 0, 0}), false, false, 2);
+    verifyNoMoreInteractions(sink);
+
+    framer.flush();
+    verify(sink).deliverFrame(toWriteBufferWithMinSize(new byte[] {1, 3}, 12), false, true, 0);
+    verifyNoMoreInteractions(sink);
+    assertEquals(2, allocator.allocCount);
+    checkStats(3, 3, 1, 1);
+  }
+
+  @Test
+  public void emptyPayloadYieldsFrame() throws Exception {
+    writeKnownLength(framer, new byte[0]);
+    framer.flush();
+    verify(sink).deliverFrame(toWriteBuffer(new byte[] {0, 0, 0, 0, 0}), false, true, 1);
+    assertEquals(1, allocator.allocCount);
+    checkStats(0, 0);
+  }
+
+  @Test
+  public void emptyUnknownLengthPayloadYieldsFrame() throws Exception {
+    writeUnknownLength(framer, new byte[0]);
+    verifyZeroInteractions(sink);
+    framer.flush();
+    verify(sink).deliverFrame(toWriteBuffer(new byte[] {0, 0, 0, 0, 0}), false, true, 1);
+    // One alloc for the header
+    assertEquals(1, allocator.allocCount);
+    checkStats(0, 0);
+  }
+
+  @Test
+  public void flushIsIdempotent() {
+    writeKnownLength(framer, new byte[]{3, 14});
+    framer.flush();
+    framer.flush();
+    verify(sink).deliverFrame(toWriteBuffer(new byte[] {0, 0, 0, 0, 2, 3, 14}), false, true, 1);
+    verifyNoMoreInteractions(sink);
+    assertEquals(1, allocator.allocCount);
+    checkStats(2, 2);
+  }
+
+  @Test
+  public void largerFrameSize() throws Exception {
+    allocator = new BytesWritableBufferAllocator(0, 10000);
+    framer = new MessageFramer(sink, allocator, statsTraceCtx);
+    writeKnownLength(framer, new byte[1000]);
+    framer.flush();
+    verify(sink).deliverFrame(frameCaptor.capture(), eq(false), eq(true), eq(1));
+    ByteWritableBuffer buffer = frameCaptor.getValue();
+    assertEquals(1005, buffer.size());
+
+    byte[] data = new byte[1005];
+    data[3] = 3;
+    data[4] = (byte) 232;
+
+    assertEquals(toWriteBuffer(data), buffer);
+    verifyNoMoreInteractions(sink);
+    assertEquals(1, allocator.allocCount);
+    checkStats(1000, 1000);
+  }
+
+  @Test
+  public void largerFrameSizeUnknownLength() throws Exception {
+    // Force payload to be split into two chunks
+    allocator = new BytesWritableBufferAllocator(500, 500);
+    framer = new MessageFramer(sink, allocator, statsTraceCtx);
+    writeUnknownLength(framer, new byte[1000]);
+    framer.flush();
+    // Header and first chunk written with flush = false
+    verify(sink, times(2)).deliverFrame(frameCaptor.capture(), eq(false), eq(false), eq(0));
+    // On flush third buffer written with flish = true
+    // The message count is only bumped when a message is completely written.
+    verify(sink).deliverFrame(frameCaptor.capture(), eq(false), eq(true), eq(1));
+
+    // header has fixed length of 5 and specifies correct length
+    assertEquals(5, frameCaptor.getAllValues().get(0).readableBytes());
+    byte[] data = new byte[5];
+    data[3] = 3;
+    data[4] = (byte) 232;
+    assertEquals(toWriteBuffer(data), frameCaptor.getAllValues().get(0));
+
+    assertEquals(500, frameCaptor.getAllValues().get(1).readableBytes());
+    assertEquals(500, frameCaptor.getAllValues().get(2).readableBytes());
+
+    verifyNoMoreInteractions(sink);
+    assertEquals(3, allocator.allocCount);
+    checkStats(1000, 1000);
+  }
+
+  @Test
+  public void compressed() throws Exception {
+    allocator = new BytesWritableBufferAllocator(100, Integer.MAX_VALUE);
+    // setMessageCompression should default to true
+    framer = new MessageFramer(sink, allocator, statsTraceCtx)
+        .setCompressor(new Codec.Gzip());
+    writeKnownLength(framer, new byte[1000]);
+    framer.flush();
+    // The GRPC header is written first as a separate frame.
+    // The message count is only bumped when a message is completely written.
+    verify(sink).deliverFrame(frameCaptor.capture(), eq(false), eq(false), eq(0));
+    verify(sink).deliverFrame(frameCaptor.capture(), eq(false), eq(true), eq(1));
+
+    // Check the header
+    ByteWritableBuffer buffer = frameCaptor.getAllValues().get(0);
+
+    assertEquals(0x1, buffer.data[0]);
+    ByteBuffer byteBuf = ByteBuffer.wrap(buffer.data, 1, 4);
+    byteBuf.order(ByteOrder.BIG_ENDIAN);
+    int length = byteBuf.getInt();
+    // compressed data should be smaller than uncompressed data.
+    assertTrue(length < 1000);
+
+    assertEquals(frameCaptor.getAllValues().get(1).size(), length);
+    checkStats(length, 1000);
+  }
+
+  @Test
+  public void dontCompressIfNoEncoding() throws Exception {
+    allocator = new BytesWritableBufferAllocator(100, Integer.MAX_VALUE);
+    framer = new MessageFramer(sink, allocator, statsTraceCtx)
+        .setMessageCompression(true);
+    writeKnownLength(framer, new byte[1000]);
+    framer.flush();
+    // The GRPC header is written first as a separate frame
+    verify(sink).deliverFrame(frameCaptor.capture(), eq(false), eq(true), eq(1));
+
+    // Check the header
+    ByteWritableBuffer buffer = frameCaptor.getAllValues().get(0);
+    // We purposefully don't check the last byte of length, since that depends on how exactly it
+    // compressed.
+
+    assertEquals(0x0, buffer.data[0]);
+    ByteBuffer byteBuf = ByteBuffer.wrap(buffer.data, 1, 4);
+    byteBuf.order(ByteOrder.BIG_ENDIAN);
+    int length = byteBuf.getInt();
+    assertEquals(1000, length);
+
+    assertEquals(buffer.data.length - 5 , length);
+    checkStats(1000, 1000);
+  }
+
+  @Test
+  public void dontCompressIfNotRequested() throws Exception {
+    allocator = new BytesWritableBufferAllocator(100, Integer.MAX_VALUE);
+    framer = new MessageFramer(sink, allocator, statsTraceCtx)
+        .setCompressor(new Codec.Gzip())
+        .setMessageCompression(false);
+    writeKnownLength(framer, new byte[1000]);
+    framer.flush();
+    // The GRPC header is written first as a separate frame
+    verify(sink).deliverFrame(frameCaptor.capture(), eq(false), eq(true), eq(1));
+
+    // Check the header
+    ByteWritableBuffer buffer = frameCaptor.getAllValues().get(0);
+    // We purposefully don't check the last byte of length, since that depends on how exactly it
+    // compressed.
+
+    assertEquals(0x0, buffer.data[0]);
+    ByteBuffer byteBuf = ByteBuffer.wrap(buffer.data, 1, 4);
+    byteBuf.order(ByteOrder.BIG_ENDIAN);
+    int length = byteBuf.getInt();
+    assertEquals(1000, length);
+
+    assertEquals(buffer.data.length - 5 , length);
+    checkStats(1000, 1000);
+  }
+
+  @Test
+  public void closeIsRentrantSafe() throws Exception {
+    MessageFramer.Sink reentrant = new MessageFramer.Sink() {
+      int count = 0;
+      @Override
+      public void deliverFrame(
+          WritableBuffer frame, boolean endOfStream, boolean flush, int numMessages) {
+        if (count == 0) {
+          framer.close();
+          count++;
+        } else {
+          fail("received event from reentrant call to close");
+        }
+      }
+    };
+    framer = new MessageFramer(reentrant, allocator, statsTraceCtx);
+    writeKnownLength(framer, new byte[]{3, 14});
+    framer.close();
+  }
+
+  @Test
+  public void zeroLengthCompressibleMessageIsNotCompressed() {
+    framer.setCompressor(new Codec.Gzip());
+    framer.setMessageCompression(true);
+    writeKnownLength(framer, new byte[]{});
+    framer.flush();
+    verify(sink).deliverFrame(toWriteBuffer(new byte[] {0, 0, 0, 0, 0}), false, true, 1);
+    checkStats(0, 0);
+  }
+
+  private static WritableBuffer toWriteBuffer(byte[] data) {
+    return toWriteBufferWithMinSize(data, 0);
+  }
+
+  private static WritableBuffer toWriteBufferWithMinSize(byte[] data, int minFrameSize) {
+    ByteWritableBuffer buffer = new ByteWritableBuffer(Math.max(data.length, minFrameSize));
+    buffer.write(data, 0, data.length);
+    return buffer;
+  }
+
+  private static void writeUnknownLength(MessageFramer framer, byte[] bytes) {
+    framer.writePayload(new BufferedInputStream(new ByteArrayInputStream(bytes)));
+  }
+
+  private static void writeKnownLength(MessageFramer framer, byte[] bytes) {
+    framer.writePayload(new ByteArrayInputStream(bytes));
+    // TODO(carl-mastrangelo): add framer.flush() here.
+  }
+
+  /**
+   * @param sizes in the format {wire0, uncompressed0, wire1, uncompressed1, ...}
+   */
+  private void checkStats(long... sizes) {
+    assertEquals(0, sizes.length % 2);
+    int count = sizes.length / 2;
+    long expectedWireSize = 0;
+    long expectedUncompressedSize = 0;
+    for (int i = 0; i < count; i++) {
+      assertEquals("outboundMessage(" + i + ")", tracer.nextOutboundEvent());
+      assertEquals(
+          String.format("outboundMessageSent(%d, %d, %d)", i, sizes[i * 2], sizes[i * 2 + 1]),
+          tracer.nextOutboundEvent());
+      expectedWireSize += sizes[i * 2];
+      expectedUncompressedSize += sizes[i * 2 + 1];
+    }
+    assertNull(tracer.nextOutboundEvent());
+    assertNull(tracer.nextInboundEvent());
+    assertEquals(expectedWireSize, tracer.getOutboundWireSize());
+    assertEquals(expectedUncompressedSize, tracer.getOutboundUncompressedSize());
+  }
+
+  static class ByteWritableBuffer implements WritableBuffer {
+    byte[] data;
+    private int writeIdx;
+
+    ByteWritableBuffer(int maxFrameSize) {
+      data = new byte[maxFrameSize];
+    }
+
+    @Override
+    public void write(byte[] bytes, int srcIndex, int length) {
+      System.arraycopy(bytes, srcIndex, data, writeIdx, length);
+      writeIdx += length;
+    }
+
+    @Override
+    public void write(byte b) {
+      data[writeIdx++] = b;
+    }
+
+    @Override
+    public int writableBytes() {
+      return data.length - writeIdx;
+    }
+
+    @Override
+    public int readableBytes() {
+      return writeIdx;
+    }
+
+    @Override
+    public void release() {
+      data = null;
+    }
+
+    int size() {
+      return writeIdx;
+    }
+
+    @Override
+    public boolean equals(Object buffer) {
+      if (!(buffer instanceof ByteWritableBuffer)) {
+        return false;
+      }
+
+      ByteWritableBuffer other = (ByteWritableBuffer) buffer;
+
+      return readableBytes() == other.readableBytes()
+          && Arrays.equals(Arrays.copyOf(data, readableBytes()),
+            Arrays.copyOf(other.data, readableBytes()));
+    }
+
+    @Override
+    public int hashCode() {
+      return Arrays.hashCode(data) + writableBytes() + readableBytes();
+    }
+  }
+
+  static class BytesWritableBufferAllocator implements WritableBufferAllocator {
+    public int minSize;
+    public int maxSize;
+    public int allocCount = 0;
+
+    BytesWritableBufferAllocator(int minSize, int maxSize) {
+      this.minSize = minSize;
+      this.maxSize = maxSize;
+    }
+
+    @Override
+    public WritableBuffer allocate(int capacityHint) {
+      allocCount++;
+      return new ByteWritableBuffer(Math.min(maxSize, Math.max(capacityHint, minSize)));
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/NoopClientStreamListener.java b/core/src/test/java/io/grpc/internal/NoopClientStreamListener.java
new file mode 100644
index 0000000..90a1c8c
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/NoopClientStreamListener.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Metadata;
+import io.grpc.Status;
+
+/**
+ * No-op base class for testing.
+ */
+public class NoopClientStreamListener implements ClientStreamListener {
+  @Override
+  public void messagesAvailable(MessageProducer producer) {}
+
+  @Override
+  public void onReady() {}
+
+  @Override
+  public void headersRead(Metadata headers) {}
+
+  @Override
+  public void closed(Status status, Metadata trailers) {}
+
+  @Override
+  public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) {}
+}
diff --git a/core/src/test/java/io/grpc/internal/OverrideAuthorityNameResolverTest.java b/core/src/test/java/io/grpc/internal/OverrideAuthorityNameResolverTest.java
new file mode 100644
index 0000000..619b5ff
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/OverrideAuthorityNameResolverTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static junit.framework.TestCase.assertNotNull;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.grpc.Attributes;
+import io.grpc.NameResolver;
+import java.net.URI;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link OverrideAuthorityNameResolverFactory}. */
+@RunWith(JUnit4.class)
+public class OverrideAuthorityNameResolverTest {
+  @Test
+  public void overridesAuthority() {
+    NameResolver nameResolverMock = mock(NameResolver.class);
+    NameResolver.Factory wrappedFactory = mock(NameResolver.Factory.class);
+    when(wrappedFactory.newNameResolver(any(URI.class), any(Attributes.class)))
+        .thenReturn(nameResolverMock);
+    String override = "override:5678";
+    NameResolver.Factory factory =
+        new OverrideAuthorityNameResolverFactory(wrappedFactory, override);
+    NameResolver nameResolver = factory.newNameResolver(URI.create("dns:///localhost:443"),
+        Attributes.EMPTY);
+    assertNotNull(nameResolver);
+    assertEquals(override, nameResolver.getServiceAuthority());
+  }
+
+  @Test
+  public void wontWrapNull() {
+    NameResolver.Factory wrappedFactory = mock(NameResolver.Factory.class);
+    when(wrappedFactory.newNameResolver(any(URI.class), any(Attributes.class))).thenReturn(null);
+    NameResolver.Factory factory =
+        new OverrideAuthorityNameResolverFactory(wrappedFactory, "override:5678");
+    assertEquals(null,
+        factory.newNameResolver(URI.create("dns:///localhost:443"), Attributes.EMPTY));
+  }
+
+  @Test
+  public void forwardsNonOverridenCalls() {
+    NameResolver.Factory wrappedFactory = mock(NameResolver.Factory.class);
+    NameResolver mockResolver = mock(NameResolver.class);
+    when(wrappedFactory.newNameResolver(any(URI.class), any(Attributes.class)))
+        .thenReturn(mockResolver);
+    NameResolver.Factory factory =
+        new OverrideAuthorityNameResolverFactory(wrappedFactory, "override:5678");
+    NameResolver overrideResolver =
+        factory.newNameResolver(URI.create("dns:///localhost:443"), Attributes.EMPTY);
+    assertNotNull(overrideResolver);
+    NameResolver.Listener listener = mock(NameResolver.Listener.class);
+
+    overrideResolver.start(listener);
+    verify(mockResolver).start(listener);
+
+    overrideResolver.shutdown();
+    verify(mockResolver).shutdown();
+
+    overrideResolver.refresh();
+    verify(mockResolver).refresh();
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ProxyDetectorImplTest.java b/core/src/test/java/io/grpc/internal/ProxyDetectorImplTest.java
new file mode 100644
index 0000000..f2a21de
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ProxyDetectorImplTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static junit.framework.TestCase.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.PasswordAuthentication;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URI;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class ProxyDetectorImplTest {
+  private static final String NO_USER = null;
+  private static final String NO_PW = null;
+  @Mock private ProxySelector proxySelector;
+  @Mock private ProxyDetectorImpl.AuthenticationProvider authenticator;
+  private InetSocketAddress destination = InetSocketAddress.createUnresolved("10.10.10.10", 5678);
+  private Supplier<ProxySelector> proxySelectorSupplier;
+  private ProxyDetector proxyDetector;
+  private InetSocketAddress unresolvedProxy;
+  private ProxyParameters proxyParmeters;
+
+  @Before
+  public void setUp() throws Exception {
+    MockitoAnnotations.initMocks(this);
+    proxySelectorSupplier = new Supplier<ProxySelector>() {
+      @Override
+      public ProxySelector get() {
+        return proxySelector;
+      }
+    };
+    proxyDetector = new ProxyDetectorImpl(proxySelectorSupplier, authenticator, null);
+    int proxyPort = 1234;
+    unresolvedProxy = InetSocketAddress.createUnresolved("10.0.0.1", proxyPort);
+    proxyParmeters = new ProxyParameters(
+        new InetSocketAddress(InetAddress.getByName(unresolvedProxy.getHostName()), proxyPort),
+        NO_USER,
+        NO_PW);
+  }
+
+  @Test
+  public void override_hostPort() throws Exception {
+    final String overrideHost = "10.99.99.99";
+    final int overridePort = 1234;
+    final String overrideHostWithPort = overrideHost + ":" + overridePort;
+    ProxyDetectorImpl proxyDetector = new ProxyDetectorImpl(
+        proxySelectorSupplier,
+        authenticator,
+        overrideHostWithPort);
+    ProxyParameters detected = proxyDetector.proxyFor(destination);
+    assertNotNull(detected);
+    assertEquals(
+        new ProxyParameters(
+            new InetSocketAddress(InetAddress.getByName(overrideHost), overridePort),
+            NO_USER,
+            NO_PW),
+        detected);
+  }
+
+  @Test
+  public void override_hostOnly() throws Exception {
+    final String overrideHostWithoutPort = "10.99.99.99";
+    final int defaultPort = 80;
+    ProxyDetectorImpl proxyDetector = new ProxyDetectorImpl(
+        proxySelectorSupplier,
+        authenticator,
+        overrideHostWithoutPort);
+    ProxyParameters detected = proxyDetector.proxyFor(destination);
+    assertNotNull(detected);
+    assertEquals(
+        new ProxyParameters(
+            new InetSocketAddress(
+                InetAddress.getByName(overrideHostWithoutPort), defaultPort),
+            NO_USER,
+            NO_PW),
+        detected);
+  }
+
+  @Test
+  public void returnNullWhenNoProxy() throws Exception {
+    when(proxySelector.select(any(URI.class)))
+        .thenReturn(ImmutableList.of(java.net.Proxy.NO_PROXY));
+    assertNull(proxyDetector.proxyFor(destination));
+  }
+
+  @Test
+  public void detectProxyForUnresolvedDestination() throws Exception {
+    Proxy proxy = new Proxy(Proxy.Type.HTTP, unresolvedProxy);
+    when(proxySelector.select(any(URI.class))).thenReturn(ImmutableList.of(proxy));
+
+    ProxyParameters detected = proxyDetector.proxyFor(destination);
+    assertNotNull(detected);
+    assertEquals(proxyParmeters, detected);
+  }
+
+  @Test
+  public void detectProxyForResolvedDestination() throws Exception {
+    InetSocketAddress resolved = new InetSocketAddress(InetAddress.getByName("10.1.2.3"), 10);
+    assertFalse(resolved.isUnresolved());
+    destination = resolved;
+
+    Proxy proxy = new Proxy(Proxy.Type.HTTP, unresolvedProxy);
+    when(proxySelector.select(any(URI.class))).thenReturn(ImmutableList.of(proxy));
+
+    ProxyParameters detected = proxyDetector.proxyFor(destination);
+    assertNotNull(detected);
+    assertEquals(proxyParmeters, detected);
+  }
+
+  @Test
+  public void unresolvedProxyAddressBecomesResolved() throws Exception {
+    InetSocketAddress unresolvedProxy = InetSocketAddress.createUnresolved("10.0.0.100", 1234);
+    assertTrue(unresolvedProxy.isUnresolved());
+    Proxy proxy1 = new java.net.Proxy(java.net.Proxy.Type.HTTP, unresolvedProxy);
+    when(proxySelector.select(any(URI.class))).thenReturn(ImmutableList.of(proxy1));
+    ProxyParameters proxy = proxyDetector.proxyFor(destination);
+    assertFalse(proxy.proxyAddress.isUnresolved());
+  }
+
+  @Test
+  public void pickFirstHttpProxy() throws Exception {
+    InetSocketAddress otherProxy = InetSocketAddress.createUnresolved("10.0.0.2", 11111);
+    assertNotEquals(unresolvedProxy, otherProxy);
+    Proxy proxy1 = new java.net.Proxy(java.net.Proxy.Type.HTTP, unresolvedProxy);
+    Proxy proxy2 = new java.net.Proxy(java.net.Proxy.Type.HTTP, otherProxy);
+    when(proxySelector.select(any(URI.class))).thenReturn(ImmutableList.of(proxy1, proxy2));
+
+    ProxyParameters detected = proxyDetector.proxyFor(destination);
+    assertNotNull(detected);
+    assertEquals(proxyParmeters, detected);
+  }
+
+  // Mainly for InProcessSocketAddress
+  @Test
+  public void noProxyForNonInetSocket() throws Exception {
+    assertNull(proxyDetector.proxyFor(mock(SocketAddress.class)));
+  }
+
+  @Test
+  public void authRequired() throws Exception {
+    Proxy proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, unresolvedProxy);
+    final String proxyUser = "testuser";
+    final String proxyPassword = "testpassword";
+    PasswordAuthentication auth = new PasswordAuthentication(
+        proxyUser,
+        proxyPassword.toCharArray());
+    when(authenticator.requestPasswordAuthentication(
+        any(String.class),
+        any(InetAddress.class),
+        any(Integer.class),
+        any(String.class),
+        any(String.class),
+        any(String.class))).thenReturn(auth);
+    when(proxySelector.select(any(URI.class))).thenReturn(ImmutableList.of(proxy));
+
+    ProxyParameters detected = proxyDetector.proxyFor(destination);
+    assertEquals(
+        new ProxyParameters(
+            new InetSocketAddress(
+                InetAddress.getByName(unresolvedProxy.getHostName()),
+                unresolvedProxy.getPort()),
+            proxyUser,
+            proxyPassword),
+        detected);
+  }
+
+  @Test
+  public void proxySelectorReturnsNull() throws Exception {
+    ProxyDetectorImpl proxyDetector = new ProxyDetectorImpl(
+        new Supplier<ProxySelector>() {
+          @Override
+          public ProxySelector get() {
+            return null;
+          }
+        },
+        authenticator,
+        null);
+    assertNull(proxyDetector.proxyFor(destination));
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ReadableBufferTestBase.java b/core/src/test/java/io/grpc/internal/ReadableBufferTestBase.java
new file mode 100644
index 0000000..c53b891
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ReadableBufferTestBase.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Abstract base class for tests of {@link ReadableBuffer} subclasses.
+ */
+@RunWith(JUnit4.class)
+public abstract class ReadableBufferTestBase {
+  // Use a long string to ensure that any chunking/splitting works correctly.
+  protected static final String msg = repeatUntilLength("hello", 8 * 1024);
+
+  @Test
+  public void bufferShouldReadAllBytes() {
+    ReadableBuffer buffer = buffer();
+    for (int ix = 0; ix < msg.length(); ++ix) {
+      assertEquals(msg.length() - ix, buffer.readableBytes());
+      assertEquals(msg.charAt(ix), buffer.readUnsignedByte());
+    }
+    assertEquals(0, buffer.readableBytes());
+  }
+
+  @Test
+  public void readToArrayShouldSucceed() {
+    ReadableBuffer buffer = buffer();
+    byte[] array = new byte[msg.length()];
+    buffer.readBytes(array, 0, array.length);
+    assertArrayEquals(msg.getBytes(UTF_8), array);
+    assertEquals(0, buffer.readableBytes());
+  }
+
+  @Test
+  public void partialReadToArrayShouldSucceed() {
+    ReadableBuffer buffer = buffer();
+    byte[] array = new byte[msg.length()];
+    buffer.readBytes(array, 1, 2);
+    assertArrayEquals(new byte[] {'h', 'e'}, Arrays.copyOfRange(array, 1, 3));
+    assertEquals(msg.length() - 2, buffer.readableBytes());
+  }
+
+  @Test
+  public void readToStreamShouldSucceed() throws Exception {
+    ReadableBuffer buffer = buffer();
+    ByteArrayOutputStream stream = new ByteArrayOutputStream();
+    buffer.readBytes(stream, msg.length());
+    assertArrayEquals(msg.getBytes(UTF_8), stream.toByteArray());
+    assertEquals(0, buffer.readableBytes());
+  }
+
+  @Test
+  public void partialReadToStreamShouldSucceed() throws Exception {
+    ReadableBuffer buffer = buffer();
+    ByteArrayOutputStream stream = new ByteArrayOutputStream();
+    buffer.readBytes(stream, 2);
+    assertArrayEquals(new byte[]{'h', 'e'}, Arrays.copyOfRange(stream.toByteArray(), 0, 2));
+    assertEquals(msg.length() - 2, buffer.readableBytes());
+  }
+
+  @Test
+  public void readToByteBufferShouldSucceed() {
+    ReadableBuffer buffer = buffer();
+    ByteBuffer byteBuffer = ByteBuffer.allocate(msg.length());
+    buffer.readBytes(byteBuffer);
+    byteBuffer.flip();
+    byte[] array = new byte[msg.length()];
+    byteBuffer.get(array);
+    assertArrayEquals(msg.getBytes(UTF_8), array);
+    assertEquals(0, buffer.readableBytes());
+  }
+
+  @Test
+  public void partialReadToByteBufferShouldSucceed() {
+    ReadableBuffer buffer = buffer();
+    ByteBuffer byteBuffer = ByteBuffer.allocate(2);
+    buffer.readBytes(byteBuffer);
+    byteBuffer.flip();
+    byte[] array = new byte[2];
+    byteBuffer.get(array);
+    assertArrayEquals(new byte[]{'h', 'e'}, array);
+    assertEquals(msg.length() - 2, buffer.readableBytes());
+  }
+
+  @Test
+  public void partialReadToReadableBufferShouldSucceed() {
+    ReadableBuffer buffer = buffer();
+    ReadableBuffer newBuffer = buffer.readBytes(2);
+    assertEquals(2, newBuffer.readableBytes());
+    assertEquals(msg.length() - 2, buffer.readableBytes());
+    byte[] array = new byte[2];
+    newBuffer.readBytes(array, 0, 2);
+    assertArrayEquals(new byte[] {'h', 'e'}, Arrays.copyOfRange(array, 0, 2));     
+  }
+
+  protected abstract ReadableBuffer buffer();
+
+  private static String repeatUntilLength(String toRepeat, int length) {
+    StringBuilder buf = new StringBuilder();
+    while (buf.length() < length) {
+      buf.append(toRepeat);
+    }
+    buf.setLength(length);
+    return buf.toString();
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ReadableBuffersArrayTest.java b/core/src/test/java/io/grpc/internal/ReadableBuffersArrayTest.java
new file mode 100644
index 0000000..2bed7d5
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ReadableBuffersArrayTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static io.grpc.internal.ReadableBuffers.wrap;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Tests for the array-backed {@link ReadableBuffer} returned by {@link ReadableBuffers#wrap(byte[],
+ * int, int)}.
+ */
+public class ReadableBuffersArrayTest extends ReadableBufferTestBase {
+
+  @Test
+  public void bufferShouldExposeArray() {
+    byte[] array = msg.getBytes(UTF_8);
+    ReadableBuffer buffer = wrap(array, 1, msg.length() - 1);
+    assertTrue(buffer.hasArray());
+    assertSame(array, buffer.array());
+    assertEquals(1, buffer.arrayOffset());
+
+    // Now read a byte and verify that the offset changes.
+    buffer.readUnsignedByte();
+    assertEquals(2, buffer.arrayOffset());
+  }
+
+  @Override
+  protected ReadableBuffer buffer() {
+    return ReadableBuffers.wrap(msg.getBytes(UTF_8), 0, msg.length());
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ReadableBuffersByteBufferTest.java b/core/src/test/java/io/grpc/internal/ReadableBuffersByteBufferTest.java
new file mode 100644
index 0000000..a040182
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ReadableBuffersByteBufferTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Charsets.UTF_8;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Tests for the array-backed {@link ReadableBuffer} returned by {@link
+ * ReadableBuffers#wrap(ByteBuffer)}.
+ */
+public class ReadableBuffersByteBufferTest extends ReadableBufferTestBase {
+
+  @Override
+  protected ReadableBuffer buffer() {
+    return ReadableBuffers.wrap(ByteBuffer.wrap(msg.getBytes(UTF_8)));
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ReadableBuffersTest.java b/core/src/test/java/io/grpc/internal/ReadableBuffersTest.java
new file mode 100644
index 0000000..ea9daee
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ReadableBuffersTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.io.InputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link ReadableBuffers}.
+ * See also: {@link ReadableBuffersArrayTest}, {@link ReadableBuffersByteBufferTest}.
+ */
+@RunWith(JUnit4.class)
+public class ReadableBuffersTest {
+  private static final byte[] MSG_BYTES = "hello".getBytes(UTF_8);
+
+  @Test
+  public void empty_returnsEmptyBuffer() {
+    ReadableBuffer buffer = ReadableBuffers.empty();
+    assertArrayEquals(new byte[0], buffer.array());
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void readArray_checksNotNull() {
+    ReadableBuffers.readArray(null);
+  }
+
+  @Test
+  public void readArray_returnsBufferArray() {
+    ReadableBuffer buffer = ReadableBuffers.wrap(MSG_BYTES);
+    assertArrayEquals(new byte[]{'h', 'e', 'l', 'l', 'o'}, ReadableBuffers.readArray(buffer));
+  }
+
+  @Test
+  public void readAsString_returnsString() {
+    ReadableBuffer buffer = ReadableBuffers.wrap(MSG_BYTES);
+    assertEquals("hello", ReadableBuffers.readAsString(buffer, UTF_8));
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void readAsString_checksNotNull() {
+    ReadableBuffers.readAsString(null, UTF_8);
+  }
+
+  @Test
+  public void readAsStringUtf8_returnsString() {
+    ReadableBuffer buffer = ReadableBuffers.wrap(MSG_BYTES);
+    assertEquals("hello", ReadableBuffers.readAsStringUtf8(buffer));
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void readAsStringUtf8_checksNotNull() {
+    ReadableBuffers.readAsStringUtf8(null);
+  }
+
+  @Test
+  public void openStream_ignoresClose() throws Exception {
+    ReadableBuffer buffer = mock(ReadableBuffer.class);
+    InputStream stream = ReadableBuffers.openStream(buffer, false);
+    stream.close();
+    verify(buffer, never()).close();
+  }
+
+  @Test
+  public void bufferInputStream_available_returnsReadableBytes() throws Exception {
+    ReadableBuffer buffer = ReadableBuffers.wrap(MSG_BYTES);
+    InputStream inputStream = ReadableBuffers.openStream(buffer, true);
+    assertEquals(5, inputStream.available());
+    while (inputStream.available() != 0) {
+      inputStream.read();
+    }
+    assertEquals(-1, inputStream.read());
+  }
+
+  @Test
+  public void bufferInputStream_read_returnsUnsignedByte() throws Exception {
+    ReadableBuffer buffer = ReadableBuffers.wrap(MSG_BYTES);
+    InputStream inputStream = ReadableBuffers.openStream(buffer, true);
+    assertEquals((int) 'h', inputStream.read());
+  }
+
+  @Test
+  public void bufferInputStream_read_writes() throws Exception {
+    ReadableBuffer buffer = ReadableBuffers.wrap(MSG_BYTES);
+    InputStream inputStream = ReadableBuffers.openStream(buffer, true);
+    byte[] dest = new byte[5];
+    assertEquals(5, inputStream.read(dest, /*destOffset*/ 0, /*length*/ 5));
+    assertArrayEquals(new byte[]{'h', 'e', 'l', 'l', 'o'}, dest);
+    assertEquals(-1, inputStream.read(/*dest*/ new byte[1], /*destOffset*/ 0, /*length*/1));
+  }
+
+  @Test
+  public void bufferInputStream_read_writesPartially() throws Exception {
+    ReadableBuffer buffer = ReadableBuffers.wrap(MSG_BYTES);
+    InputStream inputStream = ReadableBuffers.openStream(buffer, true);
+    byte[] dest = new byte[3];
+    assertEquals(2, inputStream.read(dest, /*destOffset*/ 1, /*length*/ 2));
+    assertArrayEquals(new byte[]{0x00, 'h', 'e'}, dest);
+  }
+
+  @Test
+  public void bufferInputStream_close_closesBuffer() throws Exception {
+    ReadableBuffer buffer = mock(ReadableBuffer.class);
+    InputStream inputStream = ReadableBuffers.openStream(buffer, true);
+    inputStream.close();
+    verify(buffer, times(1)).close();
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ReflectionLongAdderCounterTest.java b/core/src/test/java/io/grpc/internal/ReflectionLongAdderCounterTest.java
new file mode 100644
index 0000000..8e3b67b
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ReflectionLongAdderCounterTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ReflectionLongAdderCounterTest {
+  private ReflectionLongAdderCounter counter = new ReflectionLongAdderCounter();
+
+  @Test
+  public void testInitialValue() {
+    assertEquals(0, counter.value());
+  }
+
+  @Test
+  public void testIncrement() {
+    counter.add(1);
+    assertEquals(1, counter.value());
+  }
+
+  @Test
+  public void testIncrementDelta() {
+    counter.add(2);
+    assertEquals(2, counter.value());
+  }
+
+  @Test
+  public void testIncrementMulti() {
+    counter.add(2);
+    counter.add(1);
+    assertEquals(3, counter.value());
+  }
+
+  @Test
+  public void testDecrement() {
+    counter.add(2);
+    counter.add(-1);
+    assertEquals(1, counter.value());
+  }
+
+  @Test
+  public void testNegativeValue() {
+    counter.add(-2);
+    assertEquals(-2, counter.value());
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ReschedulerTest.java b/core/src/test/java/io/grpc/internal/ReschedulerTest.java
new file mode 100644
index 0000000..c2e2fc7
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ReschedulerTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link Rescheduler}.
+ */
+@RunWith(JUnit4.class)
+public class ReschedulerTest {
+
+  private final Runner runner = new Runner();
+  private final Exec exec = new Exec();
+  private final FakeClock scheduler = new FakeClock();
+  private final Rescheduler rescheduler = new Rescheduler(
+      runner,
+      exec,
+      scheduler.getScheduledExecutorService(),
+      scheduler.getStopwatchSupplier().get());
+
+  @Test
+  public void runs() {
+    assertFalse(runner.ran);
+    rescheduler.reschedule(1, TimeUnit.NANOSECONDS);
+    assertFalse(runner.ran);
+
+    scheduler.forwardNanos(1);
+
+    assertTrue(runner.ran);
+  }
+
+  @Test
+  public void cancels() {
+    assertFalse(runner.ran);
+    rescheduler.reschedule(1, TimeUnit.NANOSECONDS);
+    assertFalse(runner.ran);
+    rescheduler.cancel(/* permanent= */ false);
+
+    scheduler.forwardNanos(1);
+
+    assertFalse(runner.ran);
+    assertTrue(exec.executed);
+  }
+
+  @Test
+  public void cancelPermanently() {
+    assertFalse(runner.ran);
+    rescheduler.reschedule(1, TimeUnit.NANOSECONDS);
+    assertFalse(runner.ran);
+    rescheduler.cancel(/* permanent= */ true);
+
+    scheduler.forwardNanos(1);
+
+    assertFalse(runner.ran);
+    assertFalse(exec.executed);
+  }
+
+  @Test
+  public void reschedules() {
+    assertFalse(runner.ran);
+    rescheduler.reschedule(1, TimeUnit.NANOSECONDS);
+    assertFalse(runner.ran);
+    assertFalse(exec.executed);
+    rescheduler.reschedule(50, TimeUnit.NANOSECONDS);
+    assertFalse(runner.ran);
+    assertFalse(exec.executed);
+
+    scheduler.forwardNanos(1);
+    assertFalse(runner.ran);
+    assertTrue(exec.executed);
+
+    scheduler.forwardNanos(50);
+
+    assertTrue(runner.ran);
+  }
+
+  @Test
+  public void reschedulesShortDelay() {
+    assertFalse(runner.ran);
+    rescheduler.reschedule(50, TimeUnit.NANOSECONDS);
+    assertFalse(runner.ran);
+    assertFalse(exec.executed);
+    rescheduler.reschedule(1, TimeUnit.NANOSECONDS);
+    assertFalse(runner.ran);
+    assertFalse(exec.executed);
+
+    scheduler.forwardNanos(1);
+    assertTrue(runner.ran);
+    assertTrue(exec.executed);
+  }
+
+  private static final class Exec implements Executor {
+    boolean executed;
+
+    @Override
+    public void execute(Runnable command) {
+      executed = true;
+
+      command.run();
+    }
+  }
+
+  private static final class Runner implements Runnable {
+    boolean ran;
+
+    @Override
+    public void run() {
+      ran = true;
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java
new file mode 100644
index 0000000..fc13d10
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java
@@ -0,0 +1,1563 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.internal.ClientStreamListener.RpcProgress.DROPPED;
+import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED;
+import static io.grpc.internal.ClientStreamListener.RpcProgress.REFUSED;
+import static io.grpc.internal.RetriableStream.GRPC_PREVIOUS_RPC_ATTEMPTS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
+import io.grpc.Codec;
+import io.grpc.Compressor;
+import io.grpc.DecompressorRegistry;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.Status;
+import io.grpc.Status.Code;
+import io.grpc.StringMarshaller;
+import io.grpc.internal.RetriableStream.ChannelBufferMeter;
+import io.grpc.internal.RetriableStream.Throttle;
+import io.grpc.internal.StreamListener.MessageProducer;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.Nullable;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+
+/** Unit tests for {@link RetriableStream}. */
+@RunWith(JUnit4.class)
+public class RetriableStreamTest {
+  private static final String CANCELLED_BECAUSE_COMMITTED =
+      "Stream thrown away because RetriableStream committed";
+  private static final String AUTHORITY = "fakeAuthority";
+  private static final Compressor COMPRESSOR = Codec.Identity.NONE;
+  private static final DecompressorRegistry DECOMPRESSOR_REGISTRY =
+      DecompressorRegistry.getDefaultInstance();
+  private static final int MAX_INBOUND_MESSAGE_SIZE = 1234;
+  private static final int MAX_OUTBOUND_MESSAGE_SIZE = 5678;
+  private static final long PER_RPC_BUFFER_LIMIT = 1000;
+  private static final long CHANNEL_BUFFER_LIMIT = 2000;
+  private static final int MAX_ATTEMPTS = 6;
+  private static final long INITIAL_BACKOFF_IN_SECONDS = 100;
+  private static final long MAX_BACKOFF_IN_SECONDS = 700;
+  private static final double BACKOFF_MULTIPLIER = 2D;
+  private static final double FAKE_RANDOM = .5D;
+
+  static {
+    RetriableStream.setRandom(
+        // not random
+        new Random() {
+          @Override
+          public double nextDouble() {
+            return FAKE_RANDOM;
+          }
+        });
+  }
+
+  private static final Code RETRIABLE_STATUS_CODE_1 = Code.UNAVAILABLE;
+  private static final Code RETRIABLE_STATUS_CODE_2 = Code.DATA_LOSS;
+  private static final Code NON_RETRIABLE_STATUS_CODE = Code.INTERNAL;
+  private static final RetryPolicy RETRY_POLICY =
+      new RetryPolicy(
+          MAX_ATTEMPTS,
+          TimeUnit.SECONDS.toNanos(INITIAL_BACKOFF_IN_SECONDS),
+          TimeUnit.SECONDS.toNanos(MAX_BACKOFF_IN_SECONDS),
+          BACKOFF_MULTIPLIER,
+          ImmutableSet.of(RETRIABLE_STATUS_CODE_1, RETRIABLE_STATUS_CODE_2));
+
+  private final RetriableStreamRecorder retriableStreamRecorder =
+      mock(RetriableStreamRecorder.class);
+  private final ClientStreamListener masterListener = mock(ClientStreamListener.class);
+  private final MethodDescriptor<String, String> method =
+      MethodDescriptor.<String, String>newBuilder()
+          .setType(MethodType.BIDI_STREAMING)
+          .setFullMethodName(MethodDescriptor.generateFullMethodName("service_foo", "method_bar"))
+          .setRequestMarshaller(new StringMarshaller())
+          .setResponseMarshaller(new StringMarshaller())
+          .build();
+  private final ChannelBufferMeter channelBufferUsed = new ChannelBufferMeter();
+  private final FakeClock fakeClock = new FakeClock();
+
+  private final class RecordedRetriableStream extends RetriableStream<String> {
+    RecordedRetriableStream(MethodDescriptor<String, ?> method, Metadata headers,
+        ChannelBufferMeter channelBufferUsed, long perRpcBufferLimit, long channelBufferLimit,
+        Executor callExecutor,
+        ScheduledExecutorService scheduledExecutorService,
+        final RetryPolicy retryPolicy,
+        final HedgingPolicy hedgingPolicy,
+        @Nullable Throttle throttle) {
+      super(
+          method, headers, channelBufferUsed, perRpcBufferLimit, channelBufferLimit, callExecutor,
+          scheduledExecutorService,
+          new RetryPolicy.Provider() {
+            @Override
+            public RetryPolicy get() {
+              return retryPolicy;
+            }
+          },
+          new HedgingPolicy.Provider() {
+            @Override
+            public HedgingPolicy get() {
+              return hedgingPolicy;
+            }
+          },
+          throttle);
+    }
+
+    @Override
+    void postCommit() {
+      retriableStreamRecorder.postCommit();
+    }
+
+    @Override
+    ClientStream newSubstream(ClientStreamTracer.Factory tracerFactory, Metadata metadata) {
+      bufferSizeTracer =
+          tracerFactory.newClientStreamTracer(CallOptions.DEFAULT, new Metadata());
+      int actualPreviousRpcAttemptsInHeader = metadata.get(GRPC_PREVIOUS_RPC_ATTEMPTS) == null
+          ? 0 : Integer.valueOf(metadata.get(GRPC_PREVIOUS_RPC_ATTEMPTS));
+      return retriableStreamRecorder.newSubstream(actualPreviousRpcAttemptsInHeader);
+    }
+
+    @Override
+    Status prestart() {
+      return retriableStreamRecorder.prestart();
+    }
+  }
+
+  private final RetriableStream<String> retriableStream =
+      newThrottledRetriableStream(null /* throttle */);
+
+  private ClientStreamTracer bufferSizeTracer;
+
+  private RetriableStream<String> newThrottledRetriableStream(Throttle throttle) {
+    return new RecordedRetriableStream(
+        method, new Metadata(), channelBufferUsed, PER_RPC_BUFFER_LIMIT, CHANNEL_BUFFER_LIMIT,
+        MoreExecutors.directExecutor(), fakeClock.getScheduledExecutorService(), RETRY_POLICY,
+        HedgingPolicy.DEFAULT, throttle);
+  }
+
+  @After
+  public void tearDown() {
+    assertEquals(0, fakeClock.numPendingTasks());
+  }
+
+  @Test
+  public void retry_everythingDrained() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    ClientStream mockStream2 = mock(ClientStream.class);
+    ClientStream mockStream3 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    InOrder inOrder =
+        inOrder(retriableStreamRecorder, masterListener, mockStream1, mockStream2, mockStream3);
+
+    // stream settings before start
+    retriableStream.setAuthority(AUTHORITY);
+    retriableStream.setCompressor(COMPRESSOR);
+    retriableStream.setDecompressorRegistry(DECOMPRESSOR_REGISTRY);
+    retriableStream.setFullStreamDecompression(false);
+    retriableStream.setFullStreamDecompression(true);
+    retriableStream.setMaxInboundMessageSize(MAX_INBOUND_MESSAGE_SIZE);
+    retriableStream.setMessageCompression(true);
+    retriableStream.setMaxOutboundMessageSize(MAX_OUTBOUND_MESSAGE_SIZE);
+    retriableStream.setMessageCompression(false);
+
+    inOrder.verifyNoMoreInteractions();
+
+    // start
+    retriableStream.start(masterListener);
+
+    inOrder.verify(retriableStreamRecorder).prestart();
+    inOrder.verify(retriableStreamRecorder).newSubstream(0);
+
+    inOrder.verify(mockStream1).setAuthority(AUTHORITY);
+    inOrder.verify(mockStream1).setCompressor(COMPRESSOR);
+    inOrder.verify(mockStream1).setDecompressorRegistry(DECOMPRESSOR_REGISTRY);
+    inOrder.verify(mockStream1).setFullStreamDecompression(false);
+    inOrder.verify(mockStream1).setFullStreamDecompression(true);
+    inOrder.verify(mockStream1).setMaxInboundMessageSize(MAX_INBOUND_MESSAGE_SIZE);
+    inOrder.verify(mockStream1).setMessageCompression(true);
+    inOrder.verify(mockStream1).setMaxOutboundMessageSize(MAX_OUTBOUND_MESSAGE_SIZE);
+    inOrder.verify(mockStream1).setMessageCompression(false);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    retriableStream.sendMessage("msg1");
+    retriableStream.sendMessage("msg2");
+    retriableStream.request(345);
+    retriableStream.flush();
+    retriableStream.flush();
+    retriableStream.sendMessage("msg3");
+    retriableStream.request(456);
+
+    inOrder.verify(mockStream1, times(2)).writeMessage(any(InputStream.class));
+    inOrder.verify(mockStream1).request(345);
+    inOrder.verify(mockStream1, times(2)).flush();
+    inOrder.verify(mockStream1).writeMessage(any(InputStream.class));
+    inOrder.verify(mockStream1).request(456);
+    inOrder.verifyNoMoreInteractions();
+
+    // retry1
+    doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+    sublistenerCaptor1.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_2), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+
+    // send more messages during backoff
+    retriableStream.sendMessage("msg1 during backoff1");
+    retriableStream.sendMessage("msg2 during backoff1");
+
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM) - 1L, TimeUnit.SECONDS);
+    inOrder.verifyNoMoreInteractions();
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(1L, TimeUnit.SECONDS);
+    assertEquals(0, fakeClock.numPendingTasks());
+    inOrder.verify(retriableStreamRecorder).newSubstream(1);
+    inOrder.verify(mockStream2).setAuthority(AUTHORITY);
+    inOrder.verify(mockStream2).setCompressor(COMPRESSOR);
+    inOrder.verify(mockStream2).setDecompressorRegistry(DECOMPRESSOR_REGISTRY);
+    inOrder.verify(mockStream2).setFullStreamDecompression(false);
+    inOrder.verify(mockStream2).setFullStreamDecompression(true);
+    inOrder.verify(mockStream2).setMaxInboundMessageSize(MAX_INBOUND_MESSAGE_SIZE);
+    inOrder.verify(mockStream2).setMessageCompression(true);
+    inOrder.verify(mockStream2).setMaxOutboundMessageSize(MAX_OUTBOUND_MESSAGE_SIZE);
+    inOrder.verify(mockStream2).setMessageCompression(false);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor2 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+    inOrder.verify(mockStream2, times(2)).writeMessage(any(InputStream.class));
+    inOrder.verify(mockStream2).request(345);
+    inOrder.verify(mockStream2, times(2)).flush();
+    inOrder.verify(mockStream2).writeMessage(any(InputStream.class));
+    inOrder.verify(mockStream2).request(456);
+    inOrder.verify(mockStream2, times(2)).writeMessage(any(InputStream.class));
+    inOrder.verifyNoMoreInteractions();
+
+    // send more messages
+    retriableStream.sendMessage("msg1 after retry1");
+    retriableStream.sendMessage("msg2 after retry1");
+
+    // mockStream1 is closed so it is not in the drainedSubstreams
+    verifyNoMoreInteractions(mockStream1);
+    inOrder.verify(mockStream2, times(2)).writeMessage(any(InputStream.class));
+
+    // retry2
+    doReturn(mockStream3).when(retriableStreamRecorder).newSubstream(2);
+    sublistenerCaptor2.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+
+    // send more messages during backoff
+    retriableStream.sendMessage("msg1 during backoff2");
+    retriableStream.sendMessage("msg2 during backoff2");
+    retriableStream.sendMessage("msg3 during backoff2");
+
+    fakeClock.forwardTime(
+        (long) (INITIAL_BACKOFF_IN_SECONDS * BACKOFF_MULTIPLIER * FAKE_RANDOM) - 1L,
+        TimeUnit.SECONDS);
+    inOrder.verifyNoMoreInteractions();
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(1L, TimeUnit.SECONDS);
+    assertEquals(0, fakeClock.numPendingTasks());
+    inOrder.verify(retriableStreamRecorder).newSubstream(2);
+    inOrder.verify(mockStream3).setAuthority(AUTHORITY);
+    inOrder.verify(mockStream3).setCompressor(COMPRESSOR);
+    inOrder.verify(mockStream3).setDecompressorRegistry(DECOMPRESSOR_REGISTRY);
+    inOrder.verify(mockStream3).setFullStreamDecompression(false);
+    inOrder.verify(mockStream3).setFullStreamDecompression(true);
+    inOrder.verify(mockStream3).setMaxInboundMessageSize(MAX_INBOUND_MESSAGE_SIZE);
+    inOrder.verify(mockStream3).setMessageCompression(true);
+    inOrder.verify(mockStream3).setMaxOutboundMessageSize(MAX_OUTBOUND_MESSAGE_SIZE);
+    inOrder.verify(mockStream3).setMessageCompression(false);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor3 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream3).start(sublistenerCaptor3.capture());
+    inOrder.verify(mockStream3, times(2)).writeMessage(any(InputStream.class));
+    inOrder.verify(mockStream3).request(345);
+    inOrder.verify(mockStream3, times(2)).flush();
+    inOrder.verify(mockStream3).writeMessage(any(InputStream.class));
+    inOrder.verify(mockStream3).request(456);
+    inOrder.verify(mockStream3, times(7)).writeMessage(any(InputStream.class));
+    inOrder.verifyNoMoreInteractions();
+
+    // no more retry
+    sublistenerCaptor3.getValue().closed(
+        Status.fromCode(NON_RETRIABLE_STATUS_CODE), new Metadata());
+
+    inOrder.verify(retriableStreamRecorder).postCommit();
+    inOrder.verify(masterListener).closed(any(Status.class), any(Metadata.class));
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void headersRead_cancel() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    InOrder inOrder = inOrder(retriableStreamRecorder);
+
+    retriableStream.start(masterListener);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    sublistenerCaptor1.getValue().headersRead(new Metadata());
+
+    inOrder.verify(retriableStreamRecorder).postCommit();
+
+    retriableStream.cancel(Status.CANCELLED);
+
+    inOrder.verify(retriableStreamRecorder, never()).postCommit();
+  }
+
+  @Test
+  public void retry_headersRead_cancel() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    InOrder inOrder = inOrder(retriableStreamRecorder);
+
+    retriableStream.start(masterListener);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    // retry
+    ClientStream mockStream2 = mock(ClientStream.class);
+    doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+    sublistenerCaptor1.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor2 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream2).start(sublistenerCaptor2.capture());
+    inOrder.verify(retriableStreamRecorder, never()).postCommit();
+
+    // headersRead
+    sublistenerCaptor2.getValue().headersRead(new Metadata());
+
+    inOrder.verify(retriableStreamRecorder).postCommit();
+
+    // cancel
+    retriableStream.cancel(Status.CANCELLED);
+
+    inOrder.verify(retriableStreamRecorder, never()).postCommit();
+  }
+
+  @Test
+  public void headersRead_closed() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    InOrder inOrder = inOrder(retriableStreamRecorder);
+
+    retriableStream.start(masterListener);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    sublistenerCaptor1.getValue().headersRead(new Metadata());
+
+    inOrder.verify(retriableStreamRecorder).postCommit();
+
+    Status status = Status.fromCode(RETRIABLE_STATUS_CODE_1);
+    Metadata metadata = new Metadata();
+    sublistenerCaptor1.getValue().closed(status, metadata);
+
+    verify(masterListener).closed(status, metadata);
+    inOrder.verify(retriableStreamRecorder, never()).postCommit();
+  }
+
+  @Test
+  public void retry_headersRead_closed() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    InOrder inOrder = inOrder(retriableStreamRecorder);
+
+    retriableStream.start(masterListener);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    // retry
+    ClientStream mockStream2 = mock(ClientStream.class);
+    doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+    sublistenerCaptor1.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor2 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream2).start(sublistenerCaptor2.capture());
+    inOrder.verify(retriableStreamRecorder, never()).postCommit();
+
+    // headersRead
+    sublistenerCaptor2.getValue().headersRead(new Metadata());
+
+    inOrder.verify(retriableStreamRecorder).postCommit();
+
+    // closed even with retriable status
+    Status status = Status.fromCode(RETRIABLE_STATUS_CODE_1);
+    Metadata metadata = new Metadata();
+    sublistenerCaptor2.getValue().closed(status, metadata);
+
+    verify(masterListener).closed(status, metadata);
+    inOrder.verify(retriableStreamRecorder, never()).postCommit();
+  }
+
+  @Test
+  public void cancel_closed() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    InOrder inOrder = inOrder(retriableStreamRecorder);
+
+    retriableStream.start(masterListener);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    // cancel
+    retriableStream.cancel(Status.CANCELLED);
+
+    inOrder.verify(retriableStreamRecorder).postCommit();
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(mockStream1).cancel(statusCaptor.capture());
+    assertEquals(Status.CANCELLED.getCode(), statusCaptor.getValue().getCode());
+    assertEquals(CANCELLED_BECAUSE_COMMITTED, statusCaptor.getValue().getDescription());
+
+    // closed even with retriable status
+    Status status = Status.fromCode(RETRIABLE_STATUS_CODE_1);
+    Metadata metadata = new Metadata();
+    sublistenerCaptor1.getValue().closed(status, metadata);
+    inOrder.verify(retriableStreamRecorder, never()).postCommit();
+  }
+
+  @Test
+  public void retry_cancel_closed() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    InOrder inOrder = inOrder(retriableStreamRecorder);
+
+    retriableStream.start(masterListener);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    // retry
+    ClientStream mockStream2 = mock(ClientStream.class);
+    doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+    sublistenerCaptor1.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor2 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream2).start(sublistenerCaptor2.capture());
+    inOrder.verify(retriableStreamRecorder, never()).postCommit();
+
+    // cancel
+    retriableStream.cancel(Status.CANCELLED);
+
+    inOrder.verify(retriableStreamRecorder).postCommit();
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(mockStream2).cancel(statusCaptor.capture());
+    assertEquals(Status.CANCELLED.getCode(), statusCaptor.getValue().getCode());
+    assertEquals(CANCELLED_BECAUSE_COMMITTED, statusCaptor.getValue().getDescription());
+
+    // closed
+    Status status = Status.fromCode(NON_RETRIABLE_STATUS_CODE);
+    Metadata metadata = new Metadata();
+    sublistenerCaptor2.getValue().closed(status, metadata);
+    inOrder.verify(retriableStreamRecorder, never()).postCommit();
+  }
+
+  @Test
+  public void unretriableClosed_cancel() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    InOrder inOrder = inOrder(retriableStreamRecorder);
+
+    retriableStream.start(masterListener);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    // closed
+    Status status = Status.fromCode(NON_RETRIABLE_STATUS_CODE);
+    Metadata metadata = new Metadata();
+    sublistenerCaptor1.getValue().closed(status, metadata);
+
+    inOrder.verify(retriableStreamRecorder).postCommit();
+    verify(masterListener).closed(status, metadata);
+
+    // cancel
+    retriableStream.cancel(Status.CANCELLED);
+    inOrder.verify(retriableStreamRecorder, never()).postCommit();
+  }
+
+  @Test
+  public void retry_unretriableClosed_cancel() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    InOrder inOrder = inOrder(retriableStreamRecorder);
+
+    retriableStream.start(masterListener);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    // retry
+    ClientStream mockStream2 = mock(ClientStream.class);
+    doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+    sublistenerCaptor1.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor2 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream2).start(sublistenerCaptor2.capture());
+    inOrder.verify(retriableStreamRecorder, never()).postCommit();
+
+    // closed
+    Status status = Status.fromCode(NON_RETRIABLE_STATUS_CODE);
+    Metadata metadata = new Metadata();
+    sublistenerCaptor2.getValue().closed(status, metadata);
+
+    inOrder.verify(retriableStreamRecorder).postCommit();
+    verify(masterListener).closed(status, metadata);
+
+    // cancel
+    retriableStream.cancel(Status.CANCELLED);
+    inOrder.verify(retriableStreamRecorder, never()).postCommit();
+  }
+
+  @Test
+  public void retry_cancelWhileBackoff() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+
+    retriableStream.start(masterListener);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    // retry
+    ClientStream mockStream2 = mock(ClientStream.class);
+    doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+    sublistenerCaptor1.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+
+    // cancel while backoff
+    assertEquals(1, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder, never()).postCommit();
+    retriableStream.cancel(Status.CANCELLED);
+    verify(retriableStreamRecorder).postCommit();
+
+    verifyNoMoreInteractions(mockStream1);
+    verifyNoMoreInteractions(mockStream2);
+  }
+
+  @Test
+  public void operationsWhileDraining() {
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    final AtomicReference<ClientStreamListener> sublistenerCaptor2 =
+        new AtomicReference<ClientStreamListener>();
+    final Status cancelStatus = Status.CANCELLED.withDescription("c");
+    ClientStream mockStream1 =
+        mock(
+            ClientStream.class,
+            delegatesTo(
+                new NoopClientStream() {
+                  @Override
+                  public void request(int numMessages) {
+                    retriableStream.sendMessage("substream1 request " + numMessages);
+                    if (numMessages > 1) {
+                      retriableStream.request(--numMessages);
+                    }
+                  }
+                }));
+
+    final ClientStream mockStream2 =
+        mock(
+            ClientStream.class,
+            delegatesTo(
+                new NoopClientStream() {
+                  @Override
+                  public void start(ClientStreamListener listener) {
+                    sublistenerCaptor2.set(listener);
+                  }
+
+                  @Override
+                  public void request(int numMessages) {
+                    retriableStream.sendMessage("substream2 request " + numMessages);
+
+                    if (numMessages == 3) {
+                      sublistenerCaptor2.get().headersRead(new Metadata());
+                    }
+                    if (numMessages == 2) {
+                      retriableStream.request(100);
+                    }
+                    if (numMessages == 100) {
+                      retriableStream.cancel(cancelStatus);
+                    }
+                  }
+                }));
+
+    InOrder inOrder = inOrder(retriableStreamRecorder, mockStream1, mockStream2);
+
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    retriableStream.start(masterListener);
+
+    inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    retriableStream.request(3);
+
+    inOrder.verify(mockStream1).request(3);
+    inOrder.verify(mockStream1).writeMessage(any(InputStream.class)); // msg "substream1 request 3"
+    inOrder.verify(mockStream1).request(2);
+    inOrder.verify(mockStream1).writeMessage(any(InputStream.class)); // msg "substream1 request 2"
+    inOrder.verify(mockStream1).request(1);
+    inOrder.verify(mockStream1).writeMessage(any(InputStream.class)); // msg "substream1 request 1"
+
+    // retry
+    doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+    sublistenerCaptor1.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+
+    // send more requests during backoff
+    retriableStream.request(789);
+
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+
+    inOrder.verify(mockStream2).start(sublistenerCaptor2.get());
+    inOrder.verify(mockStream2).request(3);
+    inOrder.verify(retriableStreamRecorder).postCommit();
+    inOrder.verify(mockStream2).writeMessage(any(InputStream.class)); // msg "substream1 request 3"
+    inOrder.verify(mockStream2).request(2);
+    inOrder.verify(mockStream2).writeMessage(any(InputStream.class)); // msg "substream1 request 2"
+    inOrder.verify(mockStream2).request(1);
+
+    // msg "substream1 request 1"
+    inOrder.verify(mockStream2).writeMessage(any(InputStream.class));
+    inOrder.verify(mockStream2).request(789);
+    // msg "substream2 request 3"
+    // msg "substream2 request 2"
+    inOrder.verify(mockStream2, times(2)).writeMessage(any(InputStream.class));
+    inOrder.verify(mockStream2).request(100);
+
+    verify(mockStream2).cancel(cancelStatus);
+
+    // "substream2 request 1" will never be sent
+    inOrder.verify(mockStream2, never()).writeMessage(any(InputStream.class));
+  }
+
+  @Test
+  public void operationsAfterImmediateCommit() {
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    ClientStream mockStream1 = mock(ClientStream.class);
+
+    InOrder inOrder = inOrder(retriableStreamRecorder, mockStream1);
+
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    retriableStream.start(masterListener);
+
+    // drained
+    inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    // commit
+    sublistenerCaptor1.getValue().headersRead(new Metadata());
+
+    retriableStream.request(3);
+    inOrder.verify(mockStream1).request(3);
+    retriableStream.sendMessage("msg 1");
+    inOrder.verify(mockStream1).writeMessage(any(InputStream.class));
+  }
+
+  @Test
+  public void isReady_whenDrained() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    retriableStream.start(masterListener);
+
+    assertFalse(retriableStream.isReady());
+
+    doReturn(true).when(mockStream1).isReady();
+
+    assertTrue(retriableStream.isReady());
+  }
+
+  @Test
+  public void isReady_whileDraining() {
+    final AtomicReference<ClientStreamListener> sublistenerCaptor1 =
+        new AtomicReference<ClientStreamListener>();
+    final List<Boolean> readiness = new ArrayList<>();
+    ClientStream mockStream1 =
+        mock(
+            ClientStream.class,
+            delegatesTo(
+                new NoopClientStream() {
+                  @Override
+                  public void start(ClientStreamListener listener) {
+                    sublistenerCaptor1.set(listener);
+                    readiness.add(retriableStream.isReady()); // expected false b/c in draining
+                  }
+
+                  @Override
+                  public boolean isReady() {
+                    return true;
+                  }
+                }));
+
+    final ClientStream mockStream2 =
+        mock(
+            ClientStream.class,
+            delegatesTo(
+                new NoopClientStream() {
+                  @Override
+                  public void start(ClientStreamListener listener) {
+                    readiness.add(retriableStream.isReady()); // expected false b/c in draining
+                  }
+
+                  @Override
+                  public boolean isReady() {
+                    return true;
+                  }
+                }));
+
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    retriableStream.start(masterListener);
+
+    verify(mockStream1).start(sublistenerCaptor1.get());
+    readiness.add(retriableStream.isReady()); // expected true
+
+    // retry
+    doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+    doReturn(false).when(mockStream1).isReady(); // mockStream1 closed, so isReady false
+    sublistenerCaptor1.get().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+
+    // send more requests during backoff
+    retriableStream.request(789);
+    readiness.add(retriableStream.isReady()); // expected false b/c in backoff
+
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+
+    verify(mockStream2).start(any(ClientStreamListener.class));
+    readiness.add(retriableStream.isReady()); // expected true
+
+    assertThat(readiness).containsExactly(false, true, false, false, true).inOrder();
+  }
+
+  @Test
+  public void messageAvailable() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+
+    retriableStream.start(masterListener);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    ClientStreamListener listener = sublistenerCaptor1.getValue();
+    listener.headersRead(new Metadata());
+    MessageProducer messageProducer = mock(MessageProducer.class);
+    listener.messagesAvailable(messageProducer);
+    verify(masterListener).messagesAvailable(messageProducer);
+  }
+
+  @Test
+  public void closedWhileDraining() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    final ClientStream mockStream2 =
+        mock(
+            ClientStream.class,
+            delegatesTo(
+                new NoopClientStream() {
+                  @Override
+                  public void start(ClientStreamListener listener) {
+                    // closed while draning
+                    listener.closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+                  }
+                }));
+    final ClientStream mockStream3 = mock(ClientStream.class);
+
+    when(retriableStreamRecorder.newSubstream(anyInt()))
+        .thenReturn(mockStream1, mockStream2, mockStream3);
+
+    retriableStream.start(masterListener);
+    retriableStream.sendMessage("msg1");
+    retriableStream.sendMessage("msg2");
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    ClientStreamListener listener1 = sublistenerCaptor1.getValue();
+
+    // retry
+    listener1.closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+    assertEquals(1, fakeClock.numPendingTasks());
+
+    // send requests during backoff
+    retriableStream.request(3);
+    fakeClock.forwardTime(
+        (long) (INITIAL_BACKOFF_IN_SECONDS * BACKOFF_MULTIPLIER * FAKE_RANDOM), TimeUnit.SECONDS);
+
+    retriableStream.request(1);
+    verify(mockStream1, never()).request(anyInt());
+    verify(mockStream2, never()).request(anyInt());
+    verify(mockStream3).request(3);
+    verify(mockStream3).request(1);
+  }
+
+
+  @Test
+  public void perRpcBufferLimitExceeded() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+
+    retriableStream.start(masterListener);
+
+    bufferSizeTracer.outboundWireSize(PER_RPC_BUFFER_LIMIT);
+
+    assertEquals(PER_RPC_BUFFER_LIMIT, channelBufferUsed.addAndGet(0));
+
+    verify(retriableStreamRecorder, never()).postCommit();
+    bufferSizeTracer.outboundWireSize(2);
+    verify(retriableStreamRecorder).postCommit();
+
+    // verify channel buffer is adjusted
+    assertEquals(0, channelBufferUsed.addAndGet(0));
+  }
+
+  @Test
+  public void perRpcBufferLimitExceededDuringBackoff() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+
+    retriableStream.start(masterListener);
+
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    bufferSizeTracer.outboundWireSize(PER_RPC_BUFFER_LIMIT - 1);
+
+    // retry
+    ClientStream mockStream2 = mock(ClientStream.class);
+    doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+    sublistenerCaptor1.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+
+    // bufferSizeTracer.outboundWireSize() quits immediately while backoff b/c substream1 is closed
+    assertEquals(1, fakeClock.numPendingTasks());
+    bufferSizeTracer.outboundWireSize(2);
+    verify(retriableStreamRecorder, never()).postCommit();
+
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+    verify(mockStream2).start(any(ClientStreamListener.class));
+
+    // bufferLimitExceeded
+    bufferSizeTracer.outboundWireSize(PER_RPC_BUFFER_LIMIT - 1);
+    verify(retriableStreamRecorder, never()).postCommit();
+    bufferSizeTracer.outboundWireSize(2);
+    verify(retriableStreamRecorder).postCommit();
+
+    verifyNoMoreInteractions(mockStream1);
+    verifyNoMoreInteractions(mockStream2);
+  }
+
+  @Test
+  public void channelBufferLimitExceeded() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+
+    retriableStream.start(masterListener);
+
+    bufferSizeTracer.outboundWireSize(100);
+
+    assertEquals(100, channelBufferUsed.addAndGet(0));
+
+    channelBufferUsed.addAndGet(CHANNEL_BUFFER_LIMIT - 200);
+    verify(retriableStreamRecorder, never()).postCommit();
+    bufferSizeTracer.outboundWireSize(100 + 1);
+    verify(retriableStreamRecorder).postCommit();
+
+    // verify channel buffer is adjusted
+    assertEquals(CHANNEL_BUFFER_LIMIT - 200, channelBufferUsed.addAndGet(0));
+  }
+
+  @Test
+  public void updateHeaders() {
+    Metadata originalHeaders = new Metadata();
+    Metadata headers = retriableStream.updateHeaders(originalHeaders, 0);
+    assertNotSame(originalHeaders, headers);
+    assertNull(headers.get(GRPC_PREVIOUS_RPC_ATTEMPTS));
+
+    headers = retriableStream.updateHeaders(originalHeaders, 345);
+    assertEquals("345", headers.get(GRPC_PREVIOUS_RPC_ATTEMPTS));
+    assertNull(originalHeaders.get(GRPC_PREVIOUS_RPC_ATTEMPTS));
+  }
+
+  @Test
+  public void expBackoff_maxBackoff_maxRetryAttempts() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    ClientStream mockStream2 = mock(ClientStream.class);
+    ClientStream mockStream3 = mock(ClientStream.class);
+    ClientStream mockStream4 = mock(ClientStream.class);
+    ClientStream mockStream5 = mock(ClientStream.class);
+    ClientStream mockStream6 = mock(ClientStream.class);
+    ClientStream mockStream7 = mock(ClientStream.class);
+    InOrder inOrder = inOrder(
+        mockStream1, mockStream2, mockStream3, mockStream4, mockStream5, mockStream6, mockStream7);
+    when(retriableStreamRecorder.newSubstream(anyInt())).thenReturn(
+        mockStream1, mockStream2, mockStream3, mockStream4, mockStream5, mockStream6, mockStream7);
+
+    retriableStream.start(masterListener);
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder).newSubstream(0);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+    inOrder.verifyNoMoreInteractions();
+
+
+    // retry1
+    sublistenerCaptor1.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM) - 1L, TimeUnit.SECONDS);
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(1L, TimeUnit.SECONDS);
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder).newSubstream(1);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor2 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    // retry2
+    sublistenerCaptor2.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_2), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(
+        (long) (INITIAL_BACKOFF_IN_SECONDS * BACKOFF_MULTIPLIER * FAKE_RANDOM) - 1L,
+        TimeUnit.SECONDS);
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(1L, TimeUnit.SECONDS);
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder).newSubstream(2);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor3 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream3).start(sublistenerCaptor3.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    // retry3
+    sublistenerCaptor3.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(
+        (long) (INITIAL_BACKOFF_IN_SECONDS * BACKOFF_MULTIPLIER * BACKOFF_MULTIPLIER * FAKE_RANDOM)
+            - 1L,
+        TimeUnit.SECONDS);
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(1L, TimeUnit.SECONDS);
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder).newSubstream(3);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor4 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream4).start(sublistenerCaptor4.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    // retry4
+    sublistenerCaptor4.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_2), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime((long) (MAX_BACKOFF_IN_SECONDS * FAKE_RANDOM) - 1L, TimeUnit.SECONDS);
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(1L, TimeUnit.SECONDS);
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder).newSubstream(4);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor5 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream5).start(sublistenerCaptor5.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    // retry5
+    sublistenerCaptor5.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_2), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime((long) (MAX_BACKOFF_IN_SECONDS * FAKE_RANDOM) - 1L, TimeUnit.SECONDS);
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(1L, TimeUnit.SECONDS);
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder).newSubstream(5);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor6 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream6).start(sublistenerCaptor6.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    // can not retry any more
+    verify(retriableStreamRecorder, never()).postCommit();
+    sublistenerCaptor6.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+    verify(retriableStreamRecorder).postCommit();
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void pushback() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    ClientStream mockStream2 = mock(ClientStream.class);
+    ClientStream mockStream3 = mock(ClientStream.class);
+    ClientStream mockStream4 = mock(ClientStream.class);
+    ClientStream mockStream5 = mock(ClientStream.class);
+    ClientStream mockStream6 = mock(ClientStream.class);
+    ClientStream mockStream7 = mock(ClientStream.class);
+    InOrder inOrder = inOrder(
+        mockStream1, mockStream2, mockStream3, mockStream4, mockStream5, mockStream6, mockStream7);
+    when(retriableStreamRecorder.newSubstream(anyInt())).thenReturn(
+        mockStream1, mockStream2, mockStream3, mockStream4, mockStream5, mockStream6, mockStream7);
+
+    retriableStream.start(masterListener);
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder).newSubstream(0);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+    inOrder.verifyNoMoreInteractions();
+
+
+    // retry1
+    int pushbackInMillis = 123;
+    Metadata headers = new Metadata();
+    headers.put(RetriableStream.GRPC_RETRY_PUSHBACK_MS, "" + pushbackInMillis);
+    sublistenerCaptor1.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), headers);
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(pushbackInMillis - 1, TimeUnit.MILLISECONDS);
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(1L, TimeUnit.MILLISECONDS);
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder).newSubstream(1);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor2 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    // retry2
+    pushbackInMillis = 4567 * 1000;
+    headers = new Metadata();
+    headers.put(RetriableStream.GRPC_RETRY_PUSHBACK_MS, "" + pushbackInMillis);
+    sublistenerCaptor2.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_2), headers);
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(pushbackInMillis - 1, TimeUnit.MILLISECONDS);
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(1L, TimeUnit.MILLISECONDS);
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder).newSubstream(2);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor3 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream3).start(sublistenerCaptor3.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    // retry3
+    sublistenerCaptor3.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM) - 1L, TimeUnit.SECONDS);
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(1L, TimeUnit.SECONDS);
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder).newSubstream(3);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor4 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream4).start(sublistenerCaptor4.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    // retry4
+    sublistenerCaptor4.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_2), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(
+        (long) (INITIAL_BACKOFF_IN_SECONDS * BACKOFF_MULTIPLIER * FAKE_RANDOM) - 1L,
+        TimeUnit.SECONDS);
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(1L, TimeUnit.SECONDS);
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder).newSubstream(4);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor5 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream5).start(sublistenerCaptor5.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    // retry5
+    sublistenerCaptor5.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_2), new Metadata());
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(
+        (long) (INITIAL_BACKOFF_IN_SECONDS * BACKOFF_MULTIPLIER * BACKOFF_MULTIPLIER * FAKE_RANDOM)
+            - 1L,
+        TimeUnit.SECONDS);
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(1L, TimeUnit.SECONDS);
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder).newSubstream(5);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor6 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream6).start(sublistenerCaptor6.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    // can not retry any more even pushback is positive
+    pushbackInMillis = 4567 * 1000;
+    headers = new Metadata();
+    headers.put(RetriableStream.GRPC_RETRY_PUSHBACK_MS, "" + pushbackInMillis);
+    verify(retriableStreamRecorder, never()).postCommit();
+    sublistenerCaptor6.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), headers);
+    verify(retriableStreamRecorder).postCommit();
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void pushback_noRetry() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(anyInt());
+
+    retriableStream.start(masterListener);
+    assertEquals(0, fakeClock.numPendingTasks());
+    verify(retriableStreamRecorder).newSubstream(0);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+    verify(retriableStreamRecorder, never()).postCommit();
+
+    // pushback no retry
+    Metadata headers = new Metadata();
+    headers.put(RetriableStream.GRPC_RETRY_PUSHBACK_MS, "");
+    sublistenerCaptor1.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), headers);
+
+    verify(retriableStreamRecorder, never()).newSubstream(1);
+    verify(retriableStreamRecorder).postCommit();
+  }
+
+  @Test
+  public void throttle() {
+    Throttle throttle = new Throttle(4f, 0.8f);
+    assertTrue(throttle.isAboveThreshold());
+    assertTrue(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // token = 3
+    assertTrue(throttle.isAboveThreshold());
+    assertFalse(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // token = 2
+    assertFalse(throttle.isAboveThreshold());
+    assertFalse(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // token = 1
+    assertFalse(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // token = 0
+    assertFalse(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // token = 0
+    assertFalse(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // token = 0
+    assertFalse(throttle.isAboveThreshold());
+
+    throttle.onSuccess(); // token = 0.8
+    assertFalse(throttle.isAboveThreshold());
+    throttle.onSuccess(); // token = 1.6
+    assertFalse(throttle.isAboveThreshold());
+    throttle.onSuccess(); // token = 3.2
+    assertTrue(throttle.isAboveThreshold());
+    throttle.onSuccess(); // token = 4
+    assertTrue(throttle.isAboveThreshold());
+    throttle.onSuccess(); // token = 4
+    assertTrue(throttle.isAboveThreshold());
+    throttle.onSuccess(); // token = 4
+    assertTrue(throttle.isAboveThreshold());
+
+    assertTrue(throttle.isAboveThreshold());
+    assertTrue(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // token = 3
+    assertTrue(throttle.isAboveThreshold());
+    assertFalse(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // token = 2
+    assertFalse(throttle.isAboveThreshold());
+  }
+
+  @Test
+  public void throttledStream_FailWithRetriableStatusCode_WithoutPushback() {
+    Throttle throttle = new Throttle(4f, 0.8f);
+    RetriableStream<String> retriableStream = newThrottledRetriableStream(throttle);
+
+    ClientStream mockStream = mock(ClientStream.class);
+    doReturn(mockStream).when(retriableStreamRecorder).newSubstream(anyInt());
+    retriableStream.start(masterListener);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream).start(sublistenerCaptor.capture());
+
+    // mimic some other call in the channel triggers a throttle countdown
+    assertTrue(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // count = 3
+
+    sublistenerCaptor.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), new Metadata());
+    verify(retriableStreamRecorder).postCommit();
+    assertFalse(throttle.isAboveThreshold()); // count = 2
+  }
+
+  @Test
+  public void throttledStream_FailWithNonRetriableStatusCode_WithoutPushback() {
+    Throttle throttle = new Throttle(4f, 0.8f);
+    RetriableStream<String> retriableStream = newThrottledRetriableStream(throttle);
+
+    ClientStream mockStream = mock(ClientStream.class);
+    doReturn(mockStream).when(retriableStreamRecorder).newSubstream(anyInt());
+    retriableStream.start(masterListener);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream).start(sublistenerCaptor.capture());
+
+    // mimic some other call in the channel triggers a throttle countdown
+    assertTrue(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // count = 3
+
+    sublistenerCaptor.getValue().closed(Status.fromCode(NON_RETRIABLE_STATUS_CODE), new Metadata());
+    verify(retriableStreamRecorder).postCommit();
+    assertTrue(throttle.isAboveThreshold()); // count = 3
+
+    assertFalse(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // count = 2
+  }
+
+  @Test
+  public void throttledStream_FailWithRetriableStatusCode_WithRetriablePushback() {
+    Throttle throttle = new Throttle(4f, 0.8f);
+    RetriableStream<String> retriableStream = newThrottledRetriableStream(throttle);
+
+    ClientStream mockStream = mock(ClientStream.class);
+    doReturn(mockStream).when(retriableStreamRecorder).newSubstream(anyInt());
+    retriableStream.start(masterListener);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream).start(sublistenerCaptor.capture());
+
+    // mimic some other call in the channel triggers a throttle countdown
+    assertTrue(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // count = 3
+
+    int pushbackInMillis = 123;
+    Metadata headers = new Metadata();
+    headers.put(RetriableStream.GRPC_RETRY_PUSHBACK_MS, "" + pushbackInMillis);
+    sublistenerCaptor.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), headers);
+    verify(retriableStreamRecorder).postCommit();
+    assertFalse(throttle.isAboveThreshold()); // count = 2
+  }
+
+  @Test
+  public void throttledStream_FailWithNonRetriableStatusCode_WithRetriablePushback() {
+    Throttle throttle = new Throttle(4f, 0.8f);
+    RetriableStream<String> retriableStream = newThrottledRetriableStream(throttle);
+
+    ClientStream mockStream = mock(ClientStream.class);
+    doReturn(mockStream).when(retriableStreamRecorder).newSubstream(anyInt());
+    retriableStream.start(masterListener);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream).start(sublistenerCaptor.capture());
+
+    // mimic some other call in the channel triggers a throttle countdown
+    assertTrue(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // count = 3
+
+    int pushbackInMillis = 123;
+    Metadata headers = new Metadata();
+    headers.put(RetriableStream.GRPC_RETRY_PUSHBACK_MS, "" + pushbackInMillis);
+    sublistenerCaptor.getValue().closed(Status.fromCode(NON_RETRIABLE_STATUS_CODE), headers);
+    verify(retriableStreamRecorder, never()).postCommit();
+    assertTrue(throttle.isAboveThreshold()); // count = 3
+
+    // drain pending retry
+    fakeClock.forwardTime(pushbackInMillis, TimeUnit.MILLISECONDS);
+
+    assertTrue(throttle.isAboveThreshold()); // count = 3
+    assertFalse(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // count = 2
+  }
+
+  @Test
+  public void throttledStream_FailWithRetriableStatusCode_WithNonRetriablePushback() {
+    Throttle throttle = new Throttle(4f, 0.8f);
+    RetriableStream<String> retriableStream = newThrottledRetriableStream(throttle);
+
+    ClientStream mockStream = mock(ClientStream.class);
+    doReturn(mockStream).when(retriableStreamRecorder).newSubstream(anyInt());
+    retriableStream.start(masterListener);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream).start(sublistenerCaptor.capture());
+
+    // mimic some other call in the channel triggers a throttle countdown
+    assertTrue(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // count = 3
+
+    Metadata headers = new Metadata();
+    headers.put(RetriableStream.GRPC_RETRY_PUSHBACK_MS, "");
+    sublistenerCaptor.getValue().closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), headers);
+    verify(retriableStreamRecorder).postCommit();
+    assertFalse(throttle.isAboveThreshold()); // count = 2
+  }
+
+  @Test
+  public void throttledStream_FailWithNonRetriableStatusCode_WithNonRetriablePushback() {
+    Throttle throttle = new Throttle(4f, 0.8f);
+    RetriableStream<String> retriableStream = newThrottledRetriableStream(throttle);
+
+    ClientStream mockStream = mock(ClientStream.class);
+    doReturn(mockStream).when(retriableStreamRecorder).newSubstream(anyInt());
+    retriableStream.start(masterListener);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream).start(sublistenerCaptor.capture());
+
+    // mimic some other call in the channel triggers a throttle countdown
+    assertTrue(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // count = 3
+
+    Metadata headers = new Metadata();
+    headers.put(RetriableStream.GRPC_RETRY_PUSHBACK_MS, "");
+    sublistenerCaptor.getValue().closed(Status.fromCode(NON_RETRIABLE_STATUS_CODE), headers);
+    verify(retriableStreamRecorder).postCommit();
+    assertFalse(throttle.isAboveThreshold()); // count = 2
+  }
+
+  @Test
+  public void throttleStream_Succeed() {
+    Throttle throttle = new Throttle(4f, 0.8f);
+    RetriableStream<String> retriableStream = newThrottledRetriableStream(throttle);
+
+    ClientStream mockStream = mock(ClientStream.class);
+    doReturn(mockStream).when(retriableStreamRecorder).newSubstream(anyInt());
+    retriableStream.start(masterListener);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream).start(sublistenerCaptor.capture());
+
+    // mimic some other calls in the channel trigger throttle countdowns
+    assertTrue(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // count = 3
+    assertFalse(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // count = 2
+    assertFalse(throttle.onQualifiedFailureThenCheckIsAboveThreshold()); // count = 1
+
+    sublistenerCaptor.getValue().headersRead(new Metadata());
+    verify(retriableStreamRecorder).postCommit();
+    assertFalse(throttle.isAboveThreshold());  // count = 1.8
+
+    // mimic some other call in the channel triggers a success
+    throttle.onSuccess();
+    assertTrue(throttle.isAboveThreshold()); // count = 2.6
+  }
+
+  @Test
+  public void transparentRetry() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    ClientStream mockStream2 = mock(ClientStream.class);
+    ClientStream mockStream3 = mock(ClientStream.class);
+    InOrder inOrder = inOrder(
+        retriableStreamRecorder,
+        mockStream1, mockStream2, mockStream3);
+
+    // start
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    retriableStream.start(masterListener);
+
+    inOrder.verify(retriableStreamRecorder).newSubstream(0);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    // transparent retry
+    doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(0);
+    sublistenerCaptor1.getValue()
+        .closed(Status.fromCode(NON_RETRIABLE_STATUS_CODE), REFUSED, new Metadata());
+
+    inOrder.verify(retriableStreamRecorder).newSubstream(0);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor2 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+    inOrder.verifyNoMoreInteractions();
+    verify(retriableStreamRecorder, never()).postCommit();
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    // no more transparent retry
+    doReturn(mockStream3).when(retriableStreamRecorder).newSubstream(1);
+    sublistenerCaptor2.getValue()
+        .closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), REFUSED, new Metadata());
+
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+    inOrder.verify(retriableStreamRecorder).newSubstream(1);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor3 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream3).start(sublistenerCaptor3.capture());
+    inOrder.verifyNoMoreInteractions();
+    verify(retriableStreamRecorder, never()).postCommit();
+    assertEquals(0, fakeClock.numPendingTasks());
+  }
+
+  @Test
+  public void normalRetry_thenNoTransparentRetry_butNormalRetry() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    ClientStream mockStream2 = mock(ClientStream.class);
+    ClientStream mockStream3 = mock(ClientStream.class);
+    InOrder inOrder = inOrder(
+        retriableStreamRecorder,
+        mockStream1, mockStream2, mockStream3);
+
+    // start
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    retriableStream.start(masterListener);
+
+    inOrder.verify(retriableStreamRecorder).newSubstream(0);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    // normal retry
+    doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+    sublistenerCaptor1.getValue()
+        .closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), PROCESSED, new Metadata());
+
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+    inOrder.verify(retriableStreamRecorder).newSubstream(1);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor2 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+    inOrder.verifyNoMoreInteractions();
+    verify(retriableStreamRecorder, never()).postCommit();
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    // no more transparent retry
+    doReturn(mockStream3).when(retriableStreamRecorder).newSubstream(2);
+    sublistenerCaptor2.getValue()
+        .closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), REFUSED, new Metadata());
+
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime(
+        (long) (INITIAL_BACKOFF_IN_SECONDS * BACKOFF_MULTIPLIER * FAKE_RANDOM), TimeUnit.SECONDS);
+    inOrder.verify(retriableStreamRecorder).newSubstream(2);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor3 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream3).start(sublistenerCaptor3.capture());
+    inOrder.verifyNoMoreInteractions();
+    verify(retriableStreamRecorder, never()).postCommit();
+  }
+
+  @Test
+  public void normalRetry_thenNoTransparentRetry_andNoMoreRetry() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    ClientStream mockStream2 = mock(ClientStream.class);
+    ClientStream mockStream3 = mock(ClientStream.class);
+    InOrder inOrder = inOrder(
+        retriableStreamRecorder,
+        mockStream1, mockStream2, mockStream3);
+
+    // start
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    retriableStream.start(masterListener);
+
+    inOrder.verify(retriableStreamRecorder).newSubstream(0);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream1).start(sublistenerCaptor1.capture());
+    inOrder.verifyNoMoreInteractions();
+
+    // normal retry
+    doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+    sublistenerCaptor1.getValue()
+        .closed(Status.fromCode(RETRIABLE_STATUS_CODE_1), PROCESSED, new Metadata());
+
+    assertEquals(1, fakeClock.numPendingTasks());
+    fakeClock.forwardTime((long) (INITIAL_BACKOFF_IN_SECONDS * FAKE_RANDOM), TimeUnit.SECONDS);
+    inOrder.verify(retriableStreamRecorder).newSubstream(1);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor2 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    inOrder.verify(mockStream2).start(sublistenerCaptor2.capture());
+    inOrder.verifyNoMoreInteractions();
+    verify(retriableStreamRecorder, never()).postCommit();
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    // no more transparent retry
+    doReturn(mockStream3).when(retriableStreamRecorder).newSubstream(2);
+    sublistenerCaptor2.getValue()
+        .closed(Status.fromCode(NON_RETRIABLE_STATUS_CODE), REFUSED, new Metadata());
+
+    verify(retriableStreamRecorder).postCommit();
+  }
+
+  @Test
+  public void droppedShouldNeverRetry() {
+    ClientStream mockStream1 = mock(ClientStream.class);
+    ClientStream mockStream2 = mock(ClientStream.class);
+    doReturn(mockStream1).when(retriableStreamRecorder).newSubstream(0);
+    doReturn(mockStream2).when(retriableStreamRecorder).newSubstream(1);
+
+    // start
+    retriableStream.start(masterListener);
+
+    verify(retriableStreamRecorder).newSubstream(0);
+    ArgumentCaptor<ClientStreamListener> sublistenerCaptor1 =
+        ArgumentCaptor.forClass(ClientStreamListener.class);
+    verify(mockStream1).start(sublistenerCaptor1.capture());
+
+    // drop and verify no retry
+    Status status = Status.fromCode(RETRIABLE_STATUS_CODE_1);
+    sublistenerCaptor1.getValue().closed(status, DROPPED, new Metadata());
+
+    verifyNoMoreInteractions(mockStream1, mockStream2);
+    verify(retriableStreamRecorder).postCommit();
+    verify(masterListener).closed(same(status), any(Metadata.class));
+  }
+
+  /**
+   * Used to stub a retriable stream as well as to record methods of the retriable stream being
+   * called.
+   */
+  private interface RetriableStreamRecorder {
+    void postCommit();
+
+    ClientStream newSubstream(int previousAttempts);
+
+    Status prestart();
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/RetryPolicyTest.java b/core/src/test/java/io/grpc/internal/RetryPolicyTest.java
new file mode 100644
index 0000000..e9296b9
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/RetryPolicyTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.internal.ServiceConfigInterceptor.RETRY_POLICY_KEY;
+import static java.lang.Double.parseDouble;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import com.google.common.collect.ImmutableSet;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status.Code;
+import io.grpc.internal.RetriableStream.Throttle;
+import io.grpc.testing.TestMethodDescriptors;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+/** Unit tests for RetryPolicy. */
+@RunWith(JUnit4.class)
+public class RetryPolicyTest {
+
+  @Test
+  public void getRetryPolicies() throws Exception {
+    BufferedReader reader = null;
+    try {
+      reader = new BufferedReader(new InputStreamReader(RetryPolicyTest.class.getResourceAsStream(
+          "/io/grpc/internal/test_retry_service_config.json"), "UTF-8"));
+      StringBuilder sb = new StringBuilder();
+      String line;
+      while ((line = reader.readLine()) != null) {
+        sb.append(line).append('\n');
+      }
+      Object serviceConfigObj = JsonParser.parse(sb.toString());
+      assertTrue(serviceConfigObj instanceof Map);
+
+      @SuppressWarnings("unchecked")
+      Map<String, Object> serviceConfig = (Map<String, Object>) serviceConfigObj;
+
+      ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor(
+          /* retryEnabled = */ true, /* maxRetryAttemptsLimit = */ 4,
+          /* maxHedgedAttemptsLimit = */ 3);
+      serviceConfigInterceptor.handleUpdate(serviceConfig);
+
+      MethodDescriptor.Builder<Void, Void> builder = TestMethodDescriptors.voidMethod().toBuilder();
+
+      MethodDescriptor<Void, Void> method = builder.setFullMethodName("not/exist").build();
+      assertEquals(
+          RetryPolicy.DEFAULT,
+          serviceConfigInterceptor.getRetryPolicyFromConfig(method));
+
+      method = builder.setFullMethodName("not_exist/Foo1").build();
+      assertEquals(
+          RetryPolicy.DEFAULT,
+          serviceConfigInterceptor.getRetryPolicyFromConfig(method));
+
+      method = builder.setFullMethodName("SimpleService1/not_exist").build();
+
+      assertEquals(
+          new RetryPolicy(
+              3,
+              TimeUnit.MILLISECONDS.toNanos(2100),
+              TimeUnit.MILLISECONDS.toNanos(2200),
+              parseDouble("3"),
+              ImmutableSet.of(Code.UNAVAILABLE, Code.RESOURCE_EXHAUSTED)),
+          serviceConfigInterceptor.getRetryPolicyFromConfig(method));
+
+      method = builder.setFullMethodName("SimpleService1/Foo1").build();
+      assertEquals(
+          new RetryPolicy(
+              4,
+              TimeUnit.MILLISECONDS.toNanos(100),
+              TimeUnit.MILLISECONDS.toNanos(1000),
+              parseDouble("2"),
+              ImmutableSet.of(Code.UNAVAILABLE)),
+          serviceConfigInterceptor.getRetryPolicyFromConfig(method));
+
+      method = builder.setFullMethodName("SimpleService2/not_exist").build();
+      assertEquals(
+          RetryPolicy.DEFAULT,
+          serviceConfigInterceptor.getRetryPolicyFromConfig(method));
+
+      method = builder.setFullMethodName("SimpleService2/Foo2").build();
+      assertEquals(
+          new RetryPolicy(
+              4,
+              TimeUnit.MILLISECONDS.toNanos(100),
+              TimeUnit.MILLISECONDS.toNanos(1000),
+              parseDouble("2"),
+              ImmutableSet.of(Code.UNAVAILABLE)),
+          serviceConfigInterceptor.getRetryPolicyFromConfig(method));
+    } finally {
+      if (reader != null) {
+        reader.close();
+      }
+    }
+  }
+
+  @Test
+  public void getRetryPolicies_retryDisabled() throws Exception {
+    Channel channel = mock(Channel.class);
+    ArgumentCaptor<CallOptions> callOptionsCap = ArgumentCaptor.forClass(CallOptions.class);
+    BufferedReader reader = null;
+    try {
+      reader = new BufferedReader(new InputStreamReader(RetryPolicyTest.class.getResourceAsStream(
+          "/io/grpc/internal/test_retry_service_config.json"), "UTF-8"));
+      StringBuilder sb = new StringBuilder();
+      String line;
+      while ((line = reader.readLine()) != null) {
+        sb.append(line).append('\n');
+      }
+      Object serviceConfigObj = JsonParser.parse(sb.toString());
+      assertTrue(serviceConfigObj instanceof Map);
+
+      @SuppressWarnings("unchecked")
+      Map<String, Object> serviceConfig = (Map<String, Object>) serviceConfigObj;
+
+      ServiceConfigInterceptor serviceConfigInterceptor = new ServiceConfigInterceptor(
+          /* retryEnabled = */ false, /* maxRetryAttemptsLimit = */ 4,
+          /* maxHedgedAttemptsLimit = */ 3);
+      serviceConfigInterceptor.handleUpdate(serviceConfig);
+
+      MethodDescriptor.Builder<Void, Void> builder = TestMethodDescriptors.voidMethod().toBuilder();
+
+      MethodDescriptor<Void, Void> method =
+          builder.setFullMethodName("SimpleService1/Foo1").build();
+
+      serviceConfigInterceptor.interceptCall(method, CallOptions.DEFAULT, channel);
+      verify(channel).newCall(eq(method), callOptionsCap.capture());
+      assertThat(callOptionsCap.getValue().getOption(RETRY_POLICY_KEY)).isNull();
+    } finally {
+      if (reader != null) {
+        reader.close();
+      }
+    }
+  }
+
+  @Test
+  public void getThrottle() throws Exception {
+    BufferedReader reader = null;
+    try {
+      reader = new BufferedReader(new InputStreamReader(RetryPolicyTest.class.getResourceAsStream(
+          "/io/grpc/internal/test_retry_service_config.json"), "UTF-8"));
+      StringBuilder sb = new StringBuilder();
+      String line;
+      while ((line = reader.readLine()) != null) {
+        sb.append(line).append('\n');
+      }
+      Object serviceConfigObj = JsonParser.parse(sb.toString());
+      assertTrue(serviceConfigObj instanceof Map);
+
+      @SuppressWarnings("unchecked")
+      Map<String, Object> serviceConfig = (Map<String, Object>) serviceConfigObj;
+      Throttle throttle = ServiceConfigUtil.getThrottlePolicy(serviceConfig);
+
+      assertEquals(new Throttle(10f, 0.1f), throttle);
+    } finally {
+      if (reader != null) {
+        reader.close();
+      }
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/SerializeReentrantCallsDirectExecutorTest.java b/core/src/test/java/io/grpc/internal/SerializeReentrantCallsDirectExecutorTest.java
new file mode 100644
index 0000000..493f79c
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/SerializeReentrantCallsDirectExecutorTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SerializeReentrantCallsDirectExecutorTest {
+
+  SerializeReentrantCallsDirectExecutor executor;
+
+  @Before
+  public void setup() {
+    executor = new SerializeReentrantCallsDirectExecutor();
+  }
+
+  @Test public void reentrantCallsShouldBeSerialized() {
+    final List<Integer> callOrder = new ArrayList<>(4);
+    executor.execute(new Runnable() {
+      @Override
+      public void run() {
+        executor.execute(new Runnable() {
+          @Override
+          public void run() {
+            executor.execute(new Runnable() {
+              @Override public void run() {
+                callOrder.add(3);
+              }
+            });
+
+            callOrder.add(2);
+
+            executor.execute(new Runnable() {
+              @Override public void run() {
+                callOrder.add(4);
+              }
+            });
+          }
+        });
+        callOrder.add(1);
+      }
+    });
+
+    assertEquals(asList(1, 2, 3, 4), callOrder);
+  }
+
+  @Test
+  public void exceptionShouldNotCancelQueuedTasks() {
+    final AtomicBoolean executed1 = new AtomicBoolean();
+    final AtomicBoolean executed2 = new AtomicBoolean();
+    executor.execute(new Runnable() {
+      @Override
+      public void run() {
+        executor.execute(new Runnable() {
+          @Override
+          public void run() {
+            executed1.set(true);
+            throw new RuntimeException("Two");
+          }
+        });
+        executor.execute(new Runnable() {
+          @Override
+          public void run() {
+            executed2.set(true);
+          }
+        });
+
+        throw new RuntimeException("One");
+      }
+    });
+
+    assertTrue(executed1.get());
+    assertTrue(executed2.get());
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void executingNullShouldFail() {
+    executor.execute(null);
+  }
+
+  @Test
+  public void executeCanBeRepeated() {
+    final List<Integer> executes = new ArrayList<>();
+    executor.execute(new Runnable() {
+      @Override
+      public void run() {
+        executes.add(1);
+      }
+    });
+    executor.execute(new Runnable() {
+      @Override
+      public void run() {
+        executes.add(2);
+      }
+    });
+
+    assertEquals(asList(1,2), executes);
+  }
+
+  @Test
+  public void interruptDoesNotAffectExecution() {
+    final AtomicInteger callsExecuted = new AtomicInteger();
+
+    Thread.currentThread().interrupt();
+
+    executor.execute(new Runnable() {
+      @Override
+      public void run() {
+        Thread.currentThread().interrupt();
+        callsExecuted.incrementAndGet();
+      }
+    });
+
+    executor.execute(new Runnable() {
+      @Override
+      public void run() {
+        callsExecuted.incrementAndGet();
+      }
+    });
+
+    // clear interrupted flag
+    Thread.interrupted();
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/SerializingExecutorTest.java b/core/src/test/java/io/grpc/internal/SerializingExecutorTest.java
new file mode 100644
index 0000000..55f4081
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/SerializingExecutorTest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class SerializingExecutorTest {
+  private SingleExecutor singleExecutor = new SingleExecutor();
+  private SerializingExecutor executor = new SerializingExecutor(singleExecutor);
+  private List<Integer> runs = new ArrayList<>();
+
+  private class AddToRuns implements Runnable {
+    private final int val;
+
+    public AddToRuns(int val) {
+      this.val = val;
+    }
+
+    @Override
+    public void run() {
+      runs.add(val);
+    }
+  }
+
+  @Test
+  public void resumable() {
+    class CoyExecutor implements Executor {
+      int runCount;
+
+      @Override
+      public void execute(Runnable command) {
+        runCount++;
+        if (runCount == 1) {
+          throw new RuntimeException();
+        }
+        command.run();
+      }
+    }
+
+    executor = new SerializingExecutor(new CoyExecutor());
+    try {
+      executor.execute(new AddToRuns(1));
+      fail();
+    } catch (RuntimeException expected) {
+    }
+
+    // Ensure that the runnable enqueued was actually removed on the failed execute above.
+    executor.execute(new AddToRuns(2));
+
+    assertThat(runs).containsExactly(2);
+  }
+
+
+  @Test
+  public void serial() {
+    executor.execute(new AddToRuns(1));
+    assertEquals(Collections.<Integer>emptyList(), runs);
+    singleExecutor.drain();
+    assertEquals(Arrays.asList(1), runs);
+
+    executor.execute(new AddToRuns(2));
+    assertEquals(Arrays.asList(1), runs);
+    singleExecutor.drain();
+    assertEquals(Arrays.asList(1, 2), runs);
+  }
+
+  @Test
+  public void parallel() {
+    executor.execute(new AddToRuns(1));
+    executor.execute(new AddToRuns(2));
+    executor.execute(new AddToRuns(3));
+    assertEquals(Collections.<Integer>emptyList(), runs);
+    singleExecutor.drain();
+    assertEquals(Arrays.asList(1, 2, 3), runs);
+  }
+
+  @Test
+  public void reentrant() {
+    executor.execute(new Runnable() {
+      @Override
+      public void run() {
+        executor.execute(new AddToRuns(3));
+        runs.add(1);
+      }
+    });
+    executor.execute(new AddToRuns(2));
+    singleExecutor.drain();
+    assertEquals(Arrays.asList(1, 2, 3), runs);
+  }
+
+  @Test
+  public void testFirstRunnableThrows() {
+    final RuntimeException ex = new RuntimeException();
+    executor.execute(new Runnable() {
+      @Override
+      public void run() {
+        runs.add(1);
+        throw ex;
+      }
+    });
+    executor.execute(new AddToRuns(2));
+    executor.execute(new AddToRuns(3));
+
+    singleExecutor.drain();
+
+    assertEquals(Arrays.asList(1, 2, 3), runs);
+  }
+
+  @Test
+  public void lastRunnableThrows() {
+    final RuntimeException ex = new RuntimeException();
+    executor.execute(new AddToRuns(1));
+    executor.execute(new AddToRuns(2));
+    executor.execute(new Runnable() {
+      @Override
+      public void run() {
+        runs.add(3);
+        throw ex;
+      }
+    });
+
+    singleExecutor.drain();
+    assertEquals(Arrays.asList(1, 2, 3), runs);
+
+    // Scheduling more still works
+    executor.execute(new AddToRuns(4));
+    assertEquals(Arrays.asList(1, 2, 3), runs);
+    singleExecutor.drain();
+    assertEquals(Arrays.asList(1, 2, 3, 4), runs);
+  }
+
+  @Test
+  public void firstExecuteThrows() {
+    final RuntimeException ex = new RuntimeException();
+    ForwardingExecutor forwardingExecutor = new ForwardingExecutor(new Executor() {
+      @Override
+      public void execute(Runnable r) {
+        throw ex;
+      }
+    });
+    executor = new SerializingExecutor(forwardingExecutor);
+    try {
+      executor.execute(new AddToRuns(1));
+      fail("expected exception");
+    } catch (RuntimeException e) {
+      assertSame(ex, e);
+    }
+    assertEquals(Collections.<Integer>emptyList(), runs);
+
+    forwardingExecutor.executor = singleExecutor;
+    executor.execute(new AddToRuns(2));
+    executor.execute(new AddToRuns(3));
+    assertEquals(Collections.<Integer>emptyList(), runs);
+    singleExecutor.drain();
+    assertEquals(Arrays.asList(2, 3), runs);
+  }
+
+  @Test
+  public void direct() {
+    executor = new SerializingExecutor(MoreExecutors.directExecutor());
+    executor.execute(new AddToRuns(1));
+    assertEquals(Arrays.asList(1), runs);
+    executor.execute(new AddToRuns(2));
+    assertEquals(Arrays.asList(1, 2), runs);
+    executor.execute(new AddToRuns(3));
+    assertEquals(Arrays.asList(1, 2, 3), runs);
+  }
+
+  @Test
+  public void testDirectReentrant() {
+    executor = new SerializingExecutor(MoreExecutors.directExecutor());
+    executor.execute(new Runnable() {
+      @Override
+      public void run() {
+        executor.execute(new AddToRuns(2));
+        runs.add(1);
+      }
+    });
+    assertEquals(Arrays.asList(1, 2), runs);
+    executor.execute(new AddToRuns(3));
+    assertEquals(Arrays.asList(1, 2, 3), runs);
+  }
+
+  private static class SingleExecutor implements Executor {
+    private Runnable runnable;
+
+    @Override
+    public void execute(Runnable r) {
+      if (runnable != null) {
+        fail("Already have runnable scheduled");
+      }
+      runnable = r;
+    }
+
+    public void drain() {
+      if (runnable != null) {
+        Runnable r = runnable;
+        runnable = null;
+        r.run();
+      }
+    }
+  }
+
+  private static class ForwardingExecutor implements Executor {
+    Executor executor;
+
+    public ForwardingExecutor(Executor executor) {
+      this.executor = executor;
+    }
+
+    @Override
+    public void execute(Runnable r) {
+      executor.execute(r);
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ServerCallImplTest.java b/core/src/test/java/io/grpc/internal/ServerCallImplTest.java
new file mode 100644
index 0000000..cff8cae
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ServerCallImplTest.java
@@ -0,0 +1,466 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.io.CharStreams;
+import io.grpc.CompressorRegistry;
+import io.grpc.Context;
+import io.grpc.DecompressorRegistry;
+import io.grpc.InternalChannelz.ServerStats;
+import io.grpc.InternalChannelz.ServerStats.Builder;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.ServerCall;
+import io.grpc.Status;
+import io.grpc.internal.ServerCallImpl.ServerStreamListenerImpl;
+import io.grpc.internal.testing.SingleMessageProducer;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class ServerCallImplTest {
+  @Rule public final ExpectedException thrown = ExpectedException.none();
+  @Mock private ServerStream stream;
+  @Mock private ServerCall.Listener<Long> callListener;
+
+  private final CallTracer serverCallTracer = CallTracer.getDefaultFactory().create();
+  private ServerCallImpl<Long, Long> call;
+  private Context.CancellableContext context;
+
+  private static final MethodDescriptor<Long, Long> UNARY_METHOD =
+      MethodDescriptor.<Long, Long>newBuilder()
+          .setType(MethodType.UNARY)
+          .setFullMethodName("service/method")
+          .setRequestMarshaller(new LongMarshaller())
+          .setResponseMarshaller(new LongMarshaller())
+          .build();
+
+  private static final MethodDescriptor<Long, Long> CLIENT_STREAMING_METHOD =
+      MethodDescriptor.<Long, Long>newBuilder()
+          .setType(MethodType.UNARY)
+          .setFullMethodName("service/method")
+          .setRequestMarshaller(new LongMarshaller())
+          .setResponseMarshaller(new LongMarshaller())
+          .build();
+
+  private final Metadata requestHeaders = new Metadata();
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    context = Context.ROOT.withCancellation();
+    call = new ServerCallImpl<Long, Long>(stream, UNARY_METHOD, requestHeaders, context,
+        DecompressorRegistry.getDefaultInstance(), CompressorRegistry.getDefaultInstance(),
+        serverCallTracer);
+  }
+
+  @Test
+  public void callTracer_success() {
+    callTracer0(Status.OK);
+  }
+
+  @Test
+  public void callTracer_failure() {
+    callTracer0(Status.UNKNOWN);
+  }
+
+  private void callTracer0(Status status) {
+    CallTracer tracer = CallTracer.getDefaultFactory().create();
+    Builder beforeBuilder = new Builder();
+    tracer.updateBuilder(beforeBuilder);
+    ServerStats before = beforeBuilder.build();
+    assertEquals(0, before.callsStarted);
+    assertEquals(0, before.lastCallStartedNanos);
+
+    call = new ServerCallImpl<Long, Long>(stream, UNARY_METHOD, requestHeaders, context,
+        DecompressorRegistry.getDefaultInstance(), CompressorRegistry.getDefaultInstance(),
+        tracer);
+
+    // required boilerplate
+    call.sendHeaders(new Metadata());
+    call.sendMessage(123L);
+    // end: required boilerplate
+
+    call.close(status, new Metadata());
+    Builder afterBuilder = new Builder();
+    tracer.updateBuilder(afterBuilder);
+    ServerStats after = afterBuilder.build();
+    assertEquals(1, after.callsStarted);
+    if (status.isOk()) {
+      assertEquals(1, after.callsSucceeded);
+    } else {
+      assertEquals(1, after.callsFailed);
+    }
+  }
+
+  @Test
+  public void request() {
+    call.request(10);
+
+    verify(stream).request(10);
+  }
+
+  @Test
+  public void sendHeader_firstCall() {
+    Metadata headers = new Metadata();
+
+    call.sendHeaders(headers);
+
+    verify(stream).writeHeaders(headers);
+  }
+
+  @Test
+  public void sendHeader_failsOnSecondCall() {
+    call.sendHeaders(new Metadata());
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("sendHeaders has already been called");
+
+    call.sendHeaders(new Metadata());
+  }
+
+  @Test
+  public void sendHeader_failsOnClosed() {
+    call.close(Status.CANCELLED, new Metadata());
+
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("call is closed");
+
+    call.sendHeaders(new Metadata());
+  }
+
+  @Test
+  public void sendMessage() {
+    call.sendHeaders(new Metadata());
+    call.sendMessage(1234L);
+
+    verify(stream).writeMessage(isA(InputStream.class));
+    verify(stream).flush();
+  }
+
+  @Test
+  public void sendMessage_failsOnClosed() {
+    call.sendHeaders(new Metadata());
+    call.close(Status.CANCELLED, new Metadata());
+
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("call is closed");
+
+    call.sendMessage(1234L);
+  }
+
+  @Test
+  public void sendMessage_failsIfheadersUnsent() {
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("sendHeaders has not been called");
+
+    call.sendMessage(1234L);
+  }
+
+  @Test
+  public void sendMessage_closesOnFailure() {
+    call.sendHeaders(new Metadata());
+    doThrow(new RuntimeException("bad")).when(stream).writeMessage(isA(InputStream.class));
+
+    call.sendMessage(1234L);
+
+    verify(stream).close(isA(Status.class), isA(Metadata.class));
+  }
+
+  @Test
+  public void sendMessage_serverSendsOne_closeOnSecondCall_unary() {
+    sendMessage_serverSendsOne_closeOnSecondCall(UNARY_METHOD);
+  }
+
+  @Test
+  public void sendMessage_serverSendsOne_closeOnSecondCall_clientStreaming() {
+    sendMessage_serverSendsOne_closeOnSecondCall(CLIENT_STREAMING_METHOD);
+  }
+
+  private void sendMessage_serverSendsOne_closeOnSecondCall(
+      MethodDescriptor<Long, Long> method) {
+    ServerCallImpl<Long, Long> serverCall = new ServerCallImpl<Long, Long>(
+        stream,
+        method,
+        requestHeaders,
+        context,
+        DecompressorRegistry.getDefaultInstance(),
+        CompressorRegistry.getDefaultInstance(),
+        serverCallTracer);
+    serverCall.sendHeaders(new Metadata());
+    serverCall.sendMessage(1L);
+    verify(stream, times(1)).writeMessage(any(InputStream.class));
+    verify(stream, never()).close(any(Status.class), any(Metadata.class));
+
+    // trying to send a second message causes gRPC to close the underlying stream
+    serverCall.sendMessage(1L);
+    verify(stream, times(1)).writeMessage(any(InputStream.class));
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(stream, times(1)).cancel(statusCaptor.capture());
+    assertEquals(Status.Code.INTERNAL, statusCaptor.getValue().getCode());
+    assertEquals(ServerCallImpl.TOO_MANY_RESPONSES, statusCaptor.getValue().getDescription());
+  }
+
+  @Test
+  public void sendMessage_serverSendsOne_closeOnSecondCall_appRunToCompletion_unary() {
+    sendMessage_serverSendsOne_closeOnSecondCall_appRunToCompletion(UNARY_METHOD);
+  }
+
+  @Test
+  public void sendMessage_serverSendsOne_closeOnSecondCall_appRunToCompletion_clientStreaming() {
+    sendMessage_serverSendsOne_closeOnSecondCall_appRunToCompletion(CLIENT_STREAMING_METHOD);
+  }
+
+  private void sendMessage_serverSendsOne_closeOnSecondCall_appRunToCompletion(
+      MethodDescriptor<Long, Long> method) {
+    ServerCallImpl<Long, Long> serverCall = new ServerCallImpl<Long, Long>(
+        stream,
+        method,
+        requestHeaders,
+        context,
+        DecompressorRegistry.getDefaultInstance(),
+        CompressorRegistry.getDefaultInstance(),
+        serverCallTracer);
+    serverCall.sendHeaders(new Metadata());
+    serverCall.sendMessage(1L);
+    serverCall.sendMessage(1L);
+    verify(stream, times(1)).writeMessage(any(InputStream.class));
+    verify(stream, times(1)).cancel(any(Status.class));
+
+    // App runs to completion but everything is ignored
+    serverCall.sendMessage(1L);
+    serverCall.close(Status.OK, new Metadata());
+    try {
+      serverCall.close(Status.OK, new Metadata());
+      fail("calling a second time should still cause an error");
+    } catch (IllegalStateException expected) {
+      // noop
+    }
+  }
+
+  @Test
+  public void serverSendsOne_okFailsOnMissingResponse_unary() {
+    serverSendsOne_okFailsOnMissingResponse(UNARY_METHOD);
+  }
+
+  @Test
+  public void serverSendsOne_okFailsOnMissingResponse_clientStreaming() {
+    serverSendsOne_okFailsOnMissingResponse(CLIENT_STREAMING_METHOD);
+  }
+
+  private void serverSendsOne_okFailsOnMissingResponse(
+      MethodDescriptor<Long, Long> method) {
+    ServerCallImpl<Long, Long> serverCall = new ServerCallImpl<Long, Long>(
+        stream,
+        method,
+        requestHeaders,
+        context,
+        DecompressorRegistry.getDefaultInstance(),
+        CompressorRegistry.getDefaultInstance(),
+        serverCallTracer);
+    serverCall.close(Status.OK, new Metadata());
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(stream, times(1)).cancel(statusCaptor.capture());
+    assertEquals(Status.Code.INTERNAL, statusCaptor.getValue().getCode());
+    assertEquals(ServerCallImpl.MISSING_RESPONSE, statusCaptor.getValue().getDescription());
+  }
+
+  @Test
+  public void serverSendsOne_canErrorWithoutResponse() {
+    final String description = "test description";
+    final Status status = Status.RESOURCE_EXHAUSTED.withDescription(description);
+    final Metadata metadata = new Metadata();
+    call.close(status, metadata);
+    verify(stream, times(1)).close(same(status), same(metadata));
+  }
+
+  @Test
+  public void isReady() {
+    when(stream.isReady()).thenReturn(true);
+
+    assertTrue(call.isReady());
+  }
+
+  @Test
+  public void getAuthority() {
+    when(stream.getAuthority()).thenReturn("fooapi.googleapis.com");
+    assertEquals("fooapi.googleapis.com", call.getAuthority());
+    verify(stream).getAuthority();
+  }
+
+  @Test
+  public void getNullAuthority() {
+    when(stream.getAuthority()).thenReturn(null);
+    assertNull(call.getAuthority());
+    verify(stream).getAuthority();
+  }
+
+  @Test
+  public void setMessageCompression() {
+    call.setMessageCompression(true);
+
+    verify(stream).setMessageCompression(true);
+  }
+
+  @Test
+  public void streamListener_halfClosed() {
+    ServerStreamListenerImpl<Long> streamListener =
+        new ServerCallImpl.ServerStreamListenerImpl<Long>(call, callListener, context);
+
+    streamListener.halfClosed();
+
+    verify(callListener).onHalfClose();
+  }
+
+  @Test
+  public void streamListener_halfClosed_onlyOnce() {
+    ServerStreamListenerImpl<Long> streamListener =
+        new ServerCallImpl.ServerStreamListenerImpl<Long>(call, callListener, context);
+    streamListener.halfClosed();
+    // canceling the call should short circuit future halfClosed() calls.
+    streamListener.closed(Status.CANCELLED);
+
+    streamListener.halfClosed();
+
+    verify(callListener).onHalfClose();
+  }
+
+  @Test
+  public void streamListener_closedOk() {
+    ServerStreamListenerImpl<Long> streamListener =
+        new ServerCallImpl.ServerStreamListenerImpl<Long>(call, callListener, context);
+
+    streamListener.closed(Status.OK);
+
+    verify(callListener).onComplete();
+    assertTrue(context.isCancelled());
+    assertNull(context.cancellationCause());
+  }
+
+  @Test
+  public void streamListener_closedCancelled() {
+    ServerStreamListenerImpl<Long> streamListener =
+        new ServerCallImpl.ServerStreamListenerImpl<Long>(call, callListener, context);
+
+    streamListener.closed(Status.CANCELLED);
+
+    verify(callListener).onCancel();
+    assertTrue(context.isCancelled());
+    assertNull(context.cancellationCause());
+  }
+
+  @Test
+  public void streamListener_onReady() {
+    ServerStreamListenerImpl<Long> streamListener =
+        new ServerCallImpl.ServerStreamListenerImpl<Long>(call, callListener, context);
+
+    streamListener.onReady();
+
+    verify(callListener).onReady();
+  }
+
+  @Test
+  public void streamListener_onReady_onlyOnce() {
+    ServerStreamListenerImpl<Long> streamListener =
+        new ServerCallImpl.ServerStreamListenerImpl<Long>(call, callListener, context);
+    streamListener.onReady();
+    // canceling the call should short circuit future halfClosed() calls.
+    streamListener.closed(Status.CANCELLED);
+
+    streamListener.onReady();
+
+    verify(callListener).onReady();
+  }
+
+  @Test
+  public void streamListener_messageRead() {
+    ServerStreamListenerImpl<Long> streamListener =
+        new ServerCallImpl.ServerStreamListenerImpl<Long>(call, callListener, context);
+    streamListener.messagesAvailable(new SingleMessageProducer(UNARY_METHOD.streamRequest(1234L)));
+
+    verify(callListener).onMessage(1234L);
+  }
+
+  @Test
+  public void streamListener_messageRead_onlyOnce() {
+    ServerStreamListenerImpl<Long> streamListener =
+        new ServerCallImpl.ServerStreamListenerImpl<Long>(call, callListener, context);
+    streamListener.messagesAvailable(new SingleMessageProducer(UNARY_METHOD.streamRequest(1234L)));
+    // canceling the call should short circuit future halfClosed() calls.
+    streamListener.closed(Status.CANCELLED);
+
+    streamListener.messagesAvailable(new SingleMessageProducer(UNARY_METHOD.streamRequest(1234L)));
+
+    verify(callListener).onMessage(1234L);
+  }
+
+  @Test
+  public void streamListener_unexpectedRuntimeException() {
+    ServerStreamListenerImpl<Long> streamListener =
+        new ServerCallImpl.ServerStreamListenerImpl<Long>(call, callListener, context);
+    doThrow(new RuntimeException("unexpected exception"))
+        .when(callListener)
+        .onMessage(any(Long.class));
+
+    InputStream inputStream = UNARY_METHOD.streamRequest(1234L);
+
+    thrown.expect(RuntimeException.class);
+    thrown.expectMessage("unexpected exception");
+    streamListener.messagesAvailable(new SingleMessageProducer(inputStream));
+  }
+
+  private static class LongMarshaller implements Marshaller<Long> {
+    @Override
+    public InputStream stream(Long value) {
+      return new ByteArrayInputStream(value.toString().getBytes(UTF_8));
+    }
+
+    @Override
+    public Long parse(InputStream stream) {
+      try {
+        return Long.parseLong(CharStreams.toString(new InputStreamReader(stream, UTF_8)));
+      } catch (Exception e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/ServerImplTest.java b/core/src/test/java/io/grpc/internal/ServerImplTest.java
new file mode 100644
index 0000000..edab955
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ServerImplTest.java
@@ -0,0 +1,1426 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.InternalChannelz.id;
+import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Matchers.isNotNull;
+import static org.mockito.Matchers.notNull;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.Attributes;
+import io.grpc.BinaryLog;
+import io.grpc.Channel;
+import io.grpc.Compressor;
+import io.grpc.Context;
+import io.grpc.Grpc;
+import io.grpc.HandlerRegistry;
+import io.grpc.IntegerMarshaller;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.ServerSocketsList;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.InternalLogId;
+import io.grpc.InternalServerInterceptors;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerCall;
+import io.grpc.ServerCall.Listener;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.ServerMethodDefinition;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.ServerStreamTracer;
+import io.grpc.ServerTransportFilter;
+import io.grpc.ServiceDescriptor;
+import io.grpc.Status;
+import io.grpc.StringMarshaller;
+import io.grpc.internal.ServerImpl.JumpToApplicationThreadServerStreamListener;
+import io.grpc.internal.testing.SingleMessageProducer;
+import io.grpc.internal.testing.TestServerStreamTracer;
+import io.grpc.util.MutableHandlerRegistry;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.SocketAddress;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.Nullable;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link ServerImpl}. */
+@RunWith(JUnit4.class)
+public class ServerImplTest {
+  private static final IntegerMarshaller INTEGER_MARSHALLER = IntegerMarshaller.INSTANCE;
+  private static final StringMarshaller STRING_MARSHALLER = StringMarshaller.INSTANCE;
+  private static final MethodDescriptor<String, Integer> METHOD =
+      MethodDescriptor.<String, Integer>newBuilder()
+          .setType(MethodDescriptor.MethodType.UNKNOWN)
+          .setFullMethodName("Waiter/serve")
+          .setRequestMarshaller(STRING_MARSHALLER)
+          .setResponseMarshaller(INTEGER_MARSHALLER)
+          .build();
+  private static final Context.Key<String> SERVER_ONLY = Context.key("serverOnly");
+  private static final Context.Key<String> SERVER_TRACER_ADDED_KEY = Context.key("tracer-added");
+  private static final Context.CancellableContext SERVER_CONTEXT =
+      Context.ROOT.withValue(SERVER_ONLY, "yes").withCancellation();
+  private static final FakeClock.TaskFilter CONTEXT_CLOSER_TASK_FITLER =
+      new FakeClock.TaskFilter() {
+        @Override
+        public boolean shouldAccept(Runnable runnable) {
+          return runnable instanceof ServerImpl.ContextCloser;
+        }
+      };
+  private static final String AUTHORITY = "some_authority";
+
+  @Rule public final ExpectedException thrown = ExpectedException.none();
+
+  @BeforeClass
+  public static void beforeStartUp() {
+    // Cancel the root context. Server will fork it so the per-call context should not
+    // be cancelled.
+    SERVER_CONTEXT.cancel(null);
+  }
+
+  private final FakeClock executor = new FakeClock();
+  private final FakeClock timer = new FakeClock();
+  private final InternalChannelz channelz = new InternalChannelz();
+
+  @Mock
+  private ServerStreamTracer.Factory streamTracerFactory;
+  private List<ServerStreamTracer.Factory> streamTracerFactories;
+  private final TestServerStreamTracer streamTracer = new TestServerStreamTracer() {
+      @Override
+      public Context filterContext(Context context) {
+        Context newCtx = super.filterContext(context);
+        return newCtx.withValue(SERVER_TRACER_ADDED_KEY, "context added by tracer");
+      }
+    };
+  @Mock
+  private ObjectPool<Executor> executorPool;
+  private Builder builder = new Builder();
+  private MutableHandlerRegistry mutableFallbackRegistry = new MutableHandlerRegistry();
+  private HandlerRegistry fallbackRegistry = mock(
+      HandlerRegistry.class,
+      delegatesTo(new HandlerRegistry() {
+        @Override
+        public ServerMethodDefinition<?, ?> lookupMethod(
+            String methodName, @Nullable String authority) {
+          return mutableFallbackRegistry.lookupMethod(methodName, authority);
+        }
+
+        @Override
+        public List<ServerServiceDefinition> getServices() {
+          return mutableFallbackRegistry.getServices();
+        }
+      }));
+  private SimpleServer transportServer = new SimpleServer();
+  private ServerImpl server;
+
+  @Captor
+  private ArgumentCaptor<Status> statusCaptor;
+  @Captor
+  private ArgumentCaptor<Metadata> metadataCaptor;
+  @Captor
+  private ArgumentCaptor<ServerStreamListener> streamListenerCaptor;
+
+  @Mock
+  private ServerStream stream;
+  @Mock
+  private ServerCall.Listener<String> callListener;
+  @Mock
+  private ServerCallHandler<String, Integer> callHandler;
+
+  /** Set up for test. */
+  @Before
+  public void startUp() throws IOException {
+    MockitoAnnotations.initMocks(this);
+    builder.channelz = channelz;
+    streamTracerFactories = Arrays.asList(streamTracerFactory);
+    when(executorPool.getObject()).thenReturn(executor.getScheduledExecutorService());
+    when(streamTracerFactory.newServerStreamTracer(anyString(), any(Metadata.class)))
+        .thenReturn(streamTracer);
+    when(stream.getAuthority()).thenReturn(AUTHORITY);
+  }
+
+  @After
+  public void noPendingTasks() {
+    assertEquals(0, executor.numPendingTasks());
+    assertEquals(0, timer.numPendingTasks());
+  }
+
+  @Test
+  public void startStopImmediate() throws IOException {
+    transportServer = new SimpleServer() {
+      @Override
+      public void shutdown() {}
+    };
+    createAndStartServer();
+    server.shutdown();
+    assertTrue(server.isShutdown());
+    assertFalse(server.isTerminated());
+    transportServer.listener.serverShutdown();
+    assertTrue(server.isTerminated());
+  }
+
+  @Test
+  public void stopImmediate() throws IOException {
+    transportServer = new SimpleServer() {
+      @Override
+      public void shutdown() {
+        throw new AssertionError("Should not be called, because wasn't started");
+      }
+    };
+    createServer();
+    server.shutdown();
+    assertTrue(server.isShutdown());
+    assertTrue(server.isTerminated());
+    verifyNoMoreInteractions(executorPool);
+  }
+
+  @Test
+  public void startStopImmediateWithChildTransport() throws IOException {
+    createAndStartServer();
+    verifyExecutorsAcquired();
+    class DelayedShutdownServerTransport extends SimpleServerTransport {
+      boolean shutdown;
+
+      @Override
+      public void shutdown() {
+        shutdown = true;
+      }
+    }
+
+    DelayedShutdownServerTransport serverTransport = new DelayedShutdownServerTransport();
+    transportServer.registerNewServerTransport(serverTransport);
+    server.shutdown();
+    assertTrue(server.isShutdown());
+    assertFalse(server.isTerminated());
+    assertTrue(serverTransport.shutdown);
+    verifyExecutorsNotReturned();
+
+    serverTransport.listener.transportTerminated();
+    assertTrue(server.isTerminated());
+    verifyExecutorsReturned();
+  }
+
+  @Test
+  public void startShutdownNowImmediateWithChildTransport() throws IOException {
+    createAndStartServer();
+    verifyExecutorsAcquired();
+    class DelayedShutdownServerTransport extends SimpleServerTransport {
+      boolean shutdown;
+
+      @Override
+      public void shutdown() {}
+
+      @Override
+      public void shutdownNow(Status reason) {
+        shutdown = true;
+      }
+    }
+
+    DelayedShutdownServerTransport serverTransport = new DelayedShutdownServerTransport();
+    transportServer.registerNewServerTransport(serverTransport);
+    server.shutdownNow();
+    assertTrue(server.isShutdown());
+    assertFalse(server.isTerminated());
+    assertTrue(serverTransport.shutdown);
+    verifyExecutorsNotReturned();
+
+    serverTransport.listener.transportTerminated();
+    assertTrue(server.isTerminated());
+    verifyExecutorsReturned();
+  }
+
+  @Test
+  public void shutdownNowAfterShutdown() throws IOException {
+    createAndStartServer();
+    verifyExecutorsAcquired();
+    class DelayedShutdownServerTransport extends SimpleServerTransport {
+      boolean shutdown;
+
+      @Override
+      public void shutdown() {}
+
+      @Override
+      public void shutdownNow(Status reason) {
+        shutdown = true;
+      }
+    }
+
+    DelayedShutdownServerTransport serverTransport = new DelayedShutdownServerTransport();
+    transportServer.registerNewServerTransport(serverTransport);
+    server.shutdown();
+    assertTrue(server.isShutdown());
+    server.shutdownNow();
+    assertFalse(server.isTerminated());
+    assertTrue(serverTransport.shutdown);
+    verifyExecutorsNotReturned();
+
+    serverTransport.listener.transportTerminated();
+    assertTrue(server.isTerminated());
+    verifyExecutorsReturned();
+  }
+
+  @Test
+  public void shutdownNowAfterSlowShutdown() throws IOException {
+    transportServer = new SimpleServer() {
+      @Override
+      public void shutdown() {
+        // Don't call super which calls listener.serverShutdown(). We'll call it manually.
+      }
+    };
+    createAndStartServer();
+    verifyExecutorsAcquired();
+    class DelayedShutdownServerTransport extends SimpleServerTransport {
+      boolean shutdown;
+
+      @Override
+      public void shutdown() {}
+
+      @Override
+      public void shutdownNow(Status reason) {
+        shutdown = true;
+      }
+    }
+
+    DelayedShutdownServerTransport serverTransport = new DelayedShutdownServerTransport();
+    transportServer.registerNewServerTransport(serverTransport);
+    server.shutdown();
+    server.shutdownNow();
+    transportServer.listener.serverShutdown();
+    assertTrue(server.isShutdown());
+    assertFalse(server.isTerminated());
+
+    verifyExecutorsNotReturned();
+    serverTransport.listener.transportTerminated();
+    verifyExecutorsReturned();
+    assertTrue(server.isTerminated());
+  }
+
+  @Test
+  public void transportServerFailsStartup() {
+    final IOException ex = new IOException();
+    class FailingStartupServer extends SimpleServer {
+      @Override
+      public void start(ServerListener listener) throws IOException {
+        throw ex;
+      }
+    }
+
+    transportServer = new FailingStartupServer();
+    createServer();
+    try {
+      server.start();
+      fail("expected exception");
+    } catch (IOException e) {
+      assertSame(ex, e);
+    }
+    verifyNoMoreInteractions(executorPool);
+  }
+
+  @Test
+  public void transportHandshakeTimeout_expired() throws Exception {
+    class ShutdownRecordingTransport extends SimpleServerTransport {
+      Status shutdownNowStatus;
+
+      @Override public void shutdownNow(Status status) {
+        shutdownNowStatus = status;
+        super.shutdownNow(status);
+      }
+    }
+
+    builder.handshakeTimeout(60, TimeUnit.SECONDS);
+    createAndStartServer();
+    ShutdownRecordingTransport serverTransport = new ShutdownRecordingTransport();
+    transportServer.registerNewServerTransport(serverTransport);
+    timer.forwardTime(59, TimeUnit.SECONDS);
+    assertNull("shutdownNow status", serverTransport.shutdownNowStatus);
+    // Don't call transportReady() in time
+    timer.forwardTime(2, TimeUnit.SECONDS);
+    assertNotNull("shutdownNow status", serverTransport.shutdownNowStatus);
+  }
+
+  @Test
+  public void methodNotFound() throws Exception {
+    createAndStartServer();
+    ServerTransportListener transportListener
+        = transportServer.registerNewServerTransport(new SimpleServerTransport());
+    transportListener.transportReady(Attributes.EMPTY);
+    Metadata requestHeaders = new Metadata();
+    StatsTraceContext statsTraceCtx =
+        StatsTraceContext.newServerContext(
+            streamTracerFactories, "Waiter/nonexist", requestHeaders);
+    when(stream.statsTraceContext()).thenReturn(statsTraceCtx);
+    transportListener.streamCreated(stream, "Waiter/nonexist", requestHeaders);
+    verify(stream).setListener(isA(ServerStreamListener.class));
+    verify(stream, atLeast(1)).statsTraceContext();
+
+    assertEquals(1, executor.runDueTasks());
+    verify(stream).close(statusCaptor.capture(), any(Metadata.class));
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.Code.UNIMPLEMENTED, status.getCode());
+    assertEquals("Method not found: Waiter/nonexist", status.getDescription());
+
+    verify(streamTracerFactory).newServerStreamTracer(eq("Waiter/nonexist"), same(requestHeaders));
+    assertNull(streamTracer.getServerCallInfo());
+    assertEquals(Status.Code.UNIMPLEMENTED, statusCaptor.getValue().getCode());
+  }
+
+  @Test
+  public void decompressorNotFound() throws Exception {
+    String decompressorName = "NON_EXISTENT_DECOMPRESSOR";
+    createAndStartServer();
+    ServerTransportListener transportListener
+        = transportServer.registerNewServerTransport(new SimpleServerTransport());
+    transportListener.transportReady(Attributes.EMPTY);
+    Metadata requestHeaders = new Metadata();
+    requestHeaders.put(MESSAGE_ENCODING_KEY, decompressorName);
+    StatsTraceContext statsTraceCtx =
+        StatsTraceContext.newServerContext(
+            streamTracerFactories, "Waiter/nonexist", requestHeaders);
+    when(stream.statsTraceContext()).thenReturn(statsTraceCtx);
+
+    transportListener.streamCreated(stream, "Waiter/nonexist", requestHeaders);
+
+    verify(stream).close(statusCaptor.capture(), any(Metadata.class));
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.Code.UNIMPLEMENTED, status.getCode());
+    assertEquals("Can't find decompressor for " + decompressorName, status.getDescription());
+    verifyNoMoreInteractions(stream);
+  }
+
+  @Test
+  public void basicExchangeSuccessful() throws Exception {
+    createAndStartServer();
+    basicExchangeHelper(METHOD, "Lots of pizza, please", 314, 50);
+  }
+
+  private void basicExchangeHelper(
+      MethodDescriptor<String, Integer> method,
+      String request,
+      int firstResponse,
+      Integer extraResponse) throws Exception {
+    final Metadata.Key<String> metadataKey
+        = Metadata.Key.of("inception", Metadata.ASCII_STRING_MARSHALLER);
+    final AtomicReference<ServerCall<String, Integer>> callReference
+        = new AtomicReference<ServerCall<String, Integer>>();
+    final AtomicReference<Context> callContextReference = new AtomicReference<Context>();
+    mutableFallbackRegistry.addService(ServerServiceDefinition.builder(
+        new ServiceDescriptor("Waiter", method))
+        .addMethod(
+            method,
+            new ServerCallHandler<String, Integer>() {
+              @Override
+              public ServerCall.Listener<String> startCall(
+                  ServerCall<String, Integer> call,
+                  Metadata headers) {
+                assertEquals("Waiter/serve", call.getMethodDescriptor().getFullMethodName());
+                assertNotNull(call);
+                assertNotNull(headers);
+                assertEquals("value", headers.get(metadataKey));
+                callReference.set(call);
+                callContextReference.set(Context.current());
+                return callListener;
+              }
+            }).build());
+    ServerTransportListener transportListener
+        = transportServer.registerNewServerTransport(new SimpleServerTransport());
+    transportListener.transportReady(Attributes.EMPTY);
+
+    Metadata requestHeaders = new Metadata();
+    requestHeaders.put(metadataKey, "value");
+    StatsTraceContext statsTraceCtx =
+        StatsTraceContext.newServerContext(streamTracerFactories, "Waiter/serve", requestHeaders);
+    when(stream.statsTraceContext()).thenReturn(statsTraceCtx);
+
+    transportListener.streamCreated(stream, "Waiter/serve", requestHeaders);
+    verify(stream).setListener(streamListenerCaptor.capture());
+    ServerStreamListener streamListener = streamListenerCaptor.getValue();
+    assertNotNull(streamListener);
+    verify(stream, atLeast(1)).statsTraceContext();
+    verify(fallbackRegistry, never()).lookupMethod(any(String.class), any(String.class));
+
+    assertEquals(1, executor.runDueTasks());
+    ServerCall<String, Integer> call = callReference.get();
+    assertNotNull(call);
+    assertEquals(
+        new ServerCallInfoImpl<String, Integer>(
+            call.getMethodDescriptor(),
+            call.getAttributes(),
+            call.getAuthority()),
+        streamTracer.getServerCallInfo());
+    verify(fallbackRegistry).lookupMethod("Waiter/serve", AUTHORITY);
+    Context callContext = callContextReference.get();
+    assertNotNull(callContext);
+    assertEquals("context added by tracer", SERVER_TRACER_ADDED_KEY.get(callContext));
+
+    streamListener.messagesAvailable(new SingleMessageProducer(STRING_MARSHALLER.stream(request)));
+    assertEquals(1, executor.runDueTasks());
+    verify(callListener).onMessage(request);
+
+    Metadata responseHeaders = new Metadata();
+    responseHeaders.put(metadataKey, "response value");
+    call.sendHeaders(responseHeaders);
+    verify(stream).writeHeaders(responseHeaders);
+    verify(stream).setCompressor(isA(Compressor.class));
+
+    call.sendMessage(firstResponse);
+    ArgumentCaptor<InputStream> inputCaptor = ArgumentCaptor.forClass(InputStream.class);
+    verify(stream).writeMessage(inputCaptor.capture());
+    verify(stream).flush();
+    assertEquals(firstResponse, INTEGER_MARSHALLER.parse(inputCaptor.getValue()).intValue());
+
+    streamListener.halfClosed(); // All full; no dessert.
+    assertEquals(1, executor.runDueTasks());
+    verify(callListener).onHalfClose();
+
+    if (extraResponse != null) {
+      call.sendMessage(extraResponse);
+      verify(stream, times(2)).writeMessage(inputCaptor.capture());
+      verify(stream, times(2)).flush();
+      assertEquals(
+          (int) extraResponse, INTEGER_MARSHALLER.parse(inputCaptor.getValue()).intValue());
+    }
+
+    Metadata trailers = new Metadata();
+    trailers.put(metadataKey, "another value");
+    Status status = Status.OK.withDescription("A okay");
+    call.close(status, trailers);
+    verify(stream).close(status, trailers);
+
+    streamListener.closed(Status.OK);
+    assertEquals(1, executor.runDueTasks());
+    verify(callListener).onComplete();
+
+    verify(stream, atLeast(1)).statsTraceContext();
+    verifyNoMoreInteractions(callListener);
+
+    verify(streamTracerFactory).newServerStreamTracer(eq("Waiter/serve"), same(requestHeaders));
+  }
+
+  @Test
+  public void transportFilters() throws Exception {
+    final SocketAddress remoteAddr = mock(SocketAddress.class);
+    final Attributes.Key<String> key1 = Attributes.Key.create("test-key1");
+    final Attributes.Key<String> key2 = Attributes.Key.create("test-key2");
+    final Attributes.Key<String> key3 = Attributes.Key.create("test-key3");
+    final AtomicReference<Attributes> filter1TerminationCallbackArgument =
+        new AtomicReference<Attributes>();
+    final AtomicReference<Attributes> filter2TerminationCallbackArgument =
+        new AtomicReference<Attributes>();
+    final AtomicInteger readyCallbackCalled = new AtomicInteger(0);
+    final AtomicInteger terminationCallbackCalled = new AtomicInteger(0);
+    builder.addTransportFilter(new ServerTransportFilter() {
+        @Override
+        public Attributes transportReady(Attributes attrs) {
+          assertEquals(Attributes.newBuilder()
+              .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, remoteAddr)
+              .build(), attrs);
+          readyCallbackCalled.incrementAndGet();
+          return attrs.toBuilder()
+              .set(key1, "yalayala")
+              .set(key2, "blabla")
+              .build();
+        }
+
+        @Override
+        public void transportTerminated(Attributes attrs) {
+          terminationCallbackCalled.incrementAndGet();
+          filter1TerminationCallbackArgument.set(attrs);
+        }
+      });
+    builder.addTransportFilter(new ServerTransportFilter() {
+        @Override
+        public Attributes transportReady(Attributes attrs) {
+          assertEquals(Attributes.newBuilder()
+              .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, remoteAddr)
+              .set(key1, "yalayala")
+              .set(key2, "blabla")
+              .build(), attrs);
+          readyCallbackCalled.incrementAndGet();
+          return attrs.toBuilder()
+              .set(key1, "ouch")
+              .set(key3, "puff")
+              .build();
+        }
+
+        @Override
+        public void transportTerminated(Attributes attrs) {
+          terminationCallbackCalled.incrementAndGet();
+          filter2TerminationCallbackArgument.set(attrs);
+        }
+      });
+    Attributes expectedTransportAttrs = Attributes.newBuilder()
+        .set(key1, "ouch")
+        .set(key2, "blabla")
+        .set(key3, "puff")
+        .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, remoteAddr)
+        .build();
+
+    createAndStartServer();
+    ServerTransportListener transportListener
+        = transportServer.registerNewServerTransport(new SimpleServerTransport());
+    Attributes transportAttrs = transportListener.transportReady(Attributes.newBuilder()
+        .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, remoteAddr).build());
+
+    assertEquals(expectedTransportAttrs, transportAttrs);
+
+    server.shutdown();
+    server.awaitTermination();
+
+    assertEquals(expectedTransportAttrs, filter1TerminationCallbackArgument.get());
+    assertEquals(expectedTransportAttrs, filter2TerminationCallbackArgument.get());
+    assertEquals(2, readyCallbackCalled.get());
+    assertEquals(2, terminationCallbackCalled.get());
+  }
+
+  @Test
+  public void interceptors() throws Exception {
+    final LinkedList<Context> capturedContexts = new LinkedList<Context>();
+    final Context.Key<String> key1 = Context.key("key1");
+    final Context.Key<String> key2 = Context.key("key2");
+    final Context.Key<String> key3 = Context.key("key3");
+    ServerInterceptor intercepter1 = new ServerInterceptor() {
+        @Override
+        public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+            ServerCall<ReqT, RespT> call,
+            Metadata headers,
+            ServerCallHandler<ReqT, RespT> next) {
+          Context ctx = Context.current().withValue(key1, "value1");
+          Context origCtx = ctx.attach();
+          try {
+            capturedContexts.add(ctx);
+            return next.startCall(call, headers);
+          } finally {
+            ctx.detach(origCtx);
+          }
+        }
+      };
+    ServerInterceptor intercepter2 = new ServerInterceptor() {
+        @Override
+        public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+            ServerCall<ReqT, RespT> call,
+            Metadata headers,
+            ServerCallHandler<ReqT, RespT> next) {
+          Context ctx = Context.current().withValue(key2, "value2");
+          Context origCtx = ctx.attach();
+          try {
+            capturedContexts.add(ctx);
+            return next.startCall(call, headers);
+          } finally {
+            ctx.detach(origCtx);
+          }
+        }
+      };
+    ServerCallHandler<String, Integer> callHandler = new ServerCallHandler<String, Integer>() {
+        @Override
+        public ServerCall.Listener<String> startCall(
+            ServerCall<String, Integer> call,
+            Metadata headers) {
+          capturedContexts.add(Context.current().withValue(key3, "value3"));
+          return callListener;
+        }
+      };
+
+    mutableFallbackRegistry.addService(
+        ServerServiceDefinition.builder(new ServiceDescriptor("Waiter", METHOD))
+            .addMethod(METHOD, callHandler).build());
+    builder.intercept(intercepter2);
+    builder.intercept(intercepter1);
+    createServer();
+    server.start();
+
+    ServerTransportListener transportListener
+        = transportServer.registerNewServerTransport(new SimpleServerTransport());
+    transportListener.transportReady(Attributes.EMPTY);
+
+    Metadata requestHeaders = new Metadata();
+    StatsTraceContext statsTraceCtx =
+        StatsTraceContext.newServerContext(streamTracerFactories, "Waiter/serve", requestHeaders);
+    when(stream.statsTraceContext()).thenReturn(statsTraceCtx);
+
+    transportListener.streamCreated(stream, "Waiter/serve", requestHeaders);
+    assertEquals(1, executor.runDueTasks());
+
+    Context ctx1 = capturedContexts.poll();
+    assertEquals("value1", key1.get(ctx1));
+    assertNull(key2.get(ctx1));
+    assertNull(key3.get(ctx1));
+
+    Context ctx2 = capturedContexts.poll();
+    assertEquals("value1", key1.get(ctx2));
+    assertEquals("value2", key2.get(ctx2));
+    assertNull(key3.get(ctx2));
+
+    Context ctx3 = capturedContexts.poll();
+    assertEquals("value1", key1.get(ctx3));
+    assertEquals("value2", key2.get(ctx3));
+    assertEquals("value3", key3.get(ctx3));
+
+    assertTrue(capturedContexts.isEmpty());
+  }
+
+  @Test
+  public void exceptionInStartCallPropagatesToStream() throws Exception {
+    createAndStartServer();
+    final Status status = Status.ABORTED.withDescription("Oh, no!");
+    mutableFallbackRegistry.addService(ServerServiceDefinition.builder(
+        new ServiceDescriptor("Waiter", METHOD))
+        .addMethod(METHOD,
+            new ServerCallHandler<String, Integer>() {
+              @Override
+              public ServerCall.Listener<String> startCall(
+                  ServerCall<String, Integer> call,
+                  Metadata headers) {
+                throw status.asRuntimeException();
+              }
+            }).build());
+    ServerTransportListener transportListener
+        = transportServer.registerNewServerTransport(new SimpleServerTransport());
+    transportListener.transportReady(Attributes.EMPTY);
+
+    Metadata requestHeaders = new Metadata();
+    StatsTraceContext statsTraceCtx =
+        StatsTraceContext.newServerContext(streamTracerFactories, "Waiter/serve", requestHeaders);
+    when(stream.statsTraceContext()).thenReturn(statsTraceCtx);
+
+    transportListener.streamCreated(stream, "Waiter/serve", requestHeaders);
+    verify(stream).setListener(streamListenerCaptor.capture());
+    ServerStreamListener streamListener = streamListenerCaptor.getValue();
+    assertNotNull(streamListener);
+    verify(stream, atLeast(1)).statsTraceContext();
+    verifyNoMoreInteractions(stream);
+    verify(fallbackRegistry, never()).lookupMethod(any(String.class), any(String.class));
+
+    assertEquals(1, executor.runDueTasks());
+    verify(fallbackRegistry).lookupMethod("Waiter/serve", AUTHORITY);
+    verify(stream).close(same(status), notNull(Metadata.class));
+    verify(stream, atLeast(1)).statsTraceContext();
+  }
+
+  @Test
+  public void testNoDeadlockOnShutdown() throws Exception {
+    final Object lock = new Object();
+    final CyclicBarrier barrier = new CyclicBarrier(2);
+    class MaybeDeadlockingServer extends SimpleServer {
+      @Override
+      public void shutdown() {
+        // To deadlock, a lock would need to be held while this method is in progress.
+        try {
+          barrier.await();
+        } catch (Exception ex) {
+          throw new AssertionError(ex);
+        }
+        // If deadlock is possible with this setup, this sychronization completes the loop because
+        // the serverShutdown needs a lock that Server is holding while calling this method.
+        synchronized (lock) {
+        }
+      }
+    }
+
+    transportServer = new MaybeDeadlockingServer();
+    createAndStartServer();
+    new Thread() {
+      @Override
+      public void run() {
+        synchronized (lock) {
+          try {
+            barrier.await();
+          } catch (Exception ex) {
+            throw new AssertionError(ex);
+          }
+          // To deadlock, a lock would be needed for this call to proceed.
+          transportServer.listener.serverShutdown();
+        }
+      }
+    }.start();
+    server.shutdown();
+  }
+
+  @Test
+  public void testNoDeadlockOnTransportShutdown() throws Exception {
+    createAndStartServer();
+    final Object lock = new Object();
+    final CyclicBarrier barrier = new CyclicBarrier(2);
+    class MaybeDeadlockingServerTransport extends SimpleServerTransport {
+      @Override
+      public void shutdown() {
+        // To deadlock, a lock would need to be held while this method is in progress.
+        try {
+          barrier.await();
+        } catch (Exception ex) {
+          throw new AssertionError(ex);
+        }
+        // If deadlock is possible with this setup, this sychronization completes the loop
+        // because the transportTerminated needs a lock that Server is holding while calling this
+        // method.
+        synchronized (lock) {
+        }
+      }
+    }
+
+    final ServerTransportListener transportListener
+        = transportServer.registerNewServerTransport(new MaybeDeadlockingServerTransport());
+    new Thread() {
+      @Override
+      public void run() {
+        synchronized (lock) {
+          try {
+            barrier.await();
+          } catch (Exception ex) {
+            throw new AssertionError(ex);
+          }
+          // To deadlock, a lock would be needed for this call to proceed.
+          transportListener.transportTerminated();
+        }
+      }
+    }.start();
+    server.shutdown();
+  }
+
+  @Test
+  public void testCallContextIsBoundInListenerCallbacks() throws Exception {
+    createAndStartServer();
+    final AtomicBoolean  onReadyCalled = new AtomicBoolean(false);
+    final AtomicBoolean onMessageCalled = new AtomicBoolean(false);
+    final AtomicBoolean onHalfCloseCalled = new AtomicBoolean(false);
+    final AtomicBoolean onCancelCalled = new AtomicBoolean(false);
+    mutableFallbackRegistry.addService(ServerServiceDefinition.builder(
+        new ServiceDescriptor("Waiter", METHOD))
+        .addMethod(
+            METHOD,
+            new ServerCallHandler<String, Integer>() {
+              @Override
+              public ServerCall.Listener<String> startCall(
+                  ServerCall<String, Integer> call,
+                  Metadata headers) {
+                // Check that the current context is a descendant of SERVER_CONTEXT
+                final Context initial = Context.current();
+                assertEquals("yes", SERVER_ONLY.get(initial));
+                assertNotSame(SERVER_CONTEXT, initial);
+                assertFalse(initial.isCancelled());
+                return new ServerCall.Listener<String>() {
+
+                  @Override
+                  public void onReady() {
+                    checkContext();
+                    onReadyCalled.set(true);
+                  }
+
+                  @Override
+                  public void onMessage(String message) {
+                    checkContext();
+                    onMessageCalled.set(true);
+                  }
+
+                  @Override
+                  public void onHalfClose() {
+                    checkContext();
+                    onHalfCloseCalled.set(true);
+                  }
+
+                  @Override
+                  public void onCancel() {
+                    checkContext();
+                    onCancelCalled.set(true);
+                  }
+
+                  @Override
+                  public void onComplete() {
+                    checkContext();
+                  }
+
+                  private void checkContext() {
+                    // Check that the bound context is the same as the initial one.
+                    assertSame(initial, Context.current());
+                  }
+                };
+              }
+            }).build());
+    ServerTransportListener transportListener
+        = transportServer.registerNewServerTransport(new SimpleServerTransport());
+    transportListener.transportReady(Attributes.EMPTY);
+
+    Metadata requestHeaders = new Metadata();
+    StatsTraceContext statsTraceCtx =
+        StatsTraceContext.newServerContext(streamTracerFactories, "Waitier/serve", requestHeaders);
+    when(stream.statsTraceContext()).thenReturn(statsTraceCtx);
+
+    transportListener.streamCreated(stream, "Waiter/serve", requestHeaders);
+    verify(stream).setListener(streamListenerCaptor.capture());
+    ServerStreamListener streamListener = streamListenerCaptor.getValue();
+    assertNotNull(streamListener);
+
+    streamListener.onReady();
+    assertEquals(1, executor.runDueTasks());
+    assertTrue(onReadyCalled.get());
+
+    streamListener
+        .messagesAvailable(new SingleMessageProducer(new ByteArrayInputStream(new byte[0])));
+    assertEquals(1, executor.runDueTasks());
+    assertTrue(onMessageCalled.get());
+
+    streamListener.halfClosed();
+    assertEquals(1, executor.runDueTasks());
+    assertTrue(onHalfCloseCalled.get());
+
+    streamListener.closed(Status.CANCELLED);
+    assertEquals(1, executor.numPendingTasks(CONTEXT_CLOSER_TASK_FITLER));
+    assertEquals(2, executor.runDueTasks());
+    assertTrue(onCancelCalled.get());
+
+    // Close should never be called if asserts in listener pass.
+    verify(stream, times(0)).close(isA(Status.class), isNotNull(Metadata.class));
+  }
+
+  private ServerStreamListener testClientClose_setup(
+      final AtomicReference<ServerCall<String, Integer>> callReference,
+      final AtomicReference<Context> context,
+      final AtomicBoolean contextCancelled) throws Exception {
+    createAndStartServer();
+    callListener = new ServerCall.Listener<String>() {
+      @Override
+      public void onReady() {
+        context.set(Context.current());
+        Context.current().addListener(new Context.CancellationListener() {
+          @Override
+          public void cancelled(Context context) {
+            contextCancelled.set(true);
+          }
+        }, MoreExecutors.directExecutor());
+      }
+    };
+
+    mutableFallbackRegistry.addService(ServerServiceDefinition.builder(
+        new ServiceDescriptor("Waiter", METHOD))
+        .addMethod(METHOD,
+            new ServerCallHandler<String, Integer>() {
+              @Override
+              public ServerCall.Listener<String> startCall(
+                  ServerCall<String, Integer> call,
+                  Metadata headers) {
+                callReference.set(call);
+                return callListener;
+              }
+            }).build());
+    ServerTransportListener transportListener
+        = transportServer.registerNewServerTransport(new SimpleServerTransport());
+    transportListener.transportReady(Attributes.EMPTY);
+    Metadata requestHeaders = new Metadata();
+    StatsTraceContext statsTraceCtx =
+        StatsTraceContext.newServerContext(streamTracerFactories, "Waitier/serve", requestHeaders);
+    when(stream.statsTraceContext()).thenReturn(statsTraceCtx);
+    transportListener.streamCreated(stream, "Waiter/serve", requestHeaders);
+    verify(stream).setListener(streamListenerCaptor.capture());
+    ServerStreamListener streamListener = streamListenerCaptor.getValue();
+    assertNotNull(streamListener);
+
+    streamListener.onReady();
+    assertEquals(1, executor.runDueTasks());
+    return streamListener;
+  }
+
+  @Test
+  public void testClientClose_cancelTriggersImmediateCancellation() throws Exception {
+    AtomicBoolean contextCancelled = new AtomicBoolean(false);
+    AtomicReference<Context> context = new AtomicReference<Context>();
+    AtomicReference<ServerCall<String, Integer>> callReference
+        = new AtomicReference<ServerCall<String, Integer>>();
+
+    ServerStreamListener streamListener = testClientClose_setup(callReference,
+        context, contextCancelled);
+
+    // For close status being non OK:
+    // isCancelled is expected to be true immediately after calling closed(), without needing
+    // to wait for the main executor to run any tasks.
+    assertFalse(callReference.get().isCancelled());
+    assertFalse(context.get().isCancelled());
+    streamListener.closed(Status.CANCELLED);
+    assertEquals(1, executor.numPendingTasks(CONTEXT_CLOSER_TASK_FITLER));
+    assertEquals(2, executor.runDueTasks());
+    assertTrue(callReference.get().isCancelled());
+    assertTrue(context.get().isCancelled());
+    assertTrue(contextCancelled.get());
+  }
+
+  @Test
+  public void testClientClose_OkTriggersDelayedCancellation() throws Exception {
+    AtomicBoolean contextCancelled = new AtomicBoolean(false);
+    AtomicReference<Context> context = new AtomicReference<Context>();
+    AtomicReference<ServerCall<String, Integer>> callReference
+        = new AtomicReference<ServerCall<String, Integer>>();
+
+    ServerStreamListener streamListener = testClientClose_setup(callReference,
+        context, contextCancelled);
+
+    // For close status OK:
+    // isCancelled is expected to be true after all pending work is done
+    assertFalse(callReference.get().isCancelled());
+    assertFalse(context.get().isCancelled());
+    streamListener.closed(Status.OK);
+    assertFalse(callReference.get().isCancelled());
+    assertFalse(context.get().isCancelled());
+
+    assertEquals(1, executor.runDueTasks());
+    assertTrue(callReference.get().isCancelled());
+    assertTrue(context.get().isCancelled());
+    assertTrue(contextCancelled.get());
+  }
+
+  @Test
+  public void getPort() throws Exception {
+    transportServer = new SimpleServer() {
+      @Override
+      public int getPort() {
+        return 65535;
+      }
+    };
+    createAndStartServer();
+
+    assertThat(server.getPort()).isEqualTo(65535);
+  }
+
+  @Test
+  public void getPortBeforeStartedFails() {
+    transportServer = new SimpleServer();
+    createServer();
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("started");
+    server.getPort();
+  }
+
+  @Test
+  public void getPortAfterTerminationFails() throws Exception {
+    transportServer = new SimpleServer();
+    createAndStartServer();
+    server.shutdown();
+    server.awaitTermination();
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("terminated");
+    server.getPort();
+  }
+
+  @Test
+  public void handlerRegistryPriorities() throws Exception {
+    fallbackRegistry = mock(HandlerRegistry.class);
+    builder.addService(
+        ServerServiceDefinition.builder(new ServiceDescriptor("Waiter", METHOD))
+            .addMethod(METHOD, callHandler).build());
+    transportServer = new SimpleServer();
+    createAndStartServer();
+
+    ServerTransportListener transportListener
+        = transportServer.registerNewServerTransport(new SimpleServerTransport());
+    transportListener.transportReady(Attributes.EMPTY);
+    Metadata requestHeaders = new Metadata();
+    StatsTraceContext statsTraceCtx =
+        StatsTraceContext.newServerContext(streamTracerFactories, "Waiter/serve", requestHeaders);
+    when(stream.statsTraceContext()).thenReturn(statsTraceCtx);
+
+    // This call will be handled by callHandler from the internal registry
+    transportListener.streamCreated(stream, "Waiter/serve", requestHeaders);
+    assertEquals(1, executor.runDueTasks());
+    verify(callHandler).startCall(Matchers.<ServerCall<String, Integer>>anyObject(),
+        Matchers.<Metadata>anyObject());
+    // This call will be handled by the fallbackRegistry because it's not registred in the internal
+    // registry.
+    transportListener.streamCreated(stream, "Service1/Method2", requestHeaders);
+    assertEquals(1, executor.runDueTasks());
+    verify(fallbackRegistry).lookupMethod("Service1/Method2", AUTHORITY);
+
+    verifyNoMoreInteractions(callHandler);
+    verifyNoMoreInteractions(fallbackRegistry);
+  }
+
+  @Test
+  public void messageRead_errorCancelsCall() throws Exception {
+    JumpToApplicationThreadServerStreamListener listener
+        = new JumpToApplicationThreadServerStreamListener(
+            executor.getScheduledExecutorService(),
+            executor.getScheduledExecutorService(),
+            stream,
+            Context.ROOT.withCancellation());
+    ServerStreamListener mockListener = mock(ServerStreamListener.class);
+    listener.setListener(mockListener);
+
+    TestError expectedT = new TestError();
+    doThrow(expectedT).when(mockListener)
+        .messagesAvailable(any(StreamListener.MessageProducer.class));
+    // Closing the InputStream is done by the delegated listener (generally ServerCallImpl)
+    listener.messagesAvailable(mock(StreamListener.MessageProducer.class));
+    try {
+      executor.runDueTasks();
+      fail("Expected exception");
+    } catch (TestError t) {
+      assertSame(expectedT, t);
+      ensureServerStateNotLeaked();
+    }
+  }
+
+  @Test
+  public void messageRead_runtimeExceptionCancelsCall() throws Exception {
+    JumpToApplicationThreadServerStreamListener listener
+        = new JumpToApplicationThreadServerStreamListener(
+            executor.getScheduledExecutorService(),
+            executor.getScheduledExecutorService(),
+            stream,
+            Context.ROOT.withCancellation());
+    ServerStreamListener mockListener = mock(ServerStreamListener.class);
+    listener.setListener(mockListener);
+
+    RuntimeException expectedT = new RuntimeException();
+    doThrow(expectedT).when(mockListener)
+        .messagesAvailable(any(StreamListener.MessageProducer.class));
+    // Closing the InputStream is done by the delegated listener (generally ServerCallImpl)
+    listener.messagesAvailable(mock(StreamListener.MessageProducer.class));
+    try {
+      executor.runDueTasks();
+      fail("Expected exception");
+    } catch (RuntimeException t) {
+      assertSame(expectedT, t);
+      ensureServerStateNotLeaked();
+    }
+  }
+
+  @Test
+  public void halfClosed_errorCancelsCall() {
+    JumpToApplicationThreadServerStreamListener listener
+        = new JumpToApplicationThreadServerStreamListener(
+            executor.getScheduledExecutorService(),
+            executor.getScheduledExecutorService(),
+            stream,
+            Context.ROOT.withCancellation());
+    ServerStreamListener mockListener = mock(ServerStreamListener.class);
+    listener.setListener(mockListener);
+
+    TestError expectedT = new TestError();
+    doThrow(expectedT).when(mockListener).halfClosed();
+    listener.halfClosed();
+    try {
+      executor.runDueTasks();
+      fail("Expected exception");
+    } catch (TestError t) {
+      assertSame(expectedT, t);
+      ensureServerStateNotLeaked();
+    }
+  }
+
+  @Test
+  public void halfClosed_runtimeExceptionCancelsCall() {
+    JumpToApplicationThreadServerStreamListener listener
+        = new JumpToApplicationThreadServerStreamListener(
+            executor.getScheduledExecutorService(),
+            executor.getScheduledExecutorService(),
+            stream,
+            Context.ROOT.withCancellation());
+    ServerStreamListener mockListener = mock(ServerStreamListener.class);
+    listener.setListener(mockListener);
+
+    RuntimeException expectedT = new RuntimeException();
+    doThrow(expectedT).when(mockListener).halfClosed();
+    listener.halfClosed();
+    try {
+      executor.runDueTasks();
+      fail("Expected exception");
+    } catch (RuntimeException t) {
+      assertSame(expectedT, t);
+      ensureServerStateNotLeaked();
+    }
+  }
+
+  @Test
+  public void onReady_errorCancelsCall() {
+    JumpToApplicationThreadServerStreamListener listener
+        = new JumpToApplicationThreadServerStreamListener(
+            executor.getScheduledExecutorService(),
+            executor.getScheduledExecutorService(),
+            stream,
+            Context.ROOT.withCancellation());
+    ServerStreamListener mockListener = mock(ServerStreamListener.class);
+    listener.setListener(mockListener);
+
+    TestError expectedT = new TestError();
+    doThrow(expectedT).when(mockListener).onReady();
+    listener.onReady();
+    try {
+      executor.runDueTasks();
+      fail("Expected exception");
+    } catch (TestError t) {
+      assertSame(expectedT, t);
+      ensureServerStateNotLeaked();
+    }
+  }
+
+  @Test
+  public void onReady_runtimeExceptionCancelsCall() {
+    JumpToApplicationThreadServerStreamListener listener
+        = new JumpToApplicationThreadServerStreamListener(
+            executor.getScheduledExecutorService(),
+            executor.getScheduledExecutorService(),
+            stream,
+            Context.ROOT.withCancellation());
+    ServerStreamListener mockListener = mock(ServerStreamListener.class);
+    listener.setListener(mockListener);
+
+    RuntimeException expectedT = new RuntimeException();
+    doThrow(expectedT).when(mockListener).onReady();
+    listener.onReady();
+    try {
+      executor.runDueTasks();
+      fail("Expected exception");
+    } catch (RuntimeException t) {
+      assertSame(expectedT, t);
+      ensureServerStateNotLeaked();
+    }
+  }
+
+  @Test
+  public void binaryLogInstalled() throws Exception {
+    final SettableFuture<Boolean> intercepted = SettableFuture.create();
+    final ServerInterceptor interceptor = new ServerInterceptor() {
+      @Override
+      public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
+          Metadata headers,
+          ServerCallHandler<ReqT, RespT> next) {
+        intercepted.set(true);
+        return next.startCall(call, headers);
+      }
+    };
+
+    builder.binlog = new BinaryLog() {
+      @Override
+      public void close() throws IOException {
+        // noop
+      }
+
+      @Override
+      public <ReqT, RespT> ServerMethodDefinition<?, ?> wrapMethodDefinition(
+          ServerMethodDefinition<ReqT, RespT> oMethodDef) {
+        return ServerMethodDefinition.create(
+            oMethodDef.getMethodDescriptor(),
+            InternalServerInterceptors.interceptCallHandlerCreate(
+                interceptor,
+                oMethodDef.getServerCallHandler()));
+      }
+
+      @Override
+      public Channel wrapChannel(Channel channel) {
+        return channel;
+      }
+    };
+    createAndStartServer();
+    basicExchangeHelper(METHOD, "Lots of pizza, please", 314, 50);
+    assertTrue(intercepted.get());
+  }
+
+  @Test
+  public void channelz_membership() throws Exception {
+    createServer();
+    assertTrue(builder.channelz.containsServer(server.getLogId()));
+    server.shutdownNow().awaitTermination();
+    assertFalse(builder.channelz.containsServer(server.getLogId()));
+  }
+
+  @Test
+  public void channelz_serverStats() throws Exception {
+    createAndStartServer();
+    assertEquals(0, server.getStats().get().callsSucceeded);
+    basicExchangeHelper(METHOD, "Lots of pizza, please", 314, null);
+    assertEquals(1, server.getStats().get().callsSucceeded);
+  }
+
+  @Test
+  public void channelz_transport_membershp() throws Exception {
+    createAndStartServer();
+    SimpleServerTransport transport = new SimpleServerTransport();
+
+    ServerSocketsList before = builder.channelz
+        .getServerSockets(id(server), id(transport), /*maxPageSize=*/ 1);
+    assertThat(before.sockets).isEmpty();
+    assertTrue(before.end);
+
+    ServerTransportListener listener
+        = transportServer.registerNewServerTransport(transport);
+    ServerSocketsList added = builder.channelz
+        .getServerSockets(id(server), id(transport), /*maxPageSize=*/ 1);
+    assertThat(added.sockets).containsExactly(transport);
+    assertTrue(before.end);
+
+    listener.transportTerminated();
+    ServerSocketsList after = builder.channelz
+        .getServerSockets(id(server), id(transport), /*maxPageSize=*/ 1);
+    assertThat(after.sockets).isEmpty();
+    assertTrue(after.end);
+  }
+
+  private void createAndStartServer() throws IOException {
+    createServer();
+    server.start();
+  }
+
+  private void createServer() {
+    assertNull(server);
+
+    builder.fallbackHandlerRegistry(fallbackRegistry);
+    builder.executorPool = executorPool;
+    server = new ServerImpl(builder, transportServer, SERVER_CONTEXT);
+  }
+
+  private void verifyExecutorsAcquired() {
+    verify(executorPool).getObject();
+    verifyNoMoreInteractions(executorPool);
+  }
+
+  private void verifyExecutorsNotReturned() {
+    verify(executorPool, never()).returnObject(any(Executor.class));
+  }
+
+  private void verifyExecutorsReturned() {
+    verify(executorPool).returnObject(same(executor.getScheduledExecutorService()));
+    verifyNoMoreInteractions(executorPool);
+  }
+
+  private void ensureServerStateNotLeaked() {
+    verify(stream).close(statusCaptor.capture(), metadataCaptor.capture());
+    assertEquals(Status.UNKNOWN, statusCaptor.getValue());
+    assertNull(statusCaptor.getValue().getCause());
+    assertTrue(metadataCaptor.getValue().keys().isEmpty());
+  }
+
+  private static class SimpleServer implements io.grpc.internal.InternalServer {
+    ServerListener listener;
+
+    @Override
+    public void start(ServerListener listener) throws IOException {
+      this.listener = listener;
+    }
+
+    @Override
+    public int getPort() {
+      return -1;
+    }
+
+    @Override
+    public List<InternalInstrumented<SocketStats>> getListenSockets() {
+      return Collections.emptyList();
+    }
+
+    @Override
+    public void shutdown() {
+      listener.serverShutdown();
+    }
+
+    public ServerTransportListener registerNewServerTransport(SimpleServerTransport transport) {
+      return transport.listener = listener.transportCreated(transport);
+    }
+  }
+
+  private class SimpleServerTransport implements ServerTransport {
+    ServerTransportListener listener;
+    InternalLogId id = InternalLogId.allocate(getClass().getName());
+
+    @Override
+    public void shutdown() {
+      listener.transportTerminated();
+    }
+
+    @Override
+    public void shutdownNow(Status status) {
+      listener.transportTerminated();
+    }
+
+    @Override
+    public InternalLogId getLogId() {
+      return id;
+    }
+
+    @Override
+    public ScheduledExecutorService getScheduledExecutorService() {
+      return timer.getScheduledExecutorService();
+    }
+
+    @Override
+    public ListenableFuture<SocketStats> getStats() {
+      SettableFuture<SocketStats> ret = SettableFuture.create();
+      ret.set(null);
+      return ret;
+    }
+  }
+
+  private static class Builder extends AbstractServerImplBuilder<Builder> {
+    @Override protected InternalServer buildTransportServer(
+        List<ServerStreamTracer.Factory> streamTracerFactories) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override public Builder useTransportSecurity(File f1, File f2)  {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  /** Allows more precise catch blocks than plain Error to avoid catching AssertionError. */
+  private static final class TestError extends Error {}
+}
diff --git a/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java b/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java
new file mode 100644
index 0000000..4d06df1
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/ServiceConfigInterceptorTest.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.internal.ServiceConfigInterceptor.HEDGING_POLICY_KEY;
+import static io.grpc.internal.ServiceConfigInterceptor.RETRY_POLICY_KEY;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.Deadline;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.internal.ServiceConfigInterceptor.MethodInfo;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link ServiceConfigInterceptor}.
+ */
+@RunWith(JUnit4.class)
+public class ServiceConfigInterceptorTest {
+
+  @Rule public final ExpectedException thrown = ExpectedException.none();
+
+  @Mock private Channel channel;
+  @Captor private ArgumentCaptor<CallOptions> callOptionsCap;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+  }
+
+  private final ServiceConfigInterceptor interceptor = new ServiceConfigInterceptor(
+      /* retryEnabled = */ true, /* maxRetryAttemptsLimit = */ 5, /* maxHedgedAttemptsLimit = */ 6);
+
+  private final String fullMethodName =
+      MethodDescriptor.generateFullMethodName("service", "method");
+  private final MethodDescriptor<Void, Void> methodDescriptor =
+      MethodDescriptor.newBuilder(new NoopMarshaller(), new NoopMarshaller())
+          .setType(MethodType.UNARY)
+          .setFullMethodName(fullMethodName)
+          .build();
+
+
+
+  private static final class JsonObj extends HashMap<String, Object> {
+    private JsonObj(Object ... kv) {
+      for (int i = 0; i < kv.length; i += 2) {
+        put((String) kv[i], kv[i + 1]);
+      }
+    }
+  }
+
+  private static final class JsonList extends ArrayList<Object> {
+    private JsonList(Object ... values) {
+      addAll(Arrays.asList(values));
+    }
+  }
+
+  @Test
+  public void withWaitForReady() {
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", true);
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    interceptor.handleUpdate(serviceConfig);
+
+    interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withoutWaitForReady(), channel);
+
+    verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
+    assertThat(callOptionsCap.getValue().isWaitForReady()).isTrue();
+  }
+
+  @Test
+  public void handleUpdateNotCalledBeforeInterceptCall() {
+    interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withoutWaitForReady(), channel);
+
+    verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
+    assertThat(callOptionsCap.getValue().isWaitForReady()).isFalse();
+    assertThat(callOptionsCap.getValue().getOption(RETRY_POLICY_KEY).get())
+        .isEqualTo(RetryPolicy.DEFAULT);
+    assertThat(callOptionsCap.getValue().getOption(HEDGING_POLICY_KEY).get())
+        .isEqualTo(HedgingPolicy.DEFAULT);
+  }
+
+  @Test
+  public void withMaxRequestSize() {
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 1d);
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    interceptor.handleUpdate(serviceConfig);
+
+    interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel);
+
+    verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
+    assertThat(callOptionsCap.getValue().getMaxOutboundMessageSize()).isEqualTo(1);
+  }
+
+  @Test
+  public void withMaxRequestSize_pickSmallerExisting() {
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 10d);
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    interceptor.handleUpdate(serviceConfig);
+
+    interceptor.interceptCall(
+        methodDescriptor, CallOptions.DEFAULT.withMaxOutboundMessageSize(5), channel);
+
+    verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
+    assertThat(callOptionsCap.getValue().getMaxOutboundMessageSize()).isEqualTo(5);
+  }
+
+  @Test
+  public void withMaxRequestSize_pickSmallerNew() {
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", 5d);
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    interceptor.handleUpdate(serviceConfig);
+
+    interceptor.interceptCall(
+        methodDescriptor, CallOptions.DEFAULT.withMaxOutboundMessageSize(10), channel);
+
+    verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
+    assertThat(callOptionsCap.getValue().getMaxOutboundMessageSize()).isEqualTo(5);
+  }
+
+  @Test
+  public void withMaxResponseSize() {
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 1d);
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    interceptor.handleUpdate(serviceConfig);
+
+    interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel);
+
+    verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
+    assertThat(callOptionsCap.getValue().getMaxInboundMessageSize()).isEqualTo(1);
+  }
+
+  @Test
+  public void withMaxResponseSize_pickSmallerExisting() {
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 5d);
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    interceptor.handleUpdate(serviceConfig);
+
+    interceptor.interceptCall(
+        methodDescriptor, CallOptions.DEFAULT.withMaxInboundMessageSize(10), channel);
+
+    verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
+    assertThat(callOptionsCap.getValue().getMaxInboundMessageSize()).isEqualTo(5);
+  }
+
+  @Test
+  public void withMaxResponseSize_pickSmallerNew() {
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", 10d);
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    interceptor.handleUpdate(serviceConfig);
+
+    interceptor.interceptCall(
+        methodDescriptor, CallOptions.DEFAULT.withMaxInboundMessageSize(5), channel);
+
+    verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
+    assertThat(callOptionsCap.getValue().getMaxInboundMessageSize()).isEqualTo(5);
+  }
+
+  @Test
+  public void withoutWaitForReady() {
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "waitForReady", false);
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    interceptor.handleUpdate(serviceConfig);
+
+    interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT.withWaitForReady(), channel);
+
+    verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
+    assertThat(callOptionsCap.getValue().isWaitForReady()).isFalse();
+  }
+
+  @Test
+  public void fullMethodMatched() {
+    // Put in service that matches, but has no deadline.  It should be lower priority
+    JsonObj name1 = new JsonObj("service", "service");
+    JsonObj methodConfig1 = new JsonObj("name", new JsonList(name1));
+
+    JsonObj name2 = new JsonObj("service", "service", "method", "method");
+    JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2), "timeout", "1s");
+
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig1, methodConfig2));
+
+    interceptor.handleUpdate(serviceConfig);
+
+    interceptor.interceptCall(methodDescriptor, CallOptions.DEFAULT, channel);
+
+    verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
+    assertThat(callOptionsCap.getValue().getDeadline()).isNotNull();
+  }
+
+  @Test
+  public void nearerDeadlineKept_existing() {
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "100000s");
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    interceptor.handleUpdate(serviceConfig);
+
+    Deadline existingDeadline = Deadline.after(1000, TimeUnit.NANOSECONDS);
+    interceptor.interceptCall(
+        methodDescriptor, CallOptions.DEFAULT.withDeadline(existingDeadline), channel);
+
+    verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
+    assertThat(callOptionsCap.getValue().getDeadline()).isEqualTo(existingDeadline);
+  }
+
+  @Test
+  public void nearerDeadlineKept_new() {
+    // TODO(carl-mastrangelo): the deadlines are very large because they change over time.
+    // This should be fixed, and is tracked in https://github.com/grpc/grpc-java/issues/2531
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "1s");
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    interceptor.handleUpdate(serviceConfig);
+
+    Deadline existingDeadline = Deadline.after(1234567890, TimeUnit.NANOSECONDS);
+    interceptor.interceptCall(
+        methodDescriptor, CallOptions.DEFAULT.withDeadline(existingDeadline), channel);
+
+    verify(channel).newCall(eq(methodDescriptor), callOptionsCap.capture());
+    assertThat(callOptionsCap.getValue().getDeadline()).isNotEqualTo(existingDeadline);
+  }
+
+
+  @Test
+  public void handleUpdate_failsOnMissingServiceName() {
+    JsonObj name = new JsonObj("method", "method");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name));
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("missing service");
+
+    interceptor.handleUpdate(serviceConfig);
+  }
+
+
+  @Test
+  public void handleUpdate_failsOnDuplicateMethod() {
+    JsonObj name1 = new JsonObj("service", "service", "method", "method");
+    JsonObj name2 = new JsonObj("service", "service", "method", "method");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2));
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Duplicate method");
+
+    interceptor.handleUpdate(serviceConfig);
+  }
+
+  @Test
+  public void handleUpdate_failsOnEmptyName() {
+    JsonObj methodConfig = new JsonObj();
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("no names in method config");
+
+    interceptor.handleUpdate(serviceConfig);
+  }
+
+  @Test
+  public void handleUpdate_failsOnDuplicateService() {
+    JsonObj name1 = new JsonObj("service", "service");
+    JsonObj name2 = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2));
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Duplicate service");
+
+    interceptor.handleUpdate(serviceConfig);
+  }
+
+  @Test
+  public void handleUpdate_failsOnDuplicateServiceMultipleConfig() {
+    JsonObj name1 = new JsonObj("service", "service");
+    JsonObj name2 = new JsonObj("service", "service");
+    JsonObj methodConfig1 = new JsonObj("name", new JsonList(name1));
+    JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2));
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig1, methodConfig2));
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Duplicate service");
+
+    interceptor.handleUpdate(serviceConfig);
+  }
+
+  @Test
+  public void handleUpdate_replaceExistingConfig() {
+    JsonObj name1 = new JsonObj("service", "service");
+    JsonObj methodConfig1 = new JsonObj("name", new JsonList(name1));
+    JsonObj serviceConfig1 = new JsonObj("methodConfig", new JsonList(methodConfig1));
+
+    JsonObj name2 = new JsonObj("service", "service", "method", "method");
+    JsonObj methodConfig2 = new JsonObj("name", new JsonList(name2));
+    JsonObj serviceConfig2 = new JsonObj("methodConfig", new JsonList(methodConfig2));
+
+    interceptor.handleUpdate(serviceConfig1);
+
+    assertThat(interceptor.serviceMap.get()).isNotEmpty();
+    assertThat(interceptor.serviceMethodMap.get()).isEmpty();
+
+    interceptor.handleUpdate(serviceConfig2);
+
+    assertThat(interceptor.serviceMap.get()).isEmpty();
+    assertThat(interceptor.serviceMethodMap.get()).isNotEmpty();
+  }
+
+  @Test
+  public void handleUpdate_matchNames() {
+    JsonObj name1 = new JsonObj("service", "service2");
+    JsonObj name2 = new JsonObj("service", "service", "method", "method");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name1, name2));
+    JsonObj serviceConfig = new JsonObj("methodConfig", new JsonList(methodConfig));
+
+    interceptor.handleUpdate(serviceConfig);
+
+    assertThat(interceptor.serviceMethodMap.get())
+        .containsExactly(
+            methodDescriptor.getFullMethodName(),
+            new MethodInfo(methodConfig, false, 1, 1));
+    assertThat(interceptor.serviceMap.get()).containsExactly(
+        "service2", new MethodInfo(methodConfig, false, 1, 1));
+  }
+
+
+  @Test
+  public void methodInfo_validateDeadline() {
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "10000000000000000s");
+
+    thrown.expectMessage("Duration value is out of range");
+
+    new MethodInfo(methodConfig, false, 1, 1);
+  }
+
+  @Test
+  public void methodInfo_saturateDeadline() {
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "timeout", "315576000000s");
+
+    MethodInfo info = new MethodInfo(methodConfig, false, 1, 1);
+
+    assertThat(info.timeoutNanos).isEqualTo(Long.MAX_VALUE);
+  }
+
+
+  @Test
+  public void methodInfo_badMaxRequestSize() {
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxRequestMessageBytes", -1d);
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("exceeds bounds");
+
+    new MethodInfo(methodConfig, false, 1, 1);
+  }
+
+  @Test
+  public void methodInfo_badMaxResponseSize() {
+    JsonObj name = new JsonObj("service", "service");
+    JsonObj methodConfig = new JsonObj("name", new JsonList(name), "maxResponseMessageBytes", -1d);
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("exceeds bounds");
+
+    new MethodInfo(methodConfig, false, 1, 1);
+  }
+
+  private static final class NoopMarshaller implements MethodDescriptor.Marshaller<Void> {
+
+    @Override
+    public InputStream stream(Void value) {
+      return null;
+    }
+
+    @Override
+    public Void parse(InputStream stream) {
+      return null;
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/SharedResourceHolderTest.java b/core/src/test/java/io/grpc/internal/SharedResourceHolderTest.java
new file mode 100644
index 0000000..3b65d08
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/SharedResourceHolderTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import io.grpc.internal.SharedResourceHolder.Resource;
+import java.util.LinkedList;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/** Unit tests for {@link SharedResourceHolder}. */
+@RunWith(JUnit4.class)
+public class SharedResourceHolderTest {
+
+  private final LinkedList<MockScheduledFuture<?>> scheduledDestroyTasks =
+      new LinkedList<MockScheduledFuture<?>>();
+
+  private SharedResourceHolder holder;
+
+  private static class ResourceInstance {
+    volatile boolean closed;
+  }
+
+  private static class ResourceFactory implements Resource<ResourceInstance> {
+    @Override
+    public ResourceInstance create() {
+      return new ResourceInstance();
+    }
+
+    @Override
+    public void close(ResourceInstance instance) {
+      instance.closed = true;
+    }
+  }
+
+  // Defines two kinds of resources
+  private static final Resource<ResourceInstance> SHARED_FOO = new ResourceFactory();
+  private static final Resource<ResourceInstance> SHARED_BAR = new ResourceFactory();
+
+  @Before public void setUp() {
+    holder = new SharedResourceHolder(new MockExecutorFactory());
+  }
+
+  @Test public void destroyResourceWhenRefCountReachesZero() {
+    ResourceInstance foo1 = holder.getInternal(SHARED_FOO);
+    ResourceInstance sharedFoo = foo1;
+    ResourceInstance foo2 = holder.getInternal(SHARED_FOO);
+    assertSame(sharedFoo, foo2);
+
+    ResourceInstance bar1 = holder.getInternal(SHARED_BAR);
+    ResourceInstance sharedBar = bar1;
+
+    foo2 = holder.releaseInternal(SHARED_FOO, foo2);
+    // foo refcount not reached 0, thus shared foo is not closed
+    assertTrue(scheduledDestroyTasks.isEmpty());
+    assertFalse(sharedFoo.closed);
+    assertNull(foo2);
+
+    foo1 = holder.releaseInternal(SHARED_FOO, foo1);
+    assertNull(foo1);
+
+    // foo refcount has reached 0, a destroying task is scheduled
+    assertEquals(1, scheduledDestroyTasks.size());
+    MockScheduledFuture<?> scheduledDestroyTask = scheduledDestroyTasks.poll();
+    assertEquals(SharedResourceHolder.DESTROY_DELAY_SECONDS,
+        scheduledDestroyTask.getDelay(TimeUnit.SECONDS));
+
+    // Simluate that the destroyer executes the foo destroying task
+    scheduledDestroyTask.runTask();
+    assertTrue(sharedFoo.closed);
+
+    // After the destroying, obtaining a foo will get a different instance
+    ResourceInstance foo3 = holder.getInternal(SHARED_FOO);
+    assertNotSame(sharedFoo, foo3);
+
+    bar1 = holder.releaseInternal(SHARED_BAR, bar1);
+
+    // bar refcount has reached 0, a destroying task is scheduled
+    assertEquals(1, scheduledDestroyTasks.size());
+    scheduledDestroyTask = scheduledDestroyTasks.poll();
+    assertEquals(SharedResourceHolder.DESTROY_DELAY_SECONDS,
+        scheduledDestroyTask.getDelay(TimeUnit.SECONDS));
+
+    // Simulate that the destroyer executes the bar destroying task
+    scheduledDestroyTask.runTask();
+    assertTrue(sharedBar.closed);
+  }
+
+  @Test public void cancelDestroyTask() {
+    ResourceInstance foo1 = holder.getInternal(SHARED_FOO);
+    ResourceInstance sharedFoo = foo1;
+    foo1 = holder.releaseInternal(SHARED_FOO, foo1);
+    // A destroying task for foo is scheduled
+    MockScheduledFuture<?> scheduledDestroyTask = scheduledDestroyTasks.poll();
+    assertFalse(scheduledDestroyTask.cancelled);
+
+    // obtaining a foo before the destroying task is executed will cancel the destroy
+    ResourceInstance foo2 = holder.getInternal(SHARED_FOO);
+    assertTrue(scheduledDestroyTask.cancelled);
+    assertTrue(scheduledDestroyTasks.isEmpty());
+    assertFalse(sharedFoo.closed);
+
+    // And it will be the same foo instance
+    assertSame(sharedFoo, foo2);
+
+    // Release it and the destroying task is scheduled again
+    foo2 = holder.releaseInternal(SHARED_FOO, foo2);
+    scheduledDestroyTask = scheduledDestroyTasks.poll();
+    assertFalse(scheduledDestroyTask.cancelled);
+    scheduledDestroyTask.runTask();
+    assertTrue(sharedFoo.closed);
+  }
+
+  @Test public void releaseWrongInstance() {
+    ResourceInstance uncached = new ResourceInstance();
+    try {
+      holder.releaseInternal(SHARED_FOO, uncached);
+      fail("Should throw IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+    ResourceInstance cached = holder.getInternal(SHARED_FOO);
+    try {
+      holder.releaseInternal(SHARED_FOO, uncached);
+      fail("Should throw IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+    holder.releaseInternal(SHARED_FOO, cached);
+  }
+
+  @Test public void overreleaseInstance() {
+    ResourceInstance foo1 = holder.getInternal(SHARED_FOO);
+    holder.releaseInternal(SHARED_FOO, foo1);
+    try {
+      holder.releaseInternal(SHARED_FOO, foo1);
+      fail("Should throw IllegalStateException");
+    } catch (IllegalStateException e) {
+      // expected
+    }
+  }
+
+  private class MockExecutorFactory implements
+      SharedResourceHolder.ScheduledExecutorFactory {
+    @Override
+    public ScheduledExecutorService createScheduledExecutor() {
+      ScheduledExecutorService mockExecutor = mock(ScheduledExecutorService.class);
+      when(mockExecutor.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class))).thenAnswer(
+          new Answer<MockScheduledFuture<Void>>() {
+            @Override
+            public MockScheduledFuture<Void> answer(InvocationOnMock invocation) {
+              Object[] args = invocation.getArguments();
+              Runnable command = (Runnable) args[0];
+              long delay = (Long) args[1];
+              TimeUnit unit = (TimeUnit) args[2];
+              MockScheduledFuture<Void> future = new MockScheduledFuture<Void>(
+                  command, delay, unit);
+              scheduledDestroyTasks.add(future);
+              return future;
+            }
+          });
+      return mockExecutor;
+    }
+  }
+
+  private static class MockScheduledFuture<V> implements ScheduledFuture<V> {
+    private boolean cancelled;
+    private boolean finished;
+    final Runnable command;
+    final long delay;
+    final TimeUnit unit;
+
+    MockScheduledFuture(Runnable command, long delay, TimeUnit unit) {
+      this.command = command;
+      this.delay = delay;
+      this.unit = unit;
+    }
+
+    void runTask() {
+      command.run();
+      finished = true;
+    }
+
+    @Override
+    public boolean cancel(boolean interrupt) {
+      if (cancelled || finished) {
+        return false;
+      }
+      cancelled = true;
+      return true;
+    }
+
+    @Override
+    public boolean isCancelled() {
+      return cancelled;
+    }
+
+    @Override
+    public long getDelay(TimeUnit targetUnit) {
+      return targetUnit.convert(this.delay, this.unit);
+    }
+
+    @Override
+    public int compareTo(Delayed o) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean isDone() {
+      return cancelled || finished;
+    }
+
+    @Override
+    public V get() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public V get(long timeout, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/TestUtils.java b/core/src/test/java/io/grpc/internal/TestUtils.java
new file mode 100644
index 0000000..e63dc17
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/TestUtils.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import io.grpc.CallOptions;
+import io.grpc.InternalLogId;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import java.net.SocketAddress;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import javax.annotation.Nullable;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Common utility methods for tests.
+ */
+final class TestUtils {
+
+  static class MockClientTransportInfo {
+    /**
+     * A mock transport created by the mock transport factory.
+     */
+    final ConnectionClientTransport transport;
+
+    /**
+     * The listener passed to the start() of the mock transport.
+     */
+    final ManagedClientTransport.Listener listener;
+
+    MockClientTransportInfo(ConnectionClientTransport transport,
+        ManagedClientTransport.Listener listener) {
+      this.transport = transport;
+      this.listener = listener;
+    }
+  }
+
+  /**
+   * Stub the given mock {@link ClientTransportFactory} by returning mock
+   * {@link ManagedClientTransport}s which saves their listeners along with them. This method
+   * returns a list of {@link MockClientTransportInfo}, each of which is a started mock transport
+   * and its listener.
+   */
+  static BlockingQueue<MockClientTransportInfo> captureTransports(
+      ClientTransportFactory mockTransportFactory) {
+    return captureTransports(mockTransportFactory, null);
+  }
+
+  static BlockingQueue<MockClientTransportInfo> captureTransports(
+      ClientTransportFactory mockTransportFactory, @Nullable final Runnable startRunnable) {
+    final BlockingQueue<MockClientTransportInfo> captor =
+        new LinkedBlockingQueue<MockClientTransportInfo>();
+
+    doAnswer(new Answer<ConnectionClientTransport>() {
+      @Override
+      public ConnectionClientTransport answer(InvocationOnMock invocation) throws Throwable {
+        final ConnectionClientTransport mockTransport = mock(ConnectionClientTransport.class);
+        when(mockTransport.getLogId()).thenReturn(InternalLogId.allocate("mocktransport"));
+        when(mockTransport.newStream(
+                any(MethodDescriptor.class), any(Metadata.class), any(CallOptions.class)))
+            .thenReturn(mock(ClientStream.class));
+        // Save the listener
+        doAnswer(new Answer<Runnable>() {
+          @Override
+          public Runnable answer(InvocationOnMock invocation) throws Throwable {
+            captor.add(new MockClientTransportInfo(
+                mockTransport, (ManagedClientTransport.Listener) invocation.getArguments()[0]));
+            return startRunnable;
+          }
+        }).when(mockTransport).start(any(ManagedClientTransport.Listener.class));
+        return mockTransport;
+      }
+    }).when(mockTransportFactory)
+        .newClientTransport(
+            any(SocketAddress.class),
+            any(ClientTransportFactory.ClientTransportOptions.class));
+
+    return captor;
+  }
+
+  private TestUtils() {
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/TransportFrameUtilTest.java b/core/src/test/java/io/grpc/internal/TransportFrameUtilTest.java
new file mode 100644
index 0000000..aa99fdb
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/TransportFrameUtilTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static com.google.common.base.Charsets.US_ASCII;
+import static com.google.common.base.Charsets.UTF_8;
+import static io.grpc.Metadata.ASCII_STRING_MARSHALLER;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import com.google.common.io.BaseEncoding;
+import io.grpc.InternalMetadata;
+import io.grpc.Metadata;
+import io.grpc.Metadata.BinaryMarshaller;
+import io.grpc.Metadata.Key;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link TransportFrameUtil}. */
+@RunWith(JUnit4.class)
+public class TransportFrameUtilTest {
+
+  private static final String NONCOMPLIANT_ASCII_STRING = new String(new char[]{1, 2, 3});
+
+  private static final String COMPLIANT_ASCII_STRING = "Kyle";
+
+  private static final BinaryMarshaller<String> UTF8_STRING_MARSHALLER =
+      new BinaryMarshaller<String>() {
+    @Override
+    public byte[] toBytes(String value) {
+      return value.getBytes(UTF_8);
+    }
+
+    @Override
+    public String parseBytes(byte[] serialized) {
+      return new String(serialized, UTF_8);
+    }
+  };
+
+  private static final Key<String> PLAIN_STRING = Key.of("plainstring", ASCII_STRING_MARSHALLER);
+  private static final Key<String> BINARY_STRING = Key.of("string-bin", UTF8_STRING_MARSHALLER);
+  private static final Key<String> BINARY_STRING_WITHOUT_SUFFIX =
+      Key.of("string", ASCII_STRING_MARSHALLER);
+
+  @Test
+  public void testToHttp2Headers() {
+    Metadata headers = new Metadata();
+    headers.put(PLAIN_STRING, COMPLIANT_ASCII_STRING);
+    headers.put(BINARY_STRING, NONCOMPLIANT_ASCII_STRING);
+    headers.put(BINARY_STRING_WITHOUT_SUFFIX, NONCOMPLIANT_ASCII_STRING);
+    byte[][] http2Headers = TransportFrameUtil.toHttp2Headers(headers);
+    // BINARY_STRING_WITHOUT_SUFFIX should not get in because it contains non-compliant ASCII
+    // characters but doesn't have "-bin" in the name.
+    byte[][] answer = new byte[][] {
+        "plainstring".getBytes(US_ASCII), COMPLIANT_ASCII_STRING.getBytes(US_ASCII),
+        "string-bin".getBytes(US_ASCII),
+        base64Encode(NONCOMPLIANT_ASCII_STRING.getBytes(US_ASCII))};
+    assertEquals(answer.length, http2Headers.length);
+    // http2Headers may re-sort the keys, so we cannot compare it with the answer side-by-side.
+    for (int i = 0; i < answer.length; i += 2) {
+      assertContains(http2Headers, answer[i], answer[i + 1]);
+    }
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void binaryHeaderWithoutSuffix() {
+    Key.of("plainstring", UTF8_STRING_MARSHALLER);
+  }
+
+  @Test
+  public void testToAndFromHttp2Headers() {
+    Metadata headers = new Metadata();
+    headers.put(PLAIN_STRING, COMPLIANT_ASCII_STRING);
+    headers.put(BINARY_STRING, NONCOMPLIANT_ASCII_STRING);
+    headers.put(BINARY_STRING_WITHOUT_SUFFIX, NONCOMPLIANT_ASCII_STRING);
+    byte[][] http2Headers = TransportFrameUtil.toHttp2Headers(headers);
+    byte[][] rawSerialized = TransportFrameUtil.toRawSerializedHeaders(http2Headers);
+    Metadata recoveredHeaders = InternalMetadata.newMetadata(rawSerialized);
+    assertEquals(COMPLIANT_ASCII_STRING, recoveredHeaders.get(PLAIN_STRING));
+    assertEquals(NONCOMPLIANT_ASCII_STRING, recoveredHeaders.get(BINARY_STRING));
+    assertNull(recoveredHeaders.get(BINARY_STRING_WITHOUT_SUFFIX));
+  }
+
+  private static void assertContains(byte[][] headers, byte[] key, byte[] value) {
+    String keyString = new String(key, US_ASCII);
+    for (int i = 0; i < headers.length; i += 2) {
+      if (Arrays.equals(headers[i], key)) {
+        assertArrayEquals("value for key=" + keyString, value, headers[i + 1]);
+        return;
+      }
+    }
+    fail("key=" + keyString + " not found");
+  }
+
+  private static byte[] base64Encode(byte[] input) {
+    return BaseEncoding.base64().encode(input).getBytes(US_ASCII);
+  }
+
+}
diff --git a/core/src/test/java/io/grpc/internal/WritableBufferAllocatorTestBase.java b/core/src/test/java/io/grpc/internal/WritableBufferAllocatorTestBase.java
new file mode 100644
index 0000000..f7aff08
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/WritableBufferAllocatorTestBase.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertNotSame;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Abstract base class for tests of {@link WritableBufferAllocator} subclasses.
+ */
+@RunWith(JUnit4.class)
+public abstract class WritableBufferAllocatorTestBase {
+
+  protected abstract WritableBufferAllocator allocator();
+
+  @Test
+  public void testBuffersAreDifferent() {
+    WritableBuffer buffer1 = allocator().allocate(100);
+    WritableBuffer buffer2 = allocator().allocate(100);
+
+    assertNotSame(buffer1, buffer2);
+
+    buffer1.release();
+    buffer2.release();
+  }
+}
diff --git a/core/src/test/java/io/grpc/internal/WritableBufferTestBase.java b/core/src/test/java/io/grpc/internal/WritableBufferTestBase.java
new file mode 100644
index 0000000..0245359
--- /dev/null
+++ b/core/src/test/java/io/grpc/internal/WritableBufferTestBase.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Abstract base class for tests of {@link WritableBuffer} subclasses.
+ */
+@RunWith(JUnit4.class)
+public abstract class WritableBufferTestBase {
+
+  /**
+   * Returns a new buffer for every test case with
+   * at least 100 byte of capacity.
+   */
+  protected abstract WritableBuffer buffer();
+
+  /**
+   * Bytes written to {@link #buffer()}.
+   */
+  protected abstract byte[] writtenBytes();
+
+  @Test(expected = RuntimeException.class)
+  public void testWriteNegativeLength() {
+    buffer().write(new byte[1], 0, -1);
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void testWriteNegativeSrcIndex() {
+    buffer().write(new byte[1], -1, 0);
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void testWriteSrcIndexAndLengthExceedSrcLength() {
+    buffer().write(new byte[10], 1, 10);
+  }
+
+  @Test(expected = IndexOutOfBoundsException.class)
+  public void testWriteSrcIndexAndLengthExceedWritableBytes() {
+    buffer().write(new byte[buffer().writableBytes()], 1, buffer().writableBytes());
+  }
+
+  @Test
+  public void testWritableAndReadableBytes() {
+    int before = buffer().writableBytes();
+    buffer().write(new byte[10], 0, 5);
+
+    assertEquals(5, before - buffer().writableBytes());
+    assertEquals(5, buffer().readableBytes());
+  }
+
+  @Test
+  public void testWriteSrcIndex() {
+    byte[] b = new byte[10];
+    for (byte i = 5; i < 10; i++) {
+      b[i] = i;
+    }
+
+    buffer().write(b, 5, 5);
+
+    assertEquals(5, buffer().readableBytes());
+    byte[] writtenBytes = writtenBytes();
+    assertEquals(5, writtenBytes.length);
+    for (int i = 0; i < writtenBytes.length; i++) {
+      assertEquals(5 + i, writtenBytes[i]);
+    }
+  }
+
+  @Test
+  public void testMultipleWrites() {
+    byte[] b = new byte[100];
+    for (byte i = 0; i < b.length; i++) {
+      b[i] = i;
+    }
+
+    // Write in chunks of 10 bytes
+    for (int i = 0; i < 10; i++) {
+      buffer().write(b, 10 * i, 10);
+      assertEquals(10 * (i + 1), buffer().readableBytes());
+    }
+
+    assertArrayEquals(b, writtenBytes());
+  }
+}
diff --git a/core/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java b/core/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java
new file mode 100644
index 0000000..3ffd14d
--- /dev/null
+++ b/core/src/test/java/io/grpc/util/MutableHandlerRegistryTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.util;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.BindableService;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerMethodDefinition;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.ServiceDescriptor;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link MutableHandlerRegistry}. */
+@RunWith(JUnit4.class)
+public class MutableHandlerRegistryTest {
+  private MutableHandlerRegistry registry = new MutableHandlerRegistry();
+
+  @Mock
+  private Marshaller<String> requestMarshaller;
+
+  @Mock
+  private Marshaller<Integer> responseMarshaller;
+
+  @Mock
+  private ServerCallHandler<String, Integer> flowHandler;
+
+  @Mock
+  private ServerCallHandler<String, Integer> coupleHandler;
+
+  @Mock
+  private ServerCallHandler<String, Integer> fewHandler;
+
+  @Mock
+  private ServerCallHandler<String, Integer>  otherFlowHandler;
+
+  private ServerServiceDefinition basicServiceDefinition;
+  private ServerServiceDefinition multiServiceDefinition;
+
+  @SuppressWarnings("rawtypes")
+  private ServerMethodDefinition flowMethodDefinition;
+
+  @Before
+  public void setUp() throws Exception {
+    MockitoAnnotations.initMocks(this);
+    MethodDescriptor<String, Integer> flowMethod = MethodDescriptor.<String, Integer>newBuilder()
+        .setType(MethodType.UNKNOWN)
+        .setFullMethodName("basic/flow")
+        .setRequestMarshaller(requestMarshaller)
+        .setResponseMarshaller(responseMarshaller)
+        .build();
+    basicServiceDefinition = ServerServiceDefinition.builder(
+        new ServiceDescriptor("basic", flowMethod))
+        .addMethod(flowMethod, flowHandler)
+        .build();
+
+    MethodDescriptor<String, Integer> coupleMethod =
+        flowMethod.toBuilder().setFullMethodName("multi/couple").build();
+    MethodDescriptor<String, Integer> fewMethod =
+        flowMethod.toBuilder().setFullMethodName("multi/few").build();
+    multiServiceDefinition = ServerServiceDefinition.builder(
+        new ServiceDescriptor("multi", coupleMethod, fewMethod))
+        .addMethod(coupleMethod, coupleHandler)
+        .addMethod(fewMethod, fewHandler)
+        .build();
+
+    flowMethodDefinition = getOnlyElement(basicServiceDefinition.getMethods());
+  }
+
+  /** Final checks for all tests. */
+  @After
+  public void makeSureMocksUnused() {
+    Mockito.verifyZeroInteractions(requestMarshaller);
+    Mockito.verifyZeroInteractions(responseMarshaller);
+    Mockito.verifyNoMoreInteractions(flowHandler);
+    Mockito.verifyNoMoreInteractions(coupleHandler);
+    Mockito.verifyNoMoreInteractions(fewHandler);
+  }
+
+  @Test
+  public void simpleLookup() {
+    assertNull(registry.addService(basicServiceDefinition));
+    ServerMethodDefinition<?, ?> method = registry.lookupMethod("basic/flow");
+    assertSame(flowMethodDefinition, method);
+
+    assertNull(registry.lookupMethod("/basic/flow"));
+    assertNull(registry.lookupMethod("basic/basic"));
+    assertNull(registry.lookupMethod("flow/flow"));
+    assertNull(registry.lookupMethod("completely/random"));
+  }
+
+  @Test
+  public void simpleLookupWithBindable() {
+    BindableService bindableService =
+        new BindableService() {
+          @Override
+          public ServerServiceDefinition bindService() {
+            return basicServiceDefinition;
+          }
+        };
+
+    assertNull(registry.addService(bindableService));
+
+    ServerMethodDefinition<?, ?> method = registry.lookupMethod("basic/flow");
+    assertSame(flowMethodDefinition, method);
+  }
+
+  @Test
+  public void multiServiceLookup() {
+    assertNull(registry.addService(basicServiceDefinition));
+    assertNull(registry.addService(multiServiceDefinition));
+
+    ServerCallHandler<?, ?> handler = registry.lookupMethod("basic/flow").getServerCallHandler();
+    assertSame(flowHandler, handler);
+    handler = registry.lookupMethod("multi/couple").getServerCallHandler();
+    assertSame(coupleHandler, handler);
+    handler = registry.lookupMethod("multi/few").getServerCallHandler();
+    assertSame(fewHandler, handler);
+  }
+
+  @Test
+  public void removeAndLookup() {
+    assertNull(registry.addService(multiServiceDefinition));
+    assertNotNull(registry.lookupMethod("multi/couple"));
+    assertNotNull(registry.lookupMethod("multi/few"));
+    assertTrue(registry.removeService(multiServiceDefinition));
+    assertNull(registry.lookupMethod("multi/couple"));
+    assertNull(registry.lookupMethod("multi/few"));
+  }
+
+  @Test
+  public void replaceAndLookup() {
+    assertNull(registry.addService(basicServiceDefinition));
+    assertNotNull(registry.lookupMethod("basic/flow"));
+    MethodDescriptor<String, Integer> anotherMethod = MethodDescriptor.<String, Integer>newBuilder()
+        .setType(MethodType.UNKNOWN)
+        .setFullMethodName("basic/another")
+        .setRequestMarshaller(requestMarshaller)
+        .setResponseMarshaller(responseMarshaller)
+        .build();
+    ServerServiceDefinition replaceServiceDefinition = ServerServiceDefinition.builder(
+        new ServiceDescriptor("basic", anotherMethod))
+        .addMethod(anotherMethod, flowHandler).build();
+    ServerMethodDefinition<?, ?> anotherMethodDefinition =
+        replaceServiceDefinition.getMethod("basic/another");
+    assertSame(basicServiceDefinition, registry.addService(replaceServiceDefinition));
+
+    assertNull(registry.lookupMethod("basic/flow"));
+    ServerMethodDefinition<?, ?> method = registry.lookupMethod("basic/another");
+    assertSame(anotherMethodDefinition, method);
+  }
+
+  @Test
+  public void removeSameSucceeds() {
+    assertNull(registry.addService(basicServiceDefinition));
+    assertTrue(registry.removeService(basicServiceDefinition));
+  }
+
+  @Test
+  public void doubleRemoveFails() {
+    assertNull(registry.addService(basicServiceDefinition));
+    assertTrue(registry.removeService(basicServiceDefinition));
+    assertFalse(registry.removeService(basicServiceDefinition));
+  }
+
+  @Test
+  public void removeMissingFails() {
+    assertFalse(registry.removeService(basicServiceDefinition));
+  }
+
+  @Test
+  public void removeMissingNameConflictFails() {
+    assertNull(registry.addService(basicServiceDefinition));
+    assertFalse(registry.removeService(ServerServiceDefinition.builder(
+        new ServiceDescriptor("basic")).build()));
+  }
+
+  @Test
+  public void initialAddReturnsNull() {
+    assertNull(registry.addService(basicServiceDefinition));
+    assertNull(registry.addService(multiServiceDefinition));
+  }
+
+  @Test
+  public void missingMethodLookupReturnsNull() {
+    assertNull(registry.lookupMethod("bad"));
+  }
+
+  @Test
+  public void addAfterRemoveReturnsNull() {
+    assertNull(registry.addService(basicServiceDefinition));
+    assertTrue(registry.removeService(basicServiceDefinition));
+    assertNull(registry.addService(basicServiceDefinition));
+  }
+
+  @Test
+  public void addReturnsPrevious() {
+    assertNull(registry.addService(basicServiceDefinition));
+    assertSame(basicServiceDefinition,
+        registry.addService(ServerServiceDefinition.builder(
+            new ServiceDescriptor("basic")).build()));
+  }
+}
diff --git a/core/src/test/java/io/grpc/util/RoundRobinLoadBalancerTest.java b/core/src/test/java/io/grpc/util/RoundRobinLoadBalancerTest.java
new file mode 100644
index 0000000..fdca3b3
--- /dev/null
+++ b/core/src/test/java/io/grpc/util/RoundRobinLoadBalancerTest.java
@@ -0,0 +1,779 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.util;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.ConnectivityState.CONNECTING;
+import static io.grpc.ConnectivityState.IDLE;
+import static io.grpc.ConnectivityState.READY;
+import static io.grpc.ConnectivityState.SHUTDOWN;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+import static io.grpc.util.RoundRobinLoadBalancerFactory.RoundRobinLoadBalancer.STATE_INFO;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import io.grpc.Attributes;
+import io.grpc.ConnectivityState;
+import io.grpc.ConnectivityStateInfo;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.LoadBalancer;
+import io.grpc.LoadBalancer.Helper;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.Metadata;
+import io.grpc.Metadata.Key;
+import io.grpc.Status;
+import io.grpc.internal.GrpcAttributes;
+import io.grpc.util.RoundRobinLoadBalancerFactory.EmptyPicker;
+import io.grpc.util.RoundRobinLoadBalancerFactory.ReadyPicker;
+import io.grpc.util.RoundRobinLoadBalancerFactory.Ref;
+import io.grpc.util.RoundRobinLoadBalancerFactory.RoundRobinLoadBalancer;
+import io.grpc.util.RoundRobinLoadBalancerFactory.RoundRobinLoadBalancer.StickinessState;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/** Unit test for {@link RoundRobinLoadBalancerFactory}. */
+@RunWith(JUnit4.class)
+public class RoundRobinLoadBalancerTest {
+  private RoundRobinLoadBalancer loadBalancer;
+  private List<EquivalentAddressGroup> servers = Lists.newArrayList();
+  private Map<EquivalentAddressGroup, Subchannel> subchannels = Maps.newLinkedHashMap();
+  private static final Attributes.Key<String> MAJOR_KEY = Attributes.Key.create("major-key");
+  private Attributes affinity = Attributes.newBuilder().set(MAJOR_KEY, "I got the keys").build();
+
+  @Captor
+  private ArgumentCaptor<SubchannelPicker> pickerCaptor;
+  @Captor
+  private ArgumentCaptor<ConnectivityState> stateCaptor;
+  @Captor
+  private ArgumentCaptor<EquivalentAddressGroup> eagCaptor;
+  @Mock
+  private Helper mockHelper;
+
+  @Mock // This LoadBalancer doesn't use any of the arg fields, as verified in tearDown().
+  private PickSubchannelArgs mockArgs;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+
+    for (int i = 0; i < 3; i++) {
+      SocketAddress addr = new FakeSocketAddress("server" + i);
+      EquivalentAddressGroup eag = new EquivalentAddressGroup(addr);
+      servers.add(eag);
+      Subchannel sc = mock(Subchannel.class);
+      when(sc.getAddresses()).thenReturn(eag);
+      subchannels.put(eag, sc);
+    }
+
+    when(mockHelper.createSubchannel(any(EquivalentAddressGroup.class), any(Attributes.class)))
+        .then(new Answer<Subchannel>() {
+          @Override
+          public Subchannel answer(InvocationOnMock invocation) throws Throwable {
+            Object[] args = invocation.getArguments();
+            Subchannel subchannel = subchannels.get(args[0]);
+            when(subchannel.getAttributes()).thenReturn((Attributes) args[1]);
+            return subchannel;
+          }
+        });
+
+    loadBalancer = (RoundRobinLoadBalancer) RoundRobinLoadBalancerFactory.getInstance()
+        .newLoadBalancer(mockHelper);
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    verifyNoMoreInteractions(mockArgs);
+  }
+
+  @Test
+  public void pickAfterResolved() throws Exception {
+    final Subchannel readySubchannel = subchannels.values().iterator().next();
+    loadBalancer.handleResolvedAddressGroups(servers, affinity);
+    loadBalancer.handleSubchannelState(readySubchannel, ConnectivityStateInfo.forNonError(READY));
+
+    verify(mockHelper, times(3)).createSubchannel(eagCaptor.capture(),
+        any(Attributes.class));
+
+    assertThat(eagCaptor.getAllValues()).containsAllIn(subchannels.keySet());
+    for (Subchannel subchannel : subchannels.values()) {
+      verify(subchannel).requestConnection();
+      verify(subchannel, never()).shutdown();
+    }
+
+    verify(mockHelper, times(2))
+        .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+
+    assertEquals(CONNECTING, stateCaptor.getAllValues().get(0));
+    assertEquals(READY, stateCaptor.getAllValues().get(1));
+    assertThat(getList(pickerCaptor.getValue())).containsExactly(readySubchannel);
+
+    verifyNoMoreInteractions(mockHelper);
+  }
+
+  @Test
+  public void pickAfterResolvedUpdatedHosts() throws Exception {
+    Subchannel removedSubchannel = mock(Subchannel.class);
+    Subchannel oldSubchannel = mock(Subchannel.class);
+    Subchannel newSubchannel = mock(Subchannel.class);
+
+    for (Subchannel subchannel : Lists.newArrayList(removedSubchannel, oldSubchannel,
+        newSubchannel)) {
+      when(subchannel.getAttributes()).thenReturn(Attributes.newBuilder().set(STATE_INFO,
+          new Ref<ConnectivityStateInfo>(
+              ConnectivityStateInfo.forNonError(READY))).build());
+    }
+
+    FakeSocketAddress removedAddr = new FakeSocketAddress("removed");
+    FakeSocketAddress oldAddr = new FakeSocketAddress("old");
+    FakeSocketAddress newAddr = new FakeSocketAddress("new");
+
+    final Map<EquivalentAddressGroup, Subchannel> subchannels2 = Maps.newHashMap();
+    subchannels2.put(new EquivalentAddressGroup(removedAddr), removedSubchannel);
+    subchannels2.put(new EquivalentAddressGroup(oldAddr), oldSubchannel);
+
+    List<EquivalentAddressGroup> currentServers =
+        Lists.newArrayList(
+            new EquivalentAddressGroup(removedAddr),
+            new EquivalentAddressGroup(oldAddr));
+
+    doAnswer(new Answer<Subchannel>() {
+      @Override
+      public Subchannel answer(InvocationOnMock invocation) throws Throwable {
+        Object[] args = invocation.getArguments();
+        return subchannels2.get(args[0]);
+      }
+    }).when(mockHelper).createSubchannel(any(EquivalentAddressGroup.class), any(Attributes.class));
+
+    loadBalancer.handleResolvedAddressGroups(currentServers, affinity);
+
+    InOrder inOrder = inOrder(mockHelper);
+
+    inOrder.verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture());
+    SubchannelPicker picker = pickerCaptor.getValue();
+    assertThat(getList(picker)).containsExactly(removedSubchannel, oldSubchannel);
+
+    verify(removedSubchannel, times(1)).requestConnection();
+    verify(oldSubchannel, times(1)).requestConnection();
+
+    assertThat(loadBalancer.getSubchannels()).containsExactly(removedSubchannel,
+        oldSubchannel);
+
+    subchannels2.clear();
+    subchannels2.put(new EquivalentAddressGroup(oldAddr), oldSubchannel);
+    subchannels2.put(new EquivalentAddressGroup(newAddr), newSubchannel);
+
+    List<EquivalentAddressGroup> latestServers =
+        Lists.newArrayList(
+            new EquivalentAddressGroup(oldAddr),
+            new EquivalentAddressGroup(newAddr));
+
+    loadBalancer.handleResolvedAddressGroups(latestServers, affinity);
+
+    verify(newSubchannel, times(1)).requestConnection();
+    verify(removedSubchannel, times(1)).shutdown();
+    
+    loadBalancer.handleSubchannelState(removedSubchannel,
+            ConnectivityStateInfo.forNonError(SHUTDOWN));
+
+    assertThat(loadBalancer.getSubchannels()).containsExactly(oldSubchannel,
+        newSubchannel);
+
+    verify(mockHelper, times(3)).createSubchannel(any(EquivalentAddressGroup.class),
+        any(Attributes.class));
+    inOrder.verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture());
+
+    picker = pickerCaptor.getValue();
+    assertThat(getList(picker)).containsExactly(oldSubchannel, newSubchannel);
+
+    // test going from non-empty to empty
+    loadBalancer.handleResolvedAddressGroups(Collections.<EquivalentAddressGroup>emptyList(),
+            affinity);
+
+    inOrder.verify(mockHelper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture());
+    assertEquals(PickResult.withNoResult(), pickerCaptor.getValue().pickSubchannel(mockArgs));
+
+    verifyNoMoreInteractions(mockHelper);
+  }
+
+  @Test
+  public void pickAfterStateChange() throws Exception {
+    InOrder inOrder = inOrder(mockHelper);
+    loadBalancer.handleResolvedAddressGroups(servers, Attributes.EMPTY);
+    Subchannel subchannel = loadBalancer.getSubchannels().iterator().next();
+    Ref<ConnectivityStateInfo> subchannelStateInfo = subchannel.getAttributes().get(
+        STATE_INFO);
+
+    inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), isA(EmptyPicker.class));
+    assertThat(subchannelStateInfo.value).isEqualTo(ConnectivityStateInfo.forNonError(IDLE));
+
+    loadBalancer.handleSubchannelState(subchannel,
+        ConnectivityStateInfo.forNonError(READY));
+    inOrder.verify(mockHelper).updateBalancingState(eq(READY), pickerCaptor.capture());
+    assertThat(pickerCaptor.getValue()).isInstanceOf(ReadyPicker.class);
+    assertThat(subchannelStateInfo.value).isEqualTo(
+        ConnectivityStateInfo.forNonError(READY));
+
+    Status error = Status.UNKNOWN.withDescription("¯\\_(ツ)_//¯");
+    loadBalancer.handleSubchannelState(subchannel,
+        ConnectivityStateInfo.forTransientFailure(error));
+    assertThat(subchannelStateInfo.value).isEqualTo(
+        ConnectivityStateInfo.forTransientFailure(error));
+    inOrder.verify(mockHelper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
+    assertThat(pickerCaptor.getValue()).isInstanceOf(EmptyPicker.class);
+
+    loadBalancer.handleSubchannelState(subchannel,
+        ConnectivityStateInfo.forNonError(IDLE));
+    assertThat(subchannelStateInfo.value).isEqualTo(
+        ConnectivityStateInfo.forNonError(IDLE));
+
+    verify(subchannel, times(2)).requestConnection();
+    verify(mockHelper, times(3)).createSubchannel(any(EquivalentAddressGroup.class),
+        any(Attributes.class));
+    verifyNoMoreInteractions(mockHelper);
+  }
+
+  private Subchannel nextSubchannel(Subchannel current, List<Subchannel> allSubChannels) {
+    return allSubChannels.get((allSubChannels.indexOf(current) + 1) % allSubChannels.size());
+  }
+
+  @Test
+  public void pickerRoundRobin() throws Exception {
+    Subchannel subchannel = mock(Subchannel.class);
+    Subchannel subchannel1 = mock(Subchannel.class);
+    Subchannel subchannel2 = mock(Subchannel.class);
+
+    ReadyPicker picker = new ReadyPicker(Collections.unmodifiableList(
+        Lists.<Subchannel>newArrayList(subchannel, subchannel1, subchannel2)),
+        0 /* startIndex */, null /* stickinessState */);
+
+    assertThat(picker.getList()).containsExactly(subchannel, subchannel1, subchannel2);
+
+    assertEquals(subchannel, picker.pickSubchannel(mockArgs).getSubchannel());
+    assertEquals(subchannel1, picker.pickSubchannel(mockArgs).getSubchannel());
+    assertEquals(subchannel2, picker.pickSubchannel(mockArgs).getSubchannel());
+    assertEquals(subchannel, picker.pickSubchannel(mockArgs).getSubchannel());
+  }
+
+  @Test
+  public void pickerEmptyList() throws Exception {
+    SubchannelPicker picker = new EmptyPicker(Status.UNKNOWN);
+
+    assertEquals(null, picker.pickSubchannel(mockArgs).getSubchannel());
+    assertEquals(Status.UNKNOWN,
+        picker.pickSubchannel(mockArgs).getStatus());
+  }
+
+  @Test
+  public void nameResolutionErrorWithNoChannels() throws Exception {
+    Status error = Status.NOT_FOUND.withDescription("nameResolutionError");
+    loadBalancer.handleNameResolutionError(error);
+    verify(mockHelper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture());
+    LoadBalancer.PickResult pickResult = pickerCaptor.getValue().pickSubchannel(mockArgs);
+    assertNull(pickResult.getSubchannel());
+    assertEquals(error, pickResult.getStatus());
+    verifyNoMoreInteractions(mockHelper);
+  }
+
+  @Test
+  public void nameResolutionErrorWithActiveChannels() throws Exception {
+    final Subchannel readySubchannel = subchannels.values().iterator().next();
+    loadBalancer.handleResolvedAddressGroups(servers, affinity);
+    loadBalancer.handleSubchannelState(readySubchannel, ConnectivityStateInfo.forNonError(READY));
+    loadBalancer.handleNameResolutionError(Status.NOT_FOUND.withDescription("nameResolutionError"));
+
+    verify(mockHelper, times(3)).createSubchannel(any(EquivalentAddressGroup.class),
+        any(Attributes.class));
+    verify(mockHelper, times(3))
+        .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+
+    Iterator<ConnectivityState> stateIterator = stateCaptor.getAllValues().iterator();
+    assertEquals(CONNECTING, stateIterator.next());
+    assertEquals(READY, stateIterator.next());
+    assertEquals(TRANSIENT_FAILURE, stateIterator.next());
+
+    LoadBalancer.PickResult pickResult = pickerCaptor.getValue().pickSubchannel(mockArgs);
+    assertEquals(readySubchannel, pickResult.getSubchannel());
+    assertEquals(Status.OK.getCode(), pickResult.getStatus().getCode());
+
+    LoadBalancer.PickResult pickResult2 = pickerCaptor.getValue().pickSubchannel(mockArgs);
+    assertEquals(readySubchannel, pickResult2.getSubchannel());
+    verifyNoMoreInteractions(mockHelper);
+  }
+
+  @Test
+  public void subchannelStateIsolation() throws Exception {
+    Iterator<Subchannel> subchannelIterator = subchannels.values().iterator();
+    Subchannel sc1 = subchannelIterator.next();
+    Subchannel sc2 = subchannelIterator.next();
+    Subchannel sc3 = subchannelIterator.next();
+
+    loadBalancer.handleResolvedAddressGroups(servers, Attributes.EMPTY);
+    verify(sc1, times(1)).requestConnection();
+    verify(sc2, times(1)).requestConnection();
+    verify(sc3, times(1)).requestConnection();
+
+    loadBalancer.handleSubchannelState(sc1, ConnectivityStateInfo.forNonError(READY));
+    loadBalancer.handleSubchannelState(sc2, ConnectivityStateInfo.forNonError(READY));
+    loadBalancer.handleSubchannelState(sc3, ConnectivityStateInfo.forNonError(READY));
+    loadBalancer.handleSubchannelState(sc2, ConnectivityStateInfo.forNonError(IDLE));
+    loadBalancer
+        .handleSubchannelState(sc3, ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE));
+
+    verify(mockHelper, times(6))
+        .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+    Iterator<ConnectivityState> stateIterator = stateCaptor.getAllValues().iterator();
+    Iterator<SubchannelPicker> pickers = pickerCaptor.getAllValues().iterator();
+    // The picker is incrementally updated as subchannels become READY
+    assertEquals(CONNECTING, stateIterator.next());
+    assertThat(pickers.next()).isInstanceOf(EmptyPicker.class);
+    assertEquals(READY, stateIterator.next());
+    assertThat(getList(pickers.next())).containsExactly(sc1);
+    assertEquals(READY, stateIterator.next());
+    assertThat(getList(pickers.next())).containsExactly(sc1, sc2);
+    assertEquals(READY, stateIterator.next());
+    assertThat(getList(pickers.next())).containsExactly(sc1, sc2, sc3);
+    // The IDLE subchannel is dropped from the picker, but a reconnection is requested
+    assertEquals(READY, stateIterator.next());
+    assertThat(getList(pickers.next())).containsExactly(sc1, sc3);
+    verify(sc2, times(2)).requestConnection();
+    // The failing subchannel is dropped from the picker, with no requested reconnect
+    assertEquals(READY, stateIterator.next());
+    assertThat(getList(pickers.next())).containsExactly(sc1);
+    verify(sc3, times(1)).requestConnection();
+    assertThat(stateIterator.hasNext()).isFalse();
+    assertThat(pickers.hasNext()).isFalse();
+  }
+
+  @Test
+  public void noStickinessEnabled_withStickyHeader() {
+    loadBalancer.handleResolvedAddressGroups(servers, Attributes.EMPTY);
+    for (Subchannel subchannel : subchannels.values()) {
+      loadBalancer.handleSubchannelState(subchannel, ConnectivityStateInfo.forNonError(READY));
+    }
+    verify(mockHelper, times(4))
+        .updateBalancingState(any(ConnectivityState.class), pickerCaptor.capture());
+    SubchannelPicker picker = pickerCaptor.getValue();
+
+    Key<String> stickinessKey = Key.of("my-sticky-key", Metadata.ASCII_STRING_MARSHALLER);
+    Metadata headerWithStickinessValue = new Metadata();
+    headerWithStickinessValue.put(stickinessKey, "my-sticky-value");
+    doReturn(headerWithStickinessValue).when(mockArgs).getHeaders();
+
+    List<Subchannel> allSubchannels = getList(picker);
+    Subchannel sc1 = picker.pickSubchannel(mockArgs).getSubchannel();
+    Subchannel sc2 = picker.pickSubchannel(mockArgs).getSubchannel();
+    Subchannel sc3 = picker.pickSubchannel(mockArgs).getSubchannel();
+    Subchannel sc4 = picker.pickSubchannel(mockArgs).getSubchannel();
+
+    assertEquals(nextSubchannel(sc1, allSubchannels), sc2);
+    assertEquals(nextSubchannel(sc2, allSubchannels), sc3);
+    assertEquals(nextSubchannel(sc3, allSubchannels), sc1);
+    assertEquals(sc4, sc1);
+
+    assertNull(loadBalancer.getStickinessMapForTest());
+  }
+
+  @Test
+  public void stickinessEnabled_withoutStickyHeader() {
+    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+    serviceConfig.put("stickinessMetadataKey", "my-sticky-key");
+    Attributes attributes = Attributes.newBuilder()
+        .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build();
+    loadBalancer.handleResolvedAddressGroups(servers, attributes);
+    for (Subchannel subchannel : subchannels.values()) {
+      loadBalancer.handleSubchannelState(subchannel, ConnectivityStateInfo.forNonError(READY));
+    }
+    verify(mockHelper, times(4))
+        .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+    SubchannelPicker picker = pickerCaptor.getValue();
+
+    doReturn(new Metadata()).when(mockArgs).getHeaders();
+
+    List<Subchannel> allSubchannels = getList(picker);
+
+    Subchannel sc1 = picker.pickSubchannel(mockArgs).getSubchannel();
+    Subchannel sc2 = picker.pickSubchannel(mockArgs).getSubchannel();
+    Subchannel sc3 = picker.pickSubchannel(mockArgs).getSubchannel();
+    Subchannel sc4 = picker.pickSubchannel(mockArgs).getSubchannel();
+
+    assertEquals(nextSubchannel(sc1, allSubchannels), sc2);
+    assertEquals(nextSubchannel(sc2, allSubchannels), sc3);
+    assertEquals(nextSubchannel(sc3, allSubchannels), sc1);
+    assertEquals(sc4, sc1);
+    verify(mockArgs, times(4)).getHeaders();
+    assertNotNull(loadBalancer.getStickinessMapForTest());
+    assertThat(loadBalancer.getStickinessMapForTest()).isEmpty();
+  }
+
+  @Test
+  public void stickinessEnabled_withStickyHeader() {
+    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+    serviceConfig.put("stickinessMetadataKey", "my-sticky-key");
+    Attributes attributes = Attributes.newBuilder()
+        .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build();
+    loadBalancer.handleResolvedAddressGroups(servers, attributes);
+    for (Subchannel subchannel : subchannels.values()) {
+      loadBalancer.handleSubchannelState(subchannel, ConnectivityStateInfo.forNonError(READY));
+    }
+    verify(mockHelper, times(4))
+        .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+    SubchannelPicker picker = pickerCaptor.getValue();
+
+    Key<String> stickinessKey = Key.of("my-sticky-key", Metadata.ASCII_STRING_MARSHALLER);
+    Metadata headerWithStickinessValue = new Metadata();
+    headerWithStickinessValue.put(stickinessKey, "my-sticky-value");
+    doReturn(headerWithStickinessValue).when(mockArgs).getHeaders();
+
+    Subchannel sc1 = picker.pickSubchannel(mockArgs).getSubchannel();
+    assertEquals(sc1, picker.pickSubchannel(mockArgs).getSubchannel());
+    assertEquals(sc1, picker.pickSubchannel(mockArgs).getSubchannel());
+    assertEquals(sc1, picker.pickSubchannel(mockArgs).getSubchannel());
+    assertEquals(sc1, picker.pickSubchannel(mockArgs).getSubchannel());
+
+    verify(mockArgs, atLeast(4)).getHeaders();
+    assertNotNull(loadBalancer.getStickinessMapForTest());
+    assertThat(loadBalancer.getStickinessMapForTest()).hasSize(1);
+  }
+
+  @Test
+  public void stickinessEnabled_withDifferentStickyHeaders() {
+    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+    serviceConfig.put("stickinessMetadataKey", "my-sticky-key");
+    Attributes attributes = Attributes.newBuilder()
+        .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build();
+    loadBalancer.handleResolvedAddressGroups(servers, attributes);
+    for (Subchannel subchannel : subchannels.values()) {
+      loadBalancer.handleSubchannelState(subchannel, ConnectivityStateInfo.forNonError(READY));
+    }
+    verify(mockHelper, times(4))
+        .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+    SubchannelPicker picker = pickerCaptor.getValue();
+
+    Key<String> stickinessKey = Key.of("my-sticky-key", Metadata.ASCII_STRING_MARSHALLER);
+    Metadata headerWithStickinessValue1 = new Metadata();
+    headerWithStickinessValue1.put(stickinessKey, "my-sticky-value");
+
+    Metadata headerWithStickinessValue2 = new Metadata();
+    headerWithStickinessValue2.put(stickinessKey, "my-sticky-value2");
+
+    List<Subchannel> allSubchannels = getList(picker);
+
+    doReturn(headerWithStickinessValue1).when(mockArgs).getHeaders();
+    Subchannel sc1a = picker.pickSubchannel(mockArgs).getSubchannel();
+
+    doReturn(headerWithStickinessValue2).when(mockArgs).getHeaders();
+    Subchannel sc2a = picker.pickSubchannel(mockArgs).getSubchannel();
+
+    doReturn(headerWithStickinessValue1).when(mockArgs).getHeaders();
+    Subchannel sc1b = picker.pickSubchannel(mockArgs).getSubchannel();
+
+    doReturn(headerWithStickinessValue2).when(mockArgs).getHeaders();
+    Subchannel sc2b = picker.pickSubchannel(mockArgs).getSubchannel();
+
+    assertEquals(sc1a, sc1b);
+    assertEquals(sc2a, sc2b);
+    assertEquals(nextSubchannel(sc1a, allSubchannels), sc2a);
+    assertEquals(nextSubchannel(sc1b, allSubchannels), sc2b);
+
+    verify(mockArgs, atLeast(4)).getHeaders();
+    assertNotNull(loadBalancer.getStickinessMapForTest());
+    assertThat(loadBalancer.getStickinessMapForTest()).hasSize(2);
+  }
+
+  @Test
+  public void stickiness_goToTransientFailure_pick_backToReady() {
+    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+    serviceConfig.put("stickinessMetadataKey", "my-sticky-key");
+    Attributes attributes = Attributes.newBuilder()
+        .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build();
+    loadBalancer.handleResolvedAddressGroups(servers, attributes);
+    for (Subchannel subchannel : subchannels.values()) {
+      loadBalancer.handleSubchannelState(subchannel, ConnectivityStateInfo.forNonError(READY));
+    }
+    verify(mockHelper, times(4))
+        .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+    SubchannelPicker picker = pickerCaptor.getValue();
+
+    Key<String> stickinessKey = Key.of("my-sticky-key", Metadata.ASCII_STRING_MARSHALLER);
+    Metadata headerWithStickinessValue = new Metadata();
+    headerWithStickinessValue.put(stickinessKey, "my-sticky-value");
+    doReturn(headerWithStickinessValue).when(mockArgs).getHeaders();
+
+    // first pick
+    Subchannel sc1 = picker.pickSubchannel(mockArgs).getSubchannel();
+
+    // go to transient failure
+    loadBalancer
+        .handleSubchannelState(sc1, ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE));
+
+    verify(mockHelper, times(5))
+        .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+    picker = pickerCaptor.getValue();
+
+    // second pick
+    Subchannel sc2 = picker.pickSubchannel(mockArgs).getSubchannel();
+
+    // go back to ready
+    loadBalancer.handleSubchannelState(sc1, ConnectivityStateInfo.forNonError(READY));
+
+    verify(mockHelper, times(6))
+        .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+    picker = pickerCaptor.getValue();
+
+    // third pick
+    Subchannel sc3 = picker.pickSubchannel(mockArgs).getSubchannel();
+    assertEquals(sc2, sc3);
+    verify(mockArgs, atLeast(3)).getHeaders();
+    assertNotNull(loadBalancer.getStickinessMapForTest());
+    assertThat(loadBalancer.getStickinessMapForTest()).hasSize(1);
+  }
+
+  @Test
+  public void stickiness_goToTransientFailure_backToReady_pick() {
+    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+    serviceConfig.put("stickinessMetadataKey", "my-sticky-key");
+    Attributes attributes = Attributes.newBuilder()
+        .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build();
+    loadBalancer.handleResolvedAddressGroups(servers, attributes);
+    for (Subchannel subchannel : subchannels.values()) {
+      loadBalancer.handleSubchannelState(subchannel, ConnectivityStateInfo.forNonError(READY));
+    }
+    verify(mockHelper, times(4))
+        .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+    SubchannelPicker picker = pickerCaptor.getValue();
+
+    Key<String> stickinessKey = Key.of("my-sticky-key", Metadata.ASCII_STRING_MARSHALLER);
+    Metadata headerWithStickinessValue1 = new Metadata();
+    headerWithStickinessValue1.put(stickinessKey, "my-sticky-value");
+    doReturn(headerWithStickinessValue1).when(mockArgs).getHeaders();
+
+    // first pick
+    Subchannel sc1 = picker.pickSubchannel(mockArgs).getSubchannel();
+
+    // go to transient failure
+    loadBalancer
+        .handleSubchannelState(sc1, ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE));
+
+    Metadata headerWithStickinessValue2 = new Metadata();
+    headerWithStickinessValue2.put(stickinessKey, "my-sticky-value2");
+    doReturn(headerWithStickinessValue2).when(mockArgs).getHeaders();
+    verify(mockHelper, times(5))
+        .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+    picker = pickerCaptor.getValue();
+
+    // second pick with a different stickiness value
+    Subchannel sc2 = picker.pickSubchannel(mockArgs).getSubchannel();
+
+    // go back to ready
+    loadBalancer.handleSubchannelState(sc1, ConnectivityStateInfo.forNonError(READY));
+
+    doReturn(headerWithStickinessValue1).when(mockArgs).getHeaders();
+    verify(mockHelper, times(6))
+        .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+    picker = pickerCaptor.getValue();
+
+    // third pick with my-sticky-value1
+    Subchannel sc3 = picker.pickSubchannel(mockArgs).getSubchannel();
+    assertEquals(sc1, sc3);
+
+    verify(mockArgs, atLeast(3)).getHeaders();
+    assertNotNull(loadBalancer.getStickinessMapForTest());
+    assertThat(loadBalancer.getStickinessMapForTest()).hasSize(2);
+  }
+
+  @Test
+  public void stickiness_oneSubchannelShutdown() {
+    Map<String, Object> serviceConfig = new HashMap<String, Object>();
+    serviceConfig.put("stickinessMetadataKey", "my-sticky-key");
+    Attributes attributes = Attributes.newBuilder()
+        .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig).build();
+    loadBalancer.handleResolvedAddressGroups(servers, attributes);
+    for (Subchannel subchannel : subchannels.values()) {
+      loadBalancer.handleSubchannelState(subchannel, ConnectivityStateInfo.forNonError(READY));
+    }
+    verify(mockHelper, times(4))
+        .updateBalancingState(stateCaptor.capture(), pickerCaptor.capture());
+    SubchannelPicker picker = pickerCaptor.getValue();
+
+    Key<String> stickinessKey = Key.of("my-sticky-key", Metadata.ASCII_STRING_MARSHALLER);
+    Metadata headerWithStickinessValue = new Metadata();
+    headerWithStickinessValue.put(stickinessKey, "my-sticky-value");
+    doReturn(headerWithStickinessValue).when(mockArgs).getHeaders();
+
+    List<Subchannel> allSubchannels = Lists.newArrayList(getList(picker));
+
+    Subchannel sc1 = picker.pickSubchannel(mockArgs).getSubchannel();
+
+    // shutdown channel directly
+    loadBalancer
+        .handleSubchannelState(sc1, ConnectivityStateInfo.forNonError(ConnectivityState.SHUTDOWN));
+
+    assertNull(loadBalancer.getStickinessMapForTest().get("my-sticky-value").value);
+
+    assertEquals(nextSubchannel(sc1, allSubchannels),
+                 picker.pickSubchannel(mockArgs).getSubchannel());
+    assertThat(loadBalancer.getStickinessMapForTest()).hasSize(1);
+    verify(mockArgs, atLeast(2)).getHeaders();
+
+    Subchannel sc2 = picker.pickSubchannel(mockArgs).getSubchannel();
+
+    assertEquals(sc2, loadBalancer.getStickinessMapForTest().get("my-sticky-value").value);
+
+    // shutdown channel via name resolver change
+    List<EquivalentAddressGroup> newServers = new ArrayList<>(servers);
+    newServers.remove(sc2.getAddresses());
+
+    loadBalancer.handleResolvedAddressGroups(newServers, attributes);
+
+    verify(sc2, times(1)).shutdown();
+
+    loadBalancer.handleSubchannelState(sc2, ConnectivityStateInfo.forNonError(SHUTDOWN));
+
+    assertNull(loadBalancer.getStickinessMapForTest().get("my-sticky-value").value);
+
+    assertEquals(nextSubchannel(sc2, allSubchannels),
+            picker.pickSubchannel(mockArgs).getSubchannel());
+    assertThat(loadBalancer.getStickinessMapForTest()).hasSize(1);
+    verify(mockArgs, atLeast(2)).getHeaders();
+  }
+
+  @Test
+  public void stickiness_resolveTwice_metadataKeyChanged() {
+    Map<String, Object> serviceConfig1 = new HashMap<String, Object>();
+    serviceConfig1.put("stickinessMetadataKey", "my-sticky-key1");
+    Attributes attributes1 = Attributes.newBuilder()
+        .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig1).build();
+    loadBalancer.handleResolvedAddressGroups(servers, attributes1);
+    Map<String, ?> stickinessMap1 = loadBalancer.getStickinessMapForTest();
+
+    Map<String, Object> serviceConfig2 = new HashMap<String, Object>();
+    serviceConfig2.put("stickinessMetadataKey", "my-sticky-key2");
+    Attributes attributes2 = Attributes.newBuilder()
+        .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig2).build();
+    loadBalancer.handleResolvedAddressGroups(servers, attributes2);
+    Map<String, ?> stickinessMap2 = loadBalancer.getStickinessMapForTest();
+
+    assertNotSame(stickinessMap1, stickinessMap2);
+  }
+
+  @Test
+  public void stickiness_resolveTwice_metadataKeyUnChanged() {
+    Map<String, Object> serviceConfig1 = new HashMap<String, Object>();
+    serviceConfig1.put("stickinessMetadataKey", "my-sticky-key1");
+    Attributes attributes1 = Attributes.newBuilder()
+        .set(GrpcAttributes.NAME_RESOLVER_SERVICE_CONFIG, serviceConfig1).build();
+    loadBalancer.handleResolvedAddressGroups(servers, attributes1);
+    Map<String, ?> stickinessMap1 = loadBalancer.getStickinessMapForTest();
+
+    loadBalancer.handleResolvedAddressGroups(servers, attributes1);
+    Map<String, ?> stickinessMap2 = loadBalancer.getStickinessMapForTest();
+
+    assertSame(stickinessMap1, stickinessMap2);
+  }
+  
+  @Test(expected = IllegalArgumentException.class)
+  public void readyPicker_emptyList() {
+    // ready picker list must be non-empty
+    new ReadyPicker(Collections.<Subchannel>emptyList(), 0, null);
+  }
+
+  @Test
+  public void internalPickerComparisons() {
+    EmptyPicker emptyOk1 = new EmptyPicker(Status.OK);
+    EmptyPicker emptyOk2 = new EmptyPicker(Status.OK.withDescription("different OK"));
+    EmptyPicker emptyErr = new EmptyPicker(Status.UNKNOWN.withDescription("¯\\_(ツ)_//¯"));
+
+    Iterator<Subchannel> subchannelIterator = subchannels.values().iterator();
+    Subchannel sc1 = subchannelIterator.next();
+    Subchannel sc2 = subchannelIterator.next();
+    StickinessState stickinessState = new StickinessState("stick-key");
+    ReadyPicker ready1 = new ReadyPicker(Arrays.asList(sc1, sc2), 0, null);
+    ReadyPicker ready2 = new ReadyPicker(Arrays.asList(sc1), 0, null);
+    ReadyPicker ready3 = new ReadyPicker(Arrays.asList(sc2, sc1), 1, null);
+    ReadyPicker ready4 = new ReadyPicker(Arrays.asList(sc1, sc2), 1, stickinessState);
+    ReadyPicker ready5 = new ReadyPicker(Arrays.asList(sc2, sc1), 0, stickinessState);
+
+    assertTrue(emptyOk1.isEquivalentTo(emptyOk2));
+    assertFalse(emptyOk1.isEquivalentTo(emptyErr));
+    assertFalse(ready1.isEquivalentTo(ready2));
+    assertTrue(ready1.isEquivalentTo(ready3));
+    assertFalse(ready3.isEquivalentTo(ready4));
+    assertTrue(ready4.isEquivalentTo(ready5));
+    assertFalse(emptyOk1.isEquivalentTo(ready1));
+    assertFalse(ready1.isEquivalentTo(emptyOk1));
+  }
+
+
+  private static List<Subchannel> getList(SubchannelPicker picker) {
+    return picker instanceof ReadyPicker ? ((ReadyPicker) picker).getList() :
+        Collections.<Subchannel>emptyList();
+  }
+
+  private static class FakeSocketAddress extends SocketAddress {
+    final String name;
+
+    FakeSocketAddress(String name) {
+      this.name = name;
+    }
+
+    @Override
+    public String toString() {
+      return "FakeSocketAddress-" + name;
+    }
+  }
+}
diff --git a/core/src/test/java/io/grpc/util/UtilServerInterceptorsTest.java b/core/src/test/java/io/grpc/util/UtilServerInterceptorsTest.java
new file mode 100644
index 0000000..37433b8
--- /dev/null
+++ b/core/src/test/java/io/grpc/util/UtilServerInterceptorsTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.util;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static org.junit.Assert.assertEquals;
+
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptors;
+import io.grpc.ServerMethodDefinition;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.ServiceDescriptor;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.internal.NoopServerCall;
+import io.grpc.testing.TestMethodDescriptors;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit test for {@link io.grpc.ServerInterceptor} implementations that come with gRPC. Not to be
+ * confused with the unit tests that validate gRPC's usage of interceptors.
+ */
+@RunWith(JUnit4.class)
+public class UtilServerInterceptorsTest {
+  private static class VoidCallListener extends ServerCall.Listener<Void> {
+    public void onCall(ServerCall<Void, Void> call, Metadata headers) { }
+  }
+
+  private MethodDescriptor<Void, Void> flowMethod = TestMethodDescriptors.voidMethod();
+  private final Metadata headers = new Metadata();
+  private ServerCallHandler<Void, Void> handler = new ServerCallHandler<Void, Void>() {
+      @Override
+      public ServerCall.Listener<Void> startCall(
+          ServerCall<Void, Void> call, Metadata headers) {
+        listener.onCall(call, headers);
+        return listener;
+      }
+  };
+  private ServerServiceDefinition serviceDefinition =
+      ServerServiceDefinition.builder(new ServiceDescriptor("service_foo", flowMethod))
+          .addMethod(flowMethod, handler)
+          .build();
+  private VoidCallListener listener;
+
+  @SuppressWarnings("unchecked")
+  private static ServerMethodDefinition<Void, Void> getSoleMethod(
+      ServerServiceDefinition serviceDef) {
+    if (serviceDef.getMethods().size() != 1) {
+      throw new AssertionError("Not exactly one method present");
+    }
+    return (ServerMethodDefinition<Void, Void>) getOnlyElement(serviceDef.getMethods());
+  }
+
+  @Test
+  public void statusRuntimeExceptionTransmitter() {
+    final Status expectedStatus = Status.UNAVAILABLE;
+    final Metadata expectedMetadata = new Metadata();
+    FakeServerCall<Void, Void> call =
+        new FakeServerCall<Void, Void>(expectedStatus, expectedMetadata);
+    final StatusRuntimeException exception =
+        new StatusRuntimeException(expectedStatus, expectedMetadata);
+    listener = new VoidCallListener() {
+      @Override
+      public void onMessage(Void message) {
+        throw exception;
+      }
+
+      @Override
+      public void onHalfClose() {
+        throw exception;
+      }
+
+      @Override
+      public void onCancel() {
+        throw exception;
+      }
+
+      @Override
+      public void onComplete() {
+        throw exception;
+      }
+
+      @Override
+      public void onReady() {
+        throw exception;
+      }
+    };
+
+    ServerServiceDefinition intercepted = ServerInterceptors.intercept(
+        serviceDefinition,
+        Arrays.asList(TransmitStatusRuntimeExceptionInterceptor.instance()));
+    // The interceptor should have handled the error by directly closing the ServerCall
+    // and the exception should not propagate to the method's caller
+    getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers).onMessage(null);
+    getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers).onCancel();
+    getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers).onComplete();
+    getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers).onHalfClose();
+    getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers).onReady();
+    assertEquals(5, call.numCloses);
+  }
+
+  @Test
+  public void statusRuntimeExceptionTransmitterIgnoresClosedCalls() {
+    final Status expectedStatus = Status.UNAVAILABLE;
+    final Status unexpectedStatus = Status.CANCELLED;
+    final Metadata expectedMetadata = new Metadata();
+
+    FakeServerCall<Void, Void> call =
+        new FakeServerCall<Void, Void>(expectedStatus, expectedMetadata);
+    final StatusRuntimeException exception =
+        new StatusRuntimeException(expectedStatus, expectedMetadata);
+
+    listener = new VoidCallListener() {
+      @Override
+      public void onMessage(Void message) {
+        throw exception;
+      }
+
+      @Override
+      public void onHalfClose() {
+        throw exception;
+      }
+    };
+
+    ServerServiceDefinition intercepted = ServerInterceptors.intercept(
+        serviceDefinition,
+        Arrays.asList(TransmitStatusRuntimeExceptionInterceptor.instance()));
+    ServerCall.Listener<Void> callDoubleSreListener =
+        getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers);
+    callDoubleSreListener.onMessage(null); // the only close with our exception
+    callDoubleSreListener.onHalfClose(); // should not trigger a close
+
+    // this listener closes the call when it is initialized with startCall
+    listener = new VoidCallListener() {
+      @Override
+      public void onCall(ServerCall<Void, Void> call, Metadata headers) {
+        call.close(unexpectedStatus, headers);
+      }
+
+      @Override
+      public void onHalfClose() {
+        throw exception;
+      }
+    };
+
+    ServerCall.Listener<Void> callClosedListener =
+        getSoleMethod(intercepted).getServerCallHandler().startCall(call, headers);
+    // call is already closed, does not match exception
+    callClosedListener.onHalfClose(); // should not trigger a close
+    assertEquals(1, call.numCloses);
+  }
+
+  private static class FakeServerCall<ReqT, RespT> extends NoopServerCall<ReqT, RespT> {
+    final Status expectedStatus;
+    final Metadata expectedMetadata;
+
+    int numCloses;
+
+    FakeServerCall(Status expectedStatus, Metadata expectedMetadata) {
+      this.expectedStatus = expectedStatus;
+      this.expectedMetadata = expectedMetadata;
+    }
+
+    @Override
+    @SuppressWarnings("ReferenceEquality")
+    public void close(Status status, Metadata trailers) {
+      if (status == expectedStatus && trailers == expectedMetadata) {
+        numCloses++;
+      }
+    }
+  }
+}
diff --git a/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-empty.txt b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-empty.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-empty.txt
diff --git a/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-failAtAvailableProvider.txt b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-failAtAvailableProvider.txt
new file mode 100644
index 0000000..61cc58e
--- /dev/null
+++ b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-failAtAvailableProvider.txt
@@ -0,0 +1,2 @@
+io.grpc.ServiceProvidersTest$FailAtAvailableProvider
+io.grpc.ServiceProvidersTest$Available5Provider
diff --git a/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-failAtInitProvider.txt b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-failAtInitProvider.txt
new file mode 100644
index 0000000..c2ec06a
--- /dev/null
+++ b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-failAtInitProvider.txt
@@ -0,0 +1,2 @@
+io.grpc.ServiceProvidersTest$FailAtInitProvider
+io.grpc.ServiceProvidersTest$Available5Provider
diff --git a/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-failAtPriorityProvider.txt b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-failAtPriorityProvider.txt
new file mode 100644
index 0000000..3a3aa79
--- /dev/null
+++ b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-failAtPriorityProvider.txt
@@ -0,0 +1,2 @@
+io.grpc.ServiceProvidersTest$FailAtPriorityProvider
+io.grpc.ServiceProvidersTest$Available5Provider
diff --git a/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-multipleProvider.txt b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-multipleProvider.txt
new file mode 100644
index 0000000..cb24790
--- /dev/null
+++ b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-multipleProvider.txt
@@ -0,0 +1,3 @@
+io.grpc.ServiceProvidersTest$Available5Provider
+io.grpc.ServiceProvidersTest$Available7Provider
+io.grpc.ServiceProvidersTest$Available0Provider
diff --git a/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-unavailableProvider.txt b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-unavailableProvider.txt
new file mode 100644
index 0000000..55175ff
--- /dev/null
+++ b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-unavailableProvider.txt
@@ -0,0 +1,2 @@
+io.grpc.ServiceProvidersTest$UnavailableProvider
+io.grpc.ServiceProvidersTest$Available7Provider
diff --git a/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-unknownClassProvider.txt b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-unknownClassProvider.txt
new file mode 100644
index 0000000..58c2641
--- /dev/null
+++ b/core/src/test/resources/io/grpc/ServiceProvidersTestAbstractProvider-unknownClassProvider.txt
@@ -0,0 +1 @@
+io.grpc.ServiceProvidersTest$UnknownClassProvider
diff --git a/core/src/test/resources/io/grpc/internal/test_hedging_service_config.json b/core/src/test/resources/io/grpc/internal/test_hedging_service_config.json
new file mode 100644
index 0000000..5533973
--- /dev/null
+++ b/core/src/test/resources/io/grpc/internal/test_hedging_service_config.json
@@ -0,0 +1,54 @@
+{
+  "loadBalancingPolicy":"round_robin",
+  "methodConfig":[
+    {
+      "name":[
+        {
+          "service":"SimpleService1"
+        }
+      ],
+      "waitForReady":false,
+      "hedgingPolicy":{
+        "maxAttempts":3,
+        "hedgingDelay":"2.1s",
+        "nonFatalStatusCodes":[
+          "UNAVAILABLE",
+          "RESOURCE_EXHAUSTED"
+        ]
+      }
+    },
+    {
+      "name":[
+        {
+          "service":"SimpleService2"
+        }
+      ],
+      "waitForReady":false
+    },
+    {
+      "name":[
+        {
+          "service":"SimpleService1",
+          "method":"Foo1"
+        },
+        {
+          "service":"SimpleService2",
+          "method":"Foo2"
+        }
+      ],
+      "waitForReady":true,
+      "hedgingPolicy":{
+        "maxAttempts":5,
+        "hedgingDelay":"0.1s",
+        "nonFatalStatusCodes":[
+          "UNAVAILABLE"
+        ]
+      }
+    }
+  ],
+
+  "retryThrottling": {
+    "maxTokens": 10,
+    "tokenRatio": 0.1
+  }
+}
diff --git a/core/src/test/resources/io/grpc/internal/test_retry_service_config.json b/core/src/test/resources/io/grpc/internal/test_retry_service_config.json
new file mode 100644
index 0000000..7274bf7
--- /dev/null
+++ b/core/src/test/resources/io/grpc/internal/test_retry_service_config.json
@@ -0,0 +1,58 @@
+{
+  "loadBalancingPolicy":"round_robin",
+  "methodConfig":[
+    {
+      "name":[
+        {
+          "service":"SimpleService1"
+        }
+      ],
+      "waitForReady":false,
+      "retryPolicy":{
+        "maxAttempts":3,
+        "initialBackoff":"2.1s",
+        "maxBackoff":"2.2s",
+        "backoffMultiplier":3,
+        "retryableStatusCodes":[
+          "UNAVAILABLE",
+          "RESOURCE_EXHAUSTED"
+        ]
+      }
+    },
+    {
+      "name":[
+        {
+          "service":"SimpleService2"
+        }
+      ],
+      "waitForReady":false
+    },
+    {
+      "name":[
+        {
+          "service":"SimpleService1",
+          "method":"Foo1"
+        },
+        {
+          "service":"SimpleService2",
+          "method":"Foo2"
+        }
+      ],
+      "waitForReady":true,
+      "retryPolicy":{
+        "maxAttempts":5,
+        "initialBackoff":"0.1s",
+        "maxBackoff":"1s",
+        "backoffMultiplier":2,
+        "retryableStatusCodes":[
+          "UNAVAILABLE"
+        ]
+      }
+    }
+  ],
+
+  "retryThrottling": {
+    "maxTokens": 10,
+    "tokenRatio": 0.1
+  }
+}
diff --git a/core/src/test/resources/io/grpc/internal/uncompressable.bin b/core/src/test/resources/io/grpc/internal/uncompressable.bin
new file mode 100644
index 0000000..e5a2456
--- /dev/null
+++ b/core/src/test/resources/io/grpc/internal/uncompressable.bin
Binary files differ
diff --git a/cronet/.gitignore b/cronet/.gitignore
new file mode 100644
index 0000000..2fd4c3b
--- /dev/null
+++ b/cronet/.gitignore
@@ -0,0 +1 @@
+libs/
diff --git a/cronet/README.md b/cronet/README.md
new file mode 100644
index 0000000..42864b0
--- /dev/null
+++ b/cronet/README.md
@@ -0,0 +1,45 @@
+gRPC Cronet Transport
+========================
+
+**EXPERIMENTAL:**  *gRPC's Cronet transport is an experimental API, and is not
+yet integrated with our build system. Using Cronet with gRPC requires manually
+integrating the gRPC code in this directory into your Android application.*
+
+This code enables using the [Chromium networking stack
+(Cronet)](https://chromium.googlesource.com/chromium/src/+/master/components/cronet)
+as the transport layer for gRPC on Android. This lets your Android app make
+RPCs using the same networking stack as used in the Chrome browser.
+
+Some advantages of using Cronet with gRPC:
+* Bundles an OpenSSL implementation, enabling TLS connections even on older
+  versions of Android without additional configuration
+* Robust to Android network connectivity changes
+* Support for [QUIC](https://www.chromium.org/quic)
+
+Cronet jars are available on Google's Maven repository. See the example app at
+https://github.com/GoogleChrome/cronet-sample/blob/master/README.md. To use
+Cronet with gRPC, you will need to copy the gRPC source files contained in this
+directory into your application's code, as we do not currently provide a
+`grpc-cronet` dependency.
+
+To use Cronet, you must have the `ACCESS_NETWORK_STATE` permission set in
+`AndroidManifest.xml`:
+
+```
+<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+```
+
+Once the above steps are completed, you can create a gRPC Cronet channel as
+follows:
+
+```
+import io.grpc.cronet.CronetChannelBuilder;
+import org.chromium.net.ExperimentalCronetEngine;
+
+...
+
+ExperimentalCronetEngine engine =
+    new ExperimentalCronetEngine.Builder(context /* Android Context */).build();
+ManagedChannel channel = CronetChannelBuilder.forAddress("localhost", 8080, engine).build();
+```
+
diff --git a/cronet/build.gradle b/cronet/build.gradle
new file mode 100644
index 0000000..ae0efd2
--- /dev/null
+++ b/cronet/build.gradle
@@ -0,0 +1,50 @@
+apply plugin: 'com.android.library'
+
+description = "gRPC: Cronet Android"
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies { classpath 'com.android.tools.build:gradle:3.0.1' }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+        mavenLocal()
+    }
+}
+
+android {
+    compileSdkVersion 27
+    defaultConfig {
+        minSdkVersion 14
+        targetSdkVersion 27
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        debug { minifyEnabled false }
+        release {
+            minifyEnabled true
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    testOptions { unitTests { includeAndroidResources = true } }
+    lintOptions { disable 'InvalidPackage' }
+}
+
+dependencies {
+    implementation 'io.grpc:grpc-core:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    testImplementation 'io.grpc:grpc-testing:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+
+    implementation "org.chromium.net:cronet-embedded:66.3359.158"
+
+    testImplementation 'junit:junit:4.12'
+    testImplementation 'org.mockito:mockito-core:1.10.19'
+    testImplementation "org.robolectric:robolectric:3.5.1"
+}
diff --git a/cronet/proguard-rules.pro b/cronet/proguard-rules.pro
new file mode 100644
index 0000000..b671e77
--- /dev/null
+++ b/cronet/proguard-rules.pro
@@ -0,0 +1,13 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in $ANDROID_HOME/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+-dontwarn org.chromium.**
+-dontnote org.chromium.**
diff --git a/cronet/src/main/AndroidManifest.xml b/cronet/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..5b77412
--- /dev/null
+++ b/cronet/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.grpc.cronet" />
diff --git a/cronet/src/main/java/io/grpc/cronet/CronetCallOptions.java b/cronet/src/main/java/io/grpc/cronet/CronetCallOptions.java
new file mode 100644
index 0000000..d361d0b
--- /dev/null
+++ b/cronet/src/main/java/io/grpc/cronet/CronetCallOptions.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.cronet;
+
+import io.grpc.CallOptions;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+/** Call options for use with the Cronet transport. */
+public final class CronetCallOptions {
+  private CronetCallOptions() {}
+
+  /**
+   * Used for attaching annotation objects to Cronet streams. When the stream finishes, the user can
+   * get Cronet metrics from {@link org.chromium.net.RequestFinishedInfo.Listener} with the same
+   * annotation object.
+   *
+   * <p>The Object must not be null.
+   *
+   * @deprecated Use {@link CronetCallOptions#withAnnotation} instead.
+   */
+  @Deprecated
+  public static final CallOptions.Key<Object> CRONET_ANNOTATION_KEY =
+      CallOptions.Key.create("cronet-annotation");
+
+  /**
+   * Returns a copy of {@code callOptions} with {@code annotation} included as one of the Cronet
+   * annotation objects. When an RPC is made using a {@link CallOptions} instance returned by this
+   * method, the annotation objects will be attached to the underlying Cronet bidirectional stream.
+   * When the stream finishes, the user can retrieve the annotation objects via {@link
+   * org.chromium.net.RequestFinishedInfo.Listener}.
+   *
+   * @param annotation the object to attach to the Cronet stream
+   */
+  public static CallOptions withAnnotation(CallOptions callOptions, Object annotation) {
+    Collection<Object> existingAnnotations = callOptions.getOption(CRONET_ANNOTATIONS_KEY);
+    ArrayList<Object> newAnnotations;
+    if (existingAnnotations == null) {
+      newAnnotations = new ArrayList<>();
+    } else {
+      newAnnotations = new ArrayList<>(existingAnnotations);
+    }
+    newAnnotations.add(annotation);
+    return callOptions.withOption(
+        CronetCallOptions.CRONET_ANNOTATIONS_KEY, Collections.unmodifiableList(newAnnotations));
+  }
+
+  static final CallOptions.Key<Collection<Object>> CRONET_ANNOTATIONS_KEY =
+      CallOptions.Key.create("cronet-annotations");
+}
diff --git a/cronet/src/main/java/io/grpc/cronet/CronetChannelBuilder.java b/cronet/src/main/java/io/grpc/cronet/CronetChannelBuilder.java
new file mode 100644
index 0000000..5811387
--- /dev/null
+++ b/cronet/src/main/java/io/grpc/cronet/CronetChannelBuilder.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.cronet;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.Attributes;
+import io.grpc.ExperimentalApi;
+import io.grpc.NameResolver;
+import io.grpc.internal.AbstractManagedChannelImplBuilder;
+import io.grpc.internal.ClientTransportFactory;
+import io.grpc.internal.ConnectionClientTransport;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.ProxyParameters;
+import io.grpc.internal.SharedResourceHolder;
+import io.grpc.internal.TransportTracer;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import javax.annotation.Nullable;
+import org.chromium.net.BidirectionalStream;
+import org.chromium.net.CronetEngine;
+import org.chromium.net.ExperimentalBidirectionalStream;
+import org.chromium.net.ExperimentalCronetEngine;
+
+/** Convenience class for building channels with the cronet transport. */
+@ExperimentalApi("There is no plan to make this API stable, given transport API instability")
+public final class CronetChannelBuilder extends
+    AbstractManagedChannelImplBuilder<CronetChannelBuilder> {
+
+  /** BidirectionalStream.Builder factory used for getting the gRPC BidirectionalStream. */
+  public static abstract class StreamBuilderFactory {
+    public abstract BidirectionalStream.Builder newBidirectionalStreamBuilder(
+        String url, BidirectionalStream.Callback callback, Executor executor);
+  }
+
+  /** Creates a new builder for the given server host, port and CronetEngine. */
+  public static CronetChannelBuilder forAddress(String host, int port, CronetEngine cronetEngine) {
+    Preconditions.checkNotNull(cronetEngine, "cronetEngine");
+    return new CronetChannelBuilder(host, port, cronetEngine);
+  }
+
+  /**
+   * Always fails.  Call {@link #forAddress(String, int, CronetEngine)} instead.
+   */
+  public static CronetChannelBuilder forTarget(String target) {
+    throw new UnsupportedOperationException("call forAddress() instead");
+  }
+
+  /**
+   * Always fails.  Call {@link #forAddress(String, int, CronetEngine)} instead.
+   */
+  public static CronetChannelBuilder forAddress(String name, int port) {
+    throw new UnsupportedOperationException("call forAddress(String, int, CronetEngine) instead");
+  }
+
+  @Nullable
+  private ScheduledExecutorService scheduledExecutorService;
+
+  private final CronetEngine cronetEngine;
+
+  private boolean alwaysUsePut = false;
+
+  private int maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE;
+
+  private boolean trafficStatsTagSet;
+  private int trafficStatsTag;
+  private boolean trafficStatsUidSet;
+  private int trafficStatsUid;
+
+  private CronetChannelBuilder(String host, int port, CronetEngine cronetEngine) {
+    super(
+        InetSocketAddress.createUnresolved(host, port),
+        GrpcUtil.authorityFromHostAndPort(host, port));
+    this.cronetEngine = Preconditions.checkNotNull(cronetEngine, "cronetEngine");
+  }
+
+  /**
+   * Sets the maximum message size allowed to be received on the channel. If not called,
+   * defaults to {@link io.grpc.internal.GrpcUtil#DEFAULT_MAX_MESSAGE_SIZE}.
+   */
+  public final CronetChannelBuilder maxMessageSize(int maxMessageSize) {
+    checkArgument(maxMessageSize >= 0, "maxMessageSize must be >= 0");
+    this.maxMessageSize = maxMessageSize;
+    return this;
+  }
+
+  /**
+   * Sets the Cronet channel to always use PUT instead of POST. Defaults to false.
+   */
+  public final CronetChannelBuilder alwaysUsePut(boolean enable) {
+    this.alwaysUsePut = enable;
+    return this;
+  }
+
+  /**
+   * Not supported for building cronet channel.
+   */
+  @Override
+  public final CronetChannelBuilder usePlaintext(boolean skipNegotiation) {
+    throw new IllegalArgumentException("Plaintext not currently supported");
+  }
+
+  /**
+   * Sets {@link android.net.TrafficStats} tag to use when accounting socket traffic caused by this
+   * channel. See {@link android.net.TrafficStats} for more information. If no tag is set (e.g. this
+   * method isn't called), then Android accounts for the socket traffic caused by this channel as if
+   * the tag value were set to 0.
+   *
+   * <p><b>NOTE:</b>Setting a tag disallows sharing of sockets with channels with other tags, which
+   * may adversely effect performance by prohibiting connection sharing. In other words use of
+   * multiplexed sockets (e.g. HTTP/2 and QUIC) will only be allowed if all channels have the same
+   * socket tag.
+   *
+   * @param tag the tag value used to when accounting for socket traffic caused by this channel.
+   *     Tags between 0xFFFFFF00 and 0xFFFFFFFF are reserved and used internally by system services
+   *     like {@link android.app.DownloadManager} when performing traffic on behalf of an
+   *     application.
+   * @return the builder to facilitate chaining.
+   */
+  public final CronetChannelBuilder setTrafficStatsTag(int tag) {
+    trafficStatsTagSet = true;
+    trafficStatsTag = tag;
+    return this;
+  }
+
+  /**
+   * Sets specific UID to use when accounting socket traffic caused by this channel. See {@link
+   * android.net.TrafficStats} for more information. Designed for use when performing an operation
+   * on behalf of another application. Caller must hold {@link
+   * android.Manifest.permission#MODIFY_NETWORK_ACCOUNTING} permission. By default traffic is
+   * attributed to UID of caller.
+   *
+   * <p><b>NOTE:</b>Setting a UID disallows sharing of sockets with channels with other UIDs, which
+   * may adversely effect performance by prohibiting connection sharing. In other words use of
+   * multiplexed sockets (e.g. HTTP/2 and QUIC) will only be allowed if all channels have the same
+   * UID set.
+   *
+   * @param uid the UID to attribute socket traffic caused by this channel.
+   * @return the builder to facilitate chaining.
+   */
+  public final CronetChannelBuilder setTrafficStatsUid(int uid) {
+    trafficStatsUidSet = true;
+    trafficStatsUid = uid;
+    return this;
+  }
+
+  /**
+   * Provides a custom scheduled executor service.
+   *
+   * <p>It's an optional parameter. If the user has not provided a scheduled executor service when
+   * the channel is built, the builder will use a static cached thread pool.
+   *
+   * @return this
+   *
+   * @since 1.12.0
+   */
+  public final CronetChannelBuilder scheduledExecutorService(
+      ScheduledExecutorService scheduledExecutorService) {
+    this.scheduledExecutorService =
+        checkNotNull(scheduledExecutorService, "scheduledExecutorService");
+    return this;
+  }
+
+  @Override
+  protected final ClientTransportFactory buildTransportFactory() {
+    return new CronetTransportFactory(
+        new TaggingStreamFactory(
+            cronetEngine, trafficStatsTagSet, trafficStatsTag, trafficStatsUidSet, trafficStatsUid),
+        MoreExecutors.directExecutor(),
+        scheduledExecutorService,
+        maxMessageSize,
+        alwaysUsePut,
+        transportTracerFactory.create());
+  }
+
+  @Override
+  protected Attributes getNameResolverParams() {
+    return Attributes.newBuilder()
+        .set(NameResolver.Factory.PARAMS_DEFAULT_PORT, GrpcUtil.DEFAULT_PORT_SSL).build();
+  }
+
+  @VisibleForTesting
+  static class CronetTransportFactory implements ClientTransportFactory {
+    private final ScheduledExecutorService timeoutService;
+    private final Executor executor;
+    private final int maxMessageSize;
+    private final boolean alwaysUsePut;
+    private final StreamBuilderFactory streamFactory;
+    private final TransportTracer transportTracer;
+    private final boolean usingSharedScheduler;
+
+    private CronetTransportFactory(
+        StreamBuilderFactory streamFactory,
+        Executor executor,
+        @Nullable ScheduledExecutorService timeoutService,
+        int maxMessageSize,
+        boolean alwaysUsePut,
+        TransportTracer transportTracer) {
+      usingSharedScheduler = timeoutService == null;
+      this.timeoutService = usingSharedScheduler
+          ? SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE) : timeoutService;
+      this.maxMessageSize = maxMessageSize;
+      this.alwaysUsePut = alwaysUsePut;
+      this.streamFactory = streamFactory;
+      this.executor = Preconditions.checkNotNull(executor, "executor");
+      this.transportTracer = Preconditions.checkNotNull(transportTracer, "transportTracer");
+    }
+
+    @Override
+    public ConnectionClientTransport newClientTransport(
+        SocketAddress addr, ClientTransportOptions options) {
+      InetSocketAddress inetSocketAddr = (InetSocketAddress) addr;
+      return new CronetClientTransport(streamFactory, inetSocketAddr, options.getAuthority(),
+          options.getUserAgent(), executor, maxMessageSize, alwaysUsePut, transportTracer);
+    }
+
+    @Override
+    public ScheduledExecutorService getScheduledExecutorService() {
+      return timeoutService;
+    }
+
+    @Override
+    public void close() {
+      if (usingSharedScheduler) {
+        SharedResourceHolder.release(GrpcUtil.TIMER_SERVICE, timeoutService);
+      }
+    }
+  }
+
+  /**
+   * StreamBuilderFactory impl that applies TrafficStats tags to stream builders that are produced.
+   */
+  private static class TaggingStreamFactory extends StreamBuilderFactory {
+    private final CronetEngine cronetEngine;
+    private final boolean trafficStatsTagSet;
+    private final int trafficStatsTag;
+    private final boolean trafficStatsUidSet;
+    private final int trafficStatsUid;
+
+    TaggingStreamFactory(
+        CronetEngine cronetEngine,
+        boolean trafficStatsTagSet,
+        int trafficStatsTag,
+        boolean trafficStatsUidSet,
+        int trafficStatsUid) {
+      this.cronetEngine = cronetEngine;
+      this.trafficStatsTagSet = trafficStatsTagSet;
+      this.trafficStatsTag = trafficStatsTag;
+      this.trafficStatsUidSet = trafficStatsUidSet;
+      this.trafficStatsUid = trafficStatsUid;
+    }
+
+    @Override
+    public BidirectionalStream.Builder newBidirectionalStreamBuilder(
+        String url, BidirectionalStream.Callback callback, Executor executor) {
+      ExperimentalBidirectionalStream.Builder builder =
+          ((ExperimentalCronetEngine) cronetEngine)
+              .newBidirectionalStreamBuilder(url, callback, executor);
+      if (trafficStatsTagSet) {
+        builder.setTrafficStatsTag(trafficStatsTag);
+      }
+      if (trafficStatsUidSet) {
+        builder.setTrafficStatsUid(trafficStatsUid);
+      }
+      return builder;
+    }
+  }
+}
diff --git a/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java b/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java
new file mode 100644
index 0000000..b9a8a43
--- /dev/null
+++ b/cronet/src/main/java/io/grpc/cronet/CronetClientStream.java
@@ -0,0 +1,548 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.cronet;
+
+import static io.grpc.internal.GrpcUtil.CONTENT_TYPE_KEY;
+import static io.grpc.internal.GrpcUtil.TE_HEADER;
+import static io.grpc.internal.GrpcUtil.USER_AGENT_KEY;
+
+// TODO(ericgribkoff): Consider changing from android.util.Log to java logging.
+import android.util.Log;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.io.BaseEncoding;
+import io.grpc.Attributes;
+import io.grpc.CallOptions;
+import io.grpc.InternalMetadata;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.cronet.CronetChannelBuilder.StreamBuilderFactory;
+import io.grpc.internal.AbstractClientStream;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.Http2ClientStreamTransportState;
+import io.grpc.internal.ReadableBuffers;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.TransportFrameUtil;
+import io.grpc.internal.TransportTracer;
+import io.grpc.internal.WritableBuffer;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import org.chromium.net.BidirectionalStream;
+import org.chromium.net.CronetException;
+import org.chromium.net.ExperimentalBidirectionalStream;
+import org.chromium.net.UrlResponseInfo;
+
+/**
+ * Client stream for the cronet transport.
+ */
+class CronetClientStream extends AbstractClientStream {
+  private static final int READ_BUFFER_CAPACITY = 4 * 1024;
+  private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocateDirect(0);
+  private static final String LOG_TAG = "grpc-java-cronet";
+  private final String url;
+  private final String userAgent;
+  private final StatsTraceContext statsTraceCtx;
+  private final Executor executor;
+  private final Metadata headers;
+  private final CronetClientTransport transport;
+  private final Runnable startCallback;
+  @VisibleForTesting
+  final boolean idempotent;
+  private BidirectionalStream stream;
+  private final boolean delayRequestHeader;
+  private final Object annotation;
+  private final Collection<Object> annotations;
+  private final TransportState state;
+  private final Sink sink = new Sink();
+  private StreamBuilderFactory streamFactory;
+
+  CronetClientStream(
+      final String url,
+      @Nullable String userAgent,
+      Executor executor,
+      final Metadata headers,
+      CronetClientTransport transport,
+      Runnable startCallback,
+      Object lock,
+      int maxMessageSize,
+      boolean alwaysUsePut,
+      MethodDescriptor<?, ?> method,
+      StatsTraceContext statsTraceCtx,
+      CallOptions callOptions,
+      TransportTracer transportTracer) {
+    super(
+        new CronetWritableBufferAllocator(), statsTraceCtx, transportTracer, headers,
+        method.isSafe());
+    this.url = Preconditions.checkNotNull(url, "url");
+    this.userAgent = Preconditions.checkNotNull(userAgent, "userAgent");
+    this.statsTraceCtx = Preconditions.checkNotNull(statsTraceCtx, "statsTraceCtx");
+    this.executor = Preconditions.checkNotNull(executor, "executor");
+    this.headers = Preconditions.checkNotNull(headers, "headers");
+    this.transport = Preconditions.checkNotNull(transport, "transport");
+    this.startCallback = Preconditions.checkNotNull(startCallback, "startCallback");
+    this.idempotent = method.isIdempotent() || alwaysUsePut;
+    // Only delay flushing header for unary rpcs.
+    this.delayRequestHeader = (method.getType() == MethodDescriptor.MethodType.UNARY);
+    this.annotation = callOptions.getOption(CronetCallOptions.CRONET_ANNOTATION_KEY);
+    this.annotations = callOptions.getOption(CronetCallOptions.CRONET_ANNOTATIONS_KEY);
+    this.state = new TransportState(maxMessageSize, statsTraceCtx, lock, transportTracer);
+  }
+
+  @Override
+  protected TransportState transportState() {
+    return state;
+  }
+
+  @Override
+  protected Sink abstractClientStreamSink() {
+    return sink;
+  }
+
+  @Override
+  public void setAuthority(String authority) {
+    throw new UnsupportedOperationException("Cronet does not support overriding authority");
+  }
+
+  class Sink implements AbstractClientStream.Sink {
+    @Override
+    public void writeHeaders(Metadata metadata, byte[] payload) {
+      startCallback.run();
+
+      BidirectionalStreamCallback callback = new BidirectionalStreamCallback();
+      String path = url;
+      if (payload != null) {
+        path += "?" + BaseEncoding.base64().encode(payload);
+      }
+      BidirectionalStream.Builder builder =
+          streamFactory.newBidirectionalStreamBuilder(path, callback, executor);
+      if (payload != null) {
+        builder.setHttpMethod("GET");
+      } else if (idempotent) {
+        builder.setHttpMethod("PUT");
+      }
+      if (delayRequestHeader) {
+        builder.delayRequestHeadersUntilFirstFlush(true);
+      }
+      if (annotation != null) {
+        ((ExperimentalBidirectionalStream.Builder) builder).addRequestAnnotation(annotation);
+      }
+      if (annotations != null) {
+        for (Object o : annotations) {
+          ((ExperimentalBidirectionalStream.Builder) builder).addRequestAnnotation(o);
+        }
+      }
+      setGrpcHeaders(builder);
+      stream = builder.build();
+      stream.start();
+    }
+
+    @Override
+    public void writeFrame(
+        WritableBuffer buffer, boolean endOfStream, boolean flush, int numMessages) {
+      synchronized (state.lock) {
+        if (state.cancelSent) {
+          return;
+        }
+        ByteBuffer byteBuffer;
+        if (buffer != null) {
+          byteBuffer = ((CronetWritableBuffer) buffer).buffer();
+          byteBuffer.flip();
+        } else {
+          byteBuffer = EMPTY_BUFFER;
+        }
+        onSendingBytes(byteBuffer.remaining());
+        if (!state.streamReady) {
+          state.enqueuePendingData(new PendingData(byteBuffer, endOfStream, flush));
+        } else {
+          streamWrite(byteBuffer, endOfStream, flush);
+        }
+      }
+    }
+
+    @Override
+    public void request(final int numMessages) {
+      synchronized (state.lock) {
+        state.requestMessagesFromDeframer(numMessages);
+      }
+    }
+
+    @Override
+    public void cancel(Status reason) {
+      synchronized (state.lock) {
+        if (state.cancelSent) {
+          return;
+        }
+        state.cancelSent = true;
+        state.cancelReason = reason;
+        state.clearPendingData();
+        if (stream != null) {
+          // Will report stream finish when BidirectionalStreamCallback.onCanceled is called.
+          stream.cancel();
+        } else {
+          transport.finishStream(CronetClientStream.this, reason);
+        }
+      }
+    }
+  }
+
+  class TransportState extends Http2ClientStreamTransportState {
+    private final Object lock;
+    @GuardedBy("lock")
+    private Queue<PendingData> pendingData = new LinkedList<PendingData>();
+    @GuardedBy("lock")
+    private boolean streamReady;
+    @GuardedBy("lock")
+    private boolean cancelSent = false;
+    @GuardedBy("lock")
+    private int bytesPendingProcess;
+    @GuardedBy("lock")
+    private Status cancelReason;
+    @GuardedBy("lock")
+    private boolean readClosed;
+    @GuardedBy("lock")
+    private boolean firstWriteComplete;
+
+    public TransportState(
+        int maxMessageSize, StatsTraceContext statsTraceCtx, Object lock,
+        TransportTracer transportTracer) {
+      super(maxMessageSize, statsTraceCtx, transportTracer);
+      this.lock = Preconditions.checkNotNull(lock, "lock");
+    }
+
+    @GuardedBy("lock")
+    public void start(StreamBuilderFactory factory) {
+      streamFactory = factory;
+    }
+
+    @GuardedBy("lock")
+    @Override
+    protected void onStreamAllocated() {
+      super.onStreamAllocated();
+    }
+
+    @GuardedBy("lock")
+    @Override
+    protected void http2ProcessingFailed(Status status, boolean stopDelivery, Metadata trailers) {
+      stream.cancel();
+      transportReportStatus(status, stopDelivery, trailers);
+    }
+
+    @GuardedBy("lock")
+    @Override
+    public void deframeFailed(Throwable cause) {
+      http2ProcessingFailed(Status.fromThrowable(cause), true, new Metadata());
+    }
+
+    @Override
+    public void runOnTransportThread(final Runnable r) {
+      synchronized (lock) {
+        r.run();
+      }
+    }
+
+    @GuardedBy("lock")
+    @Override
+    public void bytesRead(int processedBytes) {
+      bytesPendingProcess -= processedBytes;
+      if (bytesPendingProcess == 0 && !readClosed) {
+        if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+          Log.v(LOG_TAG, "BidirectionalStream.read");
+        }
+        stream.read(ByteBuffer.allocateDirect(READ_BUFFER_CAPACITY));
+      }
+    }
+
+    @GuardedBy("lock")
+    private void transportHeadersReceived(Metadata metadata, boolean endOfStream) {
+      if (endOfStream) {
+        transportTrailersReceived(metadata);
+      } else {
+        transportHeadersReceived(metadata);
+      }
+    }
+
+    @GuardedBy("lock")
+    private void transportDataReceived(ByteBuffer buffer, boolean endOfStream) {
+      bytesPendingProcess += buffer.remaining();
+      super.transportDataReceived(ReadableBuffers.wrap(buffer), endOfStream);
+    }
+
+    @GuardedBy("lock")
+    private void clearPendingData() {
+      for (PendingData data : pendingData) {
+        data.buffer.clear();
+      }
+      pendingData.clear();
+    }
+
+    @GuardedBy("lock")
+    private void enqueuePendingData(PendingData data) {
+      pendingData.add(data);
+    }
+
+    @GuardedBy("lock")
+    private void writeAllPendingData() {
+      for (PendingData data : pendingData) {
+        streamWrite(data.buffer, data.endOfStream, data.flush);
+      }
+      pendingData.clear();
+    }
+  }
+
+  // TODO(ericgribkoff): move header related method to a common place like GrpcUtil.
+  private static boolean isApplicationHeader(String key) {
+    // Don't allow reserved non HTTP/2 pseudo headers to be added
+    // HTTP/2 headers can not be created as keys because Header.Key disallows the ':' character.
+    return !CONTENT_TYPE_KEY.name().equalsIgnoreCase(key)
+        && !USER_AGENT_KEY.name().equalsIgnoreCase(key)
+        && !TE_HEADER.name().equalsIgnoreCase(key);
+  }
+
+  private void setGrpcHeaders(BidirectionalStream.Builder builder) {
+    // Psuedo-headers are set by cronet.
+    // All non-pseudo headers must come after pseudo headers.
+    // TODO(ericgribkoff): remove this and set it on CronetEngine after crbug.com/588204 gets fixed.
+    builder.addHeader(USER_AGENT_KEY.name(), userAgent);
+    builder.addHeader(CONTENT_TYPE_KEY.name(), GrpcUtil.CONTENT_TYPE_GRPC);
+    builder.addHeader("te", GrpcUtil.TE_TRAILERS);
+
+    // Now add any application-provided headers.
+    // TODO(ericgribkoff): make a String-based version to avoid unnecessary conversion between
+    // String and byte array.
+    byte[][] serializedHeaders = TransportFrameUtil.toHttp2Headers(headers);
+    for (int i = 0; i < serializedHeaders.length; i += 2) {
+      String key = new String(serializedHeaders[i], Charset.forName("UTF-8"));
+      // TODO(ericgribkoff): log an error or throw an exception
+      if (isApplicationHeader(key)) {
+        String value = new String(serializedHeaders[i + 1], Charset.forName("UTF-8"));
+        builder.addHeader(key, value);
+      }
+    }
+  }
+
+  private void streamWrite(ByteBuffer buffer, boolean endOfStream, boolean flush) {
+    if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+      Log.v(LOG_TAG, "BidirectionalStream.write");
+    }
+    stream.write(buffer, endOfStream);
+    if (flush) {
+      if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+        Log.v(LOG_TAG, "BidirectionalStream.flush");
+      }
+      stream.flush();
+    }
+  }
+
+  private void finishStream(Status status) {
+    transport.finishStream(this, status);
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    return Attributes.EMPTY;
+  }
+
+  class BidirectionalStreamCallback extends BidirectionalStream.Callback {
+    private List<Map.Entry<String, String>> trailerList;
+
+    @Override
+    public void onStreamReady(BidirectionalStream stream) {
+      if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+        Log.v(LOG_TAG, "onStreamReady");
+      }
+      synchronized (state.lock) {
+        // Now that the stream is ready, call the listener's onReady callback if
+        // appropriate.
+        state.onStreamAllocated();
+        state.streamReady = true;
+        state.writeAllPendingData();
+      }
+    }
+
+    @Override
+    public void onResponseHeadersReceived(BidirectionalStream stream, UrlResponseInfo info) {
+      if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+        Log.v(LOG_TAG, "onResponseHeadersReceived. Header=" + info.getAllHeadersAsList());
+        Log.v(LOG_TAG, "BidirectionalStream.read");
+      }
+      reportHeaders(info.getAllHeadersAsList(), false);
+      stream.read(ByteBuffer.allocateDirect(READ_BUFFER_CAPACITY));
+    }
+
+    @Override
+    public void onReadCompleted(BidirectionalStream stream, UrlResponseInfo info,
+        ByteBuffer buffer, boolean endOfStream) {
+      buffer.flip();
+      if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+        Log.v(LOG_TAG, "onReadCompleted. Size=" + buffer.remaining());
+      }
+
+      synchronized (state.lock) {
+        state.readClosed = endOfStream;
+        // The endOfStream in gRPC has a different meaning so we always call transportDataReceived
+        // with endOfStream=false.
+        if (buffer.remaining() != 0) {
+          state.transportDataReceived(buffer, false);
+        }
+      }
+      if (endOfStream && trailerList != null) {
+        // Process trailers if we have already received any.
+        reportHeaders(trailerList, true);
+      }
+    }
+
+    @Override
+    public void onWriteCompleted(BidirectionalStream stream, UrlResponseInfo info,
+        ByteBuffer buffer, boolean endOfStream) {
+      if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+        Log.v(LOG_TAG, "onWriteCompleted");
+      }
+      synchronized (state.lock) {
+        if (!state.firstWriteComplete) {
+          // Cronet API doesn't notify when headers are written to wire, but it occurs before first
+          // onWriteCompleted callback.
+          state.firstWriteComplete = true;
+          statsTraceCtx.clientOutboundHeaders();
+        }
+        state.onSentBytes(buffer.position());
+      }
+    }
+
+    @Override
+    public void onResponseTrailersReceived(BidirectionalStream stream, UrlResponseInfo info,
+        UrlResponseInfo.HeaderBlock trailers) {
+      processTrailers(trailers.getAsList());
+    }
+
+    // We need this method because UrlResponseInfo.HeaderBlock is a final class and cannot be
+    // mocked.
+    @VisibleForTesting
+    void processTrailers(List<Map.Entry<String, String>> trailerList) {
+      this.trailerList = trailerList;
+      boolean readClosed;
+      synchronized (state.lock) {
+        readClosed = state.readClosed;
+      }
+      if (readClosed) {
+        // There's no pending onReadCompleted callback so we can report trailers now.
+        reportHeaders(trailerList, true);
+      }
+      // Otherwise report trailers in onReadCompleted, or onSucceeded.
+      if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+        Log.v(LOG_TAG, "onResponseTrailersReceived. Trailer=" + trailerList.toString());
+      }
+    }
+
+    @Override
+    public void onSucceeded(BidirectionalStream stream, UrlResponseInfo info) {
+      if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+        Log.v(LOG_TAG, "onSucceeded");
+      }
+
+      if (!haveTrailersBeenReported()) {
+        if (trailerList != null) {
+          reportHeaders(trailerList, true);
+        } else if (info != null) {
+          reportHeaders(info.getAllHeadersAsList(), true);
+        } else {
+          throw new AssertionError("No response header or trailer");
+        }
+      }
+      finishStream(toGrpcStatus(info));
+    }
+
+    @Override
+    public void onFailed(BidirectionalStream stream, UrlResponseInfo info,
+        CronetException error) {
+      if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+        Log.v(LOG_TAG, "onFailed");
+      }
+      finishStream(Status.UNAVAILABLE.withCause(error));
+    }
+
+    @Override
+    public void onCanceled(BidirectionalStream stream, UrlResponseInfo info) {
+      if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
+        Log.v(LOG_TAG, "onCanceled");
+      }
+      Status status;
+      synchronized (state.lock) {
+        if (state.cancelReason != null) {
+          status = state.cancelReason;
+        } else if (info != null) {
+          status = toGrpcStatus(info);
+        } else {
+          status = Status.CANCELLED.withDescription("stream cancelled without reason");
+        }
+      }
+      finishStream(status);
+    }
+
+    private void reportHeaders(List<Map.Entry<String, String>> headers, boolean endOfStream) {
+      // TODO(ericgribkoff): create new utility methods to eliminate all these conversions
+      List<String> headerList = new ArrayList<>();
+      for (Map.Entry<String, String> entry : headers) {
+        headerList.add(entry.getKey());
+        headerList.add(entry.getValue());
+      }
+
+      byte[][] headerValues = new byte[headerList.size()][];
+      for (int i = 0; i < headerList.size(); i += 2) {
+        headerValues[i] = headerList.get(i).getBytes(Charset.forName("UTF-8"));
+        headerValues[i + 1] = headerList.get(i + 1).getBytes(Charset.forName("UTF-8"));
+      }
+      Metadata metadata =
+          InternalMetadata.newMetadata(TransportFrameUtil.toRawSerializedHeaders(headerValues));
+      synchronized (state.lock) {
+        // There's no pending onReadCompleted callback so we can report trailers now.
+        state.transportHeadersReceived(metadata, endOfStream);
+      }
+    }
+
+    private boolean haveTrailersBeenReported() {
+      synchronized (state.lock) {
+        return trailerList != null && state.readClosed;
+      }
+    }
+
+    private Status toGrpcStatus(UrlResponseInfo info) {
+      return GrpcUtil.httpStatusToGrpcStatus(info.getHttpStatusCode());
+    }
+  }
+
+  private static class PendingData {
+    ByteBuffer buffer;
+    boolean endOfStream;
+    boolean flush;
+
+    PendingData(ByteBuffer buffer, boolean endOfStream, boolean flush) {
+      this.buffer = buffer;
+      this.endOfStream = endOfStream;
+      this.flush = flush;
+    }
+  }
+}
diff --git a/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java b/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java
new file mode 100644
index 0000000..38b871d
--- /dev/null
+++ b/cronet/src/main/java/io/grpc/cronet/CronetClientTransport.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.cronet;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.Attributes;
+import io.grpc.CallCredentials;
+import io.grpc.CallOptions;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalLogId;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.SecurityLevel;
+import io.grpc.Status;
+import io.grpc.Status.Code;
+import io.grpc.cronet.CronetChannelBuilder.StreamBuilderFactory;
+import io.grpc.internal.ConnectionClientTransport;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.TransportTracer;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * A cronet-based {@link ConnectionClientTransport} implementation.
+ */
+class CronetClientTransport implements ConnectionClientTransport {
+  private final InternalLogId logId = InternalLogId.allocate(getClass().getName());
+  private final InetSocketAddress address;
+  private final String authority;
+  private final String userAgent;
+  private Listener listener;
+  private final Object lock = new Object();
+  @GuardedBy("lock")
+  private final Set<CronetClientStream> streams =
+      new HashSet<CronetClientStream>();
+  private final Executor executor;
+  private final int maxMessageSize;
+  private final boolean alwaysUsePut;
+  private final TransportTracer transportTracer;
+  private final Attributes attrs;
+  // Indicates the transport is in go-away state: no new streams will be processed,
+  // but existing streams may continue.
+  @GuardedBy("lock")
+  private boolean goAway;
+  // Used to indicate the special phase while we are going to enter go-away state but before
+  // goAway is turned to true, see the comment at where this is set about why it is needed.
+  @GuardedBy("lock")
+  private boolean startedGoAway;
+  @GuardedBy("lock")
+  private Status goAwayStatus;
+  @GuardedBy("lock")
+  private boolean stopped;
+  @GuardedBy("lock")
+  // Whether this transport has started.
+  private boolean started;
+  private StreamBuilderFactory streamFactory;
+
+  CronetClientTransport(
+      StreamBuilderFactory streamFactory,
+      InetSocketAddress address,
+      String authority,
+      @Nullable String userAgent,
+      Executor executor,
+      int maxMessageSize,
+      boolean alwaysUsePut,
+      TransportTracer transportTracer) {
+    this.address = Preconditions.checkNotNull(address, "address");
+    this.authority = authority;
+    this.userAgent = GrpcUtil.getGrpcUserAgent("cronet", userAgent);
+    this.maxMessageSize = maxMessageSize;
+    this.alwaysUsePut = alwaysUsePut;
+    this.executor = Preconditions.checkNotNull(executor, "executor");
+    this.streamFactory = Preconditions.checkNotNull(streamFactory, "streamFactory");
+    this.transportTracer = Preconditions.checkNotNull(transportTracer, "transportTracer");
+    this.attrs = Attributes.newBuilder()
+        .set(CallCredentials.ATTR_AUTHORITY, authority)
+        .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
+        .build();
+  }
+
+  @Override
+  public ListenableFuture<SocketStats> getStats() {
+    SettableFuture<SocketStats> f = SettableFuture.create();
+    f.set(null);
+    return f;
+  }
+
+  @Override
+  public CronetClientStream newStream(final MethodDescriptor<?, ?> method, final Metadata headers,
+      final CallOptions callOptions) {
+    Preconditions.checkNotNull(method, "method");
+    Preconditions.checkNotNull(headers, "headers");
+
+    final String defaultPath = "/" + method.getFullMethodName();
+    final String url = "https://" + authority + defaultPath;
+
+    final StatsTraceContext statsTraceCtx =
+        StatsTraceContext.newClientContext(callOptions, headers);
+    class StartCallback implements Runnable {
+      final CronetClientStream clientStream = new CronetClientStream(
+          url, userAgent, executor, headers, CronetClientTransport.this, this, lock, maxMessageSize,
+          alwaysUsePut, method, statsTraceCtx, callOptions, transportTracer);
+
+      @Override
+      public void run() {
+        synchronized (lock) {
+          if (goAway) {
+            clientStream.transportState().transportReportStatus(goAwayStatus, true, new Metadata());
+          } else if (started) {
+            startStream(clientStream);
+          } else {
+            throw new AssertionError("Transport is not started");
+          }
+        }
+      }
+    }
+
+    return new StartCallback().clientStream;
+  }
+
+  @GuardedBy("lock")
+  private void startStream(CronetClientStream stream) {
+    streams.add(stream);
+    stream.transportState().start(streamFactory);
+  }
+
+  @Override
+  public Runnable start(Listener listener) {
+    this.listener = Preconditions.checkNotNull(listener, "listener");
+    synchronized (lock) {
+      started = true;
+    }
+    return new Runnable() {
+      @Override
+      public void run() {
+        // Listener callbacks should not be called simultaneously
+        CronetClientTransport.this.listener.transportReady();
+      }
+    };
+  }
+
+  @Override
+  public String toString() {
+    return super.toString() + "(" + address + ")";
+  }
+
+  public void shutdown() {
+    shutdown(Status.UNAVAILABLE.withDescription("Transport stopped"));
+  }
+
+  @Override
+  public void shutdown(Status status) {
+    synchronized (lock) {
+      if (goAway) {
+        return;
+      }
+    }
+
+    startGoAway(status);
+  }
+
+  @Override
+  public void shutdownNow(Status status) {
+    shutdown(status);
+    ArrayList<CronetClientStream> streamsCopy;
+    synchronized (lock) {
+      // A copy is always necessary since cancel() can call finishStream() which calls
+      // streams.remove()
+      streamsCopy = new ArrayList<>(streams);
+    }
+    for (int i = 0; i < streamsCopy.size(); i++) {
+      // Avoid deadlock by calling into stream without lock held
+      streamsCopy.get(i).cancel(status);
+    }
+    stopIfNecessary();
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    return attrs;
+  }
+
+  private void startGoAway(Status status) {
+    synchronized (lock) {
+      if (startedGoAway) {
+        // Another go-away is in progress, ignore this one.
+        return;
+      }
+      // We use startedGoAway here instead of goAway, because once the goAway becomes true, other
+      // thread in stopIfNecessary() may stop the transport and cause the
+      // listener.transportTerminated() be called before listener.transportShutdown().
+      startedGoAway = true;
+    }
+
+    listener.transportShutdown(status);
+
+    synchronized (lock) {
+      goAway = true;
+      goAwayStatus = status;
+    }
+
+    stopIfNecessary();
+  }
+
+  @Override
+  public void ping(final PingCallback callback, Executor executor) {
+    // TODO(ericgribkoff): depend on cronet implemenetation
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public InternalLogId getLogId() {
+    return logId;
+  }
+
+  /**
+   * When the transport is in goAway state, we should stop it once all active streams finish.
+   */
+  void stopIfNecessary() {
+    synchronized (lock) {
+      if (goAway && !stopped && streams.size() == 0) {
+        stopped = true;
+      } else {
+        return;
+      }
+    }
+    listener.transportTerminated();
+  }
+
+  void finishStream(CronetClientStream stream, Status status) {
+    synchronized (lock) {
+      if (streams.remove(stream)) {
+        boolean isCancelled = (status.getCode() == Code.CANCELLED
+            || status.getCode() == Code.DEADLINE_EXCEEDED);
+        stream.transportState().transportReportStatus(status, isCancelled, new Metadata());
+      } else {
+        return;
+      }
+    }
+    stopIfNecessary();
+  }
+}
diff --git a/cronet/src/main/java/io/grpc/cronet/CronetWritableBuffer.java b/cronet/src/main/java/io/grpc/cronet/CronetWritableBuffer.java
new file mode 100644
index 0000000..9895206
--- /dev/null
+++ b/cronet/src/main/java/io/grpc/cronet/CronetWritableBuffer.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.cronet;
+
+import com.google.common.base.Preconditions;
+import io.grpc.internal.WritableBuffer;
+import java.nio.ByteBuffer;
+
+class CronetWritableBuffer implements WritableBuffer {
+  private final ByteBuffer buffer;
+
+  public CronetWritableBuffer(ByteBuffer buffer, int capacity) {
+    this.buffer = Preconditions.checkNotNull(buffer, "buffer");
+  }
+
+  @Override
+  public void write(byte[] src, int srcIndex, int length) {
+    buffer.put(src, srcIndex, length);
+  }
+
+  @Override
+  public void write(byte b) {
+    buffer.put(b);
+  }
+
+  @Override
+  public int writableBytes() {
+    return buffer.remaining();
+  }
+
+  @Override
+  public int readableBytes() {
+    return buffer.position();
+  }
+
+  @Override
+  public void release() {
+  }
+
+  ByteBuffer buffer() {
+    return buffer;
+  }
+}
diff --git a/cronet/src/main/java/io/grpc/cronet/CronetWritableBufferAllocator.java b/cronet/src/main/java/io/grpc/cronet/CronetWritableBufferAllocator.java
new file mode 100644
index 0000000..0ccb211
--- /dev/null
+++ b/cronet/src/main/java/io/grpc/cronet/CronetWritableBufferAllocator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.cronet;
+
+import io.grpc.internal.WritableBuffer;
+import io.grpc.internal.WritableBufferAllocator;
+import java.nio.ByteBuffer;
+
+/**
+ * The default allocator for {@link CronetWritableBuffer}s used by the Cronet transport.
+ */
+class CronetWritableBufferAllocator implements WritableBufferAllocator {
+  // Set the maximum buffer size to 1MB
+  private static final int MAX_BUFFER = 1024 * 1024;
+
+  /**
+   * Construct a new instance.
+   */
+  CronetWritableBufferAllocator() {
+  }
+
+  @Override
+  public WritableBuffer allocate(int capacityHint) {
+    capacityHint = Math.min(MAX_BUFFER, capacityHint);
+    return new CronetWritableBuffer(ByteBuffer.allocateDirect(capacityHint), capacityHint);
+  }
+}
diff --git a/cronet/src/test/java/io/grpc/cronet/CronetChannelBuilderTest.java b/cronet/src/test/java/io/grpc/cronet/CronetChannelBuilderTest.java
new file mode 100644
index 0000000..28ac249
--- /dev/null
+++ b/cronet/src/test/java/io/grpc/cronet/CronetChannelBuilderTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.cronet;
+
+import static io.grpc.internal.GrpcUtil.TIMER_SERVICE;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import io.grpc.CallOptions;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.cronet.CronetChannelBuilder.CronetTransportFactory;
+import io.grpc.internal.ClientTransportFactory;
+import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
+import io.grpc.internal.SharedResourceHolder;
+import io.grpc.testing.TestMethodDescriptors;
+import java.net.InetSocketAddress;
+import java.util.concurrent.ScheduledExecutorService;
+import org.chromium.net.ExperimentalCronetEngine;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class CronetChannelBuilderTest {
+
+  @Mock private ExperimentalCronetEngine mockEngine;
+
+  private MethodDescriptor<?, ?> method = TestMethodDescriptors.voidMethod();
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+  }
+
+  @Test
+  public void alwaysUsePutTrue_cronetStreamIsIdempotent() throws Exception {
+    CronetChannelBuilder builder =
+        CronetChannelBuilder.forAddress("address", 1234, mockEngine).alwaysUsePut(true);
+    CronetTransportFactory transportFactory =
+        (CronetTransportFactory) builder.buildTransportFactory();
+    CronetClientTransport transport =
+        (CronetClientTransport)
+            transportFactory.newClientTransport(
+                new InetSocketAddress("localhost", 443), new ClientTransportOptions());
+    CronetClientStream stream = transport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+
+    assertTrue(stream.idempotent);
+  }
+
+  @Test
+  public void alwaysUsePut_defaultsToFalse() throws Exception {
+    CronetChannelBuilder builder = CronetChannelBuilder.forAddress("address", 1234, mockEngine);
+    CronetTransportFactory transportFactory =
+        (CronetTransportFactory) builder.buildTransportFactory();
+    CronetClientTransport transport =
+        (CronetClientTransport)
+            transportFactory.newClientTransport(
+                new InetSocketAddress("localhost", 443), new ClientTransportOptions());
+    CronetClientStream stream = transport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+
+    assertFalse(stream.idempotent);
+  }
+
+  @Test
+  public void scheduledExecutorService_default() {
+    CronetChannelBuilder builder = CronetChannelBuilder.forAddress("address", 1234, mockEngine);
+    ClientTransportFactory clientTransportFactory = builder.buildTransportFactory();
+    assertSame(
+        SharedResourceHolder.get(TIMER_SERVICE),
+        clientTransportFactory.getScheduledExecutorService());
+
+    SharedResourceHolder.release(
+        TIMER_SERVICE, clientTransportFactory.getScheduledExecutorService());
+    clientTransportFactory.close();
+  }
+
+  @Test
+  public void scheduledExecutorService_custom() {
+    CronetChannelBuilder builder = CronetChannelBuilder.forAddress("address", 1234, mockEngine);
+    ScheduledExecutorService scheduledExecutorService = mock(ScheduledExecutorService.class);
+
+    CronetChannelBuilder builder1 = builder.scheduledExecutorService(scheduledExecutorService);
+    assertSame(builder, builder1);
+
+    ClientTransportFactory clientTransportFactory = builder1.buildTransportFactory();
+    assertSame(scheduledExecutorService, clientTransportFactory.getScheduledExecutorService());
+
+    clientTransportFactory.close();
+  }
+}
diff --git a/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java b/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java
new file mode 100644
index 0000000..3d2ad58
--- /dev/null
+++ b/cronet/src/test/java/io/grpc/cronet/CronetClientStreamTest.java
@@ -0,0 +1,801 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.cronet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.io.BaseEncoding;
+import io.grpc.CallOptions;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.cronet.CronetChannelBuilder.StreamBuilderFactory;
+import io.grpc.internal.ClientStreamListener;
+import io.grpc.internal.ClientStreamListener.RpcProgress;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.StreamListener.MessageProducer;
+import io.grpc.internal.TransportTracer;
+import io.grpc.internal.WritableBuffer;
+import io.grpc.testing.TestMethodDescriptors;
+import java.io.ByteArrayInputStream;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import org.chromium.net.BidirectionalStream;
+import org.chromium.net.CronetException;
+import org.chromium.net.ExperimentalBidirectionalStream;
+import org.chromium.net.UrlResponseInfo;
+import org.chromium.net.impl.UrlResponseInfoImpl;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class CronetClientStreamTest {
+
+  @Mock private CronetClientTransport transport;
+  private Metadata metadata = new Metadata();
+  @Mock private StreamBuilderFactory factory;
+  @Mock private ExperimentalBidirectionalStream cronetStream;
+  @Mock private Executor executor;
+  @Mock private ClientStreamListener clientListener;
+  @Mock private ExperimentalBidirectionalStream.Builder builder;
+  private final Object lock = new Object();
+  private final TransportTracer transportTracer = TransportTracer.getDefaultFactory().create();
+  CronetClientStream clientStream;
+
+  private MethodDescriptor.Marshaller<Void> marshaller = TestMethodDescriptors.voidMarshaller();
+
+  private MethodDescriptor<?, ?> method = TestMethodDescriptors.voidMethod();
+
+  private static class SetStreamFactoryRunnable implements Runnable {
+    private final StreamBuilderFactory factory;
+    private CronetClientStream stream;
+
+    SetStreamFactoryRunnable(StreamBuilderFactory factory) {
+      this.factory = factory;
+    }
+
+    void setStream(CronetClientStream stream) {
+      this.stream = stream;
+    }
+
+    @Override
+    public void run() {
+      assertTrue(stream != null);
+      stream.transportState().start(factory);
+    }
+  }
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+
+    SetStreamFactoryRunnable callback = new SetStreamFactoryRunnable(factory);
+    clientStream =
+        new CronetClientStream(
+            "https://www.google.com:443",
+            "cronet",
+            executor,
+            metadata,
+            transport,
+            callback,
+            lock,
+            100,
+            false /* alwaysUsePut */,
+            method,
+            StatsTraceContext.NOOP,
+            CallOptions.DEFAULT,
+            transportTracer);
+    callback.setStream(clientStream);
+    when(factory.newBidirectionalStreamBuilder(
+            any(String.class), any(BidirectionalStream.Callback.class), any(Executor.class)))
+        .thenReturn(builder);
+    when(builder.build()).thenReturn(cronetStream);
+    clientStream.start(clientListener);
+  }
+
+  @Test
+  public void startStream() {
+    verify(factory)
+        .newBidirectionalStreamBuilder(
+            eq("https://www.google.com:443"),
+            isA(BidirectionalStream.Callback.class),
+            eq(executor));
+    verify(builder).build();
+    // At least content type and trailer headers are set.
+    verify(builder, atLeast(2)).addHeader(isA(String.class), isA(String.class));
+    // addRequestAnnotation should only be called when we explicitly add the CRONET_ANNOTATION_KEY
+    // to CallOptions.
+    verify(builder, times(0)).addRequestAnnotation(isA(Object.class));
+    verify(builder, times(0)).setHttpMethod(any(String.class));
+    verify(cronetStream).start();
+  }
+
+  @Test
+  public void write() {
+    ArgumentCaptor<BidirectionalStream.Callback> callbackCaptor =
+        ArgumentCaptor.forClass(BidirectionalStream.Callback.class);
+    verify(factory)
+        .newBidirectionalStreamBuilder(
+            isA(String.class), callbackCaptor.capture(), isA(Executor.class));
+    BidirectionalStream.Callback callback = callbackCaptor.getValue();
+
+    // Create 5 frames to send.
+    CronetWritableBufferAllocator allocator = new CronetWritableBufferAllocator();
+    String[] requests = new String[5];
+    WritableBuffer[] buffers = new WritableBuffer[5];
+    for (int i = 0; i < 5; ++i) {
+      requests[i] = new String("request" + String.valueOf(i));
+      buffers[i] = allocator.allocate(requests[i].length());
+      buffers[i].write(requests[i].getBytes(Charset.forName("UTF-8")), 0, requests[i].length());
+      // The 3rd and 5th writeFrame calls have flush=true.
+      clientStream.abstractClientStreamSink().writeFrame(buffers[i], false, i == 2 || i == 4, 1);
+    }
+    // BidirectionalStream.write is not called because stream is not ready yet.
+    verify(cronetStream, times(0)).write(isA(ByteBuffer.class), isA(Boolean.class));
+
+    // Stream is ready.
+    callback.onStreamReady(cronetStream);
+    // 5 writes are called.
+    verify(cronetStream, times(5)).write(isA(ByteBuffer.class), eq(false));
+    ByteBuffer fakeBuffer = ByteBuffer.allocateDirect(8);
+    fakeBuffer.position(8);
+    verify(cronetStream, times(2)).flush();
+
+    // 5 onWriteCompleted callbacks for previous writes.
+    callback.onWriteCompleted(cronetStream, null, fakeBuffer, false);
+    callback.onWriteCompleted(cronetStream, null, fakeBuffer, false);
+    callback.onWriteCompleted(cronetStream, null, fakeBuffer, false);
+    callback.onWriteCompleted(cronetStream, null, fakeBuffer, false);
+    callback.onWriteCompleted(cronetStream, null, fakeBuffer, false);
+
+    // All pending data has been sent. onWriteCompleted callback will not trigger any additional
+    // write call.
+    verify(cronetStream, times(5)).write(isA(ByteBuffer.class), eq(false));
+
+    // Send end of stream. write will be immediately called since stream is ready.
+    clientStream.abstractClientStreamSink().writeFrame(null, true, true, 1);
+    verify(cronetStream, times(1)).write(isA(ByteBuffer.class), eq(true));
+    verify(cronetStream, times(3)).flush();
+  }
+
+  private static List<Map.Entry<String, String>> responseHeader(String status) {
+    Map<String, String> headers = new HashMap<String, String>();
+    headers.put(":status", status);
+    headers.put("content-type", "application/grpc");
+    headers.put("test-key", "test-value");
+    List<Map.Entry<String, String>> headerList = new ArrayList<Map.Entry<String, String>>(3);
+    for (Map.Entry<String, String> entry : headers.entrySet()) {
+      headerList.add(entry);
+    }
+    return headerList;
+  }
+
+  private static List<Map.Entry<String, String>> trailers(int status) {
+    Map<String, String> trailers = new HashMap<String, String>();
+    trailers.put("grpc-status", String.valueOf(status));
+    trailers.put("content-type", "application/grpc");
+    trailers.put("test-trailer-key", "test-trailer-value");
+    List<Map.Entry<String, String>> trailerList = new ArrayList<Map.Entry<String, String>>(3);
+    for (Map.Entry<String, String> entry : trailers.entrySet()) {
+      trailerList.add(entry);
+    }
+    return trailerList;
+  }
+
+  private static ByteBuffer createMessageFrame(byte[] bytes) {
+    ByteBuffer buffer = ByteBuffer.allocate(1 + 4 + bytes.length);
+    buffer.put((byte) 0 /* UNCOMPRESSED */);
+    buffer.putInt(bytes.length);
+    buffer.put(bytes);
+    return buffer;
+  }
+
+  @Test
+  public void read() {
+    ArgumentCaptor<BidirectionalStream.Callback> callbackCaptor =
+        ArgumentCaptor.forClass(BidirectionalStream.Callback.class);
+    verify(factory)
+        .newBidirectionalStreamBuilder(
+            isA(String.class), callbackCaptor.capture(), isA(Executor.class));
+    BidirectionalStream.Callback callback = callbackCaptor.getValue();
+
+    // Read is not called until we receive the response header.
+    verify(cronetStream, times(0)).read(isA(ByteBuffer.class));
+    UrlResponseInfo info =
+        new UrlResponseInfoImpl(
+            new ArrayList<String>(), 200, "", responseHeader("200"), false, "", "");
+    callback.onResponseHeadersReceived(cronetStream, info);
+    verify(cronetStream, times(1)).read(isA(ByteBuffer.class));
+    ArgumentCaptor<Metadata> metadataCaptor = ArgumentCaptor.forClass(Metadata.class);
+    verify(clientListener).headersRead(metadataCaptor.capture());
+    // Verify recevied headers.
+    Metadata metadata = metadataCaptor.getValue();
+    assertEquals(
+        "application/grpc",
+        metadata.get(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER)));
+    assertEquals(
+        "test-value", metadata.get(Metadata.Key.of("test-key", Metadata.ASCII_STRING_MARSHALLER)));
+
+    callback.onReadCompleted(
+        cronetStream,
+        info,
+        (ByteBuffer) createMessageFrame(new String("response1").getBytes(Charset.forName("UTF-8"))),
+        false);
+    // Haven't request any message, so no callback is called here.
+    verify(clientListener, times(0)).messagesAvailable(isA(MessageProducer.class));
+    verify(cronetStream, times(1)).read(isA(ByteBuffer.class));
+    // Request one message
+    clientStream.request(1);
+    verify(clientListener, times(1)).messagesAvailable(isA(MessageProducer.class));
+    verify(cronetStream, times(2)).read(isA(ByteBuffer.class));
+
+    // BidirectionalStream.read will not be called again after receiving endOfStream(empty buffer).
+    clientStream.request(1);
+    callback.onReadCompleted(cronetStream, info, ByteBuffer.allocate(0), true);
+    verify(clientListener, times(1)).messagesAvailable(isA(MessageProducer.class));
+    verify(cronetStream, times(2)).read(isA(ByteBuffer.class));
+  }
+
+  @Test
+  public void streamSucceeded() {
+    ArgumentCaptor<BidirectionalStream.Callback> callbackCaptor =
+        ArgumentCaptor.forClass(BidirectionalStream.Callback.class);
+    verify(factory)
+        .newBidirectionalStreamBuilder(
+            isA(String.class), callbackCaptor.capture(), isA(Executor.class));
+    BidirectionalStream.Callback callback = callbackCaptor.getValue();
+
+    callback.onStreamReady(cronetStream);
+    verify(cronetStream, times(0)).write(isA(ByteBuffer.class), isA(Boolean.class));
+    // Send the first data frame.
+    CronetWritableBufferAllocator allocator = new CronetWritableBufferAllocator();
+    String request = new String("request");
+    WritableBuffer writableBuffer = allocator.allocate(request.length());
+    writableBuffer.write(request.getBytes(Charset.forName("UTF-8")), 0, request.length());
+    clientStream.abstractClientStreamSink().writeFrame(writableBuffer, false, true, 1);
+    ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
+    verify(cronetStream, times(1)).write(bufferCaptor.capture(), isA(Boolean.class));
+    ByteBuffer buffer = bufferCaptor.getValue();
+    buffer.position(request.length());
+    verify(cronetStream, times(1)).flush();
+
+    // Receive response header
+    clientStream.request(2);
+    UrlResponseInfo info =
+        new UrlResponseInfoImpl(
+            new ArrayList<String>(), 200, "", responseHeader("200"), false, "", "");
+    callback.onResponseHeadersReceived(cronetStream, info);
+    verify(cronetStream, times(1)).read(isA(ByteBuffer.class));
+    // Receive one message
+    callback.onReadCompleted(
+        cronetStream,
+        info,
+        (ByteBuffer) createMessageFrame(new String("response").getBytes(Charset.forName("UTF-8"))),
+        false);
+    verify(clientListener, times(1)).messagesAvailable(isA(MessageProducer.class));
+    verify(cronetStream, times(2)).read(isA(ByteBuffer.class));
+
+    // Send endOfStream
+    callback.onWriteCompleted(cronetStream, null, buffer, false);
+    clientStream.abstractClientStreamSink().writeFrame(null, true, true, 1);
+    verify(cronetStream, times(2)).write(isA(ByteBuffer.class), isA(Boolean.class));
+    verify(cronetStream, times(2)).flush();
+
+    // Receive trailer
+    ((CronetClientStream.BidirectionalStreamCallback) callback).processTrailers(trailers(0));
+    callback.onSucceeded(cronetStream, info);
+
+    // Verify trailer
+    ArgumentCaptor<Metadata> trailerCaptor = ArgumentCaptor.forClass(Metadata.class);
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(clientListener)
+        .closed(statusCaptor.capture(), isA(RpcProgress.class), trailerCaptor.capture());
+    // Verify recevied headers.
+    Metadata trailers = trailerCaptor.getValue();
+    Status status = statusCaptor.getValue();
+    assertEquals(
+        "test-trailer-value",
+        trailers.get(Metadata.Key.of("test-trailer-key", Metadata.ASCII_STRING_MARSHALLER)));
+    assertEquals(
+        "application/grpc",
+        trailers.get(Metadata.Key.of("content-type", Metadata.ASCII_STRING_MARSHALLER)));
+    assertTrue(status.isOk());
+  }
+
+  @Test
+  public void streamSucceededWithGrpcError() {
+    ArgumentCaptor<BidirectionalStream.Callback> callbackCaptor =
+        ArgumentCaptor.forClass(BidirectionalStream.Callback.class);
+    verify(factory)
+        .newBidirectionalStreamBuilder(
+            isA(String.class), callbackCaptor.capture(), isA(Executor.class));
+    BidirectionalStream.Callback callback = callbackCaptor.getValue();
+
+    callback.onStreamReady(cronetStream);
+    verify(cronetStream, times(0)).write(isA(ByteBuffer.class), isA(Boolean.class));
+    clientStream.abstractClientStreamSink().writeFrame(null, true, true, 1);
+    verify(cronetStream, times(1)).write(isA(ByteBuffer.class), isA(Boolean.class));
+    verify(cronetStream, times(1)).flush();
+
+    // Receive response header
+    clientStream.request(2);
+    UrlResponseInfo info =
+        new UrlResponseInfoImpl(
+            new ArrayList<String>(), 200, "", responseHeader("200"), false, "", "");
+    callback.onResponseHeadersReceived(cronetStream, info);
+    verify(cronetStream, times(1)).read(isA(ByteBuffer.class));
+
+    // Receive trailer
+    callback.onReadCompleted(cronetStream, null, ByteBuffer.allocate(0), true);
+    ((CronetClientStream.BidirectionalStreamCallback) callback)
+        .processTrailers(trailers(Status.PERMISSION_DENIED.getCode().value()));
+    callback.onSucceeded(cronetStream, info);
+
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(clientListener)
+        .closed(statusCaptor.capture(), isA(RpcProgress.class), isA(Metadata.class));
+    // Verify error status.
+    Status status = statusCaptor.getValue();
+    assertFalse(status.isOk());
+    assertEquals(Status.PERMISSION_DENIED.getCode(), status.getCode());
+  }
+
+  @Test
+  public void streamFailed() {
+    ArgumentCaptor<BidirectionalStream.Callback> callbackCaptor =
+        ArgumentCaptor.forClass(BidirectionalStream.Callback.class);
+    verify(factory)
+        .newBidirectionalStreamBuilder(
+            isA(String.class), callbackCaptor.capture(), isA(Executor.class));
+    BidirectionalStream.Callback callback = callbackCaptor.getValue();
+
+    // Nothing happens and stream fails
+
+    CronetException exception = mock(CronetException.class);
+    callback.onFailed(cronetStream, null, exception);
+    verify(transport).finishStream(eq(clientStream), isA(Status.class));
+    // finishStream calls transportReportStatus.
+    clientStream.transportState().transportReportStatus(Status.UNAVAILABLE, false, new Metadata());
+
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(clientListener)
+        .closed(statusCaptor.capture(), isA(RpcProgress.class), isA(Metadata.class));
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.UNAVAILABLE.getCode(), status.getCode());
+  }
+
+  @Test
+  public void streamFailedAfterResponseHeaderReceived() {
+    ArgumentCaptor<BidirectionalStream.Callback> callbackCaptor =
+        ArgumentCaptor.forClass(BidirectionalStream.Callback.class);
+    verify(factory)
+        .newBidirectionalStreamBuilder(
+            isA(String.class), callbackCaptor.capture(), isA(Executor.class));
+    BidirectionalStream.Callback callback = callbackCaptor.getValue();
+
+    // Receive response header
+    UrlResponseInfo info =
+        new UrlResponseInfoImpl(
+            new ArrayList<String>(), 200, "", responseHeader("200"), false, "", "");
+    callback.onResponseHeadersReceived(cronetStream, info);
+
+    CronetException exception = mock(CronetException.class);
+    callback.onFailed(cronetStream, info, exception);
+    verify(transport).finishStream(eq(clientStream), isA(Status.class));
+    // finishStream calls transportReportStatus.
+    clientStream.transportState().transportReportStatus(Status.UNAVAILABLE, false, new Metadata());
+
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(clientListener)
+        .closed(statusCaptor.capture(), isA(RpcProgress.class), isA(Metadata.class));
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.UNAVAILABLE.getCode(), status.getCode());
+  }
+
+  @Test
+  public void streamFailedAfterTrailerReceived() {
+    ArgumentCaptor<BidirectionalStream.Callback> callbackCaptor =
+        ArgumentCaptor.forClass(BidirectionalStream.Callback.class);
+    verify(factory)
+        .newBidirectionalStreamBuilder(
+            isA(String.class), callbackCaptor.capture(), isA(Executor.class));
+    BidirectionalStream.Callback callback = callbackCaptor.getValue();
+
+    // Receive response header
+    UrlResponseInfo info =
+        new UrlResponseInfoImpl(
+            new ArrayList<String>(), 200, "", responseHeader("200"), false, "", "");
+    callback.onResponseHeadersReceived(cronetStream, info);
+
+    // Report trailer but not endOfStream.
+    ((CronetClientStream.BidirectionalStreamCallback) callback).processTrailers(trailers(0));
+
+    CronetException exception = mock(CronetException.class);
+    callback.onFailed(cronetStream, info, exception);
+    verify(transport).finishStream(eq(clientStream), isA(Status.class));
+    // finishStream calls transportReportStatus.
+    clientStream.transportState().transportReportStatus(Status.UNAVAILABLE, false, new Metadata());
+
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(clientListener)
+        .closed(statusCaptor.capture(), isA(RpcProgress.class), isA(Metadata.class));
+    Status status = statusCaptor.getValue();
+    // Stream has already finished so OK status should be reported.
+    assertEquals(Status.UNAVAILABLE.getCode(), status.getCode());
+  }
+
+  @Test
+  public void streamFailedAfterTrailerAndEndOfStreamReceived() {
+    ArgumentCaptor<BidirectionalStream.Callback> callbackCaptor =
+        ArgumentCaptor.forClass(BidirectionalStream.Callback.class);
+    verify(factory)
+        .newBidirectionalStreamBuilder(
+            isA(String.class), callbackCaptor.capture(), isA(Executor.class));
+    BidirectionalStream.Callback callback = callbackCaptor.getValue();
+
+    // Receive response header
+    UrlResponseInfo info =
+        new UrlResponseInfoImpl(
+            new ArrayList<String>(), 200, "", responseHeader("200"), false, "", "");
+    callback.onResponseHeadersReceived(cronetStream, info);
+
+    // Report trailer and endOfStream
+    callback.onReadCompleted(cronetStream, null, ByteBuffer.allocate(0), true);
+    ((CronetClientStream.BidirectionalStreamCallback) callback).processTrailers(trailers(0));
+
+    CronetException exception = mock(CronetException.class);
+    callback.onFailed(cronetStream, info, exception);
+    verify(transport).finishStream(eq(clientStream), isA(Status.class));
+    // finishStream calls transportReportStatus.
+    clientStream.transportState().transportReportStatus(Status.UNAVAILABLE, false, new Metadata());
+
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(clientListener)
+        .closed(statusCaptor.capture(), isA(RpcProgress.class), isA(Metadata.class));
+    Status status = statusCaptor.getValue();
+    // Stream has already finished so OK status should be reported.
+    assertEquals(Status.OK.getCode(), status.getCode());
+  }
+
+  @Test
+  public void cancelStream() {
+    ArgumentCaptor<BidirectionalStream.Callback> callbackCaptor =
+        ArgumentCaptor.forClass(BidirectionalStream.Callback.class);
+    verify(factory)
+        .newBidirectionalStreamBuilder(
+            isA(String.class), callbackCaptor.capture(), isA(Executor.class));
+    BidirectionalStream.Callback callback = callbackCaptor.getValue();
+
+    // Cancel the stream
+    clientStream.cancel(Status.DEADLINE_EXCEEDED);
+    verify(transport, times(0)).finishStream(eq(clientStream), isA(Status.class));
+
+    callback.onCanceled(cronetStream, null);
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(transport, times(1)).finishStream(eq(clientStream), statusCaptor.capture());
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.DEADLINE_EXCEEDED.getCode(), status.getCode());
+  }
+
+  @Test
+  public void reportTrailersWhenTrailersReceivedBeforeReadClosed() {
+    ArgumentCaptor<BidirectionalStream.Callback> callbackCaptor =
+        ArgumentCaptor.forClass(BidirectionalStream.Callback.class);
+    verify(factory)
+        .newBidirectionalStreamBuilder(
+            isA(String.class), callbackCaptor.capture(), isA(Executor.class));
+    BidirectionalStream.Callback callback = callbackCaptor.getValue();
+
+    callback.onStreamReady(cronetStream);
+    UrlResponseInfo info =
+        new UrlResponseInfoImpl(
+            new ArrayList<String>(), 200, "", responseHeader("200"), false, "", "");
+    callback.onResponseHeadersReceived(cronetStream, info);
+    // Receive trailer first
+    ((CronetClientStream.BidirectionalStreamCallback) callback)
+        .processTrailers(trailers(Status.UNAUTHENTICATED.getCode().value()));
+    verify(clientListener, times(0))
+        .closed(isA(Status.class), isA(RpcProgress.class), isA(Metadata.class));
+
+    // Receive cronet's endOfStream
+    callback.onReadCompleted(cronetStream, null, ByteBuffer.allocate(0), true);
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(clientListener, times(1))
+        .closed(statusCaptor.capture(), isA(RpcProgress.class), isA(Metadata.class));
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.UNAUTHENTICATED.getCode(), status.getCode());
+  }
+
+  @Test
+  public void reportTrailersWhenTrailersReceivedAfterReadClosed() {
+    ArgumentCaptor<BidirectionalStream.Callback> callbackCaptor =
+        ArgumentCaptor.forClass(BidirectionalStream.Callback.class);
+    verify(factory)
+        .newBidirectionalStreamBuilder(
+            isA(String.class), callbackCaptor.capture(), isA(Executor.class));
+    BidirectionalStream.Callback callback = callbackCaptor.getValue();
+
+    callback.onStreamReady(cronetStream);
+    UrlResponseInfo info =
+        new UrlResponseInfoImpl(
+            new ArrayList<String>(), 200, "", responseHeader("200"), false, "", "");
+    callback.onResponseHeadersReceived(cronetStream, info);
+    // Receive cronet's endOfStream
+    callback.onReadCompleted(cronetStream, null, ByteBuffer.allocate(0), true);
+    verify(clientListener, times(0))
+        .closed(isA(Status.class), isA(RpcProgress.class), isA(Metadata.class));
+
+    // Receive trailer
+    ((CronetClientStream.BidirectionalStreamCallback) callback)
+        .processTrailers(trailers(Status.UNAUTHENTICATED.getCode().value()));
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    verify(clientListener, times(1))
+        .closed(statusCaptor.capture(), isA(RpcProgress.class), isA(Metadata.class));
+    Status status = statusCaptor.getValue();
+    assertEquals(Status.UNAUTHENTICATED.getCode(), status.getCode());
+  }
+
+  @Test
+  public void addCronetRequestAnnotation_deprecated() {
+    Object annotation = new Object();
+    SetStreamFactoryRunnable callback = new SetStreamFactoryRunnable(factory);
+    CronetClientStream stream =
+        new CronetClientStream(
+            "https://www.google.com:443",
+            "cronet",
+            executor,
+            metadata,
+            transport,
+            callback,
+            lock,
+            100,
+            false /* alwaysUsePut */,
+            method,
+            StatsTraceContext.NOOP,
+            CallOptions.DEFAULT.withOption(CronetCallOptions.CRONET_ANNOTATION_KEY, annotation),
+            transportTracer);
+    callback.setStream(stream);
+    when(factory.newBidirectionalStreamBuilder(
+            any(String.class), any(BidirectionalStream.Callback.class), any(Executor.class)))
+        .thenReturn(builder);
+    stream.start(clientListener);
+
+    // addRequestAnnotation should be called since we add the option CRONET_ANNOTATION_KEY above.
+    verify(builder).addRequestAnnotation(annotation);
+  }
+
+  @Test
+  public void withAnnotation() {
+    Object annotation1 = new Object();
+    Object annotation2 = new Object();
+    CallOptions callOptions = CronetCallOptions.withAnnotation(CallOptions.DEFAULT, annotation1);
+    callOptions = CronetCallOptions.withAnnotation(callOptions, annotation2);
+
+    SetStreamFactoryRunnable callback = new SetStreamFactoryRunnable(factory);
+    CronetClientStream stream =
+        new CronetClientStream(
+            "https://www.google.com:443",
+            "cronet",
+            executor,
+            metadata,
+            transport,
+            callback,
+            lock,
+            100,
+            false /* alwaysUsePut */,
+            method,
+            StatsTraceContext.NOOP,
+            callOptions,
+            transportTracer);
+    callback.setStream(stream);
+    when(factory.newBidirectionalStreamBuilder(
+            any(String.class), any(BidirectionalStream.Callback.class), any(Executor.class)))
+        .thenReturn(builder);
+    stream.start(clientListener);
+
+    verify(builder).addRequestAnnotation(annotation1);
+    verify(builder).addRequestAnnotation(annotation2);
+  }
+
+  @Test
+  public void getUnaryRequest() {
+    StreamBuilderFactory getFactory = mock(StreamBuilderFactory.class);
+    MethodDescriptor<?, ?> getMethod =
+        MethodDescriptor.<Void, Void>newBuilder()
+            .setType(MethodDescriptor.MethodType.UNARY)
+            .setFullMethodName("/service/method")
+            .setIdempotent(true)
+            .setSafe(true)
+            .setRequestMarshaller(marshaller)
+            .setResponseMarshaller(marshaller)
+            .build();
+    SetStreamFactoryRunnable callback = new SetStreamFactoryRunnable(getFactory);
+    CronetClientStream stream =
+        new CronetClientStream(
+            "https://www.google.com/service/method",
+            "cronet",
+            executor,
+            metadata,
+            transport,
+            callback,
+            lock,
+            100,
+            false /* alwaysUsePut */,
+            getMethod,
+            StatsTraceContext.NOOP,
+            CallOptions.DEFAULT,
+            transportTracer);
+    callback.setStream(stream);
+    ExperimentalBidirectionalStream.Builder getBuilder =
+        mock(ExperimentalBidirectionalStream.Builder.class);
+    when(getFactory.newBidirectionalStreamBuilder(
+            any(String.class), any(BidirectionalStream.Callback.class), any(Executor.class)))
+        .thenReturn(getBuilder);
+    when(getBuilder.build()).thenReturn(cronetStream);
+    stream.start(clientListener);
+
+    // We will not create BidirectionalStream until we have the full request.
+    verify(getFactory, times(0))
+        .newBidirectionalStreamBuilder(
+            isA(String.class), isA(BidirectionalStream.Callback.class), isA(Executor.class));
+
+    byte[] msg = "request".getBytes(Charset.forName("UTF-8"));
+    stream.writeMessage(new ByteArrayInputStream(msg));
+    // We still haven't built the stream or sent anything.
+    verify(cronetStream, times(0)).write(isA(ByteBuffer.class), isA(Boolean.class));
+    verify(getFactory, times(0))
+        .newBidirectionalStreamBuilder(
+            isA(String.class), isA(BidirectionalStream.Callback.class), isA(Executor.class));
+
+    // halfClose will trigger sending.
+    stream.halfClose();
+
+    // Stream should be built with request payload in the header.
+    ArgumentCaptor<String> urlCaptor = ArgumentCaptor.forClass(String.class);
+    verify(getFactory)
+        .newBidirectionalStreamBuilder(
+            urlCaptor.capture(), isA(BidirectionalStream.Callback.class), isA(Executor.class));
+    verify(getBuilder).setHttpMethod("GET");
+    assertEquals(
+        "https://www.google.com/service/method?" + BaseEncoding.base64().encode(msg),
+        urlCaptor.getValue());
+  }
+
+  @Test
+  public void idempotentMethod_usesHttpPut() {
+    SetStreamFactoryRunnable callback = new SetStreamFactoryRunnable(factory);
+    MethodDescriptor<?, ?> idempotentMethod = method.toBuilder().setIdempotent(true).build();
+    CronetClientStream stream =
+        new CronetClientStream(
+            "https://www.google.com:443",
+            "cronet",
+            executor,
+            metadata,
+            transport,
+            callback,
+            lock,
+            100,
+            false /* alwaysUsePut */,
+            idempotentMethod,
+            StatsTraceContext.NOOP,
+            CallOptions.DEFAULT,
+            transportTracer);
+    callback.setStream(stream);
+    ExperimentalBidirectionalStream.Builder builder =
+        mock(ExperimentalBidirectionalStream.Builder.class);
+    when(factory.newBidirectionalStreamBuilder(
+            any(String.class), any(BidirectionalStream.Callback.class), any(Executor.class)))
+        .thenReturn(builder);
+    when(builder.build()).thenReturn(cronetStream);
+    stream.start(clientListener);
+
+    verify(builder).setHttpMethod("PUT");
+  }
+
+  @Test
+  public void alwaysUsePutOption_usesHttpPut() {
+    SetStreamFactoryRunnable callback = new SetStreamFactoryRunnable(factory);
+    CronetClientStream stream =
+        new CronetClientStream(
+            "https://www.google.com:443",
+            "cronet",
+            executor,
+            metadata,
+            transport,
+            callback,
+            lock,
+            100,
+            true /* alwaysUsePut */,
+            method,
+            StatsTraceContext.NOOP,
+            CallOptions.DEFAULT,
+            transportTracer);
+    callback.setStream(stream);
+    ExperimentalBidirectionalStream.Builder builder =
+        mock(ExperimentalBidirectionalStream.Builder.class);
+    when(factory.newBidirectionalStreamBuilder(
+            any(String.class), any(BidirectionalStream.Callback.class), any(Executor.class)))
+        .thenReturn(builder);
+    when(builder.build()).thenReturn(cronetStream);
+    stream.start(clientListener);
+
+    verify(builder).setHttpMethod("PUT");
+  }
+
+  @Test
+  public void reservedHeadersStripped() {
+    String userAgent = "cronet";
+    Metadata headers = new Metadata();
+    Metadata.Key<String> userKey = Metadata.Key.of("user-key", Metadata.ASCII_STRING_MARSHALLER);
+    headers.put(GrpcUtil.CONTENT_TYPE_KEY, "to-be-removed");
+    headers.put(GrpcUtil.USER_AGENT_KEY, "to-be-removed");
+    headers.put(GrpcUtil.TE_HEADER, "to-be-removed");
+    headers.put(userKey, "user-value");
+
+    SetStreamFactoryRunnable callback = new SetStreamFactoryRunnable(factory);
+    CronetClientStream stream =
+        new CronetClientStream(
+            "https://www.google.com:443",
+            userAgent,
+            executor,
+            headers,
+            transport,
+            callback,
+            lock,
+            100,
+            false /* alwaysUsePut */,
+            method,
+            StatsTraceContext.NOOP,
+            CallOptions.DEFAULT,
+            transportTracer);
+    callback.setStream(stream);
+    ExperimentalBidirectionalStream.Builder builder =
+        mock(ExperimentalBidirectionalStream.Builder.class);
+    when(factory.newBidirectionalStreamBuilder(
+            any(String.class), any(BidirectionalStream.Callback.class), any(Executor.class)))
+        .thenReturn(builder);
+    when(builder.build()).thenReturn(cronetStream);
+    stream.start(clientListener);
+
+    verify(builder, times(4)).addHeader(any(String.class), any(String.class));
+    verify(builder).addHeader(GrpcUtil.USER_AGENT_KEY.name(), userAgent);
+    verify(builder).addHeader(GrpcUtil.CONTENT_TYPE_KEY.name(), GrpcUtil.CONTENT_TYPE_GRPC);
+    verify(builder).addHeader("te", GrpcUtil.TE_TRAILERS);
+    verify(builder).addHeader(userKey.name(), "user-value");
+  }
+}
diff --git a/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java b/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java
new file mode 100644
index 0000000..539ba1c
--- /dev/null
+++ b/cronet/src/test/java/io/grpc/cronet/CronetClientTransportTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.cronet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.grpc.Attributes;
+import io.grpc.CallCredentials;
+import io.grpc.CallOptions;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.SecurityLevel;
+import io.grpc.Status;
+import io.grpc.cronet.CronetChannelBuilder.StreamBuilderFactory;
+import io.grpc.internal.ClientStreamListener;
+import io.grpc.internal.ManagedClientTransport;
+import io.grpc.internal.TransportTracer;
+import io.grpc.testing.TestMethodDescriptors;
+import java.net.InetSocketAddress;
+import java.util.concurrent.Executor;
+import org.chromium.net.BidirectionalStream;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public final class CronetClientTransportTest {
+
+  private static final String AUTHORITY = "test.example.com";
+
+  private CronetClientTransport transport;
+  @Mock private StreamBuilderFactory streamFactory;
+  @Mock private Executor executor;
+  private MethodDescriptor<Void, Void> descriptor = TestMethodDescriptors.voidMethod();
+  @Mock private ManagedClientTransport.Listener clientTransportListener;
+  @Mock private BidirectionalStream.Builder builder;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+
+    transport =
+        new CronetClientTransport(
+            streamFactory,
+            new InetSocketAddress("localhost", 443),
+            AUTHORITY,
+            null,
+            executor,
+            5000,
+            false,
+            TransportTracer.getDefaultFactory().create());
+    Runnable callback = transport.start(clientTransportListener);
+    assertTrue(callback != null);
+    callback.run();
+    verify(clientTransportListener).transportReady();
+  }
+
+  @Test
+  public void transportAttributes() {
+    Attributes attrs = transport.getAttributes();
+    assertEquals(AUTHORITY, attrs.get(CallCredentials.ATTR_AUTHORITY));
+    assertEquals(
+        SecurityLevel.PRIVACY_AND_INTEGRITY, attrs.get(CallCredentials.ATTR_SECURITY_LEVEL));
+  }
+
+  @Test
+  public void shutdownTransport() throws Exception {
+    CronetClientStream stream1 =
+        transport.newStream(descriptor, new Metadata(), CallOptions.DEFAULT);
+    CronetClientStream stream2 =
+        transport.newStream(descriptor, new Metadata(), CallOptions.DEFAULT);
+
+    // Create a transport and start two streams on it.
+    ArgumentCaptor<BidirectionalStream.Callback> callbackCaptor =
+        ArgumentCaptor.forClass(BidirectionalStream.Callback.class);
+    when(streamFactory.newBidirectionalStreamBuilder(
+            any(String.class), callbackCaptor.capture(), any(Executor.class)))
+        .thenReturn(builder);
+    BidirectionalStream cronetStream1 = mock(BidirectionalStream.class);
+    when(builder.build()).thenReturn(cronetStream1);
+    stream1.start(mock(ClientStreamListener.class));
+    BidirectionalStream.Callback callback1 = callbackCaptor.getValue();
+
+    BidirectionalStream cronetStream2 = mock(BidirectionalStream.class);
+    when(builder.build()).thenReturn(cronetStream2);
+    stream2.start(mock(ClientStreamListener.class));
+    BidirectionalStream.Callback callback2 = callbackCaptor.getValue();
+    // Shut down the transport. transportShutdown should be called immediately.
+    transport.shutdown();
+    verify(clientTransportListener).transportShutdown(any(Status.class));
+    // Have two live streams. Transport has not been terminated.
+    verify(clientTransportListener, times(0)).transportTerminated();
+
+    callback1.onCanceled(cronetStream1, null);
+    // Still has one live stream
+    verify(clientTransportListener, times(0)).transportTerminated();
+    callback2.onCanceled(cronetStream1, null);
+    // All streams are gone now.
+    verify(clientTransportListener, times(1)).transportTerminated();
+  }
+}
diff --git a/cronet/src/test/java/io/grpc/cronet/CronetWritableBufferAllocatorTest.java b/cronet/src/test/java/io/grpc/cronet/CronetWritableBufferAllocatorTest.java
new file mode 100644
index 0000000..a549d94
--- /dev/null
+++ b/cronet/src/test/java/io/grpc/cronet/CronetWritableBufferAllocatorTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.cronet;
+
+import static org.junit.Assert.assertEquals;
+
+import io.grpc.internal.WritableBuffer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class CronetWritableBufferAllocatorTest {
+
+  @Test
+  public void testAllocate() throws Exception {
+    CronetWritableBufferAllocator allocator = new CronetWritableBufferAllocator();
+    WritableBuffer buffer = allocator.allocate(1000);
+    assertEquals(1000, buffer.writableBytes());
+  }
+
+  @Test
+  public void testAllocateLargeBuffer() throws Exception {
+    CronetWritableBufferAllocator allocator = new CronetWritableBufferAllocator();
+    // Ask for 1GB
+    WritableBuffer buffer = allocator.allocate(1024 * 1024 * 1024);
+    // Only get 1MB
+    assertEquals(1024 * 1024, buffer.writableBytes());
+  }
+}
diff --git a/documentation/android-channel-builder.md b/documentation/android-channel-builder.md
new file mode 100644
index 0000000..cd316b4
--- /dev/null
+++ b/documentation/android-channel-builder.md
@@ -0,0 +1,65 @@
+# AndroidChannelBuilder
+
+Since gRPC's 1.12 release, the `grpc-android` package provides access to the
+`AndroidChannelBuilder` class. Given an Android Context, this builder will
+register a network event listener upon channel construction.  The listener is
+used to automatically respond to changes in the device's network state, avoiding
+delays and interrupted RPCs that may otherwise occur.
+
+By default, gRPC uses exponential backoff to recover from connection failures.
+Depending on the scheduled backoff delay when the device regains connectivity,
+this could result in a  one minute or longer delay before gRPC re-establishes
+the connection. This delay is removed when `AndroidChannelBuilder` is provided
+with the app's Android Context.  Notifications from the network listener will
+cause the channel to immediately reconnect upon network recovery.
+
+On Android API levels 24+, `AndroidChannelBuilder`'s network listener mechanism
+allows graceful switching from cellular to wifi connections. When an Android
+device on a cellular network connects to a wifi network, there is a brief
+(typically 30 second) interval when both cellular and wifi networks remain
+available, then any connections on the cellular network are terminated.  By
+listening for changes in the device's default network, `AndroidChannelBuilder`
+sends new RPCs via wifi rather than using an already-established cellular
+connection. Without listening for pending network changes, new RPCs sent on an
+already established cellular connection would fail when the device terminates
+cellular connections.
+
+***Note:*** *Currently, `AndroidChannelBuilder` is only compatible with gRPC
+OkHttp. We plan to offer additional Android-specific features compatible with
+both the OkHttp and Cronet transports in the future, but the network listener
+mechanism is only necessary with OkHttp; the Cronet library internally handles
+connection management on Android devices.*
+
+## Example usage:
+
+In your `build.gradle` file, include a dependency on both `grpc-android` and
+`grpc-okhttp`:
+
+```
+compile 'io.grpc:grpc-android:1.16.0' // CURRENT_GRPC_VERSION
+compile 'io.grpc:grpc-okhttp:1.16.0' // CURRENT_GRPC_VERSION
+```
+
+You will also need permission to access the device's network state in your
+`AndroidManifest.xml`:
+
+```
+<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+```
+
+When constructing your channel, use `AndroidChannelBuilder` and provide it with
+your app's Context:
+
+```
+import io.grpc.android.AndroidChannelBuilder;
+...
+ManagedChannel channel = AndroidChannelBuilder.forAddress("localhost", 8080)
+    .context(getApplicationContext())
+    .build();
+```
+
+You continue to use the constructed channel exactly as you would any other
+channel. gRPC will now monitor and respond to the device's network state
+automatically. When you shutdown the managed channel, the network listener
+registered by `AndroidChannelBuilder` will be unregistered.
+
diff --git a/documentation/server-reflection-tutorial.md b/documentation/server-reflection-tutorial.md
new file mode 100644
index 0000000..5fad5a2
--- /dev/null
+++ b/documentation/server-reflection-tutorial.md
@@ -0,0 +1,163 @@
+# gRPC Server Reflection Tutorial
+
+gRPC Server Reflection provides information about publicly-accessible gRPC
+services on a server, and assists clients at runtime with constructing RPC
+requests and responses without precompiled service information. It is used by
+the gRPC command line tool (gRPC CLI), which can be used to introspect server
+protos and send/receive test RPCs. Reflection is only supported for
+proto-based services.
+
+## Enable Server Reflection
+
+gRPC-Java Server Reflection is implemented by
+`io.grpc.protobuf.services.ProtoReflectionService` in the `grpc-services`
+package. To enable server reflection, you need to add the
+`ProtoReflectionService` to your gRPC server.
+
+For example, to enable server reflection in
+`examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java`, we
+need to make the following changes:
+
+```diff
+--- a/examples/build.gradle
++++ b/examples/build.gradle
+@@ -27,6 +27,7 @@
+ dependencies {
+   compile "io.grpc:grpc-netty-shaded:${grpcVersion}"
+   compile "io.grpc:grpc-protobuf:${grpcVersion}"
++  compile "io.grpc:grpc-services:${grpcVersion}"
+   compile "io.grpc:grpc-stub:${grpcVersion}"
+ 
+   testCompile "junit:junit:4.12"
+--- a/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java
++++ b/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java
+@@ -33,6 +33,7 @@ package io.grpc.examples.helloworld;
+ 
+ import io.grpc.Server;
+ import io.grpc.ServerBuilder;
++import io.grpc.protobuf.services.ProtoReflectionService;
+ import io.grpc.stub.StreamObserver;
+ import java.io.IOException;
+ import java.util.logging.Logger;
+@@ -50,6 +51,7 @@ public class HelloWorldServer {
+     int port = 50051;
+     server = ServerBuilder.forPort(port)
+         .addService(new GreeterImpl())
++        .addService(ProtoReflectionService.newInstance())
+         .build()
+         .start();
+     logger.info("Server started, listening on " + port);
+```
+
+In the following examples, we assume you have made these changes to
+enable reflection in `HelloWorldServer.java`.
+
+## gRPC CLI
+
+After enabling server reflection in a server application, you can use gRPC
+CLI to get information about its available services. gRPC CLI is written
+in C++. Instructions on how to build gRPC CLI can be found at
+[command_line_tool.md](https://github.com/grpc/grpc/blob/master/doc/command_line_tool.md).
+
+## Use gRPC CLI to check services
+
+First, build and start the `hello-world-server`:
+
+```sh
+$ cd examples/
+$ ./gradlew installDist
+$ build/install/examples/bin/hello-world-server
+```
+
+Open a new terminal and make sure you are in the directory where the `grpc_cli`
+binary is located:
+
+```sh
+$ cd <grpc-cpp-directory>/bins/opt
+```
+
+### List services
+
+`grpc_cli ls` command lists services and methods exposed at a given port:
+
+- List all the services exposed at a given port
+
+  ```sh
+  $ ./grpc_cli ls localhost:50051
+  ```
+
+  output:
+  ```sh
+  helloworld.Greeter
+  grpc.reflection.v1alpha.ServerReflection
+  ```
+
+- List one service with details
+
+  `grpc_cli ls` command inspects a service given its full name (in the format of
+  \<package\>.\<service\>). It can print information with a long listing format
+  when `-l` flag is set. This flag can be used to get more details about a
+  service.
+
+  ```sh
+  $ ./grpc_cli ls localhost:50051 helloworld.Greeter -l
+  ```
+
+  output:
+  ```sh
+  filename: helloworld.proto
+  package: helloworld;
+  service Greeter {
+    rpc SayHello(helloworld.HelloRequest) returns (helloworld.HelloReply) {}
+  }
+
+  ```
+
+### List methods
+
+- List one method with details
+
+  `grpc_cli ls` command also inspects a method given its full name (in the
+  format of \<package\>.\<service\>.\<method\>).
+
+  ```sh
+  $ ./grpc_cli ls localhost:50051 helloworld.Greeter.SayHello -l
+  ```
+
+  output:
+  ```sh
+    rpc SayHello(helloworld.HelloRequest) returns (helloworld.HelloReply) {}
+  ```
+
+### Inspect message types
+
+We can use`grpc_cli type` command to inspect request/response types given the
+full name of the type (in the format of \<package\>.\<type\>).
+
+- Get information about the request type
+
+  ```sh
+  $ ./grpc_cli type localhost:50051 helloworld.HelloRequest
+  ```
+
+  output:
+  ```sh
+  message HelloRequest {
+    optional string name = 1[json_name = "name"];
+  }
+  ```
+
+### Call a remote method
+
+We can send RPCs to a server and get responses using `grpc_cli call` command.
+
+- Call a unary method
+
+  ```sh
+  $ ./grpc_cli call localhost:50051 SayHello "name: 'gRPC CLI'"
+  ```
+
+  output:
+  ```sh
+  message: "Hello gRPC CLI"
+  ```
diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel
new file mode 100644
index 0000000..dce466c
--- /dev/null
+++ b/examples/BUILD.bazel
@@ -0,0 +1,177 @@
+load("@io_grpc_grpc_java//:java_grpc_library.bzl", "java_grpc_library")
+
+proto_library(
+    name = "helloworld_proto",
+    srcs = ["src/main/proto/helloworld.proto"],
+)
+
+java_proto_library(
+    name = "helloworld_java_proto",
+    deps = [":helloworld_proto"],
+)
+
+java_grpc_library(
+    name = "helloworld_java_grpc",
+    srcs = [":helloworld_proto"],
+    deps = [":helloworld_java_proto"],
+)
+
+proto_library(
+    name = "hello_streaming_proto",
+    srcs = [
+        "src/main/proto/hello_streaming.proto",
+    ],
+)
+
+java_proto_library(
+    name = "hello_streaming_java_proto",
+    deps = [":hello_streaming_proto"],
+)
+
+java_grpc_library(
+    name = "hello_streaming_java_grpc",
+    srcs = [":hello_streaming_proto"],
+    deps = [":hello_streaming_java_proto"],
+)
+
+proto_library(
+    name = "route_guide_proto",
+    srcs = ["src/main/proto/route_guide.proto"],
+)
+
+java_proto_library(
+    name = "route_guide_java_proto",
+    deps = [":route_guide_proto"],
+)
+
+java_grpc_library(
+    name = "route_guide_java_grpc",
+    srcs = [":route_guide_proto"],
+    deps = [":route_guide_java_proto"],
+)
+
+java_library(
+    name = "examples",
+    testonly = 1,
+    srcs = glob(
+        ["src/main/java/**/*.java"],
+    ),
+    resources = glob(
+        ["src/main/resources/**"],
+    ),
+    deps = [
+        ":hello_streaming_java_grpc",
+        ":hello_streaming_java_proto",
+        ":helloworld_java_grpc",
+        ":helloworld_java_proto",
+        ":route_guide_java_grpc",
+        ":route_guide_java_proto",
+        "@com_google_api_grpc_proto_google_common_protos//jar",
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_guava_guava//jar",
+        "@com_google_protobuf//:protobuf_java",
+        "@com_google_protobuf//:protobuf_java_util",
+        "@io_grpc_grpc_java//alts",
+        "@io_grpc_grpc_java//core",
+        "@io_grpc_grpc_java//netty",
+        "@io_grpc_grpc_java//protobuf",
+        "@io_grpc_grpc_java//stub",
+        "@io_netty_netty_handler//jar",
+    ],
+)
+
+java_binary(
+    name = "hello-world-client",
+    testonly = 1,
+    main_class = "io.grpc.examples.helloworld.HelloWorldClient",
+    runtime_deps = [
+        ":examples",
+    ],
+)
+
+java_binary(
+    name = "hello-world-server",
+    testonly = 1,
+    main_class = "io.grpc.examples.helloworld.HelloWorldServer",
+    runtime_deps = [
+        ":examples",
+    ],
+)
+
+java_binary(
+    name = "hello-world-alts-client",
+    testonly = 1,
+    main_class = "io.grpc.examples.alts.HelloWorldAltsClient",
+    runtime_deps = [
+        ":examples",
+        "@io_grpc_grpc_java//alts",
+        "@io_grpc_grpc_java//netty",
+    ],
+)
+
+java_binary(
+    name = "hello-world-alts-server",
+    testonly = 1,
+    main_class = "io.grpc.examples.alts.HelloWorldAltsServer",
+    runtime_deps = [
+        ":examples",
+        "@io_grpc_grpc_java//alts",
+        "@io_grpc_grpc_java//netty",
+    ],
+)
+
+java_binary(
+    name = "route-guide-client",
+    testonly = 1,
+    main_class = "io.grpc.examples.routeguide.RouteGuideClient",
+    runtime_deps = [
+        ":examples",
+    ],
+)
+
+java_binary(
+    name = "route-guide-server",
+    testonly = 1,
+    main_class = "io.grpc.examples.routeguide.RouteGuideServer",
+    runtime_deps = [
+        ":examples",
+    ],
+)
+
+java_binary(
+    name = "manual-flow-control-client",
+    testonly = 1,
+    main_class = "io.grpc.examples.manualflowcontrol.ManualFlowControlClient",
+    runtime_deps = [
+        ":examples",
+    ],
+)
+
+java_binary(
+    name = "manual-flow-control-server",
+    testonly = 1,
+    main_class = "io.grpc.examples.manualflowcontrol.ManualFlowControlServer",
+    runtime_deps = [
+        ":examples",
+    ],
+)
+
+java_binary(
+    name = "hello-world-tls-client",
+    testonly = 1,
+    main_class = "io.grpc.examples.helloworldtls.HelloWorldClientTls",
+    runtime_deps = [
+        ":examples",
+        "@io_netty_netty_tcnative_boringssl_static//jar",
+    ],
+)
+
+java_binary(
+    name = "hello-world-tls-server",
+    testonly = 1,
+    main_class = "io.grpc.examples.helloworldtls.HelloWorldServerTls",
+    runtime_deps = [
+        ":examples",
+        "@io_netty_netty_tcnative_boringssl_static//jar",
+    ],
+)
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..a5e6782
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,162 @@
+grpc Examples
+==============================================
+
+The examples require grpc-java to already be built. You are strongly encouraged
+to check out a git release tag, since there will already be a build of grpc
+available. Otherwise you must follow [COMPILING](../COMPILING.md).
+
+You may want to read through the
+[Quick Start Guide](https://grpc.io/docs/quickstart/java.html)
+before trying out the examples.
+
+To build the examples, run in this directory:
+
+```
+$ ./gradlew installDist
+```
+
+This creates the scripts `hello-world-server`, `hello-world-client`, 
+`hello-world-tls-server`, `hello-world-tls-client`,
+`route-guide-server`, and `route-guide-client` in the
+`build/install/examples/bin/` directory that run the examples. Each
+example requires the server to be running before starting the client.
+
+For example, to try the hello world example first run:
+
+```
+$ ./build/install/examples/bin/hello-world-server
+```
+
+And in a different terminal window run:
+
+```
+$ ./build/install/examples/bin/hello-world-client
+```
+
+### Hello World with TLS 
+
+Running the hello world with TLS is the same as the normal hello world, but takes additional args:
+
+**hello-world-tls-server**:
+
+```text
+USAGE: HelloWorldServerTls host port certChainFilePath privateKeyFilePath [trustCertCollectionFilePath]
+  Note: You only need to supply trustCertCollectionFilePath if you want to enable Mutual TLS.
+```
+
+**hello-world-tls-client**:
+
+```text
+USAGE: HelloWorldClientTls host port [trustCertCollectionFilePath] [clientCertChainFilePath] [clientPrivateKeyFilePath]
+  Note: clientCertChainFilePath and clientPrivateKeyFilePath are only needed if mutual auth is desired. And if you specify clientCertChainFilePath you must also specify clientPrivateKeyFilePath
+```
+
+#### Generating self-signed certificates for use with grpc
+
+You can use the following script to generate self-signed certificates for grpc-java including the hello world with TLS examples:
+
+```bash
+# Changes these CN's to match your hosts in your environment if needed.
+SERVER_CN=localhost
+CLIENT_CN=localhost # Used when doing mutual TLS
+
+echo Generate CA key:
+openssl genrsa -passout pass:1111 -des3 -out ca.key 4096
+echo Generate CA certificate:
+# Generates ca.crt which is the trustCertCollectionFile
+openssl req -passin pass:1111 -new -x509 -days 365 -key ca.key -out ca.crt -subj "/CN=${SERVER_CN}"
+echo Generate server key:
+openssl genrsa -passout pass:1111 -des3 -out server.key 4096
+echo Generate server signing request:
+openssl req -passin pass:1111 -new -key server.key -out server.csr -subj "/CN=${SERVER_CN}"
+echo Self-signed server certificate:
+# Generates server.crt which is the certChainFile for the server
+openssl x509 -req -passin pass:1111 -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt 
+echo Remove passphrase from server key:
+openssl rsa -passin pass:1111 -in server.key -out server.key
+echo Generate client key
+openssl genrsa -passout pass:1111 -des3 -out client.key 4096
+echo Generate client signing request:
+openssl req -passin pass:1111 -new -key client.key -out client.csr -subj "/CN=${CLIENT_CN}"
+echo Self-signed client certificate:
+# Generates client.crt which is the clientCertChainFile for the client (need for mutual TLS only)
+openssl x509 -passin pass:1111 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
+echo Remove passphrase from client key:
+openssl rsa -passin pass:1111 -in client.key -out client.key
+echo Converting the private keys to X.509:
+# Generates client.pem which is the clientPrivateKeyFile for the Client (needed for mutual TLS only)
+openssl pkcs8 -topk8 -nocrypt -in client.key -out client.pem
+# Generates server.pem which is the privateKeyFile for the Server
+openssl pkcs8 -topk8 -nocrypt -in server.key -out server.pem
+```
+
+#### Hello world example with TLS (no mutual auth):
+
+```bash
+# Server
+./build/install/examples/bin/hello-world-server-tls mate 50440 ~/Downloads/sslcert/server.crt ~/Downloads/sslcert/server.pem
+# Client
+./build/install/examples/bin/hello-world-client-tls mate 50440 ~/Downloads/sslcert/ca.crt
+```
+
+#### Hello world example with TLS with mutual auth:
+
+```bash
+# Server
+./build/install/examples/bin/hello-world-server-tls mate 54440 ~/Downloads/sslcert/server.crt ~/Downloads/sslcert/server.pem ~/Downloads/sslcert/ca.crt
+# Client
+./build/install/examples/bin/hello-world-client-tls mate 54440 ~/Downloads/sslcert/ca.crt ~/Downloads/sslcert/client.crt ~/Downloads/sslcert/client.pem
+```
+
+That's it!
+
+Please refer to gRPC Java's [README](../README.md) and
+[tutorial](https://grpc.io/docs/tutorials/basic/java.html) for more
+information.
+
+## Maven
+
+If you prefer to use Maven:
+```
+$ mvn verify
+$ # Run the server
+$ mvn exec:java -Dexec.mainClass=io.grpc.examples.helloworld.HelloWorldServer
+$ # In another terminal run the client
+$ mvn exec:java -Dexec.mainClass=io.grpc.examples.helloworld.HelloWorldClient
+```
+
+## Bazel
+
+If you prefer to use Bazel:
+```
+(With Bazel v0.8.0 or above.)
+$ bazel build :hello-world-server :hello-world-client
+$ # Run the server:
+$ bazel-bin/hello-world-server
+$ # In another terminal run the client
+$ bazel-bin/hello-world-client
+```
+
+Unit test examples
+==============================================
+
+Examples for unit testing gRPC clients and servers are located in [examples/src/test](src/test).
+
+In general, we DO NOT allow overriding the client stub.
+We encourage users to leverage `InProcessTransport` as demonstrated in the examples to
+write unit tests. `InProcessTransport` is light-weight and runs the server
+and client in the same process without any socket/TCP connection.
+
+For testing a gRPC client, create the client with a real stub
+using an
+[InProcessChannel](../core/src/main/java/io/grpc/inprocess/InProcessChannelBuilder.java),
+and test it against an
+[InProcessServer](../core/src/main/java/io/grpc/inprocess/InProcessServerBuilder.java)
+with a mock/fake service implementation.
+
+For testing a gRPC server, create the server as an InProcessServer,
+and test it against a real client stub with an InProcessChannel.
+
+The gRPC-java library also provides a JUnit rule,
+[GrpcServerRule](../testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java), to do the graceful
+shutdown boilerplate for you.
diff --git a/examples/WORKSPACE b/examples/WORKSPACE
new file mode 100644
index 0000000..b86f705
--- /dev/null
+++ b/examples/WORKSPACE
@@ -0,0 +1,16 @@
+workspace(name = "examples")
+
+# For released versions, use the tagged git-repository:
+# git_repository(
+#     name = "io_grpc_grpc_java",
+#     remote = "https://github.com/grpc/grpc-java.git",
+#     tag = "<TAG>",
+# )
+local_repository(
+    name = "io_grpc_grpc_java",
+    path = "..",
+)
+
+load("@io_grpc_grpc_java//:repositories.bzl", "grpc_java_repositories")
+
+grpc_java_repositories()
diff --git a/examples/android/README.md b/examples/android/README.md
new file mode 100644
index 0000000..3222c12
--- /dev/null
+++ b/examples/android/README.md
@@ -0,0 +1,32 @@
+gRPC Hello World Example (Android Java)
+========================
+
+PREREQUISITES
+-------------
+- [Java gRPC](https://github.com/grpc/grpc-java)
+
+- [Android Tutorial](https://developer.android.com/training/basics/firstapp/index.html) if you're new to Android development
+
+- [gRPC Java Android Quick Start Guide](https://grpc.io/docs/quickstart/android.html)
+
+- We only have Android gRPC client in this example. Please follow examples in other languages to build and run a gRPC server.
+
+INSTALL
+-------
+
+1. (Only for non-released versions) Install gRPC Java
+```sh
+$ cd ../..
+$ ./gradlew install -PskipCodegen=true
+$ cd examples/android
+```
+
+2. Install the app
+```sh
+$ cd helloworld  # or "cd routeguide"
+$ ./gradlew installDebug
+```
+
+Please refer to the
+[tutorial](https://grpc.io/docs/tutorials/basic/android.html) on
+how to use gRPC in Android programs.
diff --git a/examples/android/clientcache/app/build.gradle b/examples/android/clientcache/app/build.gradle
new file mode 100644
index 0000000..1b69529
--- /dev/null
+++ b/examples/android/clientcache/app/build.gradle
@@ -0,0 +1,60 @@
+apply plugin: 'com.android.application'
+apply plugin: 'com.google.protobuf'
+
+android {
+    compileSdkVersion 27
+
+    defaultConfig {
+        applicationId "io.grpc.clientcacheexample"
+        minSdkVersion 19
+        targetSdkVersion 27
+        multiDexEnabled true
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        debug { minifyEnabled false }
+        release {
+            minifyEnabled true
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    lintOptions {
+        disable 'GoogleAppIndexingWarning', 'HardcodedText', 'InvalidPackage'
+        textReport true
+        textOutput "stdout"
+    }
+}
+
+protobuf {
+    protoc { artifact = 'com.google.protobuf:protoc:3.4.0' }
+    plugins {
+        javalite { artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0" }
+        grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+        }
+    }
+    generateProtoTasks {
+        all().each { task ->
+            task.plugins {
+                javalite {}
+                grpc { // Options added to --grpc_out
+                    option 'lite' }
+            }
+        }
+    }
+}
+
+dependencies {
+    compile 'com.android.support:appcompat-v7:27.0.2'
+
+    // You need to build grpc-java to obtain these libraries below.
+    compile 'io.grpc:grpc-okhttp:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-protobuf-lite:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-stub:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    compile 'javax.annotation:javax.annotation-api:1.2'
+
+    testCompile 'junit:junit:4.12'
+    testCompile 'com.google.truth:truth:0.36'
+    testCompile 'io.grpc:grpc-testing:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+}
diff --git a/examples/android/clientcache/app/proguard-rules.pro b/examples/android/clientcache/app/proguard-rules.pro
new file mode 100644
index 0000000..1507a52
--- /dev/null
+++ b/examples/android/clientcache/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in $ANDROID_HOME/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+-dontwarn com.google.common.**
+# Ignores: can't find referenced class javax.lang.model.element.Modifier
+-dontwarn com.google.errorprone.annotations.**
+-dontwarn javax.naming.**
+-dontwarn okio.**
+-dontwarn sun.misc.Unsafe
diff --git a/examples/android/clientcache/app/src/main/AndroidManifest.xml b/examples/android/clientcache/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6fc8e94
--- /dev/null
+++ b/examples/android/clientcache/app/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.grpc.clientcacheexample" >
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application
+        android:allowBackup="false"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/Base.V7.Theme.AppCompat.Light" >
+        <activity
+            android:name="io.grpc.clientcacheexample.ClientCacheExampleActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/examples/android/clientcache/app/src/main/java/io/grpc/clientcacheexample/ClientCacheExampleActivity.java b/examples/android/clientcache/app/src/main/java/io/grpc/clientcacheexample/ClientCacheExampleActivity.java
new file mode 100644
index 0000000..232ab6e
--- /dev/null
+++ b/examples/android/clientcache/app/src/main/java/io/grpc/clientcacheexample/ClientCacheExampleActivity.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.clientcacheexample;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.text.TextUtils;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.TextView;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientInterceptors;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.MethodDescriptor;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.stub.ClientCalls;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.TimeUnit;
+
+public final class ClientCacheExampleActivity extends AppCompatActivity {
+  private static final int CACHE_SIZE_IN_BYTES = 1 * 1024 * 1024; // 1MB
+  private static final String TAG = "grpcCacheExample";
+  private Button sendButton;
+  private EditText hostEdit;
+  private EditText portEdit;
+  private EditText messageEdit;
+  private TextView resultText;
+  private CheckBox getCheckBox;
+  private CheckBox noCacheCheckBox;
+  private CheckBox onlyIfCachedCheckBox;
+  private SafeMethodCachingInterceptor.Cache cache;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_clientcacheexample);
+    sendButton = (Button) findViewById(R.id.send_button);
+    hostEdit = (EditText) findViewById(R.id.host_edit_text);
+    portEdit = (EditText) findViewById(R.id.port_edit_text);
+    messageEdit = (EditText) findViewById(R.id.message_edit_text);
+    getCheckBox = (CheckBox) findViewById(R.id.get_checkbox);
+    noCacheCheckBox = (CheckBox) findViewById(R.id.no_cache_checkbox);
+    onlyIfCachedCheckBox = (CheckBox) findViewById(R.id.only_if_cached_checkbox);
+    resultText = (TextView) findViewById(R.id.grpc_response_text);
+    resultText.setMovementMethod(new ScrollingMovementMethod());
+    cache = SafeMethodCachingInterceptor.newLruCache(CACHE_SIZE_IN_BYTES);
+  }
+
+  /** Sends RPC. Invoked when app button is pressed. */
+  public void sendMessage(View view) {
+    ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
+        .hideSoftInputFromWindow(hostEdit.getWindowToken(), 0);
+    sendButton.setEnabled(false);
+    resultText.setText("");
+    new GrpcTask(this, cache)
+        .execute(
+            hostEdit.getText().toString(),
+            messageEdit.getText().toString(),
+            portEdit.getText().toString(),
+            getCheckBox.isChecked(),
+            noCacheCheckBox.isChecked(),
+            onlyIfCachedCheckBox.isChecked());
+  }
+
+  private static class GrpcTask extends AsyncTask<Object, Void, String> {
+    private final WeakReference<Activity> activityReference;
+    private final SafeMethodCachingInterceptor.Cache cache;
+    private ManagedChannel channel;
+
+    private GrpcTask(Activity activity, SafeMethodCachingInterceptor.Cache cache) {
+      this.activityReference = new WeakReference<Activity>(activity);
+      this.cache = cache;
+    }
+
+    @Override
+    protected String doInBackground(Object... params) {
+      String host = (String) params[0];
+      String message = (String) params[1];
+      String portStr = (String) params[2];
+      boolean useGet = (boolean) params[3];
+      boolean noCache = (boolean) params[4];
+      boolean onlyIfCached = (boolean) params[5];
+      int port = TextUtils.isEmpty(portStr) ? 0 : Integer.valueOf(portStr);
+      try {
+        channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
+        Channel channelToUse =
+            ClientInterceptors.intercept(
+                channel, SafeMethodCachingInterceptor.newSafeMethodCachingInterceptor(cache));
+        HelloRequest request = HelloRequest.newBuilder().setName(message).build();
+        HelloReply reply;
+        if (useGet) {
+          MethodDescriptor<HelloRequest, HelloReply> safeCacheableUnaryCallMethod =
+              GreeterGrpc.getSayHelloMethod().toBuilder().setSafe(true).build();
+          CallOptions callOptions = CallOptions.DEFAULT;
+          if (noCache) {
+            callOptions =
+                callOptions.withOption(SafeMethodCachingInterceptor.NO_CACHE_CALL_OPTION, true);
+          }
+          if (onlyIfCached) {
+            callOptions =
+                callOptions.withOption(
+                    SafeMethodCachingInterceptor.ONLY_IF_CACHED_CALL_OPTION, true);
+          }
+          reply =
+              ClientCalls.blockingUnaryCall(
+                  channelToUse, safeCacheableUnaryCallMethod, callOptions, request);
+        } else {
+          GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channelToUse);
+          reply = stub.sayHello(request);
+        }
+        return reply.getMessage();
+      } catch (Exception e) {
+        Log.e(TAG, "RPC failed", e);
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        e.printStackTrace(pw);
+        pw.flush();
+        return String.format("Failed... : %n%s", sw);
+      }
+    }
+
+    @Override
+    protected void onPostExecute(String result) {
+      if (channel != null) {
+        try {
+          channel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+          Thread.currentThread().interrupt();
+        }
+      }
+      Activity activity = activityReference.get();
+      if (activity == null) {
+        return;
+      }
+      TextView resultText = (TextView) activity.findViewById(R.id.grpc_response_text);
+      Button sendButton = (Button) activity.findViewById(R.id.send_button);
+      resultText.setText(result);
+      sendButton.setEnabled(true);
+    }
+  }
+}
diff --git a/examples/android/clientcache/app/src/main/java/io/grpc/clientcacheexample/SafeMethodCachingInterceptor.java b/examples/android/clientcache/app/src/main/java/io/grpc/clientcacheexample/SafeMethodCachingInterceptor.java
new file mode 100644
index 0000000..6dfc6d4
--- /dev/null
+++ b/examples/android/clientcache/app/src/main/java/io/grpc/clientcacheexample/SafeMethodCachingInterceptor.java
@@ -0,0 +1,301 @@
+package io.grpc.clientcacheexample;
+
+import android.util.Log;
+import android.util.LruCache;
+import com.google.common.base.Splitter;
+import com.google.protobuf.MessageLite;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.Deadline;
+import io.grpc.ForwardingClientCall;
+import io.grpc.ForwardingClientCallListener;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An example of an on-device cache for Android implemented using the {@link ClientInterceptor} API.
+ *
+ * <p>Client-side cache-control directives are not directly supported. Instead, two call options can
+ * be added to the call: no-cache (always go to the network) or only-if-cached (never use network;
+ * if response is not in cache, the request fails).
+ *
+ * <p>This interceptor respects the cache-control directives in the server's response: max-age
+ * determines when the cache entry goes stale. no-cache, no-store, and no-transform entirely skip
+ * caching of the response. must-revalidate is ignored, as the cache does not support returning
+ * stale responses.
+ *
+ * <p>Note: other response headers besides cache-control (such as Expiration, Varies) are ignored by
+ * this implementation.
+ */
+final class SafeMethodCachingInterceptor implements ClientInterceptor {
+  static CallOptions.Key<Boolean> NO_CACHE_CALL_OPTION = CallOptions.Key.of("no-cache", false);
+  static CallOptions.Key<Boolean> ONLY_IF_CACHED_CALL_OPTION =
+      CallOptions.Key.of("only-if-cached", false);
+  private static final String TAG = "grpcCacheExample";
+
+  public static final class Key {
+    private final String fullMethodName;
+    private final MessageLite request;
+
+    public Key(String fullMethodName, MessageLite request) {
+      this.fullMethodName = fullMethodName;
+      this.request = request;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+      if (object instanceof Key) {
+        Key other = (Key) object;
+        return Objects.equals(this.fullMethodName, other.fullMethodName)
+            && Objects.equals(this.request, other.request);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(fullMethodName, request);
+    }
+  }
+
+  public static final class Value {
+    private final MessageLite response;
+    private final Deadline maxAgeDeadline;
+
+    public Value(MessageLite response, Deadline maxAgeDeadline) {
+      this.response = response;
+      this.maxAgeDeadline = maxAgeDeadline;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+      if (object instanceof Value) {
+        Value other = (Value) object;
+        return Objects.equals(this.response, other.response)
+            && Objects.equals(this.maxAgeDeadline, other.maxAgeDeadline);
+      }
+      return false;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(response, maxAgeDeadline);
+    }
+  }
+
+  public interface Cache {
+    void put(Key key, Value value);
+
+    Value get(Key key);
+
+    void remove(Key key);
+
+    void clear();
+  }
+
+  /**
+   * Obtain a new cache with a least-recently used eviction policy and the specified size limit. The
+   * backing caching implementation is provided by {@link LruCache}. It is safe for a single cache
+   * to be shared across multiple {@link SafeMethodCachingInterceptor}s without synchronization.
+   */
+  public static Cache newLruCache(final int cacheSizeInBytes) {
+    return new Cache() {
+      private final LruCache<Key, Value> lruCache =
+          new LruCache<Key, Value>(cacheSizeInBytes) {
+            protected int sizeOf(Key key, Value value) {
+              return value.response.getSerializedSize();
+            }
+          };
+
+      @Override
+      public void put(Key key, Value value) {
+        lruCache.put(key, value);
+      }
+
+      @Override
+      public Value get(Key key) {
+        return lruCache.get(key);
+      }
+
+      @Override
+      public void remove(Key key) {
+        lruCache.remove(key);
+      }
+
+      @Override
+      public void clear() {
+        lruCache.evictAll();
+      }
+    };
+  }
+
+  public static SafeMethodCachingInterceptor newSafeMethodCachingInterceptor(Cache cache) {
+    return newSafeMethodCachingInterceptor(cache, DEFAULT_MAX_AGE_SECONDS);
+  }
+
+  public static SafeMethodCachingInterceptor newSafeMethodCachingInterceptor(
+      Cache cache, int defaultMaxAge) {
+    return new SafeMethodCachingInterceptor(cache, defaultMaxAge);
+  }
+
+  private static int DEFAULT_MAX_AGE_SECONDS = 3600;
+
+  private static final Metadata.Key<String> CACHE_CONTROL_KEY =
+      Metadata.Key.of("cache-control", Metadata.ASCII_STRING_MARSHALLER);
+
+  private static final Splitter CACHE_CONTROL_SPLITTER =
+      Splitter.on(',').trimResults().omitEmptyStrings();
+
+  private final Cache internalCache;
+  private final int defaultMaxAge;
+
+  private SafeMethodCachingInterceptor(Cache cache, int defaultMaxAge) {
+    this.internalCache = cache;
+    this.defaultMaxAge = defaultMaxAge;
+  }
+
+  @Override
+  public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+      final MethodDescriptor<ReqT, RespT> method, final CallOptions callOptions, Channel next) {
+    // Currently only unary methods can be marked safe, but check anyways.
+    if (!method.isSafe() || method.getType() != MethodDescriptor.MethodType.UNARY) {
+      return next.newCall(method, callOptions);
+    }
+
+    final String fullMethodName = method.getFullMethodName();
+
+    return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
+        next.newCall(method, callOptions)) {
+      private Listener<RespT> interceptedListener;
+      private Key requestKey;
+      private boolean cacheResponse = true;
+      private volatile String cacheOptionsErrorMsg;
+
+      @Override
+      public void start(Listener<RespT> responseListener, Metadata headers) {
+        interceptedListener =
+            new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(
+                responseListener) {
+              private Deadline deadline;
+              private int maxAge = -1;
+
+              @Override
+              public void onHeaders(Metadata headers) {
+                Iterable<String> cacheControlHeaders = headers.getAll(CACHE_CONTROL_KEY);
+                if (cacheResponse && cacheControlHeaders != null) {
+                  for (String cacheControlHeader : cacheControlHeaders) {
+                    for (String directive : CACHE_CONTROL_SPLITTER.split(cacheControlHeader)) {
+                      if (directive.equalsIgnoreCase("no-cache")) {
+                        cacheResponse = false;
+                        break;
+                      } else if (directive.equalsIgnoreCase("no-store")) {
+                        cacheResponse = false;
+                        break;
+                      } else if (directive.equalsIgnoreCase("no-transform")) {
+                        cacheResponse = false;
+                        break;
+                      } else if (directive.toLowerCase(Locale.US).startsWith("max-age")) {
+                        String[] parts = directive.split("=");
+                        if (parts.length == 2) {
+                          try {
+                            maxAge = Integer.parseInt(parts[1]);
+                          } catch (NumberFormatException e) {
+                            Log.e(TAG, "max-age directive failed to parse", e);
+                            continue;
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+                if (cacheResponse) {
+                  if (maxAge > -1) {
+                    deadline = Deadline.after(maxAge, TimeUnit.SECONDS);
+                  } else {
+                    deadline = Deadline.after(defaultMaxAge, TimeUnit.SECONDS);
+                  }
+                }
+                super.onHeaders(headers);
+              }
+
+              @Override
+              public void onMessage(RespT message) {
+                if (cacheResponse && !deadline.isExpired()) {
+                  Value value = new Value((MessageLite) message, deadline);
+                  internalCache.put(requestKey, value);
+                }
+                super.onMessage(message);
+              }
+
+              @Override
+              public void onClose(Status status, Metadata trailers) {
+                if (cacheOptionsErrorMsg != null) {
+                  // UNAVAILABLE is the canonical gRPC mapping for HTTP response code 504 (as used
+                  // by the built-in Android HTTP request cache).
+                  super.onClose(
+                      Status.UNAVAILABLE.withDescription(cacheOptionsErrorMsg), new Metadata());
+                } else {
+                  super.onClose(status, trailers);
+                }
+              }
+            };
+        delegate().start(interceptedListener, headers);
+      }
+
+      @Override
+      public void sendMessage(ReqT message) {
+        boolean noCache = callOptions.getOption(NO_CACHE_CALL_OPTION);
+        boolean onlyIfCached = callOptions.getOption(ONLY_IF_CACHED_CALL_OPTION);
+
+        if (noCache) {
+          if (onlyIfCached) {
+            cacheOptionsErrorMsg = "Unsatisfiable Request (no-cache and only-if-cached conflict)";
+            super.cancel(cacheOptionsErrorMsg, null);
+            return;
+          }
+          cacheResponse = false;
+          super.sendMessage(message);
+          return;
+        }
+
+        // Check the cache
+        requestKey = new Key(fullMethodName, (MessageLite) message);
+        Value cachedResponse = internalCache.get(requestKey);
+        if (cachedResponse != null) {
+          if (cachedResponse.maxAgeDeadline.isExpired()) {
+            internalCache.remove(requestKey);
+          } else {
+            cacheResponse = false; // already cached
+            interceptedListener.onMessage((RespT) cachedResponse.response);
+            Metadata metadata = new Metadata();
+            interceptedListener.onClose(Status.OK, metadata);
+            return;
+          }
+        }
+
+        if (onlyIfCached) {
+          cacheOptionsErrorMsg =
+              "Unsatisfiable Request (only-if-cached set, but value not in cache)";
+          super.cancel(cacheOptionsErrorMsg, null);
+          return;
+        }
+        super.sendMessage(message);
+      }
+
+      @Override
+      public void halfClose() {
+        if (cacheOptionsErrorMsg != null) {
+          // already canceled
+          return;
+        }
+        super.halfClose();
+      }
+    };
+  }
+}
diff --git a/examples/android/clientcache/app/src/main/proto/helloworld.proto b/examples/android/clientcache/app/src/main/proto/helloworld.proto
new file mode 100644
index 0000000..3ee78e7
--- /dev/null
+++ b/examples/android/clientcache/app/src/main/proto/helloworld.proto
@@ -0,0 +1,44 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.examples.helloworld";
+option java_outer_classname = "HelloWorldProto";
+option objc_class_prefix = "HLW";
+
+package helloworld;
+
+// The greeting service definition.
+service Greeter {
+  // Sends a greeting
+  rpc SayHello (HelloRequest) returns (HelloReply) {}
+
+  rpc SayAnotherHello (HelloRequest) returns (HelloReply) {}
+
+}
+
+service AnotherGreeter {
+  rpc SayHello (HelloRequest) returns (HelloReply) {}
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+  string name = 1;
+}
+
+// The response message containing the greetings
+message HelloReply {
+  string message = 1;
+}
diff --git a/examples/android/clientcache/app/src/main/res/layout/activity_clientcacheexample.xml b/examples/android/clientcache/app/src/main/res/layout/activity_clientcacheexample.xml
new file mode 100644
index 0000000..2e48e8a
--- /dev/null
+++ b/examples/android/clientcache/app/src/main/res/layout/activity_clientcacheexample.xml
@@ -0,0 +1,73 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              tools:context=".MainActivity"
+              android:orientation="vertical" >
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+        <EditText
+                android:id="@+id/host_edit_text"
+                android:layout_weight="2"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:hint="Enter Host" />
+        <EditText
+                android:id="@+id/port_edit_text"
+                android:layout_weight="1"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:inputType="numberDecimal"
+                android:hint="Enter Port" />
+    </LinearLayout>
+
+
+    <EditText
+            android:id="@+id/message_edit_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:hint="Enter message to send" />
+
+    <CheckBox android:id="@+id/get_checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Use GET"
+        />
+
+    <CheckBox android:id="@+id/no_cache_checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Cache-control: no-cache (only for GET)"
+        />
+
+    <CheckBox android:id="@+id/only_if_cached_checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Cache-control: only-if-cached (only for GET)"
+        />
+
+    <Button
+            android:id="@+id/send_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:onClick="sendMessage"
+            android:text="Send Grpc Request" />
+
+    <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="12dp"
+            android:paddingBottom="12dp"
+            android:textSize="16sp"
+            android:text="Response:" />
+
+    <TextView
+            android:id="@+id/grpc_response_text"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scrollbars = "vertical"
+            android:textSize="16sp" />
+
+</LinearLayout>
diff --git a/examples/android/clientcache/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/android/clientcache/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/examples/android/clientcache/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/clientcache/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/android/clientcache/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/examples/android/clientcache/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/clientcache/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/android/clientcache/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/examples/android/clientcache/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/clientcache/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/android/clientcache/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/examples/android/clientcache/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/clientcache/app/src/main/res/values/strings.xml b/examples/android/clientcache/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..0f8d82d
--- /dev/null
+++ b/examples/android/clientcache/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">GrpcClientCacheExample</string>
+</resources>
diff --git a/examples/android/clientcache/app/src/test/java/io/grpc/clientcacheexample/SafeMethodCachingInterceptorTest.java b/examples/android/clientcache/app/src/test/java/io/grpc/clientcacheexample/SafeMethodCachingInterceptorTest.java
new file mode 100644
index 0000000..63e8a32
--- /dev/null
+++ b/examples/android/clientcache/app/src/test/java/io/grpc/clientcacheexample/SafeMethodCachingInterceptorTest.java
@@ -0,0 +1,557 @@
+package io.grpc.clientcacheexample;
+
+import com.google.common.truth.Truth;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientInterceptors;
+import io.grpc.ForwardingServerCall;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.ServerInterceptors;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.examples.helloworld.AnotherGreeterGrpc;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.stub.ClientCalls;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.GrpcServerRule;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class SafeMethodCachingInterceptorTest {
+  private static final Metadata.Key<String> CACHE_CONTROL_METADATA_KEY =
+      Metadata.Key.of("cache-control", Metadata.ASCII_STRING_MARSHALLER);
+
+  @Rule public final GrpcServerRule grpcServerRule = new GrpcServerRule().directExecutor();
+
+  private final GreeterGrpc.GreeterImplBase greeterServiceImpl =
+      new GreeterGrpc.GreeterImplBase() {
+        private int count = 1;
+
+        @Override
+        public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
+          HelloReply reply =
+              HelloReply.newBuilder().setMessage("Hello " + req.getName() + " " + count++).build();
+          responseObserver.onNext(reply);
+          responseObserver.onCompleted();
+        }
+
+        @Override
+        public void sayAnotherHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
+          HelloReply reply =
+              HelloReply.newBuilder()
+                  .setMessage("Hello again " + req.getName() + " " + count++)
+                  .build();
+          responseObserver.onNext(reply);
+          responseObserver.onCompleted();
+        }
+      };
+
+  private final AnotherGreeterGrpc.AnotherGreeterImplBase anotherGreeterServiceImpl =
+      new AnotherGreeterGrpc.AnotherGreeterImplBase() {
+        private int count = 1;
+
+        @Override
+        public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
+          HelloReply reply =
+              HelloReply.newBuilder().setMessage("Hey " + req.getName() + " " + count++).build();
+          responseObserver.onNext(reply);
+          responseObserver.onCompleted();
+        }
+      };
+
+  private final List<String> cacheControlDirectives = new ArrayList<>();
+  private ServerInterceptor injectCacheControlInterceptor =
+      new ServerInterceptor() {
+        @Override
+        public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+            ServerCall<ReqT, RespT> call,
+            final Metadata requestHeaders,
+            ServerCallHandler<ReqT, RespT> next) {
+          return next.startCall(
+              new ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT>(call) {
+                @Override
+                public void sendHeaders(Metadata headers) {
+                  for (String cacheControlDirective : cacheControlDirectives) {
+                    headers.put(CACHE_CONTROL_METADATA_KEY, cacheControlDirective);
+                  }
+                  super.sendHeaders(headers);
+                }
+              },
+              requestHeaders);
+        }
+      };
+
+  private final HelloRequest message = HelloRequest.newBuilder().setName("Test Name").build();
+  private final MethodDescriptor<HelloRequest, HelloReply> safeGreeterSayHelloMethod =
+      GreeterGrpc.getSayHelloMethod().toBuilder().setSafe(true).build();
+  private final TestCache cache = new TestCache();
+
+  private ManagedChannel baseChannel;
+  private Channel channelToUse;
+
+  @Before
+  public void setUp() throws Exception {
+    grpcServerRule
+        .getServiceRegistry()
+        .addService(
+            ServerInterceptors.intercept(greeterServiceImpl, injectCacheControlInterceptor));
+    grpcServerRule.getServiceRegistry().addService(anotherGreeterServiceImpl);
+    baseChannel = grpcServerRule.getChannel();
+
+    SafeMethodCachingInterceptor interceptor =
+        SafeMethodCachingInterceptor.newSafeMethodCachingInterceptor(cache);
+
+    channelToUse = ClientInterceptors.intercept(baseChannel, interceptor);
+  }
+
+  @After
+  public void tearDown() {
+    baseChannel.shutdown();
+  }
+
+  @Test
+  public void safeCallsAreCached() {
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+
+    assertSame(reply1, reply2);
+  }
+
+  @Test
+  public void safeCallsAreCachedWithCopiedMethodDescriptor() {
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse,
+            safeGreeterSayHelloMethod.toBuilder().build(),
+            CallOptions.DEFAULT,
+            message);
+
+    assertSame(reply1, reply2);
+  }
+
+  @Test
+  public void requestWithNoCacheOptionSkipsCache() {
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse,
+            safeGreeterSayHelloMethod,
+            CallOptions.DEFAULT.withOption(SafeMethodCachingInterceptor.NO_CACHE_CALL_OPTION, true),
+            message);
+    HelloReply reply3 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+
+    assertNotEquals(reply1, reply2);
+    assertSame(reply1, reply3);
+  }
+
+  @Test
+  public void requestWithOnlyIfCachedOption_unavailableIfNotInCache() {
+    try {
+      ClientCalls.blockingUnaryCall(
+          channelToUse,
+          safeGreeterSayHelloMethod,
+          CallOptions.DEFAULT.withOption(
+              SafeMethodCachingInterceptor.ONLY_IF_CACHED_CALL_OPTION, true),
+          message);
+      fail("Expected call to fail");
+    } catch (StatusRuntimeException sre) {
+      assertEquals(Status.UNAVAILABLE.getCode(), sre.getStatus().getCode());
+      assertEquals(
+          "Unsatisfiable Request (only-if-cached set, but value not in cache)",
+          sre.getStatus().getDescription());
+    }
+  }
+
+  @Test
+  public void requestWithOnlyIfCachedOption_usesCache() {
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse,
+            safeGreeterSayHelloMethod,
+            CallOptions.DEFAULT.withOption(
+                SafeMethodCachingInterceptor.ONLY_IF_CACHED_CALL_OPTION, true),
+            message);
+
+    assertSame(reply1, reply2);
+  }
+
+  @Test
+  public void requestWithNoCacheAndOnlyIfCached_fails() {
+    try {
+      ClientCalls.blockingUnaryCall(
+          channelToUse,
+          safeGreeterSayHelloMethod,
+          CallOptions.DEFAULT
+              .withOption(SafeMethodCachingInterceptor.NO_CACHE_CALL_OPTION, true)
+              .withOption(SafeMethodCachingInterceptor.ONLY_IF_CACHED_CALL_OPTION, true),
+          message);
+      fail("Expected call to fail");
+    } catch (StatusRuntimeException sre) {
+      assertEquals(Status.UNAVAILABLE.getCode(), sre.getStatus().getCode());
+      assertEquals(
+          "Unsatisfiable Request (no-cache and only-if-cached conflict)",
+          sre.getStatus().getDescription());
+    }
+  }
+
+  @Test
+  public void responseNoCacheDirective_notCached() throws Exception {
+    cacheControlDirectives.add("no-cache");
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+
+    assertNotEquals(reply1, reply2);
+    assertNotEquals(reply1, reply2);
+    Truth.assertThat(cache.internalCache).isEmpty();
+    Truth.assertThat(cache.removedKeys).isEmpty();
+  }
+
+  @Test
+  public void responseNoStoreDirective_notCached() throws Exception {
+    cacheControlDirectives.add("no-store");
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+
+    assertNotEquals(reply1, reply2);
+    Truth.assertThat(cache.internalCache).isEmpty();
+    Truth.assertThat(cache.removedKeys).isEmpty();
+  }
+
+  @Test
+  public void responseNoTransformDirective_notCached() throws Exception {
+    cacheControlDirectives.add("no-transform");
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+
+    assertNotEquals(reply1, reply2);
+    Truth.assertThat(cache.internalCache).isEmpty();
+    Truth.assertThat(cache.removedKeys).isEmpty();
+  }
+
+  @Test
+  public void responseMustRevalidateDirective_isIgnored() throws Exception {
+    cacheControlDirectives.add("must-revalidate");
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+
+    assertSame(reply1, reply2);
+  }
+
+  @Test
+  public void responseMaxAge_caseInsensitive() throws Exception {
+    cacheControlDirectives.add("MaX-aGe=0");
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+
+    assertNotEquals(reply1, reply2);
+    Truth.assertThat(cache.internalCache).isEmpty();
+    Truth.assertThat(cache.removedKeys).isEmpty();
+  }
+
+  @Test
+  public void responseNoCache_caseInsensitive() throws Exception {
+    cacheControlDirectives.add("No-CaCHe");
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+
+    assertNotEquals(reply1, reply2);
+    Truth.assertThat(cache.internalCache).isEmpty();
+    Truth.assertThat(cache.removedKeys).isEmpty();
+  }
+
+  @Test
+  public void combinedResponseCacheControlDirectives_parsesWithoutError() throws Exception {
+    cacheControlDirectives.add("max-age=1,no-store , no-cache");
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+
+    assertNotEquals(reply1, reply2);
+    Truth.assertThat(cache.internalCache).isEmpty();
+    Truth.assertThat(cache.removedKeys).isEmpty();
+  }
+
+  @Test
+  public void separateResponseCacheControlDirectives_parsesWithoutError() throws Exception {
+    cacheControlDirectives.add("max-age=1");
+    cacheControlDirectives.add("no-store , no-cache");
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+
+    assertNotEquals(reply1, reply2);
+    Truth.assertThat(cache.internalCache).isEmpty();
+    Truth.assertThat(cache.removedKeys).isEmpty();
+  }
+
+  @Test
+  public void afterResponseMaxAge_cacheEntryInvalidated() throws Exception {
+    cacheControlDirectives.add("max-age=1");
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    assertSame(reply1, reply2);
+
+    // Wait for cache entry to expire
+    sleepAtLeast(1001);
+
+    assertNotEquals(
+        reply1,
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message));
+    Truth.assertThat(cache.removedKeys).hasSize(1);
+    assertEquals(
+        new SafeMethodCachingInterceptor.Key(
+            GreeterGrpc.getSayHelloMethod().getFullMethodName(), message),
+        cache.removedKeys.get(0));
+  }
+
+  @Test
+  public void invalidResponseMaxAge_usesDefault() throws Exception {
+    SafeMethodCachingInterceptor interceptorWithCustomMaxAge =
+        SafeMethodCachingInterceptor.newSafeMethodCachingInterceptor(cache, 1);
+    channelToUse = ClientInterceptors.intercept(baseChannel, interceptorWithCustomMaxAge);
+    cacheControlDirectives.add("max-age=-10");
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    assertEquals(reply1, reply2);
+
+    // Wait for cache entry to expire
+    sleepAtLeast(1001);
+
+    assertNotEquals(
+        reply1,
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message));
+    Truth.assertThat(cache.removedKeys).hasSize(1);
+    assertEquals(
+        new SafeMethodCachingInterceptor.Key(
+            GreeterGrpc.getSayHelloMethod().getFullMethodName(), message),
+        cache.removedKeys.get(0));
+  }
+
+  @Test
+  public void responseMaxAgeZero_notAddedToCache() throws Exception {
+    cacheControlDirectives.add("max-age=0");
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+
+    assertNotEquals(reply1, reply2);
+    Truth.assertThat(cache.internalCache).isEmpty();
+    Truth.assertThat(cache.removedKeys).isEmpty();
+  }
+
+  @Test
+  public void cacheHit_doesNotResetExpiration() throws Exception {
+    cacheControlDirectives.add("max-age=1");
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+
+    sleepAtLeast(1001);
+
+    HelloReply reply3 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+
+    assertSame(reply1, reply2);
+    assertNotEquals(reply1, reply3);
+    Truth.assertThat(cache.internalCache).hasSize(1);
+    Truth.assertThat(cache.removedKeys).hasSize(1);
+  }
+
+  @Test
+  public void afterDefaultMaxAge_cacheEntryInvalidated() throws Exception {
+    SafeMethodCachingInterceptor interceptorWithCustomMaxAge =
+        SafeMethodCachingInterceptor.newSafeMethodCachingInterceptor(cache, 1);
+    channelToUse = ClientInterceptors.intercept(baseChannel, interceptorWithCustomMaxAge);
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    assertSame(reply1, reply2);
+
+    // Wait for cache entry to expire
+    sleepAtLeast(1001);
+
+    assertNotEquals(
+        reply1,
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message));
+    Truth.assertThat(cache.removedKeys).hasSize(1);
+    assertEquals(
+        new SafeMethodCachingInterceptor.Key(
+            GreeterGrpc.getSayHelloMethod().getFullMethodName(), message),
+        cache.removedKeys.get(0));
+  }
+
+  @Test
+  public void unsafeCallsAreNotCached() {
+    GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channelToUse);
+
+    HelloReply reply1 = stub.sayHello(message);
+    HelloReply reply2 = stub.sayHello(message);
+
+    assertNotEquals(reply1, reply2);
+  }
+
+  @Test
+  public void differentMethodCallsAreNotConflated() {
+    MethodDescriptor<HelloRequest, HelloReply> anotherSafeMethod =
+        GreeterGrpc.getSayAnotherHelloMethod().toBuilder().setSafe(true).build();
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, anotherSafeMethod, CallOptions.DEFAULT, message);
+
+    assertNotEquals(reply1, reply2);
+  }
+
+  @Test
+  public void differentServiceCallsAreNotConflated() {
+    MethodDescriptor<HelloRequest, HelloReply> anotherSafeMethod =
+        AnotherGreeterGrpc.getSayHelloMethod().toBuilder().setSafe(true).build();
+
+    HelloReply reply1 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, safeGreeterSayHelloMethod, CallOptions.DEFAULT, message);
+    HelloReply reply2 =
+        ClientCalls.blockingUnaryCall(
+            channelToUse, anotherSafeMethod, CallOptions.DEFAULT, message);
+
+    assertNotEquals(reply1, reply2);
+  }
+
+  private static void sleepAtLeast(long millis) throws InterruptedException {
+    long delay = TimeUnit.MILLISECONDS.toNanos(millis);
+    long end = System.nanoTime() + delay;
+    while (delay > 0) {
+      TimeUnit.NANOSECONDS.sleep(delay);
+      delay = end - System.nanoTime();
+    }
+  }
+
+  private static class TestCache implements SafeMethodCachingInterceptor.Cache {
+    private Map<SafeMethodCachingInterceptor.Key, SafeMethodCachingInterceptor.Value>
+        internalCache =
+            new HashMap<SafeMethodCachingInterceptor.Key, SafeMethodCachingInterceptor.Value>();
+    private List<SafeMethodCachingInterceptor.Key> removedKeys =
+        new ArrayList<SafeMethodCachingInterceptor.Key>();
+
+    @Override
+    public void put(
+        SafeMethodCachingInterceptor.Key key, SafeMethodCachingInterceptor.Value value) {
+      internalCache.put(key, value);
+    }
+
+    @Override
+    public SafeMethodCachingInterceptor.Value get(SafeMethodCachingInterceptor.Key key) {
+      return internalCache.get(key);
+    }
+
+    @Override
+    public void remove(SafeMethodCachingInterceptor.Key key) {
+      removedKeys.add(key);
+      internalCache.remove(key);
+    }
+
+    @Override
+    public void clear() {}
+  }
+}
diff --git a/examples/android/clientcache/build.gradle b/examples/android/clientcache/build.gradle
new file mode 100644
index 0000000..2e20251
--- /dev/null
+++ b/examples/android/clientcache/build.gradle
@@ -0,0 +1,23 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.0.1'
+        classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.5"
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+        mavenLocal()
+    }
+}
diff --git a/examples/android/clientcache/gradle/wrapper/gradle-wrapper.jar b/examples/android/clientcache/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..0d4a951
--- /dev/null
+++ b/examples/android/clientcache/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/examples/android/clientcache/gradle/wrapper/gradle-wrapper.properties b/examples/android/clientcache/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..a95009c
--- /dev/null
+++ b/examples/android/clientcache/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/examples/android/clientcache/gradlew b/examples/android/clientcache/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/examples/android/clientcache/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/examples/android/clientcache/gradlew.bat b/examples/android/clientcache/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/examples/android/clientcache/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/examples/android/clientcache/settings.gradle b/examples/android/clientcache/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/examples/android/clientcache/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/examples/android/helloworld/.gitignore b/examples/android/helloworld/.gitignore
new file mode 100644
index 0000000..6345b76
--- /dev/null
+++ b/examples/android/helloworld/.gitignore
@@ -0,0 +1,21 @@
+.gradle
+/local.properties
+/gradle.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+.idea/
+
+*.iml
+*.apk
+*.ap_
+*.dex
+*.class
+bin/
+gen/
+.gradle/
+/*/build/
+local.properties
+proguard/
+*.log
diff --git a/examples/android/helloworld/app/build.gradle b/examples/android/helloworld/app/build.gradle
new file mode 100644
index 0000000..a994ab4
--- /dev/null
+++ b/examples/android/helloworld/app/build.gradle
@@ -0,0 +1,55 @@
+apply plugin: 'com.android.application'
+apply plugin: 'com.google.protobuf'
+
+android {
+    compileSdkVersion 27
+
+    defaultConfig {
+        applicationId "io.grpc.helloworldexample"
+        // API level 14+ is required for TLS since Google Play Services v10.2
+        minSdkVersion 14
+        targetSdkVersion 27
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        debug { minifyEnabled false }
+        release {
+            minifyEnabled true
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    lintOptions {
+        disable 'GoogleAppIndexingWarning', 'HardcodedText', 'InvalidPackage'
+        textReport true
+        textOutput "stdout"
+    }
+}
+
+protobuf {
+    protoc { artifact = 'com.google.protobuf:protoc:3.5.1-1' }
+    plugins {
+        javalite { artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0" }
+        grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+        }
+    }
+    generateProtoTasks {
+        all().each { task ->
+            task.plugins {
+                javalite {}
+                grpc { // Options added to --grpc_out
+                    option 'lite' }
+            }
+        }
+    }
+}
+
+dependencies {
+    compile 'com.android.support:appcompat-v7:27.0.2'
+
+    // You need to build grpc-java to obtain these libraries below.
+    compile 'io.grpc:grpc-okhttp:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-protobuf-lite:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-stub:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    compile 'javax.annotation:javax.annotation-api:1.2'
+}
diff --git a/examples/android/helloworld/app/proguard-rules.pro b/examples/android/helloworld/app/proguard-rules.pro
new file mode 100644
index 0000000..1507a52
--- /dev/null
+++ b/examples/android/helloworld/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in $ANDROID_HOME/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+-dontwarn com.google.common.**
+# Ignores: can't find referenced class javax.lang.model.element.Modifier
+-dontwarn com.google.errorprone.annotations.**
+-dontwarn javax.naming.**
+-dontwarn okio.**
+-dontwarn sun.misc.Unsafe
diff --git a/examples/android/helloworld/app/src/main/AndroidManifest.xml b/examples/android/helloworld/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..eee4057
--- /dev/null
+++ b/examples/android/helloworld/app/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.grpc.helloworldexample" >
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application
+        android:allowBackup="false"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/Base.V7.Theme.AppCompat.Light" >
+        <activity
+            android:name=".HelloworldActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/examples/android/helloworld/app/src/main/java/io/grpc/helloworldexample/HelloworldActivity.java b/examples/android/helloworld/app/src/main/java/io/grpc/helloworldexample/HelloworldActivity.java
new file mode 100644
index 0000000..28cd488
--- /dev/null
+++ b/examples/android/helloworld/app/src/main/java/io/grpc/helloworldexample/HelloworldActivity.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.helloworldexample;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.text.TextUtils;
+import android.text.method.ScrollingMovementMethod;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.ref.WeakReference;
+import java.util.concurrent.TimeUnit;
+
+public class HelloworldActivity extends AppCompatActivity {
+  private Button sendButton;
+  private EditText hostEdit;
+  private EditText portEdit;
+  private EditText messageEdit;
+  private TextView resultText;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_helloworld);
+    sendButton = (Button) findViewById(R.id.send_button);
+    hostEdit = (EditText) findViewById(R.id.host_edit_text);
+    portEdit = (EditText) findViewById(R.id.port_edit_text);
+    messageEdit = (EditText) findViewById(R.id.message_edit_text);
+    resultText = (TextView) findViewById(R.id.grpc_response_text);
+    resultText.setMovementMethod(new ScrollingMovementMethod());
+  }
+
+  public void sendMessage(View view) {
+    ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
+        .hideSoftInputFromWindow(hostEdit.getWindowToken(), 0);
+    sendButton.setEnabled(false);
+    resultText.setText("");
+    new GrpcTask(this)
+        .execute(
+            hostEdit.getText().toString(),
+            messageEdit.getText().toString(),
+            portEdit.getText().toString());
+  }
+
+  private static class GrpcTask extends AsyncTask<String, Void, String> {
+    private final WeakReference<Activity> activityReference;
+    private ManagedChannel channel;
+
+    private GrpcTask(Activity activity) {
+      this.activityReference = new WeakReference<Activity>(activity);
+    }
+
+    @Override
+    protected String doInBackground(String... params) {
+      String host = params[0];
+      String message = params[1];
+      String portStr = params[2];
+      int port = TextUtils.isEmpty(portStr) ? 0 : Integer.valueOf(portStr);
+      try {
+        channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
+        GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);
+        HelloRequest request = HelloRequest.newBuilder().setName(message).build();
+        HelloReply reply = stub.sayHello(request);
+        return reply.getMessage();
+      } catch (Exception e) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        e.printStackTrace(pw);
+        pw.flush();
+        return String.format("Failed... : %n%s", sw);
+      }
+    }
+
+    @Override
+    protected void onPostExecute(String result) {
+      try {
+        channel.shutdown().awaitTermination(1, TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+      }
+      Activity activity = activityReference.get();
+      if (activity == null) {
+        return;
+      }
+      TextView resultText = (TextView) activity.findViewById(R.id.grpc_response_text);
+      Button sendButton = (Button) activity.findViewById(R.id.send_button);
+      resultText.setText(result);
+      sendButton.setEnabled(true);
+    }
+  }
+}
diff --git a/examples/android/helloworld/app/src/main/proto/helloworld.proto b/examples/android/helloworld/app/src/main/proto/helloworld.proto
new file mode 100644
index 0000000..c60d941
--- /dev/null
+++ b/examples/android/helloworld/app/src/main/proto/helloworld.proto
@@ -0,0 +1,37 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.examples.helloworld";
+option java_outer_classname = "HelloWorldProto";
+option objc_class_prefix = "HLW";
+
+package helloworld;
+
+// The greeting service definition.
+service Greeter {
+  // Sends a greeting
+  rpc SayHello (HelloRequest) returns (HelloReply) {}
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+  string name = 1;
+}
+
+// The response message containing the greetings
+message HelloReply {
+  string message = 1;
+}
diff --git a/examples/android/helloworld/app/src/main/res/layout/activity_helloworld.xml b/examples/android/helloworld/app/src/main/res/layout/activity_helloworld.xml
new file mode 100644
index 0000000..18b05c3
--- /dev/null
+++ b/examples/android/helloworld/app/src/main/res/layout/activity_helloworld.xml
@@ -0,0 +1,55 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              tools:context=".MainActivity"
+              android:orientation="vertical" >
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+        <EditText
+                android:id="@+id/host_edit_text"
+                android:layout_weight="2"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:hint="Enter Host" />
+        <EditText
+                android:id="@+id/port_edit_text"
+                android:layout_weight="1"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:inputType="numberDecimal"
+                android:hint="Enter Port" />
+    </LinearLayout>
+
+
+    <EditText
+            android:id="@+id/message_edit_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:hint="Enter message to send" />
+
+    <Button
+            android:id="@+id/send_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:onClick="sendMessage"
+            android:text="Send Grpc Request" />
+
+    <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="12dp"
+            android:paddingBottom="12dp"
+            android:textSize="16sp"
+            android:text="Response:" />
+
+    <TextView
+            android:id="@+id/grpc_response_text"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scrollbars = "vertical"
+            android:textSize="16sp" />
+
+</LinearLayout>
diff --git a/examples/android/helloworld/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/android/helloworld/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/examples/android/helloworld/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/helloworld/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/android/helloworld/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/examples/android/helloworld/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/helloworld/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/android/helloworld/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/examples/android/helloworld/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/helloworld/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/android/helloworld/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/examples/android/helloworld/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/helloworld/app/src/main/res/values/strings.xml b/examples/android/helloworld/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..64cb312
--- /dev/null
+++ b/examples/android/helloworld/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">GrpcHelloworldExample</string>
+</resources>
diff --git a/examples/android/helloworld/build.gradle b/examples/android/helloworld/build.gradle
new file mode 100644
index 0000000..2e20251
--- /dev/null
+++ b/examples/android/helloworld/build.gradle
@@ -0,0 +1,23 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.0.1'
+        classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.5"
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+        mavenLocal()
+    }
+}
diff --git a/examples/android/helloworld/gradle/wrapper/gradle-wrapper.jar b/examples/android/helloworld/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..0d4a951
--- /dev/null
+++ b/examples/android/helloworld/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/examples/android/helloworld/gradle/wrapper/gradle-wrapper.properties b/examples/android/helloworld/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..a95009c
--- /dev/null
+++ b/examples/android/helloworld/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/examples/android/helloworld/gradlew b/examples/android/helloworld/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/examples/android/helloworld/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/examples/android/helloworld/gradlew.bat b/examples/android/helloworld/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/examples/android/helloworld/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/examples/android/helloworld/settings.gradle b/examples/android/helloworld/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/examples/android/helloworld/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/examples/android/routeguide/.gitignore b/examples/android/routeguide/.gitignore
new file mode 100644
index 0000000..6345b76
--- /dev/null
+++ b/examples/android/routeguide/.gitignore
@@ -0,0 +1,21 @@
+.gradle
+/local.properties
+/gradle.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+.idea/
+
+*.iml
+*.apk
+*.ap_
+*.dex
+*.class
+bin/
+gen/
+.gradle/
+/*/build/
+local.properties
+proguard/
+*.log
diff --git a/examples/android/routeguide/app/.gitignore b/examples/android/routeguide/app/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/examples/android/routeguide/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/examples/android/routeguide/app/build.gradle b/examples/android/routeguide/app/build.gradle
new file mode 100644
index 0000000..f92a996
--- /dev/null
+++ b/examples/android/routeguide/app/build.gradle
@@ -0,0 +1,54 @@
+apply plugin: 'com.android.application'
+apply plugin: 'com.google.protobuf'
+
+android {
+    compileSdkVersion 27
+
+    defaultConfig {
+        applicationId "io.grpc.routeguideexample"
+        minSdkVersion 14
+        targetSdkVersion 27
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        debug { minifyEnabled false }
+        release {
+            minifyEnabled true
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    lintOptions {
+        disable 'GoogleAppIndexingWarning', 'HardcodedText', 'InvalidPackage'
+        textReport true
+        textOutput "stdout"
+    }
+}
+
+protobuf {
+    protoc { artifact = 'com.google.protobuf:protoc:3.5.1-1' }
+    plugins {
+        javalite { artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0" }
+        grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+        }
+    }
+    generateProtoTasks {
+        all().each { task ->
+            task.plugins {
+                javalite {}
+                grpc { // Options added to --grpc_out
+                    option 'lite' }
+            }
+        }
+    }
+}
+
+dependencies {
+    compile 'com.android.support:appcompat-v7:27.0.2'
+
+    // You need to build grpc-java to obtain these libraries below.
+    compile 'io.grpc:grpc-okhttp:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-protobuf-lite:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-stub:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    compile 'javax.annotation:javax.annotation-api:1.2'
+}
diff --git a/examples/android/routeguide/app/proguard-rules.pro b/examples/android/routeguide/app/proguard-rules.pro
new file mode 100644
index 0000000..8f066eb
--- /dev/null
+++ b/examples/android/routeguide/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in $ANDROID_HOME/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+-dontwarn sun.misc.Unsafe
+-dontwarn com.google.common.**
+-dontwarn javax.naming.**
+-dontwarn okio.**
+# Ignores: can't find referenced class javax.lang.model.element.Modifier
+-dontwarn com.google.errorprone.annotations.**
diff --git a/examples/android/routeguide/app/src/main/AndroidManifest.xml b/examples/android/routeguide/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..f740975
--- /dev/null
+++ b/examples/android/routeguide/app/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.grpc.routeguideexample">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application android:allowBackup="false"
+        android:label="@string/app_name"
+        android:icon="@mipmap/ic_launcher"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name=".RouteGuideActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+    </application>
+
+</manifest>
diff --git a/examples/android/routeguide/app/src/main/java/io/grpc/routeguideexample/RouteGuideActivity.java b/examples/android/routeguide/app/src/main/java/io/grpc/routeguideexample/RouteGuideActivity.java
new file mode 100644
index 0000000..f892057
--- /dev/null
+++ b/examples/android/routeguide/app/src/main/java/io/grpc/routeguideexample/RouteGuideActivity.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.routeguideexample;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.text.TextUtils;
+import android.text.method.ScrollingMovementMethod;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.StatusRuntimeException;
+import io.grpc.routeguideexample.RouteGuideGrpc.RouteGuideBlockingStub;
+import io.grpc.routeguideexample.RouteGuideGrpc.RouteGuideStub;
+import io.grpc.stub.StreamObserver;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.ref.WeakReference;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class RouteGuideActivity extends AppCompatActivity {
+  private EditText hostEdit;
+  private EditText portEdit;
+  private Button startRouteGuideButton;
+  private Button exitRouteGuideButton;
+  private Button getFeatureButton;
+  private Button listFeaturesButton;
+  private Button recordRouteButton;
+  private Button routeChatButton;
+  private TextView resultText;
+  private ManagedChannel channel;
+
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    setContentView(R.layout.activity_routeguide);
+    hostEdit = (EditText) findViewById(R.id.host_edit_text);
+    portEdit = (EditText) findViewById(R.id.port_edit_text);
+    startRouteGuideButton = (Button) findViewById(R.id.start_route_guide_button);
+    exitRouteGuideButton = (Button) findViewById(R.id.exit_route_guide_button);
+    getFeatureButton = (Button) findViewById(R.id.get_feature_button);
+    listFeaturesButton = (Button) findViewById(R.id.list_features_button);
+    recordRouteButton = (Button) findViewById(R.id.record_route_button);
+    routeChatButton = (Button) findViewById(R.id.route_chat_button);
+    resultText = (TextView) findViewById(R.id.result_text);
+    resultText.setMovementMethod(new ScrollingMovementMethod());
+    disableButtons();
+  }
+
+  public void startRouteGuide(View view) {
+    String host = hostEdit.getText().toString();
+    String portStr = portEdit.getText().toString();
+    int port = TextUtils.isEmpty(portStr) ? 0 : Integer.valueOf(portStr);
+    ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))
+        .hideSoftInputFromWindow(hostEdit.getWindowToken(), 0);
+    channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
+    hostEdit.setEnabled(false);
+    portEdit.setEnabled(false);
+    startRouteGuideButton.setEnabled(false);
+    enableButtons();
+  }
+
+  public void exitRouteGuide(View view) {
+    channel.shutdown();
+    disableButtons();
+    hostEdit.setEnabled(true);
+    portEdit.setEnabled(true);
+    startRouteGuideButton.setEnabled(true);
+  }
+
+  public void getFeature(View view) {
+    setResultText("");
+    disableButtons();
+    new GrpcTask(new GetFeatureRunnable(), channel, this).execute();
+  }
+
+  public void listFeatures(View view) {
+    setResultText("");
+    disableButtons();
+    new GrpcTask(new ListFeaturesRunnable(), channel, this).execute();
+  }
+
+  public void recordRoute(View view) {
+    setResultText("");
+    disableButtons();
+    new GrpcTask(new RecordRouteRunnable(), channel, this).execute();
+  }
+
+  public void routeChat(View view) {
+    setResultText("");
+    disableButtons();
+    new GrpcTask(new RouteChatRunnable(), channel, this).execute();
+  }
+
+  private void setResultText(String text) {
+    resultText.setText(text);
+  }
+
+  private void disableButtons() {
+    getFeatureButton.setEnabled(false);
+    listFeaturesButton.setEnabled(false);
+    recordRouteButton.setEnabled(false);
+    routeChatButton.setEnabled(false);
+    exitRouteGuideButton.setEnabled(false);
+  }
+
+  private void enableButtons() {
+    exitRouteGuideButton.setEnabled(true);
+    getFeatureButton.setEnabled(true);
+    listFeaturesButton.setEnabled(true);
+    recordRouteButton.setEnabled(true);
+    routeChatButton.setEnabled(true);
+  }
+
+  private static class GrpcTask extends AsyncTask<Void, Void, String> {
+    private final GrpcRunnable grpcRunnable;
+    private final ManagedChannel channel;
+    private final WeakReference<RouteGuideActivity> activityReference;
+
+    GrpcTask(GrpcRunnable grpcRunnable, ManagedChannel channel, RouteGuideActivity activity) {
+      this.grpcRunnable = grpcRunnable;
+      this.channel = channel;
+      this.activityReference = new WeakReference<RouteGuideActivity>(activity);
+    }
+
+    @Override
+    protected String doInBackground(Void... nothing) {
+      try {
+        String logs =
+            grpcRunnable.run(
+                RouteGuideGrpc.newBlockingStub(channel), RouteGuideGrpc.newStub(channel));
+        return "Success!\n" + logs;
+      } catch (Exception e) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        e.printStackTrace(pw);
+        pw.flush();
+        return "Failed... :\n" + sw;
+      }
+    }
+
+    @Override
+    protected void onPostExecute(String result) {
+      RouteGuideActivity activity = activityReference.get();
+      if (activity == null) {
+        return;
+      }
+      activity.setResultText(result);
+      activity.enableButtons();
+    }
+  }
+
+  private interface GrpcRunnable {
+    /** Perform a grpcRunnable and return all the logs. */
+    String run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub) throws Exception;
+  }
+
+  private static class GetFeatureRunnable implements GrpcRunnable {
+    @Override
+    public String run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub)
+        throws Exception {
+      return getFeature(409146138, -746188906, blockingStub);
+    }
+
+    /** Blocking unary call example. Calls getFeature and prints the response. */
+    private String getFeature(int lat, int lon, RouteGuideBlockingStub blockingStub)
+        throws StatusRuntimeException {
+      StringBuffer logs = new StringBuffer();
+      appendLogs(logs, "*** GetFeature: lat={0} lon={1}", lat, lon);
+
+      Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build();
+
+      Feature feature;
+      feature = blockingStub.getFeature(request);
+      if (RouteGuideUtil.exists(feature)) {
+        appendLogs(
+            logs,
+            "Found feature called \"{0}\" at {1}, {2}",
+            feature.getName(),
+            RouteGuideUtil.getLatitude(feature.getLocation()),
+            RouteGuideUtil.getLongitude(feature.getLocation()));
+      } else {
+        appendLogs(
+            logs,
+            "Found no feature at {0}, {1}",
+            RouteGuideUtil.getLatitude(feature.getLocation()),
+            RouteGuideUtil.getLongitude(feature.getLocation()));
+      }
+      return logs.toString();
+    }
+  }
+
+  private static class ListFeaturesRunnable implements GrpcRunnable {
+    @Override
+    public String run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub)
+        throws Exception {
+      return listFeatures(400000000, -750000000, 420000000, -730000000, blockingStub);
+    }
+
+    /**
+     * Blocking server-streaming example. Calls listFeatures with a rectangle of interest. Prints
+     * each response feature as it arrives.
+     */
+    private String listFeatures(
+        int lowLat, int lowLon, int hiLat, int hiLon, RouteGuideBlockingStub blockingStub)
+        throws StatusRuntimeException {
+      StringBuffer logs = new StringBuffer("Result: ");
+      appendLogs(
+          logs,
+          "*** ListFeatures: lowLat={0} lowLon={1} hiLat={2} hiLon={3}",
+          lowLat,
+          lowLon,
+          hiLat,
+          hiLon);
+
+      Rectangle request =
+          Rectangle.newBuilder()
+              .setLo(Point.newBuilder().setLatitude(lowLat).setLongitude(lowLon).build())
+              .setHi(Point.newBuilder().setLatitude(hiLat).setLongitude(hiLon).build())
+              .build();
+      Iterator<Feature> features;
+      features = blockingStub.listFeatures(request);
+
+      while (features.hasNext()) {
+        Feature feature = features.next();
+        appendLogs(logs, feature.toString());
+      }
+      return logs.toString();
+    }
+  }
+
+  private static class RecordRouteRunnable implements GrpcRunnable {
+    private Throwable failed;
+
+    @Override
+    public String run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub)
+        throws Exception {
+      List<Point> points = new ArrayList<>();
+      points.add(Point.newBuilder().setLatitude(407838351).setLongitude(-746143763).build());
+      points.add(Point.newBuilder().setLatitude(408122808).setLongitude(-743999179).build());
+      points.add(Point.newBuilder().setLatitude(413628156).setLongitude(-749015468).build());
+      return recordRoute(points, 5, asyncStub);
+    }
+
+    /**
+     * Async client-streaming example. Sends {@code numPoints} randomly chosen points from {@code
+     * features} with a variable delay in between. Prints the statistics when they are sent from the
+     * server.
+     */
+    private String recordRoute(List<Point> points, int numPoints, RouteGuideStub asyncStub)
+        throws InterruptedException, RuntimeException {
+      final StringBuffer logs = new StringBuffer();
+      appendLogs(logs, "*** RecordRoute");
+
+      final CountDownLatch finishLatch = new CountDownLatch(1);
+      StreamObserver<RouteSummary> responseObserver =
+          new StreamObserver<RouteSummary>() {
+            @Override
+            public void onNext(RouteSummary summary) {
+              appendLogs(
+                  logs,
+                  "Finished trip with {0} points. Passed {1} features. "
+                      + "Travelled {2} meters. It took {3} seconds.",
+                  summary.getPointCount(),
+                  summary.getFeatureCount(),
+                  summary.getDistance(),
+                  summary.getElapsedTime());
+            }
+
+            @Override
+            public void onError(Throwable t) {
+              failed = t;
+              finishLatch.countDown();
+            }
+
+            @Override
+            public void onCompleted() {
+              appendLogs(logs, "Finished RecordRoute");
+              finishLatch.countDown();
+            }
+          };
+
+      StreamObserver<Point> requestObserver = asyncStub.recordRoute(responseObserver);
+      try {
+        // Send numPoints points randomly selected from the points list.
+        Random rand = new Random();
+        for (int i = 0; i < numPoints; ++i) {
+          int index = rand.nextInt(points.size());
+          Point point = points.get(index);
+          appendLogs(
+              logs,
+              "Visiting point {0}, {1}",
+              RouteGuideUtil.getLatitude(point),
+              RouteGuideUtil.getLongitude(point));
+          requestObserver.onNext(point);
+          // Sleep for a bit before sending the next one.
+          Thread.sleep(rand.nextInt(1000) + 500);
+          if (finishLatch.getCount() == 0) {
+            // RPC completed or errored before we finished sending.
+            // Sending further requests won't error, but they will just be thrown away.
+            break;
+          }
+        }
+      } catch (RuntimeException e) {
+        // Cancel RPC
+        requestObserver.onError(e);
+        throw e;
+      }
+      // Mark the end of requests
+      requestObserver.onCompleted();
+
+      // Receiving happens asynchronously
+      if (!finishLatch.await(1, TimeUnit.MINUTES)) {
+        throw new RuntimeException(
+            "Could not finish rpc within 1 minute, the server is likely down");
+      }
+
+      if (failed != null) {
+        throw new RuntimeException(failed);
+      }
+      return logs.toString();
+    }
+  }
+
+  private static class RouteChatRunnable implements GrpcRunnable {
+    private Throwable failed;
+
+    @Override
+    public String run(RouteGuideBlockingStub blockingStub, RouteGuideStub asyncStub)
+        throws Exception {
+      return routeChat(asyncStub);
+    }
+
+    /**
+     * Bi-directional example, which can only be asynchronous. Send some chat messages, and print
+     * any chat messages that are sent from the server.
+     */
+    private String routeChat(RouteGuideStub asyncStub)
+        throws InterruptedException, RuntimeException {
+      final StringBuffer logs = new StringBuffer();
+      appendLogs(logs, "*** RouteChat");
+      final CountDownLatch finishLatch = new CountDownLatch(1);
+      StreamObserver<RouteNote> requestObserver =
+          asyncStub.routeChat(
+              new StreamObserver<RouteNote>() {
+                @Override
+                public void onNext(RouteNote note) {
+                  appendLogs(
+                      logs,
+                      "Got message \"{0}\" at {1}, {2}",
+                      note.getMessage(),
+                      note.getLocation().getLatitude(),
+                      note.getLocation().getLongitude());
+                }
+
+                @Override
+                public void onError(Throwable t) {
+                  failed = t;
+                  finishLatch.countDown();
+                }
+
+                @Override
+                public void onCompleted() {
+                  appendLogs(logs, "Finished RouteChat");
+                  finishLatch.countDown();
+                }
+              });
+
+      try {
+        RouteNote[] requests = {
+          newNote("First message", 0, 0),
+          newNote("Second message", 0, 1),
+          newNote("Third message", 1, 0),
+          newNote("Fourth message", 1, 1)
+        };
+
+        for (RouteNote request : requests) {
+          appendLogs(
+              logs,
+              "Sending message \"{0}\" at {1}, {2}",
+              request.getMessage(),
+              request.getLocation().getLatitude(),
+              request.getLocation().getLongitude());
+          requestObserver.onNext(request);
+        }
+      } catch (RuntimeException e) {
+        // Cancel RPC
+        requestObserver.onError(e);
+        throw e;
+      }
+      // Mark the end of requests
+      requestObserver.onCompleted();
+
+      // Receiving happens asynchronously
+      if (!finishLatch.await(1, TimeUnit.MINUTES)) {
+        throw new RuntimeException(
+            "Could not finish rpc within 1 minute, the server is likely down");
+      }
+
+      if (failed != null) {
+        throw new RuntimeException(failed);
+      }
+
+      return logs.toString();
+    }
+  }
+
+  private static void appendLogs(StringBuffer logs, String msg, Object... params) {
+    if (params.length > 0) {
+      logs.append(MessageFormat.format(msg, params));
+    } else {
+      logs.append(msg);
+    }
+    logs.append("\n");
+  }
+
+  private static RouteNote newNote(String message, int lat, int lon) {
+    return RouteNote.newBuilder()
+        .setMessage(message)
+        .setLocation(Point.newBuilder().setLatitude(lat).setLongitude(lon).build())
+        .build();
+  }
+}
diff --git a/examples/android/routeguide/app/src/main/java/io/grpc/routeguideexample/RouteGuideUtil.java b/examples/android/routeguide/app/src/main/java/io/grpc/routeguideexample/RouteGuideUtil.java
new file mode 100644
index 0000000..56af278
--- /dev/null
+++ b/examples/android/routeguide/app/src/main/java/io/grpc/routeguideexample/RouteGuideUtil.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.routeguideexample;
+
+
+/** Common utilities for the RouteGuide demo. */
+public class RouteGuideUtil {
+  private static final double COORD_FACTOR = 1e7;
+
+  /** Gets the latitude for the given point. */
+  public static double getLatitude(Point location) {
+    return location.getLatitude() / COORD_FACTOR;
+  }
+
+  /** Gets the longitude for the given point. */
+  public static double getLongitude(Point location) {
+    return location.getLongitude() / COORD_FACTOR;
+  }
+
+  /** Indicates whether the given feature exists (i.e. has a valid name). */
+  public static boolean exists(Feature feature) {
+    return feature != null && !feature.getName().isEmpty();
+  }
+}
diff --git a/examples/android/routeguide/app/src/main/proto/route_guide.proto b/examples/android/routeguide/app/src/main/proto/route_guide.proto
new file mode 100644
index 0000000..3991c96
--- /dev/null
+++ b/examples/android/routeguide/app/src/main/proto/route_guide.proto
@@ -0,0 +1,115 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.routeguideexample";
+option java_outer_classname = "RouteGuideProto";
+option objc_class_prefix = "RTG";
+
+package routeguide;
+
+// Interface exported by the server.
+service RouteGuide {
+  // A simple RPC.
+  //
+  // Obtains the feature at a given position.
+  //
+  // A feature with an empty name is returned if there's no feature at the given
+  // position.
+  rpc GetFeature(Point) returns (Feature) {}
+
+  // A server-to-client streaming RPC.
+  //
+  // Obtains the Features available within the given Rectangle.  Results are
+  // streamed rather than returned at once (e.g. in a response message with a
+  // repeated field), as the rectangle may cover a large area and contain a
+  // huge number of features.
+  rpc ListFeatures(Rectangle) returns (stream Feature) {}
+
+  // A client-to-server streaming RPC.
+  //
+  // Accepts a stream of Points on a route being traversed, returning a
+  // RouteSummary when traversal is completed.
+  rpc RecordRoute(stream Point) returns (RouteSummary) {}
+
+  // A Bidirectional streaming RPC.
+  //
+  // Accepts a stream of RouteNotes sent while a route is being traversed,
+  // while receiving other RouteNotes (e.g. from other users).
+  rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
+}
+
+// Points are represented as latitude-longitude pairs in the E7 representation
+// (degrees multiplied by 10**7 and rounded to the nearest integer).
+// Latitudes should be in the range +/- 90 degrees and longitude should be in
+// the range +/- 180 degrees (inclusive).
+message Point {
+  int32 latitude = 1;
+  int32 longitude = 2;
+}
+
+// A latitude-longitude rectangle, represented as two diagonally opposite
+// points "lo" and "hi".
+message Rectangle {
+  // One corner of the rectangle.
+  Point lo = 1;
+
+  // The other corner of the rectangle.
+  Point hi = 2;
+}
+
+// A feature names something at a given point.
+//
+// If a feature could not be named, the name is empty.
+message Feature {
+  // The name of the feature.
+  string name = 1;
+
+  // The point where the feature is detected.
+  Point location = 2;
+}
+
+// Not used in the RPC.  Instead, this is here for the form serialized to disk.
+message FeatureDatabase {
+  repeated Feature feature = 1;
+}
+
+// A RouteNote is a message sent while at a given point.
+message RouteNote {
+  // The location from which the message is sent.
+  Point location = 1;
+
+  // The message to be sent.
+  string message = 2;
+}
+
+// A RouteSummary is received in response to a RecordRoute rpc.
+//
+// It contains the number of individual points received, the number of
+// detected features, and the total distance covered as the cumulative sum of
+// the distance between each point.
+message RouteSummary {
+  // The number of points received.
+  int32 point_count = 1;
+
+  // The number of known features passed while traversing the route.
+  int32 feature_count = 2;
+
+  // The distance covered in metres.
+  int32 distance = 3;
+
+  // The duration of the traversal in seconds.
+  int32 elapsed_time = 4;
+}
diff --git a/examples/android/routeguide/app/src/main/res/layout/activity_routeguide.xml b/examples/android/routeguide/app/src/main/res/layout/activity_routeguide.xml
new file mode 100644
index 0000000..b76a7a3
--- /dev/null
+++ b/examples/android/routeguide/app/src/main/res/layout/activity_routeguide.xml
@@ -0,0 +1,81 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              tools:context=".RouteGuideActivity"
+              android:orientation="vertical" >
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+        <EditText
+                android:id="@+id/host_edit_text"
+                android:layout_weight="2"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:hint="Enter Host" />
+        <EditText
+                android:id="@+id/port_edit_text"
+                android:layout_weight="1"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:inputType="numberDecimal"
+                android:hint="Enter Port" />
+    </LinearLayout>
+
+    <LinearLayout style="?android:buttonBarStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+        <Button style="?android:buttonBarButtonStyle"
+            android:id="@+id/start_route_guide_button"
+            android:layout_weight="1"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:onClick="startRouteGuide"
+            android:text="Start Route Guide" />
+        <Button style="?android:buttonBarButtonStyle"
+            android:id="@+id/exit_route_guide_button"
+            android:layout_weight="1"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:onClick="exitRouteGuide"
+            android:text="Exit Route Guide" />
+    </LinearLayout>
+
+    <Button
+        android:id="@+id/get_feature_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:onClick="getFeature"
+        android:text="Get Feature" />
+
+    <Button
+        android:id="@+id/list_features_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:onClick="listFeatures"
+        android:text="List Features" />
+
+    <Button
+        android:id="@+id/record_route_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:onClick="recordRoute"
+        android:text="Record Route" />
+
+    <Button
+        android:id="@+id/route_chat_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:onClick="routeChat"
+        android:text="Route Chat" />
+
+    <TextView
+            android:id="@+id/result_text"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scrollbars = "vertical"
+            android:textSize="16sp" />
+
+</LinearLayout>
diff --git a/examples/android/routeguide/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/android/routeguide/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/examples/android/routeguide/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/routeguide/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/android/routeguide/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/examples/android/routeguide/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/routeguide/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/android/routeguide/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/examples/android/routeguide/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/routeguide/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/android/routeguide/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/examples/android/routeguide/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/routeguide/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/examples/android/routeguide/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..aee44e1
--- /dev/null
+++ b/examples/android/routeguide/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/android/routeguide/app/src/main/res/values/colors.xml b/examples/android/routeguide/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..1c10bfe
--- /dev/null
+++ b/examples/android/routeguide/app/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#3F51B5</color>
+    <color name="colorPrimaryDark">#303F9F</color>
+    <color name="colorAccent">#FF4081</color>
+</resources>
+
diff --git a/examples/android/routeguide/app/src/main/res/values/strings.xml b/examples/android/routeguide/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..7b287fc
--- /dev/null
+++ b/examples/android/routeguide/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">RouteGuideExample</string>
+</resources>
diff --git a/examples/android/routeguide/app/src/main/res/values/styles.xml b/examples/android/routeguide/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..7312f6a
--- /dev/null
+++ b/examples/android/routeguide/app/src/main/res/values/styles.xml
@@ -0,0 +1,12 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+</resources>
+
diff --git a/examples/android/routeguide/build.gradle b/examples/android/routeguide/build.gradle
new file mode 100644
index 0000000..5ead69a
--- /dev/null
+++ b/examples/android/routeguide/build.gradle
@@ -0,0 +1,25 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.0.1'
+        classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.5"
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+        mavenLocal()
+    }
+}
+
+task clean(type: Delete) { delete rootProject.buildDir }
diff --git a/examples/android/routeguide/gradle/wrapper/gradle-wrapper.jar b/examples/android/routeguide/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..0d4a951
--- /dev/null
+++ b/examples/android/routeguide/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/examples/android/routeguide/gradle/wrapper/gradle-wrapper.properties b/examples/android/routeguide/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..a95009c
--- /dev/null
+++ b/examples/android/routeguide/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/examples/android/routeguide/gradlew b/examples/android/routeguide/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/examples/android/routeguide/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/examples/android/routeguide/gradlew.bat b/examples/android/routeguide/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/examples/android/routeguide/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/examples/android/routeguide/settings.gradle b/examples/android/routeguide/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/examples/android/routeguide/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/examples/build.gradle b/examples/build.gradle
new file mode 100644
index 0000000..1649e7f
--- /dev/null
+++ b/examples/build.gradle
@@ -0,0 +1,151 @@
+apply plugin: 'java'
+apply plugin: 'com.google.protobuf'
+
+buildscript {
+    repositories {
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    }
+    dependencies { // ASSUMES GRADLE 2.12 OR HIGHER. Use plugin version 0.7.5 with earlier
+        // gradle versions
+        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.5' }
+}
+
+repositories {
+    maven { // The google mirror is less flaky than mavenCentral()
+        url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    mavenLocal()
+}
+
+// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you
+// are looking at a tagged version of the example and not "master"!
+
+// Feel free to delete the comment at the next line. It is just for safely
+// updating the version in our release process.
+def grpcVersion = '1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+def nettyTcNativeVersion = '2.0.7.Final'
+def protobufVersion = '3.5.1'
+def protocVersion = '3.5.1-1'
+
+dependencies {
+    compile "com.google.api.grpc:proto-google-common-protos:1.0.0"
+    compile "io.grpc:grpc-alts:${grpcVersion}"
+    compile "io.grpc:grpc-netty-shaded:${grpcVersion}"
+    compile "io.grpc:grpc-protobuf:${grpcVersion}"
+    compile "io.grpc:grpc-stub:${grpcVersion}"
+    compileOnly "javax.annotation:javax.annotation-api:1.2"
+
+    // Used in HelloWorldServerTls
+    compile "io.grpc:grpc-netty:${grpcVersion}"
+    compile "io.netty:netty-tcnative-boringssl-static:${nettyTcNativeVersion}"
+
+    compile "com.google.protobuf:protobuf-java-util:${protobufVersion}"
+
+    testCompile "io.grpc:grpc-testing:${grpcVersion}"
+    testCompile "junit:junit:4.12"
+    testCompile "org.mockito:mockito-core:1.9.5"
+}
+
+protobuf {
+    protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
+    plugins {
+        grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
+    }
+    generateProtoTasks {
+        all()*.plugins { grpc {} }
+    }
+}
+
+// Inform IDEs like IntelliJ IDEA, Eclipse or NetBeans about the generated code.
+sourceSets {
+    main {
+        java {
+            srcDirs 'build/generated/source/proto/main/grpc'
+            srcDirs 'build/generated/source/proto/main/java'
+        }
+    }
+}
+
+// Generate IntelliJ IDEA's .idea & .iml project files
+apply plugin: 'idea'
+
+// Provide convenience executables for trying out the examples.
+apply plugin: 'application'
+
+startScripts.enabled = false
+
+task routeGuideServer(type: CreateStartScripts) {
+    mainClassName = 'io.grpc.examples.routeguide.RouteGuideServer'
+    applicationName = 'route-guide-server'
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+task routeGuideClient(type: CreateStartScripts) {
+    mainClassName = 'io.grpc.examples.routeguide.RouteGuideClient'
+    applicationName = 'route-guide-client'
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+task helloWorldServer(type: CreateStartScripts) {
+    mainClassName = 'io.grpc.examples.helloworld.HelloWorldServer'
+    applicationName = 'hello-world-server'
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+task helloWorldClient(type: CreateStartScripts) {
+    mainClassName = 'io.grpc.examples.helloworld.HelloWorldClient'
+    applicationName = 'hello-world-client'
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+task helloWorldAltsServer(type: CreateStartScripts) {
+    mainClassName = 'io.grpc.examples.alts.HelloWorldAltsServer'
+    applicationName = 'hello-world-alts-server'
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+task helloWorldAltsClient(type: CreateStartScripts) {
+    mainClassName = 'io.grpc.examples.alts.HelloWorldAltsClient'
+    applicationName = 'hello-world-alts-client'
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+task helloWorldTlsServer(type: CreateStartScripts) {
+    mainClassName = 'io.grpc.examples.helloworldtls.HelloWorldServerTls'
+    applicationName = 'hello-world-tls-server'
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+task helloWorldTlsClient(type: CreateStartScripts) {
+    mainClassName = 'io.grpc.examples.helloworldtls.HelloWorldClientTls'
+    applicationName = 'hello-world-tls-client'
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+task compressingHelloWorldClient(type: CreateStartScripts) {
+    mainClassName = 'io.grpc.examples.experimental.CompressingHelloWorldClient'
+    applicationName = 'compressing-hello-world-client'
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+applicationDistribution.into('bin') {
+    from(routeGuideServer)
+    from(routeGuideClient)
+    from(helloWorldServer)
+    from(helloWorldClient)
+    from(helloWorldAltsServer)
+    from(helloWorldAltsClient)
+    from(helloWorldTlsServer)
+    from(helloWorldTlsClient)
+    from(compressingHelloWorldClient)
+    fileMode = 0755
+}
diff --git a/examples/example-kotlin/README.md b/examples/example-kotlin/README.md
new file mode 100644
index 0000000..b7dbcb3
--- /dev/null
+++ b/examples/example-kotlin/README.md
@@ -0,0 +1,59 @@
+grpc Kotlin example
+==============================================
+
+The examples require grpc-java to already be built. You are strongly encouraged
+to check out a git release tag, since there will already be a build of grpc
+available. Otherwise you must follow COMPILING.md.
+
+You may want to read through the
+[Quick Start Guide](https://grpc.io/docs/quickstart/java.html)
+before trying out the examples.
+
+To build the examples, run in this directory:
+
+```
+$ ./gradlew installDist
+```
+
+This creates the scripts `hello-world-server`, `hello-world-client`,
+`route-guide-server`, and `route-guide-client` in the
+`build/install/examples/bin/` directory that run the examples. Each
+example requires the server to be running before starting the client.
+
+For example, to try the hello world example first run:
+
+```
+$ ./build/install/examples/bin/hello-world-server
+```
+
+And in a different terminal window run:
+
+```
+$ ./build/install/examples/bin/hello-world-client
+```
+
+That's it!
+
+Please refer to gRPC Java's [README](../README.md) and
+[tutorial](https://grpc.io/docs/tutorials/basic/java.html) for more
+information.
+
+Unit test examples
+==============================================
+
+Examples for unit testing gRPC clients and servers are located in [./src/test](./src/test).
+
+In general, we DO NOT allow overriding the client stub.
+We encourage users to leverage `InProcessTransport` as demonstrated in the examples to
+write unit tests. `InProcessTransport` is light-weight and runs the server
+and client in the same process without any socket/TCP connection.
+
+For testing a gRPC client, create the client with a real stub
+using an InProcessChannelBuilder.java and test it against an InProcessServer.java
+with a mock/fake service implementation.
+
+For testing a gRPC server, create the server as an InProcessServer,
+and test it against a real client stub with an InProcessChannel.
+
+The gRPC-java library also provides a JUnit rule, GrpcCleanupRule.java, to do the graceful shutdown
+boilerplate for you.
diff --git a/examples/example-kotlin/android/helloworld/app/build.gradle b/examples/example-kotlin/android/helloworld/app/build.gradle
new file mode 100644
index 0000000..64c8dc2
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/app/build.gradle
@@ -0,0 +1,80 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'com.google.protobuf'
+
+android {
+    compileSdkVersion 27
+
+    defaultConfig {
+        applicationId "io.grpc.helloworldexample"
+        // API level 14+ is required for TLS since Google Play Services v10.2
+        minSdkVersion 14
+        targetSdkVersion 27
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        debug { minifyEnabled false }
+        release {
+            minifyEnabled true
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+    lintOptions {
+        disable 'GoogleAppIndexingWarning', 'HardcodedText', 'InvalidPackage'
+        textReport true
+        textOutput "stdout"
+    }
+    // Android Studio 3.1 does not automatically pick up '<src_set>/kotlin' as source input
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+        test.java.srcDirs += 'src/test/kotlin'
+        androidTest.java.srcDirs += 'src/androidTest/kotlin'
+    }
+
+    lintOptions {
+        // Do not complain about outdated deps, so that this can javax.annotation-api can be same
+        // as other projects in this repo. Your project is not required to do this, and can
+        // upgrade the dep.
+        disable 'GradleDependency'
+        // The Android linter does not correctly detect resources used in Kotlin.
+        // See:
+        //   - https://youtrack.jetbrains.com/issue/KT-7729
+        //   - https://youtrack.jetbrains.com/issue/KT-12499
+        disable 'UnusedResources'
+        textReport true
+        textOutput "stdout"
+    }
+}
+
+protobuf {
+    protoc { artifact = 'com.google.protobuf:protoc:3.5.1-1' }
+    plugins {
+        javalite { artifact = "com.google.protobuf:protoc-gen-javalite:3.0.0" }
+        grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+        }
+    }
+    generateProtoTasks {
+        all().each { task ->
+            task.plugins {
+                javalite {}
+                grpc { // Options added to --grpc_out
+                    option 'lite' }
+            }
+        }
+    }
+}
+
+dependencies {
+    compile 'com.android.support:appcompat-v7:27.0.2'
+    compile 'javax.annotation:javax.annotation-api:1.2'
+    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+
+    // You need to build grpc-java to obtain these libraries below.
+    compile 'io.grpc:grpc-okhttp:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-protobuf-lite:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+    compile 'io.grpc:grpc-stub:1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+}
+
+repositories { mavenCentral() }
diff --git a/examples/example-kotlin/android/helloworld/app/proguard-rules.pro b/examples/example-kotlin/android/helloworld/app/proguard-rules.pro
new file mode 100644
index 0000000..1507a52
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/app/proguard-rules.pro
@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in $ANDROID_HOME/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+-dontwarn com.google.common.**
+# Ignores: can't find referenced class javax.lang.model.element.Modifier
+-dontwarn com.google.errorprone.annotations.**
+-dontwarn javax.naming.**
+-dontwarn okio.**
+-dontwarn sun.misc.Unsafe
diff --git a/examples/example-kotlin/android/helloworld/app/src/main/AndroidManifest.xml b/examples/example-kotlin/android/helloworld/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..eee4057
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/app/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.grpc.helloworldexample" >
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application
+        android:allowBackup="false"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/Base.V7.Theme.AppCompat.Light" >
+        <activity
+            android:name=".HelloworldActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/examples/example-kotlin/android/helloworld/app/src/main/kotlin/io/grpc/helloworldexample/HelloworldActivity.kt b/examples/example-kotlin/android/helloworld/app/src/main/kotlin/io/grpc/helloworldexample/HelloworldActivity.kt
new file mode 100644
index 0000000..66c96b9
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/app/src/main/kotlin/io/grpc/helloworldexample/HelloworldActivity.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.helloworldexample
+
+import android.app.Activity
+import android.content.Context
+import android.os.AsyncTask
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+import android.text.TextUtils
+import android.text.method.ScrollingMovementMethod
+import android.view.View
+import android.view.inputmethod.InputMethodManager
+import android.widget.Button
+import android.widget.TextView
+import io.grpc.ManagedChannel
+import io.grpc.ManagedChannelBuilder
+import io.grpc.examples.helloworld.GreeterGrpc
+import io.grpc.examples.helloworld.HelloRequest
+import java.io.PrintWriter
+import java.io.StringWriter
+import java.lang.ref.WeakReference
+import java.util.concurrent.TimeUnit
+import kotlinx.android.synthetic.main.activity_helloworld.*
+
+class HelloworldActivity : AppCompatActivity(), View.OnClickListener {
+  override fun onCreate(savedInstanceState: Bundle?) {
+    super.onCreate(savedInstanceState)
+    setContentView(R.layout.activity_helloworld)
+    grpc_response_text!!.movementMethod = ScrollingMovementMethod()
+    send_button!!.setOnClickListener(this)
+  }
+
+  override fun onClick(view: View) {
+    (getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
+        .hideSoftInputFromWindow(host_edit_text!!.windowToken, 0)
+    send_button!!.isEnabled = false
+    grpc_response_text!!.text = ""
+    GrpcTask(this)
+        .execute(
+            host_edit_text!!.text.toString(),
+            message_edit_text!!.text.toString(),
+            port_edit_text!!.text.toString())
+  }
+
+  private class GrpcTask constructor(activity: Activity) : AsyncTask<String, Void, String>() {
+    private val activityReference: WeakReference<Activity> = WeakReference(activity)
+    private var channel: ManagedChannel? = null
+
+    override fun doInBackground(vararg params: String): String {
+      val host = params[0]
+      val message = params[1]
+      val portStr = params[2]
+      val port = if (TextUtils.isEmpty(portStr)) 0 else Integer.valueOf(portStr)
+      return try {
+        channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build()
+        val stub = GreeterGrpc.newBlockingStub(channel)
+        val request = HelloRequest.newBuilder().setName(message).build()
+        val reply = stub.sayHello(request)
+        reply.message
+      } catch (e: Exception) {
+        val sw = StringWriter()
+        val pw = PrintWriter(sw)
+        e.printStackTrace(pw)
+        pw.flush()
+
+        "Failed... : %s".format(sw)
+      }
+    }
+
+    override fun onPostExecute(result: String) {
+      try {
+        channel?.shutdown()?.awaitTermination(1, TimeUnit.SECONDS)
+      } catch (e: InterruptedException) {
+        Thread.currentThread().interrupt()
+      }
+
+      val activity = activityReference.get() ?: return
+      val resultText: TextView = activity.findViewById(R.id.grpc_response_text)
+      val sendButton: Button = activity.findViewById(R.id.send_button)
+
+      resultText.text = result
+      sendButton.isEnabled = true
+    }
+  }
+}
diff --git a/examples/example-kotlin/android/helloworld/app/src/main/proto/helloworld.proto b/examples/example-kotlin/android/helloworld/app/src/main/proto/helloworld.proto
new file mode 100644
index 0000000..c60d941
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/app/src/main/proto/helloworld.proto
@@ -0,0 +1,37 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.examples.helloworld";
+option java_outer_classname = "HelloWorldProto";
+option objc_class_prefix = "HLW";
+
+package helloworld;
+
+// The greeting service definition.
+service Greeter {
+  // Sends a greeting
+  rpc SayHello (HelloRequest) returns (HelloReply) {}
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+  string name = 1;
+}
+
+// The response message containing the greetings
+message HelloReply {
+  string message = 1;
+}
diff --git a/examples/example-kotlin/android/helloworld/app/src/main/res/layout/activity_helloworld.xml b/examples/example-kotlin/android/helloworld/app/src/main/res/layout/activity_helloworld.xml
new file mode 100644
index 0000000..e9f41f4
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/app/src/main/res/layout/activity_helloworld.xml
@@ -0,0 +1,54 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              tools:context=".MainActivity"
+              android:orientation="vertical" >
+
+    <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+        <EditText
+                android:id="@+id/host_edit_text"
+                android:layout_weight="2"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:hint="Enter Host" />
+        <EditText
+                android:id="@+id/port_edit_text"
+                android:layout_weight="1"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:inputType="numberDecimal"
+                android:hint="Enter Port" />
+    </LinearLayout>
+
+
+    <EditText
+            android:id="@+id/message_edit_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:hint="Enter message to send" />
+
+    <Button
+            android:id="@+id/send_button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="Send Grpc Request" />
+
+    <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="12dp"
+            android:paddingBottom="12dp"
+            android:textSize="16sp"
+            android:text="Response:" />
+
+    <TextView
+            android:id="@+id/grpc_response_text"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scrollbars = "vertical"
+            android:textSize="16sp" />
+
+</LinearLayout>
diff --git a/examples/example-kotlin/android/helloworld/app/src/main/res/mipmap-hdpi/ic_launcher.png b/examples/example-kotlin/android/helloworld/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..cde69bc
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/examples/example-kotlin/android/helloworld/app/src/main/res/mipmap-mdpi/ic_launcher.png b/examples/example-kotlin/android/helloworld/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..c133a0c
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/examples/example-kotlin/android/helloworld/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/examples/example-kotlin/android/helloworld/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..bfa42f0
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/example-kotlin/android/helloworld/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/examples/example-kotlin/android/helloworld/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..324e72c
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/examples/example-kotlin/android/helloworld/app/src/main/res/values/strings.xml b/examples/example-kotlin/android/helloworld/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..64cb312
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">GrpcHelloworldExample</string>
+</resources>
diff --git a/examples/example-kotlin/android/helloworld/build.gradle b/examples/example-kotlin/android/helloworld/build.gradle
new file mode 100644
index 0000000..ef4b2a1
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    ext.kotlin_version = '1.2.21'
+
+    repositories {
+        google()
+        jcenter()
+        mavenLocal()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.0.1'
+        classpath "com.google.protobuf:protobuf-gradle-plugin:0.8.5"
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+        mavenLocal()
+    }
+}
diff --git a/examples/example-kotlin/android/helloworld/gradle/wrapper/gradle-wrapper.jar b/examples/example-kotlin/android/helloworld/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..0d4a951
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/examples/example-kotlin/android/helloworld/gradle/wrapper/gradle-wrapper.properties b/examples/example-kotlin/android/helloworld/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..a95009c
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/examples/example-kotlin/android/helloworld/gradlew b/examples/example-kotlin/android/helloworld/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/examples/example-kotlin/android/helloworld/gradlew.bat b/examples/example-kotlin/android/helloworld/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/examples/example-kotlin/android/helloworld/settings.gradle b/examples/example-kotlin/android/helloworld/settings.gradle
new file mode 100644
index 0000000..e7b4def
--- /dev/null
+++ b/examples/example-kotlin/android/helloworld/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/examples/example-kotlin/build.gradle b/examples/example-kotlin/build.gradle
new file mode 100644
index 0000000..4ebe65b
--- /dev/null
+++ b/examples/example-kotlin/build.gradle
@@ -0,0 +1,82 @@
+apply plugin: 'kotlin'
+apply plugin: 'com.google.protobuf'
+
+// Generate IntelliJ IDEA's .idea & .iml project files
+// Starting with 0.8.4 of protobuf-gradle-plugin, *.proto and the gen output files are added
+// to IntelliJ as sources. It is no longer necessary to add them manually to the idea {} block
+// to jump to definitions from Java and Kotlin files.
+// For best results, install the Protobuf and Kotlin plugins for IntelliJ.
+apply plugin: 'idea'
+
+// Provide convenience executables for trying out the examples.
+apply plugin: 'application'
+
+
+buildscript {
+    ext.kotlin_version = '1.2.21'
+
+    repositories {
+        mavenCentral()
+        mavenLocal()
+    }
+    dependencies {
+        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.5'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+repositories {
+    mavenCentral()
+    mavenLocal()
+}
+
+// IMPORTANT: You probably want the non-SNAPSHOT version of gRPC. Make sure you
+// are looking at a tagged version of the example and not "master"!
+
+// Feel free to delete the comment at the next line. It is just for safely
+// updating the version in our release process.
+def grpcVersion = '1.16.0-SNAPSHOT' // CURRENT_GRPC_VERSION
+
+dependencies {
+    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
+    compile "com.google.api.grpc:proto-google-common-protos:1.0.0"
+    compile "io.grpc:grpc-netty-shaded:${grpcVersion}"
+    compile "io.grpc:grpc-protobuf:${grpcVersion}"
+    compile "io.grpc:grpc-stub:${grpcVersion}"
+
+    testCompile "io.grpc:grpc-testing:${grpcVersion}" // gRCP testing utilities
+    testCompile "junit:junit:4.12"
+    testCompile "org.mockito:mockito-core:1.9.5"
+}
+
+protobuf {
+    protoc { artifact = 'com.google.protobuf:protoc:3.5.1-1' }
+    plugins {
+        grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
+    }
+    generateProtoTasks {
+        all()*.plugins { grpc {} }
+    }
+}
+
+startScripts.enabled = false
+
+task helloWorldServer(type: CreateStartScripts) {
+    mainClassName = 'io.grpc.examples.helloworld.HelloWorldServer'
+    applicationName = 'hello-world-server'
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+task helloWorldClient(type: CreateStartScripts) {
+    mainClassName = 'io.grpc.examples.helloworld.HelloWorldClient'
+    applicationName = 'hello-world-client'
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + project.configurations.runtime
+}
+
+applicationDistribution.into('bin') {
+    from(helloWorldServer)
+    from(helloWorldClient)
+    fileMode = 0755
+}
diff --git a/examples/example-kotlin/gradle/wrapper/gradle-wrapper.jar b/examples/example-kotlin/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..0d4a951
--- /dev/null
+++ b/examples/example-kotlin/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/examples/example-kotlin/gradle/wrapper/gradle-wrapper.properties b/examples/example-kotlin/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..a95009c
--- /dev/null
+++ b/examples/example-kotlin/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/examples/example-kotlin/gradlew b/examples/example-kotlin/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/examples/example-kotlin/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/examples/example-kotlin/gradlew.bat b/examples/example-kotlin/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/examples/example-kotlin/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/examples/example-kotlin/settings.gradle b/examples/example-kotlin/settings.gradle
new file mode 100644
index 0000000..9512a19
--- /dev/null
+++ b/examples/example-kotlin/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'examples'
diff --git a/examples/example-kotlin/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldClient.kt b/examples/example-kotlin/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldClient.kt
new file mode 100644
index 0000000..783aca6
--- /dev/null
+++ b/examples/example-kotlin/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldClient.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.helloworld
+
+import io.grpc.ManagedChannel
+import io.grpc.ManagedChannelBuilder
+import io.grpc.StatusRuntimeException
+import java.util.concurrent.TimeUnit
+import java.util.logging.Level
+import java.util.logging.Logger
+
+/**
+ * A simple client that requests a greeting from the [HelloWorldServer].
+ */
+class HelloWorldClient
+/** Construct client for accessing RouteGuide server using the existing channel.  */
+internal constructor(private val channel: ManagedChannel) {
+    private val blockingStub: GreeterGrpc.GreeterBlockingStub
+            = GreeterGrpc.newBlockingStub(channel)
+
+    /** Construct client connecting to HelloWorld server at `host:port`.  */
+    constructor(host: String, port: Int) : this(ManagedChannelBuilder.forAddress(host, port)
+            // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
+            // needing certificates.
+            .usePlaintext()
+            .build()) {
+    }
+
+
+    @Throws(InterruptedException::class)
+    fun shutdown() {
+        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS)
+    }
+
+    /** Say hello to server.  */
+    fun greet(name: String) {
+        logger.log(Level.INFO, "Will try to greet {0}...", name)
+        val request = HelloRequest.newBuilder().setName(name).build()
+        val response: HelloReply =  try {
+            blockingStub.sayHello(request)
+        } catch (e: StatusRuntimeException) {
+            logger.log(Level.WARNING, "RPC failed: {0}", e.status)
+            return
+        }
+
+        logger.info("Greeting: ${response.message}")
+    }
+
+    companion object {
+        private val logger = Logger.getLogger(HelloWorldClient::class.java.name)
+
+        /**
+         * Greet server. If provided, the first element of `args` is the name to use in the
+         * greeting.
+         */
+        @Throws(Exception::class)
+        @JvmStatic
+        fun main(args: Array<String>) {
+            val client = HelloWorldClient("localhost", 50051)
+            try {
+                /* Access a service running on the local machine on port 50051 */
+                val user = if (args.size > 0) "world" else "world"
+                client.greet(user)
+            } finally {
+                client.shutdown()
+            }
+        }
+    }
+}
diff --git a/examples/example-kotlin/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldServer.kt b/examples/example-kotlin/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldServer.kt
new file mode 100644
index 0000000..6e390d2
--- /dev/null
+++ b/examples/example-kotlin/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldServer.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.helloworld
+
+import io.grpc.Server
+import io.grpc.ServerBuilder
+import io.grpc.stub.StreamObserver
+import java.io.IOException
+import java.util.logging.Level
+import java.util.logging.Logger
+
+/**
+ * Server that manages startup/shutdown of a `Greeter` server.
+ *
+ * Note: this file was automatically converted from Java
+ */
+class HelloWorldServer {
+
+    private var server: Server? = null
+
+    @Throws(IOException::class)
+    private fun start() {
+        /* The port on which the server should run */
+        val port = 50051
+        server = ServerBuilder.forPort(port)
+                .addService(GreeterImpl())
+                .build()
+                .start()
+        logger.log(Level.INFO, "Server started, listening on {0}", port)
+        Runtime.getRuntime().addShutdownHook(object : Thread() {
+            override fun run() {
+                // Use stderr here since the logger may have been reset by its JVM shutdown hook.
+                System.err.println("*** shutting down gRPC server since JVM is shutting down")
+                this@HelloWorldServer.stop()
+                System.err.println("*** server shut down")
+            }
+        })
+    }
+
+    private fun stop() {
+        server?.shutdown()
+    }
+
+    /**
+     * Await termination on the main thread since the grpc library uses daemon threads.
+     */
+    @Throws(InterruptedException::class)
+    private fun blockUntilShutdown() {
+        server?.awaitTermination()
+    }
+
+    internal class GreeterImpl : GreeterGrpc.GreeterImplBase() {
+
+        override fun sayHello(req: HelloRequest, responseObserver: StreamObserver<HelloReply>) {
+            val reply = HelloReply.newBuilder().setMessage("Hello ${req.name}").build()
+            responseObserver.onNext(reply)
+            responseObserver.onCompleted()
+        }
+    }
+
+    companion object {
+        private val logger = Logger.getLogger(HelloWorldServer::class.java.name)
+
+        /**
+         * Main launches the server from the command line.
+         */
+        @Throws(IOException::class, InterruptedException::class)
+        @JvmStatic
+        fun main(args: Array<String>) {
+            val server = HelloWorldServer()
+            server.start()
+            server.blockUntilShutdown()
+        }
+    }
+}
diff --git a/examples/example-kotlin/src/main/proto/helloworld.proto b/examples/example-kotlin/src/main/proto/helloworld.proto
new file mode 100644
index 0000000..c60d941
--- /dev/null
+++ b/examples/example-kotlin/src/main/proto/helloworld.proto
@@ -0,0 +1,37 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.examples.helloworld";
+option java_outer_classname = "HelloWorldProto";
+option objc_class_prefix = "HLW";
+
+package helloworld;
+
+// The greeting service definition.
+service Greeter {
+  // Sends a greeting
+  rpc SayHello (HelloRequest) returns (HelloReply) {}
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+  string name = 1;
+}
+
+// The response message containing the greetings
+message HelloReply {
+  string message = 1;
+}
diff --git a/examples/example-kotlin/src/test/kotlin/io/grpc/examples/helloworld/HelloWorldClientTest.kt b/examples/example-kotlin/src/test/kotlin/io/grpc/examples/helloworld/HelloWorldClientTest.kt
new file mode 100644
index 0000000..c03cd81
--- /dev/null
+++ b/examples/example-kotlin/src/test/kotlin/io/grpc/examples/helloworld/HelloWorldClientTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.helloworld
+
+import org.junit.Assert.assertEquals
+import org.mockito.AdditionalAnswers.delegatesTo
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+
+
+import io.grpc.inprocess.InProcessChannelBuilder
+import io.grpc.inprocess.InProcessServerBuilder
+import io.grpc.stub.StreamObserver
+import io.grpc.testing.GrpcCleanupRule
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Matchers
+
+/**
+ * Unit tests for [HelloWorldClient].
+ * For demonstrating how to write gRPC unit test only.
+ * Not intended to provide a high code coverage or to test every major usecase.
+ *
+ *
+ * For more unit test examples see [io.grpc.examples.routeguide.RouteGuideClientTest] and
+ * [io.grpc.examples.routeguide.RouteGuideServerTest].
+ */
+@RunWith(JUnit4::class)
+class HelloWorldClientTest {
+    /**
+     * This rule manages automatic graceful shutdown for the registered servers and channels at the
+     * end of test.
+     */
+    @get:Rule
+    val grpcCleanup = GrpcCleanupRule()
+
+    private val serviceImpl = mock(GreeterGrpc.GreeterImplBase::class.java, delegatesTo<Any>(object : GreeterGrpc.GreeterImplBase() {
+
+    }))
+    private var client: HelloWorldClient? = null
+
+    @Before
+    @Throws(Exception::class)
+    fun setUp() {
+        // Generate a unique in-process server name.
+        val serverName = InProcessServerBuilder.generateName()
+
+        // Create a server, add service, start, and register for automatic graceful shutdown.
+        grpcCleanup.register(InProcessServerBuilder
+                .forName(serverName).directExecutor().addService(serviceImpl).build().start())
+
+        // Create a client channel and register for automatic graceful shutdown.
+        val channel = grpcCleanup.register(
+                InProcessChannelBuilder.forName(serverName).directExecutor().build())
+
+        // Create a HelloWorldClient using the in-process channel;
+        client = HelloWorldClient(channel)
+    }
+
+    /**
+     * To test the client, call from the client against the fake server, and verify behaviors or state
+     * changes from the server side.
+     */
+    @Test
+    fun greet_messageDeliveredToServer() {
+        val requestCaptor = ArgumentCaptor.forClass(HelloRequest::class.java)
+
+        client!!.greet("test name")
+
+        verify<GreeterGrpc.GreeterImplBase>(serviceImpl)
+                .sayHello(requestCaptor.capture(), Matchers.any<StreamObserver<HelloReply>>())
+        assertEquals("test name", requestCaptor.value.name)
+    }
+}
diff --git a/examples/example-kotlin/src/test/kotlin/io/grpc/examples/helloworld/HelloWorldServerTest.kt b/examples/example-kotlin/src/test/kotlin/io/grpc/examples/helloworld/HelloWorldServerTest.kt
new file mode 100644
index 0000000..66e2f2b
--- /dev/null
+++ b/examples/example-kotlin/src/test/kotlin/io/grpc/examples/helloworld/HelloWorldServerTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.helloworld
+
+import org.junit.Assert.assertEquals
+
+import io.grpc.examples.helloworld.HelloWorldServer.GreeterImpl
+import io.grpc.inprocess.InProcessChannelBuilder
+import io.grpc.inprocess.InProcessServerBuilder
+import io.grpc.testing.GrpcCleanupRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/**
+ * Unit tests for [HelloWorldServer].
+ * For demonstrating how to write gRPC unit test only.
+ * Not intended to provide a high code coverage or to test every major usecase.
+ *
+ *
+ * For more unit test examples see [io.grpc.examples.routeguide.RouteGuideClientTest] and
+ * [io.grpc.examples.routeguide.RouteGuideServerTest].
+ */
+@RunWith(JUnit4::class)
+class HelloWorldServerTest {
+    /**
+     * This rule manages automatic graceful shutdown for the registered servers and channels at the
+     * end of test.
+     */
+    @get:Rule
+    val grpcCleanup = GrpcCleanupRule()
+
+    /**
+     * To test the server, make calls with a real stub using the in-process channel, and verify
+     * behaviors or state changes from the client side.
+     */
+    @Test
+    @Throws(Exception::class)
+    fun greeterImpl_replyMessage() {
+        // Generate a unique in-process server name.
+        val serverName = InProcessServerBuilder.generateName()
+
+        // Create a server, add service, start, and register for automatic graceful shutdown.
+        grpcCleanup.register(InProcessServerBuilder
+                .forName(serverName).directExecutor().addService(GreeterImpl()).build().start())
+
+        val blockingStub = GreeterGrpc.newBlockingStub(
+                // Create a client channel and register for automatic graceful shutdown.
+                grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor()
+                        .build()))
+
+
+        val reply = blockingStub.sayHello(HelloRequest.newBuilder().setName("test name").build())
+
+        assertEquals("Hello test name", reply.message)
+    }
+}
diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..0d4a951
--- /dev/null
+++ b/examples/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..a95009c
--- /dev/null
+++ b/examples/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/examples/gradlew b/examples/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/examples/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/examples/gradlew.bat b/examples/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/examples/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/examples/pom.xml b/examples/pom.xml
new file mode 100644
index 0000000..6de5524
--- /dev/null
+++ b/examples/pom.xml
@@ -0,0 +1,132 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>io.grpc</groupId>
+  <artifactId>examples</artifactId>
+  <packaging>jar</packaging>
+  <!-- Feel free to delete the comment at the end of these lines. It is just
+       for safely updating the version in our release process. -->
+  <version>1.16.0-SNAPSHOT</version><!-- CURRENT_GRPC_VERSION -->
+  <name>examples</name>
+  <url>http://maven.apache.org</url>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <grpc.version>1.16.0-SNAPSHOT</grpc.version><!-- CURRENT_GRPC_VERSION -->
+    <protobuf.version>3.5.1</protobuf.version>
+    <protoc.version>3.5.1-1</protoc.version>
+    <netty.tcnative.version>2.0.7.Final</netty.tcnative.version>
+    <!-- required for jdk9 -->
+    <maven.compiler.source>1.7</maven.compiler.source>
+    <maven.compiler.target>1.7</maven.compiler.target>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>io.grpc</groupId>
+      <artifactId>grpc-netty-shaded</artifactId>
+      <version>${grpc.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>io.grpc</groupId>
+      <artifactId>grpc-protobuf</artifactId>
+      <version>${grpc.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>io.grpc</groupId>
+      <artifactId>grpc-stub</artifactId>
+      <version>${grpc.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>io.grpc</groupId>
+      <artifactId>grpc-alts</artifactId>
+      <version>${grpc.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>io.grpc</groupId>
+      <artifactId>grpc-testing</artifactId>
+      <version>${grpc.version}</version>
+      <scope>test</scope>
+    </dependency>
+
+    <!-- Used in HelloWorldServerTls -->
+    <dependency>
+      <groupId>io.grpc</groupId>
+      <artifactId>grpc-netty</artifactId>
+      <version>${grpc.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-tcnative-boringssl-static</artifactId>
+      <version>${netty.tcnative.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>com.google.api.grpc</groupId>
+      <artifactId>proto-google-common-protos</artifactId>
+      <version>1.0.0</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.protobuf</groupId>
+      <artifactId>protobuf-java-util</artifactId>
+      <version>${protobuf.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.12</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>1.9.5</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <extensions>
+      <extension>
+        <groupId>kr.motd.maven</groupId>
+        <artifactId>os-maven-plugin</artifactId>
+        <version>1.5.0.Final</version>
+      </extension>
+    </extensions>
+    <plugins>
+      <plugin>
+        <groupId>org.xolstice.maven.plugins</groupId>
+        <artifactId>protobuf-maven-plugin</artifactId>
+        <version>0.5.1</version>
+        <configuration>
+          <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
+          <pluginId>grpc-java</pluginId>
+          <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>compile</goal>
+              <goal>compile-custom</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-enforcer-plugin</artifactId>
+        <version>1.4.1</version>
+        <executions>
+          <execution>
+            <id>enforce</id>
+            <goals>
+              <goal>enforce</goal>
+            </goals>
+            <configuration>
+              <rules>
+                <requireUpperBoundDeps/>
+              </rules>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/examples/settings.gradle b/examples/settings.gradle
new file mode 100644
index 0000000..9512a19
--- /dev/null
+++ b/examples/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'examples'
diff --git a/examples/src/main/java/io/grpc/examples/advanced/HelloJsonClient.java b/examples/src/main/java/io/grpc/examples/advanced/HelloJsonClient.java
new file mode 100644
index 0000000..0e16316
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/advanced/HelloJsonClient.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.advanced;
+
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.MethodDescriptor;
+import io.grpc.StatusRuntimeException;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.examples.helloworld.HelloWorldClient;
+import io.grpc.stub.AbstractStub;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Advanced example of how to swap out the serialization logic.  Normal users do not need to do
+ * this.  This code is not intended to be a production-ready implementation, since JSON encoding
+ * is slow.  Additionally, JSON serialization as implemented may be not resilient to malicious
+ * input.
+ *
+ * <p>If you are considering implementing your own serialization logic, contact the grpc team at
+ * https://groups.google.com/forum/#!forum/grpc-io
+ */
+public final class HelloJsonClient {
+  private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
+
+  private final ManagedChannel channel;
+  private final HelloJsonStub blockingStub;
+
+  /** Construct client connecting to HelloWorld server at {@code host:port}. */
+  public HelloJsonClient(String host, int port) {
+    channel = ManagedChannelBuilder.forAddress(host, port)
+        .usePlaintext()
+        .build();
+    blockingStub = new HelloJsonStub(channel);
+  }
+
+  public void shutdown() throws InterruptedException {
+    channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
+  }
+
+  /** Say hello to server. */
+  public void greet(String name) {
+    logger.info("Will try to greet " + name + " ...");
+    HelloRequest request = HelloRequest.newBuilder().setName(name).build();
+    HelloReply response;
+    try {
+      response = blockingStub.sayHello(request);
+    } catch (StatusRuntimeException e) {
+      logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
+      return;
+    }
+    logger.info("Greeting: " + response.getMessage());
+  }
+
+  /**
+   * Greet server. If provided, the first element of {@code args} is the name to use in the
+   * greeting.
+   */
+  public static void main(String[] args) throws Exception {
+    HelloJsonClient client = new HelloJsonClient("localhost", 50051);
+    try {
+      /* Access a service running on the local machine on port 50051 */
+      String user = "world";
+      if (args.length > 0) {
+        user = args[0]; /* Use the arg as the name to greet if provided */
+      }
+      client.greet(user);
+    } finally {
+      client.shutdown();
+    }
+  }
+
+  static final class HelloJsonStub extends AbstractStub<HelloJsonStub> {
+
+    static final MethodDescriptor<HelloRequest, HelloReply> METHOD_SAY_HELLO =
+        GreeterGrpc.getSayHelloMethod()
+            .toBuilder(
+                JsonMarshaller.jsonMarshaller(HelloRequest.getDefaultInstance()),
+                JsonMarshaller.jsonMarshaller(HelloReply.getDefaultInstance()))
+            .build();
+
+    protected HelloJsonStub(Channel channel) {
+      super(channel);
+    }
+
+    protected HelloJsonStub(Channel channel, CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @Override
+    protected HelloJsonStub build(Channel channel, CallOptions callOptions) {
+      return new HelloJsonStub(channel, callOptions);
+    }
+
+    public HelloReply sayHello(HelloRequest request) {
+      return blockingUnaryCall(
+          getChannel(), METHOD_SAY_HELLO, getCallOptions(), request);
+    }
+  }
+}
+
diff --git a/examples/src/main/java/io/grpc/examples/advanced/HelloJsonServer.java b/examples/src/main/java/io/grpc/examples/advanced/HelloJsonServer.java
new file mode 100644
index 0000000..1c822e7
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/advanced/HelloJsonServer.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.advanced;
+
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+
+import io.grpc.BindableService;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.examples.helloworld.HelloWorldServer;
+import io.grpc.stub.ServerCalls.UnaryMethod;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+/**
+ * Server that manages startup/shutdown of a {@code Greeter} server.
+ *
+ * <p>This is an advanced example of how to swap out the serialization logic.  Normal users do not
+ * need to do this.  This code is not intended to be a production-ready implementation, since JSON
+ * encoding is slow.  Additionally, JSON serialization as implemented may be not resilient to
+ * malicious input.
+ *
+ * <p>If you are considering implementing your own serialization logic, contact the grpc team at
+ * https://groups.google.com/forum/#!forum/grpc-io
+ */
+public class HelloJsonServer {
+  private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
+
+  private Server server;
+
+  private void start() throws IOException {
+    /* The port on which the server should run */
+    int port = 50051;
+    server = ServerBuilder.forPort(port)
+        .addService(new GreeterImpl())
+        .build()
+        .start();
+    logger.info("Server started, listening on " + port);
+    Runtime.getRuntime().addShutdownHook(new Thread() {
+      @Override
+      public void run() {
+        // Use stderr here since the logger may have been reset by its JVM shutdown hook.
+        System.err.println("*** shutting down gRPC server since JVM is shutting down");
+        HelloJsonServer.this.stop();
+        System.err.println("*** server shut down");
+      }
+    });
+  }
+
+  private void stop() {
+    if (server != null) {
+      server.shutdown();
+    }
+  }
+
+  /**
+   * Await termination on the main thread since the grpc library uses daemon threads.
+   */
+  private void blockUntilShutdown() throws InterruptedException {
+    if (server != null) {
+      server.awaitTermination();
+    }
+  }
+
+  /**
+   * Main launches the server from the command line.
+   */
+  public static void main(String[] args) throws IOException, InterruptedException {
+    final HelloJsonServer server = new HelloJsonServer();
+    server.start();
+    server.blockUntilShutdown();
+  }
+
+  private static class GreeterImpl implements BindableService {
+
+    private void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
+      HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
+      responseObserver.onNext(reply);
+      responseObserver.onCompleted();
+    }
+
+    @Override
+    public ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition
+          .builder(GreeterGrpc.getServiceDescriptor().getName())
+          .addMethod(HelloJsonClient.HelloJsonStub.METHOD_SAY_HELLO,
+              asyncUnaryCall(
+                  new UnaryMethod<HelloRequest, HelloReply>() {
+                    @Override
+                    public void invoke(
+                        HelloRequest request, StreamObserver<HelloReply> responseObserver) {
+                      sayHello(request, responseObserver);
+                    }
+                  }))
+          .build();
+    }
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/advanced/JsonMarshaller.java b/examples/src/main/java/io/grpc/examples/advanced/JsonMarshaller.java
new file mode 100644
index 0000000..8b3068c
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/advanced/JsonMarshaller.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.advanced;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.Message;
+import com.google.protobuf.Message.Builder;
+import com.google.protobuf.util.JsonFormat;
+import com.google.protobuf.util.JsonFormat.Parser;
+import com.google.protobuf.util.JsonFormat.Printer;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.Status;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+
+/**
+ * A {@link Marshaller} for JSON.  This marshals in the Protobuf 3 format described here:
+ * https://developers.google.com/protocol-buffers/docs/proto3#json
+ */
+final class JsonMarshaller {
+
+  private JsonMarshaller() {}
+
+  /**
+   * Create a {@code Marshaller} for json protos of the same type as {@code defaultInstance}.
+   *
+   * <p>This is an unstable API and has not been optimized yet for performance.
+   */
+  public static <T extends Message> Marshaller<T> jsonMarshaller(final T defaultInstance) {
+    final Parser parser = JsonFormat.parser();
+    final Printer printer = JsonFormat.printer();
+    return jsonMarshaller(defaultInstance, parser, printer);
+  }
+
+  /**
+   * Create a {@code Marshaller} for json protos of the same type as {@code defaultInstance}.
+   *
+   * <p>This is an unstable API and has not been optimized yet for performance.
+   */
+  public static <T extends Message> Marshaller<T> jsonMarshaller(
+      final T defaultInstance, final Parser parser, final Printer printer) {
+
+    final Charset charset = Charset.forName("UTF-8");
+
+    return new Marshaller<T>() {
+      @Override
+      public InputStream stream(T value) {
+        try {
+          return new ByteArrayInputStream(printer.print(value).getBytes(charset));
+        } catch (InvalidProtocolBufferException e) {
+          throw Status.INTERNAL
+              .withCause(e)
+              .withDescription("Unable to print json proto")
+              .asRuntimeException();
+        }
+      }
+
+      @SuppressWarnings("unchecked")
+      @Override
+      public T parse(InputStream stream) {
+        Builder builder = defaultInstance.newBuilderForType();
+        Reader reader = new InputStreamReader(stream, charset);
+        T proto;
+        try {
+          parser.merge(reader, builder);
+          proto = (T) builder.build();
+          reader.close();
+        } catch (InvalidProtocolBufferException e) {
+          throw Status.INTERNAL.withDescription("Invalid protobuf byte sequence")
+              .withCause(e).asRuntimeException();
+        } catch (IOException e) {
+          // Same for now, might be unavailable
+          throw Status.INTERNAL.withDescription("Invalid protobuf byte sequence")
+              .withCause(e).asRuntimeException();
+        }
+        return proto;
+      }
+    };
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/alts/HelloWorldAltsClient.java b/examples/src/main/java/io/grpc/examples/alts/HelloWorldAltsClient.java
new file mode 100644
index 0000000..9635180
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/alts/HelloWorldAltsClient.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.alts;
+
+import io.grpc.alts.AltsChannelBuilder;
+import io.grpc.ManagedChannel;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * An example gRPC client that uses ALTS. Shows how to do a Unary RPC. This example can only be run
+ * on Google Cloud Platform.
+ */
+public final class HelloWorldAltsClient {
+  private static final Logger logger = Logger.getLogger(HelloWorldAltsClient.class.getName());
+  private String serverAddress = "localhost:10001";
+
+  public static void main(String[] args) throws InterruptedException {
+    new HelloWorldAltsClient().run(args);
+  }
+
+  private void parseArgs(String[] args) {
+    boolean usage = false;
+    for (String arg : args) {
+      if (!arg.startsWith("--")) {
+        System.err.println("All arguments must start with '--': " + arg);
+        usage = true;
+        break;
+      }
+      String[] parts = arg.substring(2).split("=", 2);
+      String key = parts[0];
+      if ("help".equals(key)) {
+        usage = true;
+        break;
+      }
+      if (parts.length != 2) {
+        System.err.println("All arguments must be of the form --arg=value");
+        usage = true;
+        break;
+      }
+      String value = parts[1];
+      if ("server".equals(key)) {
+        serverAddress = value;
+      } else {
+        System.err.println("Unknown argument: " + key);
+        usage = true;
+        break;
+      }
+    }
+    if (usage) {
+      HelloWorldAltsClient c = new HelloWorldAltsClient();
+      System.out.println(
+          "Usage: [ARGS...]"
+              + "\n"
+              + "\n  --server=SERVER_ADDRESS        Server address to connect to. Default "
+              + c.serverAddress);
+      System.exit(1);
+    }
+  }
+
+  private void run(String[] args) throws InterruptedException {
+    parseArgs(args);
+    ExecutorService executor = Executors.newFixedThreadPool(1);
+    ManagedChannel channel = AltsChannelBuilder.forTarget(serverAddress).executor(executor).build();
+    try {
+      GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);
+      HelloReply resp = stub.sayHello(HelloRequest.newBuilder().setName("Waldo").build());
+
+      logger.log(Level.INFO, "Got {0}", resp);
+    } finally {
+      channel.shutdown();
+      channel.awaitTermination(1, TimeUnit.SECONDS);
+      // Wait until the channel has terminated, since tasks can be queued after the channel is
+      // shutdown.
+      executor.shutdown();
+    }
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/alts/HelloWorldAltsServer.java b/examples/src/main/java/io/grpc/examples/alts/HelloWorldAltsServer.java
new file mode 100644
index 0000000..fd66206
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/alts/HelloWorldAltsServer.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.alts;
+
+import io.grpc.alts.AltsServerBuilder;
+import io.grpc.Server;
+import io.grpc.examples.helloworld.GreeterGrpc.GreeterImplBase;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * An example gRPC server that uses ALTS. Shows how to do a Unary RPC. This example can only be run
+ * on Google Cloud Platform.
+ */
+public final class HelloWorldAltsServer extends GreeterImplBase {
+  private static final Logger logger = Logger.getLogger(HelloWorldAltsServer.class.getName());
+  private Server server;
+  private int port = 10001;
+
+  public static void main(String[] args) throws IOException, InterruptedException {
+    new HelloWorldAltsServer().start(args);
+  }
+
+  private void parseArgs(String[] args) {
+    boolean usage = false;
+    for (String arg : args) {
+      if (!arg.startsWith("--")) {
+        System.err.println("All arguments must start with '--': " + arg);
+        usage = true;
+        break;
+      }
+      String[] parts = arg.substring(2).split("=", 2);
+      String key = parts[0];
+      if ("help".equals(key)) {
+        usage = true;
+        break;
+      }
+      if (parts.length != 2) {
+        System.err.println("All arguments must be of the form --arg=value");
+        usage = true;
+        break;
+      }
+      String value = parts[1];
+      if ("port".equals(key)) {
+        port = Integer.parseInt(value);
+      } else {
+        System.err.println("Unknown argument: " + key);
+        usage = true;
+        break;
+      }
+    }
+    if (usage) {
+      HelloWorldAltsServer s = new HelloWorldAltsServer();
+      System.out.println(
+          "Usage: [ARGS...]"
+              + "\n"
+              + "\n  --port=PORT           Server port to bind to. Default "
+              + s.port);
+      System.exit(1);
+    }
+  }
+
+  private void start(String[] args) throws IOException, InterruptedException {
+    parseArgs(args);
+    server =
+        AltsServerBuilder.forPort(port)
+            .addService(this)
+            .executor(Executors.newFixedThreadPool(1))
+            .build();
+    server.start();
+    logger.log(Level.INFO, "Started on {0}", port);
+    server.awaitTermination();
+  }
+
+  @Override
+  public void sayHello(HelloRequest request, StreamObserver<HelloReply> observer) {
+    observer.onNext(HelloReply.newBuilder().setMessage("Hello, " + request.getName()).build());
+    observer.onCompleted();
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/errorhandling/DetailErrorSample.java b/examples/src/main/java/io/grpc/examples/errorhandling/DetailErrorSample.java
new file mode 100644
index 0000000..b743b46
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/errorhandling/DetailErrorSample.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.errorhandling;
+
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import com.google.common.base.Verify;
+import com.google.common.base.VerifyException;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.Uninterruptibles;
+import com.google.rpc.DebugInfo;
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.Metadata;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.Status;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.GreeterGrpc.GreeterBlockingStub;
+import io.grpc.examples.helloworld.GreeterGrpc.GreeterFutureStub;
+import io.grpc.examples.helloworld.GreeterGrpc.GreeterStub;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.protobuf.ProtoUtils;
+import io.grpc.stub.StreamObserver;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+/**
+ * Shows how to setting and reading RPC error details.
+ * Proto used here is just an example proto, but the pattern sending
+ * application error information as an application-specific binary protos
+ * in the response trailers is the recommended way to return application
+ * level error.
+ */
+public class DetailErrorSample {
+  private static final Metadata.Key<DebugInfo> DEBUG_INFO_TRAILER_KEY =
+      ProtoUtils.keyForProto(DebugInfo.getDefaultInstance());
+
+  private static final DebugInfo DEBUG_INFO =
+      DebugInfo.newBuilder()
+          .addStackEntries("stack_entry_1")
+          .addStackEntries("stack_entry_2")
+          .addStackEntries("stack_entry_3")
+          .setDetail("detailed error info.").build();
+
+  private static final String DEBUG_DESC = "detailed error description";
+
+  public static void main(String[] args) throws Exception {
+    new DetailErrorSample().run();
+  }
+
+  private ManagedChannel channel;
+
+  void run() throws Exception {
+    Server server = ServerBuilder.forPort(0).addService(new GreeterGrpc.GreeterImplBase() {
+      @Override
+      public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
+        Metadata trailers = new Metadata();
+        trailers.put(DEBUG_INFO_TRAILER_KEY, DEBUG_INFO);
+        responseObserver.onError(Status.INTERNAL.withDescription(DEBUG_DESC)
+            .asRuntimeException(trailers));
+      }
+    }).build().start();
+    channel =
+        ManagedChannelBuilder.forAddress("localhost", server.getPort()).usePlaintext().build();
+
+    blockingCall();
+    futureCallDirect();
+    futureCallCallback();
+    asyncCall();
+    advancedAsyncCall();
+
+    channel.shutdown();
+    server.shutdown();
+    channel.awaitTermination(1, TimeUnit.SECONDS);
+    server.awaitTermination();
+  }
+
+  static void verifyErrorReply(Throwable t) {
+    Status status = Status.fromThrowable(t);
+    Metadata trailers = Status.trailersFromThrowable(t);
+    Verify.verify(status.getCode() == Status.Code.INTERNAL);
+    Verify.verify(trailers.containsKey(DEBUG_INFO_TRAILER_KEY));
+    Verify.verify(status.getDescription().equals(DEBUG_DESC));
+    try {
+      Verify.verify(trailers.get(DEBUG_INFO_TRAILER_KEY).equals(DEBUG_INFO));
+    } catch (IllegalArgumentException e) {
+      throw new VerifyException(e);
+    }
+  }
+
+  void blockingCall() {
+    GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);
+    try {
+      stub.sayHello(HelloRequest.newBuilder().build());
+    } catch (Exception e) {
+      verifyErrorReply(e);
+    }
+  }
+
+  void futureCallDirect() {
+    GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
+    ListenableFuture<HelloReply> response =
+        stub.sayHello(HelloRequest.newBuilder().build());
+
+    try {
+      response.get();
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      throw new RuntimeException(e);
+    } catch (ExecutionException e) {
+      verifyErrorReply(e.getCause());
+    }
+  }
+
+  void futureCallCallback() {
+    GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
+    ListenableFuture<HelloReply> response =
+        stub.sayHello(HelloRequest.newBuilder().build());
+
+    final CountDownLatch latch = new CountDownLatch(1);
+
+    Futures.addCallback(
+        response,
+        new FutureCallback<HelloReply>() {
+          @Override
+          public void onSuccess(@Nullable HelloReply result) {
+            // Won't be called, since the server in this example always fails.
+          }
+
+          @Override
+          public void onFailure(Throwable t) {
+            verifyErrorReply(t);
+            latch.countDown();
+          }
+        },
+        directExecutor());
+
+    if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
+      throw new RuntimeException("timeout!");
+    }
+  }
+
+  void asyncCall() {
+    GreeterStub stub = GreeterGrpc.newStub(channel);
+    HelloRequest request = HelloRequest.newBuilder().build();
+    final CountDownLatch latch = new CountDownLatch(1);
+    StreamObserver<HelloReply> responseObserver = new StreamObserver<HelloReply>() {
+
+      @Override
+      public void onNext(HelloReply value) {
+        // Won't be called.
+      }
+
+      @Override
+      public void onError(Throwable t) {
+        verifyErrorReply(t);
+        latch.countDown();
+      }
+
+      @Override
+      public void onCompleted() {
+        // Won't be called, since the server in this example always fails.
+      }
+    };
+    stub.sayHello(request, responseObserver);
+
+    if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
+      throw new RuntimeException("timeout!");
+    }
+  }
+
+
+  /**
+   * This is more advanced and does not make use of the stub.  You should not normally need to do
+   * this, but here is how you would.
+   */
+  void advancedAsyncCall() {
+    ClientCall<HelloRequest, HelloReply> call =
+        channel.newCall(GreeterGrpc.getSayHelloMethod(), CallOptions.DEFAULT);
+
+    final CountDownLatch latch = new CountDownLatch(1);
+
+    call.start(new ClientCall.Listener<HelloReply>() {
+
+      @Override
+      public void onClose(Status status, Metadata trailers) {
+        Verify.verify(status.getCode() == Status.Code.INTERNAL);
+        Verify.verify(trailers.containsKey(DEBUG_INFO_TRAILER_KEY));
+        try {
+          Verify.verify(trailers.get(DEBUG_INFO_TRAILER_KEY).equals(DEBUG_INFO));
+        } catch (IllegalArgumentException e) {
+          throw new VerifyException(e);
+        }
+
+        latch.countDown();
+      }
+    }, new Metadata());
+
+    call.sendMessage(HelloRequest.newBuilder().build());
+    call.halfClose();
+
+    if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
+      throw new RuntimeException("timeout!");
+    }
+  }
+}
+
diff --git a/examples/src/main/java/io/grpc/examples/errorhandling/ErrorHandlingClient.java b/examples/src/main/java/io/grpc/examples/errorhandling/ErrorHandlingClient.java
new file mode 100644
index 0000000..4afd39b
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/errorhandling/ErrorHandlingClient.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.errorhandling;
+
+import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+
+import com.google.common.base.Verify;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.Uninterruptibles;
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.Metadata;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.Status;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.GreeterGrpc.GreeterBlockingStub;
+import io.grpc.examples.helloworld.GreeterGrpc.GreeterFutureStub;
+import io.grpc.examples.helloworld.GreeterGrpc.GreeterStub;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.stub.StreamObserver;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+/**
+ * Shows how to extract error information from a server response.
+ */
+public class ErrorHandlingClient {
+  public static void main(String [] args) throws Exception {
+    new ErrorHandlingClient().run();
+  }
+
+  private ManagedChannel channel;
+
+  void run() throws Exception {
+    // Port 0 means that the operating system will pick an available port to use.
+    Server server = ServerBuilder.forPort(0).addService(new GreeterGrpc.GreeterImplBase() {
+      @Override
+      public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
+        responseObserver.onError(Status.INTERNAL
+            .withDescription("Eggplant Xerxes Crybaby Overbite Narwhal").asRuntimeException());
+      }
+    }).build().start();
+    channel =
+        ManagedChannelBuilder.forAddress("localhost", server.getPort()).usePlaintext().build();
+
+    blockingCall();
+    futureCallDirect();
+    futureCallCallback();
+    asyncCall();
+    advancedAsyncCall();
+
+    channel.shutdown();
+    server.shutdown();
+    channel.awaitTermination(1, TimeUnit.SECONDS);
+    server.awaitTermination();
+  }
+
+  void blockingCall() {
+    GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);
+    try {
+      stub.sayHello(HelloRequest.newBuilder().setName("Bart").build());
+    } catch (Exception e) {
+      Status status = Status.fromThrowable(e);
+      Verify.verify(status.getCode() == Status.Code.INTERNAL);
+      Verify.verify(status.getDescription().contains("Eggplant"));
+      // Cause is not transmitted over the wire.
+    }
+  }
+
+  void futureCallDirect() {
+    GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
+    ListenableFuture<HelloReply> response =
+        stub.sayHello(HelloRequest.newBuilder().setName("Lisa").build());
+
+    try {
+      response.get();
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      throw new RuntimeException(e);
+    } catch (ExecutionException e) {
+      Status status = Status.fromThrowable(e.getCause());
+      Verify.verify(status.getCode() == Status.Code.INTERNAL);
+      Verify.verify(status.getDescription().contains("Xerxes"));
+      // Cause is not transmitted over the wire.
+    }
+  }
+
+  void futureCallCallback() {
+    GreeterFutureStub stub = GreeterGrpc.newFutureStub(channel);
+    ListenableFuture<HelloReply> response =
+        stub.sayHello(HelloRequest.newBuilder().setName("Maggie").build());
+
+    final CountDownLatch latch = new CountDownLatch(1);
+
+    Futures.addCallback(
+        response,
+        new FutureCallback<HelloReply>() {
+          @Override
+          public void onSuccess(@Nullable HelloReply result) {
+            // Won't be called, since the server in this example always fails.
+          }
+
+          @Override
+          public void onFailure(Throwable t) {
+            Status status = Status.fromThrowable(t);
+            Verify.verify(status.getCode() == Status.Code.INTERNAL);
+            Verify.verify(status.getDescription().contains("Crybaby"));
+            // Cause is not transmitted over the wire..
+            latch.countDown();
+          }
+        },
+        directExecutor());
+
+    if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
+      throw new RuntimeException("timeout!");
+    }
+  }
+
+  void asyncCall() {
+    GreeterStub stub = GreeterGrpc.newStub(channel);
+    HelloRequest request = HelloRequest.newBuilder().setName("Homer").build();
+    final CountDownLatch latch = new CountDownLatch(1);
+    StreamObserver<HelloReply> responseObserver = new StreamObserver<HelloReply>() {
+
+      @Override
+      public void onNext(HelloReply value) {
+        // Won't be called.
+      }
+
+      @Override
+      public void onError(Throwable t) {
+        Status status = Status.fromThrowable(t);
+        Verify.verify(status.getCode() == Status.Code.INTERNAL);
+        Verify.verify(status.getDescription().contains("Overbite"));
+        // Cause is not transmitted over the wire..
+        latch.countDown();
+      }
+
+      @Override
+      public void onCompleted() {
+        // Won't be called, since the server in this example always fails.
+      }
+    };
+    stub.sayHello(request, responseObserver);
+
+    if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
+      throw new RuntimeException("timeout!");
+    }
+  }
+
+
+  /**
+   * This is more advanced and does not make use of the stub.  You should not normally need to do
+   * this, but here is how you would.
+   */
+  void advancedAsyncCall() {
+    ClientCall<HelloRequest, HelloReply> call =
+        channel.newCall(GreeterGrpc.getSayHelloMethod(), CallOptions.DEFAULT);
+
+    final CountDownLatch latch = new CountDownLatch(1);
+
+    call.start(new ClientCall.Listener<HelloReply>() {
+
+      @Override
+      public void onClose(Status status, Metadata trailers) {
+        Verify.verify(status.getCode() == Status.Code.INTERNAL);
+        Verify.verify(status.getDescription().contains("Narwhal"));
+        // Cause is not transmitted over the wire.
+        latch.countDown();
+      }
+    }, new Metadata());
+
+    call.sendMessage(HelloRequest.newBuilder().setName("Marge").build());
+    call.halfClose();
+
+    if (!Uninterruptibles.awaitUninterruptibly(latch, 1, TimeUnit.SECONDS)) {
+      throw new RuntimeException("timeout!");
+    }
+  }
+}
+
diff --git a/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldClient.java b/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldClient.java
new file mode 100644
index 0000000..2ed51a4
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/experimental/CompressingHelloWorldClient.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.experimental;
+
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.StatusRuntimeException;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A simple client that requests a greeting from the
+ *      {@link io.grpc.examples.helloworld.HelloWorldServer}.
+ *
+ * <p>This class should act a drop in replacement for
+ *      {@link io.grpc.examples.helloworld.HelloWorldClient}.
+ */
+public class CompressingHelloWorldClient {
+  private static final Logger logger =
+      Logger.getLogger(CompressingHelloWorldClient.class.getName());
+
+  private final ManagedChannel channel;
+  private final GreeterGrpc.GreeterBlockingStub blockingStub;
+
+  /** Construct client connecting to HelloWorld server at {@code host:port}. */
+  public CompressingHelloWorldClient(String host, int port) {
+    channel = ManagedChannelBuilder.forAddress(host, port)
+        .usePlaintext()
+        .build();
+    blockingStub = GreeterGrpc.newBlockingStub(channel);
+  }
+
+  public void shutdown() throws InterruptedException {
+    channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
+  }
+
+  /** Say hello to server. */
+  public void greet(String name) {
+    logger.info("Will try to greet " + name + " ...");
+    HelloRequest request = HelloRequest.newBuilder().setName(name).build();
+    HelloReply response;
+    try {
+      // This enables compression for requests. Independent of this setting, servers choose whether
+      // to compress responses.
+      response = blockingStub.withCompression("gzip").sayHello(request);
+    } catch (StatusRuntimeException e) {
+      logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
+      return;
+    }
+    logger.info("Greeting: " + response.getMessage());
+  }
+
+  /**
+   * Greet server. If provided, the first element of {@code args} is the name to use in the
+   * greeting.
+   */
+  public static void main(String[] args) throws Exception {
+    CompressingHelloWorldClient client = new CompressingHelloWorldClient("localhost", 50051);
+    try {
+      /* Access a service running on the local machine on port 50051 */
+      String user = "world";
+      if (args.length > 0) {
+        user = args[0]; /* Use the arg as the name to greet if provided */
+      }
+      client.greet(user);
+    } finally {
+      client.shutdown();
+    }
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/header/CustomHeaderClient.java b/examples/src/main/java/io/grpc/examples/header/CustomHeaderClient.java
new file mode 100644
index 0000000..1095c73
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/header/CustomHeaderClient.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.header;
+
+import io.grpc.Channel;
+import io.grpc.ClientInterceptor;
+import io.grpc.ClientInterceptors;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.StatusRuntimeException;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A simple client that like {@link io.grpc.examples.helloworld.HelloWorldClient}.
+ * This client can help you create custom headers.
+ */
+public class CustomHeaderClient {
+  private static final Logger logger = Logger.getLogger(CustomHeaderClient.class.getName());
+
+  private final ManagedChannel originChannel;
+  private final GreeterGrpc.GreeterBlockingStub blockingStub;
+
+  /**
+   * A custom client.
+   */
+  private CustomHeaderClient(String host, int port) {
+    originChannel = ManagedChannelBuilder.forAddress(host, port)
+        .usePlaintext()
+        .build();
+    ClientInterceptor interceptor = new HeaderClientInterceptor();
+    Channel channel = ClientInterceptors.intercept(originChannel, interceptor);
+    blockingStub = GreeterGrpc.newBlockingStub(channel);
+  }
+
+  private void shutdown() throws InterruptedException {
+    originChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
+  }
+
+  /**
+   * A simple client method that like {@link io.grpc.examples.helloworld.HelloWorldClient}.
+   */
+  private void greet(String name) {
+    logger.info("Will try to greet " + name + " ...");
+    HelloRequest request = HelloRequest.newBuilder().setName(name).build();
+    HelloReply response;
+    try {
+      response = blockingStub.sayHello(request);
+    } catch (StatusRuntimeException e) {
+      logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
+      return;
+    }
+    logger.info("Greeting: " + response.getMessage());
+  }
+
+  /**
+   * Main start the client from the command line.
+   */
+  public static void main(String[] args) throws Exception {
+    CustomHeaderClient client = new CustomHeaderClient("localhost", 50051);
+    try {
+      /* Access a service running on the local machine on port 50051 */
+      String user = "world";
+      if (args.length > 0) {
+        user = args[0]; /* Use the arg as the name to greet if provided */
+      }
+      client.greet(user);
+    } finally {
+      client.shutdown();
+    }
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/header/CustomHeaderServer.java b/examples/src/main/java/io/grpc/examples/header/CustomHeaderServer.java
new file mode 100644
index 0000000..21ad5f2
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/header/CustomHeaderServer.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.header;
+
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.ServerInterceptors;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+/**
+ * A simple server that like {@link io.grpc.examples.helloworld.HelloWorldServer}.
+ * You can get and response any header in {@link io.grpc.examples.header.HeaderServerInterceptor}.
+ */
+public class CustomHeaderServer {
+  private static final Logger logger = Logger.getLogger(CustomHeaderServer.class.getName());
+
+  /* The port on which the server should run */
+  private static final int PORT = 50051;
+  private Server server;
+
+  private void start() throws IOException {
+    server = ServerBuilder.forPort(PORT)
+        .addService(ServerInterceptors.intercept(new GreeterImpl(), new HeaderServerInterceptor()))
+        .build()
+        .start();
+    logger.info("Server started, listening on " + PORT);
+    Runtime.getRuntime().addShutdownHook(new Thread() {
+      @Override
+      public void run() {
+        // Use stderr here since the logger may have been reset by its JVM shutdown hook.
+        System.err.println("*** shutting down gRPC server since JVM is shutting down");
+        CustomHeaderServer.this.stop();
+        System.err.println("*** server shut down");
+      }
+    });
+  }
+
+  private void stop() {
+    if (server != null) {
+      server.shutdown();
+    }
+  }
+
+  /**
+   * Await termination on the main thread since the grpc library uses daemon threads.
+   */
+  private void blockUntilShutdown() throws InterruptedException {
+    if (server != null) {
+      server.awaitTermination();
+    }
+  }
+
+  /**
+   * Main launches the server from the command line.
+   */
+  public static void main(String[] args) throws IOException, InterruptedException {
+    final CustomHeaderServer server = new CustomHeaderServer();
+    server.start();
+    server.blockUntilShutdown();
+  }
+
+  private static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
+
+    @Override
+    public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
+      HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
+      responseObserver.onNext(reply);
+      responseObserver.onCompleted();
+    }
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/header/HeaderClientInterceptor.java b/examples/src/main/java/io/grpc/examples/header/HeaderClientInterceptor.java
new file mode 100644
index 0000000..b9a7393
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/header/HeaderClientInterceptor.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.header;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import java.util.logging.Logger;
+
+/**
+ * A interceptor to handle client header.
+ */
+public class HeaderClientInterceptor implements ClientInterceptor {
+
+  private static final Logger logger = Logger.getLogger(HeaderClientInterceptor.class.getName());
+
+  @VisibleForTesting
+  static final Metadata.Key<String> CUSTOM_HEADER_KEY =
+      Metadata.Key.of("custom_client_header_key", Metadata.ASCII_STRING_MARSHALLER);
+
+  @Override
+  public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method,
+      CallOptions callOptions, Channel next) {
+    return new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
+
+      @Override
+      public void start(Listener<RespT> responseListener, Metadata headers) {
+        /* put custom header */
+        headers.put(CUSTOM_HEADER_KEY, "customRequestValue");
+        super.start(new SimpleForwardingClientCallListener<RespT>(responseListener) {
+          @Override
+          public void onHeaders(Metadata headers) {
+            /**
+             * if you don't need receive header from server,
+             * you can use {@link io.grpc.stub.MetadataUtils#attachHeaders}
+             * directly to send header
+             */
+            logger.info("header received from server:" + headers);
+            super.onHeaders(headers);
+          }
+        }, headers);
+      }
+    };
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/header/HeaderServerInterceptor.java b/examples/src/main/java/io/grpc/examples/header/HeaderServerInterceptor.java
new file mode 100644
index 0000000..35ca71e
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/header/HeaderServerInterceptor.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.header;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
+import io.grpc.Metadata;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import java.util.logging.Logger;
+
+/**
+ * A interceptor to handle server header.
+ */
+public class HeaderServerInterceptor implements ServerInterceptor {
+
+  private static final Logger logger = Logger.getLogger(HeaderServerInterceptor.class.getName());
+
+  @VisibleForTesting
+  static final Metadata.Key<String> CUSTOM_HEADER_KEY =
+      Metadata.Key.of("custom_server_header_key", Metadata.ASCII_STRING_MARSHALLER);
+
+
+  @Override
+  public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+      ServerCall<ReqT, RespT> call,
+      final Metadata requestHeaders,
+      ServerCallHandler<ReqT, RespT> next) {
+    logger.info("header received from client:" + requestHeaders);
+    return next.startCall(new SimpleForwardingServerCall<ReqT, RespT>(call) {
+      @Override
+      public void sendHeaders(Metadata responseHeaders) {
+        responseHeaders.put(CUSTOM_HEADER_KEY, "customRespondValue");
+        super.sendHeaders(responseHeaders);
+      }
+    }, requestHeaders);
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java b/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java
new file mode 100644
index 0000000..760f77c
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldClient.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.helloworld;
+
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.StatusRuntimeException;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A simple client that requests a greeting from the {@link HelloWorldServer}.
+ */
+public class HelloWorldClient {
+  private static final Logger logger = Logger.getLogger(HelloWorldClient.class.getName());
+
+  private final ManagedChannel channel;
+  private final GreeterGrpc.GreeterBlockingStub blockingStub;
+
+  /** Construct client connecting to HelloWorld server at {@code host:port}. */
+  public HelloWorldClient(String host, int port) {
+    this(ManagedChannelBuilder.forAddress(host, port)
+        // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
+        // needing certificates.
+        .usePlaintext()
+        .build());
+  }
+
+  /** Construct client for accessing HelloWorld server using the existing channel. */
+  HelloWorldClient(ManagedChannel channel) {
+    this.channel = channel;
+    blockingStub = GreeterGrpc.newBlockingStub(channel);
+  }
+
+  public void shutdown() throws InterruptedException {
+    channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
+  }
+
+  /** Say hello to server. */
+  public void greet(String name) {
+    logger.info("Will try to greet " + name + " ...");
+    HelloRequest request = HelloRequest.newBuilder().setName(name).build();
+    HelloReply response;
+    try {
+      response = blockingStub.sayHello(request);
+    } catch (StatusRuntimeException e) {
+      logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
+      return;
+    }
+    logger.info("Greeting: " + response.getMessage());
+  }
+
+  /**
+   * Greet server. If provided, the first element of {@code args} is the name to use in the
+   * greeting.
+   */
+  public static void main(String[] args) throws Exception {
+    HelloWorldClient client = new HelloWorldClient("localhost", 50051);
+    try {
+      /* Access a service running on the local machine on port 50051 */
+      String user = "world";
+      if (args.length > 0) {
+        user = args[0]; /* Use the arg as the name to greet if provided */
+      }
+      client.greet(user);
+    } finally {
+      client.shutdown();
+    }
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java b/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java
new file mode 100644
index 0000000..4c021a8
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/helloworld/HelloWorldServer.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.helloworld;
+
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+/**
+ * Server that manages startup/shutdown of a {@code Greeter} server.
+ */
+public class HelloWorldServer {
+  private static final Logger logger = Logger.getLogger(HelloWorldServer.class.getName());
+
+  private Server server;
+
+  private void start() throws IOException {
+    /* The port on which the server should run */
+    int port = 50051;
+    server = ServerBuilder.forPort(port)
+        .addService(new GreeterImpl())
+        .build()
+        .start();
+    logger.info("Server started, listening on " + port);
+    Runtime.getRuntime().addShutdownHook(new Thread() {
+      @Override
+      public void run() {
+        // Use stderr here since the logger may have been reset by its JVM shutdown hook.
+        System.err.println("*** shutting down gRPC server since JVM is shutting down");
+        HelloWorldServer.this.stop();
+        System.err.println("*** server shut down");
+      }
+    });
+  }
+
+  private void stop() {
+    if (server != null) {
+      server.shutdown();
+    }
+  }
+
+  /**
+   * Await termination on the main thread since the grpc library uses daemon threads.
+   */
+  private void blockUntilShutdown() throws InterruptedException {
+    if (server != null) {
+      server.awaitTermination();
+    }
+  }
+
+  /**
+   * Main launches the server from the command line.
+   */
+  public static void main(String[] args) throws IOException, InterruptedException {
+    final HelloWorldServer server = new HelloWorldServer();
+    server.start();
+    server.blockUntilShutdown();
+  }
+
+  static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
+
+    @Override
+    public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
+      HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
+      responseObserver.onNext(reply);
+      responseObserver.onCompleted();
+    }
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java b/examples/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java
new file mode 100644
index 0000000..8be0b38
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/helloworldtls/HelloWorldClientTls.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.helloworldtls;
+
+import io.grpc.ManagedChannel;
+import io.grpc.StatusRuntimeException;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.examples.helloworld.HelloWorldServer;
+import io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.NegotiationType;
+import io.grpc.netty.NettyChannelBuilder;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+
+import javax.net.ssl.SSLException;
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A simple client that requests a greeting from the {@link HelloWorldServer} with TLS.
+ */
+public class HelloWorldClientTls {
+    private static final Logger logger = Logger.getLogger(HelloWorldClientTls.class.getName());
+
+    private final ManagedChannel channel;
+    private final GreeterGrpc.GreeterBlockingStub blockingStub;
+
+    private static SslContext buildSslContext(String trustCertCollectionFilePath,
+                                              String clientCertChainFilePath,
+                                              String clientPrivateKeyFilePath) throws SSLException {
+        SslContextBuilder builder = GrpcSslContexts.forClient();
+        if (trustCertCollectionFilePath != null) {
+            builder.trustManager(new File(trustCertCollectionFilePath));
+        }
+        if (clientCertChainFilePath != null && clientPrivateKeyFilePath != null) {
+            builder.keyManager(new File(clientCertChainFilePath), new File(clientPrivateKeyFilePath));
+        }
+        return builder.build();
+    }
+
+    /**
+     * Construct client connecting to HelloWorld server at {@code host:port}.
+     */
+    public HelloWorldClientTls(String host,
+                               int port,
+                               SslContext sslContext) throws SSLException {
+
+        this(NettyChannelBuilder.forAddress(host, port)
+                .negotiationType(NegotiationType.TLS)
+                .sslContext(sslContext)
+                .build());
+    }
+
+    /**
+     * Construct client for accessing RouteGuide server using the existing channel.
+     */
+    HelloWorldClientTls(ManagedChannel channel) {
+        this.channel = channel;
+        blockingStub = GreeterGrpc.newBlockingStub(channel);
+    }
+
+    public void shutdown() throws InterruptedException {
+        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
+    }
+
+    /**
+     * Say hello to server.
+     */
+    public void greet(String name) {
+        logger.info("Will try to greet " + name + " ...");
+        HelloRequest request = HelloRequest.newBuilder().setName(name).build();
+        HelloReply response;
+        try {
+            response = blockingStub.sayHello(request);
+        } catch (StatusRuntimeException e) {
+            logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
+            return;
+        }
+        logger.info("Greeting: " + response.getMessage());
+    }
+
+    /**
+     * Greet server. If provided, the first element of {@code args} is the name to use in the
+     * greeting.
+     */
+    public static void main(String[] args) throws Exception {
+
+        if (args.length < 2 || args.length == 4 || args.length > 5) {
+            System.out.println("USAGE: HelloWorldClientTls host port [trustCertCollectionFilePath] " +
+                    "[clientCertChainFilePath] [clientPrivateKeyFilePath]\n  Note: clientCertChainFilePath and " +
+                    "clientPrivateKeyFilePath are only needed if mutual auth is desired. And if you specify " +
+                    "clientCertChainFilePath you must also specify clientPrivateKeyFilePath");
+            System.exit(0);
+        }
+
+        HelloWorldClientTls client;
+        switch (args.length) {
+            case 2:
+                client = new HelloWorldClientTls(args[0], Integer.parseInt(args[1]),
+                        buildSslContext(null, null, null));
+                break;
+            case 3:
+                client = new HelloWorldClientTls(args[0], Integer.parseInt(args[1]),
+                        buildSslContext(args[2], null, null));
+                break;
+            default:
+                client = new HelloWorldClientTls(args[0], Integer.parseInt(args[1]),
+                        buildSslContext(args[2], args[3], args[4]));
+        }
+
+        try {
+            /* Access a service running on the local machine on port 50051 */
+            String user = "world";
+            if (args.length > 0) {
+                user = args[0]; /* Use the arg as the name to greet if provided */
+            }
+            client.greet(user);
+        } finally {
+            client.shutdown();
+        }
+    }
+}
diff --git a/examples/src/main/java/io/grpc/examples/helloworldtls/HelloWorldServerTls.java b/examples/src/main/java/io/grpc/examples/helloworldtls/HelloWorldServerTls.java
new file mode 100644
index 0000000..58e1ed0
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/helloworldtls/HelloWorldServerTls.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.helloworldtls;
+
+import io.grpc.Server;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.NettyServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.logging.Logger;
+
+/**
+ * Server that manages startup/shutdown of a {@code Greeter} server with TLS enabled.
+ */
+public class HelloWorldServerTls {
+    private static final Logger logger = Logger.getLogger(HelloWorldServerTls.class.getName());
+
+    private Server server;
+
+    private final String host;
+    private final int port;
+    private final String certChainFilePath;
+    private final String privateKeyFilePath;
+    private final String trustCertCollectionFilePath;
+
+    public HelloWorldServerTls(String host,
+                               int port,
+                               String certChainFilePath,
+                               String privateKeyFilePath,
+                               String trustCertCollectionFilePath) {
+        this.host = host;
+        this.port = port;
+        this.certChainFilePath = certChainFilePath;
+        this.privateKeyFilePath = privateKeyFilePath;
+        this.trustCertCollectionFilePath = trustCertCollectionFilePath;
+    }
+
+    private SslContextBuilder getSslContextBuilder() {
+        SslContextBuilder sslClientContextBuilder = SslContextBuilder.forServer(new File(certChainFilePath),
+                new File(privateKeyFilePath));
+        if (trustCertCollectionFilePath != null) {
+            sslClientContextBuilder.trustManager(new File(trustCertCollectionFilePath));
+            sslClientContextBuilder.clientAuth(ClientAuth.REQUIRE);
+        }
+        return GrpcSslContexts.configure(sslClientContextBuilder,
+                SslProvider.OPENSSL);
+    }
+
+    private void start() throws IOException {
+        server = NettyServerBuilder.forAddress(new InetSocketAddress(host, port))
+                .addService(new GreeterImpl())
+                .sslContext(getSslContextBuilder().build())
+                .build()
+                .start();
+        logger.info("Server started, listening on " + port);
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                // Use stderr here since the logger may have been reset by its JVM shutdown hook.
+                System.err.println("*** shutting down gRPC server since JVM is shutting down");
+                HelloWorldServerTls.this.stop();
+                System.err.println("*** server shut down");
+            }
+        });
+    }
+
+    private void stop() {
+        if (server != null) {
+            server.shutdown();
+        }
+    }
+
+    /**
+     * Await termination on the main thread since the grpc library uses daemon threads.
+     */
+    private void blockUntilShutdown() throws InterruptedException {
+        if (server != null) {
+            server.awaitTermination();
+        }
+    }
+
+    /**
+     * Main launches the server from the command line.
+     */
+    public static void main(String[] args) throws IOException, InterruptedException {
+
+        if (args.length < 4 || args.length > 5) {
+            System.out.println(
+                    "USAGE: HelloWorldServerTls host port certChainFilePath privateKeyFilePath " +
+                    "[trustCertCollectionFilePath]\n  Note: You only need to supply trustCertCollectionFilePath if you want " +
+                    "to enable Mutual TLS.");
+            System.exit(0);
+        }
+
+        final HelloWorldServerTls server = new HelloWorldServerTls(args[0],
+                Integer.parseInt(args[1]),
+                args[2],
+                args[3],
+                args.length == 5 ? args[4] : null);
+        server.start();
+        server.blockUntilShutdown();
+    }
+
+    static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
+
+        @Override
+        public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
+            HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
+            responseObserver.onNext(reply);
+            responseObserver.onCompleted();
+        }
+    }
+}
diff --git a/examples/src/main/java/io/grpc/examples/manualflowcontrol/ManualFlowControlClient.java b/examples/src/main/java/io/grpc/examples/manualflowcontrol/ManualFlowControlClient.java
new file mode 100644
index 0000000..60c799e
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/manualflowcontrol/ManualFlowControlClient.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.manualflowcontrol;
+
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.stub.ClientCallStreamObserver;
+import io.grpc.stub.ClientResponseObserver;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+public class ManualFlowControlClient {
+    private static final Logger logger =
+        Logger.getLogger(ManualFlowControlClient.class.getName());
+
+  public static void main(String[] args) throws InterruptedException {
+    final CountDownLatch done = new CountDownLatch(1);
+
+    // Create a channel and a stub
+    ManagedChannel channel = ManagedChannelBuilder
+        .forAddress("localhost", 50051)
+        .usePlaintext()
+        .build();
+    StreamingGreeterGrpc.StreamingGreeterStub stub = StreamingGreeterGrpc.newStub(channel);
+
+    // When using manual flow-control and back-pressure on the client, the ClientResponseObserver handles both
+    // request and response streams.
+    ClientResponseObserver<HelloRequest, HelloReply> clientResponseObserver =
+        new ClientResponseObserver<HelloRequest, HelloReply>() {
+
+          ClientCallStreamObserver<HelloRequest> requestStream;
+
+          @Override
+          public void beforeStart(final ClientCallStreamObserver<HelloRequest> requestStream) {
+            this.requestStream = requestStream;
+            // Set up manual flow control for the response stream. It feels backwards to configure the response
+            // stream's flow control using the request stream's observer, but this is the way it is.
+            requestStream.disableAutoInboundFlowControl();
+
+            // Set up a back-pressure-aware producer for the request stream. The onReadyHandler will be invoked
+            // when the consuming side has enough buffer space to receive more messages.
+            //
+            // Messages are serialized into a transport-specific transmit buffer. Depending on the size of this buffer,
+            // MANY messages may be buffered, however, they haven't yet been sent to the server. The server must call
+            // request() to pull a buffered message from the client.
+            //
+            // Note: the onReadyHandler's invocation is serialized on the same thread pool as the incoming
+            // StreamObserver'sonNext(), onError(), and onComplete() handlers. Blocking the onReadyHandler will prevent
+            // additional messages from being processed by the incoming StreamObserver. The onReadyHandler must return
+            // in a timely manor or else message processing throughput will suffer.
+            requestStream.setOnReadyHandler(new Runnable() {
+              // An iterator is used so we can pause and resume iteration of the request data.
+              Iterator<String> iterator = names().iterator();
+
+              @Override
+              public void run() {
+                // Start generating values from where we left off on a non-gRPC thread.
+                while (requestStream.isReady()) {
+                  if (iterator.hasNext()) {
+                      // Send more messages if there are more messages to send.
+                      String name = iterator.next();
+                      logger.info("--> " + name);
+                      HelloRequest request = HelloRequest.newBuilder().setName(name).build();
+                      requestStream.onNext(request);
+                  } else {
+                      // Signal completion if there is nothing left to send.
+                      requestStream.onCompleted();
+                  }
+                }
+              }
+            });
+          }
+
+          @Override
+          public void onNext(HelloReply value) {
+            logger.info("<-- " + value.getMessage());
+            // Signal the sender to send one message.
+            requestStream.request(1);
+          }
+
+          @Override
+          public void onError(Throwable t) {
+            t.printStackTrace();
+            done.countDown();
+          }
+
+          @Override
+          public void onCompleted() {
+            logger.info("All Done");
+            done.countDown();
+          }
+        };
+
+    // Note: clientResponseObserver is handling both request and response stream processing.
+    stub.sayHelloStreaming(clientResponseObserver);
+
+    done.await();
+
+    channel.shutdown();
+    channel.awaitTermination(1, TimeUnit.SECONDS);
+  }
+
+  private static List<String> names() {
+    return Arrays.asList(
+        "Sophia",
+        "Jackson",
+        "Emma",
+        "Aiden",
+        "Olivia",
+        "Lucas",
+        "Ava",
+        "Liam",
+        "Mia",
+        "Noah",
+        "Isabella",
+        "Ethan",
+        "Riley",
+        "Mason",
+        "Aria",
+        "Caden",
+        "Zoe",
+        "Oliver",
+        "Charlotte",
+        "Elijah",
+        "Lily",
+        "Grayson",
+        "Layla",
+        "Jacob",
+        "Amelia",
+        "Michael",
+        "Emily",
+        "Benjamin",
+        "Madelyn",
+        "Carter",
+        "Aubrey",
+        "James",
+        "Adalyn",
+        "Jayden",
+        "Madison",
+        "Logan",
+        "Chloe",
+        "Alexander",
+        "Harper",
+        "Caleb",
+        "Abigail",
+        "Ryan",
+        "Aaliyah",
+        "Luke",
+        "Avery",
+        "Daniel",
+        "Evelyn",
+        "Jack",
+        "Kaylee",
+        "William",
+        "Ella",
+        "Owen",
+        "Ellie",
+        "Gabriel",
+        "Scarlett",
+        "Matthew",
+        "Arianna",
+        "Connor",
+        "Hailey",
+        "Jayce",
+        "Nora",
+        "Isaac",
+        "Addison",
+        "Sebastian",
+        "Brooklyn",
+        "Henry",
+        "Hannah",
+        "Muhammad",
+        "Mila",
+        "Cameron",
+        "Leah",
+        "Wyatt",
+        "Elizabeth",
+        "Dylan",
+        "Sarah",
+        "Nathan",
+        "Eliana",
+        "Nicholas",
+        "Mackenzie",
+        "Julian",
+        "Peyton",
+        "Eli",
+        "Maria",
+        "Levi",
+        "Grace",
+        "Isaiah",
+        "Adeline",
+        "Landon",
+        "Elena",
+        "David",
+        "Anna",
+        "Christian",
+        "Victoria",
+        "Andrew",
+        "Camilla",
+        "Brayden",
+        "Lillian",
+        "John",
+        "Natalie",
+        "Lincoln"
+    );
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/manualflowcontrol/ManualFlowControlServer.java b/examples/src/main/java/io/grpc/examples/manualflowcontrol/ManualFlowControlServer.java
new file mode 100644
index 0000000..3d12fc5
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/manualflowcontrol/ManualFlowControlServer.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.manualflowcontrol;
+
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.Status;
+import io.grpc.stub.ServerCallStreamObserver;
+import io.grpc.stub.StreamObserver;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+public class ManualFlowControlServer {
+  private static final Logger logger =
+      Logger.getLogger(ManualFlowControlServer.class.getName());
+
+  public static void main(String[] args) throws InterruptedException, IOException {
+    // Service class implementation
+    StreamingGreeterGrpc.StreamingGreeterImplBase svc = new StreamingGreeterGrpc.StreamingGreeterImplBase() {
+      @Override
+      public StreamObserver<HelloRequest> sayHelloStreaming(final StreamObserver<HelloReply> responseObserver) {
+        // Set up manual flow control for the request stream. It feels backwards to configure the request
+        // stream's flow control using the response stream's observer, but this is the way it is.
+        final ServerCallStreamObserver<HelloReply> serverCallStreamObserver =
+            (ServerCallStreamObserver<HelloReply>) responseObserver;
+        serverCallStreamObserver.disableAutoInboundFlowControl();
+
+        // Guard against spurious onReady() calls caused by a race between onNext() and onReady(). If the transport
+        // toggles isReady() from false to true while onNext() is executing, but before onNext() checks isReady(),
+        // request(1) would be called twice - once by onNext() and once by the onReady() scheduled during onNext()'s
+        // execution.
+        final AtomicBoolean wasReady = new AtomicBoolean(false);
+
+        // Set up a back-pressure-aware consumer for the request stream. The onReadyHandler will be invoked
+        // when the consuming side has enough buffer space to receive more messages.
+        //
+        // Note: the onReadyHandler's invocation is serialized on the same thread pool as the incoming StreamObserver's
+        // onNext(), onError(), and onComplete() handlers. Blocking the onReadyHandler will prevent additional messages
+        // from being processed by the incoming StreamObserver. The onReadyHandler must return in a timely manor or else
+        // message processing throughput will suffer.
+        serverCallStreamObserver.setOnReadyHandler(new Runnable() {
+          public void run() {
+            if (serverCallStreamObserver.isReady() && wasReady.compareAndSet(false, true)) {
+              logger.info("READY");
+              // Signal the request sender to send one message. This happens when isReady() turns true, signaling that
+              // the receive buffer has enough free space to receive more messages. Calling request() serves to prime
+              // the message pump.
+              serverCallStreamObserver.request(1);
+            }
+          }
+        });
+
+        // Give gRPC a StreamObserver that can observe and process incoming requests.
+        return new StreamObserver<HelloRequest>() {
+          @Override
+          public void onNext(HelloRequest request) {
+            // Process the request and send a response or an error.
+            try {
+              // Accept and enqueue the request.
+              String name = request.getName();
+              logger.info("--> " + name);
+
+              // Simulate server "work"
+              Thread.sleep(100);
+
+              // Send a response.
+              String message = "Hello " + name;
+              logger.info("<-- " + message);
+              HelloReply reply = HelloReply.newBuilder().setMessage(message).build();
+              responseObserver.onNext(reply);
+
+              // Check the provided ServerCallStreamObserver to see if it is still ready to accept more messages.
+              if (serverCallStreamObserver.isReady()) {
+                // Signal the sender to send another request. As long as isReady() stays true, the server will keep
+                // cycling through the loop of onNext() -> request()...onNext() -> request()... until either the client
+                // runs out of messages and ends the loop or the server runs out of receive buffer space.
+                //
+                // If the server runs out of buffer space, isReady() will turn false. When the receive buffer has
+                // sufficiently drained, isReady() will turn true, and the serverCallStreamObserver's onReadyHandler
+                // will be called to restart the message pump.
+                serverCallStreamObserver.request(1);
+              } else {
+                // If not, note that back-pressure has begun.
+                wasReady.set(false);
+              }
+            } catch (Throwable throwable) {
+              throwable.printStackTrace();
+              responseObserver.onError(
+                  Status.UNKNOWN.withDescription("Error handling request").withCause(throwable).asException());
+            }
+          }
+
+          @Override
+          public void onError(Throwable t) {
+            // End the response stream if the client presents an error.
+            t.printStackTrace();
+            responseObserver.onCompleted();
+          }
+
+          @Override
+          public void onCompleted() {
+            // Signal the end of work when the client ends the request stream.
+            logger.info("COMPLETED");
+            responseObserver.onCompleted();
+          }
+        };
+      }
+    };
+
+    final Server server = ServerBuilder
+        .forPort(50051)
+        .addService(svc)
+        .build()
+        .start();
+
+    logger.info("Listening on " + server.getPort());
+
+    Runtime.getRuntime().addShutdownHook(new Thread() {
+      @Override
+      public void run() {
+        logger.info("Shutting down");
+        server.shutdown();
+      }
+    });
+    server.awaitTermination();
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideClient.java b/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideClient.java
new file mode 100644
index 0000000..6e22ba5
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideClient.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.routeguide;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.protobuf.Message;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.examples.routeguide.RouteGuideGrpc.RouteGuideBlockingStub;
+import io.grpc.examples.routeguide.RouteGuideGrpc.RouteGuideStub;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Sample client code that makes gRPC calls to the server.
+ */
+public class RouteGuideClient {
+  private static final Logger logger = Logger.getLogger(RouteGuideClient.class.getName());
+
+  private final ManagedChannel channel;
+  private final RouteGuideBlockingStub blockingStub;
+  private final RouteGuideStub asyncStub;
+
+  private Random random = new Random();
+  private TestHelper testHelper;
+
+  /** Construct client for accessing RouteGuide server at {@code host:port}. */
+  public RouteGuideClient(String host, int port) {
+    this(ManagedChannelBuilder.forAddress(host, port).usePlaintext());
+  }
+
+  /** Construct client for accessing RouteGuide server using the existing channel. */
+  public RouteGuideClient(ManagedChannelBuilder<?> channelBuilder) {
+    channel = channelBuilder.build();
+    blockingStub = RouteGuideGrpc.newBlockingStub(channel);
+    asyncStub = RouteGuideGrpc.newStub(channel);
+  }
+
+  public void shutdown() throws InterruptedException {
+    channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
+  }
+
+  /**
+   * Blocking unary call example.  Calls getFeature and prints the response.
+   */
+  public void getFeature(int lat, int lon) {
+    info("*** GetFeature: lat={0} lon={1}", lat, lon);
+
+    Point request = Point.newBuilder().setLatitude(lat).setLongitude(lon).build();
+
+    Feature feature;
+    try {
+      feature = blockingStub.getFeature(request);
+      if (testHelper != null) {
+        testHelper.onMessage(feature);
+      }
+    } catch (StatusRuntimeException e) {
+      warning("RPC failed: {0}", e.getStatus());
+      if (testHelper != null) {
+        testHelper.onRpcError(e);
+      }
+      return;
+    }
+    if (RouteGuideUtil.exists(feature)) {
+      info("Found feature called \"{0}\" at {1}, {2}",
+          feature.getName(),
+          RouteGuideUtil.getLatitude(feature.getLocation()),
+          RouteGuideUtil.getLongitude(feature.getLocation()));
+    } else {
+      info("Found no feature at {0}, {1}",
+          RouteGuideUtil.getLatitude(feature.getLocation()),
+          RouteGuideUtil.getLongitude(feature.getLocation()));
+    }
+  }
+
+  /**
+   * Blocking server-streaming example. Calls listFeatures with a rectangle of interest. Prints each
+   * response feature as it arrives.
+   */
+  public void listFeatures(int lowLat, int lowLon, int hiLat, int hiLon) {
+    info("*** ListFeatures: lowLat={0} lowLon={1} hiLat={2} hiLon={3}", lowLat, lowLon, hiLat,
+        hiLon);
+
+    Rectangle request =
+        Rectangle.newBuilder()
+            .setLo(Point.newBuilder().setLatitude(lowLat).setLongitude(lowLon).build())
+            .setHi(Point.newBuilder().setLatitude(hiLat).setLongitude(hiLon).build()).build();
+    Iterator<Feature> features;
+    try {
+      features = blockingStub.listFeatures(request);
+      for (int i = 1; features.hasNext(); i++) {
+        Feature feature = features.next();
+        info("Result #" + i + ": {0}", feature);
+        if (testHelper != null) {
+          testHelper.onMessage(feature);
+        }
+      }
+    } catch (StatusRuntimeException e) {
+      warning("RPC failed: {0}", e.getStatus());
+      if (testHelper != null) {
+        testHelper.onRpcError(e);
+      }
+    }
+  }
+
+  /**
+   * Async client-streaming example. Sends {@code numPoints} randomly chosen points from {@code
+   * features} with a variable delay in between. Prints the statistics when they are sent from the
+   * server.
+   */
+  public void recordRoute(List<Feature> features, int numPoints) throws InterruptedException {
+    info("*** RecordRoute");
+    final CountDownLatch finishLatch = new CountDownLatch(1);
+    StreamObserver<RouteSummary> responseObserver = new StreamObserver<RouteSummary>() {
+      @Override
+      public void onNext(RouteSummary summary) {
+        info("Finished trip with {0} points. Passed {1} features. "
+            + "Travelled {2} meters. It took {3} seconds.", summary.getPointCount(),
+            summary.getFeatureCount(), summary.getDistance(), summary.getElapsedTime());
+        if (testHelper != null) {
+          testHelper.onMessage(summary);
+        }
+      }
+
+      @Override
+      public void onError(Throwable t) {
+        warning("RecordRoute Failed: {0}", Status.fromThrowable(t));
+        if (testHelper != null) {
+          testHelper.onRpcError(t);
+        }
+        finishLatch.countDown();
+      }
+
+      @Override
+      public void onCompleted() {
+        info("Finished RecordRoute");
+        finishLatch.countDown();
+      }
+    };
+
+    StreamObserver<Point> requestObserver = asyncStub.recordRoute(responseObserver);
+    try {
+      // Send numPoints points randomly selected from the features list.
+      for (int i = 0; i < numPoints; ++i) {
+        int index = random.nextInt(features.size());
+        Point point = features.get(index).getLocation();
+        info("Visiting point {0}, {1}", RouteGuideUtil.getLatitude(point),
+            RouteGuideUtil.getLongitude(point));
+        requestObserver.onNext(point);
+        // Sleep for a bit before sending the next one.
+        Thread.sleep(random.nextInt(1000) + 500);
+        if (finishLatch.getCount() == 0) {
+          // RPC completed or errored before we finished sending.
+          // Sending further requests won't error, but they will just be thrown away.
+          return;
+        }
+      }
+    } catch (RuntimeException e) {
+      // Cancel RPC
+      requestObserver.onError(e);
+      throw e;
+    }
+    // Mark the end of requests
+    requestObserver.onCompleted();
+
+    // Receiving happens asynchronously
+    if (!finishLatch.await(1, TimeUnit.MINUTES)) {
+      warning("recordRoute can not finish within 1 minutes");
+    }
+  }
+
+  /**
+   * Bi-directional example, which can only be asynchronous. Send some chat messages, and print any
+   * chat messages that are sent from the server.
+   */
+  public CountDownLatch routeChat() {
+    info("*** RouteChat");
+    final CountDownLatch finishLatch = new CountDownLatch(1);
+    StreamObserver<RouteNote> requestObserver =
+        asyncStub.routeChat(new StreamObserver<RouteNote>() {
+          @Override
+          public void onNext(RouteNote note) {
+            info("Got message \"{0}\" at {1}, {2}", note.getMessage(), note.getLocation()
+                .getLatitude(), note.getLocation().getLongitude());
+            if (testHelper != null) {
+              testHelper.onMessage(note);
+            }
+          }
+
+          @Override
+          public void onError(Throwable t) {
+            warning("RouteChat Failed: {0}", Status.fromThrowable(t));
+            if (testHelper != null) {
+              testHelper.onRpcError(t);
+            }
+            finishLatch.countDown();
+          }
+
+          @Override
+          public void onCompleted() {
+            info("Finished RouteChat");
+            finishLatch.countDown();
+          }
+        });
+
+    try {
+      RouteNote[] requests =
+          {newNote("First message", 0, 0), newNote("Second message", 0, 1),
+              newNote("Third message", 1, 0), newNote("Fourth message", 1, 1)};
+
+      for (RouteNote request : requests) {
+        info("Sending message \"{0}\" at {1}, {2}", request.getMessage(), request.getLocation()
+            .getLatitude(), request.getLocation().getLongitude());
+        requestObserver.onNext(request);
+      }
+    } catch (RuntimeException e) {
+      // Cancel RPC
+      requestObserver.onError(e);
+      throw e;
+    }
+    // Mark the end of requests
+    requestObserver.onCompleted();
+
+    // return the latch while receiving happens asynchronously
+    return finishLatch;
+  }
+
+  /** Issues several different requests and then exits. */
+  public static void main(String[] args) throws InterruptedException {
+    List<Feature> features;
+    try {
+      features = RouteGuideUtil.parseFeatures(RouteGuideUtil.getDefaultFeaturesFile());
+    } catch (IOException ex) {
+      ex.printStackTrace();
+      return;
+    }
+
+    RouteGuideClient client = new RouteGuideClient("localhost", 8980);
+    try {
+      // Looking for a valid feature
+      client.getFeature(409146138, -746188906);
+
+      // Feature missing.
+      client.getFeature(0, 0);
+
+      // Looking for features between 40, -75 and 42, -73.
+      client.listFeatures(400000000, -750000000, 420000000, -730000000);
+
+      // Record a few randomly selected points from the features file.
+      client.recordRoute(features, 10);
+
+      // Send and receive some notes.
+      CountDownLatch finishLatch = client.routeChat();
+
+      if (!finishLatch.await(1, TimeUnit.MINUTES)) {
+        client.warning("routeChat can not finish within 1 minutes");
+      }
+    } finally {
+      client.shutdown();
+    }
+  }
+
+  private void info(String msg, Object... params) {
+    logger.log(Level.INFO, msg, params);
+  }
+
+  private void warning(String msg, Object... params) {
+    logger.log(Level.WARNING, msg, params);
+  }
+
+  private RouteNote newNote(String message, int lat, int lon) {
+    return RouteNote.newBuilder().setMessage(message)
+        .setLocation(Point.newBuilder().setLatitude(lat).setLongitude(lon).build()).build();
+  }
+
+  /**
+   * Only used for unit test, as we do not want to introduce randomness in unit test.
+   */
+  @VisibleForTesting
+  void setRandom(Random random) {
+    this.random = random;
+  }
+
+  /**
+   * Only used for helping unit test.
+   */
+  @VisibleForTesting
+  interface TestHelper {
+    /**
+     * Used for verify/inspect message received from server.
+     */
+    void onMessage(Message message);
+
+    /**
+     * Used for verify/inspect error received from server.
+     */
+    void onRpcError(Throwable exception);
+  }
+
+  @VisibleForTesting
+  void setTestHelper(TestHelper testHelper) {
+    this.testHelper = testHelper;
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java b/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java
new file mode 100644
index 0000000..36f12e6
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideServer.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.routeguide;
+
+import static java.lang.Math.atan2;
+import static java.lang.Math.cos;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import static java.lang.Math.sin;
+import static java.lang.Math.sqrt;
+import static java.lang.Math.toRadians;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A sample gRPC server that serve the RouteGuide (see route_guide.proto) service.
+ */
+public class RouteGuideServer {
+  private static final Logger logger = Logger.getLogger(RouteGuideServer.class.getName());
+
+  private final int port;
+  private final Server server;
+
+  public RouteGuideServer(int port) throws IOException {
+    this(port, RouteGuideUtil.getDefaultFeaturesFile());
+  }
+
+  /** Create a RouteGuide server listening on {@code port} using {@code featureFile} database. */
+  public RouteGuideServer(int port, URL featureFile) throws IOException {
+    this(ServerBuilder.forPort(port), port, RouteGuideUtil.parseFeatures(featureFile));
+  }
+
+  /** Create a RouteGuide server using serverBuilder as a base and features as data. */
+  public RouteGuideServer(ServerBuilder<?> serverBuilder, int port, Collection<Feature> features) {
+    this.port = port;
+    server = serverBuilder.addService(new RouteGuideService(features))
+        .build();
+  }
+
+  /** Start serving requests. */
+  public void start() throws IOException {
+    server.start();
+    logger.info("Server started, listening on " + port);
+    Runtime.getRuntime().addShutdownHook(new Thread() {
+      @Override
+      public void run() {
+        // Use stderr here since the logger may has been reset by its JVM shutdown hook.
+        System.err.println("*** shutting down gRPC server since JVM is shutting down");
+        RouteGuideServer.this.stop();
+        System.err.println("*** server shut down");
+      }
+    });
+  }
+
+  /** Stop serving requests and shutdown resources. */
+  public void stop() {
+    if (server != null) {
+      server.shutdown();
+    }
+  }
+
+  /**
+   * Await termination on the main thread since the grpc library uses daemon threads.
+   */
+  private void blockUntilShutdown() throws InterruptedException {
+    if (server != null) {
+      server.awaitTermination();
+    }
+  }
+
+  /**
+   * Main method.  This comment makes the linter happy.
+   */
+  public static void main(String[] args) throws Exception {
+    RouteGuideServer server = new RouteGuideServer(8980);
+    server.start();
+    server.blockUntilShutdown();
+  }
+
+  /**
+   * Our implementation of RouteGuide service.
+   *
+   * <p>See route_guide.proto for details of the methods.
+   */
+  private static class RouteGuideService extends RouteGuideGrpc.RouteGuideImplBase {
+    private final Collection<Feature> features;
+    private final ConcurrentMap<Point, List<RouteNote>> routeNotes =
+        new ConcurrentHashMap<Point, List<RouteNote>>();
+
+    RouteGuideService(Collection<Feature> features) {
+      this.features = features;
+    }
+
+    /**
+     * Gets the {@link Feature} at the requested {@link Point}. If no feature at that location
+     * exists, an unnamed feature is returned at the provided location.
+     *
+     * @param request the requested location for the feature.
+     * @param responseObserver the observer that will receive the feature at the requested point.
+     */
+    @Override
+    public void getFeature(Point request, StreamObserver<Feature> responseObserver) {
+      responseObserver.onNext(checkFeature(request));
+      responseObserver.onCompleted();
+    }
+
+    /**
+     * Gets all features contained within the given bounding {@link Rectangle}.
+     *
+     * @param request the bounding rectangle for the requested features.
+     * @param responseObserver the observer that will receive the features.
+     */
+    @Override
+    public void listFeatures(Rectangle request, StreamObserver<Feature> responseObserver) {
+      int left = min(request.getLo().getLongitude(), request.getHi().getLongitude());
+      int right = max(request.getLo().getLongitude(), request.getHi().getLongitude());
+      int top = max(request.getLo().getLatitude(), request.getHi().getLatitude());
+      int bottom = min(request.getLo().getLatitude(), request.getHi().getLatitude());
+
+      for (Feature feature : features) {
+        if (!RouteGuideUtil.exists(feature)) {
+          continue;
+        }
+
+        int lat = feature.getLocation().getLatitude();
+        int lon = feature.getLocation().getLongitude();
+        if (lon >= left && lon <= right && lat >= bottom && lat <= top) {
+          responseObserver.onNext(feature);
+        }
+      }
+      responseObserver.onCompleted();
+    }
+
+    /**
+     * Gets a stream of points, and responds with statistics about the "trip": number of points,
+     * number of known features visited, total distance traveled, and total time spent.
+     *
+     * @param responseObserver an observer to receive the response summary.
+     * @return an observer to receive the requested route points.
+     */
+    @Override
+    public StreamObserver<Point> recordRoute(final StreamObserver<RouteSummary> responseObserver) {
+      return new StreamObserver<Point>() {
+        int pointCount;
+        int featureCount;
+        int distance;
+        Point previous;
+        final long startTime = System.nanoTime();
+
+        @Override
+        public void onNext(Point point) {
+          pointCount++;
+          if (RouteGuideUtil.exists(checkFeature(point))) {
+            featureCount++;
+          }
+          // For each point after the first, add the incremental distance from the previous point to
+          // the total distance value.
+          if (previous != null) {
+            distance += calcDistance(previous, point);
+          }
+          previous = point;
+        }
+
+        @Override
+        public void onError(Throwable t) {
+          logger.log(Level.WARNING, "recordRoute cancelled");
+        }
+
+        @Override
+        public void onCompleted() {
+          long seconds = NANOSECONDS.toSeconds(System.nanoTime() - startTime);
+          responseObserver.onNext(RouteSummary.newBuilder().setPointCount(pointCount)
+              .setFeatureCount(featureCount).setDistance(distance)
+              .setElapsedTime((int) seconds).build());
+          responseObserver.onCompleted();
+        }
+      };
+    }
+
+    /**
+     * Receives a stream of message/location pairs, and responds with a stream of all previous
+     * messages at each of those locations.
+     *
+     * @param responseObserver an observer to receive the stream of previous messages.
+     * @return an observer to handle requested message/location pairs.
+     */
+    @Override
+    public StreamObserver<RouteNote> routeChat(final StreamObserver<RouteNote> responseObserver) {
+      return new StreamObserver<RouteNote>() {
+        @Override
+        public void onNext(RouteNote note) {
+          List<RouteNote> notes = getOrCreateNotes(note.getLocation());
+
+          // Respond with all previous notes at this location.
+          for (RouteNote prevNote : notes.toArray(new RouteNote[0])) {
+            responseObserver.onNext(prevNote);
+          }
+
+          // Now add the new note to the list
+          notes.add(note);
+        }
+
+        @Override
+        public void onError(Throwable t) {
+          logger.log(Level.WARNING, "routeChat cancelled");
+        }
+
+        @Override
+        public void onCompleted() {
+          responseObserver.onCompleted();
+        }
+      };
+    }
+
+    /**
+     * Get the notes list for the given location. If missing, create it.
+     */
+    private List<RouteNote> getOrCreateNotes(Point location) {
+      List<RouteNote> notes = Collections.synchronizedList(new ArrayList<RouteNote>());
+      List<RouteNote> prevNotes = routeNotes.putIfAbsent(location, notes);
+      return prevNotes != null ? prevNotes : notes;
+    }
+
+    /**
+     * Gets the feature at the given point.
+     *
+     * @param location the location to check.
+     * @return The feature object at the point. Note that an empty name indicates no feature.
+     */
+    private Feature checkFeature(Point location) {
+      for (Feature feature : features) {
+        if (feature.getLocation().getLatitude() == location.getLatitude()
+            && feature.getLocation().getLongitude() == location.getLongitude()) {
+          return feature;
+        }
+      }
+
+      // No feature was found, return an unnamed feature.
+      return Feature.newBuilder().setName("").setLocation(location).build();
+    }
+
+    /**
+     * Calculate the distance between two points using the "haversine" formula.
+     * The formula is based on http://mathforum.org/library/drmath/view/51879.html.
+     *
+     * @param start The starting point
+     * @param end The end point
+     * @return The distance between the points in meters
+     */
+    private static int calcDistance(Point start, Point end) {
+      int r = 6371000; // earth radius in meters
+      double lat1 = toRadians(RouteGuideUtil.getLatitude(start));
+      double lat2 = toRadians(RouteGuideUtil.getLatitude(end));
+      double lon1 = toRadians(RouteGuideUtil.getLongitude(start));
+      double lon2 = toRadians(RouteGuideUtil.getLongitude(end));
+      double deltaLat = lat2 - lat1;
+      double deltaLon = lon2 - lon1;
+
+      double a = sin(deltaLat / 2) * sin(deltaLat / 2)
+          + cos(lat1) * cos(lat2) * sin(deltaLon / 2) * sin(deltaLon / 2);
+      double c = 2 * atan2(sqrt(a), sqrt(1 - a));
+
+      return (int) (r * c);
+    }
+  }
+}
diff --git a/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java b/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java
new file mode 100644
index 0000000..6e49492
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/routeguide/RouteGuideUtil.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.routeguide;
+
+import com.google.protobuf.util.JsonFormat;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.List;
+
+/**
+ * Common utilities for the RouteGuide demo.
+ */
+public class RouteGuideUtil {
+  private static final double COORD_FACTOR = 1e7;
+
+  /**
+   * Gets the latitude for the given point.
+   */
+  public static double getLatitude(Point location) {
+    return location.getLatitude() / COORD_FACTOR;
+  }
+
+  /**
+   * Gets the longitude for the given point.
+   */
+  public static double getLongitude(Point location) {
+    return location.getLongitude() / COORD_FACTOR;
+  }
+
+  /**
+   * Gets the default features file from classpath.
+   */
+  public static URL getDefaultFeaturesFile() {
+    return RouteGuideServer.class.getResource("route_guide_db.json");
+  }
+
+  /**
+   * Parses the JSON input file containing the list of features.
+   */
+  public static List<Feature> parseFeatures(URL file) throws IOException {
+    InputStream input = file.openStream();
+    try {
+      Reader reader = new InputStreamReader(input, Charset.forName("UTF-8"));
+      try {
+        FeatureDatabase.Builder database = FeatureDatabase.newBuilder();
+        JsonFormat.parser().merge(reader, database);
+        return database.getFeatureList();
+      } finally {
+        reader.close();
+      }
+    } finally {
+      input.close();
+    }
+  }
+
+  /**
+   * Indicates whether the given feature exists (i.e. has a valid name).
+   */
+  public static boolean exists(Feature feature) {
+    return feature != null && !feature.getName().isEmpty();
+  }
+}
diff --git a/examples/src/main/proto/hello_streaming.proto b/examples/src/main/proto/hello_streaming.proto
new file mode 100644
index 0000000..325b909
--- /dev/null
+++ b/examples/src/main/proto/hello_streaming.proto
@@ -0,0 +1,37 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.examples.manualflowcontrol";
+option java_outer_classname = "HelloStreamingProto";
+option objc_class_prefix = "HLWS";
+
+package manualflowcontrol;
+
+// The greeting service definition.
+service StreamingGreeter {
+  // Streams a many greetings
+  rpc SayHelloStreaming (stream HelloRequest) returns (stream HelloReply) {}
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+  string name = 1;
+}
+
+// The response message containing the greetings
+message HelloReply {
+  string message = 1;
+}
diff --git a/examples/src/main/proto/helloworld.proto b/examples/src/main/proto/helloworld.proto
new file mode 100644
index 0000000..c60d941
--- /dev/null
+++ b/examples/src/main/proto/helloworld.proto
@@ -0,0 +1,37 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.examples.helloworld";
+option java_outer_classname = "HelloWorldProto";
+option objc_class_prefix = "HLW";
+
+package helloworld;
+
+// The greeting service definition.
+service Greeter {
+  // Sends a greeting
+  rpc SayHello (HelloRequest) returns (HelloReply) {}
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+  string name = 1;
+}
+
+// The response message containing the greetings
+message HelloReply {
+  string message = 1;
+}
diff --git a/examples/src/main/proto/route_guide.proto b/examples/src/main/proto/route_guide.proto
new file mode 100644
index 0000000..3c179f2
--- /dev/null
+++ b/examples/src/main/proto/route_guide.proto
@@ -0,0 +1,115 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.examples.routeguide";
+option java_outer_classname = "RouteGuideProto";
+option objc_class_prefix = "RTG";
+
+package routeguide;
+
+// Interface exported by the server.
+service RouteGuide {
+  // A simple RPC.
+  //
+  // Obtains the feature at a given position.
+  //
+  // A feature with an empty name is returned if there's no feature at the given
+  // position.
+  rpc GetFeature(Point) returns (Feature) {}
+
+  // A server-to-client streaming RPC.
+  //
+  // Obtains the Features available within the given Rectangle.  Results are
+  // streamed rather than returned at once (e.g. in a response message with a
+  // repeated field), as the rectangle may cover a large area and contain a
+  // huge number of features.
+  rpc ListFeatures(Rectangle) returns (stream Feature) {}
+
+  // A client-to-server streaming RPC.
+  //
+  // Accepts a stream of Points on a route being traversed, returning a
+  // RouteSummary when traversal is completed.
+  rpc RecordRoute(stream Point) returns (RouteSummary) {}
+
+  // A Bidirectional streaming RPC.
+  //
+  // Accepts a stream of RouteNotes sent while a route is being traversed,
+  // while receiving other RouteNotes (e.g. from other users).
+  rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
+}
+
+// Points are represented as latitude-longitude pairs in the E7 representation
+// (degrees multiplied by 10**7 and rounded to the nearest integer).
+// Latitudes should be in the range +/- 90 degrees and longitude should be in
+// the range +/- 180 degrees (inclusive).
+message Point {
+  int32 latitude = 1;
+  int32 longitude = 2;
+}
+
+// A latitude-longitude rectangle, represented as two diagonally opposite
+// points "lo" and "hi".
+message Rectangle {
+  // One corner of the rectangle.
+  Point lo = 1;
+
+  // The other corner of the rectangle.
+  Point hi = 2;
+}
+
+// A feature names something at a given point.
+//
+// If a feature could not be named, the name is empty.
+message Feature {
+  // The name of the feature.
+  string name = 1;
+
+  // The point where the feature is detected.
+  Point location = 2;
+}
+
+// Not used in the RPC.  Instead, this is here for the form serialized to disk.
+message FeatureDatabase {
+  repeated Feature feature = 1;
+}
+
+// A RouteNote is a message sent while at a given point.
+message RouteNote {
+  // The location from which the message is sent.
+  Point location = 1;
+
+  // The message to be sent.
+  string message = 2;
+}
+
+// A RouteSummary is received in response to a RecordRoute rpc.
+//
+// It contains the number of individual points received, the number of
+// detected features, and the total distance covered as the cumulative sum of
+// the distance between each point.
+message RouteSummary {
+  // The number of points received.
+  int32 point_count = 1;
+
+  // The number of known features passed while traversing the route.
+  int32 feature_count = 2;
+
+  // The distance covered in metres.
+  int32 distance = 3;
+
+  // The duration of the traversal in seconds.
+  int32 elapsed_time = 4;
+}
diff --git a/examples/src/main/resources/io/grpc/examples/routeguide/route_guide_db.json b/examples/src/main/resources/io/grpc/examples/routeguide/route_guide_db.json
new file mode 100644
index 0000000..e0ee318
--- /dev/null
+++ b/examples/src/main/resources/io/grpc/examples/routeguide/route_guide_db.json
@@ -0,0 +1,603 @@
+{
+  "feature": [{
+      "location": {
+          "latitude": 407838351,
+          "longitude": -746143763
+      },
+      "name": "Patriots Path, Mendham, NJ 07945, USA"
+  }, {
+      "location": {
+          "latitude": 408122808,
+          "longitude": -743999179
+      },
+      "name": "101 New Jersey 10, Whippany, NJ 07981, USA"
+  }, {
+      "location": {
+          "latitude": 413628156,
+          "longitude": -749015468
+      },
+      "name": "U.S. 6, Shohola, PA 18458, USA"
+  }, {
+      "location": {
+          "latitude": 419999544,
+          "longitude": -740371136
+      },
+      "name": "5 Conners Road, Kingston, NY 12401, USA"
+  }, {
+      "location": {
+          "latitude": 414008389,
+          "longitude": -743951297
+      },
+      "name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA"
+  }, {
+      "location": {
+          "latitude": 419611318,
+          "longitude": -746524769
+      },
+      "name": "287 Flugertown Road, Livingston Manor, NY 12758, USA"
+  }, {
+      "location": {
+          "latitude": 406109563,
+          "longitude": -742186778
+      },
+      "name": "4001 Tremley Point Road, Linden, NJ 07036, USA"
+  }, {
+      "location": {
+          "latitude": 416802456,
+          "longitude": -742370183
+      },
+      "name": "352 South Mountain Road, Wallkill, NY 12589, USA"
+  }, {
+      "location": {
+          "latitude": 412950425,
+          "longitude": -741077389
+      },
+      "name": "Bailey Turn Road, Harriman, NY 10926, USA"
+  }, {
+      "location": {
+          "latitude": 412144655,
+          "longitude": -743949739
+      },
+      "name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA"
+  }, {
+      "location": {
+          "latitude": 415736605,
+          "longitude": -742847522
+      },
+      "name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA"
+  }, {
+      "location": {
+          "latitude": 413843930,
+          "longitude": -740501726
+      },
+      "name": "162 Merrill Road, Highland Mills, NY 10930, USA"
+  }, {
+      "location": {
+          "latitude": 410873075,
+          "longitude": -744459023
+      },
+      "name": "Clinton Road, West Milford, NJ 07480, USA"
+  }, {
+      "location": {
+          "latitude": 412346009,
+          "longitude": -744026814
+      },
+      "name": "16 Old Brook Lane, Warwick, NY 10990, USA"
+  }, {
+      "location": {
+          "latitude": 402948455,
+          "longitude": -747903913
+      },
+      "name": "3 Drake Lane, Pennington, NJ 08534, USA"
+  }, {
+      "location": {
+          "latitude": 406337092,
+          "longitude": -740122226
+      },
+      "name": "6324 8th Avenue, Brooklyn, NY 11220, USA"
+  }, {
+      "location": {
+          "latitude": 406421967,
+          "longitude": -747727624
+      },
+      "name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA"
+  }, {
+      "location": {
+          "latitude": 416318082,
+          "longitude": -749677716
+      },
+      "name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA"
+  }, {
+      "location": {
+          "latitude": 415301720,
+          "longitude": -748416257
+      },
+      "name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA"
+  }, {
+      "location": {
+          "latitude": 402647019,
+          "longitude": -747071791
+      },
+      "name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA"
+  }, {
+      "location": {
+          "latitude": 412567807,
+          "longitude": -741058078
+      },
+      "name": "New York State Reference Route 987E, Southfields, NY 10975, USA"
+  }, {
+      "location": {
+          "latitude": 416855156,
+          "longitude": -744420597
+      },
+      "name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA"
+  }, {
+      "location": {
+          "latitude": 404663628,
+          "longitude": -744820157
+      },
+      "name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA"
+  }, {
+      "location": {
+          "latitude": 407113723,
+          "longitude": -749746483
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 402133926,
+          "longitude": -743613249
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 400273442,
+          "longitude": -741220915
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 411236786,
+          "longitude": -744070769
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 411633782,
+          "longitude": -746784970
+      },
+      "name": "211-225 Plains Road, Augusta, NJ 07822, USA"
+  }, {
+      "location": {
+          "latitude": 415830701,
+          "longitude": -742952812
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 413447164,
+          "longitude": -748712898
+      },
+      "name": "165 Pedersen Ridge Road, Milford, PA 18337, USA"
+  }, {
+      "location": {
+          "latitude": 405047245,
+          "longitude": -749800722
+      },
+      "name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA"
+  }, {
+      "location": {
+          "latitude": 418858923,
+          "longitude": -746156790
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 417951888,
+          "longitude": -748484944
+      },
+      "name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA"
+  }, {
+      "location": {
+          "latitude": 407033786,
+          "longitude": -743977337
+      },
+      "name": "26 East 3rd Street, New Providence, NJ 07974, USA"
+  }, {
+      "location": {
+          "latitude": 417548014,
+          "longitude": -740075041
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 410395868,
+          "longitude": -744972325
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 404615353,
+          "longitude": -745129803
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 406589790,
+          "longitude": -743560121
+      },
+      "name": "611 Lawrence Avenue, Westfield, NJ 07090, USA"
+  }, {
+      "location": {
+          "latitude": 414653148,
+          "longitude": -740477477
+      },
+      "name": "18 Lannis Avenue, New Windsor, NY 12553, USA"
+  }, {
+      "location": {
+          "latitude": 405957808,
+          "longitude": -743255336
+      },
+      "name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA"
+  }, {
+      "location": {
+          "latitude": 411733589,
+          "longitude": -741648093
+      },
+      "name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA"
+  }, {
+      "location": {
+          "latitude": 412676291,
+          "longitude": -742606606
+      },
+      "name": "1270 Lakes Road, Monroe, NY 10950, USA"
+  }, {
+      "location": {
+          "latitude": 409224445,
+          "longitude": -748286738
+      },
+      "name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA"
+  }, {
+      "location": {
+          "latitude": 406523420,
+          "longitude": -742135517
+      },
+      "name": "652 Garden Street, Elizabeth, NJ 07202, USA"
+  }, {
+      "location": {
+          "latitude": 401827388,
+          "longitude": -740294537
+      },
+      "name": "349 Sea Spray Court, Neptune City, NJ 07753, USA"
+  }, {
+      "location": {
+          "latitude": 410564152,
+          "longitude": -743685054
+      },
+      "name": "13-17 Stanley Street, West Milford, NJ 07480, USA"
+  }, {
+      "location": {
+          "latitude": 408472324,
+          "longitude": -740726046
+      },
+      "name": "47 Industrial Avenue, Teterboro, NJ 07608, USA"
+  }, {
+      "location": {
+          "latitude": 412452168,
+          "longitude": -740214052
+      },
+      "name": "5 White Oak Lane, Stony Point, NY 10980, USA"
+  }, {
+      "location": {
+          "latitude": 409146138,
+          "longitude": -746188906
+      },
+      "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA"
+  }, {
+      "location": {
+          "latitude": 404701380,
+          "longitude": -744781745
+      },
+      "name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA"
+  }, {
+      "location": {
+          "latitude": 409642566,
+          "longitude": -746017679
+      },
+      "name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA"
+  }, {
+      "location": {
+          "latitude": 408031728,
+          "longitude": -748645385
+      },
+      "name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA"
+  }, {
+      "location": {
+          "latitude": 413700272,
+          "longitude": -742135189
+      },
+      "name": "367 Prospect Road, Chester, NY 10918, USA"
+  }, {
+      "location": {
+          "latitude": 404310607,
+          "longitude": -740282632
+      },
+      "name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA"
+  }, {
+      "location": {
+          "latitude": 409319800,
+          "longitude": -746201391
+      },
+      "name": "11 Ward Street, Mount Arlington, NJ 07856, USA"
+  }, {
+      "location": {
+          "latitude": 406685311,
+          "longitude": -742108603
+      },
+      "name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA"
+  }, {
+      "location": {
+          "latitude": 419018117,
+          "longitude": -749142781
+      },
+      "name": "43 Dreher Road, Roscoe, NY 12776, USA"
+  }, {
+      "location": {
+          "latitude": 412856162,
+          "longitude": -745148837
+      },
+      "name": "Swan Street, Pine Island, NY 10969, USA"
+  }, {
+      "location": {
+          "latitude": 416560744,
+          "longitude": -746721964
+      },
+      "name": "66 Pleasantview Avenue, Monticello, NY 12701, USA"
+  }, {
+      "location": {
+          "latitude": 405314270,
+          "longitude": -749836354
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 414219548,
+          "longitude": -743327440
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 415534177,
+          "longitude": -742900616
+      },
+      "name": "565 Winding Hills Road, Montgomery, NY 12549, USA"
+  }, {
+      "location": {
+          "latitude": 406898530,
+          "longitude": -749127080
+      },
+      "name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA"
+  }, {
+      "location": {
+          "latitude": 407586880,
+          "longitude": -741670168
+      },
+      "name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA"
+  }, {
+      "location": {
+          "latitude": 400106455,
+          "longitude": -742870190
+      },
+      "name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA"
+  }, {
+      "location": {
+          "latitude": 400066188,
+          "longitude": -746793294
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 418803880,
+          "longitude": -744102673
+      },
+      "name": "40 Mountain Road, Napanoch, NY 12458, USA"
+  }, {
+      "location": {
+          "latitude": 414204288,
+          "longitude": -747895140
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 414777405,
+          "longitude": -740615601
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 415464475,
+          "longitude": -747175374
+      },
+      "name": "48 North Road, Forestburgh, NY 12777, USA"
+  }, {
+      "location": {
+          "latitude": 404062378,
+          "longitude": -746376177
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 405688272,
+          "longitude": -749285130
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 400342070,
+          "longitude": -748788996
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 401809022,
+          "longitude": -744157964
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 404226644,
+          "longitude": -740517141
+      },
+      "name": "9 Thompson Avenue, Leonardo, NJ 07737, USA"
+  }, {
+      "location": {
+          "latitude": 410322033,
+          "longitude": -747871659
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 407100674,
+          "longitude": -747742727
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 418811433,
+          "longitude": -741718005
+      },
+      "name": "213 Bush Road, Stone Ridge, NY 12484, USA"
+  }, {
+      "location": {
+          "latitude": 415034302,
+          "longitude": -743850945
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 411349992,
+          "longitude": -743694161
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 404839914,
+          "longitude": -744759616
+      },
+      "name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA"
+  }, {
+      "location": {
+          "latitude": 414638017,
+          "longitude": -745957854
+      },
+      "name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA"
+  }, {
+      "location": {
+          "latitude": 412127800,
+          "longitude": -740173578
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 401263460,
+          "longitude": -747964303
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 412843391,
+          "longitude": -749086026
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 418512773,
+          "longitude": -743067823
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 404318328,
+          "longitude": -740835638
+      },
+      "name": "42-102 Main Street, Belford, NJ 07718, USA"
+  }, {
+      "location": {
+          "latitude": 419020746,
+          "longitude": -741172328
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 404080723,
+          "longitude": -746119569
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 401012643,
+          "longitude": -744035134
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 404306372,
+          "longitude": -741079661
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 403966326,
+          "longitude": -748519297
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 405002031,
+          "longitude": -748407866
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 409532885,
+          "longitude": -742200683
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 416851321,
+          "longitude": -742674555
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 406411633,
+          "longitude": -741722051
+      },
+      "name": "3387 Richmond Terrace, Staten Island, NY 10303, USA"
+  }, {
+      "location": {
+          "latitude": 413069058,
+          "longitude": -744597778
+      },
+      "name": "261 Van Sickle Road, Goshen, NY 10924, USA"
+  }, {
+      "location": {
+          "latitude": 418465462,
+          "longitude": -746859398
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 411733222,
+          "longitude": -744228360
+      },
+      "name": ""
+  }, {
+      "location": {
+          "latitude": 410248224,
+          "longitude": -747127767
+      },
+      "name": "3 Hasta Way, Newton, NJ 07860, USA"
+  }]
+}
diff --git a/examples/src/test/java/io/grpc/examples/header/HeaderClientInterceptorTest.java b/examples/src/test/java/io/grpc/examples/header/HeaderClientInterceptorTest.java
new file mode 100644
index 0000000..357e6eb
--- /dev/null
+++ b/examples/src/test/java/io/grpc/examples/header/HeaderClientInterceptorTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.header;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.ClientInterceptors;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.ServerCall;
+import io.grpc.ServerCall.Listener;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.ServerInterceptors;
+import io.grpc.StatusRuntimeException;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.GreeterGrpc.GreeterBlockingStub;
+import io.grpc.examples.helloworld.GreeterGrpc.GreeterImplBase;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.testing.GrpcCleanupRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+
+/**
+ * Unit tests for {@link HeaderClientInterceptor}.
+ * For demonstrating how to write gRPC unit test only.
+ * Not intended to provide a high code coverage or to test every major usecase.
+ *
+ * <p>For basic unit test examples see {@link io.grpc.examples.helloworld.HelloWorldClientTest} and
+ * {@link io.grpc.examples.helloworld.HelloWorldServerTest}.
+ */
+@RunWith(JUnit4.class)
+public class HeaderClientInterceptorTest {
+  /**
+   * This rule manages automatic graceful shutdown for the registered servers and channels at the
+   * end of test.
+   */
+  @Rule
+  public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+  private final ServerInterceptor mockServerInterceptor = mock(ServerInterceptor.class, delegatesTo(
+      new ServerInterceptor() {
+        @Override
+        public <ReqT, RespT> Listener<ReqT> interceptCall(
+            ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
+          return next.startCall(call, headers);
+        }
+      }));
+
+  @Test
+  public void clientHeaderDeliveredToServer() throws Exception {
+    // Generate a unique in-process server name.
+    String serverName = InProcessServerBuilder.generateName();
+    // Create a server, add service, start, and register for automatic graceful shutdown.
+    grpcCleanup.register(InProcessServerBuilder.forName(serverName).directExecutor()
+        .addService(ServerInterceptors.intercept(new GreeterImplBase() {}, mockServerInterceptor))
+        .build().start());
+    // Create a client channel and register for automatic graceful shutdown.
+    ManagedChannel channel = grpcCleanup.register(
+        InProcessChannelBuilder.forName(serverName).directExecutor().build());
+    GreeterBlockingStub blockingStub = GreeterGrpc.newBlockingStub(
+        ClientInterceptors.intercept(channel, new HeaderClientInterceptor()));
+    ArgumentCaptor<Metadata> metadataCaptor = ArgumentCaptor.forClass(Metadata.class);
+
+    try {
+      blockingStub.sayHello(HelloRequest.getDefaultInstance());
+      fail();
+    } catch (StatusRuntimeException expected) {
+      // expected because the method is not implemented at server side
+    }
+
+    verify(mockServerInterceptor).interceptCall(
+        Matchers.<ServerCall<HelloRequest, HelloReply>>any(),
+        metadataCaptor.capture(),
+        Matchers.<ServerCallHandler<HelloRequest, HelloReply>>any());
+    assertEquals(
+        "customRequestValue",
+        metadataCaptor.getValue().get(HeaderClientInterceptor.CUSTOM_HEADER_KEY));
+  }
+}
diff --git a/examples/src/test/java/io/grpc/examples/header/HeaderServerInterceptorTest.java b/examples/src/test/java/io/grpc/examples/header/HeaderServerInterceptorTest.java
new file mode 100644
index 0000000..4fc5c29
--- /dev/null
+++ b/examples/src/test/java/io/grpc/examples/header/HeaderServerInterceptorTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.header;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerInterceptors;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.GreeterGrpc.GreeterBlockingStub;
+import io.grpc.examples.helloworld.GreeterGrpc.GreeterImplBase;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.GrpcCleanupRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Unit tests for {@link HeaderClientInterceptor}.
+ * For demonstrating how to write gRPC unit test only.
+ * Not intended to provide a high code coverage or to test every major usecase.
+ *
+ * <p>For basic unit test examples see {@link io.grpc.examples.helloworld.HelloWorldClientTest} and
+ * {@link io.grpc.examples.helloworld.HelloWorldServerTest}.
+ */
+@RunWith(JUnit4.class)
+public class HeaderServerInterceptorTest {
+  /**
+   * This rule manages automatic graceful shutdown for the registered servers and channels at the
+   * end of test.
+   */
+  @Rule
+  public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+  private Channel channel;
+
+  @Before
+  public void setUp() throws Exception {
+    GreeterImplBase greeterImplBase =
+        new GreeterImplBase() {
+          @Override
+          public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
+            responseObserver.onNext(HelloReply.getDefaultInstance());
+            responseObserver.onCompleted();
+          }
+        };
+    // Generate a unique in-process server name.
+    String serverName = InProcessServerBuilder.generateName();
+    // Create a server, add service, start, and register for automatic graceful shutdown.
+    grpcCleanup.register(InProcessServerBuilder.forName(serverName).directExecutor()
+        .addService(ServerInterceptors.intercept(greeterImplBase, new HeaderServerInterceptor()))
+        .build().start());
+    // Create a client channel and register for automatic graceful shutdown.
+    channel =
+        grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build());
+  }
+
+  @Test
+  public void serverHeaderDeliveredToClient() {
+    class SpyingClientInterceptor implements ClientInterceptor {
+      ClientCall.Listener<?> spyListener;
+
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+          MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+        return new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
+          @Override
+          public void start(Listener<RespT> responseListener, Metadata headers) {
+            spyListener = responseListener =
+                mock(ClientCall.Listener.class, delegatesTo(responseListener));
+            super.start(responseListener, headers);
+          }
+        };
+      }
+    }
+
+    SpyingClientInterceptor clientInterceptor = new SpyingClientInterceptor();
+    GreeterBlockingStub blockingStub = GreeterGrpc.newBlockingStub(channel)
+        .withInterceptors(clientInterceptor);
+    ArgumentCaptor<Metadata> metadataCaptor = ArgumentCaptor.forClass(Metadata.class);
+
+    blockingStub.sayHello(HelloRequest.getDefaultInstance());
+
+    assertNotNull(clientInterceptor.spyListener);
+    verify(clientInterceptor.spyListener).onHeaders(metadataCaptor.capture());
+    assertEquals(
+        "customRespondValue",
+        metadataCaptor.getValue().get(HeaderServerInterceptor.CUSTOM_HEADER_KEY));
+  }
+}
diff --git a/examples/src/test/java/io/grpc/examples/helloworld/HelloWorldClientTest.java b/examples/src/test/java/io/grpc/examples/helloworld/HelloWorldClientTest.java
new file mode 100644
index 0000000..14712f7
--- /dev/null
+++ b/examples/src/test/java/io/grpc/examples/helloworld/HelloWorldClientTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.helloworld;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.ManagedChannel;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.GrpcCleanupRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+
+/**
+ * Unit tests for {@link HelloWorldClient}.
+ * For demonstrating how to write gRPC unit test only.
+ * Not intended to provide a high code coverage or to test every major usecase.
+ *
+ * <p>For more unit test examples see {@link io.grpc.examples.routeguide.RouteGuideClientTest} and
+ * {@link io.grpc.examples.routeguide.RouteGuideServerTest}.
+ */
+@RunWith(JUnit4.class)
+public class HelloWorldClientTest {
+  /**
+   * This rule manages automatic graceful shutdown for the registered servers and channels at the
+   * end of test.
+   */
+  @Rule
+  public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+  private final GreeterGrpc.GreeterImplBase serviceImpl =
+      mock(GreeterGrpc.GreeterImplBase.class, delegatesTo(new GreeterGrpc.GreeterImplBase() {}));
+  private HelloWorldClient client;
+
+  @Before
+  public void setUp() throws Exception {
+    // Generate a unique in-process server name.
+    String serverName = InProcessServerBuilder.generateName();
+
+    // Create a server, add service, start, and register for automatic graceful shutdown.
+    grpcCleanup.register(InProcessServerBuilder
+        .forName(serverName).directExecutor().addService(serviceImpl).build().start());
+
+    // Create a client channel and register for automatic graceful shutdown.
+    ManagedChannel channel = grpcCleanup.register(
+        InProcessChannelBuilder.forName(serverName).directExecutor().build());
+
+    // Create a HelloWorldClient using the in-process channel;
+    client = new HelloWorldClient(channel);
+  }
+
+  /**
+   * To test the client, call from the client against the fake server, and verify behaviors or state
+   * changes from the server side.
+   */
+  @Test
+  public void greet_messageDeliveredToServer() {
+    ArgumentCaptor<HelloRequest> requestCaptor = ArgumentCaptor.forClass(HelloRequest.class);
+
+    client.greet("test name");
+
+    verify(serviceImpl)
+        .sayHello(requestCaptor.capture(), Matchers.<StreamObserver<HelloReply>>any());
+    assertEquals("test name", requestCaptor.getValue().getName());
+  }
+}
diff --git a/examples/src/test/java/io/grpc/examples/helloworld/HelloWorldServerTest.java b/examples/src/test/java/io/grpc/examples/helloworld/HelloWorldServerTest.java
new file mode 100644
index 0000000..46d3955
--- /dev/null
+++ b/examples/src/test/java/io/grpc/examples/helloworld/HelloWorldServerTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.helloworld;
+
+import static org.junit.Assert.assertEquals;
+
+import io.grpc.examples.helloworld.HelloWorldServer.GreeterImpl;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.testing.GrpcCleanupRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link HelloWorldServer}.
+ * For demonstrating how to write gRPC unit test only.
+ * Not intended to provide a high code coverage or to test every major usecase.
+ *
+ * <p>For more unit test examples see {@link io.grpc.examples.routeguide.RouteGuideClientTest} and
+ * {@link io.grpc.examples.routeguide.RouteGuideServerTest}.
+ */
+@RunWith(JUnit4.class)
+public class HelloWorldServerTest {
+  /**
+   * This rule manages automatic graceful shutdown for the registered servers and channels at the
+   * end of test.
+   */
+  @Rule
+  public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+  /**
+   * To test the server, make calls with a real stub using the in-process channel, and verify
+   * behaviors or state changes from the client side.
+   */
+  @Test
+  public void greeterImpl_replyMessage() throws Exception {
+    // Generate a unique in-process server name.
+    String serverName = InProcessServerBuilder.generateName();
+
+    // Create a server, add service, start, and register for automatic graceful shutdown.
+    grpcCleanup.register(InProcessServerBuilder
+        .forName(serverName).directExecutor().addService(new GreeterImpl()).build().start());
+
+    GreeterGrpc.GreeterBlockingStub blockingStub = GreeterGrpc.newBlockingStub(
+        // Create a client channel and register for automatic graceful shutdown.
+        grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build()));
+
+
+    HelloReply reply =
+        blockingStub.sayHello(HelloRequest.newBuilder().setName( "test name").build());
+
+    assertEquals("Hello test name", reply.getMessage());
+  }
+}
diff --git a/examples/src/test/java/io/grpc/examples/routeguide/RouteGuideClientTest.java b/examples/src/test/java/io/grpc/examples/routeguide/RouteGuideClientTest.java
new file mode 100644
index 0000000..9f9b04d
--- /dev/null
+++ b/examples/src/test/java/io/grpc/examples/routeguide/RouteGuideClientTest.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.routeguide;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import com.google.protobuf.Message;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.examples.routeguide.RouteGuideClient.TestHelper;
+import io.grpc.examples.routeguide.RouteGuideGrpc.RouteGuideImplBase;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.GrpcCleanupRule;
+import io.grpc.util.MutableHandlerRegistry;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Unit tests for {@link RouteGuideClient}.
+ * For demonstrating how to write gRPC unit test only.
+ * Not intended to provide a high code coverage or to test every major usecase.
+ *
+ * <p>For basic unit test examples see {@link io.grpc.examples.helloworld.HelloWorldClientTest} and
+ * {@link io.grpc.examples.helloworld.HelloWorldServerTest}.
+ */
+@RunWith(JUnit4.class)
+public class RouteGuideClientTest {
+  /**
+   * This rule manages automatic graceful shutdown for the registered server at the end of test.
+   */
+  @Rule
+  public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+  private final MutableHandlerRegistry serviceRegistry = new MutableHandlerRegistry();
+  private final TestHelper testHelper = mock(TestHelper.class);
+  private final Random noRandomness =
+      new Random() {
+        int index;
+        boolean isForSleep;
+
+        /**
+         * Returns a number deterministically. If the random number is for sleep time, then return
+         * -500 so that {@code Thread.sleep(random.nextInt(1000) + 500)} sleeps 0 ms. Otherwise, it
+         * is for list index, then return incrementally (and cyclically).
+         */
+        @Override
+        public int nextInt(int bound) {
+          int retVal = isForSleep ? -500 : (index++ % bound);
+          isForSleep = ! isForSleep;
+          return retVal;
+        }
+      };
+  private RouteGuideClient client;
+
+  @Before
+  public void setUp() throws Exception {
+    // Generate a unique in-process server name.
+    String serverName = InProcessServerBuilder.generateName();
+    // Use a mutable service registry for later registering the service impl for each test case.
+    grpcCleanup.register(InProcessServerBuilder.forName(serverName)
+        .fallbackHandlerRegistry(serviceRegistry).directExecutor().build().start());
+    client =
+        new RouteGuideClient(InProcessChannelBuilder.forName(serverName).directExecutor());
+    client.setTestHelper(testHelper);
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    client.shutdown();
+  }
+
+  /**
+   * Example for testing blocking unary call.
+   */
+  @Test
+  public void getFeature() {
+    Point requestPoint =  Point.newBuilder().setLatitude(-1).setLongitude(-1).build();
+    Point responsePoint = Point.newBuilder().setLatitude(-123).setLongitude(-123).build();
+    final AtomicReference<Point> pointDelivered = new AtomicReference<Point>();
+    final Feature responseFeature =
+        Feature.newBuilder().setName("dummyFeature").setLocation(responsePoint).build();
+
+    // implement the fake service
+    RouteGuideImplBase getFeatureImpl =
+        new RouteGuideImplBase() {
+          @Override
+          public void getFeature(Point point, StreamObserver<Feature> responseObserver) {
+            pointDelivered.set(point);
+            responseObserver.onNext(responseFeature);
+            responseObserver.onCompleted();
+          }
+        };
+    serviceRegistry.addService(getFeatureImpl);
+
+    client.getFeature(-1, -1);
+
+    assertEquals(requestPoint, pointDelivered.get());
+    verify(testHelper).onMessage(responseFeature);
+    verify(testHelper, never()).onRpcError(any(Throwable.class));
+  }
+
+  /**
+   * Example for testing blocking unary call.
+   */
+  @Test
+  public void getFeature_error() {
+    Point requestPoint =  Point.newBuilder().setLatitude(-1).setLongitude(-1).build();
+    final AtomicReference<Point> pointDelivered = new AtomicReference<Point>();
+    final StatusRuntimeException fakeError = new StatusRuntimeException(Status.DATA_LOSS);
+
+    // implement the fake service
+    RouteGuideImplBase getFeatureImpl =
+        new RouteGuideImplBase() {
+          @Override
+          public void getFeature(Point point, StreamObserver<Feature> responseObserver) {
+            pointDelivered.set(point);
+            responseObserver.onError(fakeError);
+          }
+        };
+    serviceRegistry.addService(getFeatureImpl);
+
+    client.getFeature(-1, -1);
+
+    assertEquals(requestPoint, pointDelivered.get());
+    ArgumentCaptor<Throwable> errorCaptor = ArgumentCaptor.forClass(Throwable.class);
+    verify(testHelper).onRpcError(errorCaptor.capture());
+    assertEquals(fakeError.getStatus(), Status.fromThrowable(errorCaptor.getValue()));
+  }
+
+  /**
+   * Example for testing blocking server-streaming.
+   */
+  @Test
+  public void listFeatures() {
+    final Feature responseFeature1 = Feature.newBuilder().setName("feature 1").build();
+    final Feature responseFeature2 = Feature.newBuilder().setName("feature 2").build();
+    final AtomicReference<Rectangle> rectangleDelivered = new AtomicReference<Rectangle>();
+
+    // implement the fake service
+    RouteGuideImplBase listFeaturesImpl =
+        new RouteGuideImplBase() {
+          @Override
+          public void listFeatures(Rectangle rectangle, StreamObserver<Feature> responseObserver) {
+            rectangleDelivered.set(rectangle);
+
+            // send two response messages
+            responseObserver.onNext(responseFeature1);
+            responseObserver.onNext(responseFeature2);
+
+            // complete the response
+            responseObserver.onCompleted();
+          }
+        };
+    serviceRegistry.addService(listFeaturesImpl);
+
+    client.listFeatures(1, 2, 3, 4);
+
+    assertEquals(Rectangle.newBuilder()
+                     .setLo(Point.newBuilder().setLatitude(1).setLongitude(2).build())
+                     .setHi(Point.newBuilder().setLatitude(3).setLongitude(4).build())
+                     .build(),
+                 rectangleDelivered.get());
+    verify(testHelper).onMessage(responseFeature1);
+    verify(testHelper).onMessage(responseFeature2);
+    verify(testHelper, never()).onRpcError(any(Throwable.class));
+  }
+
+  /**
+   * Example for testing blocking server-streaming.
+   */
+  @Test
+  public void listFeatures_error() {
+    final Feature responseFeature1 =
+        Feature.newBuilder().setName("feature 1").build();
+    final AtomicReference<Rectangle> rectangleDelivered = new AtomicReference<Rectangle>();
+    final StatusRuntimeException fakeError = new StatusRuntimeException(Status.INVALID_ARGUMENT);
+
+    // implement the fake service
+    RouteGuideImplBase listFeaturesImpl =
+        new RouteGuideImplBase() {
+          @Override
+          public void listFeatures(Rectangle rectangle, StreamObserver<Feature> responseObserver) {
+            rectangleDelivered.set(rectangle);
+
+            // send one response message
+            responseObserver.onNext(responseFeature1);
+
+            // let the rpc fail
+            responseObserver.onError(fakeError);
+          }
+        };
+    serviceRegistry.addService(listFeaturesImpl);
+
+    client.listFeatures(1, 2, 3, 4);
+
+    assertEquals(Rectangle.newBuilder()
+                     .setLo(Point.newBuilder().setLatitude(1).setLongitude(2).build())
+                     .setHi(Point.newBuilder().setLatitude(3).setLongitude(4).build())
+                     .build(),
+                 rectangleDelivered.get());
+    ArgumentCaptor<Throwable> errorCaptor = ArgumentCaptor.forClass(Throwable.class);
+    verify(testHelper).onMessage(responseFeature1);
+    verify(testHelper).onRpcError(errorCaptor.capture());
+    assertEquals(fakeError.getStatus(), Status.fromThrowable(errorCaptor.getValue()));
+  }
+
+  /**
+   * Example for testing async client-streaming.
+   */
+  @Test
+  public void recordRoute() throws Exception {
+    client.setRandom(noRandomness);
+    Point point1 = Point.newBuilder().setLatitude(1).setLongitude(1).build();
+    Point point2 = Point.newBuilder().setLatitude(2).setLongitude(2).build();
+    Point point3 = Point.newBuilder().setLatitude(3).setLongitude(3).build();
+    Feature requestFeature1 =
+        Feature.newBuilder().setLocation(point1).build();
+    Feature requestFeature2 =
+        Feature.newBuilder().setLocation(point2).build();
+    Feature requestFeature3 =
+        Feature.newBuilder().setLocation(point3).build();
+    final List<Feature> features = Arrays.asList(
+        requestFeature1, requestFeature2, requestFeature3);
+    final List<Point> pointsDelivered = new ArrayList<>();
+    final RouteSummary fakeResponse = RouteSummary
+        .newBuilder()
+        .setPointCount(7)
+        .setFeatureCount(8)
+        .setDistance(9)
+        .setElapsedTime(10)
+        .build();
+
+    // implement the fake service
+    RouteGuideImplBase recordRouteImpl =
+        new RouteGuideImplBase() {
+          @Override
+          public StreamObserver<Point> recordRoute(
+              final StreamObserver<RouteSummary> responseObserver) {
+            StreamObserver<Point> requestObserver = new StreamObserver<Point>() {
+              @Override
+              public void onNext(Point value) {
+                pointsDelivered.add(value);
+              }
+
+              @Override
+              public void onError(Throwable t) {
+              }
+
+              @Override
+              public void onCompleted() {
+                responseObserver.onNext(fakeResponse);
+                responseObserver.onCompleted();
+              }
+            };
+
+            return requestObserver;
+          }
+        };
+    serviceRegistry.addService(recordRouteImpl);
+
+    // send requestFeature1, requestFeature2, requestFeature3, and then requestFeature1 again
+    client.recordRoute(features, 4);
+
+    assertEquals(
+        Arrays.asList(
+            requestFeature1.getLocation(),
+            requestFeature2.getLocation(),
+            requestFeature3.getLocation(),
+            requestFeature1.getLocation()),
+        pointsDelivered);
+    verify(testHelper).onMessage(fakeResponse);
+    verify(testHelper, never()).onRpcError(any(Throwable.class));
+  }
+
+  /**
+   * Example for testing async client-streaming.
+   */
+  @Test
+  public void recordRoute_serverError() throws Exception {
+    client.setRandom(noRandomness);
+    Point point1 = Point.newBuilder().setLatitude(1).setLongitude(1).build();
+    final Feature requestFeature1 =
+        Feature.newBuilder().setLocation(point1).build();
+    final List<Feature> features = Arrays.asList(requestFeature1);
+    final StatusRuntimeException fakeError = new StatusRuntimeException(Status.INVALID_ARGUMENT);
+
+    // implement the fake service
+    RouteGuideImplBase recordRouteImpl =
+        new RouteGuideImplBase() {
+          @Override
+          public StreamObserver<Point> recordRoute(StreamObserver<RouteSummary> responseObserver) {
+            // send an error immediately
+            responseObserver.onError(fakeError);
+
+            StreamObserver<Point> requestObserver = new StreamObserver<Point>() {
+              @Override
+              public void onNext(Point value) {
+              }
+
+              @Override
+              public void onError(Throwable t) {
+              }
+
+              @Override
+              public void onCompleted() {
+              }
+            };
+            return requestObserver;
+          }
+        };
+    serviceRegistry.addService(recordRouteImpl);
+
+    client.recordRoute(features, 4);
+
+    ArgumentCaptor<Throwable> errorCaptor = ArgumentCaptor.forClass(Throwable.class);
+    verify(testHelper).onRpcError(errorCaptor.capture());
+    assertEquals(fakeError.getStatus(), Status.fromThrowable(errorCaptor.getValue()));
+  }
+
+  /**
+   * Example for testing bi-directional call.
+   */
+  @Test
+  public void routeChat_simpleResponse() throws Exception {
+    RouteNote fakeResponse1 = RouteNote.newBuilder().setMessage("dummy msg1").build();
+    RouteNote fakeResponse2 = RouteNote.newBuilder().setMessage("dummy msg2").build();
+    final List<String> messagesDelivered = new ArrayList<>();
+    final List<Point> locationsDelivered = new ArrayList<>();
+    final AtomicReference<StreamObserver<RouteNote>> responseObserverRef =
+        new AtomicReference<StreamObserver<RouteNote>>();
+    final CountDownLatch allRequestsDelivered = new CountDownLatch(1);
+    // implement the fake service
+    RouteGuideImplBase routeChatImpl =
+        new RouteGuideImplBase() {
+          @Override
+          public StreamObserver<RouteNote> routeChat(StreamObserver<RouteNote> responseObserver) {
+            responseObserverRef.set(responseObserver);
+
+            StreamObserver<RouteNote> requestObserver = new StreamObserver<RouteNote>() {
+              @Override
+              public void onNext(RouteNote value) {
+                messagesDelivered.add(value.getMessage());
+                locationsDelivered.add(value.getLocation());
+              }
+
+              @Override
+              public void onError(Throwable t) {
+              }
+
+              @Override
+              public void onCompleted() {
+                allRequestsDelivered.countDown();
+              }
+            };
+
+            return requestObserver;
+          }
+        };
+    serviceRegistry.addService(routeChatImpl);
+
+    // start routeChat
+    CountDownLatch latch = client.routeChat();
+
+    // request message sent and delivered for four times
+    assertTrue(allRequestsDelivered.await(1, TimeUnit.SECONDS));
+    assertEquals(
+        Arrays.asList("First message", "Second message", "Third message", "Fourth message"),
+        messagesDelivered);
+    assertEquals(
+        Arrays.asList(
+            Point.newBuilder().setLatitude(0).setLongitude(0).build(),
+            Point.newBuilder().setLatitude(0).setLongitude(1).build(),
+            Point.newBuilder().setLatitude(1).setLongitude(0).build(),
+            Point.newBuilder().setLatitude(1).setLongitude(1).build()
+        ),
+        locationsDelivered);
+
+    // Let the server send out two simple response messages
+    // and verify that the client receives them.
+    // Allow some timeout for verify() if not using directExecutor
+    responseObserverRef.get().onNext(fakeResponse1);
+    verify(testHelper).onMessage(fakeResponse1);
+    responseObserverRef.get().onNext(fakeResponse2);
+    verify(testHelper).onMessage(fakeResponse2);
+
+    // let server complete.
+    responseObserverRef.get().onCompleted();
+
+    assertTrue(latch.await(1, TimeUnit.SECONDS));
+    verify(testHelper, never()).onRpcError(any(Throwable.class));
+  }
+
+  /**
+   * Example for testing bi-directional call.
+   */
+  @Test
+  public void routeChat_echoResponse() throws Exception {
+    final List<RouteNote> notesDelivered = new ArrayList<>();
+
+    // implement the fake service
+    RouteGuideImplBase routeChatImpl =
+        new RouteGuideImplBase() {
+          @Override
+          public StreamObserver<RouteNote> routeChat(
+              final StreamObserver<RouteNote> responseObserver) {
+            StreamObserver<RouteNote> requestObserver = new StreamObserver<RouteNote>() {
+              @Override
+              public void onNext(RouteNote value) {
+                notesDelivered.add(value);
+                responseObserver.onNext(value);
+              }
+
+              @Override
+              public void onError(Throwable t) {
+                responseObserver.onError(t);
+              }
+
+              @Override
+              public void onCompleted() {
+                responseObserver.onCompleted();
+              }
+            };
+
+            return requestObserver;
+          }
+        };
+    serviceRegistry.addService(routeChatImpl);
+
+    client.routeChat().await(1, TimeUnit.SECONDS);
+
+    String[] messages =
+        {"First message", "Second message", "Third message", "Fourth message"};
+    for (int i = 0; i < 4; i++) {
+      verify(testHelper).onMessage(notesDelivered.get(i));
+      assertEquals(messages[i], notesDelivered.get(i).getMessage());
+    }
+
+    verify(testHelper, never()).onRpcError(any(Throwable.class));
+  }
+
+  /**
+   * Example for testing bi-directional call.
+   */
+  @Test
+  public void routeChat_errorResponse() throws Exception {
+    final List<RouteNote> notesDelivered = new ArrayList<>();
+    final StatusRuntimeException fakeError = new StatusRuntimeException(Status.PERMISSION_DENIED);
+
+    // implement the fake service
+    RouteGuideImplBase routeChatImpl =
+        new RouteGuideImplBase() {
+          @Override
+          public StreamObserver<RouteNote> routeChat(
+              final StreamObserver<RouteNote> responseObserver) {
+            StreamObserver<RouteNote> requestObserver = new StreamObserver<RouteNote>() {
+              @Override
+              public void onNext(RouteNote value) {
+                notesDelivered.add(value);
+                responseObserver.onError(fakeError);
+              }
+
+              @Override
+              public void onError(Throwable t) {
+              }
+
+              @Override
+              public void onCompleted() {
+                responseObserver.onCompleted();
+              }
+            };
+
+            return requestObserver;
+          }
+        };
+    serviceRegistry.addService(routeChatImpl);
+
+    client.routeChat().await(1, TimeUnit.SECONDS);
+
+    assertEquals("First message", notesDelivered.get(0).getMessage());
+    verify(testHelper, never()).onMessage(any(Message.class));
+    ArgumentCaptor<Throwable> errorCaptor = ArgumentCaptor.forClass(Throwable.class);
+    verify(testHelper).onRpcError(errorCaptor.capture());
+    assertEquals(fakeError.getStatus(), Status.fromThrowable(errorCaptor.getValue()));
+  }
+}
diff --git a/examples/src/test/java/io/grpc/examples/routeguide/RouteGuideServerTest.java b/examples/src/test/java/io/grpc/examples/routeguide/RouteGuideServerTest.java
new file mode 100644
index 0000000..cd6ee1f
--- /dev/null
+++ b/examples/src/test/java/io/grpc/examples/routeguide/RouteGuideServerTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.examples.routeguide;
+
+import static junit.framework.TestCase.fail;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.ManagedChannel;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.GrpcCleanupRule;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+/**
+ * Unit tests for {@link RouteGuideServer}.
+ * For demonstrating how to write gRPC unit test only.
+ * Not intended to provide a high code coverage or to test every major usecase.
+ *
+ * <p>For basic unit test examples see {@link io.grpc.examples.helloworld.HelloWorldClientTest} and
+ * {@link io.grpc.examples.helloworld.HelloWorldServerTest}.
+ */
+@RunWith(JUnit4.class)
+public class RouteGuideServerTest {
+  /**
+   * This rule manages automatic graceful shutdown for the registered channel at the end of test.
+   */
+  @Rule
+  public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+  private RouteGuideServer server;
+  private ManagedChannel inProcessChannel;
+  private Collection<Feature> features;
+
+  @Before
+  public void setUp() throws Exception {
+    // Generate a unique in-process server name.
+    String serverName = InProcessServerBuilder.generateName();
+    features = new ArrayList<>();
+    // Use directExecutor for both InProcessServerBuilder and InProcessChannelBuilder can reduce the
+    // usage timeouts and latches in test. But we still add timeout and latches where they would be
+    // needed if no directExecutor were used, just for demo purpose.
+    server = new RouteGuideServer(
+        InProcessServerBuilder.forName(serverName).directExecutor(), 0, features);
+    server.start();
+    // Create a client channel and register for automatic graceful shutdown.
+    inProcessChannel = grpcCleanup.register(
+        InProcessChannelBuilder.forName(serverName).directExecutor().build());
+  }
+
+  @After
+  public void tearDown() {
+    server.stop();
+  }
+
+  @Test
+  public void getFeature() {
+    Point point = Point.newBuilder().setLongitude(1).setLatitude(1).build();
+    Feature unnamedFeature = Feature.newBuilder()
+        .setName("").setLocation(point).build();
+    RouteGuideGrpc.RouteGuideBlockingStub stub = RouteGuideGrpc.newBlockingStub(inProcessChannel);
+
+    // feature not found in the server
+    Feature feature = stub.getFeature(point);
+
+    assertEquals(unnamedFeature, feature);
+
+    // feature found in the server
+    Feature namedFeature = Feature.newBuilder()
+        .setName("name").setLocation(point).build();
+    features.add(namedFeature);
+
+    feature = stub.getFeature(point);
+
+    assertEquals(namedFeature, feature);
+  }
+
+  @Test
+  public void listFeatures() throws Exception {
+    // setup
+    Rectangle rect = Rectangle.newBuilder()
+        .setLo(Point.newBuilder().setLongitude(0).setLatitude(0).build())
+        .setHi(Point.newBuilder().setLongitude(10).setLatitude(10).build())
+        .build();
+    Feature f1 = Feature.newBuilder()
+        .setLocation(Point.newBuilder().setLongitude(-1).setLatitude(-1).build())
+        .setName("f1")
+        .build(); // not inside rect
+    Feature f2 = Feature.newBuilder()
+        .setLocation(Point.newBuilder().setLongitude(2).setLatitude(2).build())
+        .setName("f2")
+        .build();
+    Feature f3 = Feature.newBuilder()
+        .setLocation(Point.newBuilder().setLongitude(3).setLatitude(3).build())
+        .setName("f3")
+        .build();
+    Feature f4 = Feature.newBuilder()
+        .setLocation(Point.newBuilder().setLongitude(4).setLatitude(4).build())
+        .build(); // unamed
+    features.add(f1);
+    features.add(f2);
+    features.add(f3);
+    features.add(f4);
+    final Collection<Feature> result = new HashSet<Feature>();
+    final CountDownLatch latch = new CountDownLatch(1);
+    StreamObserver<Feature> responseObserver =
+        new StreamObserver<Feature>() {
+          @Override
+          public void onNext(Feature value) {
+            result.add(value);
+          }
+
+          @Override
+          public void onError(Throwable t) {
+            fail();
+          }
+
+          @Override
+          public void onCompleted() {
+            latch.countDown();
+          }
+        };
+    RouteGuideGrpc.RouteGuideStub stub = RouteGuideGrpc.newStub(inProcessChannel);
+
+    // run
+    stub.listFeatures(rect, responseObserver);
+    assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+    // verify
+    assertEquals(new HashSet<Feature>(Arrays.asList(f2, f3)), result);
+  }
+
+  @Test
+  public void recordRoute() {
+    Point p1 = Point.newBuilder().setLongitude(1000).setLatitude(1000).build();
+    Point p2 = Point.newBuilder().setLongitude(2000).setLatitude(2000).build();
+    Point p3 = Point.newBuilder().setLongitude(3000).setLatitude(3000).build();
+    Point p4 = Point.newBuilder().setLongitude(4000).setLatitude(4000).build();
+    Feature f1 = Feature.newBuilder().setLocation(p1).build(); // unamed
+    Feature f2 = Feature.newBuilder().setLocation(p2).setName("f2").build();
+    Feature f3 = Feature.newBuilder().setLocation(p3).setName("f3").build();
+    Feature f4 = Feature.newBuilder().setLocation(p4).build(); // unamed
+    features.add(f1);
+    features.add(f2);
+    features.add(f3);
+    features.add(f4);
+
+    @SuppressWarnings("unchecked")
+    StreamObserver<RouteSummary> responseObserver =
+        (StreamObserver<RouteSummary>) mock(StreamObserver.class);
+    RouteGuideGrpc.RouteGuideStub stub = RouteGuideGrpc.newStub(inProcessChannel);
+    ArgumentCaptor<RouteSummary> routeSummaryCaptor = ArgumentCaptor.forClass(RouteSummary.class);
+
+    StreamObserver<Point> requestObserver = stub.recordRoute(responseObserver);
+
+    requestObserver.onNext(p1);
+    requestObserver.onNext(p2);
+    requestObserver.onNext(p3);
+    requestObserver.onNext(p4);
+
+    verify(responseObserver, never()).onNext(any(RouteSummary.class));
+
+    requestObserver.onCompleted();
+
+    // allow some ms to let client receive the response. Similar usage later on.
+    verify(responseObserver, timeout(100)).onNext(routeSummaryCaptor.capture());
+    RouteSummary summary = routeSummaryCaptor.getValue();
+    assertEquals(45, summary.getDistance()); // 45 is the hard coded distance from p1 to p4.
+    assertEquals(2, summary.getFeatureCount());
+    verify(responseObserver, timeout(100)).onCompleted();
+    verify(responseObserver, never()).onError(any(Throwable.class));
+  }
+
+  @Test
+  public void routeChat() {
+    Point p1 = Point.newBuilder().setLongitude(1).setLatitude(1).build();
+    Point p2 = Point.newBuilder().setLongitude(2).setLatitude(2).build();
+    RouteNote n1 = RouteNote.newBuilder().setLocation(p1).setMessage("m1").build();
+    RouteNote n2 = RouteNote.newBuilder().setLocation(p2).setMessage("m2").build();
+    RouteNote n3 = RouteNote.newBuilder().setLocation(p1).setMessage("m3").build();
+    RouteNote n4 = RouteNote.newBuilder().setLocation(p2).setMessage("m4").build();
+    RouteNote n5 = RouteNote.newBuilder().setLocation(p1).setMessage("m5").build();
+    RouteNote n6 = RouteNote.newBuilder().setLocation(p1).setMessage("m6").build();
+    int timesOnNext = 0;
+
+    @SuppressWarnings("unchecked")
+    StreamObserver<RouteNote> responseObserver =
+        (StreamObserver<RouteNote>) mock(StreamObserver.class);
+    RouteGuideGrpc.RouteGuideStub stub = RouteGuideGrpc.newStub(inProcessChannel);
+
+    StreamObserver<RouteNote> requestObserver = stub.routeChat(responseObserver);
+    verify(responseObserver, never()).onNext(any(RouteNote.class));
+
+    requestObserver.onNext(n1);
+    verify(responseObserver, never()).onNext(any(RouteNote.class));
+
+    requestObserver.onNext(n2);
+    verify(responseObserver, never()).onNext(any(RouteNote.class));
+
+    requestObserver.onNext(n3);
+    ArgumentCaptor<RouteNote> routeNoteCaptor = ArgumentCaptor.forClass(RouteNote.class);
+    verify(responseObserver, timeout(100).times(++timesOnNext)).onNext(routeNoteCaptor.capture());
+    RouteNote result = routeNoteCaptor.getValue();
+    assertEquals(p1, result.getLocation());
+    assertEquals("m1", result.getMessage());
+
+    requestObserver.onNext(n4);
+    routeNoteCaptor = ArgumentCaptor.forClass(RouteNote.class);
+    verify(responseObserver, timeout(100).times(++timesOnNext)).onNext(routeNoteCaptor.capture());
+    result = routeNoteCaptor.getAllValues().get(timesOnNext - 1);
+    assertEquals(p2, result.getLocation());
+    assertEquals("m2", result.getMessage());
+
+    requestObserver.onNext(n5);
+    routeNoteCaptor = ArgumentCaptor.forClass(RouteNote.class);
+    timesOnNext += 2;
+    verify(responseObserver, timeout(100).times(timesOnNext)).onNext(routeNoteCaptor.capture());
+    result = routeNoteCaptor.getAllValues().get(timesOnNext - 2);
+    assertEquals(p1, result.getLocation());
+    assertEquals("m1", result.getMessage());
+    result = routeNoteCaptor.getAllValues().get(timesOnNext - 1);
+    assertEquals(p1, result.getLocation());
+    assertEquals("m3", result.getMessage());
+
+    requestObserver.onNext(n6);
+    routeNoteCaptor = ArgumentCaptor.forClass(RouteNote.class);
+    timesOnNext += 3;
+    verify(responseObserver, timeout(100).times(timesOnNext)).onNext(routeNoteCaptor.capture());
+    result = routeNoteCaptor.getAllValues().get(timesOnNext - 3);
+    assertEquals(p1, result.getLocation());
+    assertEquals("m1", result.getMessage());
+    result = routeNoteCaptor.getAllValues().get(timesOnNext - 2);
+    assertEquals(p1, result.getLocation());
+    assertEquals("m3", result.getMessage());
+    result = routeNoteCaptor.getAllValues().get(timesOnNext - 1);
+    assertEquals(p1, result.getLocation());
+    assertEquals("m5", result.getMessage());
+
+    requestObserver.onCompleted();
+    verify(responseObserver, timeout(100)).onCompleted();
+    verify(responseObserver, never()).onError(any(Throwable.class));
+  }
+}
diff --git a/gae-interop-testing/README.md b/gae-interop-testing/README.md
new file mode 100644
index 0000000..f518a2b
--- /dev/null
+++ b/gae-interop-testing/README.md
@@ -0,0 +1,49 @@
+Google App Engine interop tests
+=====================================
+
+This directory contains interop tests that runs in Google App Engine
+as gRPC clients.
+
+Prerequisites
+==========================
+
+- Install the Google Cloud SDK and ensure that `gcloud` is in the path
+- Set up an [App Engine app](http://appengine.google.com) with your
+  choice of a PROJECT_ID.
+- Associate your `gcloud` environment with your app:
+  ```bash
+  # Log into Google Cloud
+  $ gcloud auth login
+
+  # Associate this codebase with a GAE project
+  $ gcloud config set project PROJECT_ID
+  ```
+
+Running the tests in GAE
+==========================
+
+You can run the gradle task to execute the interop tests.
+```bash
+# cd into either gae-jdk7 or gae-jdk8
+$ ../../gradlew runInteropTestRemote
+
+# Or run one of these from the root gRPC Java directory:
+$ ./gradlew :grpc-gae-interop-testing-jdk7:runInteropTestRemote
+$ ./gradlew :grpc-gae-interop-testing-jdk8:runInteropTestRemote
+```
+
+Optional:
+
+You can also browse to `http://${PROJECT_ID}.appspot.google.com` to
+see the result of the interop test.
+
+
+Debugging
+==========================
+
+You can find the server side logs by logging into
+`http://appengine.google.com` and scrolling down to the section titled
+`Application Errors` and `Server Errors`.
+
+Click on the `/` URI to view the log entries for each test run.
+
diff --git a/gae-interop-testing/gae-jdk7/build.gradle b/gae-interop-testing/gae-jdk7/build.gradle
new file mode 100644
index 0000000..d27f336
--- /dev/null
+++ b/gae-interop-testing/gae-jdk7/build.gradle
@@ -0,0 +1,154 @@
+// Copyright 2017 The gRPC Authors
+//
+//  Licensed 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.
+description = 'gRPC: gae interop testing (jdk7)'
+
+buildscript {
+    // Configuration for building
+    repositories {
+        jcenter()    // Bintray's repository - a fast Maven Central mirror & more
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    }
+    dependencies {
+        classpath 'com.google.cloud.tools:appengine-gradle-plugin:1.3.5'
+        classpath 'com.squareup.okhttp:okhttp:2.5.0'
+    }
+}
+
+repositories {
+    // repositories for Jar's you access in your code
+    mavenLocal()
+    maven { // The google mirror is less flaky than mavenCentral()
+        url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    jcenter()
+}
+
+apply plugin: 'java'                              // standard Java tasks
+apply plugin: 'war'                               // standard Web Archive plugin
+apply plugin: 'com.google.cloud.tools.appengine'  // App Engine tasks
+
+dependencies {
+    providedCompile group: 'javax.servlet', name: 'servlet-api', version:'2.5'
+    compile 'com.google.appengine:appengine-api-1.0-sdk:1.9.59'
+    // Deps needed by all gRPC apps in GAE
+    compile libraries.google_api_protos
+    compile project(":grpc-okhttp")
+    compile project(":grpc-protobuf")
+    compile project(":grpc-stub")
+    compile (project(":grpc-interop-testing")) {
+        exclude group: 'io.opencensus', module: 'opencensus-impl'
+    }
+    // The lite version of opencensus is required for jdk7 GAE
+    runtime libraries.opencensus_impl_lite
+}
+
+compileJava {
+    // Disable "No processor claimed any of these annotations: org.junit.Ignore"
+    options.compilerArgs += ["-Xlint:-processing"]
+}
+
+def createDefaultVersion() {
+    return new java.text.SimpleDateFormat("yyyyMMdd't'HHmmss").format(new Date())
+}
+
+// [START model]
+appengine {
+    // App Engine tasks configuration
+    run {      // local (dev_appserver) configuration (standard environments only)
+        port = 8080                 // default
+    }
+
+    deploy {
+        // deploy configuration
+        // default - stop the current version
+        stopPreviousVersion = System.getProperty('gaeStopPreviousVersion') ?: true
+        // default - do not make this the promoted version
+        promote = System.getProperty('gaePromote') ?: false
+        // Use -DgaeDeployVersion if set, otherwise the version is null and the plugin will generate it
+        version = System.getProperty('gaeDeployVersion', createDefaultVersion())
+    }
+}
+// [END model]
+
+group = 'io.grpc'   // Generated output GroupId
+version = '1.0-SNAPSHOT'          // Version in generated output
+
+sourceCompatibility = 1.7
+targetCompatibility = 1.7
+
+/** Returns the service name. */
+String getGaeProject() {
+    def stream = new ByteArrayOutputStream()
+    exec {
+        executable 'gcloud'
+        args = [
+            'config',
+            'get-value',
+            'project'
+        ]
+        standardOutput = stream
+    }
+    return stream.toString().trim()
+}
+
+String getService(java.nio.file.Path projectPath) {
+    Node xml = new XmlParser().parse(projectPath.resolve("src/main/webapp/WEB-INF/appengine-web.xml").toFile())
+    if (xml.service.isEmpty()) {
+        return "default"
+    } else {
+        return xml.service.text()
+    }
+}
+
+String getAppUrl(String project, String service, String version) {
+    return "http://${version}.${service}.${project}.appspot.com"
+}
+
+task runInteropTestRemote(dependsOn: 'appengineDeploy') {
+    doLast {
+        // give remote app some time to settle down
+        sleep(20000)
+
+        def appUrl = getAppUrl(
+                getGaeProject(),
+                getService(project.getProjectDir().toPath()),
+                appengine.deploy.version)
+        logger.log(LogLevel.INFO, "the appURL=" + appUrl)
+        def client = new com.squareup.okhttp.OkHttpClient()
+        // The test suite can take a while to run
+        client.setReadTimeout(3, java.util.concurrent.TimeUnit.MINUTES)
+        // The '?jdk8' argument is ignored by the server, it exists only to tag the request log entry
+        def interopRequest = new com.squareup.okhttp.Request.Builder()
+                .url("${appUrl}/?jdk7").build()
+
+        // Retry in case GAE is slow and times out
+        int maxRetries = 5
+        String result = null
+        Throwable caught = null
+        for (int attempt = 0; attempt < maxRetries; attempt++) {
+            try {
+                def response = client.newCall(interopRequest).execute()
+                result = response.body().string()
+                project.println(result)
+                if (response.code() == 200) {
+                    return
+                }
+            } catch (Throwable t) {
+                caught = t
+                logger.log(LogLevel.ERROR, "caught exception. will retry if possible", t)
+            }
+        }
+        throw new GradleException("Interop test failed:\nthrowable:${caught}")
+    }
+}
diff --git a/gae-interop-testing/gae-jdk7/src/main/java/io/grpc/testing/integration/OkHttpClientInteropServlet.java b/gae-interop-testing/gae-jdk7/src/main/java/io/grpc/testing/integration/OkHttpClientInteropServlet.java
new file mode 100644
index 0000000..a024304
--- /dev/null
+++ b/gae-interop-testing/gae-jdk7/src/main/java/io/grpc/testing/integration/OkHttpClientInteropServlet.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.internal.MoreThrowables;
+import io.grpc.okhttp.OkHttpChannelBuilder;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Method;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.internal.AssumptionViolatedException;
+
+/**
+ * This servlet communicates with {@code grpc-test.sandbox.googleapis.com}, which is a server
+ * managed by the gRPC team. For more information, see
+ * <a href="https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md">
+ *   Interoperability Test Case Descriptions</a>.
+ */
+@SuppressWarnings("serial")
+public final class OkHttpClientInteropServlet extends HttpServlet {
+  private static final String INTEROP_TEST_ADDRESS = "grpc-test.sandbox.googleapis.com:443";
+
+  private static final class LogEntryRecorder extends Handler {
+    private Queue<LogRecord> loggedMessages = new ConcurrentLinkedQueue<>();
+
+    @Override
+    public void publish(LogRecord logRecord) {
+      loggedMessages.add(logRecord);
+    }
+
+    @Override
+    public void flush() {}
+
+    @Override
+    public void close() {}
+
+    public String getLogOutput() {
+      SimpleFormatter formatter = new SimpleFormatter();
+      StringBuilder sb = new StringBuilder();
+      for (LogRecord loggedMessage : loggedMessages) {
+        sb.append(formatter.format(loggedMessage));
+      }
+      return sb.toString();
+    }
+  }
+
+  @Override
+  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    LogEntryRecorder handler = new LogEntryRecorder();
+    Logger.getLogger("").addHandler(handler);
+    try {
+      doGetHelper(req, resp);
+    } finally {
+      Logger.getLogger("").removeHandler(handler);
+    }
+    resp.getWriter().append("=======================================\n")
+        .append("Server side java.util.logging messages:\n")
+        .append(handler.getLogOutput());
+  }
+
+  private void doGetHelper(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    resp.setContentType("text/plain");
+    PrintWriter writer = resp.getWriter();
+    writer.println("Test invoked at: ");
+    writer.println(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss Z")
+        .format(Calendar.getInstance().getTime()));
+
+    // We can not use JUnit because it tries to spawn backgrounds threads.
+    // GAE+JDK7 does not allow arbitrary background threads.
+    // Let's use reflection to run test methods.
+    List<Method> befores = new ArrayList<>();
+    List<Method> afters = new ArrayList<>();
+    List<Method> testMethods = new ArrayList<>();
+    int ignored = 0;
+    for (Method method : Tester.class.getMethods()) {
+      if (method.getAnnotation(Test.class) != null) {
+        if (method.getAnnotation(Ignore.class) != null) {
+          ignored++;
+        } else {
+          testMethods.add(method);
+        }
+      } else if (method.getAnnotation(Before.class) != null) {
+        befores.add(method);
+      } else if (method.getAnnotation(After.class) != null) {
+        afters.add(method);
+      }
+    }
+
+    StringBuilder sb = new StringBuilder();
+    int failures = 0;
+    for (Method method : testMethods) {
+      // JUnit creates a new instance per test method, we will emulate that behavior.
+      Tester tester = new Tester();
+      try {
+        for (Method before : befores) {
+          before.invoke(tester);
+        }
+        try (AutoCloseable unused = toCloseable(tester, afters)) {
+          method.invoke(tester);
+        }
+        sb.append("================\n");
+        sb.append("PASS: Test method: ").append(method).append("\n");
+      } catch (Throwable t) {
+        // The default JUnit4 test runner skips tests with failed assumptions.
+        // We will do the same here.
+        boolean assumptionViolated = false;
+        for (Throwable iter = t; iter != null; iter = iter.getCause()) {
+          if (iter instanceof AssumptionViolatedException) {
+            assumptionViolated = true;
+            break;
+          }
+        }
+        if (assumptionViolated) {
+          continue;
+        }
+
+        sb.append("================\n");
+        sb.append("FAILED: Test method: ").append(method).append("\n");
+        failures++;
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+        t.printStackTrace(printWriter);
+        sb.append(stringWriter);
+      }
+    }
+    if (failures == 0) {
+      resp.setStatus(200);
+      writer.println(
+          String.format(
+              "PASS! Tests ran %d, tests ignored %d",
+              testMethods.size(),
+              ignored));
+    } else {
+      resp.setStatus(500);
+      writer.println(
+          String.format(
+              "FAILED! Tests ran %d, tests failed %d, tests ignored %d",
+              testMethods.size(),
+              failures,
+              ignored));
+    }
+    writer.println(sb);
+  }
+
+  private static AutoCloseable toCloseable(final Object o, final List<Method> methods) {
+    return new AutoCloseable() {
+      @Override
+      public void close() throws Exception {
+        Throwable failure = null;
+        for (Method method : methods) {
+          try {
+            method.invoke(o);
+          } catch (Throwable t) {
+            if (failure == null) {
+              failure = t;
+            } else {
+              failure.addSuppressed(t);
+            }
+          }
+        }
+        if (failure != null) {
+          MoreThrowables.throwIfUnchecked(failure);
+          throw new Exception(failure);
+        }
+      }
+    };
+  }
+
+  public static final class Tester extends AbstractInteropTest {
+    @Override
+    protected ManagedChannel createChannel() {
+      assertEquals(
+          "jdk7 required",
+          "1.7",
+          System.getProperty("java.specification.version"));
+      assertEquals(
+          "Can not run in dev servers because they lack org.conscrypt.OpenSSLProvider support",
+          "Production",
+          System.getProperty("com.google.appengine.runtime.environment"));
+      ManagedChannelBuilder<?> builder =
+          ManagedChannelBuilder.forTarget(INTEROP_TEST_ADDRESS)
+              .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE);
+      assertTrue(builder instanceof OkHttpChannelBuilder);
+      return builder.build();
+    }
+
+    @Override
+    protected boolean metricsExpected() {
+      // Server-side metrics won't be found because the server is running remotely.
+      return false;
+    }
+
+    // grpc-test.sandbox.googleapis.com does not support these tests
+    @Ignore
+    @Override
+    public void customMetadata() { }
+
+    @Ignore
+    @Override
+    public void statusCodeAndMessage() { }
+
+    @Ignore
+    @Override
+    public void exchangeMetadataUnaryCall() { }
+
+    @Ignore
+    @Override
+    public void exchangeMetadataStreamingCall() { }
+
+    @Ignore
+    @Override
+    public void specialStatusMessage() {}
+
+    // grpc-java/issues/4626: this test has become flakey on GAE JDK7
+    @Ignore
+    @Override
+    public void timeoutOnSleepingServer() {}
+  }
+}
diff --git a/gae-interop-testing/gae-jdk7/src/main/webapp/WEB-INF/appengine-web.xml b/gae-interop-testing/gae-jdk7/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 0000000..a1963ce
--- /dev/null
+++ b/gae-interop-testing/gae-jdk7/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2017 The gRPC Authors
+  Licensed 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.
+-->
+<!-- [START config] -->
+<appengine-web-app xmlns='http://appengine.google.com/ns/1.0'>
+  <threadsafe>true</threadsafe>
+  <service>java-gae-interop-test</service>
+  <runtime>java7</runtime>
+</appengine-web-app>
+<!-- [END config] -->
diff --git a/gae-interop-testing/gae-jdk7/src/main/webapp/WEB-INF/web.xml b/gae-interop-testing/gae-jdk7/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..35d6f83
--- /dev/null
+++ b/gae-interop-testing/gae-jdk7/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
+  xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+  version="2.5">
+  <servlet>
+    <servlet-name>interoptest</servlet-name>
+    <servlet-class>io.grpc.testing.integration.OkHttpClientInteropServlet</servlet-class>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>interoptest</servlet-name>
+    <url-pattern>/</url-pattern>
+  </servlet-mapping>
+</web-app>
diff --git a/gae-interop-testing/gae-jdk8/build.gradle b/gae-interop-testing/gae-jdk8/build.gradle
new file mode 100644
index 0000000..69f48bd
--- /dev/null
+++ b/gae-interop-testing/gae-jdk8/build.gradle
@@ -0,0 +1,161 @@
+// Copyright 2017 The gRPC Authors
+//
+//  Licensed 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.
+description = 'gRPC: gae interop testing (jdk8)'
+
+buildscript {
+    // Configuration for building
+    repositories {
+        jcenter()    // Bintray's repository - a fast Maven Central mirror & more
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    }
+    dependencies {
+        classpath 'com.google.cloud.tools:appengine-gradle-plugin:1.3.5'
+        classpath 'com.squareup.okhttp:okhttp:2.5.0'
+    }
+}
+
+repositories {
+    // repositories for Jar's you access in your code
+    mavenLocal()
+    maven { // The google mirror is less flaky than mavenCentral()
+        url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    jcenter()
+}
+
+apply plugin: 'java'                              // standard Java tasks
+apply plugin: 'war'                               // standard Web Archive plugin
+apply plugin: 'com.google.cloud.tools.appengine'  // App Engine tasks
+
+dependencies {
+    providedCompile group: 'javax.servlet', name: 'servlet-api', version:'2.5'
+    compile 'com.google.appengine:appengine-api-1.0-sdk:1.9.59'
+    // Deps needed by all gRPC apps in GAE
+    compile libraries.google_api_protos
+    compile project(":grpc-okhttp")
+    compile project(":grpc-protobuf")
+    compile project(":grpc-stub")
+    compile project(":grpc-interop-testing")
+    compile libraries.netty_tcnative
+}
+
+compileJava {
+    // Disable "No processor claimed any of these annotations: org.junit.Ignore"
+    options.compilerArgs += ["-Xlint:-processing"]
+}
+
+def createDefaultVersion() {
+    return new java.text.SimpleDateFormat("yyyyMMdd't'HHmmss").format(new Date())
+}
+
+// [START model]
+appengine {
+    // App Engine tasks configuration
+    run {      // local (dev_appserver) configuration (standard environments only)
+        port = 8080                 // default
+    }
+
+    deploy {
+        // deploy configuration
+        // default - stop the current version
+        stopPreviousVersion = System.getProperty('gaeStopPreviousVersion') ?: true
+        // default - do not make this the promoted version
+        promote = System.getProperty('gaePromote') ?: false
+        // Use -DgaeDeployVersion if set, otherwise the version is null and the plugin will generate it
+        version = System.getProperty('gaeDeployVersion', createDefaultVersion())
+    }
+}
+// [END model]
+
+group = 'io.grpc'   // Generated output GroupId
+version = '1.0-SNAPSHOT'          // Version in generated output
+
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
+
+/** Returns the service name. */
+String getGaeProject() {
+    def stream = new ByteArrayOutputStream()
+    exec {
+        executable 'gcloud'
+        args = [
+            'config',
+            'get-value',
+            'project'
+        ]
+        standardOutput = stream
+    }
+    return stream.toString().trim()
+}
+
+String getService(java.nio.file.Path projectPath) {
+    Node xml = new XmlParser().parse(projectPath.resolve("src/main/webapp/WEB-INF/appengine-web.xml").toFile())
+    if (xml.service.isEmpty()) {
+        return "default"
+    } else {
+        return xml.service.text()
+    }
+}
+
+String getAppUrl(String project, String service, String version) {
+    return "http://${version}.${service}.${project}.appspot.com"
+}
+
+task runInteropTestRemote(dependsOn: 'appengineDeploy') {
+    doLast {
+        // give remote app some time to settle down
+        sleep(20000)
+
+        def appUrl = getAppUrl(
+                getGaeProject(),
+                getService(project.getProjectDir().toPath()),
+                appengine.deploy.version)
+        logger.log(LogLevel.INFO, "the appURL=" + appUrl)
+        def client = new com.squareup.okhttp.OkHttpClient()
+        // The '?jdk8' argument is ignored by the server, it exists only to tag the request log entry
+        client.setReadTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
+        def request = new com.squareup.okhttp.Request.Builder()
+                .url("${appUrl}/long_lived_channel?jdk8").build()
+        def result1 = client.newCall(request).execute()
+        def result2 = client.newCall(request).execute()
+        if (result1.code() != 200 || result2.code() != 200) {
+            throw new GradleException("Unable to reuse same channel across requests")
+        }
+
+        // The test suite can take a while to run
+        client.setReadTimeout(3, java.util.concurrent.TimeUnit.MINUTES)
+        // The '?jdk8' argument is ignored by the server, it exists only to tag the request log entry
+        def interopRequest = new com.squareup.okhttp.Request.Builder()
+                .url("${appUrl}/?jdk8").build()
+
+        // Retry in case GAE is slow and times out
+        int maxRetries = 5
+        String result = null
+        Throwable caught = null
+        for (int attempt = 0; attempt < maxRetries; attempt++) {
+            try {
+                def response = client.newCall(interopRequest).execute()
+                result = response.body().string()
+                project.println(result)
+                if (response.code() == 200) {
+                    return
+                }
+            } catch (Throwable t) {
+                caught = t
+                logger.log(LogLevel.ERROR, "caught exception. will retry if possible", t)
+            }
+        }
+        throw new GradleException("Interop test failed:\nthrowable:${caught}")
+    }
+}
diff --git a/gae-interop-testing/gae-jdk8/src/main/java/io/grpc/testing/integration/LongLivedChannel.java b/gae-interop-testing/gae-jdk8/src/main/java/io/grpc/testing/integration/LongLivedChannel.java
new file mode 100644
index 0000000..ec5b739
--- /dev/null
+++ b/gae-interop-testing/gae-jdk8/src/main/java/io/grpc/testing/integration/LongLivedChannel.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import com.google.protobuf.ByteString;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.testing.integration.Messages.Payload;
+import io.grpc.testing.integration.Messages.SimpleRequest;
+import io.grpc.testing.integration.Messages.SimpleResponse;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A test class that checks we can reuse a channel across requests.
+ *
+ * <p>This servlet communicates with {@code grpc-test.sandbox.googleapis.com}, which is a server
+ * managed by the gRPC team. For more information, see
+ * <a href="https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md">
+ *   Interoperability Test Case Descriptions</a>.
+ */
+@SuppressWarnings("serial")
+public final class LongLivedChannel extends HttpServlet {
+  private static final String INTEROP_TEST_ADDRESS = "grpc-test.sandbox.googleapis.com:443";
+  private final ManagedChannel channel =
+      ManagedChannelBuilder.forTarget(INTEROP_TEST_ADDRESS).build();
+
+  @Override
+  public void destroy() {
+    try {
+      channel.shutdownNow().awaitTermination(1, TimeUnit.SECONDS);
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    int requestSize = 1234;
+    int responseSize = 5678;
+    SimpleRequest request = SimpleRequest.newBuilder()
+        .setResponseSize(responseSize)
+        .setResponseType(Messages.PayloadType.COMPRESSABLE)
+        .setPayload(Payload.newBuilder()
+            .setBody(ByteString.copyFrom(new byte[requestSize])))
+        .build();
+    TestServiceGrpc.TestServiceBlockingStub blockingStub =
+            TestServiceGrpc.newBlockingStub(channel);
+    SimpleResponse simpleResponse = blockingStub.unaryCall(request);
+    resp.setContentType("text/plain");
+    if (simpleResponse.getPayload().getBody().size() == responseSize) {
+      resp.setStatus(200);
+      resp.getWriter().println("PASS!");
+    } else {
+      resp.setStatus(500);
+      resp.getWriter().println("FAILED!");
+    }
+  }
+}
diff --git a/gae-interop-testing/gae-jdk8/src/main/java/io/grpc/testing/integration/NettyClientInteropServlet.java b/gae-interop-testing/gae-jdk8/src/main/java/io/grpc/testing/integration/NettyClientInteropServlet.java
new file mode 100644
index 0000000..b8220cf
--- /dev/null
+++ b/gae-interop-testing/gae-jdk8/src/main/java/io/grpc/testing/integration/NettyClientInteropServlet.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.netty.NettyChannelBuilder;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import java.util.logging.SimpleFormatter;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Ignore;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Result;
+import org.junit.runner.notification.Failure;
+
+/**
+ * This servlet communicates with {@code grpc-test.sandbox.googleapis.com}, which is a server
+ * managed by the gRPC team. For more information, see
+ * <a href="https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md">
+ *   Interoperability Test Case Descriptions</a>.
+ */
+@SuppressWarnings("serial")
+public final class NettyClientInteropServlet extends HttpServlet {
+  private static final String INTEROP_TEST_ADDRESS = "grpc-test.sandbox.googleapis.com:443";
+
+  private static final class LogEntryRecorder extends Handler {
+    private Queue<LogRecord> loggedMessages = new ConcurrentLinkedQueue<>();
+
+    @Override
+    public void publish(LogRecord logRecord) {
+      loggedMessages.add(logRecord);
+    }
+
+    @Override
+    public void flush() {}
+
+    @Override
+    public void close() {}
+
+    public String getLogOutput() {
+      SimpleFormatter formatter = new SimpleFormatter();
+      StringBuilder sb = new StringBuilder();
+      for (LogRecord loggedMessage : loggedMessages) {
+        sb.append(formatter.format(loggedMessage));
+      }
+      return sb.toString();
+    }
+  }
+
+  @Override
+  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    LogEntryRecorder handler = new LogEntryRecorder();
+    Logger.getLogger("").addHandler(handler);
+    try {
+      doGetHelper(req, resp);
+    } finally {
+      Logger.getLogger("").removeHandler(handler);
+    }
+    resp.getWriter().append("=======================================\n")
+        .append("Server side java.util.logging messages:\n")
+        .append(handler.getLogOutput());
+  }
+
+  private void doGetHelper(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    resp.setContentType("text/plain");
+    PrintWriter writer = resp.getWriter();
+    writer.println("Test invoked at: ");
+    writer.println(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss Z")
+        .format(Calendar.getInstance().getTime()));
+
+    Result result = new JUnitCore().run(Tester.class);
+    if (result.wasSuccessful()) {
+      resp.setStatus(200);
+      writer.println(
+          String.format(
+              "PASS! Tests ran %d, tests ignored %d",
+              result.getRunCount(),
+              result.getIgnoreCount()));
+    } else {
+      resp.setStatus(500);
+      writer.println(
+          String.format(
+              "FAILED! Tests ran %d, tests failed %d, tests ignored %d",
+              result.getRunCount(),
+              result.getFailureCount(),
+              result.getIgnoreCount()));
+      for (Failure failure : result.getFailures()) {
+        writer.println("================================");
+        writer.println(failure.getTestHeader());
+        Throwable thrown = failure.getException();
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+        thrown.printStackTrace(printWriter);
+        writer.println(stringWriter);
+      }
+    }
+  }
+
+  public static final class Tester extends AbstractInteropTest {
+    @Override
+    protected ManagedChannel createChannel() {
+      assertEquals(
+          "jdk8 required",
+          "1.8",
+          System.getProperty("java.specification.version"));
+      ManagedChannelBuilder<?> builder =
+          ManagedChannelBuilder.forTarget(INTEROP_TEST_ADDRESS)
+              .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE);
+      assertTrue(builder instanceof NettyChannelBuilder);
+      ((NettyChannelBuilder) builder).flowControlWindow(65 * 1024);
+      return builder.build();
+    }
+
+    @Override
+    protected boolean metricsExpected() {
+      // Server-side metrics won't be found because the server is running remotely.
+      return false;
+    }
+
+    // grpc-test.sandbox.googleapis.com does not support these tests
+    @Ignore
+    @Override
+    public void customMetadata() { }
+
+    @Ignore
+    @Override
+    public void statusCodeAndMessage() { }
+
+    @Ignore
+    @Override
+    public void exchangeMetadataUnaryCall() { }
+
+    @Ignore
+    @Override
+    public void exchangeMetadataStreamingCall() { }
+
+    @Ignore
+    @Override
+    public void specialStatusMessage() {}
+  }
+}
diff --git a/gae-interop-testing/gae-jdk8/src/main/webapp/WEB-INF/appengine-web.xml b/gae-interop-testing/gae-jdk8/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 0000000..d8532df
--- /dev/null
+++ b/gae-interop-testing/gae-jdk8/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2017 The gRPC Authors
+  Licensed 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.
+-->
+<!-- [START config] -->
+<appengine-web-app xmlns='http://appengine.google.com/ns/1.0'>
+  <threadsafe>true</threadsafe>
+  <service>java-gae-interop-test</service>
+  <runtime>java8</runtime>
+</appengine-web-app>
+<!-- [END config] -->
diff --git a/gae-interop-testing/gae-jdk8/src/main/webapp/WEB-INF/web.xml b/gae-interop-testing/gae-jdk8/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..2d825eb
--- /dev/null
+++ b/gae-interop-testing/gae-jdk8/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
+  xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+  version="2.5">
+  <servlet>
+    <servlet-name>interoptest</servlet-name>
+    <servlet-class>io.grpc.testing.integration.NettyClientInteropServlet</servlet-class>
+  </servlet>
+  <servlet>
+    <servlet-name>longlivedchannel</servlet-name>
+    <servlet-class>io.grpc.testing.integration.LongLivedChannel</servlet-class>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>interoptest</servlet-name>
+    <url-pattern>/</url-pattern>
+  </servlet-mapping>
+  <servlet-mapping>
+    <servlet-name>longlivedchannel</servlet-name>
+    <url-pattern>/long_lived_channel</url-pattern>
+  </servlet-mapping>
+</web-app>
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..0d4a951
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..a95009c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS=

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto init

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto init

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:init

+@rem Get command-line arguments, handling Windows variants

+

+if not "%OS%" == "Windows_NT" goto win9xME_args

+

+:win9xME_args

+@rem Slurp the command line arguments.

+set CMD_LINE_ARGS=

+set _SKIP=2

+

+:win9xME_args_slurp

+if "x%~1" == "x" goto execute

+

+set CMD_LINE_ARGS=%*

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/grpclb/BUILD.bazel b/grpclb/BUILD.bazel
new file mode 100644
index 0000000..3fa78fa
--- /dev/null
+++ b/grpclb/BUILD.bazel
@@ -0,0 +1,40 @@
+load("//:java_grpc_library.bzl", "java_grpc_library")
+
+java_library(
+    name = "grpclb",
+    srcs = glob([
+        "src/main/java/io/grpc/grpclb/*.java",
+    ]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//core",
+        "//core:internal",
+        "//core:util",
+        "//stub",
+        "@com_google_protobuf//:protobuf_java_util",
+        "@com_google_guava_guava//jar",
+        "@com_google_code_findbugs_jsr305//jar",
+        ":load_balancer_java_proto",
+        ":load_balancer_java_grpc",
+    ],
+)
+
+proto_library(
+    name = "load_balancer_proto",
+    srcs = ["src/main/proto/grpc/lb/v1/load_balancer.proto"],
+    deps = [
+        "@com_google_protobuf//:timestamp_proto",
+        "@com_google_protobuf//:duration_proto",
+    ],
+)
+
+java_proto_library(
+    name = "load_balancer_java_proto",
+    deps = [":load_balancer_proto"],
+)
+
+java_grpc_library(
+    name = "load_balancer_java_grpc",
+    srcs = [":load_balancer_proto"],
+    deps = [":load_balancer_java_proto"],
+)
diff --git a/grpclb/build.gradle b/grpclb/build.gradle
new file mode 100644
index 0000000..a71a624
--- /dev/null
+++ b/grpclb/build.gradle
@@ -0,0 +1,25 @@
+description = "gRPC: GRPCLB LoadBalancer plugin"
+
+buildscript {
+    repositories {
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    }
+    dependencies { classpath libraries.protobuf_plugin }
+}
+
+dependencies {
+    compile project(':grpc-core'),
+            project(':grpc-protobuf'),
+            project(':grpc-stub'),
+            libraries.protobuf
+    compile (libraries.protobuf_util) {
+        // prefer 20.0 from libraries instead of 19.0
+        exclude group: 'com.google.guava', module: 'guava'
+    }
+    compileOnly libraries.javax_annotation
+    testCompile libraries.truth,
+            project(':grpc-core').sourceSets.test.output
+}
+
+configureProtoCompilation()
diff --git a/grpclb/src/generated/main/grpc/io/grpc/lb/v1/LoadBalancerGrpc.java b/grpclb/src/generated/main/grpc/io/grpc/lb/v1/LoadBalancerGrpc.java
new file mode 100644
index 0000000..9604415
--- /dev/null
+++ b/grpclb/src/generated/main/grpc/io/grpc/lb/v1/LoadBalancerGrpc.java
@@ -0,0 +1,270 @@
+package io.grpc.lb.v1;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: grpc/lb/v1/load_balancer.proto")
+public final class LoadBalancerGrpc {
+
+  private LoadBalancerGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.lb.v1.LoadBalancer";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.lb.v1.LoadBalanceRequest,
+      io.grpc.lb.v1.LoadBalanceResponse> getBalanceLoadMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "BalanceLoad",
+      requestType = io.grpc.lb.v1.LoadBalanceRequest.class,
+      responseType = io.grpc.lb.v1.LoadBalanceResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.lb.v1.LoadBalanceRequest,
+      io.grpc.lb.v1.LoadBalanceResponse> getBalanceLoadMethod() {
+    io.grpc.MethodDescriptor<io.grpc.lb.v1.LoadBalanceRequest, io.grpc.lb.v1.LoadBalanceResponse> getBalanceLoadMethod;
+    if ((getBalanceLoadMethod = LoadBalancerGrpc.getBalanceLoadMethod) == null) {
+      synchronized (LoadBalancerGrpc.class) {
+        if ((getBalanceLoadMethod = LoadBalancerGrpc.getBalanceLoadMethod) == null) {
+          LoadBalancerGrpc.getBalanceLoadMethod = getBalanceLoadMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.lb.v1.LoadBalanceRequest, io.grpc.lb.v1.LoadBalanceResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.lb.v1.LoadBalancer", "BalanceLoad"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.lb.v1.LoadBalanceRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.lb.v1.LoadBalanceResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new LoadBalancerMethodDescriptorSupplier("BalanceLoad"))
+                  .build();
+          }
+        }
+     }
+     return getBalanceLoadMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static LoadBalancerStub newStub(io.grpc.Channel channel) {
+    return new LoadBalancerStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static LoadBalancerBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new LoadBalancerBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static LoadBalancerFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new LoadBalancerFutureStub(channel);
+  }
+
+  /**
+   */
+  public static abstract class LoadBalancerImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * Bidirectional rpc to get a list of servers.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.lb.v1.LoadBalanceRequest> balanceLoad(
+        io.grpc.stub.StreamObserver<io.grpc.lb.v1.LoadBalanceResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getBalanceLoadMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getBalanceLoadMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.lb.v1.LoadBalanceRequest,
+                io.grpc.lb.v1.LoadBalanceResponse>(
+                  this, METHODID_BALANCE_LOAD)))
+          .build();
+    }
+  }
+
+  /**
+   */
+  public static final class LoadBalancerStub extends io.grpc.stub.AbstractStub<LoadBalancerStub> {
+    private LoadBalancerStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private LoadBalancerStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected LoadBalancerStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new LoadBalancerStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Bidirectional rpc to get a list of servers.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.lb.v1.LoadBalanceRequest> balanceLoad(
+        io.grpc.stub.StreamObserver<io.grpc.lb.v1.LoadBalanceResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getBalanceLoadMethod(), getCallOptions()), responseObserver);
+    }
+  }
+
+  /**
+   */
+  public static final class LoadBalancerBlockingStub extends io.grpc.stub.AbstractStub<LoadBalancerBlockingStub> {
+    private LoadBalancerBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private LoadBalancerBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected LoadBalancerBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new LoadBalancerBlockingStub(channel, callOptions);
+    }
+  }
+
+  /**
+   */
+  public static final class LoadBalancerFutureStub extends io.grpc.stub.AbstractStub<LoadBalancerFutureStub> {
+    private LoadBalancerFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private LoadBalancerFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected LoadBalancerFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new LoadBalancerFutureStub(channel, callOptions);
+    }
+  }
+
+  private static final int METHODID_BALANCE_LOAD = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final LoadBalancerImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(LoadBalancerImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_BALANCE_LOAD:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.balanceLoad(
+              (io.grpc.stub.StreamObserver<io.grpc.lb.v1.LoadBalanceResponse>) responseObserver);
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class LoadBalancerBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    LoadBalancerBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.lb.v1.LoadBalancerProto.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("LoadBalancer");
+    }
+  }
+
+  private static final class LoadBalancerFileDescriptorSupplier
+      extends LoadBalancerBaseDescriptorSupplier {
+    LoadBalancerFileDescriptorSupplier() {}
+  }
+
+  private static final class LoadBalancerMethodDescriptorSupplier
+      extends LoadBalancerBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    LoadBalancerMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (LoadBalancerGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new LoadBalancerFileDescriptorSupplier())
+              .addMethod(getBalanceLoadMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/ClientStats.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/ClientStats.java
new file mode 100644
index 0000000..1122881
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/ClientStats.java
@@ -0,0 +1,1372 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+/**
+ * <pre>
+ * Contains client level statistics that are useful to load balancing. Each
+ * count except the timestamp should be reset to zero after reporting the stats.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.lb.v1.ClientStats}
+ */
+public  final class ClientStats extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.lb.v1.ClientStats)
+    ClientStatsOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ClientStats.newBuilder() to construct.
+  private ClientStats(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ClientStats() {
+    numCallsStarted_ = 0L;
+    numCallsFinished_ = 0L;
+    numCallsFinishedWithClientFailedToSend_ = 0L;
+    numCallsFinishedKnownReceived_ = 0L;
+    callsFinishedWithDrop_ = java.util.Collections.emptyList();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ClientStats(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            com.google.protobuf.Timestamp.Builder subBuilder = null;
+            if (timestamp_ != null) {
+              subBuilder = timestamp_.toBuilder();
+            }
+            timestamp_ = input.readMessage(com.google.protobuf.Timestamp.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(timestamp_);
+              timestamp_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 16: {
+
+            numCallsStarted_ = input.readInt64();
+            break;
+          }
+          case 24: {
+
+            numCallsFinished_ = input.readInt64();
+            break;
+          }
+          case 48: {
+
+            numCallsFinishedWithClientFailedToSend_ = input.readInt64();
+            break;
+          }
+          case 56: {
+
+            numCallsFinishedKnownReceived_ = input.readInt64();
+            break;
+          }
+          case 66: {
+            if (!((mutable_bitField0_ & 0x00000020) == 0x00000020)) {
+              callsFinishedWithDrop_ = new java.util.ArrayList<io.grpc.lb.v1.ClientStatsPerToken>();
+              mutable_bitField0_ |= 0x00000020;
+            }
+            callsFinishedWithDrop_.add(
+                input.readMessage(io.grpc.lb.v1.ClientStatsPerToken.parser(), extensionRegistry));
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000020) == 0x00000020)) {
+        callsFinishedWithDrop_ = java.util.Collections.unmodifiableList(callsFinishedWithDrop_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ClientStats_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ClientStats_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.lb.v1.ClientStats.class, io.grpc.lb.v1.ClientStats.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int TIMESTAMP_FIELD_NUMBER = 1;
+  private com.google.protobuf.Timestamp timestamp_;
+  /**
+   * <pre>
+   * The timestamp of generating the report.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+   */
+  public boolean hasTimestamp() {
+    return timestamp_ != null;
+  }
+  /**
+   * <pre>
+   * The timestamp of generating the report.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+   */
+  public com.google.protobuf.Timestamp getTimestamp() {
+    return timestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : timestamp_;
+  }
+  /**
+   * <pre>
+   * The timestamp of generating the report.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+   */
+  public com.google.protobuf.TimestampOrBuilder getTimestampOrBuilder() {
+    return getTimestamp();
+  }
+
+  public static final int NUM_CALLS_STARTED_FIELD_NUMBER = 2;
+  private long numCallsStarted_;
+  /**
+   * <pre>
+   * The total number of RPCs that started.
+   * </pre>
+   *
+   * <code>int64 num_calls_started = 2;</code>
+   */
+  public long getNumCallsStarted() {
+    return numCallsStarted_;
+  }
+
+  public static final int NUM_CALLS_FINISHED_FIELD_NUMBER = 3;
+  private long numCallsFinished_;
+  /**
+   * <pre>
+   * The total number of RPCs that finished.
+   * </pre>
+   *
+   * <code>int64 num_calls_finished = 3;</code>
+   */
+  public long getNumCallsFinished() {
+    return numCallsFinished_;
+  }
+
+  public static final int NUM_CALLS_FINISHED_WITH_CLIENT_FAILED_TO_SEND_FIELD_NUMBER = 6;
+  private long numCallsFinishedWithClientFailedToSend_;
+  /**
+   * <pre>
+   * The total number of RPCs that failed to reach a server except dropped RPCs.
+   * </pre>
+   *
+   * <code>int64 num_calls_finished_with_client_failed_to_send = 6;</code>
+   */
+  public long getNumCallsFinishedWithClientFailedToSend() {
+    return numCallsFinishedWithClientFailedToSend_;
+  }
+
+  public static final int NUM_CALLS_FINISHED_KNOWN_RECEIVED_FIELD_NUMBER = 7;
+  private long numCallsFinishedKnownReceived_;
+  /**
+   * <pre>
+   * The total number of RPCs that finished and are known to have been received
+   * by a server.
+   * </pre>
+   *
+   * <code>int64 num_calls_finished_known_received = 7;</code>
+   */
+  public long getNumCallsFinishedKnownReceived() {
+    return numCallsFinishedKnownReceived_;
+  }
+
+  public static final int CALLS_FINISHED_WITH_DROP_FIELD_NUMBER = 8;
+  private java.util.List<io.grpc.lb.v1.ClientStatsPerToken> callsFinishedWithDrop_;
+  /**
+   * <pre>
+   * The list of dropped calls.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+   */
+  public java.util.List<io.grpc.lb.v1.ClientStatsPerToken> getCallsFinishedWithDropList() {
+    return callsFinishedWithDrop_;
+  }
+  /**
+   * <pre>
+   * The list of dropped calls.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+   */
+  public java.util.List<? extends io.grpc.lb.v1.ClientStatsPerTokenOrBuilder> 
+      getCallsFinishedWithDropOrBuilderList() {
+    return callsFinishedWithDrop_;
+  }
+  /**
+   * <pre>
+   * The list of dropped calls.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+   */
+  public int getCallsFinishedWithDropCount() {
+    return callsFinishedWithDrop_.size();
+  }
+  /**
+   * <pre>
+   * The list of dropped calls.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+   */
+  public io.grpc.lb.v1.ClientStatsPerToken getCallsFinishedWithDrop(int index) {
+    return callsFinishedWithDrop_.get(index);
+  }
+  /**
+   * <pre>
+   * The list of dropped calls.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+   */
+  public io.grpc.lb.v1.ClientStatsPerTokenOrBuilder getCallsFinishedWithDropOrBuilder(
+      int index) {
+    return callsFinishedWithDrop_.get(index);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (timestamp_ != null) {
+      output.writeMessage(1, getTimestamp());
+    }
+    if (numCallsStarted_ != 0L) {
+      output.writeInt64(2, numCallsStarted_);
+    }
+    if (numCallsFinished_ != 0L) {
+      output.writeInt64(3, numCallsFinished_);
+    }
+    if (numCallsFinishedWithClientFailedToSend_ != 0L) {
+      output.writeInt64(6, numCallsFinishedWithClientFailedToSend_);
+    }
+    if (numCallsFinishedKnownReceived_ != 0L) {
+      output.writeInt64(7, numCallsFinishedKnownReceived_);
+    }
+    for (int i = 0; i < callsFinishedWithDrop_.size(); i++) {
+      output.writeMessage(8, callsFinishedWithDrop_.get(i));
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (timestamp_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getTimestamp());
+    }
+    if (numCallsStarted_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(2, numCallsStarted_);
+    }
+    if (numCallsFinished_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(3, numCallsFinished_);
+    }
+    if (numCallsFinishedWithClientFailedToSend_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(6, numCallsFinishedWithClientFailedToSend_);
+    }
+    if (numCallsFinishedKnownReceived_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(7, numCallsFinishedKnownReceived_);
+    }
+    for (int i = 0; i < callsFinishedWithDrop_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(8, callsFinishedWithDrop_.get(i));
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.lb.v1.ClientStats)) {
+      return super.equals(obj);
+    }
+    io.grpc.lb.v1.ClientStats other = (io.grpc.lb.v1.ClientStats) obj;
+
+    boolean result = true;
+    result = result && (hasTimestamp() == other.hasTimestamp());
+    if (hasTimestamp()) {
+      result = result && getTimestamp()
+          .equals(other.getTimestamp());
+    }
+    result = result && (getNumCallsStarted()
+        == other.getNumCallsStarted());
+    result = result && (getNumCallsFinished()
+        == other.getNumCallsFinished());
+    result = result && (getNumCallsFinishedWithClientFailedToSend()
+        == other.getNumCallsFinishedWithClientFailedToSend());
+    result = result && (getNumCallsFinishedKnownReceived()
+        == other.getNumCallsFinishedKnownReceived());
+    result = result && getCallsFinishedWithDropList()
+        .equals(other.getCallsFinishedWithDropList());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasTimestamp()) {
+      hash = (37 * hash) + TIMESTAMP_FIELD_NUMBER;
+      hash = (53 * hash) + getTimestamp().hashCode();
+    }
+    hash = (37 * hash) + NUM_CALLS_STARTED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getNumCallsStarted());
+    hash = (37 * hash) + NUM_CALLS_FINISHED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getNumCallsFinished());
+    hash = (37 * hash) + NUM_CALLS_FINISHED_WITH_CLIENT_FAILED_TO_SEND_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getNumCallsFinishedWithClientFailedToSend());
+    hash = (37 * hash) + NUM_CALLS_FINISHED_KNOWN_RECEIVED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getNumCallsFinishedKnownReceived());
+    if (getCallsFinishedWithDropCount() > 0) {
+      hash = (37 * hash) + CALLS_FINISHED_WITH_DROP_FIELD_NUMBER;
+      hash = (53 * hash) + getCallsFinishedWithDropList().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.lb.v1.ClientStats parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.ClientStats parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ClientStats parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.ClientStats parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ClientStats parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.ClientStats parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ClientStats parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.ClientStats parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ClientStats parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.ClientStats parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ClientStats parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.ClientStats parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.lb.v1.ClientStats prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Contains client level statistics that are useful to load balancing. Each
+   * count except the timestamp should be reset to zero after reporting the stats.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.lb.v1.ClientStats}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.lb.v1.ClientStats)
+      io.grpc.lb.v1.ClientStatsOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ClientStats_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ClientStats_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.lb.v1.ClientStats.class, io.grpc.lb.v1.ClientStats.Builder.class);
+    }
+
+    // Construct using io.grpc.lb.v1.ClientStats.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+        getCallsFinishedWithDropFieldBuilder();
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (timestampBuilder_ == null) {
+        timestamp_ = null;
+      } else {
+        timestamp_ = null;
+        timestampBuilder_ = null;
+      }
+      numCallsStarted_ = 0L;
+
+      numCallsFinished_ = 0L;
+
+      numCallsFinishedWithClientFailedToSend_ = 0L;
+
+      numCallsFinishedKnownReceived_ = 0L;
+
+      if (callsFinishedWithDropBuilder_ == null) {
+        callsFinishedWithDrop_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000020);
+      } else {
+        callsFinishedWithDropBuilder_.clear();
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ClientStats_descriptor;
+    }
+
+    public io.grpc.lb.v1.ClientStats getDefaultInstanceForType() {
+      return io.grpc.lb.v1.ClientStats.getDefaultInstance();
+    }
+
+    public io.grpc.lb.v1.ClientStats build() {
+      io.grpc.lb.v1.ClientStats result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.lb.v1.ClientStats buildPartial() {
+      io.grpc.lb.v1.ClientStats result = new io.grpc.lb.v1.ClientStats(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      if (timestampBuilder_ == null) {
+        result.timestamp_ = timestamp_;
+      } else {
+        result.timestamp_ = timestampBuilder_.build();
+      }
+      result.numCallsStarted_ = numCallsStarted_;
+      result.numCallsFinished_ = numCallsFinished_;
+      result.numCallsFinishedWithClientFailedToSend_ = numCallsFinishedWithClientFailedToSend_;
+      result.numCallsFinishedKnownReceived_ = numCallsFinishedKnownReceived_;
+      if (callsFinishedWithDropBuilder_ == null) {
+        if (((bitField0_ & 0x00000020) == 0x00000020)) {
+          callsFinishedWithDrop_ = java.util.Collections.unmodifiableList(callsFinishedWithDrop_);
+          bitField0_ = (bitField0_ & ~0x00000020);
+        }
+        result.callsFinishedWithDrop_ = callsFinishedWithDrop_;
+      } else {
+        result.callsFinishedWithDrop_ = callsFinishedWithDropBuilder_.build();
+      }
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.lb.v1.ClientStats) {
+        return mergeFrom((io.grpc.lb.v1.ClientStats)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.lb.v1.ClientStats other) {
+      if (other == io.grpc.lb.v1.ClientStats.getDefaultInstance()) return this;
+      if (other.hasTimestamp()) {
+        mergeTimestamp(other.getTimestamp());
+      }
+      if (other.getNumCallsStarted() != 0L) {
+        setNumCallsStarted(other.getNumCallsStarted());
+      }
+      if (other.getNumCallsFinished() != 0L) {
+        setNumCallsFinished(other.getNumCallsFinished());
+      }
+      if (other.getNumCallsFinishedWithClientFailedToSend() != 0L) {
+        setNumCallsFinishedWithClientFailedToSend(other.getNumCallsFinishedWithClientFailedToSend());
+      }
+      if (other.getNumCallsFinishedKnownReceived() != 0L) {
+        setNumCallsFinishedKnownReceived(other.getNumCallsFinishedKnownReceived());
+      }
+      if (callsFinishedWithDropBuilder_ == null) {
+        if (!other.callsFinishedWithDrop_.isEmpty()) {
+          if (callsFinishedWithDrop_.isEmpty()) {
+            callsFinishedWithDrop_ = other.callsFinishedWithDrop_;
+            bitField0_ = (bitField0_ & ~0x00000020);
+          } else {
+            ensureCallsFinishedWithDropIsMutable();
+            callsFinishedWithDrop_.addAll(other.callsFinishedWithDrop_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.callsFinishedWithDrop_.isEmpty()) {
+          if (callsFinishedWithDropBuilder_.isEmpty()) {
+            callsFinishedWithDropBuilder_.dispose();
+            callsFinishedWithDropBuilder_ = null;
+            callsFinishedWithDrop_ = other.callsFinishedWithDrop_;
+            bitField0_ = (bitField0_ & ~0x00000020);
+            callsFinishedWithDropBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getCallsFinishedWithDropFieldBuilder() : null;
+          } else {
+            callsFinishedWithDropBuilder_.addAllMessages(other.callsFinishedWithDrop_);
+          }
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.lb.v1.ClientStats parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.lb.v1.ClientStats) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private com.google.protobuf.Timestamp timestamp_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> timestampBuilder_;
+    /**
+     * <pre>
+     * The timestamp of generating the report.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+     */
+    public boolean hasTimestamp() {
+      return timestampBuilder_ != null || timestamp_ != null;
+    }
+    /**
+     * <pre>
+     * The timestamp of generating the report.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+     */
+    public com.google.protobuf.Timestamp getTimestamp() {
+      if (timestampBuilder_ == null) {
+        return timestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : timestamp_;
+      } else {
+        return timestampBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The timestamp of generating the report.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+     */
+    public Builder setTimestamp(com.google.protobuf.Timestamp value) {
+      if (timestampBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        timestamp_ = value;
+        onChanged();
+      } else {
+        timestampBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The timestamp of generating the report.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+     */
+    public Builder setTimestamp(
+        com.google.protobuf.Timestamp.Builder builderForValue) {
+      if (timestampBuilder_ == null) {
+        timestamp_ = builderForValue.build();
+        onChanged();
+      } else {
+        timestampBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The timestamp of generating the report.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+     */
+    public Builder mergeTimestamp(com.google.protobuf.Timestamp value) {
+      if (timestampBuilder_ == null) {
+        if (timestamp_ != null) {
+          timestamp_ =
+            com.google.protobuf.Timestamp.newBuilder(timestamp_).mergeFrom(value).buildPartial();
+        } else {
+          timestamp_ = value;
+        }
+        onChanged();
+      } else {
+        timestampBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The timestamp of generating the report.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+     */
+    public Builder clearTimestamp() {
+      if (timestampBuilder_ == null) {
+        timestamp_ = null;
+        onChanged();
+      } else {
+        timestamp_ = null;
+        timestampBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The timestamp of generating the report.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+     */
+    public com.google.protobuf.Timestamp.Builder getTimestampBuilder() {
+      
+      onChanged();
+      return getTimestampFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The timestamp of generating the report.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+     */
+    public com.google.protobuf.TimestampOrBuilder getTimestampOrBuilder() {
+      if (timestampBuilder_ != null) {
+        return timestampBuilder_.getMessageOrBuilder();
+      } else {
+        return timestamp_ == null ?
+            com.google.protobuf.Timestamp.getDefaultInstance() : timestamp_;
+      }
+    }
+    /**
+     * <pre>
+     * The timestamp of generating the report.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> 
+        getTimestampFieldBuilder() {
+      if (timestampBuilder_ == null) {
+        timestampBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder>(
+                getTimestamp(),
+                getParentForChildren(),
+                isClean());
+        timestamp_ = null;
+      }
+      return timestampBuilder_;
+    }
+
+    private long numCallsStarted_ ;
+    /**
+     * <pre>
+     * The total number of RPCs that started.
+     * </pre>
+     *
+     * <code>int64 num_calls_started = 2;</code>
+     */
+    public long getNumCallsStarted() {
+      return numCallsStarted_;
+    }
+    /**
+     * <pre>
+     * The total number of RPCs that started.
+     * </pre>
+     *
+     * <code>int64 num_calls_started = 2;</code>
+     */
+    public Builder setNumCallsStarted(long value) {
+      
+      numCallsStarted_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The total number of RPCs that started.
+     * </pre>
+     *
+     * <code>int64 num_calls_started = 2;</code>
+     */
+    public Builder clearNumCallsStarted() {
+      
+      numCallsStarted_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long numCallsFinished_ ;
+    /**
+     * <pre>
+     * The total number of RPCs that finished.
+     * </pre>
+     *
+     * <code>int64 num_calls_finished = 3;</code>
+     */
+    public long getNumCallsFinished() {
+      return numCallsFinished_;
+    }
+    /**
+     * <pre>
+     * The total number of RPCs that finished.
+     * </pre>
+     *
+     * <code>int64 num_calls_finished = 3;</code>
+     */
+    public Builder setNumCallsFinished(long value) {
+      
+      numCallsFinished_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The total number of RPCs that finished.
+     * </pre>
+     *
+     * <code>int64 num_calls_finished = 3;</code>
+     */
+    public Builder clearNumCallsFinished() {
+      
+      numCallsFinished_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long numCallsFinishedWithClientFailedToSend_ ;
+    /**
+     * <pre>
+     * The total number of RPCs that failed to reach a server except dropped RPCs.
+     * </pre>
+     *
+     * <code>int64 num_calls_finished_with_client_failed_to_send = 6;</code>
+     */
+    public long getNumCallsFinishedWithClientFailedToSend() {
+      return numCallsFinishedWithClientFailedToSend_;
+    }
+    /**
+     * <pre>
+     * The total number of RPCs that failed to reach a server except dropped RPCs.
+     * </pre>
+     *
+     * <code>int64 num_calls_finished_with_client_failed_to_send = 6;</code>
+     */
+    public Builder setNumCallsFinishedWithClientFailedToSend(long value) {
+      
+      numCallsFinishedWithClientFailedToSend_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The total number of RPCs that failed to reach a server except dropped RPCs.
+     * </pre>
+     *
+     * <code>int64 num_calls_finished_with_client_failed_to_send = 6;</code>
+     */
+    public Builder clearNumCallsFinishedWithClientFailedToSend() {
+      
+      numCallsFinishedWithClientFailedToSend_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long numCallsFinishedKnownReceived_ ;
+    /**
+     * <pre>
+     * The total number of RPCs that finished and are known to have been received
+     * by a server.
+     * </pre>
+     *
+     * <code>int64 num_calls_finished_known_received = 7;</code>
+     */
+    public long getNumCallsFinishedKnownReceived() {
+      return numCallsFinishedKnownReceived_;
+    }
+    /**
+     * <pre>
+     * The total number of RPCs that finished and are known to have been received
+     * by a server.
+     * </pre>
+     *
+     * <code>int64 num_calls_finished_known_received = 7;</code>
+     */
+    public Builder setNumCallsFinishedKnownReceived(long value) {
+      
+      numCallsFinishedKnownReceived_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The total number of RPCs that finished and are known to have been received
+     * by a server.
+     * </pre>
+     *
+     * <code>int64 num_calls_finished_known_received = 7;</code>
+     */
+    public Builder clearNumCallsFinishedKnownReceived() {
+      
+      numCallsFinishedKnownReceived_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private java.util.List<io.grpc.lb.v1.ClientStatsPerToken> callsFinishedWithDrop_ =
+      java.util.Collections.emptyList();
+    private void ensureCallsFinishedWithDropIsMutable() {
+      if (!((bitField0_ & 0x00000020) == 0x00000020)) {
+        callsFinishedWithDrop_ = new java.util.ArrayList<io.grpc.lb.v1.ClientStatsPerToken>(callsFinishedWithDrop_);
+        bitField0_ |= 0x00000020;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.lb.v1.ClientStatsPerToken, io.grpc.lb.v1.ClientStatsPerToken.Builder, io.grpc.lb.v1.ClientStatsPerTokenOrBuilder> callsFinishedWithDropBuilder_;
+
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public java.util.List<io.grpc.lb.v1.ClientStatsPerToken> getCallsFinishedWithDropList() {
+      if (callsFinishedWithDropBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(callsFinishedWithDrop_);
+      } else {
+        return callsFinishedWithDropBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public int getCallsFinishedWithDropCount() {
+      if (callsFinishedWithDropBuilder_ == null) {
+        return callsFinishedWithDrop_.size();
+      } else {
+        return callsFinishedWithDropBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public io.grpc.lb.v1.ClientStatsPerToken getCallsFinishedWithDrop(int index) {
+      if (callsFinishedWithDropBuilder_ == null) {
+        return callsFinishedWithDrop_.get(index);
+      } else {
+        return callsFinishedWithDropBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public Builder setCallsFinishedWithDrop(
+        int index, io.grpc.lb.v1.ClientStatsPerToken value) {
+      if (callsFinishedWithDropBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureCallsFinishedWithDropIsMutable();
+        callsFinishedWithDrop_.set(index, value);
+        onChanged();
+      } else {
+        callsFinishedWithDropBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public Builder setCallsFinishedWithDrop(
+        int index, io.grpc.lb.v1.ClientStatsPerToken.Builder builderForValue) {
+      if (callsFinishedWithDropBuilder_ == null) {
+        ensureCallsFinishedWithDropIsMutable();
+        callsFinishedWithDrop_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        callsFinishedWithDropBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public Builder addCallsFinishedWithDrop(io.grpc.lb.v1.ClientStatsPerToken value) {
+      if (callsFinishedWithDropBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureCallsFinishedWithDropIsMutable();
+        callsFinishedWithDrop_.add(value);
+        onChanged();
+      } else {
+        callsFinishedWithDropBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public Builder addCallsFinishedWithDrop(
+        int index, io.grpc.lb.v1.ClientStatsPerToken value) {
+      if (callsFinishedWithDropBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureCallsFinishedWithDropIsMutable();
+        callsFinishedWithDrop_.add(index, value);
+        onChanged();
+      } else {
+        callsFinishedWithDropBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public Builder addCallsFinishedWithDrop(
+        io.grpc.lb.v1.ClientStatsPerToken.Builder builderForValue) {
+      if (callsFinishedWithDropBuilder_ == null) {
+        ensureCallsFinishedWithDropIsMutable();
+        callsFinishedWithDrop_.add(builderForValue.build());
+        onChanged();
+      } else {
+        callsFinishedWithDropBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public Builder addCallsFinishedWithDrop(
+        int index, io.grpc.lb.v1.ClientStatsPerToken.Builder builderForValue) {
+      if (callsFinishedWithDropBuilder_ == null) {
+        ensureCallsFinishedWithDropIsMutable();
+        callsFinishedWithDrop_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        callsFinishedWithDropBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public Builder addAllCallsFinishedWithDrop(
+        java.lang.Iterable<? extends io.grpc.lb.v1.ClientStatsPerToken> values) {
+      if (callsFinishedWithDropBuilder_ == null) {
+        ensureCallsFinishedWithDropIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, callsFinishedWithDrop_);
+        onChanged();
+      } else {
+        callsFinishedWithDropBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public Builder clearCallsFinishedWithDrop() {
+      if (callsFinishedWithDropBuilder_ == null) {
+        callsFinishedWithDrop_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000020);
+        onChanged();
+      } else {
+        callsFinishedWithDropBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public Builder removeCallsFinishedWithDrop(int index) {
+      if (callsFinishedWithDropBuilder_ == null) {
+        ensureCallsFinishedWithDropIsMutable();
+        callsFinishedWithDrop_.remove(index);
+        onChanged();
+      } else {
+        callsFinishedWithDropBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public io.grpc.lb.v1.ClientStatsPerToken.Builder getCallsFinishedWithDropBuilder(
+        int index) {
+      return getCallsFinishedWithDropFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public io.grpc.lb.v1.ClientStatsPerTokenOrBuilder getCallsFinishedWithDropOrBuilder(
+        int index) {
+      if (callsFinishedWithDropBuilder_ == null) {
+        return callsFinishedWithDrop_.get(index);  } else {
+        return callsFinishedWithDropBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public java.util.List<? extends io.grpc.lb.v1.ClientStatsPerTokenOrBuilder> 
+         getCallsFinishedWithDropOrBuilderList() {
+      if (callsFinishedWithDropBuilder_ != null) {
+        return callsFinishedWithDropBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(callsFinishedWithDrop_);
+      }
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public io.grpc.lb.v1.ClientStatsPerToken.Builder addCallsFinishedWithDropBuilder() {
+      return getCallsFinishedWithDropFieldBuilder().addBuilder(
+          io.grpc.lb.v1.ClientStatsPerToken.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public io.grpc.lb.v1.ClientStatsPerToken.Builder addCallsFinishedWithDropBuilder(
+        int index) {
+      return getCallsFinishedWithDropFieldBuilder().addBuilder(
+          index, io.grpc.lb.v1.ClientStatsPerToken.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * The list of dropped calls.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+     */
+    public java.util.List<io.grpc.lb.v1.ClientStatsPerToken.Builder> 
+         getCallsFinishedWithDropBuilderList() {
+      return getCallsFinishedWithDropFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.lb.v1.ClientStatsPerToken, io.grpc.lb.v1.ClientStatsPerToken.Builder, io.grpc.lb.v1.ClientStatsPerTokenOrBuilder> 
+        getCallsFinishedWithDropFieldBuilder() {
+      if (callsFinishedWithDropBuilder_ == null) {
+        callsFinishedWithDropBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.lb.v1.ClientStatsPerToken, io.grpc.lb.v1.ClientStatsPerToken.Builder, io.grpc.lb.v1.ClientStatsPerTokenOrBuilder>(
+                callsFinishedWithDrop_,
+                ((bitField0_ & 0x00000020) == 0x00000020),
+                getParentForChildren(),
+                isClean());
+        callsFinishedWithDrop_ = null;
+      }
+      return callsFinishedWithDropBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.lb.v1.ClientStats)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.lb.v1.ClientStats)
+  private static final io.grpc.lb.v1.ClientStats DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.lb.v1.ClientStats();
+  }
+
+  public static io.grpc.lb.v1.ClientStats getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ClientStats>
+      PARSER = new com.google.protobuf.AbstractParser<ClientStats>() {
+    public ClientStats parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ClientStats(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ClientStats> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ClientStats> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.lb.v1.ClientStats getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/ClientStatsOrBuilder.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/ClientStatsOrBuilder.java
new file mode 100644
index 0000000..b38a3dc
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/ClientStatsOrBuilder.java
@@ -0,0 +1,115 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+public interface ClientStatsOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.lb.v1.ClientStats)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The timestamp of generating the report.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+   */
+  boolean hasTimestamp();
+  /**
+   * <pre>
+   * The timestamp of generating the report.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+   */
+  com.google.protobuf.Timestamp getTimestamp();
+  /**
+   * <pre>
+   * The timestamp of generating the report.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 1;</code>
+   */
+  com.google.protobuf.TimestampOrBuilder getTimestampOrBuilder();
+
+  /**
+   * <pre>
+   * The total number of RPCs that started.
+   * </pre>
+   *
+   * <code>int64 num_calls_started = 2;</code>
+   */
+  long getNumCallsStarted();
+
+  /**
+   * <pre>
+   * The total number of RPCs that finished.
+   * </pre>
+   *
+   * <code>int64 num_calls_finished = 3;</code>
+   */
+  long getNumCallsFinished();
+
+  /**
+   * <pre>
+   * The total number of RPCs that failed to reach a server except dropped RPCs.
+   * </pre>
+   *
+   * <code>int64 num_calls_finished_with_client_failed_to_send = 6;</code>
+   */
+  long getNumCallsFinishedWithClientFailedToSend();
+
+  /**
+   * <pre>
+   * The total number of RPCs that finished and are known to have been received
+   * by a server.
+   * </pre>
+   *
+   * <code>int64 num_calls_finished_known_received = 7;</code>
+   */
+  long getNumCallsFinishedKnownReceived();
+
+  /**
+   * <pre>
+   * The list of dropped calls.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+   */
+  java.util.List<io.grpc.lb.v1.ClientStatsPerToken> 
+      getCallsFinishedWithDropList();
+  /**
+   * <pre>
+   * The list of dropped calls.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+   */
+  io.grpc.lb.v1.ClientStatsPerToken getCallsFinishedWithDrop(int index);
+  /**
+   * <pre>
+   * The list of dropped calls.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+   */
+  int getCallsFinishedWithDropCount();
+  /**
+   * <pre>
+   * The list of dropped calls.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+   */
+  java.util.List<? extends io.grpc.lb.v1.ClientStatsPerTokenOrBuilder> 
+      getCallsFinishedWithDropOrBuilderList();
+  /**
+   * <pre>
+   * The list of dropped calls.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.ClientStatsPerToken calls_finished_with_drop = 8;</code>
+   */
+  io.grpc.lb.v1.ClientStatsPerTokenOrBuilder getCallsFinishedWithDropOrBuilder(
+      int index);
+}
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/ClientStatsPerToken.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/ClientStatsPerToken.java
new file mode 100644
index 0000000..32f69d5
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/ClientStatsPerToken.java
@@ -0,0 +1,627 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+/**
+ * <pre>
+ * Contains the number of calls finished for a particular load balance token.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.lb.v1.ClientStatsPerToken}
+ */
+public  final class ClientStatsPerToken extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.lb.v1.ClientStatsPerToken)
+    ClientStatsPerTokenOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ClientStatsPerToken.newBuilder() to construct.
+  private ClientStatsPerToken(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ClientStatsPerToken() {
+    loadBalanceToken_ = "";
+    numCalls_ = 0L;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ClientStatsPerToken(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            loadBalanceToken_ = s;
+            break;
+          }
+          case 16: {
+
+            numCalls_ = input.readInt64();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ClientStatsPerToken_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ClientStatsPerToken_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.lb.v1.ClientStatsPerToken.class, io.grpc.lb.v1.ClientStatsPerToken.Builder.class);
+  }
+
+  public static final int LOAD_BALANCE_TOKEN_FIELD_NUMBER = 1;
+  private volatile java.lang.Object loadBalanceToken_;
+  /**
+   * <pre>
+   * See Server.load_balance_token.
+   * </pre>
+   *
+   * <code>string load_balance_token = 1;</code>
+   */
+  public java.lang.String getLoadBalanceToken() {
+    java.lang.Object ref = loadBalanceToken_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      loadBalanceToken_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * See Server.load_balance_token.
+   * </pre>
+   *
+   * <code>string load_balance_token = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getLoadBalanceTokenBytes() {
+    java.lang.Object ref = loadBalanceToken_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      loadBalanceToken_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int NUM_CALLS_FIELD_NUMBER = 2;
+  private long numCalls_;
+  /**
+   * <pre>
+   * The total number of RPCs that finished associated with the token.
+   * </pre>
+   *
+   * <code>int64 num_calls = 2;</code>
+   */
+  public long getNumCalls() {
+    return numCalls_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getLoadBalanceTokenBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, loadBalanceToken_);
+    }
+    if (numCalls_ != 0L) {
+      output.writeInt64(2, numCalls_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getLoadBalanceTokenBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, loadBalanceToken_);
+    }
+    if (numCalls_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(2, numCalls_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.lb.v1.ClientStatsPerToken)) {
+      return super.equals(obj);
+    }
+    io.grpc.lb.v1.ClientStatsPerToken other = (io.grpc.lb.v1.ClientStatsPerToken) obj;
+
+    boolean result = true;
+    result = result && getLoadBalanceToken()
+        .equals(other.getLoadBalanceToken());
+    result = result && (getNumCalls()
+        == other.getNumCalls());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + LOAD_BALANCE_TOKEN_FIELD_NUMBER;
+    hash = (53 * hash) + getLoadBalanceToken().hashCode();
+    hash = (37 * hash) + NUM_CALLS_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getNumCalls());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.lb.v1.ClientStatsPerToken parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.ClientStatsPerToken parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ClientStatsPerToken parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.ClientStatsPerToken parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ClientStatsPerToken parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.ClientStatsPerToken parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ClientStatsPerToken parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.ClientStatsPerToken parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ClientStatsPerToken parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.ClientStatsPerToken parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ClientStatsPerToken parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.ClientStatsPerToken parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.lb.v1.ClientStatsPerToken prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Contains the number of calls finished for a particular load balance token.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.lb.v1.ClientStatsPerToken}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.lb.v1.ClientStatsPerToken)
+      io.grpc.lb.v1.ClientStatsPerTokenOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ClientStatsPerToken_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ClientStatsPerToken_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.lb.v1.ClientStatsPerToken.class, io.grpc.lb.v1.ClientStatsPerToken.Builder.class);
+    }
+
+    // Construct using io.grpc.lb.v1.ClientStatsPerToken.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      loadBalanceToken_ = "";
+
+      numCalls_ = 0L;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ClientStatsPerToken_descriptor;
+    }
+
+    public io.grpc.lb.v1.ClientStatsPerToken getDefaultInstanceForType() {
+      return io.grpc.lb.v1.ClientStatsPerToken.getDefaultInstance();
+    }
+
+    public io.grpc.lb.v1.ClientStatsPerToken build() {
+      io.grpc.lb.v1.ClientStatsPerToken result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.lb.v1.ClientStatsPerToken buildPartial() {
+      io.grpc.lb.v1.ClientStatsPerToken result = new io.grpc.lb.v1.ClientStatsPerToken(this);
+      result.loadBalanceToken_ = loadBalanceToken_;
+      result.numCalls_ = numCalls_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.lb.v1.ClientStatsPerToken) {
+        return mergeFrom((io.grpc.lb.v1.ClientStatsPerToken)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.lb.v1.ClientStatsPerToken other) {
+      if (other == io.grpc.lb.v1.ClientStatsPerToken.getDefaultInstance()) return this;
+      if (!other.getLoadBalanceToken().isEmpty()) {
+        loadBalanceToken_ = other.loadBalanceToken_;
+        onChanged();
+      }
+      if (other.getNumCalls() != 0L) {
+        setNumCalls(other.getNumCalls());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.lb.v1.ClientStatsPerToken parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.lb.v1.ClientStatsPerToken) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private java.lang.Object loadBalanceToken_ = "";
+    /**
+     * <pre>
+     * See Server.load_balance_token.
+     * </pre>
+     *
+     * <code>string load_balance_token = 1;</code>
+     */
+    public java.lang.String getLoadBalanceToken() {
+      java.lang.Object ref = loadBalanceToken_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        loadBalanceToken_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * See Server.load_balance_token.
+     * </pre>
+     *
+     * <code>string load_balance_token = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getLoadBalanceTokenBytes() {
+      java.lang.Object ref = loadBalanceToken_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        loadBalanceToken_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * See Server.load_balance_token.
+     * </pre>
+     *
+     * <code>string load_balance_token = 1;</code>
+     */
+    public Builder setLoadBalanceToken(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      loadBalanceToken_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * See Server.load_balance_token.
+     * </pre>
+     *
+     * <code>string load_balance_token = 1;</code>
+     */
+    public Builder clearLoadBalanceToken() {
+      
+      loadBalanceToken_ = getDefaultInstance().getLoadBalanceToken();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * See Server.load_balance_token.
+     * </pre>
+     *
+     * <code>string load_balance_token = 1;</code>
+     */
+    public Builder setLoadBalanceTokenBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      loadBalanceToken_ = value;
+      onChanged();
+      return this;
+    }
+
+    private long numCalls_ ;
+    /**
+     * <pre>
+     * The total number of RPCs that finished associated with the token.
+     * </pre>
+     *
+     * <code>int64 num_calls = 2;</code>
+     */
+    public long getNumCalls() {
+      return numCalls_;
+    }
+    /**
+     * <pre>
+     * The total number of RPCs that finished associated with the token.
+     * </pre>
+     *
+     * <code>int64 num_calls = 2;</code>
+     */
+    public Builder setNumCalls(long value) {
+      
+      numCalls_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The total number of RPCs that finished associated with the token.
+     * </pre>
+     *
+     * <code>int64 num_calls = 2;</code>
+     */
+    public Builder clearNumCalls() {
+      
+      numCalls_ = 0L;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.lb.v1.ClientStatsPerToken)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.lb.v1.ClientStatsPerToken)
+  private static final io.grpc.lb.v1.ClientStatsPerToken DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.lb.v1.ClientStatsPerToken();
+  }
+
+  public static io.grpc.lb.v1.ClientStatsPerToken getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ClientStatsPerToken>
+      PARSER = new com.google.protobuf.AbstractParser<ClientStatsPerToken>() {
+    public ClientStatsPerToken parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ClientStatsPerToken(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ClientStatsPerToken> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ClientStatsPerToken> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.lb.v1.ClientStatsPerToken getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/ClientStatsPerTokenOrBuilder.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/ClientStatsPerTokenOrBuilder.java
new file mode 100644
index 0000000..095419e
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/ClientStatsPerTokenOrBuilder.java
@@ -0,0 +1,36 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+public interface ClientStatsPerTokenOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.lb.v1.ClientStatsPerToken)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * See Server.load_balance_token.
+   * </pre>
+   *
+   * <code>string load_balance_token = 1;</code>
+   */
+  java.lang.String getLoadBalanceToken();
+  /**
+   * <pre>
+   * See Server.load_balance_token.
+   * </pre>
+   *
+   * <code>string load_balance_token = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getLoadBalanceTokenBytes();
+
+  /**
+   * <pre>
+   * The total number of RPCs that finished associated with the token.
+   * </pre>
+   *
+   * <code>int64 num_calls = 2;</code>
+   */
+  long getNumCalls();
+}
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/InitialLoadBalanceRequest.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/InitialLoadBalanceRequest.java
new file mode 100644
index 0000000..b5397bd
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/InitialLoadBalanceRequest.java
@@ -0,0 +1,565 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+/**
+ * Protobuf type {@code grpc.lb.v1.InitialLoadBalanceRequest}
+ */
+public  final class InitialLoadBalanceRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.lb.v1.InitialLoadBalanceRequest)
+    InitialLoadBalanceRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use InitialLoadBalanceRequest.newBuilder() to construct.
+  private InitialLoadBalanceRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private InitialLoadBalanceRequest() {
+    name_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private InitialLoadBalanceRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            name_ = s;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_InitialLoadBalanceRequest_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_InitialLoadBalanceRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.lb.v1.InitialLoadBalanceRequest.class, io.grpc.lb.v1.InitialLoadBalanceRequest.Builder.class);
+  }
+
+  public static final int NAME_FIELD_NUMBER = 1;
+  private volatile java.lang.Object name_;
+  /**
+   * <pre>
+   * The name of the load balanced service (e.g., service.googleapis.com). Its
+   * length should be less than 256 bytes.
+   * The name might include a port number. How to handle the port number is up
+   * to the balancer.
+   * </pre>
+   *
+   * <code>string name = 1;</code>
+   */
+  public java.lang.String getName() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      name_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * The name of the load balanced service (e.g., service.googleapis.com). Its
+   * length should be less than 256 bytes.
+   * The name might include a port number. How to handle the port number is up
+   * to the balancer.
+   * </pre>
+   *
+   * <code>string name = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getNameBytes() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      name_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getNameBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getNameBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.lb.v1.InitialLoadBalanceRequest)) {
+      return super.equals(obj);
+    }
+    io.grpc.lb.v1.InitialLoadBalanceRequest other = (io.grpc.lb.v1.InitialLoadBalanceRequest) obj;
+
+    boolean result = true;
+    result = result && getName()
+        .equals(other.getName());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + NAME_FIELD_NUMBER;
+    hash = (53 * hash) + getName().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.lb.v1.InitialLoadBalanceRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.lb.v1.InitialLoadBalanceRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.lb.v1.InitialLoadBalanceRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.lb.v1.InitialLoadBalanceRequest)
+      io.grpc.lb.v1.InitialLoadBalanceRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_InitialLoadBalanceRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_InitialLoadBalanceRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.lb.v1.InitialLoadBalanceRequest.class, io.grpc.lb.v1.InitialLoadBalanceRequest.Builder.class);
+    }
+
+    // Construct using io.grpc.lb.v1.InitialLoadBalanceRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      name_ = "";
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_InitialLoadBalanceRequest_descriptor;
+    }
+
+    public io.grpc.lb.v1.InitialLoadBalanceRequest getDefaultInstanceForType() {
+      return io.grpc.lb.v1.InitialLoadBalanceRequest.getDefaultInstance();
+    }
+
+    public io.grpc.lb.v1.InitialLoadBalanceRequest build() {
+      io.grpc.lb.v1.InitialLoadBalanceRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.lb.v1.InitialLoadBalanceRequest buildPartial() {
+      io.grpc.lb.v1.InitialLoadBalanceRequest result = new io.grpc.lb.v1.InitialLoadBalanceRequest(this);
+      result.name_ = name_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.lb.v1.InitialLoadBalanceRequest) {
+        return mergeFrom((io.grpc.lb.v1.InitialLoadBalanceRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.lb.v1.InitialLoadBalanceRequest other) {
+      if (other == io.grpc.lb.v1.InitialLoadBalanceRequest.getDefaultInstance()) return this;
+      if (!other.getName().isEmpty()) {
+        name_ = other.name_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.lb.v1.InitialLoadBalanceRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.lb.v1.InitialLoadBalanceRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private java.lang.Object name_ = "";
+    /**
+     * <pre>
+     * The name of the load balanced service (e.g., service.googleapis.com). Its
+     * length should be less than 256 bytes.
+     * The name might include a port number. How to handle the port number is up
+     * to the balancer.
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        name_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * The name of the load balanced service (e.g., service.googleapis.com). Its
+     * length should be less than 256 bytes.
+     * The name might include a port number. How to handle the port number is up
+     * to the balancer.
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * The name of the load balanced service (e.g., service.googleapis.com). Its
+     * length should be less than 256 bytes.
+     * The name might include a port number. How to handle the port number is up
+     * to the balancer.
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public Builder setName(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      name_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The name of the load balanced service (e.g., service.googleapis.com). Its
+     * length should be less than 256 bytes.
+     * The name might include a port number. How to handle the port number is up
+     * to the balancer.
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public Builder clearName() {
+      
+      name_ = getDefaultInstance().getName();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The name of the load balanced service (e.g., service.googleapis.com). Its
+     * length should be less than 256 bytes.
+     * The name might include a port number. How to handle the port number is up
+     * to the balancer.
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public Builder setNameBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      name_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.lb.v1.InitialLoadBalanceRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.lb.v1.InitialLoadBalanceRequest)
+  private static final io.grpc.lb.v1.InitialLoadBalanceRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.lb.v1.InitialLoadBalanceRequest();
+  }
+
+  public static io.grpc.lb.v1.InitialLoadBalanceRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<InitialLoadBalanceRequest>
+      PARSER = new com.google.protobuf.AbstractParser<InitialLoadBalanceRequest>() {
+    public InitialLoadBalanceRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new InitialLoadBalanceRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<InitialLoadBalanceRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<InitialLoadBalanceRequest> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.lb.v1.InitialLoadBalanceRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/InitialLoadBalanceRequestOrBuilder.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/InitialLoadBalanceRequestOrBuilder.java
new file mode 100644
index 0000000..2d0a5be
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/InitialLoadBalanceRequestOrBuilder.java
@@ -0,0 +1,33 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+public interface InitialLoadBalanceRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.lb.v1.InitialLoadBalanceRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The name of the load balanced service (e.g., service.googleapis.com). Its
+   * length should be less than 256 bytes.
+   * The name might include a port number. How to handle the port number is up
+   * to the balancer.
+   * </pre>
+   *
+   * <code>string name = 1;</code>
+   */
+  java.lang.String getName();
+  /**
+   * <pre>
+   * The name of the load balanced service (e.g., service.googleapis.com). Its
+   * length should be less than 256 bytes.
+   * The name might include a port number. How to handle the port number is up
+   * to the balancer.
+   * </pre>
+   *
+   * <code>string name = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getNameBytes();
+}
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/InitialLoadBalanceResponse.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/InitialLoadBalanceResponse.java
new file mode 100644
index 0000000..e18a6c3
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/InitialLoadBalanceResponse.java
@@ -0,0 +1,825 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+/**
+ * Protobuf type {@code grpc.lb.v1.InitialLoadBalanceResponse}
+ */
+public  final class InitialLoadBalanceResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.lb.v1.InitialLoadBalanceResponse)
+    InitialLoadBalanceResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use InitialLoadBalanceResponse.newBuilder() to construct.
+  private InitialLoadBalanceResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private InitialLoadBalanceResponse() {
+    loadBalancerDelegate_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private InitialLoadBalanceResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            loadBalancerDelegate_ = s;
+            break;
+          }
+          case 18: {
+            com.google.protobuf.Duration.Builder subBuilder = null;
+            if (clientStatsReportInterval_ != null) {
+              subBuilder = clientStatsReportInterval_.toBuilder();
+            }
+            clientStatsReportInterval_ = input.readMessage(com.google.protobuf.Duration.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(clientStatsReportInterval_);
+              clientStatsReportInterval_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_InitialLoadBalanceResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_InitialLoadBalanceResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.lb.v1.InitialLoadBalanceResponse.class, io.grpc.lb.v1.InitialLoadBalanceResponse.Builder.class);
+  }
+
+  public static final int LOAD_BALANCER_DELEGATE_FIELD_NUMBER = 1;
+  private volatile java.lang.Object loadBalancerDelegate_;
+  /**
+   * <pre>
+   * This is an application layer redirect that indicates the client should use
+   * the specified server for load balancing. When this field is non-empty in
+   * the response, the client should open a separate connection to the
+   * load_balancer_delegate and call the BalanceLoad method. Its length should
+   * be less than 64 bytes.
+   * </pre>
+   *
+   * <code>string load_balancer_delegate = 1;</code>
+   */
+  public java.lang.String getLoadBalancerDelegate() {
+    java.lang.Object ref = loadBalancerDelegate_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      loadBalancerDelegate_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * This is an application layer redirect that indicates the client should use
+   * the specified server for load balancing. When this field is non-empty in
+   * the response, the client should open a separate connection to the
+   * load_balancer_delegate and call the BalanceLoad method. Its length should
+   * be less than 64 bytes.
+   * </pre>
+   *
+   * <code>string load_balancer_delegate = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getLoadBalancerDelegateBytes() {
+    java.lang.Object ref = loadBalancerDelegate_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      loadBalancerDelegate_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int CLIENT_STATS_REPORT_INTERVAL_FIELD_NUMBER = 2;
+  private com.google.protobuf.Duration clientStatsReportInterval_;
+  /**
+   * <pre>
+   * This interval defines how often the client should send the client stats
+   * to the load balancer. Stats should only be reported when the duration is
+   * positive.
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+   */
+  public boolean hasClientStatsReportInterval() {
+    return clientStatsReportInterval_ != null;
+  }
+  /**
+   * <pre>
+   * This interval defines how often the client should send the client stats
+   * to the load balancer. Stats should only be reported when the duration is
+   * positive.
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+   */
+  public com.google.protobuf.Duration getClientStatsReportInterval() {
+    return clientStatsReportInterval_ == null ? com.google.protobuf.Duration.getDefaultInstance() : clientStatsReportInterval_;
+  }
+  /**
+   * <pre>
+   * This interval defines how often the client should send the client stats
+   * to the load balancer. Stats should only be reported when the duration is
+   * positive.
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+   */
+  public com.google.protobuf.DurationOrBuilder getClientStatsReportIntervalOrBuilder() {
+    return getClientStatsReportInterval();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getLoadBalancerDelegateBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, loadBalancerDelegate_);
+    }
+    if (clientStatsReportInterval_ != null) {
+      output.writeMessage(2, getClientStatsReportInterval());
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getLoadBalancerDelegateBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, loadBalancerDelegate_);
+    }
+    if (clientStatsReportInterval_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(2, getClientStatsReportInterval());
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.lb.v1.InitialLoadBalanceResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.lb.v1.InitialLoadBalanceResponse other = (io.grpc.lb.v1.InitialLoadBalanceResponse) obj;
+
+    boolean result = true;
+    result = result && getLoadBalancerDelegate()
+        .equals(other.getLoadBalancerDelegate());
+    result = result && (hasClientStatsReportInterval() == other.hasClientStatsReportInterval());
+    if (hasClientStatsReportInterval()) {
+      result = result && getClientStatsReportInterval()
+          .equals(other.getClientStatsReportInterval());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + LOAD_BALANCER_DELEGATE_FIELD_NUMBER;
+    hash = (53 * hash) + getLoadBalancerDelegate().hashCode();
+    if (hasClientStatsReportInterval()) {
+      hash = (37 * hash) + CLIENT_STATS_REPORT_INTERVAL_FIELD_NUMBER;
+      hash = (53 * hash) + getClientStatsReportInterval().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.lb.v1.InitialLoadBalanceResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.InitialLoadBalanceResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.lb.v1.InitialLoadBalanceResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.lb.v1.InitialLoadBalanceResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.lb.v1.InitialLoadBalanceResponse)
+      io.grpc.lb.v1.InitialLoadBalanceResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_InitialLoadBalanceResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_InitialLoadBalanceResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.lb.v1.InitialLoadBalanceResponse.class, io.grpc.lb.v1.InitialLoadBalanceResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.lb.v1.InitialLoadBalanceResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      loadBalancerDelegate_ = "";
+
+      if (clientStatsReportIntervalBuilder_ == null) {
+        clientStatsReportInterval_ = null;
+      } else {
+        clientStatsReportInterval_ = null;
+        clientStatsReportIntervalBuilder_ = null;
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_InitialLoadBalanceResponse_descriptor;
+    }
+
+    public io.grpc.lb.v1.InitialLoadBalanceResponse getDefaultInstanceForType() {
+      return io.grpc.lb.v1.InitialLoadBalanceResponse.getDefaultInstance();
+    }
+
+    public io.grpc.lb.v1.InitialLoadBalanceResponse build() {
+      io.grpc.lb.v1.InitialLoadBalanceResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.lb.v1.InitialLoadBalanceResponse buildPartial() {
+      io.grpc.lb.v1.InitialLoadBalanceResponse result = new io.grpc.lb.v1.InitialLoadBalanceResponse(this);
+      result.loadBalancerDelegate_ = loadBalancerDelegate_;
+      if (clientStatsReportIntervalBuilder_ == null) {
+        result.clientStatsReportInterval_ = clientStatsReportInterval_;
+      } else {
+        result.clientStatsReportInterval_ = clientStatsReportIntervalBuilder_.build();
+      }
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.lb.v1.InitialLoadBalanceResponse) {
+        return mergeFrom((io.grpc.lb.v1.InitialLoadBalanceResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.lb.v1.InitialLoadBalanceResponse other) {
+      if (other == io.grpc.lb.v1.InitialLoadBalanceResponse.getDefaultInstance()) return this;
+      if (!other.getLoadBalancerDelegate().isEmpty()) {
+        loadBalancerDelegate_ = other.loadBalancerDelegate_;
+        onChanged();
+      }
+      if (other.hasClientStatsReportInterval()) {
+        mergeClientStatsReportInterval(other.getClientStatsReportInterval());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.lb.v1.InitialLoadBalanceResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.lb.v1.InitialLoadBalanceResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private java.lang.Object loadBalancerDelegate_ = "";
+    /**
+     * <pre>
+     * This is an application layer redirect that indicates the client should use
+     * the specified server for load balancing. When this field is non-empty in
+     * the response, the client should open a separate connection to the
+     * load_balancer_delegate and call the BalanceLoad method. Its length should
+     * be less than 64 bytes.
+     * </pre>
+     *
+     * <code>string load_balancer_delegate = 1;</code>
+     */
+    public java.lang.String getLoadBalancerDelegate() {
+      java.lang.Object ref = loadBalancerDelegate_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        loadBalancerDelegate_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * This is an application layer redirect that indicates the client should use
+     * the specified server for load balancing. When this field is non-empty in
+     * the response, the client should open a separate connection to the
+     * load_balancer_delegate and call the BalanceLoad method. Its length should
+     * be less than 64 bytes.
+     * </pre>
+     *
+     * <code>string load_balancer_delegate = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getLoadBalancerDelegateBytes() {
+      java.lang.Object ref = loadBalancerDelegate_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        loadBalancerDelegate_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * This is an application layer redirect that indicates the client should use
+     * the specified server for load balancing. When this field is non-empty in
+     * the response, the client should open a separate connection to the
+     * load_balancer_delegate and call the BalanceLoad method. Its length should
+     * be less than 64 bytes.
+     * </pre>
+     *
+     * <code>string load_balancer_delegate = 1;</code>
+     */
+    public Builder setLoadBalancerDelegate(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      loadBalancerDelegate_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * This is an application layer redirect that indicates the client should use
+     * the specified server for load balancing. When this field is non-empty in
+     * the response, the client should open a separate connection to the
+     * load_balancer_delegate and call the BalanceLoad method. Its length should
+     * be less than 64 bytes.
+     * </pre>
+     *
+     * <code>string load_balancer_delegate = 1;</code>
+     */
+    public Builder clearLoadBalancerDelegate() {
+      
+      loadBalancerDelegate_ = getDefaultInstance().getLoadBalancerDelegate();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * This is an application layer redirect that indicates the client should use
+     * the specified server for load balancing. When this field is non-empty in
+     * the response, the client should open a separate connection to the
+     * load_balancer_delegate and call the BalanceLoad method. Its length should
+     * be less than 64 bytes.
+     * </pre>
+     *
+     * <code>string load_balancer_delegate = 1;</code>
+     */
+    public Builder setLoadBalancerDelegateBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      loadBalancerDelegate_ = value;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.Duration clientStatsReportInterval_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Duration, com.google.protobuf.Duration.Builder, com.google.protobuf.DurationOrBuilder> clientStatsReportIntervalBuilder_;
+    /**
+     * <pre>
+     * This interval defines how often the client should send the client stats
+     * to the load balancer. Stats should only be reported when the duration is
+     * positive.
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+     */
+    public boolean hasClientStatsReportInterval() {
+      return clientStatsReportIntervalBuilder_ != null || clientStatsReportInterval_ != null;
+    }
+    /**
+     * <pre>
+     * This interval defines how often the client should send the client stats
+     * to the load balancer. Stats should only be reported when the duration is
+     * positive.
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+     */
+    public com.google.protobuf.Duration getClientStatsReportInterval() {
+      if (clientStatsReportIntervalBuilder_ == null) {
+        return clientStatsReportInterval_ == null ? com.google.protobuf.Duration.getDefaultInstance() : clientStatsReportInterval_;
+      } else {
+        return clientStatsReportIntervalBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * This interval defines how often the client should send the client stats
+     * to the load balancer. Stats should only be reported when the duration is
+     * positive.
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+     */
+    public Builder setClientStatsReportInterval(com.google.protobuf.Duration value) {
+      if (clientStatsReportIntervalBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        clientStatsReportInterval_ = value;
+        onChanged();
+      } else {
+        clientStatsReportIntervalBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This interval defines how often the client should send the client stats
+     * to the load balancer. Stats should only be reported when the duration is
+     * positive.
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+     */
+    public Builder setClientStatsReportInterval(
+        com.google.protobuf.Duration.Builder builderForValue) {
+      if (clientStatsReportIntervalBuilder_ == null) {
+        clientStatsReportInterval_ = builderForValue.build();
+        onChanged();
+      } else {
+        clientStatsReportIntervalBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This interval defines how often the client should send the client stats
+     * to the load balancer. Stats should only be reported when the duration is
+     * positive.
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+     */
+    public Builder mergeClientStatsReportInterval(com.google.protobuf.Duration value) {
+      if (clientStatsReportIntervalBuilder_ == null) {
+        if (clientStatsReportInterval_ != null) {
+          clientStatsReportInterval_ =
+            com.google.protobuf.Duration.newBuilder(clientStatsReportInterval_).mergeFrom(value).buildPartial();
+        } else {
+          clientStatsReportInterval_ = value;
+        }
+        onChanged();
+      } else {
+        clientStatsReportIntervalBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This interval defines how often the client should send the client stats
+     * to the load balancer. Stats should only be reported when the duration is
+     * positive.
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+     */
+    public Builder clearClientStatsReportInterval() {
+      if (clientStatsReportIntervalBuilder_ == null) {
+        clientStatsReportInterval_ = null;
+        onChanged();
+      } else {
+        clientStatsReportInterval_ = null;
+        clientStatsReportIntervalBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * This interval defines how often the client should send the client stats
+     * to the load balancer. Stats should only be reported when the duration is
+     * positive.
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+     */
+    public com.google.protobuf.Duration.Builder getClientStatsReportIntervalBuilder() {
+      
+      onChanged();
+      return getClientStatsReportIntervalFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * This interval defines how often the client should send the client stats
+     * to the load balancer. Stats should only be reported when the duration is
+     * positive.
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+     */
+    public com.google.protobuf.DurationOrBuilder getClientStatsReportIntervalOrBuilder() {
+      if (clientStatsReportIntervalBuilder_ != null) {
+        return clientStatsReportIntervalBuilder_.getMessageOrBuilder();
+      } else {
+        return clientStatsReportInterval_ == null ?
+            com.google.protobuf.Duration.getDefaultInstance() : clientStatsReportInterval_;
+      }
+    }
+    /**
+     * <pre>
+     * This interval defines how often the client should send the client stats
+     * to the load balancer. Stats should only be reported when the duration is
+     * positive.
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Duration, com.google.protobuf.Duration.Builder, com.google.protobuf.DurationOrBuilder> 
+        getClientStatsReportIntervalFieldBuilder() {
+      if (clientStatsReportIntervalBuilder_ == null) {
+        clientStatsReportIntervalBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Duration, com.google.protobuf.Duration.Builder, com.google.protobuf.DurationOrBuilder>(
+                getClientStatsReportInterval(),
+                getParentForChildren(),
+                isClean());
+        clientStatsReportInterval_ = null;
+      }
+      return clientStatsReportIntervalBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.lb.v1.InitialLoadBalanceResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.lb.v1.InitialLoadBalanceResponse)
+  private static final io.grpc.lb.v1.InitialLoadBalanceResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.lb.v1.InitialLoadBalanceResponse();
+  }
+
+  public static io.grpc.lb.v1.InitialLoadBalanceResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<InitialLoadBalanceResponse>
+      PARSER = new com.google.protobuf.AbstractParser<InitialLoadBalanceResponse>() {
+    public InitialLoadBalanceResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new InitialLoadBalanceResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<InitialLoadBalanceResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<InitialLoadBalanceResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.lb.v1.InitialLoadBalanceResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/InitialLoadBalanceResponseOrBuilder.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/InitialLoadBalanceResponseOrBuilder.java
new file mode 100644
index 0000000..29be247
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/InitialLoadBalanceResponseOrBuilder.java
@@ -0,0 +1,66 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+public interface InitialLoadBalanceResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.lb.v1.InitialLoadBalanceResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * This is an application layer redirect that indicates the client should use
+   * the specified server for load balancing. When this field is non-empty in
+   * the response, the client should open a separate connection to the
+   * load_balancer_delegate and call the BalanceLoad method. Its length should
+   * be less than 64 bytes.
+   * </pre>
+   *
+   * <code>string load_balancer_delegate = 1;</code>
+   */
+  java.lang.String getLoadBalancerDelegate();
+  /**
+   * <pre>
+   * This is an application layer redirect that indicates the client should use
+   * the specified server for load balancing. When this field is non-empty in
+   * the response, the client should open a separate connection to the
+   * load_balancer_delegate and call the BalanceLoad method. Its length should
+   * be less than 64 bytes.
+   * </pre>
+   *
+   * <code>string load_balancer_delegate = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getLoadBalancerDelegateBytes();
+
+  /**
+   * <pre>
+   * This interval defines how often the client should send the client stats
+   * to the load balancer. Stats should only be reported when the duration is
+   * positive.
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+   */
+  boolean hasClientStatsReportInterval();
+  /**
+   * <pre>
+   * This interval defines how often the client should send the client stats
+   * to the load balancer. Stats should only be reported when the duration is
+   * positive.
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+   */
+  com.google.protobuf.Duration getClientStatsReportInterval();
+  /**
+   * <pre>
+   * This interval defines how often the client should send the client stats
+   * to the load balancer. Stats should only be reported when the duration is
+   * positive.
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration client_stats_report_interval = 2;</code>
+   */
+  com.google.protobuf.DurationOrBuilder getClientStatsReportIntervalOrBuilder();
+}
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalanceRequest.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalanceRequest.java
new file mode 100644
index 0000000..5e7fb27
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalanceRequest.java
@@ -0,0 +1,973 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+/**
+ * Protobuf type {@code grpc.lb.v1.LoadBalanceRequest}
+ */
+public  final class LoadBalanceRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.lb.v1.LoadBalanceRequest)
+    LoadBalanceRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use LoadBalanceRequest.newBuilder() to construct.
+  private LoadBalanceRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private LoadBalanceRequest() {
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private LoadBalanceRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            io.grpc.lb.v1.InitialLoadBalanceRequest.Builder subBuilder = null;
+            if (loadBalanceRequestTypeCase_ == 1) {
+              subBuilder = ((io.grpc.lb.v1.InitialLoadBalanceRequest) loadBalanceRequestType_).toBuilder();
+            }
+            loadBalanceRequestType_ =
+                input.readMessage(io.grpc.lb.v1.InitialLoadBalanceRequest.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.lb.v1.InitialLoadBalanceRequest) loadBalanceRequestType_);
+              loadBalanceRequestType_ = subBuilder.buildPartial();
+            }
+            loadBalanceRequestTypeCase_ = 1;
+            break;
+          }
+          case 18: {
+            io.grpc.lb.v1.ClientStats.Builder subBuilder = null;
+            if (loadBalanceRequestTypeCase_ == 2) {
+              subBuilder = ((io.grpc.lb.v1.ClientStats) loadBalanceRequestType_).toBuilder();
+            }
+            loadBalanceRequestType_ =
+                input.readMessage(io.grpc.lb.v1.ClientStats.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.lb.v1.ClientStats) loadBalanceRequestType_);
+              loadBalanceRequestType_ = subBuilder.buildPartial();
+            }
+            loadBalanceRequestTypeCase_ = 2;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_LoadBalanceRequest_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_LoadBalanceRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.lb.v1.LoadBalanceRequest.class, io.grpc.lb.v1.LoadBalanceRequest.Builder.class);
+  }
+
+  private int loadBalanceRequestTypeCase_ = 0;
+  private java.lang.Object loadBalanceRequestType_;
+  public enum LoadBalanceRequestTypeCase
+      implements com.google.protobuf.Internal.EnumLite {
+    INITIAL_REQUEST(1),
+    CLIENT_STATS(2),
+    LOADBALANCEREQUESTTYPE_NOT_SET(0);
+    private final int value;
+    private LoadBalanceRequestTypeCase(int value) {
+      this.value = value;
+    }
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static LoadBalanceRequestTypeCase valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static LoadBalanceRequestTypeCase forNumber(int value) {
+      switch (value) {
+        case 1: return INITIAL_REQUEST;
+        case 2: return CLIENT_STATS;
+        case 0: return LOADBALANCEREQUESTTYPE_NOT_SET;
+        default: return null;
+      }
+    }
+    public int getNumber() {
+      return this.value;
+    }
+  };
+
+  public LoadBalanceRequestTypeCase
+  getLoadBalanceRequestTypeCase() {
+    return LoadBalanceRequestTypeCase.forNumber(
+        loadBalanceRequestTypeCase_);
+  }
+
+  public static final int INITIAL_REQUEST_FIELD_NUMBER = 1;
+  /**
+   * <pre>
+   * This message should be sent on the first request to the load balancer.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+   */
+  public boolean hasInitialRequest() {
+    return loadBalanceRequestTypeCase_ == 1;
+  }
+  /**
+   * <pre>
+   * This message should be sent on the first request to the load balancer.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+   */
+  public io.grpc.lb.v1.InitialLoadBalanceRequest getInitialRequest() {
+    if (loadBalanceRequestTypeCase_ == 1) {
+       return (io.grpc.lb.v1.InitialLoadBalanceRequest) loadBalanceRequestType_;
+    }
+    return io.grpc.lb.v1.InitialLoadBalanceRequest.getDefaultInstance();
+  }
+  /**
+   * <pre>
+   * This message should be sent on the first request to the load balancer.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+   */
+  public io.grpc.lb.v1.InitialLoadBalanceRequestOrBuilder getInitialRequestOrBuilder() {
+    if (loadBalanceRequestTypeCase_ == 1) {
+       return (io.grpc.lb.v1.InitialLoadBalanceRequest) loadBalanceRequestType_;
+    }
+    return io.grpc.lb.v1.InitialLoadBalanceRequest.getDefaultInstance();
+  }
+
+  public static final int CLIENT_STATS_FIELD_NUMBER = 2;
+  /**
+   * <pre>
+   * The client stats should be periodically reported to the load balancer
+   * based on the duration defined in the InitialLoadBalanceResponse.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+   */
+  public boolean hasClientStats() {
+    return loadBalanceRequestTypeCase_ == 2;
+  }
+  /**
+   * <pre>
+   * The client stats should be periodically reported to the load balancer
+   * based on the duration defined in the InitialLoadBalanceResponse.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+   */
+  public io.grpc.lb.v1.ClientStats getClientStats() {
+    if (loadBalanceRequestTypeCase_ == 2) {
+       return (io.grpc.lb.v1.ClientStats) loadBalanceRequestType_;
+    }
+    return io.grpc.lb.v1.ClientStats.getDefaultInstance();
+  }
+  /**
+   * <pre>
+   * The client stats should be periodically reported to the load balancer
+   * based on the duration defined in the InitialLoadBalanceResponse.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+   */
+  public io.grpc.lb.v1.ClientStatsOrBuilder getClientStatsOrBuilder() {
+    if (loadBalanceRequestTypeCase_ == 2) {
+       return (io.grpc.lb.v1.ClientStats) loadBalanceRequestType_;
+    }
+    return io.grpc.lb.v1.ClientStats.getDefaultInstance();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (loadBalanceRequestTypeCase_ == 1) {
+      output.writeMessage(1, (io.grpc.lb.v1.InitialLoadBalanceRequest) loadBalanceRequestType_);
+    }
+    if (loadBalanceRequestTypeCase_ == 2) {
+      output.writeMessage(2, (io.grpc.lb.v1.ClientStats) loadBalanceRequestType_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (loadBalanceRequestTypeCase_ == 1) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, (io.grpc.lb.v1.InitialLoadBalanceRequest) loadBalanceRequestType_);
+    }
+    if (loadBalanceRequestTypeCase_ == 2) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(2, (io.grpc.lb.v1.ClientStats) loadBalanceRequestType_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.lb.v1.LoadBalanceRequest)) {
+      return super.equals(obj);
+    }
+    io.grpc.lb.v1.LoadBalanceRequest other = (io.grpc.lb.v1.LoadBalanceRequest) obj;
+
+    boolean result = true;
+    result = result && getLoadBalanceRequestTypeCase().equals(
+        other.getLoadBalanceRequestTypeCase());
+    if (!result) return false;
+    switch (loadBalanceRequestTypeCase_) {
+      case 1:
+        result = result && getInitialRequest()
+            .equals(other.getInitialRequest());
+        break;
+      case 2:
+        result = result && getClientStats()
+            .equals(other.getClientStats());
+        break;
+      case 0:
+      default:
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    switch (loadBalanceRequestTypeCase_) {
+      case 1:
+        hash = (37 * hash) + INITIAL_REQUEST_FIELD_NUMBER;
+        hash = (53 * hash) + getInitialRequest().hashCode();
+        break;
+      case 2:
+        hash = (37 * hash) + CLIENT_STATS_FIELD_NUMBER;
+        hash = (53 * hash) + getClientStats().hashCode();
+        break;
+      case 0:
+      default:
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.lb.v1.LoadBalanceRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.LoadBalanceRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.LoadBalanceRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.LoadBalanceRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.LoadBalanceRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.LoadBalanceRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.LoadBalanceRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.LoadBalanceRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.LoadBalanceRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.LoadBalanceRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.LoadBalanceRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.LoadBalanceRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.lb.v1.LoadBalanceRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.lb.v1.LoadBalanceRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.lb.v1.LoadBalanceRequest)
+      io.grpc.lb.v1.LoadBalanceRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_LoadBalanceRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_LoadBalanceRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.lb.v1.LoadBalanceRequest.class, io.grpc.lb.v1.LoadBalanceRequest.Builder.class);
+    }
+
+    // Construct using io.grpc.lb.v1.LoadBalanceRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      loadBalanceRequestTypeCase_ = 0;
+      loadBalanceRequestType_ = null;
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_LoadBalanceRequest_descriptor;
+    }
+
+    public io.grpc.lb.v1.LoadBalanceRequest getDefaultInstanceForType() {
+      return io.grpc.lb.v1.LoadBalanceRequest.getDefaultInstance();
+    }
+
+    public io.grpc.lb.v1.LoadBalanceRequest build() {
+      io.grpc.lb.v1.LoadBalanceRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.lb.v1.LoadBalanceRequest buildPartial() {
+      io.grpc.lb.v1.LoadBalanceRequest result = new io.grpc.lb.v1.LoadBalanceRequest(this);
+      if (loadBalanceRequestTypeCase_ == 1) {
+        if (initialRequestBuilder_ == null) {
+          result.loadBalanceRequestType_ = loadBalanceRequestType_;
+        } else {
+          result.loadBalanceRequestType_ = initialRequestBuilder_.build();
+        }
+      }
+      if (loadBalanceRequestTypeCase_ == 2) {
+        if (clientStatsBuilder_ == null) {
+          result.loadBalanceRequestType_ = loadBalanceRequestType_;
+        } else {
+          result.loadBalanceRequestType_ = clientStatsBuilder_.build();
+        }
+      }
+      result.loadBalanceRequestTypeCase_ = loadBalanceRequestTypeCase_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.lb.v1.LoadBalanceRequest) {
+        return mergeFrom((io.grpc.lb.v1.LoadBalanceRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.lb.v1.LoadBalanceRequest other) {
+      if (other == io.grpc.lb.v1.LoadBalanceRequest.getDefaultInstance()) return this;
+      switch (other.getLoadBalanceRequestTypeCase()) {
+        case INITIAL_REQUEST: {
+          mergeInitialRequest(other.getInitialRequest());
+          break;
+        }
+        case CLIENT_STATS: {
+          mergeClientStats(other.getClientStats());
+          break;
+        }
+        case LOADBALANCEREQUESTTYPE_NOT_SET: {
+          break;
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.lb.v1.LoadBalanceRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.lb.v1.LoadBalanceRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int loadBalanceRequestTypeCase_ = 0;
+    private java.lang.Object loadBalanceRequestType_;
+    public LoadBalanceRequestTypeCase
+        getLoadBalanceRequestTypeCase() {
+      return LoadBalanceRequestTypeCase.forNumber(
+          loadBalanceRequestTypeCase_);
+    }
+
+    public Builder clearLoadBalanceRequestType() {
+      loadBalanceRequestTypeCase_ = 0;
+      loadBalanceRequestType_ = null;
+      onChanged();
+      return this;
+    }
+
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.lb.v1.InitialLoadBalanceRequest, io.grpc.lb.v1.InitialLoadBalanceRequest.Builder, io.grpc.lb.v1.InitialLoadBalanceRequestOrBuilder> initialRequestBuilder_;
+    /**
+     * <pre>
+     * This message should be sent on the first request to the load balancer.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+     */
+    public boolean hasInitialRequest() {
+      return loadBalanceRequestTypeCase_ == 1;
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first request to the load balancer.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+     */
+    public io.grpc.lb.v1.InitialLoadBalanceRequest getInitialRequest() {
+      if (initialRequestBuilder_ == null) {
+        if (loadBalanceRequestTypeCase_ == 1) {
+          return (io.grpc.lb.v1.InitialLoadBalanceRequest) loadBalanceRequestType_;
+        }
+        return io.grpc.lb.v1.InitialLoadBalanceRequest.getDefaultInstance();
+      } else {
+        if (loadBalanceRequestTypeCase_ == 1) {
+          return initialRequestBuilder_.getMessage();
+        }
+        return io.grpc.lb.v1.InitialLoadBalanceRequest.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first request to the load balancer.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+     */
+    public Builder setInitialRequest(io.grpc.lb.v1.InitialLoadBalanceRequest value) {
+      if (initialRequestBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        loadBalanceRequestType_ = value;
+        onChanged();
+      } else {
+        initialRequestBuilder_.setMessage(value);
+      }
+      loadBalanceRequestTypeCase_ = 1;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first request to the load balancer.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+     */
+    public Builder setInitialRequest(
+        io.grpc.lb.v1.InitialLoadBalanceRequest.Builder builderForValue) {
+      if (initialRequestBuilder_ == null) {
+        loadBalanceRequestType_ = builderForValue.build();
+        onChanged();
+      } else {
+        initialRequestBuilder_.setMessage(builderForValue.build());
+      }
+      loadBalanceRequestTypeCase_ = 1;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first request to the load balancer.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+     */
+    public Builder mergeInitialRequest(io.grpc.lb.v1.InitialLoadBalanceRequest value) {
+      if (initialRequestBuilder_ == null) {
+        if (loadBalanceRequestTypeCase_ == 1 &&
+            loadBalanceRequestType_ != io.grpc.lb.v1.InitialLoadBalanceRequest.getDefaultInstance()) {
+          loadBalanceRequestType_ = io.grpc.lb.v1.InitialLoadBalanceRequest.newBuilder((io.grpc.lb.v1.InitialLoadBalanceRequest) loadBalanceRequestType_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          loadBalanceRequestType_ = value;
+        }
+        onChanged();
+      } else {
+        if (loadBalanceRequestTypeCase_ == 1) {
+          initialRequestBuilder_.mergeFrom(value);
+        }
+        initialRequestBuilder_.setMessage(value);
+      }
+      loadBalanceRequestTypeCase_ = 1;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first request to the load balancer.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+     */
+    public Builder clearInitialRequest() {
+      if (initialRequestBuilder_ == null) {
+        if (loadBalanceRequestTypeCase_ == 1) {
+          loadBalanceRequestTypeCase_ = 0;
+          loadBalanceRequestType_ = null;
+          onChanged();
+        }
+      } else {
+        if (loadBalanceRequestTypeCase_ == 1) {
+          loadBalanceRequestTypeCase_ = 0;
+          loadBalanceRequestType_ = null;
+        }
+        initialRequestBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first request to the load balancer.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+     */
+    public io.grpc.lb.v1.InitialLoadBalanceRequest.Builder getInitialRequestBuilder() {
+      return getInitialRequestFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first request to the load balancer.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+     */
+    public io.grpc.lb.v1.InitialLoadBalanceRequestOrBuilder getInitialRequestOrBuilder() {
+      if ((loadBalanceRequestTypeCase_ == 1) && (initialRequestBuilder_ != null)) {
+        return initialRequestBuilder_.getMessageOrBuilder();
+      } else {
+        if (loadBalanceRequestTypeCase_ == 1) {
+          return (io.grpc.lb.v1.InitialLoadBalanceRequest) loadBalanceRequestType_;
+        }
+        return io.grpc.lb.v1.InitialLoadBalanceRequest.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first request to the load balancer.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.lb.v1.InitialLoadBalanceRequest, io.grpc.lb.v1.InitialLoadBalanceRequest.Builder, io.grpc.lb.v1.InitialLoadBalanceRequestOrBuilder> 
+        getInitialRequestFieldBuilder() {
+      if (initialRequestBuilder_ == null) {
+        if (!(loadBalanceRequestTypeCase_ == 1)) {
+          loadBalanceRequestType_ = io.grpc.lb.v1.InitialLoadBalanceRequest.getDefaultInstance();
+        }
+        initialRequestBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.lb.v1.InitialLoadBalanceRequest, io.grpc.lb.v1.InitialLoadBalanceRequest.Builder, io.grpc.lb.v1.InitialLoadBalanceRequestOrBuilder>(
+                (io.grpc.lb.v1.InitialLoadBalanceRequest) loadBalanceRequestType_,
+                getParentForChildren(),
+                isClean());
+        loadBalanceRequestType_ = null;
+      }
+      loadBalanceRequestTypeCase_ = 1;
+      onChanged();;
+      return initialRequestBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.lb.v1.ClientStats, io.grpc.lb.v1.ClientStats.Builder, io.grpc.lb.v1.ClientStatsOrBuilder> clientStatsBuilder_;
+    /**
+     * <pre>
+     * The client stats should be periodically reported to the load balancer
+     * based on the duration defined in the InitialLoadBalanceResponse.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+     */
+    public boolean hasClientStats() {
+      return loadBalanceRequestTypeCase_ == 2;
+    }
+    /**
+     * <pre>
+     * The client stats should be periodically reported to the load balancer
+     * based on the duration defined in the InitialLoadBalanceResponse.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+     */
+    public io.grpc.lb.v1.ClientStats getClientStats() {
+      if (clientStatsBuilder_ == null) {
+        if (loadBalanceRequestTypeCase_ == 2) {
+          return (io.grpc.lb.v1.ClientStats) loadBalanceRequestType_;
+        }
+        return io.grpc.lb.v1.ClientStats.getDefaultInstance();
+      } else {
+        if (loadBalanceRequestTypeCase_ == 2) {
+          return clientStatsBuilder_.getMessage();
+        }
+        return io.grpc.lb.v1.ClientStats.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * The client stats should be periodically reported to the load balancer
+     * based on the duration defined in the InitialLoadBalanceResponse.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+     */
+    public Builder setClientStats(io.grpc.lb.v1.ClientStats value) {
+      if (clientStatsBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        loadBalanceRequestType_ = value;
+        onChanged();
+      } else {
+        clientStatsBuilder_.setMessage(value);
+      }
+      loadBalanceRequestTypeCase_ = 2;
+      return this;
+    }
+    /**
+     * <pre>
+     * The client stats should be periodically reported to the load balancer
+     * based on the duration defined in the InitialLoadBalanceResponse.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+     */
+    public Builder setClientStats(
+        io.grpc.lb.v1.ClientStats.Builder builderForValue) {
+      if (clientStatsBuilder_ == null) {
+        loadBalanceRequestType_ = builderForValue.build();
+        onChanged();
+      } else {
+        clientStatsBuilder_.setMessage(builderForValue.build());
+      }
+      loadBalanceRequestTypeCase_ = 2;
+      return this;
+    }
+    /**
+     * <pre>
+     * The client stats should be periodically reported to the load balancer
+     * based on the duration defined in the InitialLoadBalanceResponse.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+     */
+    public Builder mergeClientStats(io.grpc.lb.v1.ClientStats value) {
+      if (clientStatsBuilder_ == null) {
+        if (loadBalanceRequestTypeCase_ == 2 &&
+            loadBalanceRequestType_ != io.grpc.lb.v1.ClientStats.getDefaultInstance()) {
+          loadBalanceRequestType_ = io.grpc.lb.v1.ClientStats.newBuilder((io.grpc.lb.v1.ClientStats) loadBalanceRequestType_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          loadBalanceRequestType_ = value;
+        }
+        onChanged();
+      } else {
+        if (loadBalanceRequestTypeCase_ == 2) {
+          clientStatsBuilder_.mergeFrom(value);
+        }
+        clientStatsBuilder_.setMessage(value);
+      }
+      loadBalanceRequestTypeCase_ = 2;
+      return this;
+    }
+    /**
+     * <pre>
+     * The client stats should be periodically reported to the load balancer
+     * based on the duration defined in the InitialLoadBalanceResponse.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+     */
+    public Builder clearClientStats() {
+      if (clientStatsBuilder_ == null) {
+        if (loadBalanceRequestTypeCase_ == 2) {
+          loadBalanceRequestTypeCase_ = 0;
+          loadBalanceRequestType_ = null;
+          onChanged();
+        }
+      } else {
+        if (loadBalanceRequestTypeCase_ == 2) {
+          loadBalanceRequestTypeCase_ = 0;
+          loadBalanceRequestType_ = null;
+        }
+        clientStatsBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The client stats should be periodically reported to the load balancer
+     * based on the duration defined in the InitialLoadBalanceResponse.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+     */
+    public io.grpc.lb.v1.ClientStats.Builder getClientStatsBuilder() {
+      return getClientStatsFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The client stats should be periodically reported to the load balancer
+     * based on the duration defined in the InitialLoadBalanceResponse.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+     */
+    public io.grpc.lb.v1.ClientStatsOrBuilder getClientStatsOrBuilder() {
+      if ((loadBalanceRequestTypeCase_ == 2) && (clientStatsBuilder_ != null)) {
+        return clientStatsBuilder_.getMessageOrBuilder();
+      } else {
+        if (loadBalanceRequestTypeCase_ == 2) {
+          return (io.grpc.lb.v1.ClientStats) loadBalanceRequestType_;
+        }
+        return io.grpc.lb.v1.ClientStats.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * The client stats should be periodically reported to the load balancer
+     * based on the duration defined in the InitialLoadBalanceResponse.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.lb.v1.ClientStats, io.grpc.lb.v1.ClientStats.Builder, io.grpc.lb.v1.ClientStatsOrBuilder> 
+        getClientStatsFieldBuilder() {
+      if (clientStatsBuilder_ == null) {
+        if (!(loadBalanceRequestTypeCase_ == 2)) {
+          loadBalanceRequestType_ = io.grpc.lb.v1.ClientStats.getDefaultInstance();
+        }
+        clientStatsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.lb.v1.ClientStats, io.grpc.lb.v1.ClientStats.Builder, io.grpc.lb.v1.ClientStatsOrBuilder>(
+                (io.grpc.lb.v1.ClientStats) loadBalanceRequestType_,
+                getParentForChildren(),
+                isClean());
+        loadBalanceRequestType_ = null;
+      }
+      loadBalanceRequestTypeCase_ = 2;
+      onChanged();;
+      return clientStatsBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.lb.v1.LoadBalanceRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.lb.v1.LoadBalanceRequest)
+  private static final io.grpc.lb.v1.LoadBalanceRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.lb.v1.LoadBalanceRequest();
+  }
+
+  public static io.grpc.lb.v1.LoadBalanceRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<LoadBalanceRequest>
+      PARSER = new com.google.protobuf.AbstractParser<LoadBalanceRequest>() {
+    public LoadBalanceRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new LoadBalanceRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<LoadBalanceRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<LoadBalanceRequest> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.lb.v1.LoadBalanceRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalanceRequestOrBuilder.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalanceRequestOrBuilder.java
new file mode 100644
index 0000000..46d153d
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalanceRequestOrBuilder.java
@@ -0,0 +1,64 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+public interface LoadBalanceRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.lb.v1.LoadBalanceRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * This message should be sent on the first request to the load balancer.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+   */
+  boolean hasInitialRequest();
+  /**
+   * <pre>
+   * This message should be sent on the first request to the load balancer.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+   */
+  io.grpc.lb.v1.InitialLoadBalanceRequest getInitialRequest();
+  /**
+   * <pre>
+   * This message should be sent on the first request to the load balancer.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.InitialLoadBalanceRequest initial_request = 1;</code>
+   */
+  io.grpc.lb.v1.InitialLoadBalanceRequestOrBuilder getInitialRequestOrBuilder();
+
+  /**
+   * <pre>
+   * The client stats should be periodically reported to the load balancer
+   * based on the duration defined in the InitialLoadBalanceResponse.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+   */
+  boolean hasClientStats();
+  /**
+   * <pre>
+   * The client stats should be periodically reported to the load balancer
+   * based on the duration defined in the InitialLoadBalanceResponse.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+   */
+  io.grpc.lb.v1.ClientStats getClientStats();
+  /**
+   * <pre>
+   * The client stats should be periodically reported to the load balancer
+   * based on the duration defined in the InitialLoadBalanceResponse.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.ClientStats client_stats = 2;</code>
+   */
+  io.grpc.lb.v1.ClientStatsOrBuilder getClientStatsOrBuilder();
+
+  public io.grpc.lb.v1.LoadBalanceRequest.LoadBalanceRequestTypeCase getLoadBalanceRequestTypeCase();
+}
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalanceResponse.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalanceResponse.java
new file mode 100644
index 0000000..d5f6e20
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalanceResponse.java
@@ -0,0 +1,973 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+/**
+ * Protobuf type {@code grpc.lb.v1.LoadBalanceResponse}
+ */
+public  final class LoadBalanceResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.lb.v1.LoadBalanceResponse)
+    LoadBalanceResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use LoadBalanceResponse.newBuilder() to construct.
+  private LoadBalanceResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private LoadBalanceResponse() {
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private LoadBalanceResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            io.grpc.lb.v1.InitialLoadBalanceResponse.Builder subBuilder = null;
+            if (loadBalanceResponseTypeCase_ == 1) {
+              subBuilder = ((io.grpc.lb.v1.InitialLoadBalanceResponse) loadBalanceResponseType_).toBuilder();
+            }
+            loadBalanceResponseType_ =
+                input.readMessage(io.grpc.lb.v1.InitialLoadBalanceResponse.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.lb.v1.InitialLoadBalanceResponse) loadBalanceResponseType_);
+              loadBalanceResponseType_ = subBuilder.buildPartial();
+            }
+            loadBalanceResponseTypeCase_ = 1;
+            break;
+          }
+          case 18: {
+            io.grpc.lb.v1.ServerList.Builder subBuilder = null;
+            if (loadBalanceResponseTypeCase_ == 2) {
+              subBuilder = ((io.grpc.lb.v1.ServerList) loadBalanceResponseType_).toBuilder();
+            }
+            loadBalanceResponseType_ =
+                input.readMessage(io.grpc.lb.v1.ServerList.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.lb.v1.ServerList) loadBalanceResponseType_);
+              loadBalanceResponseType_ = subBuilder.buildPartial();
+            }
+            loadBalanceResponseTypeCase_ = 2;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_LoadBalanceResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_LoadBalanceResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.lb.v1.LoadBalanceResponse.class, io.grpc.lb.v1.LoadBalanceResponse.Builder.class);
+  }
+
+  private int loadBalanceResponseTypeCase_ = 0;
+  private java.lang.Object loadBalanceResponseType_;
+  public enum LoadBalanceResponseTypeCase
+      implements com.google.protobuf.Internal.EnumLite {
+    INITIAL_RESPONSE(1),
+    SERVER_LIST(2),
+    LOADBALANCERESPONSETYPE_NOT_SET(0);
+    private final int value;
+    private LoadBalanceResponseTypeCase(int value) {
+      this.value = value;
+    }
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static LoadBalanceResponseTypeCase valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static LoadBalanceResponseTypeCase forNumber(int value) {
+      switch (value) {
+        case 1: return INITIAL_RESPONSE;
+        case 2: return SERVER_LIST;
+        case 0: return LOADBALANCERESPONSETYPE_NOT_SET;
+        default: return null;
+      }
+    }
+    public int getNumber() {
+      return this.value;
+    }
+  };
+
+  public LoadBalanceResponseTypeCase
+  getLoadBalanceResponseTypeCase() {
+    return LoadBalanceResponseTypeCase.forNumber(
+        loadBalanceResponseTypeCase_);
+  }
+
+  public static final int INITIAL_RESPONSE_FIELD_NUMBER = 1;
+  /**
+   * <pre>
+   * This message should be sent on the first response to the client.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+   */
+  public boolean hasInitialResponse() {
+    return loadBalanceResponseTypeCase_ == 1;
+  }
+  /**
+   * <pre>
+   * This message should be sent on the first response to the client.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+   */
+  public io.grpc.lb.v1.InitialLoadBalanceResponse getInitialResponse() {
+    if (loadBalanceResponseTypeCase_ == 1) {
+       return (io.grpc.lb.v1.InitialLoadBalanceResponse) loadBalanceResponseType_;
+    }
+    return io.grpc.lb.v1.InitialLoadBalanceResponse.getDefaultInstance();
+  }
+  /**
+   * <pre>
+   * This message should be sent on the first response to the client.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+   */
+  public io.grpc.lb.v1.InitialLoadBalanceResponseOrBuilder getInitialResponseOrBuilder() {
+    if (loadBalanceResponseTypeCase_ == 1) {
+       return (io.grpc.lb.v1.InitialLoadBalanceResponse) loadBalanceResponseType_;
+    }
+    return io.grpc.lb.v1.InitialLoadBalanceResponse.getDefaultInstance();
+  }
+
+  public static final int SERVER_LIST_FIELD_NUMBER = 2;
+  /**
+   * <pre>
+   * Contains the list of servers selected by the load balancer. The client
+   * should send requests to these servers in the specified order.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+   */
+  public boolean hasServerList() {
+    return loadBalanceResponseTypeCase_ == 2;
+  }
+  /**
+   * <pre>
+   * Contains the list of servers selected by the load balancer. The client
+   * should send requests to these servers in the specified order.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+   */
+  public io.grpc.lb.v1.ServerList getServerList() {
+    if (loadBalanceResponseTypeCase_ == 2) {
+       return (io.grpc.lb.v1.ServerList) loadBalanceResponseType_;
+    }
+    return io.grpc.lb.v1.ServerList.getDefaultInstance();
+  }
+  /**
+   * <pre>
+   * Contains the list of servers selected by the load balancer. The client
+   * should send requests to these servers in the specified order.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+   */
+  public io.grpc.lb.v1.ServerListOrBuilder getServerListOrBuilder() {
+    if (loadBalanceResponseTypeCase_ == 2) {
+       return (io.grpc.lb.v1.ServerList) loadBalanceResponseType_;
+    }
+    return io.grpc.lb.v1.ServerList.getDefaultInstance();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (loadBalanceResponseTypeCase_ == 1) {
+      output.writeMessage(1, (io.grpc.lb.v1.InitialLoadBalanceResponse) loadBalanceResponseType_);
+    }
+    if (loadBalanceResponseTypeCase_ == 2) {
+      output.writeMessage(2, (io.grpc.lb.v1.ServerList) loadBalanceResponseType_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (loadBalanceResponseTypeCase_ == 1) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, (io.grpc.lb.v1.InitialLoadBalanceResponse) loadBalanceResponseType_);
+    }
+    if (loadBalanceResponseTypeCase_ == 2) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(2, (io.grpc.lb.v1.ServerList) loadBalanceResponseType_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.lb.v1.LoadBalanceResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.lb.v1.LoadBalanceResponse other = (io.grpc.lb.v1.LoadBalanceResponse) obj;
+
+    boolean result = true;
+    result = result && getLoadBalanceResponseTypeCase().equals(
+        other.getLoadBalanceResponseTypeCase());
+    if (!result) return false;
+    switch (loadBalanceResponseTypeCase_) {
+      case 1:
+        result = result && getInitialResponse()
+            .equals(other.getInitialResponse());
+        break;
+      case 2:
+        result = result && getServerList()
+            .equals(other.getServerList());
+        break;
+      case 0:
+      default:
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    switch (loadBalanceResponseTypeCase_) {
+      case 1:
+        hash = (37 * hash) + INITIAL_RESPONSE_FIELD_NUMBER;
+        hash = (53 * hash) + getInitialResponse().hashCode();
+        break;
+      case 2:
+        hash = (37 * hash) + SERVER_LIST_FIELD_NUMBER;
+        hash = (53 * hash) + getServerList().hashCode();
+        break;
+      case 0:
+      default:
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.lb.v1.LoadBalanceResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.LoadBalanceResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.LoadBalanceResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.LoadBalanceResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.LoadBalanceResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.LoadBalanceResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.LoadBalanceResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.LoadBalanceResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.LoadBalanceResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.LoadBalanceResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.LoadBalanceResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.LoadBalanceResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.lb.v1.LoadBalanceResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.lb.v1.LoadBalanceResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.lb.v1.LoadBalanceResponse)
+      io.grpc.lb.v1.LoadBalanceResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_LoadBalanceResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_LoadBalanceResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.lb.v1.LoadBalanceResponse.class, io.grpc.lb.v1.LoadBalanceResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.lb.v1.LoadBalanceResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      loadBalanceResponseTypeCase_ = 0;
+      loadBalanceResponseType_ = null;
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_LoadBalanceResponse_descriptor;
+    }
+
+    public io.grpc.lb.v1.LoadBalanceResponse getDefaultInstanceForType() {
+      return io.grpc.lb.v1.LoadBalanceResponse.getDefaultInstance();
+    }
+
+    public io.grpc.lb.v1.LoadBalanceResponse build() {
+      io.grpc.lb.v1.LoadBalanceResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.lb.v1.LoadBalanceResponse buildPartial() {
+      io.grpc.lb.v1.LoadBalanceResponse result = new io.grpc.lb.v1.LoadBalanceResponse(this);
+      if (loadBalanceResponseTypeCase_ == 1) {
+        if (initialResponseBuilder_ == null) {
+          result.loadBalanceResponseType_ = loadBalanceResponseType_;
+        } else {
+          result.loadBalanceResponseType_ = initialResponseBuilder_.build();
+        }
+      }
+      if (loadBalanceResponseTypeCase_ == 2) {
+        if (serverListBuilder_ == null) {
+          result.loadBalanceResponseType_ = loadBalanceResponseType_;
+        } else {
+          result.loadBalanceResponseType_ = serverListBuilder_.build();
+        }
+      }
+      result.loadBalanceResponseTypeCase_ = loadBalanceResponseTypeCase_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.lb.v1.LoadBalanceResponse) {
+        return mergeFrom((io.grpc.lb.v1.LoadBalanceResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.lb.v1.LoadBalanceResponse other) {
+      if (other == io.grpc.lb.v1.LoadBalanceResponse.getDefaultInstance()) return this;
+      switch (other.getLoadBalanceResponseTypeCase()) {
+        case INITIAL_RESPONSE: {
+          mergeInitialResponse(other.getInitialResponse());
+          break;
+        }
+        case SERVER_LIST: {
+          mergeServerList(other.getServerList());
+          break;
+        }
+        case LOADBALANCERESPONSETYPE_NOT_SET: {
+          break;
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.lb.v1.LoadBalanceResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.lb.v1.LoadBalanceResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int loadBalanceResponseTypeCase_ = 0;
+    private java.lang.Object loadBalanceResponseType_;
+    public LoadBalanceResponseTypeCase
+        getLoadBalanceResponseTypeCase() {
+      return LoadBalanceResponseTypeCase.forNumber(
+          loadBalanceResponseTypeCase_);
+    }
+
+    public Builder clearLoadBalanceResponseType() {
+      loadBalanceResponseTypeCase_ = 0;
+      loadBalanceResponseType_ = null;
+      onChanged();
+      return this;
+    }
+
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.lb.v1.InitialLoadBalanceResponse, io.grpc.lb.v1.InitialLoadBalanceResponse.Builder, io.grpc.lb.v1.InitialLoadBalanceResponseOrBuilder> initialResponseBuilder_;
+    /**
+     * <pre>
+     * This message should be sent on the first response to the client.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+     */
+    public boolean hasInitialResponse() {
+      return loadBalanceResponseTypeCase_ == 1;
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first response to the client.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+     */
+    public io.grpc.lb.v1.InitialLoadBalanceResponse getInitialResponse() {
+      if (initialResponseBuilder_ == null) {
+        if (loadBalanceResponseTypeCase_ == 1) {
+          return (io.grpc.lb.v1.InitialLoadBalanceResponse) loadBalanceResponseType_;
+        }
+        return io.grpc.lb.v1.InitialLoadBalanceResponse.getDefaultInstance();
+      } else {
+        if (loadBalanceResponseTypeCase_ == 1) {
+          return initialResponseBuilder_.getMessage();
+        }
+        return io.grpc.lb.v1.InitialLoadBalanceResponse.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first response to the client.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+     */
+    public Builder setInitialResponse(io.grpc.lb.v1.InitialLoadBalanceResponse value) {
+      if (initialResponseBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        loadBalanceResponseType_ = value;
+        onChanged();
+      } else {
+        initialResponseBuilder_.setMessage(value);
+      }
+      loadBalanceResponseTypeCase_ = 1;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first response to the client.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+     */
+    public Builder setInitialResponse(
+        io.grpc.lb.v1.InitialLoadBalanceResponse.Builder builderForValue) {
+      if (initialResponseBuilder_ == null) {
+        loadBalanceResponseType_ = builderForValue.build();
+        onChanged();
+      } else {
+        initialResponseBuilder_.setMessage(builderForValue.build());
+      }
+      loadBalanceResponseTypeCase_ = 1;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first response to the client.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+     */
+    public Builder mergeInitialResponse(io.grpc.lb.v1.InitialLoadBalanceResponse value) {
+      if (initialResponseBuilder_ == null) {
+        if (loadBalanceResponseTypeCase_ == 1 &&
+            loadBalanceResponseType_ != io.grpc.lb.v1.InitialLoadBalanceResponse.getDefaultInstance()) {
+          loadBalanceResponseType_ = io.grpc.lb.v1.InitialLoadBalanceResponse.newBuilder((io.grpc.lb.v1.InitialLoadBalanceResponse) loadBalanceResponseType_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          loadBalanceResponseType_ = value;
+        }
+        onChanged();
+      } else {
+        if (loadBalanceResponseTypeCase_ == 1) {
+          initialResponseBuilder_.mergeFrom(value);
+        }
+        initialResponseBuilder_.setMessage(value);
+      }
+      loadBalanceResponseTypeCase_ = 1;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first response to the client.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+     */
+    public Builder clearInitialResponse() {
+      if (initialResponseBuilder_ == null) {
+        if (loadBalanceResponseTypeCase_ == 1) {
+          loadBalanceResponseTypeCase_ = 0;
+          loadBalanceResponseType_ = null;
+          onChanged();
+        }
+      } else {
+        if (loadBalanceResponseTypeCase_ == 1) {
+          loadBalanceResponseTypeCase_ = 0;
+          loadBalanceResponseType_ = null;
+        }
+        initialResponseBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first response to the client.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+     */
+    public io.grpc.lb.v1.InitialLoadBalanceResponse.Builder getInitialResponseBuilder() {
+      return getInitialResponseFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first response to the client.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+     */
+    public io.grpc.lb.v1.InitialLoadBalanceResponseOrBuilder getInitialResponseOrBuilder() {
+      if ((loadBalanceResponseTypeCase_ == 1) && (initialResponseBuilder_ != null)) {
+        return initialResponseBuilder_.getMessageOrBuilder();
+      } else {
+        if (loadBalanceResponseTypeCase_ == 1) {
+          return (io.grpc.lb.v1.InitialLoadBalanceResponse) loadBalanceResponseType_;
+        }
+        return io.grpc.lb.v1.InitialLoadBalanceResponse.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * This message should be sent on the first response to the client.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.lb.v1.InitialLoadBalanceResponse, io.grpc.lb.v1.InitialLoadBalanceResponse.Builder, io.grpc.lb.v1.InitialLoadBalanceResponseOrBuilder> 
+        getInitialResponseFieldBuilder() {
+      if (initialResponseBuilder_ == null) {
+        if (!(loadBalanceResponseTypeCase_ == 1)) {
+          loadBalanceResponseType_ = io.grpc.lb.v1.InitialLoadBalanceResponse.getDefaultInstance();
+        }
+        initialResponseBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.lb.v1.InitialLoadBalanceResponse, io.grpc.lb.v1.InitialLoadBalanceResponse.Builder, io.grpc.lb.v1.InitialLoadBalanceResponseOrBuilder>(
+                (io.grpc.lb.v1.InitialLoadBalanceResponse) loadBalanceResponseType_,
+                getParentForChildren(),
+                isClean());
+        loadBalanceResponseType_ = null;
+      }
+      loadBalanceResponseTypeCase_ = 1;
+      onChanged();;
+      return initialResponseBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.lb.v1.ServerList, io.grpc.lb.v1.ServerList.Builder, io.grpc.lb.v1.ServerListOrBuilder> serverListBuilder_;
+    /**
+     * <pre>
+     * Contains the list of servers selected by the load balancer. The client
+     * should send requests to these servers in the specified order.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+     */
+    public boolean hasServerList() {
+      return loadBalanceResponseTypeCase_ == 2;
+    }
+    /**
+     * <pre>
+     * Contains the list of servers selected by the load balancer. The client
+     * should send requests to these servers in the specified order.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+     */
+    public io.grpc.lb.v1.ServerList getServerList() {
+      if (serverListBuilder_ == null) {
+        if (loadBalanceResponseTypeCase_ == 2) {
+          return (io.grpc.lb.v1.ServerList) loadBalanceResponseType_;
+        }
+        return io.grpc.lb.v1.ServerList.getDefaultInstance();
+      } else {
+        if (loadBalanceResponseTypeCase_ == 2) {
+          return serverListBuilder_.getMessage();
+        }
+        return io.grpc.lb.v1.ServerList.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * Contains the list of servers selected by the load balancer. The client
+     * should send requests to these servers in the specified order.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+     */
+    public Builder setServerList(io.grpc.lb.v1.ServerList value) {
+      if (serverListBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        loadBalanceResponseType_ = value;
+        onChanged();
+      } else {
+        serverListBuilder_.setMessage(value);
+      }
+      loadBalanceResponseTypeCase_ = 2;
+      return this;
+    }
+    /**
+     * <pre>
+     * Contains the list of servers selected by the load balancer. The client
+     * should send requests to these servers in the specified order.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+     */
+    public Builder setServerList(
+        io.grpc.lb.v1.ServerList.Builder builderForValue) {
+      if (serverListBuilder_ == null) {
+        loadBalanceResponseType_ = builderForValue.build();
+        onChanged();
+      } else {
+        serverListBuilder_.setMessage(builderForValue.build());
+      }
+      loadBalanceResponseTypeCase_ = 2;
+      return this;
+    }
+    /**
+     * <pre>
+     * Contains the list of servers selected by the load balancer. The client
+     * should send requests to these servers in the specified order.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+     */
+    public Builder mergeServerList(io.grpc.lb.v1.ServerList value) {
+      if (serverListBuilder_ == null) {
+        if (loadBalanceResponseTypeCase_ == 2 &&
+            loadBalanceResponseType_ != io.grpc.lb.v1.ServerList.getDefaultInstance()) {
+          loadBalanceResponseType_ = io.grpc.lb.v1.ServerList.newBuilder((io.grpc.lb.v1.ServerList) loadBalanceResponseType_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          loadBalanceResponseType_ = value;
+        }
+        onChanged();
+      } else {
+        if (loadBalanceResponseTypeCase_ == 2) {
+          serverListBuilder_.mergeFrom(value);
+        }
+        serverListBuilder_.setMessage(value);
+      }
+      loadBalanceResponseTypeCase_ = 2;
+      return this;
+    }
+    /**
+     * <pre>
+     * Contains the list of servers selected by the load balancer. The client
+     * should send requests to these servers in the specified order.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+     */
+    public Builder clearServerList() {
+      if (serverListBuilder_ == null) {
+        if (loadBalanceResponseTypeCase_ == 2) {
+          loadBalanceResponseTypeCase_ = 0;
+          loadBalanceResponseType_ = null;
+          onChanged();
+        }
+      } else {
+        if (loadBalanceResponseTypeCase_ == 2) {
+          loadBalanceResponseTypeCase_ = 0;
+          loadBalanceResponseType_ = null;
+        }
+        serverListBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Contains the list of servers selected by the load balancer. The client
+     * should send requests to these servers in the specified order.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+     */
+    public io.grpc.lb.v1.ServerList.Builder getServerListBuilder() {
+      return getServerListFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Contains the list of servers selected by the load balancer. The client
+     * should send requests to these servers in the specified order.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+     */
+    public io.grpc.lb.v1.ServerListOrBuilder getServerListOrBuilder() {
+      if ((loadBalanceResponseTypeCase_ == 2) && (serverListBuilder_ != null)) {
+        return serverListBuilder_.getMessageOrBuilder();
+      } else {
+        if (loadBalanceResponseTypeCase_ == 2) {
+          return (io.grpc.lb.v1.ServerList) loadBalanceResponseType_;
+        }
+        return io.grpc.lb.v1.ServerList.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * Contains the list of servers selected by the load balancer. The client
+     * should send requests to these servers in the specified order.
+     * </pre>
+     *
+     * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.lb.v1.ServerList, io.grpc.lb.v1.ServerList.Builder, io.grpc.lb.v1.ServerListOrBuilder> 
+        getServerListFieldBuilder() {
+      if (serverListBuilder_ == null) {
+        if (!(loadBalanceResponseTypeCase_ == 2)) {
+          loadBalanceResponseType_ = io.grpc.lb.v1.ServerList.getDefaultInstance();
+        }
+        serverListBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.lb.v1.ServerList, io.grpc.lb.v1.ServerList.Builder, io.grpc.lb.v1.ServerListOrBuilder>(
+                (io.grpc.lb.v1.ServerList) loadBalanceResponseType_,
+                getParentForChildren(),
+                isClean());
+        loadBalanceResponseType_ = null;
+      }
+      loadBalanceResponseTypeCase_ = 2;
+      onChanged();;
+      return serverListBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.lb.v1.LoadBalanceResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.lb.v1.LoadBalanceResponse)
+  private static final io.grpc.lb.v1.LoadBalanceResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.lb.v1.LoadBalanceResponse();
+  }
+
+  public static io.grpc.lb.v1.LoadBalanceResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<LoadBalanceResponse>
+      PARSER = new com.google.protobuf.AbstractParser<LoadBalanceResponse>() {
+    public LoadBalanceResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new LoadBalanceResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<LoadBalanceResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<LoadBalanceResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.lb.v1.LoadBalanceResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalanceResponseOrBuilder.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalanceResponseOrBuilder.java
new file mode 100644
index 0000000..038ca57
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalanceResponseOrBuilder.java
@@ -0,0 +1,64 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+public interface LoadBalanceResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.lb.v1.LoadBalanceResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * This message should be sent on the first response to the client.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+   */
+  boolean hasInitialResponse();
+  /**
+   * <pre>
+   * This message should be sent on the first response to the client.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+   */
+  io.grpc.lb.v1.InitialLoadBalanceResponse getInitialResponse();
+  /**
+   * <pre>
+   * This message should be sent on the first response to the client.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.InitialLoadBalanceResponse initial_response = 1;</code>
+   */
+  io.grpc.lb.v1.InitialLoadBalanceResponseOrBuilder getInitialResponseOrBuilder();
+
+  /**
+   * <pre>
+   * Contains the list of servers selected by the load balancer. The client
+   * should send requests to these servers in the specified order.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+   */
+  boolean hasServerList();
+  /**
+   * <pre>
+   * Contains the list of servers selected by the load balancer. The client
+   * should send requests to these servers in the specified order.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+   */
+  io.grpc.lb.v1.ServerList getServerList();
+  /**
+   * <pre>
+   * Contains the list of servers selected by the load balancer. The client
+   * should send requests to these servers in the specified order.
+   * </pre>
+   *
+   * <code>.grpc.lb.v1.ServerList server_list = 2;</code>
+   */
+  io.grpc.lb.v1.ServerListOrBuilder getServerListOrBuilder();
+
+  public io.grpc.lb.v1.LoadBalanceResponse.LoadBalanceResponseTypeCase getLoadBalanceResponseTypeCase();
+}
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalancerProto.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalancerProto.java
new file mode 100644
index 0000000..ac36ec2
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/LoadBalancerProto.java
@@ -0,0 +1,168 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+public final class LoadBalancerProto {
+  private LoadBalancerProto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_lb_v1_LoadBalanceRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_lb_v1_LoadBalanceRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_lb_v1_InitialLoadBalanceRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_lb_v1_InitialLoadBalanceRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_lb_v1_ClientStatsPerToken_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_lb_v1_ClientStatsPerToken_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_lb_v1_ClientStats_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_lb_v1_ClientStats_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_lb_v1_LoadBalanceResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_lb_v1_LoadBalanceResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_lb_v1_InitialLoadBalanceResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_lb_v1_InitialLoadBalanceResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_lb_v1_ServerList_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_lb_v1_ServerList_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_lb_v1_Server_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_lb_v1_Server_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\036grpc/lb/v1/load_balancer.proto\022\ngrpc.l" +
+      "b.v1\032\036google/protobuf/duration.proto\032\037go" +
+      "ogle/protobuf/timestamp.proto\"\244\001\n\022LoadBa" +
+      "lanceRequest\022@\n\017initial_request\030\001 \001(\0132%." +
+      "grpc.lb.v1.InitialLoadBalanceRequestH\000\022/" +
+      "\n\014client_stats\030\002 \001(\0132\027.grpc.lb.v1.Client" +
+      "StatsH\000B\033\n\031load_balance_request_type\")\n\031" +
+      "InitialLoadBalanceRequest\022\014\n\004name\030\001 \001(\t\"" +
+      "D\n\023ClientStatsPerToken\022\032\n\022load_balance_t" +
+      "oken\030\001 \001(\t\022\021\n\tnum_calls\030\002 \001(\003\"\244\002\n\013Client" +
+      "Stats\022-\n\ttimestamp\030\001 \001(\0132\032.google.protob" +
+      "uf.Timestamp\022\031\n\021num_calls_started\030\002 \001(\003\022" +
+      "\032\n\022num_calls_finished\030\003 \001(\003\0225\n-num_calls" +
+      "_finished_with_client_failed_to_send\030\006 \001" +
+      "(\003\022)\n!num_calls_finished_known_received\030" +
+      "\007 \001(\003\022A\n\030calls_finished_with_drop\030\010 \003(\0132" +
+      "\037.grpc.lb.v1.ClientStatsPerTokenJ\004\010\004\020\005J\004" +
+      "\010\005\020\006\"\246\001\n\023LoadBalanceResponse\022B\n\020initial_" +
+      "response\030\001 \001(\0132&.grpc.lb.v1.InitialLoadB" +
+      "alanceResponseH\000\022-\n\013server_list\030\002 \001(\0132\026." +
+      "grpc.lb.v1.ServerListH\000B\034\n\032load_balance_" +
+      "response_type\"}\n\032InitialLoadBalanceRespo" +
+      "nse\022\036\n\026load_balancer_delegate\030\001 \001(\t\022?\n\034c" +
+      "lient_stats_report_interval\030\002 \001(\0132\031.goog" +
+      "le.protobuf.Duration\"7\n\nServerList\022#\n\007se" +
+      "rvers\030\001 \003(\0132\022.grpc.lb.v1.ServerJ\004\010\003\020\004\"Z\n" +
+      "\006Server\022\022\n\nip_address\030\001 \001(\014\022\014\n\004port\030\002 \001(" +
+      "\005\022\032\n\022load_balance_token\030\003 \001(\t\022\014\n\004drop\030\004 " +
+      "\001(\010J\004\010\005\020\0062b\n\014LoadBalancer\022R\n\013BalanceLoad" +
+      "\022\036.grpc.lb.v1.LoadBalanceRequest\032\037.grpc." +
+      "lb.v1.LoadBalanceResponse(\0010\001BW\n\rio.grpc" +
+      ".lb.v1B\021LoadBalancerProtoP\001Z1google.gola" +
+      "ng.org/grpc/balancer/grpclb/grpc_lb_v1b\006" +
+      "proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          com.google.protobuf.DurationProto.getDescriptor(),
+          com.google.protobuf.TimestampProto.getDescriptor(),
+        }, assigner);
+    internal_static_grpc_lb_v1_LoadBalanceRequest_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_lb_v1_LoadBalanceRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_lb_v1_LoadBalanceRequest_descriptor,
+        new java.lang.String[] { "InitialRequest", "ClientStats", "LoadBalanceRequestType", });
+    internal_static_grpc_lb_v1_InitialLoadBalanceRequest_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_lb_v1_InitialLoadBalanceRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_lb_v1_InitialLoadBalanceRequest_descriptor,
+        new java.lang.String[] { "Name", });
+    internal_static_grpc_lb_v1_ClientStatsPerToken_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_lb_v1_ClientStatsPerToken_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_lb_v1_ClientStatsPerToken_descriptor,
+        new java.lang.String[] { "LoadBalanceToken", "NumCalls", });
+    internal_static_grpc_lb_v1_ClientStats_descriptor =
+      getDescriptor().getMessageTypes().get(3);
+    internal_static_grpc_lb_v1_ClientStats_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_lb_v1_ClientStats_descriptor,
+        new java.lang.String[] { "Timestamp", "NumCallsStarted", "NumCallsFinished", "NumCallsFinishedWithClientFailedToSend", "NumCallsFinishedKnownReceived", "CallsFinishedWithDrop", });
+    internal_static_grpc_lb_v1_LoadBalanceResponse_descriptor =
+      getDescriptor().getMessageTypes().get(4);
+    internal_static_grpc_lb_v1_LoadBalanceResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_lb_v1_LoadBalanceResponse_descriptor,
+        new java.lang.String[] { "InitialResponse", "ServerList", "LoadBalanceResponseType", });
+    internal_static_grpc_lb_v1_InitialLoadBalanceResponse_descriptor =
+      getDescriptor().getMessageTypes().get(5);
+    internal_static_grpc_lb_v1_InitialLoadBalanceResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_lb_v1_InitialLoadBalanceResponse_descriptor,
+        new java.lang.String[] { "LoadBalancerDelegate", "ClientStatsReportInterval", });
+    internal_static_grpc_lb_v1_ServerList_descriptor =
+      getDescriptor().getMessageTypes().get(6);
+    internal_static_grpc_lb_v1_ServerList_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_lb_v1_ServerList_descriptor,
+        new java.lang.String[] { "Servers", });
+    internal_static_grpc_lb_v1_Server_descriptor =
+      getDescriptor().getMessageTypes().get(7);
+    internal_static_grpc_lb_v1_Server_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_lb_v1_Server_descriptor,
+        new java.lang.String[] { "IpAddress", "Port", "LoadBalanceToken", "Drop", });
+    com.google.protobuf.DurationProto.getDescriptor();
+    com.google.protobuf.TimestampProto.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/Server.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/Server.java
new file mode 100644
index 0000000..2f80bf0
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/Server.java
@@ -0,0 +1,827 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+/**
+ * <pre>
+ * Contains server information. When the drop field is not true, use the other
+ * fields.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.lb.v1.Server}
+ */
+public  final class Server extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.lb.v1.Server)
+    ServerOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Server.newBuilder() to construct.
+  private Server(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Server() {
+    ipAddress_ = com.google.protobuf.ByteString.EMPTY;
+    port_ = 0;
+    loadBalanceToken_ = "";
+    drop_ = false;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Server(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+
+            ipAddress_ = input.readBytes();
+            break;
+          }
+          case 16: {
+
+            port_ = input.readInt32();
+            break;
+          }
+          case 26: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            loadBalanceToken_ = s;
+            break;
+          }
+          case 32: {
+
+            drop_ = input.readBool();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_Server_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_Server_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.lb.v1.Server.class, io.grpc.lb.v1.Server.Builder.class);
+  }
+
+  public static final int IP_ADDRESS_FIELD_NUMBER = 1;
+  private com.google.protobuf.ByteString ipAddress_;
+  /**
+   * <pre>
+   * A resolved address for the server, serialized in network-byte-order. It may
+   * either be an IPv4 or IPv6 address.
+   * </pre>
+   *
+   * <code>bytes ip_address = 1;</code>
+   */
+  public com.google.protobuf.ByteString getIpAddress() {
+    return ipAddress_;
+  }
+
+  public static final int PORT_FIELD_NUMBER = 2;
+  private int port_;
+  /**
+   * <pre>
+   * A resolved port number for the server.
+   * </pre>
+   *
+   * <code>int32 port = 2;</code>
+   */
+  public int getPort() {
+    return port_;
+  }
+
+  public static final int LOAD_BALANCE_TOKEN_FIELD_NUMBER = 3;
+  private volatile java.lang.Object loadBalanceToken_;
+  /**
+   * <pre>
+   * An opaque but printable token for load reporting. The client must include
+   * the token of the picked server into the initial metadata when it starts a
+   * call to that server. The token is used by the server to verify the request
+   * and to allow the server to report load to the gRPC LB system. The token is
+   * also used in client stats for reporting dropped calls.
+   * Its length can be variable but must be less than 50 bytes.
+   * </pre>
+   *
+   * <code>string load_balance_token = 3;</code>
+   */
+  public java.lang.String getLoadBalanceToken() {
+    java.lang.Object ref = loadBalanceToken_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      loadBalanceToken_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * An opaque but printable token for load reporting. The client must include
+   * the token of the picked server into the initial metadata when it starts a
+   * call to that server. The token is used by the server to verify the request
+   * and to allow the server to report load to the gRPC LB system. The token is
+   * also used in client stats for reporting dropped calls.
+   * Its length can be variable but must be less than 50 bytes.
+   * </pre>
+   *
+   * <code>string load_balance_token = 3;</code>
+   */
+  public com.google.protobuf.ByteString
+      getLoadBalanceTokenBytes() {
+    java.lang.Object ref = loadBalanceToken_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      loadBalanceToken_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int DROP_FIELD_NUMBER = 4;
+  private boolean drop_;
+  /**
+   * <pre>
+   * Indicates whether this particular request should be dropped by the client.
+   * If the request is dropped, there will be a corresponding entry in
+   * ClientStats.calls_finished_with_drop.
+   * </pre>
+   *
+   * <code>bool drop = 4;</code>
+   */
+  public boolean getDrop() {
+    return drop_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!ipAddress_.isEmpty()) {
+      output.writeBytes(1, ipAddress_);
+    }
+    if (port_ != 0) {
+      output.writeInt32(2, port_);
+    }
+    if (!getLoadBalanceTokenBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 3, loadBalanceToken_);
+    }
+    if (drop_ != false) {
+      output.writeBool(4, drop_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!ipAddress_.isEmpty()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBytesSize(1, ipAddress_);
+    }
+    if (port_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt32Size(2, port_);
+    }
+    if (!getLoadBalanceTokenBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, loadBalanceToken_);
+    }
+    if (drop_ != false) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBoolSize(4, drop_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.lb.v1.Server)) {
+      return super.equals(obj);
+    }
+    io.grpc.lb.v1.Server other = (io.grpc.lb.v1.Server) obj;
+
+    boolean result = true;
+    result = result && getIpAddress()
+        .equals(other.getIpAddress());
+    result = result && (getPort()
+        == other.getPort());
+    result = result && getLoadBalanceToken()
+        .equals(other.getLoadBalanceToken());
+    result = result && (getDrop()
+        == other.getDrop());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + IP_ADDRESS_FIELD_NUMBER;
+    hash = (53 * hash) + getIpAddress().hashCode();
+    hash = (37 * hash) + PORT_FIELD_NUMBER;
+    hash = (53 * hash) + getPort();
+    hash = (37 * hash) + LOAD_BALANCE_TOKEN_FIELD_NUMBER;
+    hash = (53 * hash) + getLoadBalanceToken().hashCode();
+    hash = (37 * hash) + DROP_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+        getDrop());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.lb.v1.Server parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.Server parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.Server parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.Server parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.Server parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.Server parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.Server parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.Server parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.Server parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.Server parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.Server parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.Server parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.lb.v1.Server prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Contains server information. When the drop field is not true, use the other
+   * fields.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.lb.v1.Server}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.lb.v1.Server)
+      io.grpc.lb.v1.ServerOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_Server_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_Server_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.lb.v1.Server.class, io.grpc.lb.v1.Server.Builder.class);
+    }
+
+    // Construct using io.grpc.lb.v1.Server.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      ipAddress_ = com.google.protobuf.ByteString.EMPTY;
+
+      port_ = 0;
+
+      loadBalanceToken_ = "";
+
+      drop_ = false;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_Server_descriptor;
+    }
+
+    public io.grpc.lb.v1.Server getDefaultInstanceForType() {
+      return io.grpc.lb.v1.Server.getDefaultInstance();
+    }
+
+    public io.grpc.lb.v1.Server build() {
+      io.grpc.lb.v1.Server result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.lb.v1.Server buildPartial() {
+      io.grpc.lb.v1.Server result = new io.grpc.lb.v1.Server(this);
+      result.ipAddress_ = ipAddress_;
+      result.port_ = port_;
+      result.loadBalanceToken_ = loadBalanceToken_;
+      result.drop_ = drop_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.lb.v1.Server) {
+        return mergeFrom((io.grpc.lb.v1.Server)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.lb.v1.Server other) {
+      if (other == io.grpc.lb.v1.Server.getDefaultInstance()) return this;
+      if (other.getIpAddress() != com.google.protobuf.ByteString.EMPTY) {
+        setIpAddress(other.getIpAddress());
+      }
+      if (other.getPort() != 0) {
+        setPort(other.getPort());
+      }
+      if (!other.getLoadBalanceToken().isEmpty()) {
+        loadBalanceToken_ = other.loadBalanceToken_;
+        onChanged();
+      }
+      if (other.getDrop() != false) {
+        setDrop(other.getDrop());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.lb.v1.Server parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.lb.v1.Server) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private com.google.protobuf.ByteString ipAddress_ = com.google.protobuf.ByteString.EMPTY;
+    /**
+     * <pre>
+     * A resolved address for the server, serialized in network-byte-order. It may
+     * either be an IPv4 or IPv6 address.
+     * </pre>
+     *
+     * <code>bytes ip_address = 1;</code>
+     */
+    public com.google.protobuf.ByteString getIpAddress() {
+      return ipAddress_;
+    }
+    /**
+     * <pre>
+     * A resolved address for the server, serialized in network-byte-order. It may
+     * either be an IPv4 or IPv6 address.
+     * </pre>
+     *
+     * <code>bytes ip_address = 1;</code>
+     */
+    public Builder setIpAddress(com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      ipAddress_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * A resolved address for the server, serialized in network-byte-order. It may
+     * either be an IPv4 or IPv6 address.
+     * </pre>
+     *
+     * <code>bytes ip_address = 1;</code>
+     */
+    public Builder clearIpAddress() {
+      
+      ipAddress_ = getDefaultInstance().getIpAddress();
+      onChanged();
+      return this;
+    }
+
+    private int port_ ;
+    /**
+     * <pre>
+     * A resolved port number for the server.
+     * </pre>
+     *
+     * <code>int32 port = 2;</code>
+     */
+    public int getPort() {
+      return port_;
+    }
+    /**
+     * <pre>
+     * A resolved port number for the server.
+     * </pre>
+     *
+     * <code>int32 port = 2;</code>
+     */
+    public Builder setPort(int value) {
+      
+      port_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * A resolved port number for the server.
+     * </pre>
+     *
+     * <code>int32 port = 2;</code>
+     */
+    public Builder clearPort() {
+      
+      port_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object loadBalanceToken_ = "";
+    /**
+     * <pre>
+     * An opaque but printable token for load reporting. The client must include
+     * the token of the picked server into the initial metadata when it starts a
+     * call to that server. The token is used by the server to verify the request
+     * and to allow the server to report load to the gRPC LB system. The token is
+     * also used in client stats for reporting dropped calls.
+     * Its length can be variable but must be less than 50 bytes.
+     * </pre>
+     *
+     * <code>string load_balance_token = 3;</code>
+     */
+    public java.lang.String getLoadBalanceToken() {
+      java.lang.Object ref = loadBalanceToken_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        loadBalanceToken_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An opaque but printable token for load reporting. The client must include
+     * the token of the picked server into the initial metadata when it starts a
+     * call to that server. The token is used by the server to verify the request
+     * and to allow the server to report load to the gRPC LB system. The token is
+     * also used in client stats for reporting dropped calls.
+     * Its length can be variable but must be less than 50 bytes.
+     * </pre>
+     *
+     * <code>string load_balance_token = 3;</code>
+     */
+    public com.google.protobuf.ByteString
+        getLoadBalanceTokenBytes() {
+      java.lang.Object ref = loadBalanceToken_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        loadBalanceToken_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An opaque but printable token for load reporting. The client must include
+     * the token of the picked server into the initial metadata when it starts a
+     * call to that server. The token is used by the server to verify the request
+     * and to allow the server to report load to the gRPC LB system. The token is
+     * also used in client stats for reporting dropped calls.
+     * Its length can be variable but must be less than 50 bytes.
+     * </pre>
+     *
+     * <code>string load_balance_token = 3;</code>
+     */
+    public Builder setLoadBalanceToken(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      loadBalanceToken_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An opaque but printable token for load reporting. The client must include
+     * the token of the picked server into the initial metadata when it starts a
+     * call to that server. The token is used by the server to verify the request
+     * and to allow the server to report load to the gRPC LB system. The token is
+     * also used in client stats for reporting dropped calls.
+     * Its length can be variable but must be less than 50 bytes.
+     * </pre>
+     *
+     * <code>string load_balance_token = 3;</code>
+     */
+    public Builder clearLoadBalanceToken() {
+      
+      loadBalanceToken_ = getDefaultInstance().getLoadBalanceToken();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An opaque but printable token for load reporting. The client must include
+     * the token of the picked server into the initial metadata when it starts a
+     * call to that server. The token is used by the server to verify the request
+     * and to allow the server to report load to the gRPC LB system. The token is
+     * also used in client stats for reporting dropped calls.
+     * Its length can be variable but must be less than 50 bytes.
+     * </pre>
+     *
+     * <code>string load_balance_token = 3;</code>
+     */
+    public Builder setLoadBalanceTokenBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      loadBalanceToken_ = value;
+      onChanged();
+      return this;
+    }
+
+    private boolean drop_ ;
+    /**
+     * <pre>
+     * Indicates whether this particular request should be dropped by the client.
+     * If the request is dropped, there will be a corresponding entry in
+     * ClientStats.calls_finished_with_drop.
+     * </pre>
+     *
+     * <code>bool drop = 4;</code>
+     */
+    public boolean getDrop() {
+      return drop_;
+    }
+    /**
+     * <pre>
+     * Indicates whether this particular request should be dropped by the client.
+     * If the request is dropped, there will be a corresponding entry in
+     * ClientStats.calls_finished_with_drop.
+     * </pre>
+     *
+     * <code>bool drop = 4;</code>
+     */
+    public Builder setDrop(boolean value) {
+      
+      drop_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Indicates whether this particular request should be dropped by the client.
+     * If the request is dropped, there will be a corresponding entry in
+     * ClientStats.calls_finished_with_drop.
+     * </pre>
+     *
+     * <code>bool drop = 4;</code>
+     */
+    public Builder clearDrop() {
+      
+      drop_ = false;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.lb.v1.Server)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.lb.v1.Server)
+  private static final io.grpc.lb.v1.Server DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.lb.v1.Server();
+  }
+
+  public static io.grpc.lb.v1.Server getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<Server>
+      PARSER = new com.google.protobuf.AbstractParser<Server>() {
+    public Server parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Server(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Server> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Server> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.lb.v1.Server getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/ServerList.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/ServerList.java
new file mode 100644
index 0000000..5b6af21
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/ServerList.java
@@ -0,0 +1,895 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+/**
+ * Protobuf type {@code grpc.lb.v1.ServerList}
+ */
+public  final class ServerList extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.lb.v1.ServerList)
+    ServerListOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ServerList.newBuilder() to construct.
+  private ServerList(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ServerList() {
+    servers_ = java.util.Collections.emptyList();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ServerList(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+              servers_ = new java.util.ArrayList<io.grpc.lb.v1.Server>();
+              mutable_bitField0_ |= 0x00000001;
+            }
+            servers_.add(
+                input.readMessage(io.grpc.lb.v1.Server.parser(), extensionRegistry));
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+        servers_ = java.util.Collections.unmodifiableList(servers_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ServerList_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ServerList_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.lb.v1.ServerList.class, io.grpc.lb.v1.ServerList.Builder.class);
+  }
+
+  public static final int SERVERS_FIELD_NUMBER = 1;
+  private java.util.List<io.grpc.lb.v1.Server> servers_;
+  /**
+   * <pre>
+   * Contains a list of servers selected by the load balancer. The list will
+   * be updated when server resolutions change or as needed to balance load
+   * across more servers. The client should consume the server list in order
+   * unless instructed otherwise via the client_config.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+   */
+  public java.util.List<io.grpc.lb.v1.Server> getServersList() {
+    return servers_;
+  }
+  /**
+   * <pre>
+   * Contains a list of servers selected by the load balancer. The list will
+   * be updated when server resolutions change or as needed to balance load
+   * across more servers. The client should consume the server list in order
+   * unless instructed otherwise via the client_config.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+   */
+  public java.util.List<? extends io.grpc.lb.v1.ServerOrBuilder> 
+      getServersOrBuilderList() {
+    return servers_;
+  }
+  /**
+   * <pre>
+   * Contains a list of servers selected by the load balancer. The list will
+   * be updated when server resolutions change or as needed to balance load
+   * across more servers. The client should consume the server list in order
+   * unless instructed otherwise via the client_config.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+   */
+  public int getServersCount() {
+    return servers_.size();
+  }
+  /**
+   * <pre>
+   * Contains a list of servers selected by the load balancer. The list will
+   * be updated when server resolutions change or as needed to balance load
+   * across more servers. The client should consume the server list in order
+   * unless instructed otherwise via the client_config.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+   */
+  public io.grpc.lb.v1.Server getServers(int index) {
+    return servers_.get(index);
+  }
+  /**
+   * <pre>
+   * Contains a list of servers selected by the load balancer. The list will
+   * be updated when server resolutions change or as needed to balance load
+   * across more servers. The client should consume the server list in order
+   * unless instructed otherwise via the client_config.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+   */
+  public io.grpc.lb.v1.ServerOrBuilder getServersOrBuilder(
+      int index) {
+    return servers_.get(index);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    for (int i = 0; i < servers_.size(); i++) {
+      output.writeMessage(1, servers_.get(i));
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    for (int i = 0; i < servers_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, servers_.get(i));
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.lb.v1.ServerList)) {
+      return super.equals(obj);
+    }
+    io.grpc.lb.v1.ServerList other = (io.grpc.lb.v1.ServerList) obj;
+
+    boolean result = true;
+    result = result && getServersList()
+        .equals(other.getServersList());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (getServersCount() > 0) {
+      hash = (37 * hash) + SERVERS_FIELD_NUMBER;
+      hash = (53 * hash) + getServersList().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.lb.v1.ServerList parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.ServerList parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ServerList parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.ServerList parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ServerList parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.lb.v1.ServerList parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ServerList parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.ServerList parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ServerList parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.ServerList parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.lb.v1.ServerList parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.lb.v1.ServerList parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.lb.v1.ServerList prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.lb.v1.ServerList}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.lb.v1.ServerList)
+      io.grpc.lb.v1.ServerListOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ServerList_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ServerList_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.lb.v1.ServerList.class, io.grpc.lb.v1.ServerList.Builder.class);
+    }
+
+    // Construct using io.grpc.lb.v1.ServerList.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+        getServersFieldBuilder();
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (serversBuilder_ == null) {
+        servers_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+      } else {
+        serversBuilder_.clear();
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.lb.v1.LoadBalancerProto.internal_static_grpc_lb_v1_ServerList_descriptor;
+    }
+
+    public io.grpc.lb.v1.ServerList getDefaultInstanceForType() {
+      return io.grpc.lb.v1.ServerList.getDefaultInstance();
+    }
+
+    public io.grpc.lb.v1.ServerList build() {
+      io.grpc.lb.v1.ServerList result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.lb.v1.ServerList buildPartial() {
+      io.grpc.lb.v1.ServerList result = new io.grpc.lb.v1.ServerList(this);
+      int from_bitField0_ = bitField0_;
+      if (serversBuilder_ == null) {
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          servers_ = java.util.Collections.unmodifiableList(servers_);
+          bitField0_ = (bitField0_ & ~0x00000001);
+        }
+        result.servers_ = servers_;
+      } else {
+        result.servers_ = serversBuilder_.build();
+      }
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.lb.v1.ServerList) {
+        return mergeFrom((io.grpc.lb.v1.ServerList)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.lb.v1.ServerList other) {
+      if (other == io.grpc.lb.v1.ServerList.getDefaultInstance()) return this;
+      if (serversBuilder_ == null) {
+        if (!other.servers_.isEmpty()) {
+          if (servers_.isEmpty()) {
+            servers_ = other.servers_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+          } else {
+            ensureServersIsMutable();
+            servers_.addAll(other.servers_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.servers_.isEmpty()) {
+          if (serversBuilder_.isEmpty()) {
+            serversBuilder_.dispose();
+            serversBuilder_ = null;
+            servers_ = other.servers_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+            serversBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getServersFieldBuilder() : null;
+          } else {
+            serversBuilder_.addAllMessages(other.servers_);
+          }
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.lb.v1.ServerList parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.lb.v1.ServerList) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.util.List<io.grpc.lb.v1.Server> servers_ =
+      java.util.Collections.emptyList();
+    private void ensureServersIsMutable() {
+      if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+        servers_ = new java.util.ArrayList<io.grpc.lb.v1.Server>(servers_);
+        bitField0_ |= 0x00000001;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.lb.v1.Server, io.grpc.lb.v1.Server.Builder, io.grpc.lb.v1.ServerOrBuilder> serversBuilder_;
+
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public java.util.List<io.grpc.lb.v1.Server> getServersList() {
+      if (serversBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(servers_);
+      } else {
+        return serversBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public int getServersCount() {
+      if (serversBuilder_ == null) {
+        return servers_.size();
+      } else {
+        return serversBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public io.grpc.lb.v1.Server getServers(int index) {
+      if (serversBuilder_ == null) {
+        return servers_.get(index);
+      } else {
+        return serversBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public Builder setServers(
+        int index, io.grpc.lb.v1.Server value) {
+      if (serversBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureServersIsMutable();
+        servers_.set(index, value);
+        onChanged();
+      } else {
+        serversBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public Builder setServers(
+        int index, io.grpc.lb.v1.Server.Builder builderForValue) {
+      if (serversBuilder_ == null) {
+        ensureServersIsMutable();
+        servers_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        serversBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public Builder addServers(io.grpc.lb.v1.Server value) {
+      if (serversBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureServersIsMutable();
+        servers_.add(value);
+        onChanged();
+      } else {
+        serversBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public Builder addServers(
+        int index, io.grpc.lb.v1.Server value) {
+      if (serversBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureServersIsMutable();
+        servers_.add(index, value);
+        onChanged();
+      } else {
+        serversBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public Builder addServers(
+        io.grpc.lb.v1.Server.Builder builderForValue) {
+      if (serversBuilder_ == null) {
+        ensureServersIsMutable();
+        servers_.add(builderForValue.build());
+        onChanged();
+      } else {
+        serversBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public Builder addServers(
+        int index, io.grpc.lb.v1.Server.Builder builderForValue) {
+      if (serversBuilder_ == null) {
+        ensureServersIsMutable();
+        servers_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        serversBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public Builder addAllServers(
+        java.lang.Iterable<? extends io.grpc.lb.v1.Server> values) {
+      if (serversBuilder_ == null) {
+        ensureServersIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, servers_);
+        onChanged();
+      } else {
+        serversBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public Builder clearServers() {
+      if (serversBuilder_ == null) {
+        servers_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+        onChanged();
+      } else {
+        serversBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public Builder removeServers(int index) {
+      if (serversBuilder_ == null) {
+        ensureServersIsMutable();
+        servers_.remove(index);
+        onChanged();
+      } else {
+        serversBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public io.grpc.lb.v1.Server.Builder getServersBuilder(
+        int index) {
+      return getServersFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public io.grpc.lb.v1.ServerOrBuilder getServersOrBuilder(
+        int index) {
+      if (serversBuilder_ == null) {
+        return servers_.get(index);  } else {
+        return serversBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public java.util.List<? extends io.grpc.lb.v1.ServerOrBuilder> 
+         getServersOrBuilderList() {
+      if (serversBuilder_ != null) {
+        return serversBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(servers_);
+      }
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public io.grpc.lb.v1.Server.Builder addServersBuilder() {
+      return getServersFieldBuilder().addBuilder(
+          io.grpc.lb.v1.Server.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public io.grpc.lb.v1.Server.Builder addServersBuilder(
+        int index) {
+      return getServersFieldBuilder().addBuilder(
+          index, io.grpc.lb.v1.Server.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * Contains a list of servers selected by the load balancer. The list will
+     * be updated when server resolutions change or as needed to balance load
+     * across more servers. The client should consume the server list in order
+     * unless instructed otherwise via the client_config.
+     * </pre>
+     *
+     * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+     */
+    public java.util.List<io.grpc.lb.v1.Server.Builder> 
+         getServersBuilderList() {
+      return getServersFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.lb.v1.Server, io.grpc.lb.v1.Server.Builder, io.grpc.lb.v1.ServerOrBuilder> 
+        getServersFieldBuilder() {
+      if (serversBuilder_ == null) {
+        serversBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.lb.v1.Server, io.grpc.lb.v1.Server.Builder, io.grpc.lb.v1.ServerOrBuilder>(
+                servers_,
+                ((bitField0_ & 0x00000001) == 0x00000001),
+                getParentForChildren(),
+                isClean());
+        servers_ = null;
+      }
+      return serversBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.lb.v1.ServerList)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.lb.v1.ServerList)
+  private static final io.grpc.lb.v1.ServerList DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.lb.v1.ServerList();
+  }
+
+  public static io.grpc.lb.v1.ServerList getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ServerList>
+      PARSER = new com.google.protobuf.AbstractParser<ServerList>() {
+    public ServerList parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ServerList(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ServerList> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ServerList> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.lb.v1.ServerList getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/ServerListOrBuilder.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/ServerListOrBuilder.java
new file mode 100644
index 0000000..6c39984
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/ServerListOrBuilder.java
@@ -0,0 +1,68 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+public interface ServerListOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.lb.v1.ServerList)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * Contains a list of servers selected by the load balancer. The list will
+   * be updated when server resolutions change or as needed to balance load
+   * across more servers. The client should consume the server list in order
+   * unless instructed otherwise via the client_config.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+   */
+  java.util.List<io.grpc.lb.v1.Server> 
+      getServersList();
+  /**
+   * <pre>
+   * Contains a list of servers selected by the load balancer. The list will
+   * be updated when server resolutions change or as needed to balance load
+   * across more servers. The client should consume the server list in order
+   * unless instructed otherwise via the client_config.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+   */
+  io.grpc.lb.v1.Server getServers(int index);
+  /**
+   * <pre>
+   * Contains a list of servers selected by the load balancer. The list will
+   * be updated when server resolutions change or as needed to balance load
+   * across more servers. The client should consume the server list in order
+   * unless instructed otherwise via the client_config.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+   */
+  int getServersCount();
+  /**
+   * <pre>
+   * Contains a list of servers selected by the load balancer. The list will
+   * be updated when server resolutions change or as needed to balance load
+   * across more servers. The client should consume the server list in order
+   * unless instructed otherwise via the client_config.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+   */
+  java.util.List<? extends io.grpc.lb.v1.ServerOrBuilder> 
+      getServersOrBuilderList();
+  /**
+   * <pre>
+   * Contains a list of servers selected by the load balancer. The list will
+   * be updated when server resolutions change or as needed to balance load
+   * across more servers. The client should consume the server list in order
+   * unless instructed otherwise via the client_config.
+   * </pre>
+   *
+   * <code>repeated .grpc.lb.v1.Server servers = 1;</code>
+   */
+  io.grpc.lb.v1.ServerOrBuilder getServersOrBuilder(
+      int index);
+}
diff --git a/grpclb/src/generated/main/java/io/grpc/lb/v1/ServerOrBuilder.java b/grpclb/src/generated/main/java/io/grpc/lb/v1/ServerOrBuilder.java
new file mode 100644
index 0000000..bcf14e6
--- /dev/null
+++ b/grpclb/src/generated/main/java/io/grpc/lb/v1/ServerOrBuilder.java
@@ -0,0 +1,67 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/lb/v1/load_balancer.proto
+
+package io.grpc.lb.v1;
+
+public interface ServerOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.lb.v1.Server)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * A resolved address for the server, serialized in network-byte-order. It may
+   * either be an IPv4 or IPv6 address.
+   * </pre>
+   *
+   * <code>bytes ip_address = 1;</code>
+   */
+  com.google.protobuf.ByteString getIpAddress();
+
+  /**
+   * <pre>
+   * A resolved port number for the server.
+   * </pre>
+   *
+   * <code>int32 port = 2;</code>
+   */
+  int getPort();
+
+  /**
+   * <pre>
+   * An opaque but printable token for load reporting. The client must include
+   * the token of the picked server into the initial metadata when it starts a
+   * call to that server. The token is used by the server to verify the request
+   * and to allow the server to report load to the gRPC LB system. The token is
+   * also used in client stats for reporting dropped calls.
+   * Its length can be variable but must be less than 50 bytes.
+   * </pre>
+   *
+   * <code>string load_balance_token = 3;</code>
+   */
+  java.lang.String getLoadBalanceToken();
+  /**
+   * <pre>
+   * An opaque but printable token for load reporting. The client must include
+   * the token of the picked server into the initial metadata when it starts a
+   * call to that server. The token is used by the server to verify the request
+   * and to allow the server to report load to the gRPC LB system. The token is
+   * also used in client stats for reporting dropped calls.
+   * Its length can be variable but must be less than 50 bytes.
+   * </pre>
+   *
+   * <code>string load_balance_token = 3;</code>
+   */
+  com.google.protobuf.ByteString
+      getLoadBalanceTokenBytes();
+
+  /**
+   * <pre>
+   * Indicates whether this particular request should be dropped by the client.
+   * If the request is dropped, there will be a corresponding entry in
+   * ClientStats.calls_finished_with_drop.
+   * </pre>
+   *
+   * <code>bool drop = 4;</code>
+   */
+  boolean getDrop();
+}
diff --git a/grpclb/src/main/java/io/grpc/grpclb/BackendAddressGroup.java b/grpclb/src/main/java/io/grpc/grpclb/BackendAddressGroup.java
new file mode 100644
index 0000000..7398be4
--- /dev/null
+++ b/grpclb/src/main/java/io/grpc/grpclb/BackendAddressGroup.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.grpclb;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.grpc.EquivalentAddressGroup;
+import javax.annotation.Nullable;
+
+final class BackendAddressGroup {
+  private final EquivalentAddressGroup addresses;
+  @Nullable
+  private final String token;
+
+  BackendAddressGroup(EquivalentAddressGroup addresses, @Nullable String token) {
+    this.addresses = checkNotNull(addresses, "addresses");
+    this.token = token;
+  }
+
+  EquivalentAddressGroup getAddresses() {
+    return addresses;
+  }
+
+  @Nullable
+  String getToken() {
+    return token;
+  }
+
+  @Override
+  public String toString() {
+    return "[addrs=" + addresses + " token=" + token + "]";
+  }
+}
diff --git a/grpclb/src/main/java/io/grpc/grpclb/CachedSubchannelPool.java b/grpclb/src/main/java/io/grpc/grpclb/CachedSubchannelPool.java
new file mode 100644
index 0000000..4091ce2
--- /dev/null
+++ b/grpclb/src/main/java/io/grpc/grpclb/CachedSubchannelPool.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.grpclb;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.Attributes;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.LoadBalancer.Helper;
+import io.grpc.LoadBalancer.Subchannel;
+import java.util.HashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link SubchannelPool} that keeps returned {@link Subchannel}s for a given time before it's
+ * shut down by the pool.
+ */
+final class CachedSubchannelPool implements SubchannelPool {
+  private final HashMap<EquivalentAddressGroup, CacheEntry> cache =
+      new HashMap<EquivalentAddressGroup, CacheEntry>();
+
+  private Helper helper;
+  private ScheduledExecutorService timerService;
+
+  @VisibleForTesting
+  static final long SHUTDOWN_TIMEOUT_MS = 10000;
+
+  @Override
+  public void init(Helper helper, ScheduledExecutorService timerService) {
+    this.helper = checkNotNull(helper, "helper");
+    this.timerService = checkNotNull(timerService, "timerService");
+  }
+
+  @Override
+  public Subchannel takeOrCreateSubchannel(
+      EquivalentAddressGroup eag, Attributes defaultAttributes) {
+    CacheEntry entry = cache.remove(eag);
+    Subchannel subchannel;
+    if (entry == null) {
+      subchannel = helper.createSubchannel(eag, defaultAttributes);
+    } else {
+      subchannel = entry.subchannel;
+      entry.shutdownTimer.cancel(false);
+    }
+    return subchannel;
+  }
+
+  @Override
+  public void returnSubchannel(Subchannel subchannel) {
+    CacheEntry prev = cache.get(subchannel.getAddresses());
+    if (prev != null) {
+      // Returning the same Subchannel twice has no effect.
+      // Returning a different Subchannel for an already cached EAG will cause the
+      // latter Subchannel to be shutdown immediately.
+      if (prev.subchannel != subchannel) {
+        subchannel.shutdown();
+      }
+      return;
+    }
+    final ShutdownSubchannelTask shutdownTask = new ShutdownSubchannelTask(subchannel);
+    ScheduledFuture<?> shutdownTimer =
+        timerService.schedule(
+            new ShutdownSubchannelScheduledTask(shutdownTask),
+            SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    shutdownTask.timer = shutdownTimer;
+    CacheEntry entry = new CacheEntry(subchannel, shutdownTimer);
+    cache.put(subchannel.getAddresses(), entry);
+  }
+
+  @Override
+  public void clear() {
+    for (CacheEntry entry : cache.values()) {
+      entry.shutdownTimer.cancel(false);
+      entry.subchannel.shutdown();
+    }
+    cache.clear();
+  }
+
+  @VisibleForTesting
+  final class ShutdownSubchannelScheduledTask implements Runnable {
+    private final ShutdownSubchannelTask task;
+
+    ShutdownSubchannelScheduledTask(ShutdownSubchannelTask task) {
+      this.task = checkNotNull(task, "task");
+    }
+
+    @Override
+    public void run() {
+      helper.runSerialized(task);
+    }
+  }
+
+  @VisibleForTesting
+  final class ShutdownSubchannelTask implements Runnable {
+    private final Subchannel subchannel;
+    private ScheduledFuture<?> timer;
+
+    private ShutdownSubchannelTask(Subchannel subchannel) {
+      this.subchannel = checkNotNull(subchannel, "subchannel");
+    }
+
+    // This runs in channelExecutor
+    @Override
+    public void run() {
+      // getSubchannel() may have cancelled the timer after the timer has expired but before this
+      // task is actually run in the channelExecutor.
+      if (!timer.isCancelled()) {
+        CacheEntry entry = cache.remove(subchannel.getAddresses());
+        checkState(entry.subchannel == subchannel, "Inconsistent state");
+        subchannel.shutdown();
+      }
+    }
+  }
+
+  private static class CacheEntry {
+    final Subchannel subchannel;
+    final ScheduledFuture<?> shutdownTimer;
+
+    CacheEntry(Subchannel subchannel, ScheduledFuture<?> shutdownTimer) {
+      this.subchannel = checkNotNull(subchannel, "subchannel");
+      this.shutdownTimer = checkNotNull(shutdownTimer, "shutdownTimer");
+    }
+  }
+}
diff --git a/grpclb/src/main/java/io/grpc/grpclb/DropType.java b/grpclb/src/main/java/io/grpc/grpclb/DropType.java
new file mode 100644
index 0000000..704b79e
--- /dev/null
+++ b/grpclb/src/main/java/io/grpc/grpclb/DropType.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.grpclb;
+
+/**
+ * The reason for dropping a request as instructed by the remote balancer.
+ */
+enum DropType {
+    RATE_LIMITING,
+    LOAD_BALANCING
+}
diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbClientLoadRecorder.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbClientLoadRecorder.java
new file mode 100644
index 0000000..02031ea
--- /dev/null
+++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbClientLoadRecorder.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.grpclb;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.protobuf.util.Timestamps;
+import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.TimeProvider;
+import io.grpc.lb.v1.ClientStats;
+import io.grpc.lb.v1.ClientStatsPerToken;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Record and aggregate client-side load data for GRPCLB.  This records load occurred during the
+ * span of an LB stream with the remote load-balancer.
+ */
+@ThreadSafe
+final class GrpclbClientLoadRecorder extends ClientStreamTracer.Factory {
+
+  private static final AtomicLongFieldUpdater<GrpclbClientLoadRecorder> callsStartedUpdater =
+      AtomicLongFieldUpdater.newUpdater(GrpclbClientLoadRecorder.class, "callsStarted");
+  private static final AtomicLongFieldUpdater<GrpclbClientLoadRecorder> callsFinishedUpdater =
+      AtomicLongFieldUpdater.newUpdater(GrpclbClientLoadRecorder.class, "callsFinished");
+  private static final AtomicLongFieldUpdater<GrpclbClientLoadRecorder> callsFailedToSendUpdater =
+      AtomicLongFieldUpdater.newUpdater(GrpclbClientLoadRecorder.class, "callsFailedToSend");
+  private static final AtomicLongFieldUpdater<GrpclbClientLoadRecorder>
+      callsFinishedKnownReceivedUpdater =
+          AtomicLongFieldUpdater.newUpdater(
+              GrpclbClientLoadRecorder.class, "callsFinishedKnownReceived");
+
+  private final TimeProvider time;
+  @SuppressWarnings("unused")
+  private volatile long callsStarted;
+  @SuppressWarnings("unused")
+  private volatile long callsFinished;
+
+  private static final class LongHolder {
+    long num;
+  }
+
+  // Specific finish types
+  @GuardedBy("this")
+  private Map<String, LongHolder> callsDroppedPerToken = new HashMap<String, LongHolder>(1);
+  @SuppressWarnings("unused")
+  private volatile long callsFailedToSend;
+  @SuppressWarnings("unused")
+  private volatile long callsFinishedKnownReceived;
+
+  GrpclbClientLoadRecorder(TimeProvider time) {
+    this.time = checkNotNull(time, "time provider");
+  }
+
+  @Override
+  public ClientStreamTracer newClientStreamTracer(CallOptions callOptions, Metadata headers) {
+    callsStartedUpdater.getAndIncrement(this);
+    return new StreamTracer();
+  }
+
+  /**
+   * Records that a request has been dropped as instructed by the remote balancer.
+   */
+  void recordDroppedRequest(String token) {
+    callsStartedUpdater.getAndIncrement(this);
+    callsFinishedUpdater.getAndIncrement(this);
+
+    synchronized (this) {
+      LongHolder holder;
+      if ((holder = callsDroppedPerToken.get(token)) == null) {
+        callsDroppedPerToken.put(token, (holder = new LongHolder()));
+      }
+      holder.num++;
+    }
+  }
+
+  /**
+   * Generate the report with the data recorded this LB stream since the last report.
+   */
+  ClientStats generateLoadReport() {
+    ClientStats.Builder statsBuilder =
+        ClientStats.newBuilder()
+        .setTimestamp(Timestamps.fromNanos(time.currentTimeNanos()))
+        .setNumCallsStarted(callsStartedUpdater.getAndSet(this, 0))
+        .setNumCallsFinished(callsFinishedUpdater.getAndSet(this, 0))
+        .setNumCallsFinishedWithClientFailedToSend(callsFailedToSendUpdater.getAndSet(this, 0))
+        .setNumCallsFinishedKnownReceived(callsFinishedKnownReceivedUpdater.getAndSet(this, 0));
+
+    Map<String, LongHolder> localCallsDroppedPerToken = Collections.emptyMap();
+    synchronized (this) {
+      if (!callsDroppedPerToken.isEmpty()) {
+        localCallsDroppedPerToken = callsDroppedPerToken;
+        callsDroppedPerToken = new HashMap<String, LongHolder>(localCallsDroppedPerToken.size());
+      }
+    }
+    for (Entry<String, LongHolder> entry : localCallsDroppedPerToken.entrySet()) {
+      statsBuilder.addCallsFinishedWithDrop(
+          ClientStatsPerToken.newBuilder()
+              .setLoadBalanceToken(entry.getKey())
+              .setNumCalls(entry.getValue().num)
+              .build());
+    }
+    return statsBuilder.build();
+  }
+
+  private class StreamTracer extends ClientStreamTracer {
+    private volatile boolean headersSent;
+    private volatile boolean anythingReceived;
+
+    @Override
+    public void outboundHeaders() {
+      headersSent = true;
+    }
+
+    @Override
+    public void inboundHeaders() {
+      anythingReceived = true;
+    }
+
+    @Override
+    public void inboundMessage(int seqNo) {
+      anythingReceived = true;
+    }
+
+    @Override
+    public void streamClosed(Status status) {
+      callsFinishedUpdater.getAndIncrement(GrpclbClientLoadRecorder.this);
+      if (!headersSent) {
+        callsFailedToSendUpdater.getAndIncrement(GrpclbClientLoadRecorder.this);
+      }
+      if (anythingReceived) {
+        callsFinishedKnownReceivedUpdater.getAndIncrement(GrpclbClientLoadRecorder.this);
+      }
+    }
+  }
+}
diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbConstants.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbConstants.java
new file mode 100644
index 0000000..87e2b6b
--- /dev/null
+++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbConstants.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.grpclb;
+
+import io.grpc.ExperimentalApi;
+import io.grpc.Metadata;
+
+/**
+ * Constants for the GRPCLB load-balancer.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1782")
+public final class GrpclbConstants {
+
+  /**
+   * The opaque token given by the remote balancer for each returned server address.  The client
+   * will send this token with any requests sent to the associated server.
+   */
+  public static final Metadata.Key<String> TOKEN_METADATA_KEY =
+      Metadata.Key.of("lb-token", Metadata.ASCII_STRING_MARSHALLER);
+
+  private GrpclbConstants() { }
+}
diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbLoadBalancer.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbLoadBalancer.java
new file mode 100644
index 0000000..a97b2de
--- /dev/null
+++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbLoadBalancer.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.grpclb;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.Attributes;
+import io.grpc.ConnectivityStateInfo;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.InternalLogId;
+import io.grpc.InternalWithLogId;
+import io.grpc.LoadBalancer;
+import io.grpc.Status;
+import io.grpc.internal.BackoffPolicy;
+import io.grpc.internal.GrpcAttributes;
+import io.grpc.internal.ObjectPool;
+import io.grpc.internal.TimeProvider;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import javax.annotation.Nullable;
+
+/**
+ * A {@link LoadBalancer} that uses the GRPCLB protocol.
+ *
+ * <p>Optionally, when requested by the naming system, will delegate the work to a local pick-first
+ * or round-robin balancer.
+ */
+class GrpclbLoadBalancer extends LoadBalancer implements InternalWithLogId {
+
+  private final InternalLogId logId = InternalLogId.allocate(getClass().getName());
+  private final SubchannelPool subchannelPool;
+  private final ObjectPool<ScheduledExecutorService> timerServicePool;
+
+  // All mutable states in this class are mutated ONLY from Channel Executor
+  private ScheduledExecutorService timerService;
+
+  @Nullable
+  private GrpclbState grpclbState;
+
+  GrpclbLoadBalancer(
+      Helper helper,
+      SubchannelPool subchannelPool,
+      ObjectPool<ScheduledExecutorService> timerServicePool,
+      TimeProvider time,
+      BackoffPolicy.Provider backoffPolicyProvider) {
+    checkNotNull(helper, "helper");
+    this.timerServicePool = checkNotNull(timerServicePool, "timerServicePool");
+    this.timerService = checkNotNull(timerServicePool.getObject(), "timerService");
+    checkNotNull(time, "time provider");
+    checkNotNull(backoffPolicyProvider, "backoffPolicyProvider");
+    this.subchannelPool = checkNotNull(subchannelPool, "subchannelPool");
+    this.subchannelPool.init(helper, timerService);
+    grpclbState =
+        new GrpclbState(helper, subchannelPool, time, timerService, backoffPolicyProvider, logId);
+  }
+
+  @Override
+  public InternalLogId getLogId() {
+    return logId;
+  }
+
+  @Override
+  public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo newState) {
+    // grpclbState should never be null here since handleSubchannelState cannot be called while the
+    // lb is shutdown.
+    grpclbState.handleSubchannelState(subchannel, newState);
+  }
+
+  @Override
+  public void handleResolvedAddressGroups(
+      List<EquivalentAddressGroup> updatedServers, Attributes attributes) {
+    // LB addresses and backend addresses are treated separately
+    List<LbAddressGroup> newLbAddressGroups = new ArrayList<>();
+    List<EquivalentAddressGroup> newBackendServers = new ArrayList<>();
+    for (EquivalentAddressGroup server : updatedServers) {
+      String lbAddrAuthority = server.getAttributes().get(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY);
+      if (lbAddrAuthority != null) {
+        newLbAddressGroups.add(new LbAddressGroup(server, lbAddrAuthority));
+      } else {
+        newBackendServers.add(server);
+      }
+    }
+
+    newLbAddressGroups = Collections.unmodifiableList(newLbAddressGroups);
+    newBackendServers = Collections.unmodifiableList(newBackendServers);
+    grpclbState.handleAddresses(newLbAddressGroups, newBackendServers);
+  }
+
+  private void resetStates() {
+    if (grpclbState != null) {
+      grpclbState.shutdown();
+      grpclbState = null;
+    }
+  }
+
+  @Override
+  public void shutdown() {
+    resetStates();
+    timerService = timerServicePool.returnObject(timerService);
+  }
+
+  @Override
+  public void handleNameResolutionError(Status error) {
+    if (grpclbState != null) {
+      grpclbState.propagateError(error);
+    }
+  }
+
+  @VisibleForTesting
+  @Nullable
+  GrpclbState getGrpclbState() {
+    return grpclbState;
+  }
+}
diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbLoadBalancerFactory.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbLoadBalancerFactory.java
new file mode 100644
index 0000000..e46095f
--- /dev/null
+++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbLoadBalancerFactory.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.grpclb;
+
+import io.grpc.ExperimentalApi;
+import io.grpc.LoadBalancer;
+import io.grpc.internal.ExponentialBackoffPolicy;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.SharedResourcePool;
+import io.grpc.internal.TimeProvider;
+
+/**
+ * A factory for {@link LoadBalancer}s that uses the GRPCLB protocol.
+ *
+ * <p><b>Experimental:</b>This only works with the GRPCLB load-balancer service, which is not
+ * available yet. Right now it's only good for internal testing.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1782")
+public class GrpclbLoadBalancerFactory extends LoadBalancer.Factory {
+
+  private static final GrpclbLoadBalancerFactory INSTANCE = new GrpclbLoadBalancerFactory();
+
+  private GrpclbLoadBalancerFactory() {
+  }
+
+  public static GrpclbLoadBalancerFactory getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public LoadBalancer newLoadBalancer(LoadBalancer.Helper helper) {
+    return new GrpclbLoadBalancer(
+        helper, new CachedSubchannelPool(),
+        // TODO(zhangkun83): balancer sends load reporting RPCs from it, which also involves
+        // channelExecutor thus may also run other tasks queued in the channelExecutor.  If such
+        // load should not be on the shared scheduled executor, we should use a combination of the
+        // scheduled executor and the default app executor.
+        SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE),
+        TimeProvider.SYSTEM_TIME_PROVIDER,
+        new ExponentialBackoffPolicy.Provider());
+  }
+}
diff --git a/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java b/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java
new file mode 100644
index 0000000..95ad813
--- /dev/null
+++ b/grpclb/src/main/java/io/grpc/grpclb/GrpclbState.java
@@ -0,0 +1,952 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.grpclb;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static io.grpc.ConnectivityState.CONNECTING;
+import static io.grpc.ConnectivityState.IDLE;
+import static io.grpc.ConnectivityState.READY;
+import static io.grpc.ConnectivityState.SHUTDOWN;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.protobuf.util.Durations;
+import io.grpc.Attributes;
+import io.grpc.ConnectivityState;
+import io.grpc.ConnectivityStateInfo;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.InternalLogId;
+import io.grpc.LoadBalancer.Helper;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.BackoffPolicy;
+import io.grpc.internal.GrpcAttributes;
+import io.grpc.internal.TimeProvider;
+import io.grpc.lb.v1.ClientStats;
+import io.grpc.lb.v1.InitialLoadBalanceRequest;
+import io.grpc.lb.v1.InitialLoadBalanceResponse;
+import io.grpc.lb.v1.LoadBalanceRequest;
+import io.grpc.lb.v1.LoadBalanceResponse;
+import io.grpc.lb.v1.LoadBalanceResponse.LoadBalanceResponseTypeCase;
+import io.grpc.lb.v1.LoadBalancerGrpc;
+import io.grpc.lb.v1.Server;
+import io.grpc.lb.v1.ServerList;
+import io.grpc.stub.StreamObserver;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * The states of a GRPCLB working session of {@link GrpclbLoadBalancer}.  Created when
+ * GrpclbLoadBalancer switches to GRPCLB mode.  Closed and discarded when GrpclbLoadBalancer
+ * switches away from GRPCLB mode.
+ */
+@NotThreadSafe
+final class GrpclbState {
+  private static final Logger logger = Logger.getLogger(GrpclbState.class.getName());
+
+  static final long FALLBACK_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
+  private static final Attributes LB_PROVIDED_BACKEND_ATTRS =
+      Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_PROVIDED_BACKEND, true).build();
+
+  @VisibleForTesting
+  static final PickResult DROP_PICK_RESULT =
+      PickResult.withDrop(Status.UNAVAILABLE.withDescription("Dropped as requested by balancer"));
+
+  @VisibleForTesting
+  static final RoundRobinEntry BUFFER_ENTRY = new RoundRobinEntry() {
+      @Override
+      public PickResult picked(Metadata headers) {
+        return PickResult.withNoResult();
+      }
+
+      @Override
+      public String toString() {
+        return "BUFFER_ENTRY";
+      }
+    };
+
+  private final InternalLogId logId;
+  private final String serviceName;
+  private final Helper helper;
+  private final SubchannelPool subchannelPool;
+  private final TimeProvider time;
+  private final ScheduledExecutorService timerService;
+
+  private static final Attributes.Key<AtomicReference<ConnectivityStateInfo>> STATE_INFO =
+      Attributes.Key.create("io.grpc.grpclb.GrpclbLoadBalancer.stateInfo");
+  private final BackoffPolicy.Provider backoffPolicyProvider;
+
+  // Scheduled only once.  Never reset.
+  @Nullable
+  private FallbackModeTask fallbackTimer;
+  private List<EquivalentAddressGroup> fallbackBackendList = Collections.emptyList();
+  private boolean usingFallbackBackends;
+  // True if the current balancer has returned a serverlist.  Will be reset to false when lost
+  // connection to a balancer.
+  private boolean balancerWorking;
+  @Nullable
+  private BackoffPolicy lbRpcRetryPolicy;
+  @Nullable
+  private LbRpcRetryTask lbRpcRetryTimer;
+  private long prevLbRpcStartNanos;
+
+  @Nullable
+  private ManagedChannel lbCommChannel;
+
+  @Nullable
+  private LbStream lbStream;
+  private Map<EquivalentAddressGroup, Subchannel> subchannels = Collections.emptyMap();
+
+  // Has the same size as the round-robin list from the balancer.
+  // A drop entry from the round-robin list becomes a DropEntry here.
+  // A backend entry from the robin-robin list becomes a null here.
+  private List<DropEntry> dropList = Collections.emptyList();
+  // Contains only non-drop, i.e., backends from the round-robin list from the balancer.
+  private List<BackendEntry> backendList = Collections.emptyList();
+  private RoundRobinPicker currentPicker =
+      new RoundRobinPicker(Collections.<DropEntry>emptyList(), Arrays.asList(BUFFER_ENTRY));
+
+  GrpclbState(
+      Helper helper,
+      SubchannelPool subchannelPool,
+      TimeProvider time,
+      ScheduledExecutorService timerService,
+      BackoffPolicy.Provider backoffPolicyProvider,
+      InternalLogId logId) {
+    this.helper = checkNotNull(helper, "helper");
+    this.subchannelPool = checkNotNull(subchannelPool, "subchannelPool");
+    this.time = checkNotNull(time, "time provider");
+    this.timerService = checkNotNull(timerService, "timerService");
+    this.backoffPolicyProvider = checkNotNull(backoffPolicyProvider, "backoffPolicyProvider");
+    this.serviceName = checkNotNull(helper.getAuthority(), "helper returns null authority");
+    this.logId = checkNotNull(logId, "logId");
+  }
+
+  void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo newState) {
+    if (newState.getState() == SHUTDOWN || !(subchannels.values().contains(subchannel))) {
+      return;
+    }
+    if (newState.getState() == IDLE) {
+      subchannel.requestConnection();
+    }
+    subchannel.getAttributes().get(STATE_INFO).set(newState);
+    maybeUseFallbackBackends();
+    maybeUpdatePicker();
+  }
+
+  /**
+   * Handle new addresses of the balancer and backends from the resolver, and create connection if
+   * not yet connected.
+   */
+  void handleAddresses(
+      List<LbAddressGroup> newLbAddressGroups, List<EquivalentAddressGroup> newBackendServers) {
+    if (newLbAddressGroups.isEmpty()) {
+      propagateError(Status.UNAVAILABLE.withDescription(
+              "NameResolver returned no LB address while asking for GRPCLB"));
+      return;
+    }
+    LbAddressGroup newLbAddressGroup = flattenLbAddressGroups(newLbAddressGroups);
+    startLbComm(newLbAddressGroup);
+    // Avoid creating a new RPC just because the addresses were updated, as it can cause a
+    // stampeding herd. The current RPC may be on a connection to an address not present in
+    // newLbAddressGroups, but we're considering that "okay". If we detected the RPC is to an
+    // outdated backend, we could choose to re-create the RPC.
+    if (lbStream == null) {
+      startLbRpc();
+    }
+    fallbackBackendList = newBackendServers;
+    // Start the fallback timer if it's never started
+    if (fallbackTimer == null) {
+      logger.log(Level.FINE, "[{0}] Starting fallback timer.", new Object[] {logId});
+      fallbackTimer = new FallbackModeTask();
+      fallbackTimer.schedule();
+    }
+    if (usingFallbackBackends) {
+      // Populate the new fallback backends to round-robin list.
+      useFallbackBackends();
+    }
+    maybeUpdatePicker();
+  }
+
+  private void maybeUseFallbackBackends() {
+    if (balancerWorking) {
+      return;
+    }
+    if (usingFallbackBackends) {
+      return;
+    }
+    if (fallbackTimer != null && !fallbackTimer.discarded) {
+      return;
+    }
+    int numReadySubchannels = 0;
+    for (Subchannel subchannel : subchannels.values()) {
+      if (subchannel.getAttributes().get(STATE_INFO).get().getState() == READY) {
+        numReadySubchannels++;
+      }
+    }
+    if (numReadySubchannels > 0) {
+      return;
+    }
+    // Fallback contiditions met
+    useFallbackBackends();
+  }
+
+  /**
+   * Populate the round-robin lists with the fallback backends.
+   */
+  private void useFallbackBackends() {
+    usingFallbackBackends = true;
+    logger.log(Level.INFO, "[{0}] Using fallback: {1}", new Object[] {logId, fallbackBackendList});
+
+    List<DropEntry> newDropList = new ArrayList<>();
+    List<BackendAddressGroup> newBackendAddrList = new ArrayList<>();
+    for (EquivalentAddressGroup eag : fallbackBackendList) {
+      newDropList.add(null);
+      newBackendAddrList.add(new BackendAddressGroup(eag, null));
+    }
+    useRoundRobinLists(newDropList, newBackendAddrList, null);
+  }
+
+  private void shutdownLbComm() {
+    if (lbCommChannel != null) {
+      lbCommChannel.shutdown();
+      lbCommChannel = null;
+    }
+    shutdownLbRpc();
+  }
+
+  private void shutdownLbRpc() {
+    if (lbStream != null) {
+      lbStream.close(null);
+      // lbStream will be set to null in LbStream.cleanup()
+    }
+  }
+
+  private void startLbComm(LbAddressGroup lbAddressGroup) {
+    checkNotNull(lbAddressGroup, "lbAddressGroup");
+    if (lbCommChannel == null) {
+      lbCommChannel = helper.createOobChannel(
+          lbAddressGroup.getAddresses(), lbAddressGroup.getAuthority());
+    } else if (lbAddressGroup.getAuthority().equals(lbCommChannel.authority())) {
+      helper.updateOobChannelAddresses(lbCommChannel, lbAddressGroup.getAddresses());
+    } else {
+      // Full restart of channel
+      shutdownLbComm();
+      lbCommChannel = helper.createOobChannel(
+          lbAddressGroup.getAddresses(), lbAddressGroup.getAuthority());
+    }
+  }
+
+  private void startLbRpc() {
+    checkState(lbStream == null, "previous lbStream has not been cleared yet");
+    LoadBalancerGrpc.LoadBalancerStub stub = LoadBalancerGrpc.newStub(lbCommChannel);
+    lbStream = new LbStream(stub);
+    lbStream.start();
+    prevLbRpcStartNanos = time.currentTimeNanos();
+
+    LoadBalanceRequest initRequest = LoadBalanceRequest.newBuilder()
+        .setInitialRequest(InitialLoadBalanceRequest.newBuilder()
+            .setName(serviceName).build())
+        .build();
+    try {
+      lbStream.lbRequestWriter.onNext(initRequest);
+    } catch (Exception e) {
+      lbStream.close(e);
+    }
+  }
+
+  private void cancelFallbackTimer() {
+    if (fallbackTimer != null) {
+      fallbackTimer.cancel();
+    }
+  }
+
+  private void cancelLbRpcRetryTimer() {
+    if (lbRpcRetryTimer != null) {
+      lbRpcRetryTimer.cancel();
+    }
+  }
+
+  void shutdown() {
+    shutdownLbComm();
+    // We close the subchannels through subchannelPool instead of helper just for convenience of
+    // testing.
+    for (Subchannel subchannel : subchannels.values()) {
+      subchannelPool.returnSubchannel(subchannel);
+    }
+    subchannels = Collections.emptyMap();
+    subchannelPool.clear();
+    cancelFallbackTimer();
+    cancelLbRpcRetryTimer();
+  }
+
+  void propagateError(Status status) {
+    logger.log(Level.FINE, "[{0}] Had an error: {1}; dropList={2}; backendList={3}",
+        new Object[] {logId, status, dropList, backendList});
+    if (backendList.isEmpty()) {
+      maybeUpdatePicker(
+          TRANSIENT_FAILURE, new RoundRobinPicker(dropList, Arrays.asList(new ErrorEntry(status))));
+    }
+  }
+
+  @VisibleForTesting
+  @Nullable
+  GrpclbClientLoadRecorder getLoadRecorder() {
+    if (lbStream == null) {
+      return null;
+    }
+    return lbStream.loadRecorder;
+  }
+
+  /**
+   * Populate the round-robin lists with the given values.
+   */
+  private void useRoundRobinLists(
+      List<DropEntry> newDropList, List<BackendAddressGroup> newBackendAddrList,
+      @Nullable GrpclbClientLoadRecorder loadRecorder) {
+    logger.log(Level.FINE, "[{0}] Using round-robin list: {1}, droplist={2}",
+         new Object[] {logId, newBackendAddrList, newDropList});
+    HashMap<EquivalentAddressGroup, Subchannel> newSubchannelMap =
+        new HashMap<EquivalentAddressGroup, Subchannel>();
+    List<BackendEntry> newBackendList = new ArrayList<>();
+
+    for (BackendAddressGroup backendAddr : newBackendAddrList) {
+      EquivalentAddressGroup eag = backendAddr.getAddresses();
+      Subchannel subchannel = newSubchannelMap.get(eag);
+      if (subchannel == null) {
+        subchannel = subchannels.get(eag);
+        if (subchannel == null) {
+          Attributes subchannelAttrs = Attributes.newBuilder()
+              .set(STATE_INFO,
+                  new AtomicReference<ConnectivityStateInfo>(
+                      ConnectivityStateInfo.forNonError(IDLE)))
+              .build();
+          subchannel = subchannelPool.takeOrCreateSubchannel(eag, subchannelAttrs);
+          subchannel.requestConnection();
+        }
+        newSubchannelMap.put(eag, subchannel);
+      }
+      BackendEntry entry;
+      // Only picks with tokens are reported to LoadRecorder
+      if (backendAddr.getToken() == null) {
+        entry = new BackendEntry(subchannel);
+      } else {
+        entry = new BackendEntry(subchannel, loadRecorder, backendAddr.getToken());
+      }
+      newBackendList.add(entry);
+    }
+
+    // Close Subchannels whose addresses have been delisted
+    for (Entry<EquivalentAddressGroup, Subchannel> entry : subchannels.entrySet()) {
+      EquivalentAddressGroup eag = entry.getKey();
+      if (!newSubchannelMap.containsKey(eag)) {
+        subchannelPool.returnSubchannel(entry.getValue());
+      }
+    }
+
+    subchannels = Collections.unmodifiableMap(newSubchannelMap);
+    dropList = Collections.unmodifiableList(newDropList);
+    backendList = Collections.unmodifiableList(newBackendList);
+  }
+
+  @VisibleForTesting
+  class FallbackModeTask implements Runnable {
+    private ScheduledFuture<?> scheduledFuture;
+    private boolean discarded;
+
+    @Override
+    public void run() {
+      helper.runSerialized(new Runnable() {
+          @Override
+          public void run() {
+            checkState(fallbackTimer == FallbackModeTask.this, "fallback timer mismatch");
+            discarded = true;
+            maybeUseFallbackBackends();
+            maybeUpdatePicker();
+          }
+        });
+    }
+
+    void cancel() {
+      discarded = true;
+      scheduledFuture.cancel(false);
+    }
+
+    void schedule() {
+      checkState(scheduledFuture == null, "FallbackModeTask already scheduled");
+      scheduledFuture = timerService.schedule(this, FALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    }
+  }
+
+  @VisibleForTesting
+  class LbRpcRetryTask implements Runnable {
+    private ScheduledFuture<?> scheduledFuture;
+
+    @Override
+    public void run() {
+      helper.runSerialized(new Runnable() {
+          @Override
+          public void run() {
+            checkState(
+                lbRpcRetryTimer == LbRpcRetryTask.this, "LbRpc retry timer mismatch");
+            startLbRpc();
+          }
+        });
+    }
+
+    void cancel() {
+      scheduledFuture.cancel(false);
+    }
+
+    void schedule(long delayNanos) {
+      checkState(scheduledFuture == null, "LbRpcRetryTask already scheduled");
+      scheduledFuture = timerService.schedule(this, delayNanos, TimeUnit.NANOSECONDS);
+    }
+  }
+
+  @VisibleForTesting
+  class LoadReportingTask implements Runnable {
+    private final LbStream stream;
+
+    LoadReportingTask(LbStream stream) {
+      this.stream = stream;
+    }
+
+    @Override
+    public void run() {
+      helper.runSerialized(new Runnable() {
+          @Override
+          public void run() {
+            stream.loadReportFuture = null;
+            stream.sendLoadReport();
+          }
+        });
+    }
+  }
+
+  private class LbStream implements StreamObserver<LoadBalanceResponse> {
+    final GrpclbClientLoadRecorder loadRecorder;
+    final LoadBalancerGrpc.LoadBalancerStub stub;
+    StreamObserver<LoadBalanceRequest> lbRequestWriter;
+
+    // These fields are only accessed from helper.runSerialized()
+    boolean initialResponseReceived;
+    boolean closed;
+    long loadReportIntervalMillis = -1;
+    ScheduledFuture<?> loadReportFuture;
+
+    LbStream(LoadBalancerGrpc.LoadBalancerStub stub) {
+      this.stub = checkNotNull(stub, "stub");
+      // Stats data only valid for current LbStream.  We do not carry over data from previous
+      // stream.
+      loadRecorder = new GrpclbClientLoadRecorder(time);
+    }
+
+    void start() {
+      lbRequestWriter = stub.withWaitForReady().balanceLoad(this);
+    }
+
+    @Override public void onNext(final LoadBalanceResponse response) {
+      helper.runSerialized(new Runnable() {
+          @Override
+          public void run() {
+            handleResponse(response);
+          }
+        });
+    }
+
+    @Override public void onError(final Throwable error) {
+      helper.runSerialized(new Runnable() {
+          @Override
+          public void run() {
+            handleStreamClosed(Status.fromThrowable(error)
+                .augmentDescription("Stream to GRPCLB LoadBalancer had an error"));
+          }
+        });
+    }
+
+    @Override public void onCompleted() {
+      helper.runSerialized(new Runnable() {
+          @Override
+          public void run() {
+            handleStreamClosed(
+                Status.UNAVAILABLE.withDescription("Stream to GRPCLB LoadBalancer was closed"));
+          }
+        });
+    }
+
+    // Following methods must be run in helper.runSerialized()
+
+    private void sendLoadReport() {
+      if (closed) {
+        return;
+      }
+      ClientStats stats = loadRecorder.generateLoadReport();
+      // TODO(zhangkun83): flow control?
+      try {
+        lbRequestWriter.onNext(LoadBalanceRequest.newBuilder().setClientStats(stats).build());
+        scheduleNextLoadReport();
+      } catch (Exception e) {
+        close(e);
+      }
+    }
+
+    private void scheduleNextLoadReport() {
+      if (loadReportIntervalMillis > 0) {
+        loadReportFuture = timerService.schedule(
+            new LoadReportingTask(this), loadReportIntervalMillis, TimeUnit.MILLISECONDS);
+      }
+    }
+
+    private void handleResponse(LoadBalanceResponse response) {
+      if (closed) {
+        return;
+      }
+      logger.log(Level.FINER, "[{0}] Got an LB response: {1}", new Object[] {logId, response});
+
+      LoadBalanceResponseTypeCase typeCase = response.getLoadBalanceResponseTypeCase();
+      if (!initialResponseReceived) {
+        if (typeCase != LoadBalanceResponseTypeCase.INITIAL_RESPONSE) {
+          logger.log(
+              Level.WARNING,
+              "[{0}] : Did not receive response with type initial response: {1}",
+              new Object[] {logId, response});
+          return;
+        }
+        initialResponseReceived = true;
+        InitialLoadBalanceResponse initialResponse = response.getInitialResponse();
+        loadReportIntervalMillis =
+            Durations.toMillis(initialResponse.getClientStatsReportInterval());
+        scheduleNextLoadReport();
+        return;
+      }
+
+      if (typeCase != LoadBalanceResponseTypeCase.SERVER_LIST) {
+        logger.log(
+            Level.WARNING,
+            "[{0}] : Ignoring unexpected response type: {1}",
+            new Object[] {logId, response});
+        return;
+      }
+
+      balancerWorking = true;
+      // TODO(zhangkun83): handle delegate from initialResponse
+      ServerList serverList = response.getServerList();
+      List<DropEntry> newDropList = new ArrayList<>();
+      List<BackendAddressGroup> newBackendAddrList = new ArrayList<>();
+      // Construct the new collections. Create new Subchannels when necessary.
+      for (Server server : serverList.getServersList()) {
+        String token = server.getLoadBalanceToken();
+        if (server.getDrop()) {
+          newDropList.add(new DropEntry(loadRecorder, token));
+        } else {
+          newDropList.add(null);
+          InetSocketAddress address;
+          try {
+            address = new InetSocketAddress(
+                InetAddress.getByAddress(server.getIpAddress().toByteArray()), server.getPort());
+          } catch (UnknownHostException e) {
+            propagateError(
+                Status.UNAVAILABLE
+                    .withDescription("Host for server not found: " + server)
+                    .withCause(e));
+            continue;
+          }
+          // ALTS code can use the presence of ATTR_LB_PROVIDED_BACKEND to select ALTS instead of
+          // TLS, with Netty.
+          EquivalentAddressGroup eag =
+              new EquivalentAddressGroup(address, LB_PROVIDED_BACKEND_ATTRS);
+          newBackendAddrList.add(new BackendAddressGroup(eag, token));
+        }
+      }
+      // Stop using fallback backends as soon as a new server list is received from the balancer.
+      usingFallbackBackends = false;
+      cancelFallbackTimer();
+      useRoundRobinLists(newDropList, newBackendAddrList, loadRecorder);
+      maybeUpdatePicker();
+    }
+
+    private void handleStreamClosed(Status error) {
+      checkArgument(!error.isOk(), "unexpected OK status");
+      if (closed) {
+        return;
+      }
+      closed = true;
+      cleanUp();
+      propagateError(error);
+      balancerWorking = false;
+      maybeUseFallbackBackends();
+      maybeUpdatePicker();
+
+      long delayNanos = 0;
+      if (initialResponseReceived || lbRpcRetryPolicy == null) {
+        // Reset the backoff sequence if balancer has sent the initial response, or backoff sequence
+        // has never been initialized.
+        lbRpcRetryPolicy = backoffPolicyProvider.get();
+      }
+      // Backoff only when balancer wasn't working previously.
+      if (!initialResponseReceived) {
+        // The back-off policy determines the interval between consecutive RPC upstarts, thus the
+        // actual delay may be smaller than the value from the back-off policy, or even negative,
+        // depending how much time was spent in the previous RPC.
+        delayNanos =
+            prevLbRpcStartNanos + lbRpcRetryPolicy.nextBackoffNanos() - time.currentTimeNanos();
+      }
+      if (delayNanos <= 0) {
+        startLbRpc();
+      } else {
+        lbRpcRetryTimer = new LbRpcRetryTask();
+        lbRpcRetryTimer.schedule(delayNanos);
+      }
+    }
+
+    void close(@Nullable Exception error) {
+      if (closed) {
+        return;
+      }
+      closed = true;
+      cleanUp();
+      try {
+        if (error == null) {
+          lbRequestWriter.onCompleted();
+        } else {
+          lbRequestWriter.onError(error);
+        }
+      } catch (Exception e) {
+        // Don't care
+      }
+    }
+
+    private void cleanUp() {
+      if (loadReportFuture != null) {
+        loadReportFuture.cancel(false);
+        loadReportFuture = null;
+      }
+      if (lbStream == this) {
+        lbStream = null;
+      }
+    }
+  }
+
+  /**
+   * Make and use a picker out of the current lists and the states of subchannels if they have
+   * changed since the last picker created.
+   */
+  private void maybeUpdatePicker() {
+    List<RoundRobinEntry> pickList = new ArrayList<>(backendList.size());
+    Status error = null;
+    boolean hasIdle = false;
+    for (BackendEntry entry : backendList) {
+      Subchannel subchannel = entry.result.getSubchannel();
+      Attributes attrs = subchannel.getAttributes();
+      ConnectivityStateInfo stateInfo = attrs.get(STATE_INFO).get();
+      if (stateInfo.getState() == READY) {
+        pickList.add(entry);
+      } else if (stateInfo.getState() == TRANSIENT_FAILURE) {
+        error = stateInfo.getStatus();
+      } else if (stateInfo.getState() == IDLE) {
+        hasIdle = true;
+      }
+    }
+    ConnectivityState state;
+    if (pickList.isEmpty()) {
+      if (error != null && !hasIdle) {
+        logger.log(Level.FINE, "[{0}] No ready Subchannel. Using error: {1}",
+            new Object[] {logId, error});
+        pickList.add(new ErrorEntry(error));
+        state = TRANSIENT_FAILURE;
+      } else {
+        logger.log(Level.FINE, "[{0}] No ready Subchannel and still connecting", logId);
+        pickList.add(BUFFER_ENTRY);
+        state = CONNECTING;
+      }
+    } else {
+      logger.log(
+          Level.FINE, "[{0}] Using drop list {1} and pick list {2}",
+          new Object[] {logId, dropList, pickList});
+      state = READY;
+    }
+    maybeUpdatePicker(state, new RoundRobinPicker(dropList, pickList));
+  }
+
+  /**
+   * Update the given picker to the helper if it's different from the current one.
+   */
+  private void maybeUpdatePicker(ConnectivityState state, RoundRobinPicker picker) {
+    // Discard the new picker if we are sure it won't make any difference, in order to save
+    // re-processing pending streams, and avoid unnecessary resetting of the pointer in
+    // RoundRobinPicker.
+    if (picker.dropList.equals(currentPicker.dropList)
+        && picker.pickList.equals(currentPicker.pickList)) {
+      return;
+    }
+    // No need to skip ErrorPicker. If the current picker is ErrorPicker, there won't be any pending
+    // stream thus no time is wasted in re-process.
+    currentPicker = picker;
+    helper.updateBalancingState(state, picker);
+  }
+
+  private LbAddressGroup flattenLbAddressGroups(List<LbAddressGroup> groupList) {
+    assert !groupList.isEmpty();
+    List<EquivalentAddressGroup> eags = new ArrayList<>(groupList.size());
+    String authority = groupList.get(0).getAuthority();
+    for (LbAddressGroup group : groupList) {
+      if (!authority.equals(group.getAuthority())) {
+        // TODO(ejona): Allow different authorities for different addresses. Requires support from
+        // Helper.
+        logger.log(Level.WARNING,
+            "[{0}] Multiple authorities found for LB. "
+            + "Skipping addresses for {0} in preference to {1}",
+            new Object[] {logId, group.getAuthority(), authority});
+      } else {
+        eags.add(group.getAddresses());
+      }
+    }
+    // ALTS code can use the presence of ATTR_LB_ADDR_AUTHORITY to select ALTS instead of TLS, with
+    // Netty.
+    // TODO(ejona): The process here is a bit of a hack because ATTR_LB_ADDR_AUTHORITY isn't
+    // actually used in the normal case. https://github.com/grpc/grpc-java/issues/4618 should allow
+    // this to be more obvious.
+    Attributes attrs = Attributes.newBuilder()
+        .set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, authority)
+        .build();
+    return new LbAddressGroup(flattenEquivalentAddressGroup(eags, attrs), authority);
+  }
+
+  /**
+   * Flattens list of EquivalentAddressGroup objects into one EquivalentAddressGroup object.
+   */
+  private static EquivalentAddressGroup flattenEquivalentAddressGroup(
+      List<EquivalentAddressGroup> groupList, Attributes attrs) {
+    List<SocketAddress> addrs = new ArrayList<>();
+    for (EquivalentAddressGroup group : groupList) {
+      addrs.addAll(group.getAddresses());
+    }
+    return new EquivalentAddressGroup(addrs, attrs);
+  }
+
+  @VisibleForTesting
+  static final class DropEntry {
+    private final GrpclbClientLoadRecorder loadRecorder;
+    private final String token;
+
+    DropEntry(GrpclbClientLoadRecorder loadRecorder, String token) {
+      this.loadRecorder = checkNotNull(loadRecorder, "loadRecorder");
+      this.token = checkNotNull(token, "token");
+    }
+
+    PickResult picked() {
+      loadRecorder.recordDroppedRequest(token);
+      return DROP_PICK_RESULT;
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("loadRecorder", loadRecorder)
+          .add("token", token)
+          .toString();
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(loadRecorder, token);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (!(other instanceof DropEntry)) {
+        return false;
+      }
+      DropEntry that = (DropEntry) other;
+      return Objects.equal(loadRecorder, that.loadRecorder) && Objects.equal(token, that.token);
+    }
+  }
+
+  private interface RoundRobinEntry {
+    PickResult picked(Metadata headers);
+  }
+
+  @VisibleForTesting
+  static final class BackendEntry implements RoundRobinEntry {
+    @VisibleForTesting
+    final PickResult result;
+    @Nullable
+    private final GrpclbClientLoadRecorder loadRecorder;
+    @Nullable
+    private final String token;
+
+    /**
+     * Creates a BackendEntry whose usage will be reported to load recorder.
+     */
+    BackendEntry(Subchannel subchannel, GrpclbClientLoadRecorder loadRecorder, String token) {
+      this.result = PickResult.withSubchannel(subchannel, loadRecorder);
+      this.loadRecorder = checkNotNull(loadRecorder, "loadRecorder");
+      this.token = checkNotNull(token, "token");
+    }
+
+    /**
+     * Creates a BackendEntry whose usage will not be reported.
+     */
+    BackendEntry(Subchannel subchannel) {
+      this.result = PickResult.withSubchannel(subchannel);
+      this.loadRecorder = null;
+      this.token = null;
+    }
+
+    @Override
+    public PickResult picked(Metadata headers) {
+      headers.discardAll(GrpclbConstants.TOKEN_METADATA_KEY);
+      if (token != null) {
+        headers.put(GrpclbConstants.TOKEN_METADATA_KEY, token);
+      }
+      return result;
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("result", result)
+          .add("loadRecorder", loadRecorder)
+          .add("token", token)
+          .toString();
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(loadRecorder, result, token);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (!(other instanceof BackendEntry)) {
+        return false;
+      }
+      BackendEntry that = (BackendEntry) other;
+      return Objects.equal(result, that.result) && Objects.equal(token, that.token)
+          && Objects.equal(loadRecorder, that.loadRecorder);
+    }
+  }
+
+  @VisibleForTesting
+  static final class ErrorEntry implements RoundRobinEntry {
+    final PickResult result;
+
+    ErrorEntry(Status status) {
+      result = PickResult.withError(status);
+    }
+
+    @Override
+    public PickResult picked(Metadata headers) {
+      return result;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(result);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (!(other instanceof ErrorEntry)) {
+        return false;
+      }
+      return Objects.equal(result, ((ErrorEntry) other).result);
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("result", result)
+          .toString();
+    }
+  }
+
+  @VisibleForTesting
+  static final class RoundRobinPicker extends SubchannelPicker {
+    @VisibleForTesting
+    final List<DropEntry> dropList;
+    private int dropIndex;
+
+    @VisibleForTesting
+    final List<? extends RoundRobinEntry> pickList;
+    private int pickIndex;
+
+    // dropList can be empty, which means no drop.
+    // pickList must not be empty.
+    RoundRobinPicker(List<DropEntry> dropList, List<? extends RoundRobinEntry> pickList) {
+      this.dropList = checkNotNull(dropList, "dropList");
+      this.pickList = checkNotNull(pickList, "pickList");
+      checkArgument(!pickList.isEmpty(), "pickList is empty");
+    }
+
+    @Override
+    public PickResult pickSubchannel(PickSubchannelArgs args) {
+      synchronized (pickList) {
+        // Two-level round-robin.
+        // First round-robin on dropList. If a drop entry is selected, request will be dropped.  If
+        // a non-drop entry is selected, then round-robin on pickList.  This makes sure requests are
+        // dropped at the same proportion as the drop entries appear on the round-robin list from
+        // the balancer, while only READY backends (that make up pickList) are selected for the
+        // non-drop cases.
+        if (!dropList.isEmpty()) {
+          DropEntry drop = dropList.get(dropIndex);
+          dropIndex++;
+          if (dropIndex == dropList.size()) {
+            dropIndex = 0;
+          }
+          if (drop != null) {
+            return drop.picked();
+          }
+        }
+
+        RoundRobinEntry pick = pickList.get(pickIndex);
+        pickIndex++;
+        if (pickIndex == pickList.size()) {
+          pickIndex = 0;
+        }
+        return pick.picked(args.getHeaders());
+      }
+    }
+  }
+}
diff --git a/grpclb/src/main/java/io/grpc/grpclb/LbAddressGroup.java b/grpclb/src/main/java/io/grpc/grpclb/LbAddressGroup.java
new file mode 100644
index 0000000..cca096f
--- /dev/null
+++ b/grpclb/src/main/java/io/grpc/grpclb/LbAddressGroup.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.grpclb;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.grpc.EquivalentAddressGroup;
+
+/**
+ * Represents a balancer address entry.
+ */
+final class LbAddressGroup {
+  private final EquivalentAddressGroup addresses;
+  private final String authority;
+
+  LbAddressGroup(EquivalentAddressGroup addresses, String authority) {
+    this.addresses = checkNotNull(addresses, "addresses");
+    this.authority = checkNotNull(authority, "authority");
+  }
+
+  EquivalentAddressGroup getAddresses() {
+    return addresses;
+  }
+
+  String getAuthority() {
+    return authority;
+  }
+}
diff --git a/grpclb/src/main/java/io/grpc/grpclb/SubchannelPool.java b/grpclb/src/main/java/io/grpc/grpclb/SubchannelPool.java
new file mode 100644
index 0000000..05d750b
--- /dev/null
+++ b/grpclb/src/main/java/io/grpc/grpclb/SubchannelPool.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.grpclb;
+
+import io.grpc.Attributes;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.LoadBalancer.Helper;
+import io.grpc.LoadBalancer.Subchannel;
+import java.util.concurrent.ScheduledExecutorService;
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * Manages life-cycle of Subchannels for {@link GrpclbState}.
+ *
+ * <p>All methods are run from the ChannelExecutor that the helper uses.
+ */
+@NotThreadSafe
+interface SubchannelPool {
+  /**
+   * Pass essential utilities.
+   */
+  void init(Helper helper, ScheduledExecutorService timerService);
+
+  /**
+   * Takes a {@link Subchannel} from the pool for the given {@code eag} if there is one available.
+   * Otherwise, creates and returns a new {@code Subchannel} with the given {@code eag} and {@code
+   * defaultAttributes}.
+   */
+  Subchannel takeOrCreateSubchannel(EquivalentAddressGroup eag, Attributes defaultAttributes);
+
+  /**
+   * Puts a {@link Subchannel} back to the pool.  From this point the Subchannel is owned by the
+   * pool.
+   */
+  void returnSubchannel(Subchannel subchannel);
+
+  /**
+   * Shuts down all subchannels in the pool immediately.
+   */
+  void clear();
+}
diff --git a/grpclb/src/main/proto/grpc/lb/v1/load_balancer.proto b/grpclb/src/main/proto/grpc/lb/v1/load_balancer.proto
new file mode 100644
index 0000000..7f65e0e
--- /dev/null
+++ b/grpclb/src/main/proto/grpc/lb/v1/load_balancer.proto
@@ -0,0 +1,150 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+
+// This file defines the GRPCLB LoadBalancing protocol.
+//
+// The canonical version of this proto can be found at
+// https://github.com/grpc/grpc-proto/blob/master/grpc/lb/v1/load_balancer.proto
+syntax = "proto3";
+
+package grpc.lb.v1;
+
+import "google/protobuf/duration.proto";
+import "google/protobuf/timestamp.proto";
+
+option go_package = "google.golang.org/grpc/balancer/grpclb/grpc_lb_v1";
+option java_multiple_files = true;
+option java_outer_classname = "LoadBalancerProto";
+option java_package = "io.grpc.lb.v1";
+
+service LoadBalancer {
+  // Bidirectional rpc to get a list of servers.
+  rpc BalanceLoad(stream LoadBalanceRequest) returns (stream LoadBalanceResponse);
+}
+
+message LoadBalanceRequest {
+  oneof load_balance_request_type {
+    // This message should be sent on the first request to the load balancer.
+    InitialLoadBalanceRequest initial_request = 1;
+
+    // The client stats should be periodically reported to the load balancer
+    // based on the duration defined in the InitialLoadBalanceResponse.
+    ClientStats client_stats = 2;
+  }
+}
+
+message InitialLoadBalanceRequest {
+  // The name of the load balanced service (e.g., service.googleapis.com). Its
+  // length should be less than 256 bytes.
+  // The name might include a port number. How to handle the port number is up
+  // to the balancer.
+  string name = 1;
+}
+
+// Contains the number of calls finished for a particular load balance token.
+message ClientStatsPerToken {
+  // See Server.load_balance_token.
+  string load_balance_token = 1;
+
+  // The total number of RPCs that finished associated with the token.
+  int64 num_calls = 2;
+}
+
+// Contains client level statistics that are useful to load balancing. Each
+// count except the timestamp should be reset to zero after reporting the stats.
+message ClientStats {
+  // The timestamp of generating the report.
+  google.protobuf.Timestamp timestamp = 1;
+
+  // The total number of RPCs that started.
+  int64 num_calls_started = 2;
+
+  // The total number of RPCs that finished.
+  int64 num_calls_finished = 3;
+
+  // The total number of RPCs that failed to reach a server except dropped RPCs.
+  int64 num_calls_finished_with_client_failed_to_send = 6;
+
+  // The total number of RPCs that finished and are known to have been received
+  // by a server.
+  int64 num_calls_finished_known_received = 7;
+
+  // The list of dropped calls.
+  repeated ClientStatsPerToken calls_finished_with_drop = 8;
+
+  reserved 4, 5;
+}
+
+message LoadBalanceResponse {
+  oneof load_balance_response_type {
+    // This message should be sent on the first response to the client.
+    InitialLoadBalanceResponse initial_response = 1;
+
+    // Contains the list of servers selected by the load balancer. The client
+    // should send requests to these servers in the specified order.
+    ServerList server_list = 2;
+  }
+}
+
+message InitialLoadBalanceResponse {
+  // This is an application layer redirect that indicates the client should use
+  // the specified server for load balancing. When this field is non-empty in
+  // the response, the client should open a separate connection to the
+  // load_balancer_delegate and call the BalanceLoad method. Its length should
+  // be less than 64 bytes.
+  string load_balancer_delegate = 1;
+
+  // This interval defines how often the client should send the client stats
+  // to the load balancer. Stats should only be reported when the duration is
+  // positive.
+  google.protobuf.Duration client_stats_report_interval = 2;
+}
+
+message ServerList {
+  // Contains a list of servers selected by the load balancer. The list will
+  // be updated when server resolutions change or as needed to balance load
+  // across more servers. The client should consume the server list in order
+  // unless instructed otherwise via the client_config.
+  repeated Server servers = 1;
+
+  // Was google.protobuf.Duration expiration_interval.
+  reserved 3;
+}
+
+// Contains server information. When the drop field is not true, use the other
+// fields.
+message Server {
+  // A resolved address for the server, serialized in network-byte-order. It may
+  // either be an IPv4 or IPv6 address.
+  bytes ip_address = 1;
+
+  // A resolved port number for the server.
+  int32 port = 2;
+
+  // An opaque but printable token for load reporting. The client must include
+  // the token of the picked server into the initial metadata when it starts a
+  // call to that server. The token is used by the server to verify the request
+  // and to allow the server to report load to the gRPC LB system. The token is
+  // also used in client stats for reporting dropped calls.
+  //
+  // Its length can be variable but must be less than 50 bytes.
+  string load_balance_token = 3;
+
+  // Indicates whether this particular request should be dropped by the client.
+  // If the request is dropped, there will be a corresponding entry in
+  // ClientStats.calls_finished_with_drop.
+  bool drop = 4;
+
+  reserved 5;
+}
diff --git a/grpclb/src/test/java/io/grpc/grpclb/CachedSubchannelPoolTest.java b/grpclb/src/test/java/io/grpc/grpclb/CachedSubchannelPoolTest.java
new file mode 100644
index 0000000..9c5151f
--- /dev/null
+++ b/grpclb/src/test/java/io/grpc/grpclb/CachedSubchannelPoolTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.grpclb;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.grpclb.CachedSubchannelPool.SHUTDOWN_TIMEOUT_MS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.Attributes;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.LoadBalancer.Helper;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.grpclb.CachedSubchannelPool.ShutdownSubchannelScheduledTask;
+import io.grpc.grpclb.CachedSubchannelPool.ShutdownSubchannelTask;
+import io.grpc.internal.FakeClock;
+import io.grpc.internal.SerializingExecutor;
+import java.util.ArrayList;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/** Unit tests for {@link CachedSubchannelPool}. */
+@RunWith(JUnit4.class)
+public class CachedSubchannelPoolTest {
+  private static final EquivalentAddressGroup EAG1 =
+      new EquivalentAddressGroup(new FakeSocketAddress("fake-address-1"), Attributes.EMPTY);
+  private static final EquivalentAddressGroup EAG2 =
+      new EquivalentAddressGroup(new FakeSocketAddress("fake-address-2"), Attributes.EMPTY);
+  private static final Attributes.Key<String> ATTR_KEY = Attributes.Key.create("test-attr");
+  private static final Attributes ATTRS1 = Attributes.newBuilder().set(ATTR_KEY, "1").build();
+  private static final Attributes ATTRS2 = Attributes.newBuilder().set(ATTR_KEY, "2").build();
+  private static final FakeClock.TaskFilter SHUTDOWN_SCHEDULED_TASK_FILTER =
+      new FakeClock.TaskFilter() {
+        @Override
+        public boolean shouldAccept(Runnable command) {
+          return command instanceof ShutdownSubchannelScheduledTask;
+        }
+      };
+
+  private final SerializingExecutor channelExecutor =
+      new SerializingExecutor(MoreExecutors.directExecutor());
+  private final Helper helper = mock(Helper.class);
+  private final FakeClock clock = new FakeClock();
+  private final CachedSubchannelPool pool = new CachedSubchannelPool();
+  private final ArrayList<Subchannel> mockSubchannels = new ArrayList<>();
+
+  @Before
+  public void setUp() {
+    doAnswer(new Answer<Subchannel>() {
+        @Override
+        public Subchannel answer(InvocationOnMock invocation) throws Throwable {
+          Subchannel subchannel = mock(Subchannel.class);
+          EquivalentAddressGroup eag = (EquivalentAddressGroup) invocation.getArguments()[0];
+          Attributes attrs = (Attributes) invocation.getArguments()[1];
+          when(subchannel.getAddresses()).thenReturn(eag);
+          when(subchannel.getAttributes()).thenReturn(attrs);
+          mockSubchannels.add(subchannel);
+          return subchannel;
+        }
+      }).when(helper).createSubchannel(any(EquivalentAddressGroup.class), any(Attributes.class));
+    doAnswer(new Answer<Void>() {
+        @Override
+        public Void answer(InvocationOnMock invocation) throws Throwable {
+          Runnable task = (Runnable) invocation.getArguments()[0];
+          channelExecutor.execute(task);
+          return null;
+        }
+      }).when(helper).runSerialized(any(Runnable.class));
+    pool.init(helper, clock.getScheduledExecutorService());
+  }
+
+  @After
+  public void wrapUp() {
+    // Sanity checks
+    for (Subchannel subchannel : mockSubchannels) {
+      verify(subchannel, atMost(1)).shutdown();
+    }
+  }
+
+  @Test
+  public void subchannelExpireAfterReturned() {
+    Subchannel subchannel1 = pool.takeOrCreateSubchannel(EAG1, ATTRS1);
+    assertThat(subchannel1).isNotNull();
+    verify(helper).createSubchannel(same(EAG1), same(ATTRS1));
+
+    Subchannel subchannel2 = pool.takeOrCreateSubchannel(EAG2, ATTRS2);
+    assertThat(subchannel2).isNotNull();
+    assertThat(subchannel2).isNotSameAs(subchannel1);
+    verify(helper).createSubchannel(same(EAG2), same(ATTRS2));
+
+    pool.returnSubchannel(subchannel1);
+
+    // subchannel1 is 1ms away from expiration.
+    clock.forwardTime(SHUTDOWN_TIMEOUT_MS - 1, MILLISECONDS);
+    verify(subchannel1, never()).shutdown();
+
+    pool.returnSubchannel(subchannel2);
+
+    // subchannel1 expires. subchannel2 is (SHUTDOWN_TIMEOUT_MS - 1) away from expiration.
+    clock.forwardTime(1, MILLISECONDS);
+    verify(subchannel1).shutdown();
+
+    // subchanne2 expires.
+    clock.forwardTime(SHUTDOWN_TIMEOUT_MS - 1, MILLISECONDS);
+    verify(subchannel2).shutdown();
+
+    assertThat(clock.numPendingTasks()).isEqualTo(0);
+    verify(helper, times(2)).runSerialized(any(ShutdownSubchannelTask.class));
+  }
+
+  @Test
+  public void subchannelReused() {
+    Subchannel subchannel1 = pool.takeOrCreateSubchannel(EAG1, ATTRS1);
+    assertThat(subchannel1).isNotNull();
+    verify(helper).createSubchannel(same(EAG1), same(ATTRS1));
+
+    Subchannel subchannel2 = pool.takeOrCreateSubchannel(EAG2, ATTRS2);
+    assertThat(subchannel2).isNotNull();
+    assertThat(subchannel2).isNotSameAs(subchannel1);
+    verify(helper).createSubchannel(same(EAG2), same(ATTRS2));
+
+    pool.returnSubchannel(subchannel1);
+
+    // subchannel1 is 1ms away from expiration.
+    clock.forwardTime(SHUTDOWN_TIMEOUT_MS - 1, MILLISECONDS);
+
+    // This will cancel the shutdown timer for subchannel1
+    Subchannel subchannel1a = pool.takeOrCreateSubchannel(EAG1, ATTRS1);
+    assertThat(subchannel1a).isSameAs(subchannel1);
+
+    pool.returnSubchannel(subchannel2);
+
+    // subchannel2 expires SHUTDOWN_TIMEOUT_MS after being returned
+    clock.forwardTime(SHUTDOWN_TIMEOUT_MS - 1, MILLISECONDS);
+    verify(subchannel2, never()).shutdown();
+    clock.forwardTime(1, MILLISECONDS);
+    verify(subchannel2).shutdown();
+
+    // pool will create a new channel for EAG2 when requested
+    Subchannel subchannel2a = pool.takeOrCreateSubchannel(EAG2, ATTRS2);
+    assertThat(subchannel2a).isNotSameAs(subchannel2);
+    verify(helper, times(2)).createSubchannel(same(EAG2), same(ATTRS2));
+
+    // subchannel1 expires SHUTDOWN_TIMEOUT_MS after being returned
+    pool.returnSubchannel(subchannel1a);
+    clock.forwardTime(SHUTDOWN_TIMEOUT_MS - 1, MILLISECONDS);
+    verify(subchannel1a, never()).shutdown();
+    clock.forwardTime(1, MILLISECONDS);
+    verify(subchannel1a).shutdown();
+
+    assertThat(clock.numPendingTasks()).isEqualTo(0);
+    verify(helper, times(2)).runSerialized(any(ShutdownSubchannelTask.class));
+  }
+
+  @Test
+  public void returnDuplicateAddressSubchannel() {
+    Subchannel subchannel1 = pool.takeOrCreateSubchannel(EAG1, ATTRS1);
+    Subchannel subchannel2 = pool.takeOrCreateSubchannel(EAG1, ATTRS2);
+    Subchannel subchannel3 = pool.takeOrCreateSubchannel(EAG2, ATTRS1);
+    assertThat(subchannel1).isNotSameAs(subchannel2);
+
+    assertThat(clock.getPendingTasks(SHUTDOWN_SCHEDULED_TASK_FILTER)).isEmpty();
+    pool.returnSubchannel(subchannel2);
+    assertThat(clock.getPendingTasks(SHUTDOWN_SCHEDULED_TASK_FILTER)).hasSize(1);
+
+    // If the subchannel being returned has an address that is the same as a subchannel in the pool,
+    // the returned subchannel will be shut down.
+    verify(subchannel1, never()).shutdown();
+    pool.returnSubchannel(subchannel1);
+    assertThat(clock.getPendingTasks(SHUTDOWN_SCHEDULED_TASK_FILTER)).hasSize(1);
+    verify(subchannel1).shutdown();
+
+    pool.returnSubchannel(subchannel3);
+    assertThat(clock.getPendingTasks(SHUTDOWN_SCHEDULED_TASK_FILTER)).hasSize(2);
+    // Returning the same subchannel twice has no effect.
+    pool.returnSubchannel(subchannel3);
+    assertThat(clock.getPendingTasks(SHUTDOWN_SCHEDULED_TASK_FILTER)).hasSize(2);
+
+    verify(subchannel2, never()).shutdown();
+    verify(subchannel3, never()).shutdown();
+    verify(helper, never()).runSerialized(any(ShutdownSubchannelTask.class));
+  }
+
+  @Test
+  public void clear() {
+    Subchannel subchannel1 = pool.takeOrCreateSubchannel(EAG1, ATTRS1);
+    Subchannel subchannel2 = pool.takeOrCreateSubchannel(EAG2, ATTRS2);
+    Subchannel subchannel3 = pool.takeOrCreateSubchannel(EAG2, ATTRS2);
+
+    pool.returnSubchannel(subchannel1);
+    pool.returnSubchannel(subchannel2);
+
+    verify(subchannel1, never()).shutdown();
+    verify(subchannel2, never()).shutdown();
+    pool.clear();
+    verify(subchannel1).shutdown();
+    verify(subchannel2).shutdown();
+
+    verify(subchannel3, never()).shutdown();
+    assertThat(clock.numPendingTasks()).isEqualTo(0);
+    verify(helper, never()).runSerialized(any(ShutdownSubchannelTask.class));
+  }
+}
diff --git a/grpclb/src/test/java/io/grpc/grpclb/FakeSocketAddress.java b/grpclb/src/test/java/io/grpc/grpclb/FakeSocketAddress.java
new file mode 100644
index 0000000..b74ea74
--- /dev/null
+++ b/grpclb/src/test/java/io/grpc/grpclb/FakeSocketAddress.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.grpclb;
+
+import java.net.SocketAddress;
+
+final class FakeSocketAddress extends SocketAddress {
+  final String name;
+
+  FakeSocketAddress(String name) {
+    this.name = name;
+  }
+
+  @Override
+  public String toString() {
+    return "FakeSocketAddress-" + name;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other instanceof FakeSocketAddress) {
+      FakeSocketAddress otherAddr = (FakeSocketAddress) other;
+      return name.equals(otherAddr.name);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return name.hashCode();
+  }
+}
diff --git a/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java b/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
new file mode 100644
index 0000000..e540528
--- /dev/null
+++ b/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
@@ -0,0 +1,1595 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.grpclb;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.ConnectivityState.CONNECTING;
+import static io.grpc.ConnectivityState.IDLE;
+import static io.grpc.ConnectivityState.READY;
+import static io.grpc.ConnectivityState.SHUTDOWN;
+import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
+import static io.grpc.grpclb.GrpclbState.BUFFER_ENTRY;
+import static io.grpc.grpclb.GrpclbState.DROP_PICK_RESULT;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.Iterables;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.util.Durations;
+import com.google.protobuf.util.Timestamps;
+import io.grpc.Attributes;
+import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
+import io.grpc.ConnectivityState;
+import io.grpc.ConnectivityStateInfo;
+import io.grpc.EquivalentAddressGroup;
+import io.grpc.LoadBalancer.Helper;
+import io.grpc.LoadBalancer.PickResult;
+import io.grpc.LoadBalancer.PickSubchannelArgs;
+import io.grpc.LoadBalancer.Subchannel;
+import io.grpc.LoadBalancer.SubchannelPicker;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.grpclb.GrpclbState.BackendEntry;
+import io.grpc.grpclb.GrpclbState.DropEntry;
+import io.grpc.grpclb.GrpclbState.ErrorEntry;
+import io.grpc.grpclb.GrpclbState.RoundRobinPicker;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.internal.BackoffPolicy;
+import io.grpc.internal.FakeClock;
+import io.grpc.internal.GrpcAttributes;
+import io.grpc.internal.ObjectPool;
+import io.grpc.internal.SerializingExecutor;
+import io.grpc.internal.TimeProvider;
+import io.grpc.lb.v1.ClientStats;
+import io.grpc.lb.v1.ClientStatsPerToken;
+import io.grpc.lb.v1.InitialLoadBalanceRequest;
+import io.grpc.lb.v1.InitialLoadBalanceResponse;
+import io.grpc.lb.v1.LoadBalanceRequest;
+import io.grpc.lb.v1.LoadBalanceResponse;
+import io.grpc.lb.v1.LoadBalancerGrpc;
+import io.grpc.lb.v1.Server;
+import io.grpc.lb.v1.ServerList;
+import io.grpc.stub.StreamObserver;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/** Unit tests for {@link GrpclbLoadBalancer}. */
+@RunWith(JUnit4.class)
+public class GrpclbLoadBalancerTest {
+  private static final Attributes.Key<String> RESOLUTION_ATTR =
+      Attributes.Key.create("resolution-attr");
+  private static final String SERVICE_AUTHORITY = "api.google.com";
+  private static final FakeClock.TaskFilter LOAD_REPORTING_TASK_FILTER =
+      new FakeClock.TaskFilter() {
+        @Override
+        public boolean shouldAccept(Runnable command) {
+          return command instanceof GrpclbState.LoadReportingTask;
+        }
+      };
+  private static final FakeClock.TaskFilter FALLBACK_MODE_TASK_FILTER =
+      new FakeClock.TaskFilter() {
+        @Override
+        public boolean shouldAccept(Runnable command) {
+          return command instanceof GrpclbState.FallbackModeTask;
+        }
+      };
+  private static final FakeClock.TaskFilter LB_RPC_RETRY_TASK_FILTER =
+      new FakeClock.TaskFilter() {
+        @Override
+        public boolean shouldAccept(Runnable command) {
+          return command instanceof GrpclbState.LbRpcRetryTask;
+        }
+      };
+  private static final Attributes LB_BACKEND_ATTRS =
+      Attributes.newBuilder().set(GrpcAttributes.ATTR_LB_PROVIDED_BACKEND, true).build();
+
+  @Mock
+  private Helper helper;
+  @Mock
+  private SubchannelPool subchannelPool;
+  private SubchannelPicker currentPicker;
+  private LoadBalancerGrpc.LoadBalancerImplBase mockLbService;
+  @Captor
+  private ArgumentCaptor<StreamObserver<LoadBalanceResponse>> lbResponseObserverCaptor;
+  private final FakeClock fakeClock = new FakeClock();
+  private final LinkedList<StreamObserver<LoadBalanceRequest>> lbRequestObservers =
+      new LinkedList<StreamObserver<LoadBalanceRequest>>();
+  private final LinkedList<Subchannel> mockSubchannels = new LinkedList<Subchannel>();
+  private final LinkedList<ManagedChannel> fakeOobChannels = new LinkedList<ManagedChannel>();
+  private final ArrayList<Subchannel> subchannelTracker = new ArrayList<>();
+  private final ArrayList<ManagedChannel> oobChannelTracker = new ArrayList<>();
+  private final ArrayList<String> failingLbAuthorities = new ArrayList<>();
+  private final TimeProvider timeProvider = new TimeProvider() {
+      @Override
+      public long currentTimeNanos() {
+        return fakeClock.getTicker().read();
+      }
+    };
+  private io.grpc.Server fakeLbServer;
+  @Captor
+  private ArgumentCaptor<SubchannelPicker> pickerCaptor;
+  private final SerializingExecutor channelExecutor =
+      new SerializingExecutor(MoreExecutors.directExecutor());
+  @Mock
+  private ObjectPool<ScheduledExecutorService> timerServicePool;
+  @Mock
+  private BackoffPolicy.Provider backoffPolicyProvider;
+  @Mock
+  private BackoffPolicy backoffPolicy1;
+  @Mock
+  private BackoffPolicy backoffPolicy2;
+  private GrpclbLoadBalancer balancer;
+
+  @SuppressWarnings("unchecked")
+  @Before
+  public void setUp() throws Exception {
+    MockitoAnnotations.initMocks(this);
+    mockLbService = mock(LoadBalancerGrpc.LoadBalancerImplBase.class, delegatesTo(
+        new LoadBalancerGrpc.LoadBalancerImplBase() {
+          @Override
+          public StreamObserver<LoadBalanceRequest> balanceLoad(
+              final StreamObserver<LoadBalanceResponse> responseObserver) {
+            StreamObserver<LoadBalanceRequest> requestObserver =
+                mock(StreamObserver.class);
+            Answer<Void> closeRpc = new Answer<Void>() {
+                @Override
+                public Void answer(InvocationOnMock invocation) {
+                  responseObserver.onCompleted();
+                  return null;
+                }
+              };
+            doAnswer(closeRpc).when(requestObserver).onCompleted();
+            lbRequestObservers.add(requestObserver);
+            return requestObserver;
+          }
+        }));
+    fakeLbServer = InProcessServerBuilder.forName("fakeLb")
+        .directExecutor().addService(mockLbService).build().start();
+    doAnswer(new Answer<ManagedChannel>() {
+        @Override
+        public ManagedChannel answer(InvocationOnMock invocation) throws Throwable {
+          String authority = (String) invocation.getArguments()[1];
+          ManagedChannel channel;
+          if (failingLbAuthorities.contains(authority)) {
+            channel = InProcessChannelBuilder.forName("nonExistFakeLb").directExecutor()
+                .overrideAuthority(authority).build();
+          } else {
+            channel = InProcessChannelBuilder.forName("fakeLb").directExecutor()
+                .overrideAuthority(authority).build();
+          }
+          fakeOobChannels.add(channel);
+          oobChannelTracker.add(channel);
+          return channel;
+        }
+      }).when(helper).createOobChannel(any(EquivalentAddressGroup.class), any(String.class));
+    doAnswer(new Answer<Subchannel>() {
+        @Override
+        public Subchannel answer(InvocationOnMock invocation) throws Throwable {
+          Subchannel subchannel = mock(Subchannel.class);
+          EquivalentAddressGroup eag = (EquivalentAddressGroup) invocation.getArguments()[0];
+          Attributes attrs = (Attributes) invocation.getArguments()[1];
+          when(subchannel.getAddresses()).thenReturn(eag);
+          when(subchannel.getAttributes()).thenReturn(attrs);
+          mockSubchannels.add(subchannel);
+          subchannelTracker.add(subchannel);
+          return subchannel;
+        }
+      }).when(subchannelPool).takeOrCreateSubchannel(
+          any(EquivalentAddressGroup.class), any(Attributes.class));
+    doAnswer(new Answer<Void>() {
+        @Override
+        public Void answer(InvocationOnMock invocation) throws Throwable {
+          Runnable task = (Runnable) invocation.getArguments()[0];
+          channelExecutor.execute(task);
+          return null;
+        }
+      }).when(helper).runSerialized(any(Runnable.class));
+    doAnswer(new Answer<Void>() {
+        @Override
+        public Void answer(InvocationOnMock invocation) throws Throwable {
+          currentPicker = (SubchannelPicker) invocation.getArguments()[1];
+          return null;
+        }
+      }).when(helper).updateBalancingState(
+          any(ConnectivityState.class), any(SubchannelPicker.class));
+    when(helper.getAuthority()).thenReturn(SERVICE_AUTHORITY);
+    ScheduledExecutorService timerService = fakeClock.getScheduledExecutorService();
+    when(timerServicePool.getObject()).thenReturn(timerService);
+    when(backoffPolicy1.nextBackoffNanos()).thenReturn(10L, 100L);
+    when(backoffPolicy2.nextBackoffNanos()).thenReturn(10L, 100L);
+    when(backoffPolicyProvider.get()).thenReturn(backoffPolicy1, backoffPolicy2);
+    balancer = new GrpclbLoadBalancer(
+        helper,
+        subchannelPool,
+        timerServicePool,
+        timeProvider,
+        backoffPolicyProvider);
+    verify(subchannelPool).init(same(helper), same(timerService));
+  }
+
+  @After
+  public void tearDown() {
+    try {
+      if (balancer != null) {
+        channelExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+              balancer.shutdown();
+            }
+          });
+      }
+      for (ManagedChannel channel : oobChannelTracker) {
+        assertTrue(channel + " is shutdown", channel.isShutdown());
+        // balancer should have closed the LB stream, terminating the OOB channel.
+        assertTrue(channel + " is terminated", channel.isTerminated());
+      }
+      // GRPCLB manages subchannels only through subchannelPool
+      for (Subchannel subchannel: subchannelTracker) {
+        verify(subchannelPool).returnSubchannel(same(subchannel));
+        // Our mock subchannelPool never calls Subchannel.shutdown(), thus we can tell if
+        // LoadBalancer has called it expectedly.
+        verify(subchannel, never()).shutdown();
+      }
+      verify(helper, never())
+          .createSubchannel(any(EquivalentAddressGroup.class), any(Attributes.class));
+      // No timer should linger after shutdown
+      assertThat(fakeClock.getPendingTasks()).isEmpty();
+    } finally {
+      if (fakeLbServer != null) {
+        fakeLbServer.shutdownNow();
+      }
+    }
+  }
+
+  @Test
+  public void roundRobinPickerNoDrop() {
+    GrpclbClientLoadRecorder loadRecorder = new GrpclbClientLoadRecorder(timeProvider);
+    Subchannel subchannel = mock(Subchannel.class);
+    BackendEntry b1 = new BackendEntry(subchannel, loadRecorder, "LBTOKEN0001");
+    BackendEntry b2 = new BackendEntry(subchannel, loadRecorder, "LBTOKEN0002");
+
+    List<BackendEntry> pickList = Arrays.asList(b1, b2);
+    RoundRobinPicker picker = new RoundRobinPicker(Collections.<DropEntry>emptyList(), pickList);
+
+    PickSubchannelArgs args1 = mock(PickSubchannelArgs.class);
+    Metadata headers1 = new Metadata();
+    // The existing token on the headers will be replaced
+    headers1.put(GrpclbConstants.TOKEN_METADATA_KEY, "LBTOKEN__OLD");
+    when(args1.getHeaders()).thenReturn(headers1);
+    assertSame(b1.result, picker.pickSubchannel(args1));
+    verify(args1).getHeaders();
+    assertThat(headers1.getAll(GrpclbConstants.TOKEN_METADATA_KEY)).containsExactly("LBTOKEN0001");
+
+    PickSubchannelArgs args2 = mock(PickSubchannelArgs.class);
+    Metadata headers2 = new Metadata();
+    when(args2.getHeaders()).thenReturn(headers2);
+    assertSame(b2.result, picker.pickSubchannel(args2));
+    verify(args2).getHeaders();
+    assertThat(headers2.getAll(GrpclbConstants.TOKEN_METADATA_KEY)).containsExactly("LBTOKEN0002");
+
+    PickSubchannelArgs args3 = mock(PickSubchannelArgs.class);
+    Metadata headers3 = new Metadata();
+    when(args3.getHeaders()).thenReturn(headers3);
+    assertSame(b1.result, picker.pickSubchannel(args3));
+    verify(args3).getHeaders();
+    assertThat(headers3.getAll(GrpclbConstants.TOKEN_METADATA_KEY)).containsExactly("LBTOKEN0001");
+
+    verify(subchannel, never()).getAttributes();
+  }
+
+
+  @Test
+  public void roundRobinPickerWithDrop() {
+    assertTrue(DROP_PICK_RESULT.isDrop());
+    GrpclbClientLoadRecorder loadRecorder = new GrpclbClientLoadRecorder(timeProvider);
+    Subchannel subchannel = mock(Subchannel.class);
+    // 1 out of 2 requests are to be dropped
+    DropEntry d = new DropEntry(loadRecorder, "LBTOKEN0003");
+    List<DropEntry> dropList = Arrays.asList(null, d);
+
+    BackendEntry b1 = new BackendEntry(subchannel, loadRecorder, "LBTOKEN0001");
+    BackendEntry b2 = new BackendEntry(subchannel, loadRecorder, "LBTOKEN0002");
+    List<BackendEntry> pickList = Arrays.asList(b1, b2);
+    RoundRobinPicker picker = new RoundRobinPicker(dropList, pickList);
+
+    // dropList[0], pickList[0]
+    PickSubchannelArgs args1 = mock(PickSubchannelArgs.class);
+    Metadata headers1 = new Metadata();
+    headers1.put(GrpclbConstants.TOKEN_METADATA_KEY, "LBTOKEN__OLD");
+    when(args1.getHeaders()).thenReturn(headers1);
+    assertSame(b1.result, picker.pickSubchannel(args1));
+    verify(args1).getHeaders();
+    assertThat(headers1.getAll(GrpclbConstants.TOKEN_METADATA_KEY)).containsExactly("LBTOKEN0001");
+
+    // dropList[1]: drop
+    PickSubchannelArgs args2 = mock(PickSubchannelArgs.class);
+    Metadata headers2 = new Metadata();
+    when(args2.getHeaders()).thenReturn(headers2);
+    assertSame(DROP_PICK_RESULT, picker.pickSubchannel(args2));
+    verify(args2, never()).getHeaders();
+
+    // dropList[0], pickList[1]
+    PickSubchannelArgs args3 = mock(PickSubchannelArgs.class);
+    Metadata headers3 = new Metadata();
+    when(args3.getHeaders()).thenReturn(headers3);
+    assertSame(b2.result, picker.pickSubchannel(args3));
+    verify(args3).getHeaders();
+    assertThat(headers3.getAll(GrpclbConstants.TOKEN_METADATA_KEY)).containsExactly("LBTOKEN0002");
+
+    // dropList[1]: drop
+    PickSubchannelArgs args4 = mock(PickSubchannelArgs.class);
+    Metadata headers4 = new Metadata();
+    when(args4.getHeaders()).thenReturn(headers4);
+    assertSame(DROP_PICK_RESULT, picker.pickSubchannel(args4));
+    verify(args4, never()).getHeaders();
+
+    // dropList[0], pickList[0]
+    PickSubchannelArgs args5 = mock(PickSubchannelArgs.class);
+    Metadata headers5 = new Metadata();
+    when(args5.getHeaders()).thenReturn(headers5);
+    assertSame(b1.result, picker.pickSubchannel(args5));
+    verify(args5).getHeaders();
+    assertThat(headers5.getAll(GrpclbConstants.TOKEN_METADATA_KEY)).containsExactly("LBTOKEN0001");
+
+    verify(subchannel, never()).getAttributes();
+  }
+
+  @Test
+  public void loadReporting() {
+    Metadata headers = new Metadata();
+    PickSubchannelArgs args = mock(PickSubchannelArgs.class);
+    when(args.getHeaders()).thenReturn(headers);
+
+    long loadReportIntervalMillis = 1983;
+    List<EquivalentAddressGroup> grpclbResolutionList = createResolvedServerAddresses(true);
+    Attributes grpclbResolutionAttrs = Attributes.EMPTY;
+    deliverResolvedAddresses(grpclbResolutionList, grpclbResolutionAttrs);
+
+    // Fallback timer is started as soon as address is resolved.
+    assertEquals(1, fakeClock.numPendingTasks(FALLBACK_MODE_TASK_FILTER));
+
+    assertEquals(1, fakeOobChannels.size());
+    verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+    StreamObserver<LoadBalanceResponse> lbResponseObserver = lbResponseObserverCaptor.getValue();
+    assertEquals(1, lbRequestObservers.size());
+    StreamObserver<LoadBalanceRequest> lbRequestObserver = lbRequestObservers.poll();
+    InOrder inOrder = inOrder(lbRequestObserver);
+    InOrder helperInOrder = inOrder(helper, subchannelPool);
+
+    inOrder.verify(lbRequestObserver).onNext(
+        eq(LoadBalanceRequest.newBuilder().setInitialRequest(
+                InitialLoadBalanceRequest.newBuilder().setName(SERVICE_AUTHORITY).build())
+            .build()));
+
+    // Simulate receiving LB response
+    assertEquals(0, fakeClock.numPendingTasks(LOAD_REPORTING_TASK_FILTER));
+    lbResponseObserver.onNext(buildInitialResponse(loadReportIntervalMillis));
+
+    // Load reporting task is scheduled
+    assertEquals(1, fakeClock.numPendingTasks(LOAD_REPORTING_TASK_FILTER));
+    assertEquals(0, fakeClock.runDueTasks());
+
+    List<ServerEntry> backends = Arrays.asList(
+        new ServerEntry("127.0.0.1", 2000, "token0001"),
+        new ServerEntry("token0001"),  // drop
+        new ServerEntry("127.0.0.1", 2010, "token0002"),
+        new ServerEntry("token0003"));  // drop
+
+    lbResponseObserver.onNext(buildLbResponse(backends));
+
+    assertEquals(2, mockSubchannels.size());
+    Subchannel subchannel1 = mockSubchannels.poll();
+    Subchannel subchannel2 = mockSubchannels.poll();
+    deliverSubchannelState(subchannel1, ConnectivityStateInfo.forNonError(CONNECTING));
+    deliverSubchannelState(subchannel2, ConnectivityStateInfo.forNonError(CONNECTING));
+    deliverSubchannelState(subchannel1, ConnectivityStateInfo.forNonError(READY));
+    deliverSubchannelState(subchannel2, ConnectivityStateInfo.forNonError(READY));
+
+    helperInOrder.verify(helper, atLeast(1))
+        .updateBalancingState(eq(READY), pickerCaptor.capture());
+    RoundRobinPicker picker = (RoundRobinPicker) pickerCaptor.getValue();
+    assertThat(picker.dropList).containsExactly(
+        null,
+        new DropEntry(getLoadRecorder(), "token0001"),
+        null,
+        new DropEntry(getLoadRecorder(), "token0003")).inOrder();
+    assertThat(picker.pickList).containsExactly(
+        new BackendEntry(subchannel1, getLoadRecorder(), "token0001"),
+        new BackendEntry(subchannel2, getLoadRecorder(), "token0002")).inOrder();
+
+    // Report, no data
+    assertNextReport(
+        inOrder, lbRequestObserver, loadReportIntervalMillis,
+        ClientStats.newBuilder().build());
+
+    PickResult pick1 = picker.pickSubchannel(args);
+    assertSame(subchannel1, pick1.getSubchannel());
+    assertSame(getLoadRecorder(), pick1.getStreamTracerFactory());
+
+    // Merely the pick will not be recorded as upstart.
+    assertNextReport(
+        inOrder, lbRequestObserver, loadReportIntervalMillis,
+        ClientStats.newBuilder().build());
+
+    ClientStreamTracer tracer1 =
+        pick1.getStreamTracerFactory().newClientStreamTracer(CallOptions.DEFAULT, new Metadata());
+
+    PickResult pick2 = picker.pickSubchannel(args);
+    assertNull(pick2.getSubchannel());
+    assertSame(DROP_PICK_RESULT, pick2);
+
+    // Report includes upstart of pick1 and the drop of pick2
+    assertNextReport(
+        inOrder, lbRequestObserver, loadReportIntervalMillis,
+        ClientStats.newBuilder()
+            .setNumCallsStarted(2)
+            .setNumCallsFinished(1)  // pick2
+            .addCallsFinishedWithDrop(
+                ClientStatsPerToken.newBuilder()
+                    .setLoadBalanceToken("token0001")
+                    .setNumCalls(1)          // pick2
+                    .build())
+            .build());
+
+    PickResult pick3 = picker.pickSubchannel(args);
+    assertSame(subchannel2, pick3.getSubchannel());
+    assertSame(getLoadRecorder(), pick3.getStreamTracerFactory());
+    ClientStreamTracer tracer3 =
+        pick3.getStreamTracerFactory().newClientStreamTracer(CallOptions.DEFAULT, new Metadata());
+
+    // pick3 has sent out headers
+    tracer3.outboundHeaders();
+
+    // 3rd report includes pick3's upstart
+    assertNextReport(
+        inOrder, lbRequestObserver, loadReportIntervalMillis,
+        ClientStats.newBuilder()
+            .setNumCallsStarted(1)
+            .build());
+
+    PickResult pick4 = picker.pickSubchannel(args);
+    assertNull(pick4.getSubchannel());
+    assertSame(DROP_PICK_RESULT, pick4);
+
+    // pick1 ended without sending anything
+    tracer1.streamClosed(Status.CANCELLED);
+
+    // 4th report includes end of pick1 and drop of pick4
+    assertNextReport(
+        inOrder, lbRequestObserver, loadReportIntervalMillis,
+        ClientStats.newBuilder()
+            .setNumCallsStarted(1)  // pick4
+            .setNumCallsFinished(2)
+            .setNumCallsFinishedWithClientFailedToSend(1)   // pick1
+            .addCallsFinishedWithDrop(
+                ClientStatsPerToken.newBuilder()
+                    .setLoadBalanceToken("token0003")
+                    .setNumCalls(1)   // pick4
+                    .build())
+        .build());
+
+    PickResult pick5 = picker.pickSubchannel(args);
+    assertSame(subchannel1, pick1.getSubchannel());
+    assertSame(getLoadRecorder(), pick5.getStreamTracerFactory());
+    ClientStreamTracer tracer5 =
+        pick5.getStreamTracerFactory().newClientStreamTracer(CallOptions.DEFAULT, new Metadata());
+
+    // pick3 ended without receiving response headers
+    tracer3.streamClosed(Status.DEADLINE_EXCEEDED);
+
+    // pick5 sent and received headers
+    tracer5.outboundHeaders();
+    tracer5.inboundHeaders();
+
+    // 5th report includes pick3's end and pick5's upstart
+    assertNextReport(
+        inOrder, lbRequestObserver, loadReportIntervalMillis,
+        ClientStats.newBuilder()
+            .setNumCallsStarted(1)  // pick5
+            .setNumCallsFinished(1)  // pick3
+            .build());
+
+    // pick5 ends
+    tracer5.streamClosed(Status.OK);
+
+    // 6th report includes pick5's end
+    assertNextReport(
+        inOrder, lbRequestObserver, loadReportIntervalMillis,
+        ClientStats.newBuilder()
+            .setNumCallsFinished(1)
+            .setNumCallsFinishedKnownReceived(1)
+            .build());
+
+    assertEquals(1, fakeClock.numPendingTasks());
+    // Balancer closes the stream, scheduled reporting task cancelled
+    lbResponseObserver.onError(Status.UNAVAILABLE.asException());
+    assertEquals(0, fakeClock.numPendingTasks());
+
+    // New stream created
+    verify(mockLbService, times(2)).balanceLoad(lbResponseObserverCaptor.capture());
+    lbResponseObserver = lbResponseObserverCaptor.getValue();
+    assertEquals(1, lbRequestObservers.size());
+    lbRequestObserver = lbRequestObservers.poll();
+    inOrder = inOrder(lbRequestObserver);
+
+    inOrder.verify(lbRequestObserver).onNext(
+        eq(LoadBalanceRequest.newBuilder().setInitialRequest(
+                InitialLoadBalanceRequest.newBuilder().setName(SERVICE_AUTHORITY).build())
+            .build()));
+
+    // Load reporting is also requested
+    lbResponseObserver.onNext(buildInitialResponse(loadReportIntervalMillis));
+
+    // No picker created because balancer is still using the results from the last stream
+    helperInOrder.verify(helper, never())
+        .updateBalancingState(any(ConnectivityState.class), any(SubchannelPicker.class));
+
+    // Make a new pick on that picker.  It will not show up on the report of the new stream, because
+    // that picker is associated with the previous stream.
+    PickResult pick6 = picker.pickSubchannel(args);
+    assertNull(pick6.getSubchannel());
+    assertSame(DROP_PICK_RESULT, pick6);
+    assertNextReport(
+        inOrder, lbRequestObserver, loadReportIntervalMillis,
+        ClientStats.newBuilder().build());
+
+    // New stream got the list update
+    lbResponseObserver.onNext(buildLbResponse(backends));
+
+    // Same backends, thus no new subchannels
+    helperInOrder.verify(subchannelPool, never()).takeOrCreateSubchannel(
+        any(EquivalentAddressGroup.class), any(Attributes.class));
+    // But the new RoundRobinEntries have a new loadRecorder, thus considered different from
+    // the previous list, thus a new picker is created
+    helperInOrder.verify(helper).updateBalancingState(eq(READY), pickerCaptor.capture());
+    picker = (RoundRobinPicker) pickerCaptor.getValue();
+
+    PickResult pick1p = picker.pickSubchannel(args);
+    assertSame(subchannel1, pick1p.getSubchannel());
+    assertSame(getLoadRecorder(), pick1p.getStreamTracerFactory());
+    pick1p.getStreamTracerFactory().newClientStreamTracer(CallOptions.DEFAULT, new Metadata());
+
+    // The pick from the new stream will be included in the report
+    assertNextReport(
+        inOrder, lbRequestObserver, loadReportIntervalMillis,
+        ClientStats.newBuilder()
+            .setNumCallsStarted(1)
+            .build());
+
+    verify(args, atLeast(0)).getHeaders();
+    verifyNoMoreInteractions(args);
+  }
+
+  @Test
+  public void abundantInitialResponse() {
+    Metadata headers = new Metadata();
+    PickSubchannelArgs args = mock(PickSubchannelArgs.class);
+    when(args.getHeaders()).thenReturn(headers);
+
+    List<EquivalentAddressGroup> grpclbResolutionList = createResolvedServerAddresses(true);
+    Attributes grpclbResolutionAttrs = Attributes.EMPTY;
+    deliverResolvedAddresses(grpclbResolutionList, grpclbResolutionAttrs);
+    assertEquals(1, fakeOobChannels.size());
+    verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+    StreamObserver<LoadBalanceResponse> lbResponseObserver = lbResponseObserverCaptor.getValue();
+
+    // Simulate LB initial response
+    assertEquals(0, fakeClock.numPendingTasks(LOAD_REPORTING_TASK_FILTER));
+    lbResponseObserver.onNext(buildInitialResponse(1983));
+
+    // Load reporting task is scheduled
+    assertEquals(1, fakeClock.numPendingTasks(LOAD_REPORTING_TASK_FILTER));
+    FakeClock.ScheduledTask scheduledTask = fakeClock.getPendingTasks().iterator().next();
+    assertEquals(1983, scheduledTask.getDelay(TimeUnit.MILLISECONDS));
+
+    // Simulate an abundant LB initial response, with a different report interval
+    lbResponseObserver.onNext(buildInitialResponse(9097));
+    // It doesn't affect load-reporting at all
+    assertThat(fakeClock.getPendingTasks(LOAD_REPORTING_TASK_FILTER))
+        .containsExactly(scheduledTask);
+    assertEquals(1983, scheduledTask.getDelay(TimeUnit.MILLISECONDS));
+  }
+
+  @Test
+  public void raceBetweenLoadReportingAndLbStreamClosure() {
+    Metadata headers = new Metadata();
+    PickSubchannelArgs args = mock(PickSubchannelArgs.class);
+    when(args.getHeaders()).thenReturn(headers);
+
+    List<EquivalentAddressGroup> grpclbResolutionList = createResolvedServerAddresses(true);
+    Attributes grpclbResolutionAttrs = Attributes.EMPTY;
+    deliverResolvedAddresses(grpclbResolutionList, grpclbResolutionAttrs);
+    assertEquals(1, fakeOobChannels.size());
+    verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+    StreamObserver<LoadBalanceResponse> lbResponseObserver = lbResponseObserverCaptor.getValue();
+    assertEquals(1, lbRequestObservers.size());
+    StreamObserver<LoadBalanceRequest> lbRequestObserver = lbRequestObservers.poll();
+    InOrder inOrder = inOrder(lbRequestObserver);
+
+    inOrder.verify(lbRequestObserver).onNext(
+        eq(LoadBalanceRequest.newBuilder().setInitialRequest(
+                InitialLoadBalanceRequest.newBuilder().setName(SERVICE_AUTHORITY).build())
+            .build()));
+
+    // Simulate receiving LB response
+    assertEquals(0, fakeClock.numPendingTasks(LOAD_REPORTING_TASK_FILTER));
+    lbResponseObserver.onNext(buildInitialResponse(1983));
+
+    // Load reporting task is scheduled
+    assertEquals(1, fakeClock.numPendingTasks(LOAD_REPORTING_TASK_FILTER));
+    FakeClock.ScheduledTask scheduledTask = fakeClock.getPendingTasks().iterator().next();
+    assertEquals(1983, scheduledTask.getDelay(TimeUnit.MILLISECONDS));
+
+    // Close lbStream
+    lbResponseObserver.onCompleted();
+
+    // Reporting task cancelled
+    assertEquals(0, fakeClock.numPendingTasks(LOAD_REPORTING_TASK_FILTER));
+
+    // Simulate a race condition where the task has just started when its cancelled
+    scheduledTask.command.run();
+
+    // No report sent. No new task scheduled
+    inOrder.verify(lbRequestObserver, never()).onNext(any(LoadBalanceRequest.class));
+    assertEquals(0, fakeClock.numPendingTasks(LOAD_REPORTING_TASK_FILTER));
+  }
+
+  private void assertNextReport(
+      InOrder inOrder, StreamObserver<LoadBalanceRequest> lbRequestObserver,
+      long loadReportIntervalMillis, ClientStats expectedReport) {
+    assertEquals(0, fakeClock.forwardTime(loadReportIntervalMillis - 1, TimeUnit.MILLISECONDS));
+    inOrder.verifyNoMoreInteractions();
+    assertEquals(1, fakeClock.forwardTime(1, TimeUnit.MILLISECONDS));
+    assertEquals(1, fakeClock.numPendingTasks());
+    inOrder.verify(lbRequestObserver).onNext(
+        eq(LoadBalanceRequest.newBuilder()
+            .setClientStats(
+                ClientStats.newBuilder(expectedReport)
+                .setTimestamp(Timestamps.fromNanos(fakeClock.getTicker().read()))
+                .build())
+            .build()));
+  }
+
+  @Test
+  public void acquireAndReleaseScheduledExecutor() {
+    verify(timerServicePool).getObject();
+    verifyNoMoreInteractions(timerServicePool);
+
+    balancer.shutdown();
+    verify(timerServicePool).returnObject(same(fakeClock.getScheduledExecutorService()));
+    verifyNoMoreInteractions(timerServicePool);
+  }
+
+  @Test
+  public void nameResolutionFailsThenRecoverToDelegate() {
+    Status error = Status.NOT_FOUND.withDescription("www.google.com not found");
+    deliverNameResolutionError(error);
+    verify(helper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture());
+    RoundRobinPicker picker = (RoundRobinPicker) pickerCaptor.getValue();
+    assertThat(picker.dropList).isEmpty();
+    assertThat(picker.pickList).containsExactly(new ErrorEntry(error));
+
+    // Recover with a subsequent success
+    List<EquivalentAddressGroup> resolvedServers = createResolvedServerAddresses(false);
+
+    Attributes resolutionAttrs = Attributes.newBuilder().set(RESOLUTION_ATTR, "yeah").build();
+    deliverResolvedAddresses(resolvedServers, resolutionAttrs);
+  }
+
+  @Test
+  public void nameResolutionFailsThenRecoverToGrpclb() {
+    Status error = Status.NOT_FOUND.withDescription("www.google.com not found");
+    deliverNameResolutionError(error);
+    verify(helper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture());
+    RoundRobinPicker picker = (RoundRobinPicker) pickerCaptor.getValue();
+    assertThat(picker.dropList).isEmpty();
+    assertThat(picker.pickList).containsExactly(new ErrorEntry(error));
+
+    // Recover with a subsequent success
+    List<EquivalentAddressGroup> resolvedServers = createResolvedServerAddresses(true);
+    EquivalentAddressGroup eag = resolvedServers.get(0);
+
+    Attributes resolutionAttrs = Attributes.EMPTY;
+    deliverResolvedAddresses(resolvedServers, resolutionAttrs);
+
+    verify(helper).createOobChannel(eq(eag), eq(lbAuthority(0)));
+    verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+  }
+
+  @Test
+  public void grpclbThenNameResolutionFails() {
+    InOrder inOrder = inOrder(helper, subchannelPool);
+    // Go to GRPCLB first
+    List<EquivalentAddressGroup> grpclbResolutionList = createResolvedServerAddresses(true);
+    Attributes grpclbResolutionAttrs = Attributes.EMPTY;
+    deliverResolvedAddresses(grpclbResolutionList, grpclbResolutionAttrs);
+
+    verify(helper).createOobChannel(eq(grpclbResolutionList.get(0)), eq(lbAuthority(0)));
+    assertEquals(1, fakeOobChannels.size());
+    ManagedChannel oobChannel = fakeOobChannels.poll();
+    verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+    StreamObserver<LoadBalanceResponse> lbResponseObserver = lbResponseObserverCaptor.getValue();
+
+    // Let name resolution fail before round-robin list is ready
+    Status error = Status.NOT_FOUND.withDescription("www.google.com not found");
+    deliverNameResolutionError(error);
+
+    inOrder.verify(helper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture());
+    RoundRobinPicker picker = (RoundRobinPicker) pickerCaptor.getValue();
+    assertThat(picker.dropList).isEmpty();
+    assertThat(picker.pickList).containsExactly(new ErrorEntry(error));
+    assertFalse(oobChannel.isShutdown());
+
+    // Simulate receiving LB response
+    List<ServerEntry> backends = Arrays.asList(
+        new ServerEntry("127.0.0.1", 2000, "TOKEN1"),
+        new ServerEntry("127.0.0.1", 2010, "TOKEN2"));
+    verify(helper, never()).runSerialized(any(Runnable.class));
+    lbResponseObserver.onNext(buildInitialResponse());
+    lbResponseObserver.onNext(buildLbResponse(backends));
+
+    verify(helper, times(2)).runSerialized(any(Runnable.class));
+    inOrder.verify(subchannelPool).takeOrCreateSubchannel(
+        eq(new EquivalentAddressGroup(backends.get(0).addr, LB_BACKEND_ATTRS)),
+        any(Attributes.class));
+    inOrder.verify(subchannelPool).takeOrCreateSubchannel(
+        eq(new EquivalentAddressGroup(backends.get(1).addr, LB_BACKEND_ATTRS)),
+        any(Attributes.class));
+  }
+
+  @Test
+  public void grpclbUpdatedAddresses_avoidsReconnect() {
+    List<EquivalentAddressGroup> grpclbResolutionList =
+        createResolvedServerAddresses(true, false);
+    Attributes grpclbResolutionAttrs = Attributes.EMPTY;
+    deliverResolvedAddresses(grpclbResolutionList, grpclbResolutionAttrs);
+
+    verify(helper).createOobChannel(eq(grpclbResolutionList.get(0)), eq(lbAuthority(0)));
+    ManagedChannel oobChannel = fakeOobChannels.poll();
+    assertEquals(1, lbRequestObservers.size());
+
+    List<EquivalentAddressGroup> grpclbResolutionList2 =
+        createResolvedServerAddresses(true, false, true);
+    EquivalentAddressGroup combinedEag = new EquivalentAddressGroup(Arrays.asList(
+        grpclbResolutionList2.get(0).getAddresses().get(0),
+        grpclbResolutionList2.get(2).getAddresses().get(0)),
+        lbAttributes(lbAuthority(0)));
+    deliverResolvedAddresses(grpclbResolutionList2, grpclbResolutionAttrs);
+    verify(helper).updateOobChannelAddresses(eq(oobChannel), eq(combinedEag));
+    assertEquals(1, lbRequestObservers.size()); // No additional RPC
+  }
+
+  @Test
+  public void grpclbUpdatedAddresses_reconnectOnAuthorityChange() {
+    List<EquivalentAddressGroup> grpclbResolutionList =
+        createResolvedServerAddresses(true, false);
+    Attributes grpclbResolutionAttrs = Attributes.EMPTY;
+    deliverResolvedAddresses(grpclbResolutionList, grpclbResolutionAttrs);
+
+    verify(helper).createOobChannel(eq(grpclbResolutionList.get(0)), eq(lbAuthority(0)));
+    ManagedChannel oobChannel = fakeOobChannels.poll();
+    assertEquals(1, lbRequestObservers.size());
+
+    final String newAuthority = "some-new-authority";
+    List<EquivalentAddressGroup> grpclbResolutionList2 =
+        createResolvedServerAddresses(false);
+    grpclbResolutionList2.add(new EquivalentAddressGroup(
+        new FakeSocketAddress("somethingNew"), lbAttributes(newAuthority)));
+    deliverResolvedAddresses(grpclbResolutionList2, grpclbResolutionAttrs);
+    assertTrue(oobChannel.isTerminated());
+    verify(helper).createOobChannel(eq(grpclbResolutionList2.get(1)), eq(newAuthority));
+    assertEquals(2, lbRequestObservers.size()); // An additional RPC
+  }
+
+  @Test
+  public void grpclbWorking() {
+    InOrder inOrder = inOrder(helper, subchannelPool);
+    List<EquivalentAddressGroup> grpclbResolutionList = createResolvedServerAddresses(true);
+    Attributes grpclbResolutionAttrs = Attributes.EMPTY;
+    deliverResolvedAddresses(grpclbResolutionList, grpclbResolutionAttrs);
+
+    // Fallback timer is started as soon as the addresses are resolved.
+    assertEquals(1, fakeClock.numPendingTasks(FALLBACK_MODE_TASK_FILTER));
+
+    verify(helper).createOobChannel(eq(grpclbResolutionList.get(0)), eq(lbAuthority(0)));
+    assertEquals(1, fakeOobChannels.size());
+    ManagedChannel oobChannel = fakeOobChannels.poll();
+    verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+    StreamObserver<LoadBalanceResponse> lbResponseObserver = lbResponseObserverCaptor.getValue();
+    assertEquals(1, lbRequestObservers.size());
+    StreamObserver<LoadBalanceRequest> lbRequestObserver = lbRequestObservers.poll();
+    verify(lbRequestObserver).onNext(
+        eq(LoadBalanceRequest.newBuilder().setInitialRequest(
+                InitialLoadBalanceRequest.newBuilder().setName(SERVICE_AUTHORITY).build())
+            .build()));
+
+    // Simulate receiving LB response
+    List<ServerEntry> backends1 = Arrays.asList(
+        new ServerEntry("127.0.0.1", 2000, "token0001"),
+        new ServerEntry("127.0.0.1", 2010, "token0002"));
+    inOrder.verify(helper, never())
+        .updateBalancingState(any(ConnectivityState.class), any(SubchannelPicker.class));
+    lbResponseObserver.onNext(buildInitialResponse());
+    lbResponseObserver.onNext(buildLbResponse(backends1));
+
+    inOrder.verify(subchannelPool).takeOrCreateSubchannel(
+        eq(new EquivalentAddressGroup(backends1.get(0).addr, LB_BACKEND_ATTRS)),
+        any(Attributes.class));
+    inOrder.verify(subchannelPool).takeOrCreateSubchannel(
+        eq(new EquivalentAddressGroup(backends1.get(1).addr, LB_BACKEND_ATTRS)),
+        any(Attributes.class));
+    assertEquals(2, mockSubchannels.size());
+    Subchannel subchannel1 = mockSubchannels.poll();
+    Subchannel subchannel2 = mockSubchannels.poll();
+    verify(subchannel1).requestConnection();
+    verify(subchannel2).requestConnection();
+    assertEquals(
+        new EquivalentAddressGroup(backends1.get(0).addr, LB_BACKEND_ATTRS),
+        subchannel1.getAddresses());
+    assertEquals(
+        new EquivalentAddressGroup(backends1.get(1).addr, LB_BACKEND_ATTRS),
+        subchannel2.getAddresses());
+
+    deliverSubchannelState(subchannel1, ConnectivityStateInfo.forNonError(CONNECTING));
+    deliverSubchannelState(subchannel2, ConnectivityStateInfo.forNonError(CONNECTING));
+    inOrder.verify(helper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
+    RoundRobinPicker picker0 = (RoundRobinPicker) pickerCaptor.getValue();
+    assertThat(picker0.dropList).containsExactly(null, null);
+    assertThat(picker0.pickList).containsExactly(BUFFER_ENTRY);
+    inOrder.verifyNoMoreInteractions();
+
+    // Let subchannels be connected
+    deliverSubchannelState(subchannel2, ConnectivityStateInfo.forNonError(READY));
+    inOrder.verify(helper).updateBalancingState(eq(READY), pickerCaptor.capture());
+    RoundRobinPicker picker1 = (RoundRobinPicker) pickerCaptor.getValue();
+
+    assertThat(picker1.dropList).containsExactly(null, null);
+    assertThat(picker1.pickList).containsExactly(
+        new BackendEntry(subchannel2, getLoadRecorder(), "token0002"));
+
+    deliverSubchannelState(subchannel1, ConnectivityStateInfo.forNonError(READY));
+    inOrder.verify(helper).updateBalancingState(eq(READY), pickerCaptor.capture());
+    RoundRobinPicker picker2 = (RoundRobinPicker) pickerCaptor.getValue();
+    assertThat(picker2.dropList).containsExactly(null, null);
+    assertThat(picker2.pickList).containsExactly(
+        new BackendEntry(subchannel1, getLoadRecorder(), "token0001"),
+        new BackendEntry(subchannel2, getLoadRecorder(), "token0002"))
+        .inOrder();
+
+    // Disconnected subchannels
+    verify(subchannel1).requestConnection();
+    deliverSubchannelState(subchannel1, ConnectivityStateInfo.forNonError(IDLE));
+    verify(subchannel1, times(2)).requestConnection();
+    inOrder.verify(helper).updateBalancingState(eq(READY), pickerCaptor.capture());
+    RoundRobinPicker picker3 = (RoundRobinPicker) pickerCaptor.getValue();
+    assertThat(picker3.dropList).containsExactly(null, null);
+    assertThat(picker3.pickList).containsExactly(
+        new BackendEntry(subchannel2, getLoadRecorder(), "token0002"));
+
+    deliverSubchannelState(subchannel1, ConnectivityStateInfo.forNonError(CONNECTING));
+    inOrder.verifyNoMoreInteractions();
+
+    // As long as there is at least one READY subchannel, round robin will work.
+    Status error1 = Status.UNAVAILABLE.withDescription("error1");
+    deliverSubchannelState(subchannel1, ConnectivityStateInfo.forTransientFailure(error1));
+    inOrder.verifyNoMoreInteractions();
+
+    // If no subchannel is READY, some with error and the others are IDLE, will report CONNECTING
+    verify(subchannel2).requestConnection();
+    deliverSubchannelState(subchannel2, ConnectivityStateInfo.forNonError(IDLE));
+    verify(subchannel2, times(2)).requestConnection();
+    inOrder.verify(helper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
+    RoundRobinPicker picker4 = (RoundRobinPicker) pickerCaptor.getValue();
+    assertThat(picker4.dropList).containsExactly(null, null);
+    assertThat(picker4.pickList).containsExactly(BUFFER_ENTRY);
+
+    // Update backends, with a drop entry
+    List<ServerEntry> backends2 =
+        Arrays.asList(
+            new ServerEntry("127.0.0.1", 2030, "token0003"),  // New address
+            new ServerEntry("token0003"),  // drop
+            new ServerEntry("127.0.0.1", 2010, "token0004"),  // Existing address with token changed
+            new ServerEntry("127.0.0.1", 2030, "token0005"),  // New address appearing second time
+            new ServerEntry("token0006"));  // drop
+    verify(subchannelPool, never()).returnSubchannel(same(subchannel1));
+
+    lbResponseObserver.onNext(buildLbResponse(backends2));
+    // not in backends2, closed
+    verify(subchannelPool).returnSubchannel(same(subchannel1));
+    // backends2[2], will be kept
+    verify(subchannelPool, never()).returnSubchannel(same(subchannel2));
+
+    inOrder.verify(subchannelPool, never()).takeOrCreateSubchannel(
+        eq(new EquivalentAddressGroup(backends2.get(2).addr, LB_BACKEND_ATTRS)),
+        any(Attributes.class));
+    inOrder.verify(subchannelPool).takeOrCreateSubchannel(
+        eq(new EquivalentAddressGroup(backends2.get(0).addr, LB_BACKEND_ATTRS)),
+        any(Attributes.class));
+    assertEquals(1, mockSubchannels.size());
+    Subchannel subchannel3 = mockSubchannels.poll();
+    verify(subchannel3).requestConnection();
+    assertEquals(
+        new EquivalentAddressGroup(backends2.get(0).addr, LB_BACKEND_ATTRS),
+        subchannel3.getAddresses());
+    inOrder.verify(helper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
+    RoundRobinPicker picker7 = (RoundRobinPicker) pickerCaptor.getValue();
+    assertThat(picker7.dropList).containsExactly(
+        null,
+        new DropEntry(getLoadRecorder(), "token0003"),
+        null,
+        null,
+        new DropEntry(getLoadRecorder(), "token0006")).inOrder();
+    assertThat(picker7.pickList).containsExactly(BUFFER_ENTRY);
+
+    // State updates on obsolete subchannel1 will have no effect
+    deliverSubchannelState(subchannel1, ConnectivityStateInfo.forNonError(READY));
+    deliverSubchannelState(
+        subchannel1, ConnectivityStateInfo.forTransientFailure(Status.UNAVAILABLE));
+    deliverSubchannelState(subchannel1, ConnectivityStateInfo.forNonError(SHUTDOWN));
+    inOrder.verifyNoMoreInteractions();
+
+    deliverSubchannelState(subchannel3, ConnectivityStateInfo.forNonError(READY));
+    inOrder.verify(helper).updateBalancingState(eq(READY), pickerCaptor.capture());
+    RoundRobinPicker picker8 = (RoundRobinPicker) pickerCaptor.getValue();
+    assertThat(picker8.dropList).containsExactly(
+        null,
+        new DropEntry(getLoadRecorder(), "token0003"),
+        null,
+        null,
+        new DropEntry(getLoadRecorder(), "token0006")).inOrder();
+    // subchannel2 is still IDLE, thus not in the active list
+    assertThat(picker8.pickList).containsExactly(
+        new BackendEntry(subchannel3, getLoadRecorder(), "token0003"),
+        new BackendEntry(subchannel3, getLoadRecorder(), "token0005")).inOrder();
+    // subchannel2 becomes READY and makes it into the list
+    deliverSubchannelState(subchannel2, ConnectivityStateInfo.forNonError(READY));
+    inOrder.verify(helper).updateBalancingState(eq(READY), pickerCaptor.capture());
+    RoundRobinPicker picker9 = (RoundRobinPicker) pickerCaptor.getValue();
+    assertThat(picker9.dropList).containsExactly(
+        null,
+        new DropEntry(getLoadRecorder(), "token0003"),
+        null,
+        null,
+        new DropEntry(getLoadRecorder(), "token0006")).inOrder();
+    assertThat(picker9.pickList).containsExactly(
+        new BackendEntry(subchannel3, getLoadRecorder(), "token0003"),
+        new BackendEntry(subchannel2, getLoadRecorder(), "token0004"),
+        new BackendEntry(subchannel3, getLoadRecorder(), "token0005")).inOrder();
+    verify(subchannelPool, never()).returnSubchannel(same(subchannel3));
+
+    // Update backends, with no entry
+    lbResponseObserver.onNext(buildLbResponse(Collections.<ServerEntry>emptyList()));
+    verify(subchannelPool).returnSubchannel(same(subchannel2));
+    verify(subchannelPool).returnSubchannel(same(subchannel3));
+    inOrder.verify(helper).updateBalancingState(eq(CONNECTING), pickerCaptor.capture());
+    RoundRobinPicker picker10 = (RoundRobinPicker) pickerCaptor.getValue();
+    assertThat(picker10.dropList).isEmpty();
+    assertThat(picker10.pickList).containsExactly(BUFFER_ENTRY);
+
+    assertFalse(oobChannel.isShutdown());
+    assertEquals(0, lbRequestObservers.size());
+    verify(lbRequestObserver, never()).onCompleted();
+    verify(lbRequestObserver, never()).onError(any(Throwable.class));
+
+    // Load reporting was not requested, thus never scheduled
+    assertEquals(0, fakeClock.numPendingTasks(LOAD_REPORTING_TASK_FILTER));
+
+    verify(subchannelPool, never()).clear();
+    balancer.shutdown();
+    verify(subchannelPool).clear();
+  }
+
+  @Test
+  public void grpclbFallback_initialTimeout_serverListReceivedBeforeTimerExpires() {
+    subtestGrpclbFallbackInitialTimeout(false);
+  }
+
+  @Test
+  public void grpclbFallback_initialTimeout_timerExpires() {
+    subtestGrpclbFallbackInitialTimeout(true);
+  }
+
+  // Fallback or not within the period of the initial timeout.
+  private void subtestGrpclbFallbackInitialTimeout(boolean timerExpires) {
+    long loadReportIntervalMillis = 1983;
+    InOrder inOrder = inOrder(helper, subchannelPool);
+
+    // Create a resolution list with a mixture of balancer and backend addresses
+    List<EquivalentAddressGroup> resolutionList =
+        createResolvedServerAddresses(false, true, false);
+    Attributes resolutionAttrs = Attributes.EMPTY;
+    deliverResolvedAddresses(resolutionList, resolutionAttrs);
+
+    inOrder.verify(helper).createOobChannel(eq(resolutionList.get(1)), eq(lbAuthority(0)));
+
+    // Attempted to connect to balancer
+    assertEquals(1, fakeOobChannels.size());
+    ManagedChannel oobChannel = fakeOobChannels.poll();
+    verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+    StreamObserver<LoadBalanceResponse> lbResponseObserver = lbResponseObserverCaptor.getValue();
+    assertEquals(1, lbRequestObservers.size());
+    StreamObserver<LoadBalanceRequest> lbRequestObserver = lbRequestObservers.poll();
+
+    verify(lbRequestObserver).onNext(
+        eq(LoadBalanceRequest.newBuilder().setInitialRequest(
+                InitialLoadBalanceRequest.newBuilder().setName(SERVICE_AUTHORITY).build())
+            .build()));
+    lbResponseObserver.onNext(buildInitialResponse(loadReportIntervalMillis));
+    // We don't care if runSerialized() has been run.
+    inOrder.verify(helper, atLeast(0)).runSerialized(any(Runnable.class));
+    inOrder.verifyNoMoreInteractions();
+
+    assertEquals(1, fakeClock.numPendingTasks(FALLBACK_MODE_TASK_FILTER));
+    fakeClock.forwardTime(GrpclbState.FALLBACK_TIMEOUT_MS - 1, TimeUnit.MILLISECONDS);
+    assertEquals(1, fakeClock.numPendingTasks(FALLBACK_MODE_TASK_FILTER));
+
+    /////////////////////////////////////////////
+    // Break the LB stream before timer expires
+    /////////////////////////////////////////////
+    Status streamError = Status.UNAVAILABLE.withDescription("OOB stream broken");
+    lbResponseObserver.onError(streamError.asException());
+    // Not in fallback mode. The error will be propagated.
+    verify(helper).updateBalancingState(eq(TRANSIENT_FAILURE), pickerCaptor.capture());
+    RoundRobinPicker picker = (RoundRobinPicker) pickerCaptor.getValue();
+    assertThat(picker.dropList).isEmpty();
+    ErrorEntry errorEntry = (ErrorEntry) Iterables.getOnlyElement(picker.pickList);
+    Status status = errorEntry.result.getStatus();
+    assertThat(status.getCode()).isEqualTo(streamError.getCode());
+    assertThat(status.getDescription()).contains(streamError.getDescription());
+    // A new stream is created
+    verify(mockLbService, times(2)).balanceLoad(lbResponseObserverCaptor.capture());
+    lbResponseObserver = lbResponseObserverCaptor.getValue();
+    assertEquals(1, lbRequestObservers.size());
+    lbRequestObserver = lbRequestObservers.poll();
+    verify(lbRequestObserver).onNext(
+        eq(LoadBalanceRequest.newBuilder().setInitialRequest(
+                InitialLoadBalanceRequest.newBuilder().setName(SERVICE_AUTHORITY).build())
+            .build()));
+
+    //////////////////////////////////
+    // Fallback timer expires (or not)
+    //////////////////////////////////
+    if (timerExpires) {
+      fakeClock.forwardTime(1, TimeUnit.MILLISECONDS);
+      assertEquals(0, fakeClock.numPendingTasks(FALLBACK_MODE_TASK_FILTER));
+      // Fall back to the backends from resolver
+      fallbackTestVerifyUseOfFallbackBackendLists(
+          inOrder, Arrays.asList(resolutionList.get(0), resolutionList.get(2)));
+
+      assertFalse(oobChannel.isShutdown());
+      verify(lbRequestObserver, never()).onCompleted();
+    }
+
+    ////////////////////////////////////////////////////////
+    // Name resolver sends new list without any backend addr
+    ////////////////////////////////////////////////////////
+    resolutionList = createResolvedServerAddresses(true, true);
+    deliverResolvedAddresses(resolutionList, resolutionAttrs);
+
+    // New addresses are updated to the OobChannel
+    inOrder.verify(helper).updateOobChannelAddresses(
+        same(oobChannel),
+        eq(new EquivalentAddressGroup(
+                Arrays.asList(
+                    resolutionList.get(0).getAddresses().get(0),
+                    resolutionList.get(1).getAddresses().get(0)),
+                lbAttributes(lbAuthority(0)))));
+
+    if (timerExpires) {
+      // Still in fallback logic, except that the backend list is empty
+      fallbackTestVerifyUseOfFallbackBackendLists(
+          inOrder, Collections.<EquivalentAddressGroup>emptyList());
+    }
+
+    //////////////////////////////////////////////////
+    // Name resolver sends new list with backend addrs
+    //////////////////////////////////////////////////
+    resolutionList = createResolvedServerAddresses(true, false, false);
+    deliverResolvedAddresses(resolutionList, resolutionAttrs);
+
+    // New LB address is updated to the OobChannel
+    inOrder.verify(helper).updateOobChannelAddresses(
+        same(oobChannel),
+        eq(resolutionList.get(0)));
+
+    if (timerExpires) {
+      // New backend addresses are used for fallback
+      fallbackTestVerifyUseOfFallbackBackendLists(
+          inOrder, Arrays.asList(resolutionList.get(1), resolutionList.get(2)));
+    }
+
+    ////////////////////////////////////////////////
+    // Break the LB stream after the timer expires
+    ////////////////////////////////////////////////
+    if (timerExpires) {
+      lbResponseObserver.onError(streamError.asException());
+
+      // The error will NOT propagate to picker because fallback list is in use.
+      inOrder.verify(helper, never())
+          .updateBalancingState(any(ConnectivityState.class), any(SubchannelPicker.class));
+      // A new stream is created
+      verify(mockLbService, times(3)).balanceLoad(lbResponseObserverCaptor.capture());
+      lbResponseObserver = lbResponseObserverCaptor.getValue();
+      assertEquals(1, lbRequestObservers.size());
+      lbRequestObserver = lbRequestObservers.poll();
+      verify(lbRequestObserver).onNext(
+          eq(LoadBalanceRequest.newBuilder().setInitialRequest(
+                  InitialLoadBalanceRequest.newBuilder().setName(SERVICE_AUTHORITY).build())
+              .build()));
+    }
+
+    /////////////////////////////////
+    // Balancer returns a server list
+    /////////////////////////////////
+    List<ServerEntry> serverList = Arrays.asList(
+        new ServerEntry("127.0.0.1", 2000, "token0001"),
+        new ServerEntry("127.0.0.1", 2010, "token0002"));
+    lbResponseObserver.onNext(buildInitialResponse());
+    lbResponseObserver.onNext(buildLbResponse(serverList));
+
+    // Balancer-provided server list now in effect
+    fallbackTestVerifyUseOfBalancerBackendLists(inOrder, serverList);
+
+    ///////////////////////////////////////////////////////////////
+    // New backend addresses from resolver outside of fallback mode
+    ///////////////////////////////////////////////////////////////
+    resolutionList = createResolvedServerAddresses(true, false);
+    deliverResolvedAddresses(resolutionList, resolutionAttrs);
+    // Will not affect the round robin list at all
+    inOrder.verify(helper, never())
+        .updateBalancingState(any(ConnectivityState.class), any(SubchannelPicker.class));
+
+    // No fallback timeout timer scheduled.
+    assertEquals(0, fakeClock.numPendingTasks(FALLBACK_MODE_TASK_FILTER));
+  }
+
+  @Test
+  public void grpclbFallback_balancerLost() {
+    subtestGrpclbFallbackConnectionLost(true, false);
+  }
+
+  @Test
+  public void grpclbFallback_subchannelsLost() {
+    subtestGrpclbFallbackConnectionLost(false, true);
+  }
+
+  @Test
+  public void grpclbFallback_allLost() {
+    subtestGrpclbFallbackConnectionLost(true, true);
+  }
+
+  // Fallback outside of the initial timeout, where all connections are lost.
+  private void subtestGrpclbFallbackConnectionLost(
+      boolean balancerBroken, boolean allSubchannelsBroken) {
+    long loadReportIntervalMillis = 1983;
+    InOrder inOrder = inOrder(helper, mockLbService, subchannelPool);
+
+    // Create a resolution list with a mixture of balancer and backend addresses
+    List<EquivalentAddressGroup> resolutionList =
+        createResolvedServerAddresses(false, true, false);
+    Attributes resolutionAttrs = Attributes.EMPTY;
+    deliverResolvedAddresses(resolutionList, resolutionAttrs);
+
+    inOrder.verify(helper).createOobChannel(eq(resolutionList.get(1)), eq(lbAuthority(0)));
+
+    // Attempted to connect to balancer
+    assertEquals(1, fakeOobChannels.size());
+    fakeOobChannels.poll();
+    inOrder.verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+    StreamObserver<LoadBalanceResponse> lbResponseObserver = lbResponseObserverCaptor.getValue();
+    assertEquals(1, lbRequestObservers.size());
+    StreamObserver<LoadBalanceRequest> lbRequestObserver = lbRequestObservers.poll();
+
+    verify(lbRequestObserver).onNext(
+        eq(LoadBalanceRequest.newBuilder().setInitialRequest(
+                InitialLoadBalanceRequest.newBuilder().setName(SERVICE_AUTHORITY).build())
+            .build()));
+    lbResponseObserver.onNext(buildInitialResponse(loadReportIntervalMillis));
+    // We don't care if runSerialized() has been run.
+    inOrder.verify(helper, atLeast(0)).runSerialized(any(Runnable.class));
+    inOrder.verifyNoMoreInteractions();
+
+    // Balancer returns a server list
+    List<ServerEntry> serverList = Arrays.asList(
+        new ServerEntry("127.0.0.1", 2000, "token0001"),
+        new ServerEntry("127.0.0.1", 2010, "token0002"));
+    lbResponseObserver.onNext(buildInitialResponse());
+    lbResponseObserver.onNext(buildLbResponse(serverList));
+
+    List<Subchannel> subchannels = fallbackTestVerifyUseOfBalancerBackendLists(inOrder, serverList);
+
+    // Break connections
+    if (balancerBroken) {
+      lbResponseObserver.onError(Status.UNAVAILABLE.asException());
+      // A new stream to LB is created
+      inOrder.verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+      lbResponseObserver = lbResponseObserverCaptor.getValue();
+      assertEquals(1, lbRequestObservers.size());
+      lbRequestObserver = lbRequestObservers.poll();
+    }
+    if (allSubchannelsBroken) {
+      for (Subchannel subchannel : subchannels) {
+        // A READY subchannel transits to IDLE when receiving a go-away
+        deliverSubchannelState(subchannel, ConnectivityStateInfo.forNonError(IDLE));
+      }
+    }
+
+    if (balancerBroken && allSubchannelsBroken) {
+      // Going into fallback
+      subchannels = fallbackTestVerifyUseOfFallbackBackendLists(
+          inOrder, Arrays.asList(resolutionList.get(0), resolutionList.get(2)));
+
+      // When in fallback mode, fallback timer should not be scheduled when all backend
+      // connections are lost
+      for (Subchannel subchannel : subchannels) {
+        deliverSubchannelState(subchannel, ConnectivityStateInfo.forNonError(IDLE));
+      }
+
+      // Exit fallback mode or cancel fallback timer when receiving a new server list from balancer
+      List<ServerEntry> serverList2 = Arrays.asList(
+          new ServerEntry("127.0.0.1", 2001, "token0003"),
+          new ServerEntry("127.0.0.1", 2011, "token0004"));
+      lbResponseObserver.onNext(buildInitialResponse());
+      lbResponseObserver.onNext(buildLbResponse(serverList2));
+
+      fallbackTestVerifyUseOfBalancerBackendLists(inOrder, serverList2);
+    }
+    assertEquals(0, fakeClock.numPendingTasks(FALLBACK_MODE_TASK_FILTER));
+
+    if (!(balancerBroken && allSubchannelsBroken)) {
+      verify(subchannelPool, never()).takeOrCreateSubchannel(
+          eq(resolutionList.get(0)), any(Attributes.class));
+      verify(subchannelPool, never()).takeOrCreateSubchannel(
+          eq(resolutionList.get(2)), any(Attributes.class));
+    }
+  }
+
+  private List<Subchannel> fallbackTestVerifyUseOfFallbackBackendLists(
+      InOrder inOrder, List<EquivalentAddressGroup> addrs) {
+    return fallbackTestVerifyUseOfBackendLists(inOrder, addrs, null);
+  }
+
+  private List<Subchannel> fallbackTestVerifyUseOfBalancerBackendLists(
+      InOrder inOrder, List<ServerEntry> servers) {
+    ArrayList<EquivalentAddressGroup> addrs = new ArrayList<>();
+    ArrayList<String> tokens = new ArrayList<>();
+    for (ServerEntry server : servers) {
+      addrs.add(new EquivalentAddressGroup(server.addr, LB_BACKEND_ATTRS));
+      tokens.add(server.token);
+    }
+    return fallbackTestVerifyUseOfBackendLists(inOrder, addrs, tokens);
+  }
+
+  private List<Subchannel> fallbackTestVerifyUseOfBackendLists(
+      InOrder inOrder, List<EquivalentAddressGroup> addrs,
+      @Nullable List<String> tokens) {
+    if (tokens != null) {
+      assertEquals(addrs.size(), tokens.size());
+    }
+    for (EquivalentAddressGroup addr : addrs) {
+      inOrder.verify(subchannelPool).takeOrCreateSubchannel(eq(addr), any(Attributes.class));
+    }
+    RoundRobinPicker picker = (RoundRobinPicker) currentPicker;
+    assertThat(picker.dropList).containsExactlyElementsIn(Collections.nCopies(addrs.size(), null));
+    assertThat(picker.pickList).containsExactly(GrpclbState.BUFFER_ENTRY);
+    assertEquals(addrs.size(), mockSubchannels.size());
+    ArrayList<Subchannel> subchannels = new ArrayList<>(mockSubchannels);
+    mockSubchannels.clear();
+    for (Subchannel subchannel : subchannels) {
+      deliverSubchannelState(subchannel, ConnectivityStateInfo.forNonError(CONNECTING));
+    }
+    inOrder.verify(helper, atLeast(0))
+        .updateBalancingState(eq(CONNECTING), any(SubchannelPicker.class));
+    inOrder.verify(helper, never())
+        .updateBalancingState(any(ConnectivityState.class), any(SubchannelPicker.class));
+
+    ArrayList<BackendEntry> pickList = new ArrayList<>();
+    for (int i = 0; i < addrs.size(); i++) {
+      Subchannel subchannel = subchannels.get(i);
+      BackendEntry backend;
+      if (tokens == null) {
+        backend = new BackendEntry(subchannel);
+      } else {
+        backend = new BackendEntry(subchannel, getLoadRecorder(), tokens.get(i));
+      }
+      pickList.add(backend);
+      deliverSubchannelState(subchannel, ConnectivityStateInfo.forNonError(READY));
+      inOrder.verify(helper).updateBalancingState(eq(READY), pickerCaptor.capture());
+      picker = (RoundRobinPicker) pickerCaptor.getValue();
+      assertThat(picker.dropList)
+          .containsExactlyElementsIn(Collections.nCopies(addrs.size(), null));
+      assertThat(picker.pickList).containsExactlyElementsIn(pickList);
+      inOrder.verify(helper, never())
+          .updateBalancingState(any(ConnectivityState.class), any(SubchannelPicker.class));
+    }
+    return subchannels;
+  }
+
+  @Test
+  public void grpclbMultipleAuthorities() throws Exception {
+    List<EquivalentAddressGroup> grpclbResolutionList = Arrays.asList(
+        new EquivalentAddressGroup(
+            new FakeSocketAddress("fake-address-1"),
+            lbAttributes("fake-authority-1")),
+        new EquivalentAddressGroup(
+            new FakeSocketAddress("fake-address-2"),
+            lbAttributes("fake-authority-2")),
+        new EquivalentAddressGroup(
+            new FakeSocketAddress("not-a-lb-address")),
+        new EquivalentAddressGroup(
+            new FakeSocketAddress("fake-address-3"),
+            lbAttributes("fake-authority-1")));
+    final EquivalentAddressGroup goldenOobChannelEag = new EquivalentAddressGroup(
+        Arrays.<SocketAddress>asList(
+            new FakeSocketAddress("fake-address-1"),
+            new FakeSocketAddress("fake-address-3")),
+        lbAttributes("fake-authority-1")); // Supporting multiple authorities would be good, one day
+
+    Attributes grpclbResolutionAttrs = Attributes.EMPTY;
+    deliverResolvedAddresses(grpclbResolutionList, grpclbResolutionAttrs);
+
+    verify(helper).createOobChannel(goldenOobChannelEag, "fake-authority-1");
+  }
+
+  @Test
+  public void grpclbBalancerStreamRetry() throws Exception {
+    LoadBalanceRequest expectedInitialRequest =
+        LoadBalanceRequest.newBuilder()
+            .setInitialRequest(
+                InitialLoadBalanceRequest.newBuilder().setName(SERVICE_AUTHORITY).build())
+            .build();
+    InOrder inOrder =
+        inOrder(mockLbService, backoffPolicyProvider, backoffPolicy1, backoffPolicy2);
+    List<EquivalentAddressGroup> grpclbResolutionList = createResolvedServerAddresses(true);
+    Attributes grpclbResolutionAttrs = Attributes.EMPTY;
+    deliverResolvedAddresses(grpclbResolutionList, grpclbResolutionAttrs);
+
+    assertEquals(1, fakeOobChannels.size());
+    @SuppressWarnings("unused")
+    ManagedChannel oobChannel = fakeOobChannels.poll();
+
+    // First balancer RPC
+    inOrder.verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+    StreamObserver<LoadBalanceResponse> lbResponseObserver = lbResponseObserverCaptor.getValue();
+    assertEquals(1, lbRequestObservers.size());
+    StreamObserver<LoadBalanceRequest> lbRequestObserver = lbRequestObservers.poll();
+    verify(lbRequestObserver).onNext(eq(expectedInitialRequest));
+    assertEquals(0, fakeClock.numPendingTasks(LB_RPC_RETRY_TASK_FILTER));
+
+    // Balancer closes it immediately (erroneously)
+    lbResponseObserver.onCompleted();
+    // Will start backoff sequence 1 (10ns)
+    inOrder.verify(backoffPolicyProvider).get();
+    inOrder.verify(backoffPolicy1).nextBackoffNanos();
+    assertEquals(1, fakeClock.numPendingTasks(LB_RPC_RETRY_TASK_FILTER));
+
+    // Fast-forward to a moment before the retry
+    fakeClock.forwardNanos(9);
+    verifyNoMoreInteractions(mockLbService);
+    // Then time for retry
+    fakeClock.forwardNanos(1);
+    inOrder.verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+    lbResponseObserver = lbResponseObserverCaptor.getValue();
+    assertEquals(1, lbRequestObservers.size());
+    lbRequestObserver = lbRequestObservers.poll();
+    verify(lbRequestObserver).onNext(eq(expectedInitialRequest));
+    assertEquals(0, fakeClock.numPendingTasks(LB_RPC_RETRY_TASK_FILTER));
+
+    // Balancer closes it with an error.
+    lbResponseObserver.onError(Status.UNAVAILABLE.asException());
+    // Will continue the backoff sequence 1 (100ns)
+    verifyNoMoreInteractions(backoffPolicyProvider);
+    inOrder.verify(backoffPolicy1).nextBackoffNanos();
+    assertEquals(1, fakeClock.numPendingTasks(LB_RPC_RETRY_TASK_FILTER));
+
+    // Fast-forward to a moment before the retry
+    fakeClock.forwardNanos(100 - 1);
+    verifyNoMoreInteractions(mockLbService);
+    // Then time for retry
+    fakeClock.forwardNanos(1);
+    inOrder.verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+    lbResponseObserver = lbResponseObserverCaptor.getValue();
+    assertEquals(1, lbRequestObservers.size());
+    lbRequestObserver = lbRequestObservers.poll();
+    verify(lbRequestObserver).onNext(eq(expectedInitialRequest));
+    assertEquals(0, fakeClock.numPendingTasks(LB_RPC_RETRY_TASK_FILTER));
+
+    // Balancer sends initial response.
+    lbResponseObserver.onNext(buildInitialResponse());
+
+    // Then breaks the RPC
+    lbResponseObserver.onError(Status.UNAVAILABLE.asException());
+
+    // Will reset the retry sequence and retry immediately, because balancer has responded.
+    inOrder.verify(backoffPolicyProvider).get();
+    inOrder.verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+    lbResponseObserver = lbResponseObserverCaptor.getValue();
+    assertEquals(1, lbRequestObservers.size());
+    lbRequestObserver = lbRequestObservers.poll();
+    verify(lbRequestObserver).onNext(eq(expectedInitialRequest));
+
+    // Fail the retry after spending 4ns
+    fakeClock.forwardNanos(4);
+    lbResponseObserver.onError(Status.UNAVAILABLE.asException());
+    
+    // Will be on the first retry (10ns) of backoff sequence 2.
+    inOrder.verify(backoffPolicy2).nextBackoffNanos();
+    assertEquals(1, fakeClock.numPendingTasks(LB_RPC_RETRY_TASK_FILTER));
+
+    // Fast-forward to a moment before the retry, the time spent in the last try is deducted.
+    fakeClock.forwardNanos(10 - 4 - 1);
+    verifyNoMoreInteractions(mockLbService);
+    // Then time for retry
+    fakeClock.forwardNanos(1);
+    inOrder.verify(mockLbService).balanceLoad(lbResponseObserverCaptor.capture());
+    assertEquals(1, lbRequestObservers.size());
+    lbRequestObserver = lbRequestObservers.poll();
+    verify(lbRequestObserver).onNext(eq(expectedInitialRequest));
+    assertEquals(0, fakeClock.numPendingTasks(LB_RPC_RETRY_TASK_FILTER));
+
+    // Wrapping up
+    verify(backoffPolicyProvider, times(2)).get();
+    verify(backoffPolicy1, times(2)).nextBackoffNanos();
+    verify(backoffPolicy2, times(1)).nextBackoffNanos();
+  }
+
+  private void deliverSubchannelState(
+      final Subchannel subchannel, final ConnectivityStateInfo newState) {
+    channelExecutor.execute(new Runnable() {
+        @Override
+        public void run() {
+          balancer.handleSubchannelState(subchannel, newState);
+        }
+      });
+  }
+
+  private void deliverNameResolutionError(final Status error) {
+    channelExecutor.execute(new Runnable() {
+        @Override
+        public void run() {
+          balancer.handleNameResolutionError(error);
+        }
+      });
+  }
+
+  private void deliverResolvedAddresses(
+      final List<EquivalentAddressGroup> addrs, final Attributes attrs) {
+    channelExecutor.execute(new Runnable() {
+        @Override
+        public void run() {
+          balancer.handleResolvedAddressGroups(addrs, attrs);
+        }
+      });
+  }
+
+  private GrpclbClientLoadRecorder getLoadRecorder() {
+    return balancer.getGrpclbState().getLoadRecorder();
+  }
+
+  private static List<EquivalentAddressGroup> createResolvedServerAddresses(boolean ... isLb) {
+    ArrayList<EquivalentAddressGroup> list = new ArrayList<>();
+    for (int i = 0; i < isLb.length; i++) {
+      SocketAddress addr = new FakeSocketAddress("fake-address-" + i);
+      EquivalentAddressGroup eag =
+          new EquivalentAddressGroup(
+              addr,
+              isLb[i] ? lbAttributes(lbAuthority(i)) : Attributes.EMPTY);
+      list.add(eag);
+    }
+    return list;
+  }
+
+  private static String lbAuthority(int unused) {
+    // TODO(ejona): Support varying authorities
+    return "lb.google.com";
+  }
+
+  private static Attributes lbAttributes(String authority) {
+    return Attributes.newBuilder()
+        .set(GrpcAttributes.ATTR_LB_ADDR_AUTHORITY, authority)
+        .build();
+  }
+
+  private static LoadBalanceResponse buildInitialResponse() {
+    return buildInitialResponse(0);
+  }
+
+  private static LoadBalanceResponse buildInitialResponse(long loadReportIntervalMillis) {
+    return LoadBalanceResponse.newBuilder()
+        .setInitialResponse(
+            InitialLoadBalanceResponse.newBuilder()
+            .setClientStatsReportInterval(Durations.fromMillis(loadReportIntervalMillis)))
+        .build();
+  }
+
+  private static LoadBalanceResponse buildLbResponse(List<ServerEntry> servers) {
+    ServerList.Builder serverListBuilder = ServerList.newBuilder();
+    for (ServerEntry server : servers) {
+      if (server.addr != null) {
+        serverListBuilder.addServers(Server.newBuilder()
+            .setIpAddress(ByteString.copyFrom(server.addr.getAddress().getAddress()))
+            .setPort(server.addr.getPort())
+            .setLoadBalanceToken(server.token)
+            .build());
+      } else {
+        serverListBuilder.addServers(Server.newBuilder()
+            .setDrop(true)
+            .setLoadBalanceToken(server.token)
+            .build());
+      }
+    }
+    return LoadBalanceResponse.newBuilder()
+        .setServerList(serverListBuilder.build())
+        .build();
+  }
+
+  private static class ServerEntry {
+    final InetSocketAddress addr;
+    final String token;
+
+    ServerEntry(String host, int port, String token) {
+      this.addr = new InetSocketAddress(host, port);
+      this.token = token;
+    }
+
+    // Drop entry
+    ServerEntry(String token) {
+      this.addr = null;
+      this.token = token;
+    }
+  }
+}
diff --git a/interop-testing/build.gradle b/interop-testing/build.gradle
new file mode 100644
index 0000000..e0f60ef
--- /dev/null
+++ b/interop-testing/build.gradle
@@ -0,0 +1,108 @@
+apply plugin: 'application'
+
+description = "gRPC: Integration Testing"
+startScripts.enabled = false
+
+// Add dependency on the protobuf plugin
+buildscript {
+    repositories {
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    }
+    dependencies { classpath libraries.protobuf_plugin }
+}
+
+dependencies {
+    compile project(':grpc-alts'),
+            project(':grpc-auth'),
+            project(':grpc-core'),
+            project(':grpc-netty'),
+            project(':grpc-okhttp'),
+            project(':grpc-protobuf'),
+            project(':grpc-stub'),
+            project(':grpc-testing'),
+            libraries.junit,
+            libraries.mockito,
+            libraries.oauth_client
+    compile (libraries.truth) {
+        // Disable because it uses Java 8 bytecode, which breaks gae-java7
+        exclude group: 'com.google.auto.value', module: 'auto-value-annotations'
+        // Disable because it uses Java 8 bytecode, which breaks gae-java7
+        exclude group: 'org.checkerframework', module: 'checker-qual'
+    }
+    compileOnly libraries.javax_annotation
+    runtime libraries.opencensus_impl,
+            libraries.netty_tcnative
+    testCompile project(':grpc-context').sourceSets.test.output
+}
+
+configureProtoCompilation()
+
+compileJava {
+    // This isn't a library; it can use beta APIs
+    it.options.compilerArgs += ["-Xep:BetaApi:OFF"]
+}
+
+test {
+    // For the automated tests, use Jetty ALPN.
+    jvmArgs "-javaagent:" + configurations.alpnagent.asPath
+}
+
+// For the generated scripts, use Netty tcnative (i.e. OpenSSL).
+// Note that OkHttp currently only supports ALPN, so OpenSSL version >= 1.0.2 is required.
+
+task test_client(type: CreateStartScripts) {
+    mainClassName = "io.grpc.testing.integration.TestServiceClient"
+    applicationName = "test-client"
+    defaultJvmOpts = [
+        "-javaagent:JAVAAGENT_APP_HOME" + configurations.alpnagent.singleFile.name
+    ]
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + configurations.runtime
+    dependencies { runtime configurations.alpnagent }
+    doLast {
+        unixScript.text = unixScript.text.replace('JAVAAGENT_APP_HOME', '\$APP_HOME/lib/')
+        windowsScript.text = windowsScript.text.replace('JAVAAGENT_APP_HOME', '%APP_HOME%\\lib\\')
+    }
+}
+
+task test_server(type: CreateStartScripts) {
+    mainClassName = "io.grpc.testing.integration.TestServiceServer"
+    applicationName = "test-server"
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + configurations.runtime
+}
+
+task reconnect_test_client(type: CreateStartScripts) {
+    mainClassName = "io.grpc.testing.integration.ReconnectTestClient"
+    applicationName = "reconnect-test-client"
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + configurations.runtime
+}
+
+task stresstest_client(type: CreateStartScripts) {
+    mainClassName = "io.grpc.testing.integration.StressTestClient"
+    applicationName = "stresstest-client"
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + configurations.runtime
+    defaultJvmOpts = [
+        "-verbose:gc",
+        "-XX:+PrintFlagsFinal"
+    ]
+}
+
+task http2_client(type: CreateStartScripts) {
+    mainClassName = "io.grpc.testing.integration.Http2Client"
+    applicationName = "http2-client"
+    outputDir = new File(project.buildDir, 'tmp')
+    classpath = jar.outputs.files + configurations.runtime
+}
+
+applicationDistribution.into("bin") {
+    from(test_client)
+    from(test_server)
+    from(reconnect_test_client)
+    from(stresstest_client)
+    from(http2_client)
+    fileMode = 0755
+}
diff --git a/interop-testing/src/generated/main/grpc/io/grpc/testing/integration/MetricsServiceGrpc.java b/interop-testing/src/generated/main/grpc/io/grpc/testing/integration/MetricsServiceGrpc.java
new file mode 100644
index 0000000..124d31e
--- /dev/null
+++ b/interop-testing/src/generated/main/grpc/io/grpc/testing/integration/MetricsServiceGrpc.java
@@ -0,0 +1,372 @@
+package io.grpc.testing.integration;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: grpc/testing/metrics.proto")
+public final class MetricsServiceGrpc {
+
+  private MetricsServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.MetricsService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.integration.Metrics.EmptyMessage,
+      io.grpc.testing.integration.Metrics.GaugeResponse> getGetAllGaugesMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "GetAllGauges",
+      requestType = io.grpc.testing.integration.Metrics.EmptyMessage.class,
+      responseType = io.grpc.testing.integration.Metrics.GaugeResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.integration.Metrics.EmptyMessage,
+      io.grpc.testing.integration.Metrics.GaugeResponse> getGetAllGaugesMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.integration.Metrics.EmptyMessage, io.grpc.testing.integration.Metrics.GaugeResponse> getGetAllGaugesMethod;
+    if ((getGetAllGaugesMethod = MetricsServiceGrpc.getGetAllGaugesMethod) == null) {
+      synchronized (MetricsServiceGrpc.class) {
+        if ((getGetAllGaugesMethod = MetricsServiceGrpc.getGetAllGaugesMethod) == null) {
+          MetricsServiceGrpc.getGetAllGaugesMethod = getGetAllGaugesMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.integration.Metrics.EmptyMessage, io.grpc.testing.integration.Metrics.GaugeResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.MetricsService", "GetAllGauges"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Metrics.EmptyMessage.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Metrics.GaugeResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new MetricsServiceMethodDescriptorSupplier("GetAllGauges"))
+                  .build();
+          }
+        }
+     }
+     return getGetAllGaugesMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.integration.Metrics.GaugeRequest,
+      io.grpc.testing.integration.Metrics.GaugeResponse> getGetGaugeMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "GetGauge",
+      requestType = io.grpc.testing.integration.Metrics.GaugeRequest.class,
+      responseType = io.grpc.testing.integration.Metrics.GaugeResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.integration.Metrics.GaugeRequest,
+      io.grpc.testing.integration.Metrics.GaugeResponse> getGetGaugeMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.integration.Metrics.GaugeRequest, io.grpc.testing.integration.Metrics.GaugeResponse> getGetGaugeMethod;
+    if ((getGetGaugeMethod = MetricsServiceGrpc.getGetGaugeMethod) == null) {
+      synchronized (MetricsServiceGrpc.class) {
+        if ((getGetGaugeMethod = MetricsServiceGrpc.getGetGaugeMethod) == null) {
+          MetricsServiceGrpc.getGetGaugeMethod = getGetGaugeMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.integration.Metrics.GaugeRequest, io.grpc.testing.integration.Metrics.GaugeResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.MetricsService", "GetGauge"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Metrics.GaugeRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Metrics.GaugeResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new MetricsServiceMethodDescriptorSupplier("GetGauge"))
+                  .build();
+          }
+        }
+     }
+     return getGetGaugeMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static MetricsServiceStub newStub(io.grpc.Channel channel) {
+    return new MetricsServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static MetricsServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new MetricsServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static MetricsServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new MetricsServiceFutureStub(channel);
+  }
+
+  /**
+   */
+  public static abstract class MetricsServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * Returns the values of all the gauges that are currently being maintained by
+     * the service
+     * </pre>
+     */
+    public void getAllGauges(io.grpc.testing.integration.Metrics.EmptyMessage request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Metrics.GaugeResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getGetAllGaugesMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Returns the value of one gauge
+     * </pre>
+     */
+    public void getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Metrics.GaugeResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getGetGaugeMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getGetAllGaugesMethod(),
+            asyncServerStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.Metrics.EmptyMessage,
+                io.grpc.testing.integration.Metrics.GaugeResponse>(
+                  this, METHODID_GET_ALL_GAUGES)))
+          .addMethod(
+            getGetGaugeMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.Metrics.GaugeRequest,
+                io.grpc.testing.integration.Metrics.GaugeResponse>(
+                  this, METHODID_GET_GAUGE)))
+          .build();
+    }
+  }
+
+  /**
+   */
+  public static final class MetricsServiceStub extends io.grpc.stub.AbstractStub<MetricsServiceStub> {
+    private MetricsServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private MetricsServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected MetricsServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new MetricsServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Returns the values of all the gauges that are currently being maintained by
+     * the service
+     * </pre>
+     */
+    public void getAllGauges(io.grpc.testing.integration.Metrics.EmptyMessage request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Metrics.GaugeResponse> responseObserver) {
+      asyncServerStreamingCall(
+          getChannel().newCall(getGetAllGaugesMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Returns the value of one gauge
+     * </pre>
+     */
+    public void getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Metrics.GaugeResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getGetGaugeMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   */
+  public static final class MetricsServiceBlockingStub extends io.grpc.stub.AbstractStub<MetricsServiceBlockingStub> {
+    private MetricsServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private MetricsServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected MetricsServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new MetricsServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Returns the values of all the gauges that are currently being maintained by
+     * the service
+     * </pre>
+     */
+    public java.util.Iterator<io.grpc.testing.integration.Metrics.GaugeResponse> getAllGauges(
+        io.grpc.testing.integration.Metrics.EmptyMessage request) {
+      return blockingServerStreamingCall(
+          getChannel(), getGetAllGaugesMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * Returns the value of one gauge
+     * </pre>
+     */
+    public io.grpc.testing.integration.Metrics.GaugeResponse getGauge(io.grpc.testing.integration.Metrics.GaugeRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getGetGaugeMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   */
+  public static final class MetricsServiceFutureStub extends io.grpc.stub.AbstractStub<MetricsServiceFutureStub> {
+    private MetricsServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private MetricsServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected MetricsServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new MetricsServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Returns the value of one gauge
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.integration.Metrics.GaugeResponse> getGauge(
+        io.grpc.testing.integration.Metrics.GaugeRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getGetGaugeMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_GET_ALL_GAUGES = 0;
+  private static final int METHODID_GET_GAUGE = 1;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final MetricsServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(MetricsServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_GET_ALL_GAUGES:
+          serviceImpl.getAllGauges((io.grpc.testing.integration.Metrics.EmptyMessage) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.Metrics.GaugeResponse>) responseObserver);
+          break;
+        case METHODID_GET_GAUGE:
+          serviceImpl.getGauge((io.grpc.testing.integration.Metrics.GaugeRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.Metrics.GaugeResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class MetricsServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    MetricsServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.testing.integration.Metrics.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("MetricsService");
+    }
+  }
+
+  private static final class MetricsServiceFileDescriptorSupplier
+      extends MetricsServiceBaseDescriptorSupplier {
+    MetricsServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class MetricsServiceMethodDescriptorSupplier
+      extends MetricsServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    MetricsServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (MetricsServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new MetricsServiceFileDescriptorSupplier())
+              .addMethod(getGetAllGaugesMethod())
+              .addMethod(getGetGaugeMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/interop-testing/src/generated/main/grpc/io/grpc/testing/integration/ReconnectServiceGrpc.java b/interop-testing/src/generated/main/grpc/io/grpc/testing/integration/ReconnectServiceGrpc.java
new file mode 100644
index 0000000..595aa2a
--- /dev/null
+++ b/interop-testing/src/generated/main/grpc/io/grpc/testing/integration/ReconnectServiceGrpc.java
@@ -0,0 +1,370 @@
+package io.grpc.testing.integration;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ * <pre>
+ * A service used to control reconnect server.
+ * </pre>
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: grpc/testing/test.proto")
+public final class ReconnectServiceGrpc {
+
+  private ReconnectServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.ReconnectService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty,
+      io.grpc.testing.integration.EmptyProtos.Empty> getStartMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "Start",
+      requestType = io.grpc.testing.integration.EmptyProtos.Empty.class,
+      responseType = io.grpc.testing.integration.EmptyProtos.Empty.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty,
+      io.grpc.testing.integration.EmptyProtos.Empty> getStartMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty, io.grpc.testing.integration.EmptyProtos.Empty> getStartMethod;
+    if ((getStartMethod = ReconnectServiceGrpc.getStartMethod) == null) {
+      synchronized (ReconnectServiceGrpc.class) {
+        if ((getStartMethod = ReconnectServiceGrpc.getStartMethod) == null) {
+          ReconnectServiceGrpc.getStartMethod = getStartMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.integration.EmptyProtos.Empty, io.grpc.testing.integration.EmptyProtos.Empty>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.ReconnectService", "Start"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.EmptyProtos.Empty.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.EmptyProtos.Empty.getDefaultInstance()))
+                  .setSchemaDescriptor(new ReconnectServiceMethodDescriptorSupplier("Start"))
+                  .build();
+          }
+        }
+     }
+     return getStartMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty,
+      io.grpc.testing.integration.Messages.ReconnectInfo> getStopMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "Stop",
+      requestType = io.grpc.testing.integration.EmptyProtos.Empty.class,
+      responseType = io.grpc.testing.integration.Messages.ReconnectInfo.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty,
+      io.grpc.testing.integration.Messages.ReconnectInfo> getStopMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty, io.grpc.testing.integration.Messages.ReconnectInfo> getStopMethod;
+    if ((getStopMethod = ReconnectServiceGrpc.getStopMethod) == null) {
+      synchronized (ReconnectServiceGrpc.class) {
+        if ((getStopMethod = ReconnectServiceGrpc.getStopMethod) == null) {
+          ReconnectServiceGrpc.getStopMethod = getStopMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.integration.EmptyProtos.Empty, io.grpc.testing.integration.Messages.ReconnectInfo>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.ReconnectService", "Stop"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.EmptyProtos.Empty.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Messages.ReconnectInfo.getDefaultInstance()))
+                  .setSchemaDescriptor(new ReconnectServiceMethodDescriptorSupplier("Stop"))
+                  .build();
+          }
+        }
+     }
+     return getStopMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static ReconnectServiceStub newStub(io.grpc.Channel channel) {
+    return new ReconnectServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static ReconnectServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new ReconnectServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static ReconnectServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new ReconnectServiceFutureStub(channel);
+  }
+
+  /**
+   * <pre>
+   * A service used to control reconnect server.
+   * </pre>
+   */
+  public static abstract class ReconnectServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     */
+    public void start(io.grpc.testing.integration.EmptyProtos.Empty request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.EmptyProtos.Empty> responseObserver) {
+      asyncUnimplementedUnaryCall(getStartMethod(), responseObserver);
+    }
+
+    /**
+     */
+    public void stop(io.grpc.testing.integration.EmptyProtos.Empty request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.ReconnectInfo> responseObserver) {
+      asyncUnimplementedUnaryCall(getStopMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getStartMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.EmptyProtos.Empty,
+                io.grpc.testing.integration.EmptyProtos.Empty>(
+                  this, METHODID_START)))
+          .addMethod(
+            getStopMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.EmptyProtos.Empty,
+                io.grpc.testing.integration.Messages.ReconnectInfo>(
+                  this, METHODID_STOP)))
+          .build();
+    }
+  }
+
+  /**
+   * <pre>
+   * A service used to control reconnect server.
+   * </pre>
+   */
+  public static final class ReconnectServiceStub extends io.grpc.stub.AbstractStub<ReconnectServiceStub> {
+    private ReconnectServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ReconnectServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ReconnectServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ReconnectServiceStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public void start(io.grpc.testing.integration.EmptyProtos.Empty request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.EmptyProtos.Empty> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getStartMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     */
+    public void stop(io.grpc.testing.integration.EmptyProtos.Empty request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.ReconnectInfo> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getStopMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   * <pre>
+   * A service used to control reconnect server.
+   * </pre>
+   */
+  public static final class ReconnectServiceBlockingStub extends io.grpc.stub.AbstractStub<ReconnectServiceBlockingStub> {
+    private ReconnectServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ReconnectServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ReconnectServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ReconnectServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public io.grpc.testing.integration.EmptyProtos.Empty start(io.grpc.testing.integration.EmptyProtos.Empty request) {
+      return blockingUnaryCall(
+          getChannel(), getStartMethod(), getCallOptions(), request);
+    }
+
+    /**
+     */
+    public io.grpc.testing.integration.Messages.ReconnectInfo stop(io.grpc.testing.integration.EmptyProtos.Empty request) {
+      return blockingUnaryCall(
+          getChannel(), getStopMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   * <pre>
+   * A service used to control reconnect server.
+   * </pre>
+   */
+  public static final class ReconnectServiceFutureStub extends io.grpc.stub.AbstractStub<ReconnectServiceFutureStub> {
+    private ReconnectServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ReconnectServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ReconnectServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ReconnectServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.integration.EmptyProtos.Empty> start(
+        io.grpc.testing.integration.EmptyProtos.Empty request) {
+      return futureUnaryCall(
+          getChannel().newCall(getStartMethod(), getCallOptions()), request);
+    }
+
+    /**
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.integration.Messages.ReconnectInfo> stop(
+        io.grpc.testing.integration.EmptyProtos.Empty request) {
+      return futureUnaryCall(
+          getChannel().newCall(getStopMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_START = 0;
+  private static final int METHODID_STOP = 1;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final ReconnectServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(ReconnectServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_START:
+          serviceImpl.start((io.grpc.testing.integration.EmptyProtos.Empty) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.EmptyProtos.Empty>) responseObserver);
+          break;
+        case METHODID_STOP:
+          serviceImpl.stop((io.grpc.testing.integration.EmptyProtos.Empty) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.ReconnectInfo>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class ReconnectServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    ReconnectServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.testing.integration.Test.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("ReconnectService");
+    }
+  }
+
+  private static final class ReconnectServiceFileDescriptorSupplier
+      extends ReconnectServiceBaseDescriptorSupplier {
+    ReconnectServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class ReconnectServiceMethodDescriptorSupplier
+      extends ReconnectServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    ReconnectServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (ReconnectServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new ReconnectServiceFileDescriptorSupplier())
+              .addMethod(getStartMethod())
+              .addMethod(getStopMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/interop-testing/src/generated/main/grpc/io/grpc/testing/integration/TestServiceGrpc.java b/interop-testing/src/generated/main/grpc/io/grpc/testing/integration/TestServiceGrpc.java
new file mode 100644
index 0000000..727afa0
--- /dev/null
+++ b/interop-testing/src/generated/main/grpc/io/grpc/testing/integration/TestServiceGrpc.java
@@ -0,0 +1,872 @@
+package io.grpc.testing.integration;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ * <pre>
+ * A simple service to test the various types of RPCs and experiment with
+ * performance with various types of payload.
+ * </pre>
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: grpc/testing/test.proto")
+public final class TestServiceGrpc {
+
+  private TestServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.TestService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty,
+      io.grpc.testing.integration.EmptyProtos.Empty> getEmptyCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "EmptyCall",
+      requestType = io.grpc.testing.integration.EmptyProtos.Empty.class,
+      responseType = io.grpc.testing.integration.EmptyProtos.Empty.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty,
+      io.grpc.testing.integration.EmptyProtos.Empty> getEmptyCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty, io.grpc.testing.integration.EmptyProtos.Empty> getEmptyCallMethod;
+    if ((getEmptyCallMethod = TestServiceGrpc.getEmptyCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getEmptyCallMethod = TestServiceGrpc.getEmptyCallMethod) == null) {
+          TestServiceGrpc.getEmptyCallMethod = getEmptyCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.integration.EmptyProtos.Empty, io.grpc.testing.integration.EmptyProtos.Empty>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.TestService", "EmptyCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.EmptyProtos.Empty.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.EmptyProtos.Empty.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("EmptyCall"))
+                  .build();
+          }
+        }
+     }
+     return getEmptyCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.SimpleRequest,
+      io.grpc.testing.integration.Messages.SimpleResponse> getUnaryCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "UnaryCall",
+      requestType = io.grpc.testing.integration.Messages.SimpleRequest.class,
+      responseType = io.grpc.testing.integration.Messages.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.SimpleRequest,
+      io.grpc.testing.integration.Messages.SimpleResponse> getUnaryCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.SimpleRequest, io.grpc.testing.integration.Messages.SimpleResponse> getUnaryCallMethod;
+    if ((getUnaryCallMethod = TestServiceGrpc.getUnaryCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getUnaryCallMethod = TestServiceGrpc.getUnaryCallMethod) == null) {
+          TestServiceGrpc.getUnaryCallMethod = getUnaryCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.integration.Messages.SimpleRequest, io.grpc.testing.integration.Messages.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.TestService", "UnaryCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Messages.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Messages.SimpleResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("UnaryCall"))
+                  .build();
+          }
+        }
+     }
+     return getUnaryCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.SimpleRequest,
+      io.grpc.testing.integration.Messages.SimpleResponse> getCacheableUnaryCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "CacheableUnaryCall",
+      requestType = io.grpc.testing.integration.Messages.SimpleRequest.class,
+      responseType = io.grpc.testing.integration.Messages.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.SimpleRequest,
+      io.grpc.testing.integration.Messages.SimpleResponse> getCacheableUnaryCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.SimpleRequest, io.grpc.testing.integration.Messages.SimpleResponse> getCacheableUnaryCallMethod;
+    if ((getCacheableUnaryCallMethod = TestServiceGrpc.getCacheableUnaryCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getCacheableUnaryCallMethod = TestServiceGrpc.getCacheableUnaryCallMethod) == null) {
+          TestServiceGrpc.getCacheableUnaryCallMethod = getCacheableUnaryCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.integration.Messages.SimpleRequest, io.grpc.testing.integration.Messages.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.TestService", "CacheableUnaryCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Messages.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Messages.SimpleResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("CacheableUnaryCall"))
+                  .build();
+          }
+        }
+     }
+     return getCacheableUnaryCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.StreamingOutputCallRequest,
+      io.grpc.testing.integration.Messages.StreamingOutputCallResponse> getStreamingOutputCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "StreamingOutputCall",
+      requestType = io.grpc.testing.integration.Messages.StreamingOutputCallRequest.class,
+      responseType = io.grpc.testing.integration.Messages.StreamingOutputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.StreamingOutputCallRequest,
+      io.grpc.testing.integration.Messages.StreamingOutputCallResponse> getStreamingOutputCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.StreamingOutputCallRequest, io.grpc.testing.integration.Messages.StreamingOutputCallResponse> getStreamingOutputCallMethod;
+    if ((getStreamingOutputCallMethod = TestServiceGrpc.getStreamingOutputCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getStreamingOutputCallMethod = TestServiceGrpc.getStreamingOutputCallMethod) == null) {
+          TestServiceGrpc.getStreamingOutputCallMethod = getStreamingOutputCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.integration.Messages.StreamingOutputCallRequest, io.grpc.testing.integration.Messages.StreamingOutputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.TestService", "StreamingOutputCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Messages.StreamingOutputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Messages.StreamingOutputCallResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("StreamingOutputCall"))
+                  .build();
+          }
+        }
+     }
+     return getStreamingOutputCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.StreamingInputCallRequest,
+      io.grpc.testing.integration.Messages.StreamingInputCallResponse> getStreamingInputCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "StreamingInputCall",
+      requestType = io.grpc.testing.integration.Messages.StreamingInputCallRequest.class,
+      responseType = io.grpc.testing.integration.Messages.StreamingInputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.StreamingInputCallRequest,
+      io.grpc.testing.integration.Messages.StreamingInputCallResponse> getStreamingInputCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.StreamingInputCallRequest, io.grpc.testing.integration.Messages.StreamingInputCallResponse> getStreamingInputCallMethod;
+    if ((getStreamingInputCallMethod = TestServiceGrpc.getStreamingInputCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getStreamingInputCallMethod = TestServiceGrpc.getStreamingInputCallMethod) == null) {
+          TestServiceGrpc.getStreamingInputCallMethod = getStreamingInputCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.integration.Messages.StreamingInputCallRequest, io.grpc.testing.integration.Messages.StreamingInputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.TestService", "StreamingInputCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Messages.StreamingInputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Messages.StreamingInputCallResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("StreamingInputCall"))
+                  .build();
+          }
+        }
+     }
+     return getStreamingInputCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.StreamingOutputCallRequest,
+      io.grpc.testing.integration.Messages.StreamingOutputCallResponse> getFullDuplexCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "FullDuplexCall",
+      requestType = io.grpc.testing.integration.Messages.StreamingOutputCallRequest.class,
+      responseType = io.grpc.testing.integration.Messages.StreamingOutputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.StreamingOutputCallRequest,
+      io.grpc.testing.integration.Messages.StreamingOutputCallResponse> getFullDuplexCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.StreamingOutputCallRequest, io.grpc.testing.integration.Messages.StreamingOutputCallResponse> getFullDuplexCallMethod;
+    if ((getFullDuplexCallMethod = TestServiceGrpc.getFullDuplexCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getFullDuplexCallMethod = TestServiceGrpc.getFullDuplexCallMethod) == null) {
+          TestServiceGrpc.getFullDuplexCallMethod = getFullDuplexCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.integration.Messages.StreamingOutputCallRequest, io.grpc.testing.integration.Messages.StreamingOutputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.TestService", "FullDuplexCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Messages.StreamingOutputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Messages.StreamingOutputCallResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("FullDuplexCall"))
+                  .build();
+          }
+        }
+     }
+     return getFullDuplexCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.StreamingOutputCallRequest,
+      io.grpc.testing.integration.Messages.StreamingOutputCallResponse> getHalfDuplexCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "HalfDuplexCall",
+      requestType = io.grpc.testing.integration.Messages.StreamingOutputCallRequest.class,
+      responseType = io.grpc.testing.integration.Messages.StreamingOutputCallResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.StreamingOutputCallRequest,
+      io.grpc.testing.integration.Messages.StreamingOutputCallResponse> getHalfDuplexCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.integration.Messages.StreamingOutputCallRequest, io.grpc.testing.integration.Messages.StreamingOutputCallResponse> getHalfDuplexCallMethod;
+    if ((getHalfDuplexCallMethod = TestServiceGrpc.getHalfDuplexCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getHalfDuplexCallMethod = TestServiceGrpc.getHalfDuplexCallMethod) == null) {
+          TestServiceGrpc.getHalfDuplexCallMethod = getHalfDuplexCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.integration.Messages.StreamingOutputCallRequest, io.grpc.testing.integration.Messages.StreamingOutputCallResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.TestService", "HalfDuplexCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Messages.StreamingOutputCallRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.Messages.StreamingOutputCallResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("HalfDuplexCall"))
+                  .build();
+          }
+        }
+     }
+     return getHalfDuplexCallMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty,
+      io.grpc.testing.integration.EmptyProtos.Empty> getUnimplementedCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "UnimplementedCall",
+      requestType = io.grpc.testing.integration.EmptyProtos.Empty.class,
+      responseType = io.grpc.testing.integration.EmptyProtos.Empty.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty,
+      io.grpc.testing.integration.EmptyProtos.Empty> getUnimplementedCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty, io.grpc.testing.integration.EmptyProtos.Empty> getUnimplementedCallMethod;
+    if ((getUnimplementedCallMethod = TestServiceGrpc.getUnimplementedCallMethod) == null) {
+      synchronized (TestServiceGrpc.class) {
+        if ((getUnimplementedCallMethod = TestServiceGrpc.getUnimplementedCallMethod) == null) {
+          TestServiceGrpc.getUnimplementedCallMethod = getUnimplementedCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.integration.EmptyProtos.Empty, io.grpc.testing.integration.EmptyProtos.Empty>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.TestService", "UnimplementedCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.EmptyProtos.Empty.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.EmptyProtos.Empty.getDefaultInstance()))
+                  .setSchemaDescriptor(new TestServiceMethodDescriptorSupplier("UnimplementedCall"))
+                  .build();
+          }
+        }
+     }
+     return getUnimplementedCallMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static TestServiceStub newStub(io.grpc.Channel channel) {
+    return new TestServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static TestServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new TestServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static TestServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new TestServiceFutureStub(channel);
+  }
+
+  /**
+   * <pre>
+   * A simple service to test the various types of RPCs and experiment with
+   * performance with various types of payload.
+   * </pre>
+   */
+  public static abstract class TestServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * One empty request followed by one empty response.
+     * </pre>
+     */
+    public void emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.EmptyProtos.Empty> responseObserver) {
+      asyncUnimplementedUnaryCall(getEmptyCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * </pre>
+     */
+    public void unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.SimpleResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getUnaryCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response. Response has cache control
+     * headers set such that a caching HTTP proxy (such as GFE) can
+     * satisfy subsequent requests.
+     * </pre>
+     */
+    public void cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.SimpleResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getCacheableUnaryCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * One request followed by a sequence of responses (streamed download).
+     * The server returns the payload with client desired type and sizes.
+     * </pre>
+     */
+    public void streamingOutputCall(io.grpc.testing.integration.Messages.StreamingOutputCallRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingOutputCallResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getStreamingOutputCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by one response (streamed upload).
+     * The server returns the aggregated size of client payload as the result.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingInputCallRequest> streamingInputCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingInputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getStreamingInputCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests with each request served by the server immediately.
+     * As one request could lead to multiple responses, this interface
+     * demonstrates the idea of full duplexing.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingOutputCallRequest> fullDuplexCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingOutputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getFullDuplexCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by a sequence of responses.
+     * The server buffers all the client requests and then serves them in order. A
+     * stream of responses are returned to the client when the server starts with
+     * first request.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingOutputCallRequest> halfDuplexCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingOutputCallResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getHalfDuplexCallMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * The test server will not implement this method. It will be used
+     * to test the behavior when clients call unimplemented methods.
+     * </pre>
+     */
+    public void unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.EmptyProtos.Empty> responseObserver) {
+      asyncUnimplementedUnaryCall(getUnimplementedCallMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getEmptyCallMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.EmptyProtos.Empty,
+                io.grpc.testing.integration.EmptyProtos.Empty>(
+                  this, METHODID_EMPTY_CALL)))
+          .addMethod(
+            getUnaryCallMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.Messages.SimpleRequest,
+                io.grpc.testing.integration.Messages.SimpleResponse>(
+                  this, METHODID_UNARY_CALL)))
+          .addMethod(
+            getCacheableUnaryCallMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.Messages.SimpleRequest,
+                io.grpc.testing.integration.Messages.SimpleResponse>(
+                  this, METHODID_CACHEABLE_UNARY_CALL)))
+          .addMethod(
+            getStreamingOutputCallMethod(),
+            asyncServerStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.Messages.StreamingOutputCallRequest,
+                io.grpc.testing.integration.Messages.StreamingOutputCallResponse>(
+                  this, METHODID_STREAMING_OUTPUT_CALL)))
+          .addMethod(
+            getStreamingInputCallMethod(),
+            asyncClientStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.Messages.StreamingInputCallRequest,
+                io.grpc.testing.integration.Messages.StreamingInputCallResponse>(
+                  this, METHODID_STREAMING_INPUT_CALL)))
+          .addMethod(
+            getFullDuplexCallMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.Messages.StreamingOutputCallRequest,
+                io.grpc.testing.integration.Messages.StreamingOutputCallResponse>(
+                  this, METHODID_FULL_DUPLEX_CALL)))
+          .addMethod(
+            getHalfDuplexCallMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.Messages.StreamingOutputCallRequest,
+                io.grpc.testing.integration.Messages.StreamingOutputCallResponse>(
+                  this, METHODID_HALF_DUPLEX_CALL)))
+          .addMethod(
+            getUnimplementedCallMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.EmptyProtos.Empty,
+                io.grpc.testing.integration.EmptyProtos.Empty>(
+                  this, METHODID_UNIMPLEMENTED_CALL)))
+          .build();
+    }
+  }
+
+  /**
+   * <pre>
+   * A simple service to test the various types of RPCs and experiment with
+   * performance with various types of payload.
+   * </pre>
+   */
+  public static final class TestServiceStub extends io.grpc.stub.AbstractStub<TestServiceStub> {
+    private TestServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One empty request followed by one empty response.
+     * </pre>
+     */
+    public void emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.EmptyProtos.Empty> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getEmptyCallMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * </pre>
+     */
+    public void unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.SimpleResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getUnaryCallMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response. Response has cache control
+     * headers set such that a caching HTTP proxy (such as GFE) can
+     * satisfy subsequent requests.
+     * </pre>
+     */
+    public void cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.SimpleResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getCacheableUnaryCallMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * One request followed by a sequence of responses (streamed download).
+     * The server returns the payload with client desired type and sizes.
+     * </pre>
+     */
+    public void streamingOutputCall(io.grpc.testing.integration.Messages.StreamingOutputCallRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingOutputCallResponse> responseObserver) {
+      asyncServerStreamingCall(
+          getChannel().newCall(getStreamingOutputCallMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by one response (streamed upload).
+     * The server returns the aggregated size of client payload as the result.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingInputCallRequest> streamingInputCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingInputCallResponse> responseObserver) {
+      return asyncClientStreamingCall(
+          getChannel().newCall(getStreamingInputCallMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests with each request served by the server immediately.
+     * As one request could lead to multiple responses, this interface
+     * demonstrates the idea of full duplexing.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingOutputCallRequest> fullDuplexCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingOutputCallResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getFullDuplexCallMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * A sequence of requests followed by a sequence of responses.
+     * The server buffers all the client requests and then serves them in order. A
+     * stream of responses are returned to the client when the server starts with
+     * first request.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingOutputCallRequest> halfDuplexCall(
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingOutputCallResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getHalfDuplexCallMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * The test server will not implement this method. It will be used
+     * to test the behavior when clients call unimplemented methods.
+     * </pre>
+     */
+    public void unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.EmptyProtos.Empty> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getUnimplementedCallMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   * <pre>
+   * A simple service to test the various types of RPCs and experiment with
+   * performance with various types of payload.
+   * </pre>
+   */
+  public static final class TestServiceBlockingStub extends io.grpc.stub.AbstractStub<TestServiceBlockingStub> {
+    private TestServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One empty request followed by one empty response.
+     * </pre>
+     */
+    public io.grpc.testing.integration.EmptyProtos.Empty emptyCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
+      return blockingUnaryCall(
+          getChannel(), getEmptyCallMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * </pre>
+     */
+    public io.grpc.testing.integration.Messages.SimpleResponse unaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getUnaryCallMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response. Response has cache control
+     * headers set such that a caching HTTP proxy (such as GFE) can
+     * satisfy subsequent requests.
+     * </pre>
+     */
+    public io.grpc.testing.integration.Messages.SimpleResponse cacheableUnaryCall(io.grpc.testing.integration.Messages.SimpleRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getCacheableUnaryCallMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * One request followed by a sequence of responses (streamed download).
+     * The server returns the payload with client desired type and sizes.
+     * </pre>
+     */
+    public java.util.Iterator<io.grpc.testing.integration.Messages.StreamingOutputCallResponse> streamingOutputCall(
+        io.grpc.testing.integration.Messages.StreamingOutputCallRequest request) {
+      return blockingServerStreamingCall(
+          getChannel(), getStreamingOutputCallMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * The test server will not implement this method. It will be used
+     * to test the behavior when clients call unimplemented methods.
+     * </pre>
+     */
+    public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
+      return blockingUnaryCall(
+          getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   * <pre>
+   * A simple service to test the various types of RPCs and experiment with
+   * performance with various types of payload.
+   * </pre>
+   */
+  public static final class TestServiceFutureStub extends io.grpc.stub.AbstractStub<TestServiceFutureStub> {
+    private TestServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private TestServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected TestServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new TestServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * One empty request followed by one empty response.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.integration.EmptyProtos.Empty> emptyCall(
+        io.grpc.testing.integration.EmptyProtos.Empty request) {
+      return futureUnaryCall(
+          getChannel().newCall(getEmptyCallMethod(), getCallOptions()), request);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.integration.Messages.SimpleResponse> unaryCall(
+        io.grpc.testing.integration.Messages.SimpleRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getUnaryCallMethod(), getCallOptions()), request);
+    }
+
+    /**
+     * <pre>
+     * One request followed by one response. Response has cache control
+     * headers set such that a caching HTTP proxy (such as GFE) can
+     * satisfy subsequent requests.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.integration.Messages.SimpleResponse> cacheableUnaryCall(
+        io.grpc.testing.integration.Messages.SimpleRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getCacheableUnaryCallMethod(), getCallOptions()), request);
+    }
+
+    /**
+     * <pre>
+     * The test server will not implement this method. It will be used
+     * to test the behavior when clients call unimplemented methods.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.integration.EmptyProtos.Empty> unimplementedCall(
+        io.grpc.testing.integration.EmptyProtos.Empty request) {
+      return futureUnaryCall(
+          getChannel().newCall(getUnimplementedCallMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_EMPTY_CALL = 0;
+  private static final int METHODID_UNARY_CALL = 1;
+  private static final int METHODID_CACHEABLE_UNARY_CALL = 2;
+  private static final int METHODID_STREAMING_OUTPUT_CALL = 3;
+  private static final int METHODID_UNIMPLEMENTED_CALL = 4;
+  private static final int METHODID_STREAMING_INPUT_CALL = 5;
+  private static final int METHODID_FULL_DUPLEX_CALL = 6;
+  private static final int METHODID_HALF_DUPLEX_CALL = 7;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final TestServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(TestServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_EMPTY_CALL:
+          serviceImpl.emptyCall((io.grpc.testing.integration.EmptyProtos.Empty) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.EmptyProtos.Empty>) responseObserver);
+          break;
+        case METHODID_UNARY_CALL:
+          serviceImpl.unaryCall((io.grpc.testing.integration.Messages.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.SimpleResponse>) responseObserver);
+          break;
+        case METHODID_CACHEABLE_UNARY_CALL:
+          serviceImpl.cacheableUnaryCall((io.grpc.testing.integration.Messages.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.SimpleResponse>) responseObserver);
+          break;
+        case METHODID_STREAMING_OUTPUT_CALL:
+          serviceImpl.streamingOutputCall((io.grpc.testing.integration.Messages.StreamingOutputCallRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingOutputCallResponse>) responseObserver);
+          break;
+        case METHODID_UNIMPLEMENTED_CALL:
+          serviceImpl.unimplementedCall((io.grpc.testing.integration.EmptyProtos.Empty) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.EmptyProtos.Empty>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_STREAMING_INPUT_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.streamingInputCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingInputCallResponse>) responseObserver);
+        case METHODID_FULL_DUPLEX_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.fullDuplexCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingOutputCallResponse>) responseObserver);
+        case METHODID_HALF_DUPLEX_CALL:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.halfDuplexCall(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.Messages.StreamingOutputCallResponse>) responseObserver);
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class TestServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    TestServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.testing.integration.Test.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("TestService");
+    }
+  }
+
+  private static final class TestServiceFileDescriptorSupplier
+      extends TestServiceBaseDescriptorSupplier {
+    TestServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class TestServiceMethodDescriptorSupplier
+      extends TestServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    TestServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (TestServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new TestServiceFileDescriptorSupplier())
+              .addMethod(getEmptyCallMethod())
+              .addMethod(getUnaryCallMethod())
+              .addMethod(getCacheableUnaryCallMethod())
+              .addMethod(getStreamingOutputCallMethod())
+              .addMethod(getStreamingInputCallMethod())
+              .addMethod(getFullDuplexCallMethod())
+              .addMethod(getHalfDuplexCallMethod())
+              .addMethod(getUnimplementedCallMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/interop-testing/src/generated/main/grpc/io/grpc/testing/integration/UnimplementedServiceGrpc.java b/interop-testing/src/generated/main/grpc/io/grpc/testing/integration/UnimplementedServiceGrpc.java
new file mode 100644
index 0000000..0fe8687
--- /dev/null
+++ b/interop-testing/src/generated/main/grpc/io/grpc/testing/integration/UnimplementedServiceGrpc.java
@@ -0,0 +1,312 @@
+package io.grpc.testing.integration;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ * <pre>
+ * A simple service NOT implemented at servers so clients can test for
+ * that case.
+ * </pre>
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: grpc/testing/test.proto")
+public final class UnimplementedServiceGrpc {
+
+  private UnimplementedServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.UnimplementedService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty,
+      io.grpc.testing.integration.EmptyProtos.Empty> getUnimplementedCallMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "UnimplementedCall",
+      requestType = io.grpc.testing.integration.EmptyProtos.Empty.class,
+      responseType = io.grpc.testing.integration.EmptyProtos.Empty.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty,
+      io.grpc.testing.integration.EmptyProtos.Empty> getUnimplementedCallMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.integration.EmptyProtos.Empty, io.grpc.testing.integration.EmptyProtos.Empty> getUnimplementedCallMethod;
+    if ((getUnimplementedCallMethod = UnimplementedServiceGrpc.getUnimplementedCallMethod) == null) {
+      synchronized (UnimplementedServiceGrpc.class) {
+        if ((getUnimplementedCallMethod = UnimplementedServiceGrpc.getUnimplementedCallMethod) == null) {
+          UnimplementedServiceGrpc.getUnimplementedCallMethod = getUnimplementedCallMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.integration.EmptyProtos.Empty, io.grpc.testing.integration.EmptyProtos.Empty>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.UnimplementedService", "UnimplementedCall"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.EmptyProtos.Empty.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.integration.EmptyProtos.Empty.getDefaultInstance()))
+                  .setSchemaDescriptor(new UnimplementedServiceMethodDescriptorSupplier("UnimplementedCall"))
+                  .build();
+          }
+        }
+     }
+     return getUnimplementedCallMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static UnimplementedServiceStub newStub(io.grpc.Channel channel) {
+    return new UnimplementedServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static UnimplementedServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new UnimplementedServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static UnimplementedServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new UnimplementedServiceFutureStub(channel);
+  }
+
+  /**
+   * <pre>
+   * A simple service NOT implemented at servers so clients can test for
+   * that case.
+   * </pre>
+   */
+  public static abstract class UnimplementedServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * A call that no server should implement
+     * </pre>
+     */
+    public void unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.EmptyProtos.Empty> responseObserver) {
+      asyncUnimplementedUnaryCall(getUnimplementedCallMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getUnimplementedCallMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.integration.EmptyProtos.Empty,
+                io.grpc.testing.integration.EmptyProtos.Empty>(
+                  this, METHODID_UNIMPLEMENTED_CALL)))
+          .build();
+    }
+  }
+
+  /**
+   * <pre>
+   * A simple service NOT implemented at servers so clients can test for
+   * that case.
+   * </pre>
+   */
+  public static final class UnimplementedServiceStub extends io.grpc.stub.AbstractStub<UnimplementedServiceStub> {
+    private UnimplementedServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private UnimplementedServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected UnimplementedServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new UnimplementedServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * A call that no server should implement
+     * </pre>
+     */
+    public void unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.integration.EmptyProtos.Empty> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getUnimplementedCallMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   * <pre>
+   * A simple service NOT implemented at servers so clients can test for
+   * that case.
+   * </pre>
+   */
+  public static final class UnimplementedServiceBlockingStub extends io.grpc.stub.AbstractStub<UnimplementedServiceBlockingStub> {
+    private UnimplementedServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private UnimplementedServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected UnimplementedServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new UnimplementedServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * A call that no server should implement
+     * </pre>
+     */
+    public io.grpc.testing.integration.EmptyProtos.Empty unimplementedCall(io.grpc.testing.integration.EmptyProtos.Empty request) {
+      return blockingUnaryCall(
+          getChannel(), getUnimplementedCallMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   * <pre>
+   * A simple service NOT implemented at servers so clients can test for
+   * that case.
+   * </pre>
+   */
+  public static final class UnimplementedServiceFutureStub extends io.grpc.stub.AbstractStub<UnimplementedServiceFutureStub> {
+    private UnimplementedServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private UnimplementedServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected UnimplementedServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new UnimplementedServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * A call that no server should implement
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.integration.EmptyProtos.Empty> unimplementedCall(
+        io.grpc.testing.integration.EmptyProtos.Empty request) {
+      return futureUnaryCall(
+          getChannel().newCall(getUnimplementedCallMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_UNIMPLEMENTED_CALL = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final UnimplementedServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(UnimplementedServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_UNIMPLEMENTED_CALL:
+          serviceImpl.unimplementedCall((io.grpc.testing.integration.EmptyProtos.Empty) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.integration.EmptyProtos.Empty>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class UnimplementedServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    UnimplementedServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.testing.integration.Test.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("UnimplementedService");
+    }
+  }
+
+  private static final class UnimplementedServiceFileDescriptorSupplier
+      extends UnimplementedServiceBaseDescriptorSupplier {
+    UnimplementedServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class UnimplementedServiceMethodDescriptorSupplier
+      extends UnimplementedServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    UnimplementedServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (UnimplementedServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new UnimplementedServiceFileDescriptorSupplier())
+              .addMethod(getUnimplementedCallMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/interop-testing/src/generated/main/java/io/grpc/testing/integration/EmptyProtos.java b/interop-testing/src/generated/main/java/io/grpc/testing/integration/EmptyProtos.java
new file mode 100644
index 0000000..ba9c895
--- /dev/null
+++ b/interop-testing/src/generated/main/java/io/grpc/testing/integration/EmptyProtos.java
@@ -0,0 +1,461 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/testing/empty.proto
+
+package io.grpc.testing.integration;
+
+public final class EmptyProtos {
+  private EmptyProtos() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  public interface EmptyOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.Empty)
+      com.google.protobuf.MessageOrBuilder {
+  }
+  /**
+   * <pre>
+   * An empty message that you can re-use to avoid defining duplicated empty
+   * messages in your project. A typical example is to use it as argument or the
+   * return value of a service API. For instance:
+   *   service Foo {
+   *     rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { };
+   *   };
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.Empty}
+   */
+  public  static final class Empty extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.Empty)
+      EmptyOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use Empty.newBuilder() to construct.
+    private Empty(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private Empty() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private Empty(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownField(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.EmptyProtos.internal_static_grpc_testing_Empty_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.EmptyProtos.internal_static_grpc_testing_Empty_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.EmptyProtos.Empty.class, io.grpc.testing.integration.EmptyProtos.Empty.Builder.class);
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.EmptyProtos.Empty)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.EmptyProtos.Empty other = (io.grpc.testing.integration.EmptyProtos.Empty) obj;
+
+      boolean result = true;
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.EmptyProtos.Empty parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.EmptyProtos.Empty parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.EmptyProtos.Empty parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.EmptyProtos.Empty parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.EmptyProtos.Empty parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.EmptyProtos.Empty parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.EmptyProtos.Empty parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.EmptyProtos.Empty parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.EmptyProtos.Empty parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.EmptyProtos.Empty parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.EmptyProtos.Empty parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.EmptyProtos.Empty parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.EmptyProtos.Empty prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * An empty message that you can re-use to avoid defining duplicated empty
+     * messages in your project. A typical example is to use it as argument or the
+     * return value of a service API. For instance:
+     *   service Foo {
+     *     rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { };
+     *   };
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.Empty}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.Empty)
+        io.grpc.testing.integration.EmptyProtos.EmptyOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.EmptyProtos.internal_static_grpc_testing_Empty_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.EmptyProtos.internal_static_grpc_testing_Empty_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.EmptyProtos.Empty.class, io.grpc.testing.integration.EmptyProtos.Empty.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.EmptyProtos.Empty.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.EmptyProtos.internal_static_grpc_testing_Empty_descriptor;
+      }
+
+      public io.grpc.testing.integration.EmptyProtos.Empty getDefaultInstanceForType() {
+        return io.grpc.testing.integration.EmptyProtos.Empty.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.EmptyProtos.Empty build() {
+        io.grpc.testing.integration.EmptyProtos.Empty result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.EmptyProtos.Empty buildPartial() {
+        io.grpc.testing.integration.EmptyProtos.Empty result = new io.grpc.testing.integration.EmptyProtos.Empty(this);
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.EmptyProtos.Empty) {
+          return mergeFrom((io.grpc.testing.integration.EmptyProtos.Empty)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.EmptyProtos.Empty other) {
+        if (other == io.grpc.testing.integration.EmptyProtos.Empty.getDefaultInstance()) return this;
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.EmptyProtos.Empty parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.EmptyProtos.Empty) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFields(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.Empty)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.Empty)
+    private static final io.grpc.testing.integration.EmptyProtos.Empty DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.EmptyProtos.Empty();
+    }
+
+    public static io.grpc.testing.integration.EmptyProtos.Empty getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    @java.lang.Deprecated public static final com.google.protobuf.Parser<Empty>
+        PARSER = new com.google.protobuf.AbstractParser<Empty>() {
+      public Empty parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new Empty(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<Empty> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<Empty> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.EmptyProtos.Empty getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_Empty_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_Empty_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\030grpc/testing/empty.proto\022\014grpc.testing" +
+      "\"\007\n\005EmptyB*\n\033io.grpc.testing.integration" +
+      "B\013EmptyProtos"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        }, assigner);
+    internal_static_grpc_testing_Empty_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_testing_Empty_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_Empty_descriptor,
+        new java.lang.String[] { });
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/interop-testing/src/generated/main/java/io/grpc/testing/integration/Messages.java b/interop-testing/src/generated/main/java/io/grpc/testing/integration/Messages.java
new file mode 100644
index 0000000..575dcd5
--- /dev/null
+++ b/interop-testing/src/generated/main/java/io/grpc/testing/integration/Messages.java
@@ -0,0 +1,10548 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/testing/messages.proto
+
+package io.grpc.testing.integration;
+
+public final class Messages {
+  private Messages() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  /**
+   * <pre>
+   * DEPRECATED, don't use. To be removed shortly.
+   * The type of payload that should be returned.
+   * </pre>
+   *
+   * Protobuf enum {@code grpc.testing.PayloadType}
+   */
+  public enum PayloadType
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <pre>
+     * Compressable text format.
+     * </pre>
+     *
+     * <code>COMPRESSABLE = 0;</code>
+     */
+    COMPRESSABLE(0),
+    /**
+     * <pre>
+     * Uncompressable binary format.
+     * </pre>
+     *
+     * <code>UNCOMPRESSABLE = 1;</code>
+     */
+    UNCOMPRESSABLE(1),
+    /**
+     * <pre>
+     * Randomly chosen from all other formats defined in this enum.
+     * </pre>
+     *
+     * <code>RANDOM = 2;</code>
+     */
+    RANDOM(2),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <pre>
+     * Compressable text format.
+     * </pre>
+     *
+     * <code>COMPRESSABLE = 0;</code>
+     */
+    public static final int COMPRESSABLE_VALUE = 0;
+    /**
+     * <pre>
+     * Uncompressable binary format.
+     * </pre>
+     *
+     * <code>UNCOMPRESSABLE = 1;</code>
+     */
+    public static final int UNCOMPRESSABLE_VALUE = 1;
+    /**
+     * <pre>
+     * Randomly chosen from all other formats defined in this enum.
+     * </pre>
+     *
+     * <code>RANDOM = 2;</code>
+     */
+    public static final int RANDOM_VALUE = 2;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static PayloadType valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static PayloadType forNumber(int value) {
+      switch (value) {
+        case 0: return COMPRESSABLE;
+        case 1: return UNCOMPRESSABLE;
+        case 2: return RANDOM;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<PayloadType>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        PayloadType> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<PayloadType>() {
+            public PayloadType findValueByNumber(int number) {
+              return PayloadType.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Messages.getDescriptor().getEnumTypes().get(0);
+    }
+
+    private static final PayloadType[] VALUES = values();
+
+    public static PayloadType valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private PayloadType(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.testing.PayloadType)
+  }
+
+  public interface PayloadOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.Payload)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * The type of data in body.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType type = 1;</code>
+     */
+    int getTypeValue();
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * The type of data in body.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType type = 1;</code>
+     */
+    io.grpc.testing.integration.Messages.PayloadType getType();
+
+    /**
+     * <pre>
+     * Primary contents of payload.
+     * </pre>
+     *
+     * <code>bytes body = 2;</code>
+     */
+    com.google.protobuf.ByteString getBody();
+  }
+  /**
+   * <pre>
+   * A block of data, to simply increase gRPC message size.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.Payload}
+   */
+  public  static final class Payload extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.Payload)
+      PayloadOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use Payload.newBuilder() to construct.
+    private Payload(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private Payload() {
+      type_ = 0;
+      body_ = com.google.protobuf.ByteString.EMPTY;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private Payload(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+              int rawValue = input.readEnum();
+
+              type_ = rawValue;
+              break;
+            }
+            case 18: {
+
+              body_ = input.readBytes();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_Payload_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_Payload_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Messages.Payload.class, io.grpc.testing.integration.Messages.Payload.Builder.class);
+    }
+
+    public static final int TYPE_FIELD_NUMBER = 1;
+    private int type_;
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * The type of data in body.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType type = 1;</code>
+     */
+    public int getTypeValue() {
+      return type_;
+    }
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * The type of data in body.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType type = 1;</code>
+     */
+    public io.grpc.testing.integration.Messages.PayloadType getType() {
+      io.grpc.testing.integration.Messages.PayloadType result = io.grpc.testing.integration.Messages.PayloadType.valueOf(type_);
+      return result == null ? io.grpc.testing.integration.Messages.PayloadType.UNRECOGNIZED : result;
+    }
+
+    public static final int BODY_FIELD_NUMBER = 2;
+    private com.google.protobuf.ByteString body_;
+    /**
+     * <pre>
+     * Primary contents of payload.
+     * </pre>
+     *
+     * <code>bytes body = 2;</code>
+     */
+    public com.google.protobuf.ByteString getBody() {
+      return body_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (type_ != io.grpc.testing.integration.Messages.PayloadType.COMPRESSABLE.getNumber()) {
+        output.writeEnum(1, type_);
+      }
+      if (!body_.isEmpty()) {
+        output.writeBytes(2, body_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (type_ != io.grpc.testing.integration.Messages.PayloadType.COMPRESSABLE.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(1, type_);
+      }
+      if (!body_.isEmpty()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBytesSize(2, body_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Messages.Payload)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Messages.Payload other = (io.grpc.testing.integration.Messages.Payload) obj;
+
+      boolean result = true;
+      result = result && type_ == other.type_;
+      result = result && getBody()
+          .equals(other.getBody());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + TYPE_FIELD_NUMBER;
+      hash = (53 * hash) + type_;
+      hash = (37 * hash) + BODY_FIELD_NUMBER;
+      hash = (53 * hash) + getBody().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Messages.Payload parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.Payload parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.Payload parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.Payload parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.Payload parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.Payload parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.Payload parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.Payload parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.Payload parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.Payload parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.Payload parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.Payload parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Messages.Payload prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * A block of data, to simply increase gRPC message size.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.Payload}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.Payload)
+        io.grpc.testing.integration.Messages.PayloadOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_Payload_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_Payload_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Messages.Payload.class, io.grpc.testing.integration.Messages.Payload.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Messages.Payload.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        type_ = 0;
+
+        body_ = com.google.protobuf.ByteString.EMPTY;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_Payload_descriptor;
+      }
+
+      public io.grpc.testing.integration.Messages.Payload getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Messages.Payload.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Messages.Payload build() {
+        io.grpc.testing.integration.Messages.Payload result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Messages.Payload buildPartial() {
+        io.grpc.testing.integration.Messages.Payload result = new io.grpc.testing.integration.Messages.Payload(this);
+        result.type_ = type_;
+        result.body_ = body_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Messages.Payload) {
+          return mergeFrom((io.grpc.testing.integration.Messages.Payload)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Messages.Payload other) {
+        if (other == io.grpc.testing.integration.Messages.Payload.getDefaultInstance()) return this;
+        if (other.type_ != 0) {
+          setTypeValue(other.getTypeValue());
+        }
+        if (other.getBody() != com.google.protobuf.ByteString.EMPTY) {
+          setBody(other.getBody());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Messages.Payload parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Messages.Payload) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int type_ = 0;
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * The type of data in body.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType type = 1;</code>
+       */
+      public int getTypeValue() {
+        return type_;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * The type of data in body.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType type = 1;</code>
+       */
+      public Builder setTypeValue(int value) {
+        type_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * The type of data in body.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType type = 1;</code>
+       */
+      public io.grpc.testing.integration.Messages.PayloadType getType() {
+        io.grpc.testing.integration.Messages.PayloadType result = io.grpc.testing.integration.Messages.PayloadType.valueOf(type_);
+        return result == null ? io.grpc.testing.integration.Messages.PayloadType.UNRECOGNIZED : result;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * The type of data in body.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType type = 1;</code>
+       */
+      public Builder setType(io.grpc.testing.integration.Messages.PayloadType value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        type_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * The type of data in body.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType type = 1;</code>
+       */
+      public Builder clearType() {
+        
+        type_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private com.google.protobuf.ByteString body_ = com.google.protobuf.ByteString.EMPTY;
+      /**
+       * <pre>
+       * Primary contents of payload.
+       * </pre>
+       *
+       * <code>bytes body = 2;</code>
+       */
+      public com.google.protobuf.ByteString getBody() {
+        return body_;
+      }
+      /**
+       * <pre>
+       * Primary contents of payload.
+       * </pre>
+       *
+       * <code>bytes body = 2;</code>
+       */
+      public Builder setBody(com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        body_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Primary contents of payload.
+       * </pre>
+       *
+       * <code>bytes body = 2;</code>
+       */
+      public Builder clearBody() {
+        
+        body_ = getDefaultInstance().getBody();
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.Payload)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.Payload)
+    private static final io.grpc.testing.integration.Messages.Payload DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Messages.Payload();
+    }
+
+    public static io.grpc.testing.integration.Messages.Payload getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<Payload>
+        PARSER = new com.google.protobuf.AbstractParser<Payload>() {
+      public Payload parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new Payload(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<Payload> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<Payload> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Messages.Payload getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface EchoStatusOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.EchoStatus)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>int32 code = 1;</code>
+     */
+    int getCode();
+
+    /**
+     * <code>string message = 2;</code>
+     */
+    java.lang.String getMessage();
+    /**
+     * <code>string message = 2;</code>
+     */
+    com.google.protobuf.ByteString
+        getMessageBytes();
+  }
+  /**
+   * <pre>
+   * A protobuf representation for grpc status. This is used by test
+   * clients to specify a status that the server should attempt to return.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.EchoStatus}
+   */
+  public  static final class EchoStatus extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.EchoStatus)
+      EchoStatusOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use EchoStatus.newBuilder() to construct.
+    private EchoStatus(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private EchoStatus() {
+      code_ = 0;
+      message_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private EchoStatus(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              code_ = input.readInt32();
+              break;
+            }
+            case 18: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              message_ = s;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_EchoStatus_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_EchoStatus_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Messages.EchoStatus.class, io.grpc.testing.integration.Messages.EchoStatus.Builder.class);
+    }
+
+    public static final int CODE_FIELD_NUMBER = 1;
+    private int code_;
+    /**
+     * <code>int32 code = 1;</code>
+     */
+    public int getCode() {
+      return code_;
+    }
+
+    public static final int MESSAGE_FIELD_NUMBER = 2;
+    private volatile java.lang.Object message_;
+    /**
+     * <code>string message = 2;</code>
+     */
+    public java.lang.String getMessage() {
+      java.lang.Object ref = message_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        message_ = s;
+        return s;
+      }
+    }
+    /**
+     * <code>string message = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getMessageBytes() {
+      java.lang.Object ref = message_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        message_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (code_ != 0) {
+        output.writeInt32(1, code_);
+      }
+      if (!getMessageBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, message_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (code_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1, code_);
+      }
+      if (!getMessageBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, message_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Messages.EchoStatus)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Messages.EchoStatus other = (io.grpc.testing.integration.Messages.EchoStatus) obj;
+
+      boolean result = true;
+      result = result && (getCode()
+          == other.getCode());
+      result = result && getMessage()
+          .equals(other.getMessage());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + CODE_FIELD_NUMBER;
+      hash = (53 * hash) + getCode();
+      hash = (37 * hash) + MESSAGE_FIELD_NUMBER;
+      hash = (53 * hash) + getMessage().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Messages.EchoStatus parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.EchoStatus parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.EchoStatus parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.EchoStatus parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.EchoStatus parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.EchoStatus parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.EchoStatus parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.EchoStatus parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.EchoStatus parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.EchoStatus parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.EchoStatus parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.EchoStatus parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Messages.EchoStatus prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * A protobuf representation for grpc status. This is used by test
+     * clients to specify a status that the server should attempt to return.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.EchoStatus}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.EchoStatus)
+        io.grpc.testing.integration.Messages.EchoStatusOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_EchoStatus_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_EchoStatus_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Messages.EchoStatus.class, io.grpc.testing.integration.Messages.EchoStatus.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Messages.EchoStatus.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        code_ = 0;
+
+        message_ = "";
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_EchoStatus_descriptor;
+      }
+
+      public io.grpc.testing.integration.Messages.EchoStatus getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Messages.EchoStatus.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Messages.EchoStatus build() {
+        io.grpc.testing.integration.Messages.EchoStatus result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Messages.EchoStatus buildPartial() {
+        io.grpc.testing.integration.Messages.EchoStatus result = new io.grpc.testing.integration.Messages.EchoStatus(this);
+        result.code_ = code_;
+        result.message_ = message_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Messages.EchoStatus) {
+          return mergeFrom((io.grpc.testing.integration.Messages.EchoStatus)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Messages.EchoStatus other) {
+        if (other == io.grpc.testing.integration.Messages.EchoStatus.getDefaultInstance()) return this;
+        if (other.getCode() != 0) {
+          setCode(other.getCode());
+        }
+        if (!other.getMessage().isEmpty()) {
+          message_ = other.message_;
+          onChanged();
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Messages.EchoStatus parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Messages.EchoStatus) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int code_ ;
+      /**
+       * <code>int32 code = 1;</code>
+       */
+      public int getCode() {
+        return code_;
+      }
+      /**
+       * <code>int32 code = 1;</code>
+       */
+      public Builder setCode(int value) {
+        
+        code_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>int32 code = 1;</code>
+       */
+      public Builder clearCode() {
+        
+        code_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object message_ = "";
+      /**
+       * <code>string message = 2;</code>
+       */
+      public java.lang.String getMessage() {
+        java.lang.Object ref = message_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          message_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <code>string message = 2;</code>
+       */
+      public com.google.protobuf.ByteString
+          getMessageBytes() {
+        java.lang.Object ref = message_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          message_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <code>string message = 2;</code>
+       */
+      public Builder setMessage(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        message_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string message = 2;</code>
+       */
+      public Builder clearMessage() {
+        
+        message_ = getDefaultInstance().getMessage();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string message = 2;</code>
+       */
+      public Builder setMessageBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        message_ = value;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.EchoStatus)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.EchoStatus)
+    private static final io.grpc.testing.integration.Messages.EchoStatus DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Messages.EchoStatus();
+    }
+
+    public static io.grpc.testing.integration.Messages.EchoStatus getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<EchoStatus>
+        PARSER = new com.google.protobuf.AbstractParser<EchoStatus>() {
+      public EchoStatus parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new EchoStatus(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<EchoStatus> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<EchoStatus> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Messages.EchoStatus getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface SimpleRequestOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.SimpleRequest)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, server randomly chooses one from other formats.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    int getResponseTypeValue();
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, server randomly chooses one from other formats.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    io.grpc.testing.integration.Messages.PayloadType getResponseType();
+
+    /**
+     * <pre>
+     * Desired payload size in the response from the server.
+     * </pre>
+     *
+     * <code>int32 response_size = 2;</code>
+     */
+    int getResponseSize();
+
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    boolean hasPayload();
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    io.grpc.testing.integration.Messages.Payload getPayload();
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder();
+
+    /**
+     * <pre>
+     * Whether SimpleResponse should include username.
+     * </pre>
+     *
+     * <code>bool fill_username = 4;</code>
+     */
+    boolean getFillUsername();
+
+    /**
+     * <pre>
+     * Whether SimpleResponse should include OAuth scope.
+     * </pre>
+     *
+     * <code>bool fill_oauth_scope = 5;</code>
+     */
+    boolean getFillOauthScope();
+
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+     */
+    boolean hasResponseCompressed();
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+     */
+    com.google.protobuf.BoolValue getResponseCompressed();
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+     */
+    com.google.protobuf.BoolValueOrBuilder getResponseCompressedOrBuilder();
+
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    boolean hasResponseStatus();
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    io.grpc.testing.integration.Messages.EchoStatus getResponseStatus();
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    io.grpc.testing.integration.Messages.EchoStatusOrBuilder getResponseStatusOrBuilder();
+
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+     */
+    boolean hasExpectCompressed();
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+     */
+    com.google.protobuf.BoolValue getExpectCompressed();
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+     */
+    com.google.protobuf.BoolValueOrBuilder getExpectCompressedOrBuilder();
+  }
+  /**
+   * <pre>
+   * Unary request.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.SimpleRequest}
+   */
+  public  static final class SimpleRequest extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.SimpleRequest)
+      SimpleRequestOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use SimpleRequest.newBuilder() to construct.
+    private SimpleRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private SimpleRequest() {
+      responseType_ = 0;
+      responseSize_ = 0;
+      fillUsername_ = false;
+      fillOauthScope_ = false;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private SimpleRequest(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+              int rawValue = input.readEnum();
+
+              responseType_ = rawValue;
+              break;
+            }
+            case 16: {
+
+              responseSize_ = input.readInt32();
+              break;
+            }
+            case 26: {
+              io.grpc.testing.integration.Messages.Payload.Builder subBuilder = null;
+              if (payload_ != null) {
+                subBuilder = payload_.toBuilder();
+              }
+              payload_ = input.readMessage(io.grpc.testing.integration.Messages.Payload.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(payload_);
+                payload_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 32: {
+
+              fillUsername_ = input.readBool();
+              break;
+            }
+            case 40: {
+
+              fillOauthScope_ = input.readBool();
+              break;
+            }
+            case 50: {
+              com.google.protobuf.BoolValue.Builder subBuilder = null;
+              if (responseCompressed_ != null) {
+                subBuilder = responseCompressed_.toBuilder();
+              }
+              responseCompressed_ = input.readMessage(com.google.protobuf.BoolValue.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(responseCompressed_);
+                responseCompressed_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 58: {
+              io.grpc.testing.integration.Messages.EchoStatus.Builder subBuilder = null;
+              if (responseStatus_ != null) {
+                subBuilder = responseStatus_.toBuilder();
+              }
+              responseStatus_ = input.readMessage(io.grpc.testing.integration.Messages.EchoStatus.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(responseStatus_);
+                responseStatus_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 66: {
+              com.google.protobuf.BoolValue.Builder subBuilder = null;
+              if (expectCompressed_ != null) {
+                subBuilder = expectCompressed_.toBuilder();
+              }
+              expectCompressed_ = input.readMessage(com.google.protobuf.BoolValue.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(expectCompressed_);
+                expectCompressed_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Messages.SimpleRequest.class, io.grpc.testing.integration.Messages.SimpleRequest.Builder.class);
+    }
+
+    public static final int RESPONSE_TYPE_FIELD_NUMBER = 1;
+    private int responseType_;
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, server randomly chooses one from other formats.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    public int getResponseTypeValue() {
+      return responseType_;
+    }
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, server randomly chooses one from other formats.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    public io.grpc.testing.integration.Messages.PayloadType getResponseType() {
+      io.grpc.testing.integration.Messages.PayloadType result = io.grpc.testing.integration.Messages.PayloadType.valueOf(responseType_);
+      return result == null ? io.grpc.testing.integration.Messages.PayloadType.UNRECOGNIZED : result;
+    }
+
+    public static final int RESPONSE_SIZE_FIELD_NUMBER = 2;
+    private int responseSize_;
+    /**
+     * <pre>
+     * Desired payload size in the response from the server.
+     * </pre>
+     *
+     * <code>int32 response_size = 2;</code>
+     */
+    public int getResponseSize() {
+      return responseSize_;
+    }
+
+    public static final int PAYLOAD_FIELD_NUMBER = 3;
+    private io.grpc.testing.integration.Messages.Payload payload_;
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    public boolean hasPayload() {
+      return payload_ != null;
+    }
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    public io.grpc.testing.integration.Messages.Payload getPayload() {
+      return payload_ == null ? io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+    }
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    public io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+      return getPayload();
+    }
+
+    public static final int FILL_USERNAME_FIELD_NUMBER = 4;
+    private boolean fillUsername_;
+    /**
+     * <pre>
+     * Whether SimpleResponse should include username.
+     * </pre>
+     *
+     * <code>bool fill_username = 4;</code>
+     */
+    public boolean getFillUsername() {
+      return fillUsername_;
+    }
+
+    public static final int FILL_OAUTH_SCOPE_FIELD_NUMBER = 5;
+    private boolean fillOauthScope_;
+    /**
+     * <pre>
+     * Whether SimpleResponse should include OAuth scope.
+     * </pre>
+     *
+     * <code>bool fill_oauth_scope = 5;</code>
+     */
+    public boolean getFillOauthScope() {
+      return fillOauthScope_;
+    }
+
+    public static final int RESPONSE_COMPRESSED_FIELD_NUMBER = 6;
+    private com.google.protobuf.BoolValue responseCompressed_;
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+     */
+    public boolean hasResponseCompressed() {
+      return responseCompressed_ != null;
+    }
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+     */
+    public com.google.protobuf.BoolValue getResponseCompressed() {
+      return responseCompressed_ == null ? com.google.protobuf.BoolValue.getDefaultInstance() : responseCompressed_;
+    }
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+     */
+    public com.google.protobuf.BoolValueOrBuilder getResponseCompressedOrBuilder() {
+      return getResponseCompressed();
+    }
+
+    public static final int RESPONSE_STATUS_FIELD_NUMBER = 7;
+    private io.grpc.testing.integration.Messages.EchoStatus responseStatus_;
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    public boolean hasResponseStatus() {
+      return responseStatus_ != null;
+    }
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    public io.grpc.testing.integration.Messages.EchoStatus getResponseStatus() {
+      return responseStatus_ == null ? io.grpc.testing.integration.Messages.EchoStatus.getDefaultInstance() : responseStatus_;
+    }
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    public io.grpc.testing.integration.Messages.EchoStatusOrBuilder getResponseStatusOrBuilder() {
+      return getResponseStatus();
+    }
+
+    public static final int EXPECT_COMPRESSED_FIELD_NUMBER = 8;
+    private com.google.protobuf.BoolValue expectCompressed_;
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+     */
+    public boolean hasExpectCompressed() {
+      return expectCompressed_ != null;
+    }
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+     */
+    public com.google.protobuf.BoolValue getExpectCompressed() {
+      return expectCompressed_ == null ? com.google.protobuf.BoolValue.getDefaultInstance() : expectCompressed_;
+    }
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+     */
+    public com.google.protobuf.BoolValueOrBuilder getExpectCompressedOrBuilder() {
+      return getExpectCompressed();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (responseType_ != io.grpc.testing.integration.Messages.PayloadType.COMPRESSABLE.getNumber()) {
+        output.writeEnum(1, responseType_);
+      }
+      if (responseSize_ != 0) {
+        output.writeInt32(2, responseSize_);
+      }
+      if (payload_ != null) {
+        output.writeMessage(3, getPayload());
+      }
+      if (fillUsername_ != false) {
+        output.writeBool(4, fillUsername_);
+      }
+      if (fillOauthScope_ != false) {
+        output.writeBool(5, fillOauthScope_);
+      }
+      if (responseCompressed_ != null) {
+        output.writeMessage(6, getResponseCompressed());
+      }
+      if (responseStatus_ != null) {
+        output.writeMessage(7, getResponseStatus());
+      }
+      if (expectCompressed_ != null) {
+        output.writeMessage(8, getExpectCompressed());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (responseType_ != io.grpc.testing.integration.Messages.PayloadType.COMPRESSABLE.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(1, responseType_);
+      }
+      if (responseSize_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(2, responseSize_);
+      }
+      if (payload_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(3, getPayload());
+      }
+      if (fillUsername_ != false) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBoolSize(4, fillUsername_);
+      }
+      if (fillOauthScope_ != false) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBoolSize(5, fillOauthScope_);
+      }
+      if (responseCompressed_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(6, getResponseCompressed());
+      }
+      if (responseStatus_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(7, getResponseStatus());
+      }
+      if (expectCompressed_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(8, getExpectCompressed());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Messages.SimpleRequest)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Messages.SimpleRequest other = (io.grpc.testing.integration.Messages.SimpleRequest) obj;
+
+      boolean result = true;
+      result = result && responseType_ == other.responseType_;
+      result = result && (getResponseSize()
+          == other.getResponseSize());
+      result = result && (hasPayload() == other.hasPayload());
+      if (hasPayload()) {
+        result = result && getPayload()
+            .equals(other.getPayload());
+      }
+      result = result && (getFillUsername()
+          == other.getFillUsername());
+      result = result && (getFillOauthScope()
+          == other.getFillOauthScope());
+      result = result && (hasResponseCompressed() == other.hasResponseCompressed());
+      if (hasResponseCompressed()) {
+        result = result && getResponseCompressed()
+            .equals(other.getResponseCompressed());
+      }
+      result = result && (hasResponseStatus() == other.hasResponseStatus());
+      if (hasResponseStatus()) {
+        result = result && getResponseStatus()
+            .equals(other.getResponseStatus());
+      }
+      result = result && (hasExpectCompressed() == other.hasExpectCompressed());
+      if (hasExpectCompressed()) {
+        result = result && getExpectCompressed()
+            .equals(other.getExpectCompressed());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + RESPONSE_TYPE_FIELD_NUMBER;
+      hash = (53 * hash) + responseType_;
+      hash = (37 * hash) + RESPONSE_SIZE_FIELD_NUMBER;
+      hash = (53 * hash) + getResponseSize();
+      if (hasPayload()) {
+        hash = (37 * hash) + PAYLOAD_FIELD_NUMBER;
+        hash = (53 * hash) + getPayload().hashCode();
+      }
+      hash = (37 * hash) + FILL_USERNAME_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+          getFillUsername());
+      hash = (37 * hash) + FILL_OAUTH_SCOPE_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+          getFillOauthScope());
+      if (hasResponseCompressed()) {
+        hash = (37 * hash) + RESPONSE_COMPRESSED_FIELD_NUMBER;
+        hash = (53 * hash) + getResponseCompressed().hashCode();
+      }
+      if (hasResponseStatus()) {
+        hash = (37 * hash) + RESPONSE_STATUS_FIELD_NUMBER;
+        hash = (53 * hash) + getResponseStatus().hashCode();
+      }
+      if (hasExpectCompressed()) {
+        hash = (37 * hash) + EXPECT_COMPRESSED_FIELD_NUMBER;
+        hash = (53 * hash) + getExpectCompressed().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Messages.SimpleRequest parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleRequest parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleRequest parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleRequest parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleRequest parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleRequest parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleRequest parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleRequest parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleRequest parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleRequest parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleRequest parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleRequest parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Messages.SimpleRequest prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Unary request.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.SimpleRequest}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.SimpleRequest)
+        io.grpc.testing.integration.Messages.SimpleRequestOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleRequest_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleRequest_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Messages.SimpleRequest.class, io.grpc.testing.integration.Messages.SimpleRequest.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Messages.SimpleRequest.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        responseType_ = 0;
+
+        responseSize_ = 0;
+
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+        fillUsername_ = false;
+
+        fillOauthScope_ = false;
+
+        if (responseCompressedBuilder_ == null) {
+          responseCompressed_ = null;
+        } else {
+          responseCompressed_ = null;
+          responseCompressedBuilder_ = null;
+        }
+        if (responseStatusBuilder_ == null) {
+          responseStatus_ = null;
+        } else {
+          responseStatus_ = null;
+          responseStatusBuilder_ = null;
+        }
+        if (expectCompressedBuilder_ == null) {
+          expectCompressed_ = null;
+        } else {
+          expectCompressed_ = null;
+          expectCompressedBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleRequest_descriptor;
+      }
+
+      public io.grpc.testing.integration.Messages.SimpleRequest getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Messages.SimpleRequest.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Messages.SimpleRequest build() {
+        io.grpc.testing.integration.Messages.SimpleRequest result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Messages.SimpleRequest buildPartial() {
+        io.grpc.testing.integration.Messages.SimpleRequest result = new io.grpc.testing.integration.Messages.SimpleRequest(this);
+        result.responseType_ = responseType_;
+        result.responseSize_ = responseSize_;
+        if (payloadBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = payloadBuilder_.build();
+        }
+        result.fillUsername_ = fillUsername_;
+        result.fillOauthScope_ = fillOauthScope_;
+        if (responseCompressedBuilder_ == null) {
+          result.responseCompressed_ = responseCompressed_;
+        } else {
+          result.responseCompressed_ = responseCompressedBuilder_.build();
+        }
+        if (responseStatusBuilder_ == null) {
+          result.responseStatus_ = responseStatus_;
+        } else {
+          result.responseStatus_ = responseStatusBuilder_.build();
+        }
+        if (expectCompressedBuilder_ == null) {
+          result.expectCompressed_ = expectCompressed_;
+        } else {
+          result.expectCompressed_ = expectCompressedBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Messages.SimpleRequest) {
+          return mergeFrom((io.grpc.testing.integration.Messages.SimpleRequest)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Messages.SimpleRequest other) {
+        if (other == io.grpc.testing.integration.Messages.SimpleRequest.getDefaultInstance()) return this;
+        if (other.responseType_ != 0) {
+          setResponseTypeValue(other.getResponseTypeValue());
+        }
+        if (other.getResponseSize() != 0) {
+          setResponseSize(other.getResponseSize());
+        }
+        if (other.hasPayload()) {
+          mergePayload(other.getPayload());
+        }
+        if (other.getFillUsername() != false) {
+          setFillUsername(other.getFillUsername());
+        }
+        if (other.getFillOauthScope() != false) {
+          setFillOauthScope(other.getFillOauthScope());
+        }
+        if (other.hasResponseCompressed()) {
+          mergeResponseCompressed(other.getResponseCompressed());
+        }
+        if (other.hasResponseStatus()) {
+          mergeResponseStatus(other.getResponseStatus());
+        }
+        if (other.hasExpectCompressed()) {
+          mergeExpectCompressed(other.getExpectCompressed());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Messages.SimpleRequest parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Messages.SimpleRequest) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int responseType_ = 0;
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, server randomly chooses one from other formats.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public int getResponseTypeValue() {
+        return responseType_;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, server randomly chooses one from other formats.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public Builder setResponseTypeValue(int value) {
+        responseType_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, server randomly chooses one from other formats.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public io.grpc.testing.integration.Messages.PayloadType getResponseType() {
+        io.grpc.testing.integration.Messages.PayloadType result = io.grpc.testing.integration.Messages.PayloadType.valueOf(responseType_);
+        return result == null ? io.grpc.testing.integration.Messages.PayloadType.UNRECOGNIZED : result;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, server randomly chooses one from other formats.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public Builder setResponseType(io.grpc.testing.integration.Messages.PayloadType value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        responseType_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, server randomly chooses one from other formats.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public Builder clearResponseType() {
+        
+        responseType_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int responseSize_ ;
+      /**
+       * <pre>
+       * Desired payload size in the response from the server.
+       * </pre>
+       *
+       * <code>int32 response_size = 2;</code>
+       */
+      public int getResponseSize() {
+        return responseSize_;
+      }
+      /**
+       * <pre>
+       * Desired payload size in the response from the server.
+       * </pre>
+       *
+       * <code>int32 response_size = 2;</code>
+       */
+      public Builder setResponseSize(int value) {
+        
+        responseSize_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Desired payload size in the response from the server.
+       * </pre>
+       *
+       * <code>int32 response_size = 2;</code>
+       */
+      public Builder clearResponseSize() {
+        
+        responseSize_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private io.grpc.testing.integration.Messages.Payload payload_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder> payloadBuilder_;
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public boolean hasPayload() {
+        return payloadBuilder_ != null || payload_ != null;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public io.grpc.testing.integration.Messages.Payload getPayload() {
+        if (payloadBuilder_ == null) {
+          return payload_ == null ? io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+        } else {
+          return payloadBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder setPayload(io.grpc.testing.integration.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payload_ = value;
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder setPayload(
+          io.grpc.testing.integration.Messages.Payload.Builder builderForValue) {
+        if (payloadBuilder_ == null) {
+          payload_ = builderForValue.build();
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder mergePayload(io.grpc.testing.integration.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (payload_ != null) {
+            payload_ =
+              io.grpc.testing.integration.Messages.Payload.newBuilder(payload_).mergeFrom(value).buildPartial();
+          } else {
+            payload_ = value;
+          }
+          onChanged();
+        } else {
+          payloadBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder clearPayload() {
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+          onChanged();
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public io.grpc.testing.integration.Messages.Payload.Builder getPayloadBuilder() {
+        
+        onChanged();
+        return getPayloadFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+        if (payloadBuilder_ != null) {
+          return payloadBuilder_.getMessageOrBuilder();
+        } else {
+          return payload_ == null ?
+              io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+        }
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder> 
+          getPayloadFieldBuilder() {
+        if (payloadBuilder_ == null) {
+          payloadBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder>(
+                  getPayload(),
+                  getParentForChildren(),
+                  isClean());
+          payload_ = null;
+        }
+        return payloadBuilder_;
+      }
+
+      private boolean fillUsername_ ;
+      /**
+       * <pre>
+       * Whether SimpleResponse should include username.
+       * </pre>
+       *
+       * <code>bool fill_username = 4;</code>
+       */
+      public boolean getFillUsername() {
+        return fillUsername_;
+      }
+      /**
+       * <pre>
+       * Whether SimpleResponse should include username.
+       * </pre>
+       *
+       * <code>bool fill_username = 4;</code>
+       */
+      public Builder setFillUsername(boolean value) {
+        
+        fillUsername_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether SimpleResponse should include username.
+       * </pre>
+       *
+       * <code>bool fill_username = 4;</code>
+       */
+      public Builder clearFillUsername() {
+        
+        fillUsername_ = false;
+        onChanged();
+        return this;
+      }
+
+      private boolean fillOauthScope_ ;
+      /**
+       * <pre>
+       * Whether SimpleResponse should include OAuth scope.
+       * </pre>
+       *
+       * <code>bool fill_oauth_scope = 5;</code>
+       */
+      public boolean getFillOauthScope() {
+        return fillOauthScope_;
+      }
+      /**
+       * <pre>
+       * Whether SimpleResponse should include OAuth scope.
+       * </pre>
+       *
+       * <code>bool fill_oauth_scope = 5;</code>
+       */
+      public Builder setFillOauthScope(boolean value) {
+        
+        fillOauthScope_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether SimpleResponse should include OAuth scope.
+       * </pre>
+       *
+       * <code>bool fill_oauth_scope = 5;</code>
+       */
+      public Builder clearFillOauthScope() {
+        
+        fillOauthScope_ = false;
+        onChanged();
+        return this;
+      }
+
+      private com.google.protobuf.BoolValue responseCompressed_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          com.google.protobuf.BoolValue, com.google.protobuf.BoolValue.Builder, com.google.protobuf.BoolValueOrBuilder> responseCompressedBuilder_;
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+       */
+      public boolean hasResponseCompressed() {
+        return responseCompressedBuilder_ != null || responseCompressed_ != null;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+       */
+      public com.google.protobuf.BoolValue getResponseCompressed() {
+        if (responseCompressedBuilder_ == null) {
+          return responseCompressed_ == null ? com.google.protobuf.BoolValue.getDefaultInstance() : responseCompressed_;
+        } else {
+          return responseCompressedBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+       */
+      public Builder setResponseCompressed(com.google.protobuf.BoolValue value) {
+        if (responseCompressedBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          responseCompressed_ = value;
+          onChanged();
+        } else {
+          responseCompressedBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+       */
+      public Builder setResponseCompressed(
+          com.google.protobuf.BoolValue.Builder builderForValue) {
+        if (responseCompressedBuilder_ == null) {
+          responseCompressed_ = builderForValue.build();
+          onChanged();
+        } else {
+          responseCompressedBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+       */
+      public Builder mergeResponseCompressed(com.google.protobuf.BoolValue value) {
+        if (responseCompressedBuilder_ == null) {
+          if (responseCompressed_ != null) {
+            responseCompressed_ =
+              com.google.protobuf.BoolValue.newBuilder(responseCompressed_).mergeFrom(value).buildPartial();
+          } else {
+            responseCompressed_ = value;
+          }
+          onChanged();
+        } else {
+          responseCompressedBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+       */
+      public Builder clearResponseCompressed() {
+        if (responseCompressedBuilder_ == null) {
+          responseCompressed_ = null;
+          onChanged();
+        } else {
+          responseCompressed_ = null;
+          responseCompressedBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+       */
+      public com.google.protobuf.BoolValue.Builder getResponseCompressedBuilder() {
+        
+        onChanged();
+        return getResponseCompressedFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+       */
+      public com.google.protobuf.BoolValueOrBuilder getResponseCompressedOrBuilder() {
+        if (responseCompressedBuilder_ != null) {
+          return responseCompressedBuilder_.getMessageOrBuilder();
+        } else {
+          return responseCompressed_ == null ?
+              com.google.protobuf.BoolValue.getDefaultInstance() : responseCompressed_;
+        }
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue response_compressed = 6;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          com.google.protobuf.BoolValue, com.google.protobuf.BoolValue.Builder, com.google.protobuf.BoolValueOrBuilder> 
+          getResponseCompressedFieldBuilder() {
+        if (responseCompressedBuilder_ == null) {
+          responseCompressedBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              com.google.protobuf.BoolValue, com.google.protobuf.BoolValue.Builder, com.google.protobuf.BoolValueOrBuilder>(
+                  getResponseCompressed(),
+                  getParentForChildren(),
+                  isClean());
+          responseCompressed_ = null;
+        }
+        return responseCompressedBuilder_;
+      }
+
+      private io.grpc.testing.integration.Messages.EchoStatus responseStatus_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.EchoStatus, io.grpc.testing.integration.Messages.EchoStatus.Builder, io.grpc.testing.integration.Messages.EchoStatusOrBuilder> responseStatusBuilder_;
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public boolean hasResponseStatus() {
+        return responseStatusBuilder_ != null || responseStatus_ != null;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public io.grpc.testing.integration.Messages.EchoStatus getResponseStatus() {
+        if (responseStatusBuilder_ == null) {
+          return responseStatus_ == null ? io.grpc.testing.integration.Messages.EchoStatus.getDefaultInstance() : responseStatus_;
+        } else {
+          return responseStatusBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder setResponseStatus(io.grpc.testing.integration.Messages.EchoStatus value) {
+        if (responseStatusBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          responseStatus_ = value;
+          onChanged();
+        } else {
+          responseStatusBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder setResponseStatus(
+          io.grpc.testing.integration.Messages.EchoStatus.Builder builderForValue) {
+        if (responseStatusBuilder_ == null) {
+          responseStatus_ = builderForValue.build();
+          onChanged();
+        } else {
+          responseStatusBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder mergeResponseStatus(io.grpc.testing.integration.Messages.EchoStatus value) {
+        if (responseStatusBuilder_ == null) {
+          if (responseStatus_ != null) {
+            responseStatus_ =
+              io.grpc.testing.integration.Messages.EchoStatus.newBuilder(responseStatus_).mergeFrom(value).buildPartial();
+          } else {
+            responseStatus_ = value;
+          }
+          onChanged();
+        } else {
+          responseStatusBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder clearResponseStatus() {
+        if (responseStatusBuilder_ == null) {
+          responseStatus_ = null;
+          onChanged();
+        } else {
+          responseStatus_ = null;
+          responseStatusBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public io.grpc.testing.integration.Messages.EchoStatus.Builder getResponseStatusBuilder() {
+        
+        onChanged();
+        return getResponseStatusFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public io.grpc.testing.integration.Messages.EchoStatusOrBuilder getResponseStatusOrBuilder() {
+        if (responseStatusBuilder_ != null) {
+          return responseStatusBuilder_.getMessageOrBuilder();
+        } else {
+          return responseStatus_ == null ?
+              io.grpc.testing.integration.Messages.EchoStatus.getDefaultInstance() : responseStatus_;
+        }
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.EchoStatus, io.grpc.testing.integration.Messages.EchoStatus.Builder, io.grpc.testing.integration.Messages.EchoStatusOrBuilder> 
+          getResponseStatusFieldBuilder() {
+        if (responseStatusBuilder_ == null) {
+          responseStatusBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.testing.integration.Messages.EchoStatus, io.grpc.testing.integration.Messages.EchoStatus.Builder, io.grpc.testing.integration.Messages.EchoStatusOrBuilder>(
+                  getResponseStatus(),
+                  getParentForChildren(),
+                  isClean());
+          responseStatus_ = null;
+        }
+        return responseStatusBuilder_;
+      }
+
+      private com.google.protobuf.BoolValue expectCompressed_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          com.google.protobuf.BoolValue, com.google.protobuf.BoolValue.Builder, com.google.protobuf.BoolValueOrBuilder> expectCompressedBuilder_;
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+       */
+      public boolean hasExpectCompressed() {
+        return expectCompressedBuilder_ != null || expectCompressed_ != null;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+       */
+      public com.google.protobuf.BoolValue getExpectCompressed() {
+        if (expectCompressedBuilder_ == null) {
+          return expectCompressed_ == null ? com.google.protobuf.BoolValue.getDefaultInstance() : expectCompressed_;
+        } else {
+          return expectCompressedBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+       */
+      public Builder setExpectCompressed(com.google.protobuf.BoolValue value) {
+        if (expectCompressedBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          expectCompressed_ = value;
+          onChanged();
+        } else {
+          expectCompressedBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+       */
+      public Builder setExpectCompressed(
+          com.google.protobuf.BoolValue.Builder builderForValue) {
+        if (expectCompressedBuilder_ == null) {
+          expectCompressed_ = builderForValue.build();
+          onChanged();
+        } else {
+          expectCompressedBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+       */
+      public Builder mergeExpectCompressed(com.google.protobuf.BoolValue value) {
+        if (expectCompressedBuilder_ == null) {
+          if (expectCompressed_ != null) {
+            expectCompressed_ =
+              com.google.protobuf.BoolValue.newBuilder(expectCompressed_).mergeFrom(value).buildPartial();
+          } else {
+            expectCompressed_ = value;
+          }
+          onChanged();
+        } else {
+          expectCompressedBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+       */
+      public Builder clearExpectCompressed() {
+        if (expectCompressedBuilder_ == null) {
+          expectCompressed_ = null;
+          onChanged();
+        } else {
+          expectCompressed_ = null;
+          expectCompressedBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+       */
+      public com.google.protobuf.BoolValue.Builder getExpectCompressedBuilder() {
+        
+        onChanged();
+        return getExpectCompressedFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+       */
+      public com.google.protobuf.BoolValueOrBuilder getExpectCompressedOrBuilder() {
+        if (expectCompressedBuilder_ != null) {
+          return expectCompressedBuilder_.getMessageOrBuilder();
+        } else {
+          return expectCompressed_ == null ?
+              com.google.protobuf.BoolValue.getDefaultInstance() : expectCompressed_;
+        }
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 8;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          com.google.protobuf.BoolValue, com.google.protobuf.BoolValue.Builder, com.google.protobuf.BoolValueOrBuilder> 
+          getExpectCompressedFieldBuilder() {
+        if (expectCompressedBuilder_ == null) {
+          expectCompressedBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              com.google.protobuf.BoolValue, com.google.protobuf.BoolValue.Builder, com.google.protobuf.BoolValueOrBuilder>(
+                  getExpectCompressed(),
+                  getParentForChildren(),
+                  isClean());
+          expectCompressed_ = null;
+        }
+        return expectCompressedBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.SimpleRequest)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.SimpleRequest)
+    private static final io.grpc.testing.integration.Messages.SimpleRequest DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Messages.SimpleRequest();
+    }
+
+    public static io.grpc.testing.integration.Messages.SimpleRequest getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<SimpleRequest>
+        PARSER = new com.google.protobuf.AbstractParser<SimpleRequest>() {
+      public SimpleRequest parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new SimpleRequest(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<SimpleRequest> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<SimpleRequest> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Messages.SimpleRequest getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface SimpleResponseOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.SimpleResponse)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Payload to increase message size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    boolean hasPayload();
+    /**
+     * <pre>
+     * Payload to increase message size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    io.grpc.testing.integration.Messages.Payload getPayload();
+    /**
+     * <pre>
+     * Payload to increase message size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder();
+
+    /**
+     * <pre>
+     * The user the request came from, for verifying authentication was
+     * successful when the client expected it.
+     * </pre>
+     *
+     * <code>string username = 2;</code>
+     */
+    java.lang.String getUsername();
+    /**
+     * <pre>
+     * The user the request came from, for verifying authentication was
+     * successful when the client expected it.
+     * </pre>
+     *
+     * <code>string username = 2;</code>
+     */
+    com.google.protobuf.ByteString
+        getUsernameBytes();
+
+    /**
+     * <pre>
+     * OAuth scope.
+     * </pre>
+     *
+     * <code>string oauth_scope = 3;</code>
+     */
+    java.lang.String getOauthScope();
+    /**
+     * <pre>
+     * OAuth scope.
+     * </pre>
+     *
+     * <code>string oauth_scope = 3;</code>
+     */
+    com.google.protobuf.ByteString
+        getOauthScopeBytes();
+  }
+  /**
+   * <pre>
+   * Unary response, as configured by the request.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.SimpleResponse}
+   */
+  public  static final class SimpleResponse extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.SimpleResponse)
+      SimpleResponseOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use SimpleResponse.newBuilder() to construct.
+    private SimpleResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private SimpleResponse() {
+      username_ = "";
+      oauthScope_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private SimpleResponse(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.testing.integration.Messages.Payload.Builder subBuilder = null;
+              if (payload_ != null) {
+                subBuilder = payload_.toBuilder();
+              }
+              payload_ = input.readMessage(io.grpc.testing.integration.Messages.Payload.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(payload_);
+                payload_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 18: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              username_ = s;
+              break;
+            }
+            case 26: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              oauthScope_ = s;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Messages.SimpleResponse.class, io.grpc.testing.integration.Messages.SimpleResponse.Builder.class);
+    }
+
+    public static final int PAYLOAD_FIELD_NUMBER = 1;
+    private io.grpc.testing.integration.Messages.Payload payload_;
+    /**
+     * <pre>
+     * Payload to increase message size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public boolean hasPayload() {
+      return payload_ != null;
+    }
+    /**
+     * <pre>
+     * Payload to increase message size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public io.grpc.testing.integration.Messages.Payload getPayload() {
+      return payload_ == null ? io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+    }
+    /**
+     * <pre>
+     * Payload to increase message size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+      return getPayload();
+    }
+
+    public static final int USERNAME_FIELD_NUMBER = 2;
+    private volatile java.lang.Object username_;
+    /**
+     * <pre>
+     * The user the request came from, for verifying authentication was
+     * successful when the client expected it.
+     * </pre>
+     *
+     * <code>string username = 2;</code>
+     */
+    public java.lang.String getUsername() {
+      java.lang.Object ref = username_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        username_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * The user the request came from, for verifying authentication was
+     * successful when the client expected it.
+     * </pre>
+     *
+     * <code>string username = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getUsernameBytes() {
+      java.lang.Object ref = username_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        username_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int OAUTH_SCOPE_FIELD_NUMBER = 3;
+    private volatile java.lang.Object oauthScope_;
+    /**
+     * <pre>
+     * OAuth scope.
+     * </pre>
+     *
+     * <code>string oauth_scope = 3;</code>
+     */
+    public java.lang.String getOauthScope() {
+      java.lang.Object ref = oauthScope_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        oauthScope_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * OAuth scope.
+     * </pre>
+     *
+     * <code>string oauth_scope = 3;</code>
+     */
+    public com.google.protobuf.ByteString
+        getOauthScopeBytes() {
+      java.lang.Object ref = oauthScope_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        oauthScope_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (payload_ != null) {
+        output.writeMessage(1, getPayload());
+      }
+      if (!getUsernameBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, username_);
+      }
+      if (!getOauthScopeBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 3, oauthScope_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (payload_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, getPayload());
+      }
+      if (!getUsernameBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, username_);
+      }
+      if (!getOauthScopeBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, oauthScope_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Messages.SimpleResponse)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Messages.SimpleResponse other = (io.grpc.testing.integration.Messages.SimpleResponse) obj;
+
+      boolean result = true;
+      result = result && (hasPayload() == other.hasPayload());
+      if (hasPayload()) {
+        result = result && getPayload()
+            .equals(other.getPayload());
+      }
+      result = result && getUsername()
+          .equals(other.getUsername());
+      result = result && getOauthScope()
+          .equals(other.getOauthScope());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (hasPayload()) {
+        hash = (37 * hash) + PAYLOAD_FIELD_NUMBER;
+        hash = (53 * hash) + getPayload().hashCode();
+      }
+      hash = (37 * hash) + USERNAME_FIELD_NUMBER;
+      hash = (53 * hash) + getUsername().hashCode();
+      hash = (37 * hash) + OAUTH_SCOPE_FIELD_NUMBER;
+      hash = (53 * hash) + getOauthScope().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Messages.SimpleResponse parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleResponse parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleResponse parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleResponse parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleResponse parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleResponse parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleResponse parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleResponse parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleResponse parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleResponse parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleResponse parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleResponse parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Messages.SimpleResponse prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Unary response, as configured by the request.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.SimpleResponse}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.SimpleResponse)
+        io.grpc.testing.integration.Messages.SimpleResponseOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleResponse_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleResponse_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Messages.SimpleResponse.class, io.grpc.testing.integration.Messages.SimpleResponse.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Messages.SimpleResponse.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+        username_ = "";
+
+        oauthScope_ = "";
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleResponse_descriptor;
+      }
+
+      public io.grpc.testing.integration.Messages.SimpleResponse getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Messages.SimpleResponse.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Messages.SimpleResponse build() {
+        io.grpc.testing.integration.Messages.SimpleResponse result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Messages.SimpleResponse buildPartial() {
+        io.grpc.testing.integration.Messages.SimpleResponse result = new io.grpc.testing.integration.Messages.SimpleResponse(this);
+        if (payloadBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = payloadBuilder_.build();
+        }
+        result.username_ = username_;
+        result.oauthScope_ = oauthScope_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Messages.SimpleResponse) {
+          return mergeFrom((io.grpc.testing.integration.Messages.SimpleResponse)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Messages.SimpleResponse other) {
+        if (other == io.grpc.testing.integration.Messages.SimpleResponse.getDefaultInstance()) return this;
+        if (other.hasPayload()) {
+          mergePayload(other.getPayload());
+        }
+        if (!other.getUsername().isEmpty()) {
+          username_ = other.username_;
+          onChanged();
+        }
+        if (!other.getOauthScope().isEmpty()) {
+          oauthScope_ = other.oauthScope_;
+          onChanged();
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Messages.SimpleResponse parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Messages.SimpleResponse) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private io.grpc.testing.integration.Messages.Payload payload_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder> payloadBuilder_;
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public boolean hasPayload() {
+        return payloadBuilder_ != null || payload_ != null;
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.testing.integration.Messages.Payload getPayload() {
+        if (payloadBuilder_ == null) {
+          return payload_ == null ? io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+        } else {
+          return payloadBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder setPayload(io.grpc.testing.integration.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payload_ = value;
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder setPayload(
+          io.grpc.testing.integration.Messages.Payload.Builder builderForValue) {
+        if (payloadBuilder_ == null) {
+          payload_ = builderForValue.build();
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder mergePayload(io.grpc.testing.integration.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (payload_ != null) {
+            payload_ =
+              io.grpc.testing.integration.Messages.Payload.newBuilder(payload_).mergeFrom(value).buildPartial();
+          } else {
+            payload_ = value;
+          }
+          onChanged();
+        } else {
+          payloadBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder clearPayload() {
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+          onChanged();
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.testing.integration.Messages.Payload.Builder getPayloadBuilder() {
+        
+        onChanged();
+        return getPayloadFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+        if (payloadBuilder_ != null) {
+          return payloadBuilder_.getMessageOrBuilder();
+        } else {
+          return payload_ == null ?
+              io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+        }
+      }
+      /**
+       * <pre>
+       * Payload to increase message size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder> 
+          getPayloadFieldBuilder() {
+        if (payloadBuilder_ == null) {
+          payloadBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder>(
+                  getPayload(),
+                  getParentForChildren(),
+                  isClean());
+          payload_ = null;
+        }
+        return payloadBuilder_;
+      }
+
+      private java.lang.Object username_ = "";
+      /**
+       * <pre>
+       * The user the request came from, for verifying authentication was
+       * successful when the client expected it.
+       * </pre>
+       *
+       * <code>string username = 2;</code>
+       */
+      public java.lang.String getUsername() {
+        java.lang.Object ref = username_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          username_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The user the request came from, for verifying authentication was
+       * successful when the client expected it.
+       * </pre>
+       *
+       * <code>string username = 2;</code>
+       */
+      public com.google.protobuf.ByteString
+          getUsernameBytes() {
+        java.lang.Object ref = username_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          username_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The user the request came from, for verifying authentication was
+       * successful when the client expected it.
+       * </pre>
+       *
+       * <code>string username = 2;</code>
+       */
+      public Builder setUsername(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        username_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The user the request came from, for verifying authentication was
+       * successful when the client expected it.
+       * </pre>
+       *
+       * <code>string username = 2;</code>
+       */
+      public Builder clearUsername() {
+        
+        username_ = getDefaultInstance().getUsername();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The user the request came from, for verifying authentication was
+       * successful when the client expected it.
+       * </pre>
+       *
+       * <code>string username = 2;</code>
+       */
+      public Builder setUsernameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        username_ = value;
+        onChanged();
+        return this;
+      }
+
+      private java.lang.Object oauthScope_ = "";
+      /**
+       * <pre>
+       * OAuth scope.
+       * </pre>
+       *
+       * <code>string oauth_scope = 3;</code>
+       */
+      public java.lang.String getOauthScope() {
+        java.lang.Object ref = oauthScope_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          oauthScope_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * OAuth scope.
+       * </pre>
+       *
+       * <code>string oauth_scope = 3;</code>
+       */
+      public com.google.protobuf.ByteString
+          getOauthScopeBytes() {
+        java.lang.Object ref = oauthScope_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          oauthScope_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * OAuth scope.
+       * </pre>
+       *
+       * <code>string oauth_scope = 3;</code>
+       */
+      public Builder setOauthScope(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        oauthScope_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * OAuth scope.
+       * </pre>
+       *
+       * <code>string oauth_scope = 3;</code>
+       */
+      public Builder clearOauthScope() {
+        
+        oauthScope_ = getDefaultInstance().getOauthScope();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * OAuth scope.
+       * </pre>
+       *
+       * <code>string oauth_scope = 3;</code>
+       */
+      public Builder setOauthScopeBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        oauthScope_ = value;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.SimpleResponse)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.SimpleResponse)
+    private static final io.grpc.testing.integration.Messages.SimpleResponse DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Messages.SimpleResponse();
+    }
+
+    public static io.grpc.testing.integration.Messages.SimpleResponse getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<SimpleResponse>
+        PARSER = new com.google.protobuf.AbstractParser<SimpleResponse>() {
+      public SimpleResponse parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new SimpleResponse(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<SimpleResponse> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<SimpleResponse> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Messages.SimpleResponse getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface SimpleContextOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.SimpleContext)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>string value = 1;</code>
+     */
+    java.lang.String getValue();
+    /**
+     * <code>string value = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getValueBytes();
+  }
+  /**
+   * Protobuf type {@code grpc.testing.SimpleContext}
+   */
+  public  static final class SimpleContext extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.SimpleContext)
+      SimpleContextOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use SimpleContext.newBuilder() to construct.
+    private SimpleContext(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private SimpleContext() {
+      value_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private SimpleContext(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              value_ = s;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleContext_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleContext_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Messages.SimpleContext.class, io.grpc.testing.integration.Messages.SimpleContext.Builder.class);
+    }
+
+    public static final int VALUE_FIELD_NUMBER = 1;
+    private volatile java.lang.Object value_;
+    /**
+     * <code>string value = 1;</code>
+     */
+    public java.lang.String getValue() {
+      java.lang.Object ref = value_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        value_ = s;
+        return s;
+      }
+    }
+    /**
+     * <code>string value = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getValueBytes() {
+      java.lang.Object ref = value_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        value_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!getValueBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, value_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!getValueBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, value_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Messages.SimpleContext)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Messages.SimpleContext other = (io.grpc.testing.integration.Messages.SimpleContext) obj;
+
+      boolean result = true;
+      result = result && getValue()
+          .equals(other.getValue());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + VALUE_FIELD_NUMBER;
+      hash = (53 * hash) + getValue().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Messages.SimpleContext parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleContext parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleContext parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleContext parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleContext parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleContext parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleContext parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleContext parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleContext parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleContext parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleContext parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.SimpleContext parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Messages.SimpleContext prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.SimpleContext}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.SimpleContext)
+        io.grpc.testing.integration.Messages.SimpleContextOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleContext_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleContext_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Messages.SimpleContext.class, io.grpc.testing.integration.Messages.SimpleContext.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Messages.SimpleContext.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        value_ = "";
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_SimpleContext_descriptor;
+      }
+
+      public io.grpc.testing.integration.Messages.SimpleContext getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Messages.SimpleContext.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Messages.SimpleContext build() {
+        io.grpc.testing.integration.Messages.SimpleContext result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Messages.SimpleContext buildPartial() {
+        io.grpc.testing.integration.Messages.SimpleContext result = new io.grpc.testing.integration.Messages.SimpleContext(this);
+        result.value_ = value_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Messages.SimpleContext) {
+          return mergeFrom((io.grpc.testing.integration.Messages.SimpleContext)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Messages.SimpleContext other) {
+        if (other == io.grpc.testing.integration.Messages.SimpleContext.getDefaultInstance()) return this;
+        if (!other.getValue().isEmpty()) {
+          value_ = other.value_;
+          onChanged();
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Messages.SimpleContext parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Messages.SimpleContext) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private java.lang.Object value_ = "";
+      /**
+       * <code>string value = 1;</code>
+       */
+      public java.lang.String getValue() {
+        java.lang.Object ref = value_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          value_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <code>string value = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getValueBytes() {
+        java.lang.Object ref = value_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          value_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <code>string value = 1;</code>
+       */
+      public Builder setValue(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        value_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string value = 1;</code>
+       */
+      public Builder clearValue() {
+        
+        value_ = getDefaultInstance().getValue();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string value = 1;</code>
+       */
+      public Builder setValueBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        value_ = value;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.SimpleContext)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.SimpleContext)
+    private static final io.grpc.testing.integration.Messages.SimpleContext DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Messages.SimpleContext();
+    }
+
+    public static io.grpc.testing.integration.Messages.SimpleContext getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<SimpleContext>
+        PARSER = new com.google.protobuf.AbstractParser<SimpleContext>() {
+      public SimpleContext parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new SimpleContext(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<SimpleContext> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<SimpleContext> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Messages.SimpleContext getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface StreamingInputCallRequestOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.StreamingInputCallRequest)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    boolean hasPayload();
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    io.grpc.testing.integration.Messages.Payload getPayload();
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder();
+
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed. This field
+     * is "nullable" in order to interoperate seamlessly with servers not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the request's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+     */
+    boolean hasExpectCompressed();
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed. This field
+     * is "nullable" in order to interoperate seamlessly with servers not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the request's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+     */
+    com.google.protobuf.BoolValue getExpectCompressed();
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed. This field
+     * is "nullable" in order to interoperate seamlessly with servers not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the request's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+     */
+    com.google.protobuf.BoolValueOrBuilder getExpectCompressedOrBuilder();
+  }
+  /**
+   * <pre>
+   * Client-streaming request.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.StreamingInputCallRequest}
+   */
+  public  static final class StreamingInputCallRequest extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.StreamingInputCallRequest)
+      StreamingInputCallRequestOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use StreamingInputCallRequest.newBuilder() to construct.
+    private StreamingInputCallRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private StreamingInputCallRequest() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private StreamingInputCallRequest(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.testing.integration.Messages.Payload.Builder subBuilder = null;
+              if (payload_ != null) {
+                subBuilder = payload_.toBuilder();
+              }
+              payload_ = input.readMessage(io.grpc.testing.integration.Messages.Payload.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(payload_);
+                payload_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 18: {
+              com.google.protobuf.BoolValue.Builder subBuilder = null;
+              if (expectCompressed_ != null) {
+                subBuilder = expectCompressed_.toBuilder();
+              }
+              expectCompressed_ = input.readMessage(com.google.protobuf.BoolValue.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(expectCompressed_);
+                expectCompressed_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingInputCallRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingInputCallRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Messages.StreamingInputCallRequest.class, io.grpc.testing.integration.Messages.StreamingInputCallRequest.Builder.class);
+    }
+
+    public static final int PAYLOAD_FIELD_NUMBER = 1;
+    private io.grpc.testing.integration.Messages.Payload payload_;
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public boolean hasPayload() {
+      return payload_ != null;
+    }
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public io.grpc.testing.integration.Messages.Payload getPayload() {
+      return payload_ == null ? io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+    }
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+      return getPayload();
+    }
+
+    public static final int EXPECT_COMPRESSED_FIELD_NUMBER = 2;
+    private com.google.protobuf.BoolValue expectCompressed_;
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed. This field
+     * is "nullable" in order to interoperate seamlessly with servers not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the request's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+     */
+    public boolean hasExpectCompressed() {
+      return expectCompressed_ != null;
+    }
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed. This field
+     * is "nullable" in order to interoperate seamlessly with servers not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the request's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+     */
+    public com.google.protobuf.BoolValue getExpectCompressed() {
+      return expectCompressed_ == null ? com.google.protobuf.BoolValue.getDefaultInstance() : expectCompressed_;
+    }
+    /**
+     * <pre>
+     * Whether the server should expect this request to be compressed. This field
+     * is "nullable" in order to interoperate seamlessly with servers not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the request's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+     */
+    public com.google.protobuf.BoolValueOrBuilder getExpectCompressedOrBuilder() {
+      return getExpectCompressed();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (payload_ != null) {
+        output.writeMessage(1, getPayload());
+      }
+      if (expectCompressed_ != null) {
+        output.writeMessage(2, getExpectCompressed());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (payload_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, getPayload());
+      }
+      if (expectCompressed_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, getExpectCompressed());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Messages.StreamingInputCallRequest)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Messages.StreamingInputCallRequest other = (io.grpc.testing.integration.Messages.StreamingInputCallRequest) obj;
+
+      boolean result = true;
+      result = result && (hasPayload() == other.hasPayload());
+      if (hasPayload()) {
+        result = result && getPayload()
+            .equals(other.getPayload());
+      }
+      result = result && (hasExpectCompressed() == other.hasExpectCompressed());
+      if (hasExpectCompressed()) {
+        result = result && getExpectCompressed()
+            .equals(other.getExpectCompressed());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (hasPayload()) {
+        hash = (37 * hash) + PAYLOAD_FIELD_NUMBER;
+        hash = (53 * hash) + getPayload().hashCode();
+      }
+      if (hasExpectCompressed()) {
+        hash = (37 * hash) + EXPECT_COMPRESSED_FIELD_NUMBER;
+        hash = (53 * hash) + getExpectCompressed().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Messages.StreamingInputCallRequest parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallRequest parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallRequest parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallRequest parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallRequest parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallRequest parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallRequest parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallRequest parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallRequest parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallRequest parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallRequest parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallRequest parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Messages.StreamingInputCallRequest prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Client-streaming request.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.StreamingInputCallRequest}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.StreamingInputCallRequest)
+        io.grpc.testing.integration.Messages.StreamingInputCallRequestOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingInputCallRequest_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingInputCallRequest_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Messages.StreamingInputCallRequest.class, io.grpc.testing.integration.Messages.StreamingInputCallRequest.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Messages.StreamingInputCallRequest.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+        if (expectCompressedBuilder_ == null) {
+          expectCompressed_ = null;
+        } else {
+          expectCompressed_ = null;
+          expectCompressedBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingInputCallRequest_descriptor;
+      }
+
+      public io.grpc.testing.integration.Messages.StreamingInputCallRequest getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Messages.StreamingInputCallRequest.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Messages.StreamingInputCallRequest build() {
+        io.grpc.testing.integration.Messages.StreamingInputCallRequest result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Messages.StreamingInputCallRequest buildPartial() {
+        io.grpc.testing.integration.Messages.StreamingInputCallRequest result = new io.grpc.testing.integration.Messages.StreamingInputCallRequest(this);
+        if (payloadBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = payloadBuilder_.build();
+        }
+        if (expectCompressedBuilder_ == null) {
+          result.expectCompressed_ = expectCompressed_;
+        } else {
+          result.expectCompressed_ = expectCompressedBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Messages.StreamingInputCallRequest) {
+          return mergeFrom((io.grpc.testing.integration.Messages.StreamingInputCallRequest)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Messages.StreamingInputCallRequest other) {
+        if (other == io.grpc.testing.integration.Messages.StreamingInputCallRequest.getDefaultInstance()) return this;
+        if (other.hasPayload()) {
+          mergePayload(other.getPayload());
+        }
+        if (other.hasExpectCompressed()) {
+          mergeExpectCompressed(other.getExpectCompressed());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Messages.StreamingInputCallRequest parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Messages.StreamingInputCallRequest) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private io.grpc.testing.integration.Messages.Payload payload_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder> payloadBuilder_;
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public boolean hasPayload() {
+        return payloadBuilder_ != null || payload_ != null;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.testing.integration.Messages.Payload getPayload() {
+        if (payloadBuilder_ == null) {
+          return payload_ == null ? io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+        } else {
+          return payloadBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder setPayload(io.grpc.testing.integration.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payload_ = value;
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder setPayload(
+          io.grpc.testing.integration.Messages.Payload.Builder builderForValue) {
+        if (payloadBuilder_ == null) {
+          payload_ = builderForValue.build();
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder mergePayload(io.grpc.testing.integration.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (payload_ != null) {
+            payload_ =
+              io.grpc.testing.integration.Messages.Payload.newBuilder(payload_).mergeFrom(value).buildPartial();
+          } else {
+            payload_ = value;
+          }
+          onChanged();
+        } else {
+          payloadBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder clearPayload() {
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+          onChanged();
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.testing.integration.Messages.Payload.Builder getPayloadBuilder() {
+        
+        onChanged();
+        return getPayloadFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+        if (payloadBuilder_ != null) {
+          return payloadBuilder_.getMessageOrBuilder();
+        } else {
+          return payload_ == null ?
+              io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+        }
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder> 
+          getPayloadFieldBuilder() {
+        if (payloadBuilder_ == null) {
+          payloadBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder>(
+                  getPayload(),
+                  getParentForChildren(),
+                  isClean());
+          payload_ = null;
+        }
+        return payloadBuilder_;
+      }
+
+      private com.google.protobuf.BoolValue expectCompressed_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          com.google.protobuf.BoolValue, com.google.protobuf.BoolValue.Builder, com.google.protobuf.BoolValueOrBuilder> expectCompressedBuilder_;
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+       */
+      public boolean hasExpectCompressed() {
+        return expectCompressedBuilder_ != null || expectCompressed_ != null;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+       */
+      public com.google.protobuf.BoolValue getExpectCompressed() {
+        if (expectCompressedBuilder_ == null) {
+          return expectCompressed_ == null ? com.google.protobuf.BoolValue.getDefaultInstance() : expectCompressed_;
+        } else {
+          return expectCompressedBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+       */
+      public Builder setExpectCompressed(com.google.protobuf.BoolValue value) {
+        if (expectCompressedBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          expectCompressed_ = value;
+          onChanged();
+        } else {
+          expectCompressedBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+       */
+      public Builder setExpectCompressed(
+          com.google.protobuf.BoolValue.Builder builderForValue) {
+        if (expectCompressedBuilder_ == null) {
+          expectCompressed_ = builderForValue.build();
+          onChanged();
+        } else {
+          expectCompressedBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+       */
+      public Builder mergeExpectCompressed(com.google.protobuf.BoolValue value) {
+        if (expectCompressedBuilder_ == null) {
+          if (expectCompressed_ != null) {
+            expectCompressed_ =
+              com.google.protobuf.BoolValue.newBuilder(expectCompressed_).mergeFrom(value).buildPartial();
+          } else {
+            expectCompressed_ = value;
+          }
+          onChanged();
+        } else {
+          expectCompressedBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+       */
+      public Builder clearExpectCompressed() {
+        if (expectCompressedBuilder_ == null) {
+          expectCompressed_ = null;
+          onChanged();
+        } else {
+          expectCompressed_ = null;
+          expectCompressedBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+       */
+      public com.google.protobuf.BoolValue.Builder getExpectCompressedBuilder() {
+        
+        onChanged();
+        return getExpectCompressedFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+       */
+      public com.google.protobuf.BoolValueOrBuilder getExpectCompressedOrBuilder() {
+        if (expectCompressedBuilder_ != null) {
+          return expectCompressedBuilder_.getMessageOrBuilder();
+        } else {
+          return expectCompressed_ == null ?
+              com.google.protobuf.BoolValue.getDefaultInstance() : expectCompressed_;
+        }
+      }
+      /**
+       * <pre>
+       * Whether the server should expect this request to be compressed. This field
+       * is "nullable" in order to interoperate seamlessly with servers not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the request's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue expect_compressed = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          com.google.protobuf.BoolValue, com.google.protobuf.BoolValue.Builder, com.google.protobuf.BoolValueOrBuilder> 
+          getExpectCompressedFieldBuilder() {
+        if (expectCompressedBuilder_ == null) {
+          expectCompressedBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              com.google.protobuf.BoolValue, com.google.protobuf.BoolValue.Builder, com.google.protobuf.BoolValueOrBuilder>(
+                  getExpectCompressed(),
+                  getParentForChildren(),
+                  isClean());
+          expectCompressed_ = null;
+        }
+        return expectCompressedBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.StreamingInputCallRequest)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.StreamingInputCallRequest)
+    private static final io.grpc.testing.integration.Messages.StreamingInputCallRequest DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Messages.StreamingInputCallRequest();
+    }
+
+    public static io.grpc.testing.integration.Messages.StreamingInputCallRequest getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<StreamingInputCallRequest>
+        PARSER = new com.google.protobuf.AbstractParser<StreamingInputCallRequest>() {
+      public StreamingInputCallRequest parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new StreamingInputCallRequest(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<StreamingInputCallRequest> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<StreamingInputCallRequest> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Messages.StreamingInputCallRequest getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface StreamingInputCallResponseOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.StreamingInputCallResponse)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Aggregated size of payloads received from the client.
+     * </pre>
+     *
+     * <code>int32 aggregated_payload_size = 1;</code>
+     */
+    int getAggregatedPayloadSize();
+  }
+  /**
+   * <pre>
+   * Client-streaming response.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.StreamingInputCallResponse}
+   */
+  public  static final class StreamingInputCallResponse extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.StreamingInputCallResponse)
+      StreamingInputCallResponseOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use StreamingInputCallResponse.newBuilder() to construct.
+    private StreamingInputCallResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private StreamingInputCallResponse() {
+      aggregatedPayloadSize_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private StreamingInputCallResponse(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              aggregatedPayloadSize_ = input.readInt32();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingInputCallResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingInputCallResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Messages.StreamingInputCallResponse.class, io.grpc.testing.integration.Messages.StreamingInputCallResponse.Builder.class);
+    }
+
+    public static final int AGGREGATED_PAYLOAD_SIZE_FIELD_NUMBER = 1;
+    private int aggregatedPayloadSize_;
+    /**
+     * <pre>
+     * Aggregated size of payloads received from the client.
+     * </pre>
+     *
+     * <code>int32 aggregated_payload_size = 1;</code>
+     */
+    public int getAggregatedPayloadSize() {
+      return aggregatedPayloadSize_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (aggregatedPayloadSize_ != 0) {
+        output.writeInt32(1, aggregatedPayloadSize_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (aggregatedPayloadSize_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1, aggregatedPayloadSize_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Messages.StreamingInputCallResponse)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Messages.StreamingInputCallResponse other = (io.grpc.testing.integration.Messages.StreamingInputCallResponse) obj;
+
+      boolean result = true;
+      result = result && (getAggregatedPayloadSize()
+          == other.getAggregatedPayloadSize());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + AGGREGATED_PAYLOAD_SIZE_FIELD_NUMBER;
+      hash = (53 * hash) + getAggregatedPayloadSize();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Messages.StreamingInputCallResponse parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallResponse parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallResponse parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallResponse parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallResponse parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallResponse parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallResponse parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallResponse parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallResponse parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallResponse parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallResponse parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingInputCallResponse parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Messages.StreamingInputCallResponse prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Client-streaming response.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.StreamingInputCallResponse}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.StreamingInputCallResponse)
+        io.grpc.testing.integration.Messages.StreamingInputCallResponseOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingInputCallResponse_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingInputCallResponse_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Messages.StreamingInputCallResponse.class, io.grpc.testing.integration.Messages.StreamingInputCallResponse.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Messages.StreamingInputCallResponse.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        aggregatedPayloadSize_ = 0;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingInputCallResponse_descriptor;
+      }
+
+      public io.grpc.testing.integration.Messages.StreamingInputCallResponse getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Messages.StreamingInputCallResponse.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Messages.StreamingInputCallResponse build() {
+        io.grpc.testing.integration.Messages.StreamingInputCallResponse result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Messages.StreamingInputCallResponse buildPartial() {
+        io.grpc.testing.integration.Messages.StreamingInputCallResponse result = new io.grpc.testing.integration.Messages.StreamingInputCallResponse(this);
+        result.aggregatedPayloadSize_ = aggregatedPayloadSize_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Messages.StreamingInputCallResponse) {
+          return mergeFrom((io.grpc.testing.integration.Messages.StreamingInputCallResponse)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Messages.StreamingInputCallResponse other) {
+        if (other == io.grpc.testing.integration.Messages.StreamingInputCallResponse.getDefaultInstance()) return this;
+        if (other.getAggregatedPayloadSize() != 0) {
+          setAggregatedPayloadSize(other.getAggregatedPayloadSize());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Messages.StreamingInputCallResponse parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Messages.StreamingInputCallResponse) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int aggregatedPayloadSize_ ;
+      /**
+       * <pre>
+       * Aggregated size of payloads received from the client.
+       * </pre>
+       *
+       * <code>int32 aggregated_payload_size = 1;</code>
+       */
+      public int getAggregatedPayloadSize() {
+        return aggregatedPayloadSize_;
+      }
+      /**
+       * <pre>
+       * Aggregated size of payloads received from the client.
+       * </pre>
+       *
+       * <code>int32 aggregated_payload_size = 1;</code>
+       */
+      public Builder setAggregatedPayloadSize(int value) {
+        
+        aggregatedPayloadSize_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Aggregated size of payloads received from the client.
+       * </pre>
+       *
+       * <code>int32 aggregated_payload_size = 1;</code>
+       */
+      public Builder clearAggregatedPayloadSize() {
+        
+        aggregatedPayloadSize_ = 0;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.StreamingInputCallResponse)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.StreamingInputCallResponse)
+    private static final io.grpc.testing.integration.Messages.StreamingInputCallResponse DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Messages.StreamingInputCallResponse();
+    }
+
+    public static io.grpc.testing.integration.Messages.StreamingInputCallResponse getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<StreamingInputCallResponse>
+        PARSER = new com.google.protobuf.AbstractParser<StreamingInputCallResponse>() {
+      public StreamingInputCallResponse parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new StreamingInputCallResponse(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<StreamingInputCallResponse> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<StreamingInputCallResponse> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Messages.StreamingInputCallResponse getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ResponseParametersOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ResponseParameters)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Desired payload sizes in responses from the server.
+     * </pre>
+     *
+     * <code>int32 size = 1;</code>
+     */
+    int getSize();
+
+    /**
+     * <pre>
+     * Desired interval between consecutive responses in the response stream in
+     * microseconds.
+     * </pre>
+     *
+     * <code>int32 interval_us = 2;</code>
+     */
+    int getIntervalUs();
+
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue compressed = 3;</code>
+     */
+    boolean hasCompressed();
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue compressed = 3;</code>
+     */
+    com.google.protobuf.BoolValue getCompressed();
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue compressed = 3;</code>
+     */
+    com.google.protobuf.BoolValueOrBuilder getCompressedOrBuilder();
+  }
+  /**
+   * <pre>
+   * Configuration for a particular response.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.ResponseParameters}
+   */
+  public  static final class ResponseParameters extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ResponseParameters)
+      ResponseParametersOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ResponseParameters.newBuilder() to construct.
+    private ResponseParameters(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ResponseParameters() {
+      size_ = 0;
+      intervalUs_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ResponseParameters(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              size_ = input.readInt32();
+              break;
+            }
+            case 16: {
+
+              intervalUs_ = input.readInt32();
+              break;
+            }
+            case 26: {
+              com.google.protobuf.BoolValue.Builder subBuilder = null;
+              if (compressed_ != null) {
+                subBuilder = compressed_.toBuilder();
+              }
+              compressed_ = input.readMessage(com.google.protobuf.BoolValue.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(compressed_);
+                compressed_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ResponseParameters_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ResponseParameters_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Messages.ResponseParameters.class, io.grpc.testing.integration.Messages.ResponseParameters.Builder.class);
+    }
+
+    public static final int SIZE_FIELD_NUMBER = 1;
+    private int size_;
+    /**
+     * <pre>
+     * Desired payload sizes in responses from the server.
+     * </pre>
+     *
+     * <code>int32 size = 1;</code>
+     */
+    public int getSize() {
+      return size_;
+    }
+
+    public static final int INTERVAL_US_FIELD_NUMBER = 2;
+    private int intervalUs_;
+    /**
+     * <pre>
+     * Desired interval between consecutive responses in the response stream in
+     * microseconds.
+     * </pre>
+     *
+     * <code>int32 interval_us = 2;</code>
+     */
+    public int getIntervalUs() {
+      return intervalUs_;
+    }
+
+    public static final int COMPRESSED_FIELD_NUMBER = 3;
+    private com.google.protobuf.BoolValue compressed_;
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue compressed = 3;</code>
+     */
+    public boolean hasCompressed() {
+      return compressed_ != null;
+    }
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue compressed = 3;</code>
+     */
+    public com.google.protobuf.BoolValue getCompressed() {
+      return compressed_ == null ? com.google.protobuf.BoolValue.getDefaultInstance() : compressed_;
+    }
+    /**
+     * <pre>
+     * Whether to request the server to compress the response. This field is
+     * "nullable" in order to interoperate seamlessly with clients not able to
+     * implement the full compression tests by introspecting the call to verify
+     * the response's compression status.
+     * </pre>
+     *
+     * <code>.google.protobuf.BoolValue compressed = 3;</code>
+     */
+    public com.google.protobuf.BoolValueOrBuilder getCompressedOrBuilder() {
+      return getCompressed();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (size_ != 0) {
+        output.writeInt32(1, size_);
+      }
+      if (intervalUs_ != 0) {
+        output.writeInt32(2, intervalUs_);
+      }
+      if (compressed_ != null) {
+        output.writeMessage(3, getCompressed());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (size_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1, size_);
+      }
+      if (intervalUs_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(2, intervalUs_);
+      }
+      if (compressed_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(3, getCompressed());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Messages.ResponseParameters)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Messages.ResponseParameters other = (io.grpc.testing.integration.Messages.ResponseParameters) obj;
+
+      boolean result = true;
+      result = result && (getSize()
+          == other.getSize());
+      result = result && (getIntervalUs()
+          == other.getIntervalUs());
+      result = result && (hasCompressed() == other.hasCompressed());
+      if (hasCompressed()) {
+        result = result && getCompressed()
+            .equals(other.getCompressed());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + SIZE_FIELD_NUMBER;
+      hash = (53 * hash) + getSize();
+      hash = (37 * hash) + INTERVAL_US_FIELD_NUMBER;
+      hash = (53 * hash) + getIntervalUs();
+      if (hasCompressed()) {
+        hash = (37 * hash) + COMPRESSED_FIELD_NUMBER;
+        hash = (53 * hash) + getCompressed().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Messages.ResponseParameters parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.ResponseParameters parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ResponseParameters parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.ResponseParameters parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ResponseParameters parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.ResponseParameters parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ResponseParameters parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.ResponseParameters parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ResponseParameters parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.ResponseParameters parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ResponseParameters parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.ResponseParameters parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Messages.ResponseParameters prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Configuration for a particular response.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.ResponseParameters}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ResponseParameters)
+        io.grpc.testing.integration.Messages.ResponseParametersOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ResponseParameters_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ResponseParameters_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Messages.ResponseParameters.class, io.grpc.testing.integration.Messages.ResponseParameters.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Messages.ResponseParameters.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        size_ = 0;
+
+        intervalUs_ = 0;
+
+        if (compressedBuilder_ == null) {
+          compressed_ = null;
+        } else {
+          compressed_ = null;
+          compressedBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ResponseParameters_descriptor;
+      }
+
+      public io.grpc.testing.integration.Messages.ResponseParameters getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Messages.ResponseParameters.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Messages.ResponseParameters build() {
+        io.grpc.testing.integration.Messages.ResponseParameters result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Messages.ResponseParameters buildPartial() {
+        io.grpc.testing.integration.Messages.ResponseParameters result = new io.grpc.testing.integration.Messages.ResponseParameters(this);
+        result.size_ = size_;
+        result.intervalUs_ = intervalUs_;
+        if (compressedBuilder_ == null) {
+          result.compressed_ = compressed_;
+        } else {
+          result.compressed_ = compressedBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Messages.ResponseParameters) {
+          return mergeFrom((io.grpc.testing.integration.Messages.ResponseParameters)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Messages.ResponseParameters other) {
+        if (other == io.grpc.testing.integration.Messages.ResponseParameters.getDefaultInstance()) return this;
+        if (other.getSize() != 0) {
+          setSize(other.getSize());
+        }
+        if (other.getIntervalUs() != 0) {
+          setIntervalUs(other.getIntervalUs());
+        }
+        if (other.hasCompressed()) {
+          mergeCompressed(other.getCompressed());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Messages.ResponseParameters parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Messages.ResponseParameters) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int size_ ;
+      /**
+       * <pre>
+       * Desired payload sizes in responses from the server.
+       * </pre>
+       *
+       * <code>int32 size = 1;</code>
+       */
+      public int getSize() {
+        return size_;
+      }
+      /**
+       * <pre>
+       * Desired payload sizes in responses from the server.
+       * </pre>
+       *
+       * <code>int32 size = 1;</code>
+       */
+      public Builder setSize(int value) {
+        
+        size_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Desired payload sizes in responses from the server.
+       * </pre>
+       *
+       * <code>int32 size = 1;</code>
+       */
+      public Builder clearSize() {
+        
+        size_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private int intervalUs_ ;
+      /**
+       * <pre>
+       * Desired interval between consecutive responses in the response stream in
+       * microseconds.
+       * </pre>
+       *
+       * <code>int32 interval_us = 2;</code>
+       */
+      public int getIntervalUs() {
+        return intervalUs_;
+      }
+      /**
+       * <pre>
+       * Desired interval between consecutive responses in the response stream in
+       * microseconds.
+       * </pre>
+       *
+       * <code>int32 interval_us = 2;</code>
+       */
+      public Builder setIntervalUs(int value) {
+        
+        intervalUs_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Desired interval between consecutive responses in the response stream in
+       * microseconds.
+       * </pre>
+       *
+       * <code>int32 interval_us = 2;</code>
+       */
+      public Builder clearIntervalUs() {
+        
+        intervalUs_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private com.google.protobuf.BoolValue compressed_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          com.google.protobuf.BoolValue, com.google.protobuf.BoolValue.Builder, com.google.protobuf.BoolValueOrBuilder> compressedBuilder_;
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue compressed = 3;</code>
+       */
+      public boolean hasCompressed() {
+        return compressedBuilder_ != null || compressed_ != null;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue compressed = 3;</code>
+       */
+      public com.google.protobuf.BoolValue getCompressed() {
+        if (compressedBuilder_ == null) {
+          return compressed_ == null ? com.google.protobuf.BoolValue.getDefaultInstance() : compressed_;
+        } else {
+          return compressedBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue compressed = 3;</code>
+       */
+      public Builder setCompressed(com.google.protobuf.BoolValue value) {
+        if (compressedBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          compressed_ = value;
+          onChanged();
+        } else {
+          compressedBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue compressed = 3;</code>
+       */
+      public Builder setCompressed(
+          com.google.protobuf.BoolValue.Builder builderForValue) {
+        if (compressedBuilder_ == null) {
+          compressed_ = builderForValue.build();
+          onChanged();
+        } else {
+          compressedBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue compressed = 3;</code>
+       */
+      public Builder mergeCompressed(com.google.protobuf.BoolValue value) {
+        if (compressedBuilder_ == null) {
+          if (compressed_ != null) {
+            compressed_ =
+              com.google.protobuf.BoolValue.newBuilder(compressed_).mergeFrom(value).buildPartial();
+          } else {
+            compressed_ = value;
+          }
+          onChanged();
+        } else {
+          compressedBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue compressed = 3;</code>
+       */
+      public Builder clearCompressed() {
+        if (compressedBuilder_ == null) {
+          compressed_ = null;
+          onChanged();
+        } else {
+          compressed_ = null;
+          compressedBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue compressed = 3;</code>
+       */
+      public com.google.protobuf.BoolValue.Builder getCompressedBuilder() {
+        
+        onChanged();
+        return getCompressedFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue compressed = 3;</code>
+       */
+      public com.google.protobuf.BoolValueOrBuilder getCompressedOrBuilder() {
+        if (compressedBuilder_ != null) {
+          return compressedBuilder_.getMessageOrBuilder();
+        } else {
+          return compressed_ == null ?
+              com.google.protobuf.BoolValue.getDefaultInstance() : compressed_;
+        }
+      }
+      /**
+       * <pre>
+       * Whether to request the server to compress the response. This field is
+       * "nullable" in order to interoperate seamlessly with clients not able to
+       * implement the full compression tests by introspecting the call to verify
+       * the response's compression status.
+       * </pre>
+       *
+       * <code>.google.protobuf.BoolValue compressed = 3;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          com.google.protobuf.BoolValue, com.google.protobuf.BoolValue.Builder, com.google.protobuf.BoolValueOrBuilder> 
+          getCompressedFieldBuilder() {
+        if (compressedBuilder_ == null) {
+          compressedBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              com.google.protobuf.BoolValue, com.google.protobuf.BoolValue.Builder, com.google.protobuf.BoolValueOrBuilder>(
+                  getCompressed(),
+                  getParentForChildren(),
+                  isClean());
+          compressed_ = null;
+        }
+        return compressedBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ResponseParameters)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ResponseParameters)
+    private static final io.grpc.testing.integration.Messages.ResponseParameters DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Messages.ResponseParameters();
+    }
+
+    public static io.grpc.testing.integration.Messages.ResponseParameters getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ResponseParameters>
+        PARSER = new com.google.protobuf.AbstractParser<ResponseParameters>() {
+      public ResponseParameters parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ResponseParameters(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ResponseParameters> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ResponseParameters> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Messages.ResponseParameters getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface StreamingOutputCallRequestOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.StreamingOutputCallRequest)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, the payload from each response in the stream
+     * might be of different types. This is to simulate a mixed type of payload
+     * stream.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    int getResponseTypeValue();
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, the payload from each response in the stream
+     * might be of different types. This is to simulate a mixed type of payload
+     * stream.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    io.grpc.testing.integration.Messages.PayloadType getResponseType();
+
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    java.util.List<io.grpc.testing.integration.Messages.ResponseParameters> 
+        getResponseParametersList();
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    io.grpc.testing.integration.Messages.ResponseParameters getResponseParameters(int index);
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    int getResponseParametersCount();
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    java.util.List<? extends io.grpc.testing.integration.Messages.ResponseParametersOrBuilder> 
+        getResponseParametersOrBuilderList();
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    io.grpc.testing.integration.Messages.ResponseParametersOrBuilder getResponseParametersOrBuilder(
+        int index);
+
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    boolean hasPayload();
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    io.grpc.testing.integration.Messages.Payload getPayload();
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder();
+
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    boolean hasResponseStatus();
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    io.grpc.testing.integration.Messages.EchoStatus getResponseStatus();
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    io.grpc.testing.integration.Messages.EchoStatusOrBuilder getResponseStatusOrBuilder();
+  }
+  /**
+   * <pre>
+   * Server-streaming request.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.StreamingOutputCallRequest}
+   */
+  public  static final class StreamingOutputCallRequest extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.StreamingOutputCallRequest)
+      StreamingOutputCallRequestOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use StreamingOutputCallRequest.newBuilder() to construct.
+    private StreamingOutputCallRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private StreamingOutputCallRequest() {
+      responseType_ = 0;
+      responseParameters_ = java.util.Collections.emptyList();
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private StreamingOutputCallRequest(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+              int rawValue = input.readEnum();
+
+              responseType_ = rawValue;
+              break;
+            }
+            case 18: {
+              if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+                responseParameters_ = new java.util.ArrayList<io.grpc.testing.integration.Messages.ResponseParameters>();
+                mutable_bitField0_ |= 0x00000002;
+              }
+              responseParameters_.add(
+                  input.readMessage(io.grpc.testing.integration.Messages.ResponseParameters.parser(), extensionRegistry));
+              break;
+            }
+            case 26: {
+              io.grpc.testing.integration.Messages.Payload.Builder subBuilder = null;
+              if (payload_ != null) {
+                subBuilder = payload_.toBuilder();
+              }
+              payload_ = input.readMessage(io.grpc.testing.integration.Messages.Payload.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(payload_);
+                payload_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+            case 58: {
+              io.grpc.testing.integration.Messages.EchoStatus.Builder subBuilder = null;
+              if (responseStatus_ != null) {
+                subBuilder = responseStatus_.toBuilder();
+              }
+              responseStatus_ = input.readMessage(io.grpc.testing.integration.Messages.EchoStatus.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(responseStatus_);
+                responseStatus_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+          responseParameters_ = java.util.Collections.unmodifiableList(responseParameters_);
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingOutputCallRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingOutputCallRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Messages.StreamingOutputCallRequest.class, io.grpc.testing.integration.Messages.StreamingOutputCallRequest.Builder.class);
+    }
+
+    private int bitField0_;
+    public static final int RESPONSE_TYPE_FIELD_NUMBER = 1;
+    private int responseType_;
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, the payload from each response in the stream
+     * might be of different types. This is to simulate a mixed type of payload
+     * stream.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    public int getResponseTypeValue() {
+      return responseType_;
+    }
+    /**
+     * <pre>
+     * DEPRECATED, don't use. To be removed shortly.
+     * Desired payload type in the response from the server.
+     * If response_type is RANDOM, the payload from each response in the stream
+     * might be of different types. This is to simulate a mixed type of payload
+     * stream.
+     * </pre>
+     *
+     * <code>.grpc.testing.PayloadType response_type = 1;</code>
+     */
+    public io.grpc.testing.integration.Messages.PayloadType getResponseType() {
+      io.grpc.testing.integration.Messages.PayloadType result = io.grpc.testing.integration.Messages.PayloadType.valueOf(responseType_);
+      return result == null ? io.grpc.testing.integration.Messages.PayloadType.UNRECOGNIZED : result;
+    }
+
+    public static final int RESPONSE_PARAMETERS_FIELD_NUMBER = 2;
+    private java.util.List<io.grpc.testing.integration.Messages.ResponseParameters> responseParameters_;
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    public java.util.List<io.grpc.testing.integration.Messages.ResponseParameters> getResponseParametersList() {
+      return responseParameters_;
+    }
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    public java.util.List<? extends io.grpc.testing.integration.Messages.ResponseParametersOrBuilder> 
+        getResponseParametersOrBuilderList() {
+      return responseParameters_;
+    }
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    public int getResponseParametersCount() {
+      return responseParameters_.size();
+    }
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    public io.grpc.testing.integration.Messages.ResponseParameters getResponseParameters(int index) {
+      return responseParameters_.get(index);
+    }
+    /**
+     * <pre>
+     * Configuration for each expected response message.
+     * </pre>
+     *
+     * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+     */
+    public io.grpc.testing.integration.Messages.ResponseParametersOrBuilder getResponseParametersOrBuilder(
+        int index) {
+      return responseParameters_.get(index);
+    }
+
+    public static final int PAYLOAD_FIELD_NUMBER = 3;
+    private io.grpc.testing.integration.Messages.Payload payload_;
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    public boolean hasPayload() {
+      return payload_ != null;
+    }
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    public io.grpc.testing.integration.Messages.Payload getPayload() {
+      return payload_ == null ? io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+    }
+    /**
+     * <pre>
+     * Optional input payload sent along with the request.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 3;</code>
+     */
+    public io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+      return getPayload();
+    }
+
+    public static final int RESPONSE_STATUS_FIELD_NUMBER = 7;
+    private io.grpc.testing.integration.Messages.EchoStatus responseStatus_;
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    public boolean hasResponseStatus() {
+      return responseStatus_ != null;
+    }
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    public io.grpc.testing.integration.Messages.EchoStatus getResponseStatus() {
+      return responseStatus_ == null ? io.grpc.testing.integration.Messages.EchoStatus.getDefaultInstance() : responseStatus_;
+    }
+    /**
+     * <pre>
+     * Whether server should return a given status
+     * </pre>
+     *
+     * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+     */
+    public io.grpc.testing.integration.Messages.EchoStatusOrBuilder getResponseStatusOrBuilder() {
+      return getResponseStatus();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (responseType_ != io.grpc.testing.integration.Messages.PayloadType.COMPRESSABLE.getNumber()) {
+        output.writeEnum(1, responseType_);
+      }
+      for (int i = 0; i < responseParameters_.size(); i++) {
+        output.writeMessage(2, responseParameters_.get(i));
+      }
+      if (payload_ != null) {
+        output.writeMessage(3, getPayload());
+      }
+      if (responseStatus_ != null) {
+        output.writeMessage(7, getResponseStatus());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (responseType_ != io.grpc.testing.integration.Messages.PayloadType.COMPRESSABLE.getNumber()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeEnumSize(1, responseType_);
+      }
+      for (int i = 0; i < responseParameters_.size(); i++) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, responseParameters_.get(i));
+      }
+      if (payload_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(3, getPayload());
+      }
+      if (responseStatus_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(7, getResponseStatus());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Messages.StreamingOutputCallRequest)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Messages.StreamingOutputCallRequest other = (io.grpc.testing.integration.Messages.StreamingOutputCallRequest) obj;
+
+      boolean result = true;
+      result = result && responseType_ == other.responseType_;
+      result = result && getResponseParametersList()
+          .equals(other.getResponseParametersList());
+      result = result && (hasPayload() == other.hasPayload());
+      if (hasPayload()) {
+        result = result && getPayload()
+            .equals(other.getPayload());
+      }
+      result = result && (hasResponseStatus() == other.hasResponseStatus());
+      if (hasResponseStatus()) {
+        result = result && getResponseStatus()
+            .equals(other.getResponseStatus());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + RESPONSE_TYPE_FIELD_NUMBER;
+      hash = (53 * hash) + responseType_;
+      if (getResponseParametersCount() > 0) {
+        hash = (37 * hash) + RESPONSE_PARAMETERS_FIELD_NUMBER;
+        hash = (53 * hash) + getResponseParametersList().hashCode();
+      }
+      if (hasPayload()) {
+        hash = (37 * hash) + PAYLOAD_FIELD_NUMBER;
+        hash = (53 * hash) + getPayload().hashCode();
+      }
+      if (hasResponseStatus()) {
+        hash = (37 * hash) + RESPONSE_STATUS_FIELD_NUMBER;
+        hash = (53 * hash) + getResponseStatus().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallRequest parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallRequest parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallRequest parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallRequest parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallRequest parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallRequest parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallRequest parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallRequest parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallRequest parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallRequest parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallRequest parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallRequest parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Messages.StreamingOutputCallRequest prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Server-streaming request.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.StreamingOutputCallRequest}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.StreamingOutputCallRequest)
+        io.grpc.testing.integration.Messages.StreamingOutputCallRequestOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingOutputCallRequest_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingOutputCallRequest_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Messages.StreamingOutputCallRequest.class, io.grpc.testing.integration.Messages.StreamingOutputCallRequest.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Messages.StreamingOutputCallRequest.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+          getResponseParametersFieldBuilder();
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        responseType_ = 0;
+
+        if (responseParametersBuilder_ == null) {
+          responseParameters_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000002);
+        } else {
+          responseParametersBuilder_.clear();
+        }
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+        if (responseStatusBuilder_ == null) {
+          responseStatus_ = null;
+        } else {
+          responseStatus_ = null;
+          responseStatusBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingOutputCallRequest_descriptor;
+      }
+
+      public io.grpc.testing.integration.Messages.StreamingOutputCallRequest getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Messages.StreamingOutputCallRequest.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Messages.StreamingOutputCallRequest build() {
+        io.grpc.testing.integration.Messages.StreamingOutputCallRequest result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Messages.StreamingOutputCallRequest buildPartial() {
+        io.grpc.testing.integration.Messages.StreamingOutputCallRequest result = new io.grpc.testing.integration.Messages.StreamingOutputCallRequest(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        result.responseType_ = responseType_;
+        if (responseParametersBuilder_ == null) {
+          if (((bitField0_ & 0x00000002) == 0x00000002)) {
+            responseParameters_ = java.util.Collections.unmodifiableList(responseParameters_);
+            bitField0_ = (bitField0_ & ~0x00000002);
+          }
+          result.responseParameters_ = responseParameters_;
+        } else {
+          result.responseParameters_ = responseParametersBuilder_.build();
+        }
+        if (payloadBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = payloadBuilder_.build();
+        }
+        if (responseStatusBuilder_ == null) {
+          result.responseStatus_ = responseStatus_;
+        } else {
+          result.responseStatus_ = responseStatusBuilder_.build();
+        }
+        result.bitField0_ = to_bitField0_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Messages.StreamingOutputCallRequest) {
+          return mergeFrom((io.grpc.testing.integration.Messages.StreamingOutputCallRequest)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Messages.StreamingOutputCallRequest other) {
+        if (other == io.grpc.testing.integration.Messages.StreamingOutputCallRequest.getDefaultInstance()) return this;
+        if (other.responseType_ != 0) {
+          setResponseTypeValue(other.getResponseTypeValue());
+        }
+        if (responseParametersBuilder_ == null) {
+          if (!other.responseParameters_.isEmpty()) {
+            if (responseParameters_.isEmpty()) {
+              responseParameters_ = other.responseParameters_;
+              bitField0_ = (bitField0_ & ~0x00000002);
+            } else {
+              ensureResponseParametersIsMutable();
+              responseParameters_.addAll(other.responseParameters_);
+            }
+            onChanged();
+          }
+        } else {
+          if (!other.responseParameters_.isEmpty()) {
+            if (responseParametersBuilder_.isEmpty()) {
+              responseParametersBuilder_.dispose();
+              responseParametersBuilder_ = null;
+              responseParameters_ = other.responseParameters_;
+              bitField0_ = (bitField0_ & ~0x00000002);
+              responseParametersBuilder_ = 
+                com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                   getResponseParametersFieldBuilder() : null;
+            } else {
+              responseParametersBuilder_.addAllMessages(other.responseParameters_);
+            }
+          }
+        }
+        if (other.hasPayload()) {
+          mergePayload(other.getPayload());
+        }
+        if (other.hasResponseStatus()) {
+          mergeResponseStatus(other.getResponseStatus());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Messages.StreamingOutputCallRequest parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Messages.StreamingOutputCallRequest) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private int responseType_ = 0;
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, the payload from each response in the stream
+       * might be of different types. This is to simulate a mixed type of payload
+       * stream.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public int getResponseTypeValue() {
+        return responseType_;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, the payload from each response in the stream
+       * might be of different types. This is to simulate a mixed type of payload
+       * stream.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public Builder setResponseTypeValue(int value) {
+        responseType_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, the payload from each response in the stream
+       * might be of different types. This is to simulate a mixed type of payload
+       * stream.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public io.grpc.testing.integration.Messages.PayloadType getResponseType() {
+        io.grpc.testing.integration.Messages.PayloadType result = io.grpc.testing.integration.Messages.PayloadType.valueOf(responseType_);
+        return result == null ? io.grpc.testing.integration.Messages.PayloadType.UNRECOGNIZED : result;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, the payload from each response in the stream
+       * might be of different types. This is to simulate a mixed type of payload
+       * stream.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public Builder setResponseType(io.grpc.testing.integration.Messages.PayloadType value) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        
+        responseType_ = value.getNumber();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * DEPRECATED, don't use. To be removed shortly.
+       * Desired payload type in the response from the server.
+       * If response_type is RANDOM, the payload from each response in the stream
+       * might be of different types. This is to simulate a mixed type of payload
+       * stream.
+       * </pre>
+       *
+       * <code>.grpc.testing.PayloadType response_type = 1;</code>
+       */
+      public Builder clearResponseType() {
+        
+        responseType_ = 0;
+        onChanged();
+        return this;
+      }
+
+      private java.util.List<io.grpc.testing.integration.Messages.ResponseParameters> responseParameters_ =
+        java.util.Collections.emptyList();
+      private void ensureResponseParametersIsMutable() {
+        if (!((bitField0_ & 0x00000002) == 0x00000002)) {
+          responseParameters_ = new java.util.ArrayList<io.grpc.testing.integration.Messages.ResponseParameters>(responseParameters_);
+          bitField0_ |= 0x00000002;
+         }
+      }
+
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.testing.integration.Messages.ResponseParameters, io.grpc.testing.integration.Messages.ResponseParameters.Builder, io.grpc.testing.integration.Messages.ResponseParametersOrBuilder> responseParametersBuilder_;
+
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public java.util.List<io.grpc.testing.integration.Messages.ResponseParameters> getResponseParametersList() {
+        if (responseParametersBuilder_ == null) {
+          return java.util.Collections.unmodifiableList(responseParameters_);
+        } else {
+          return responseParametersBuilder_.getMessageList();
+        }
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public int getResponseParametersCount() {
+        if (responseParametersBuilder_ == null) {
+          return responseParameters_.size();
+        } else {
+          return responseParametersBuilder_.getCount();
+        }
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public io.grpc.testing.integration.Messages.ResponseParameters getResponseParameters(int index) {
+        if (responseParametersBuilder_ == null) {
+          return responseParameters_.get(index);
+        } else {
+          return responseParametersBuilder_.getMessage(index);
+        }
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder setResponseParameters(
+          int index, io.grpc.testing.integration.Messages.ResponseParameters value) {
+        if (responseParametersBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureResponseParametersIsMutable();
+          responseParameters_.set(index, value);
+          onChanged();
+        } else {
+          responseParametersBuilder_.setMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder setResponseParameters(
+          int index, io.grpc.testing.integration.Messages.ResponseParameters.Builder builderForValue) {
+        if (responseParametersBuilder_ == null) {
+          ensureResponseParametersIsMutable();
+          responseParameters_.set(index, builderForValue.build());
+          onChanged();
+        } else {
+          responseParametersBuilder_.setMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder addResponseParameters(io.grpc.testing.integration.Messages.ResponseParameters value) {
+        if (responseParametersBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureResponseParametersIsMutable();
+          responseParameters_.add(value);
+          onChanged();
+        } else {
+          responseParametersBuilder_.addMessage(value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder addResponseParameters(
+          int index, io.grpc.testing.integration.Messages.ResponseParameters value) {
+        if (responseParametersBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureResponseParametersIsMutable();
+          responseParameters_.add(index, value);
+          onChanged();
+        } else {
+          responseParametersBuilder_.addMessage(index, value);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder addResponseParameters(
+          io.grpc.testing.integration.Messages.ResponseParameters.Builder builderForValue) {
+        if (responseParametersBuilder_ == null) {
+          ensureResponseParametersIsMutable();
+          responseParameters_.add(builderForValue.build());
+          onChanged();
+        } else {
+          responseParametersBuilder_.addMessage(builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder addResponseParameters(
+          int index, io.grpc.testing.integration.Messages.ResponseParameters.Builder builderForValue) {
+        if (responseParametersBuilder_ == null) {
+          ensureResponseParametersIsMutable();
+          responseParameters_.add(index, builderForValue.build());
+          onChanged();
+        } else {
+          responseParametersBuilder_.addMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder addAllResponseParameters(
+          java.lang.Iterable<? extends io.grpc.testing.integration.Messages.ResponseParameters> values) {
+        if (responseParametersBuilder_ == null) {
+          ensureResponseParametersIsMutable();
+          com.google.protobuf.AbstractMessageLite.Builder.addAll(
+              values, responseParameters_);
+          onChanged();
+        } else {
+          responseParametersBuilder_.addAllMessages(values);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder clearResponseParameters() {
+        if (responseParametersBuilder_ == null) {
+          responseParameters_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000002);
+          onChanged();
+        } else {
+          responseParametersBuilder_.clear();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public Builder removeResponseParameters(int index) {
+        if (responseParametersBuilder_ == null) {
+          ensureResponseParametersIsMutable();
+          responseParameters_.remove(index);
+          onChanged();
+        } else {
+          responseParametersBuilder_.remove(index);
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public io.grpc.testing.integration.Messages.ResponseParameters.Builder getResponseParametersBuilder(
+          int index) {
+        return getResponseParametersFieldBuilder().getBuilder(index);
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public io.grpc.testing.integration.Messages.ResponseParametersOrBuilder getResponseParametersOrBuilder(
+          int index) {
+        if (responseParametersBuilder_ == null) {
+          return responseParameters_.get(index);  } else {
+          return responseParametersBuilder_.getMessageOrBuilder(index);
+        }
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public java.util.List<? extends io.grpc.testing.integration.Messages.ResponseParametersOrBuilder> 
+           getResponseParametersOrBuilderList() {
+        if (responseParametersBuilder_ != null) {
+          return responseParametersBuilder_.getMessageOrBuilderList();
+        } else {
+          return java.util.Collections.unmodifiableList(responseParameters_);
+        }
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public io.grpc.testing.integration.Messages.ResponseParameters.Builder addResponseParametersBuilder() {
+        return getResponseParametersFieldBuilder().addBuilder(
+            io.grpc.testing.integration.Messages.ResponseParameters.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public io.grpc.testing.integration.Messages.ResponseParameters.Builder addResponseParametersBuilder(
+          int index) {
+        return getResponseParametersFieldBuilder().addBuilder(
+            index, io.grpc.testing.integration.Messages.ResponseParameters.getDefaultInstance());
+      }
+      /**
+       * <pre>
+       * Configuration for each expected response message.
+       * </pre>
+       *
+       * <code>repeated .grpc.testing.ResponseParameters response_parameters = 2;</code>
+       */
+      public java.util.List<io.grpc.testing.integration.Messages.ResponseParameters.Builder> 
+           getResponseParametersBuilderList() {
+        return getResponseParametersFieldBuilder().getBuilderList();
+      }
+      private com.google.protobuf.RepeatedFieldBuilderV3<
+          io.grpc.testing.integration.Messages.ResponseParameters, io.grpc.testing.integration.Messages.ResponseParameters.Builder, io.grpc.testing.integration.Messages.ResponseParametersOrBuilder> 
+          getResponseParametersFieldBuilder() {
+        if (responseParametersBuilder_ == null) {
+          responseParametersBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+              io.grpc.testing.integration.Messages.ResponseParameters, io.grpc.testing.integration.Messages.ResponseParameters.Builder, io.grpc.testing.integration.Messages.ResponseParametersOrBuilder>(
+                  responseParameters_,
+                  ((bitField0_ & 0x00000002) == 0x00000002),
+                  getParentForChildren(),
+                  isClean());
+          responseParameters_ = null;
+        }
+        return responseParametersBuilder_;
+      }
+
+      private io.grpc.testing.integration.Messages.Payload payload_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder> payloadBuilder_;
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public boolean hasPayload() {
+        return payloadBuilder_ != null || payload_ != null;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public io.grpc.testing.integration.Messages.Payload getPayload() {
+        if (payloadBuilder_ == null) {
+          return payload_ == null ? io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+        } else {
+          return payloadBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder setPayload(io.grpc.testing.integration.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payload_ = value;
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder setPayload(
+          io.grpc.testing.integration.Messages.Payload.Builder builderForValue) {
+        if (payloadBuilder_ == null) {
+          payload_ = builderForValue.build();
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder mergePayload(io.grpc.testing.integration.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (payload_ != null) {
+            payload_ =
+              io.grpc.testing.integration.Messages.Payload.newBuilder(payload_).mergeFrom(value).buildPartial();
+          } else {
+            payload_ = value;
+          }
+          onChanged();
+        } else {
+          payloadBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public Builder clearPayload() {
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+          onChanged();
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public io.grpc.testing.integration.Messages.Payload.Builder getPayloadBuilder() {
+        
+        onChanged();
+        return getPayloadFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      public io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+        if (payloadBuilder_ != null) {
+          return payloadBuilder_.getMessageOrBuilder();
+        } else {
+          return payload_ == null ?
+              io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+        }
+      }
+      /**
+       * <pre>
+       * Optional input payload sent along with the request.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 3;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder> 
+          getPayloadFieldBuilder() {
+        if (payloadBuilder_ == null) {
+          payloadBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder>(
+                  getPayload(),
+                  getParentForChildren(),
+                  isClean());
+          payload_ = null;
+        }
+        return payloadBuilder_;
+      }
+
+      private io.grpc.testing.integration.Messages.EchoStatus responseStatus_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.EchoStatus, io.grpc.testing.integration.Messages.EchoStatus.Builder, io.grpc.testing.integration.Messages.EchoStatusOrBuilder> responseStatusBuilder_;
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public boolean hasResponseStatus() {
+        return responseStatusBuilder_ != null || responseStatus_ != null;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public io.grpc.testing.integration.Messages.EchoStatus getResponseStatus() {
+        if (responseStatusBuilder_ == null) {
+          return responseStatus_ == null ? io.grpc.testing.integration.Messages.EchoStatus.getDefaultInstance() : responseStatus_;
+        } else {
+          return responseStatusBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder setResponseStatus(io.grpc.testing.integration.Messages.EchoStatus value) {
+        if (responseStatusBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          responseStatus_ = value;
+          onChanged();
+        } else {
+          responseStatusBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder setResponseStatus(
+          io.grpc.testing.integration.Messages.EchoStatus.Builder builderForValue) {
+        if (responseStatusBuilder_ == null) {
+          responseStatus_ = builderForValue.build();
+          onChanged();
+        } else {
+          responseStatusBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder mergeResponseStatus(io.grpc.testing.integration.Messages.EchoStatus value) {
+        if (responseStatusBuilder_ == null) {
+          if (responseStatus_ != null) {
+            responseStatus_ =
+              io.grpc.testing.integration.Messages.EchoStatus.newBuilder(responseStatus_).mergeFrom(value).buildPartial();
+          } else {
+            responseStatus_ = value;
+          }
+          onChanged();
+        } else {
+          responseStatusBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public Builder clearResponseStatus() {
+        if (responseStatusBuilder_ == null) {
+          responseStatus_ = null;
+          onChanged();
+        } else {
+          responseStatus_ = null;
+          responseStatusBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public io.grpc.testing.integration.Messages.EchoStatus.Builder getResponseStatusBuilder() {
+        
+        onChanged();
+        return getResponseStatusFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      public io.grpc.testing.integration.Messages.EchoStatusOrBuilder getResponseStatusOrBuilder() {
+        if (responseStatusBuilder_ != null) {
+          return responseStatusBuilder_.getMessageOrBuilder();
+        } else {
+          return responseStatus_ == null ?
+              io.grpc.testing.integration.Messages.EchoStatus.getDefaultInstance() : responseStatus_;
+        }
+      }
+      /**
+       * <pre>
+       * Whether server should return a given status
+       * </pre>
+       *
+       * <code>.grpc.testing.EchoStatus response_status = 7;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.EchoStatus, io.grpc.testing.integration.Messages.EchoStatus.Builder, io.grpc.testing.integration.Messages.EchoStatusOrBuilder> 
+          getResponseStatusFieldBuilder() {
+        if (responseStatusBuilder_ == null) {
+          responseStatusBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.testing.integration.Messages.EchoStatus, io.grpc.testing.integration.Messages.EchoStatus.Builder, io.grpc.testing.integration.Messages.EchoStatusOrBuilder>(
+                  getResponseStatus(),
+                  getParentForChildren(),
+                  isClean());
+          responseStatus_ = null;
+        }
+        return responseStatusBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.StreamingOutputCallRequest)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.StreamingOutputCallRequest)
+    private static final io.grpc.testing.integration.Messages.StreamingOutputCallRequest DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Messages.StreamingOutputCallRequest();
+    }
+
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallRequest getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<StreamingOutputCallRequest>
+        PARSER = new com.google.protobuf.AbstractParser<StreamingOutputCallRequest>() {
+      public StreamingOutputCallRequest parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new StreamingOutputCallRequest(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<StreamingOutputCallRequest> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<StreamingOutputCallRequest> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Messages.StreamingOutputCallRequest getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface StreamingOutputCallResponseOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.StreamingOutputCallResponse)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Payload to increase response size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    boolean hasPayload();
+    /**
+     * <pre>
+     * Payload to increase response size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    io.grpc.testing.integration.Messages.Payload getPayload();
+    /**
+     * <pre>
+     * Payload to increase response size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder();
+  }
+  /**
+   * <pre>
+   * Server-streaming response, as configured by the request and parameters.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.StreamingOutputCallResponse}
+   */
+  public  static final class StreamingOutputCallResponse extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.StreamingOutputCallResponse)
+      StreamingOutputCallResponseOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use StreamingOutputCallResponse.newBuilder() to construct.
+    private StreamingOutputCallResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private StreamingOutputCallResponse() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private StreamingOutputCallResponse(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              io.grpc.testing.integration.Messages.Payload.Builder subBuilder = null;
+              if (payload_ != null) {
+                subBuilder = payload_.toBuilder();
+              }
+              payload_ = input.readMessage(io.grpc.testing.integration.Messages.Payload.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(payload_);
+                payload_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingOutputCallResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingOutputCallResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Messages.StreamingOutputCallResponse.class, io.grpc.testing.integration.Messages.StreamingOutputCallResponse.Builder.class);
+    }
+
+    public static final int PAYLOAD_FIELD_NUMBER = 1;
+    private io.grpc.testing.integration.Messages.Payload payload_;
+    /**
+     * <pre>
+     * Payload to increase response size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public boolean hasPayload() {
+      return payload_ != null;
+    }
+    /**
+     * <pre>
+     * Payload to increase response size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public io.grpc.testing.integration.Messages.Payload getPayload() {
+      return payload_ == null ? io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+    }
+    /**
+     * <pre>
+     * Payload to increase response size.
+     * </pre>
+     *
+     * <code>.grpc.testing.Payload payload = 1;</code>
+     */
+    public io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+      return getPayload();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (payload_ != null) {
+        output.writeMessage(1, getPayload());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (payload_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, getPayload());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Messages.StreamingOutputCallResponse)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Messages.StreamingOutputCallResponse other = (io.grpc.testing.integration.Messages.StreamingOutputCallResponse) obj;
+
+      boolean result = true;
+      result = result && (hasPayload() == other.hasPayload());
+      if (hasPayload()) {
+        result = result && getPayload()
+            .equals(other.getPayload());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      if (hasPayload()) {
+        hash = (37 * hash) + PAYLOAD_FIELD_NUMBER;
+        hash = (53 * hash) + getPayload().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallResponse parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallResponse parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallResponse parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallResponse parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallResponse parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallResponse parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallResponse parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallResponse parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallResponse parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallResponse parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallResponse parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallResponse parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Messages.StreamingOutputCallResponse prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Server-streaming response, as configured by the request and parameters.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.StreamingOutputCallResponse}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.StreamingOutputCallResponse)
+        io.grpc.testing.integration.Messages.StreamingOutputCallResponseOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingOutputCallResponse_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingOutputCallResponse_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Messages.StreamingOutputCallResponse.class, io.grpc.testing.integration.Messages.StreamingOutputCallResponse.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Messages.StreamingOutputCallResponse.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_StreamingOutputCallResponse_descriptor;
+      }
+
+      public io.grpc.testing.integration.Messages.StreamingOutputCallResponse getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Messages.StreamingOutputCallResponse.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Messages.StreamingOutputCallResponse build() {
+        io.grpc.testing.integration.Messages.StreamingOutputCallResponse result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Messages.StreamingOutputCallResponse buildPartial() {
+        io.grpc.testing.integration.Messages.StreamingOutputCallResponse result = new io.grpc.testing.integration.Messages.StreamingOutputCallResponse(this);
+        if (payloadBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = payloadBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Messages.StreamingOutputCallResponse) {
+          return mergeFrom((io.grpc.testing.integration.Messages.StreamingOutputCallResponse)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Messages.StreamingOutputCallResponse other) {
+        if (other == io.grpc.testing.integration.Messages.StreamingOutputCallResponse.getDefaultInstance()) return this;
+        if (other.hasPayload()) {
+          mergePayload(other.getPayload());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Messages.StreamingOutputCallResponse parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Messages.StreamingOutputCallResponse) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private io.grpc.testing.integration.Messages.Payload payload_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder> payloadBuilder_;
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public boolean hasPayload() {
+        return payloadBuilder_ != null || payload_ != null;
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.testing.integration.Messages.Payload getPayload() {
+        if (payloadBuilder_ == null) {
+          return payload_ == null ? io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+        } else {
+          return payloadBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder setPayload(io.grpc.testing.integration.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          payload_ = value;
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder setPayload(
+          io.grpc.testing.integration.Messages.Payload.Builder builderForValue) {
+        if (payloadBuilder_ == null) {
+          payload_ = builderForValue.build();
+          onChanged();
+        } else {
+          payloadBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder mergePayload(io.grpc.testing.integration.Messages.Payload value) {
+        if (payloadBuilder_ == null) {
+          if (payload_ != null) {
+            payload_ =
+              io.grpc.testing.integration.Messages.Payload.newBuilder(payload_).mergeFrom(value).buildPartial();
+          } else {
+            payload_ = value;
+          }
+          onChanged();
+        } else {
+          payloadBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public Builder clearPayload() {
+        if (payloadBuilder_ == null) {
+          payload_ = null;
+          onChanged();
+        } else {
+          payload_ = null;
+          payloadBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.testing.integration.Messages.Payload.Builder getPayloadBuilder() {
+        
+        onChanged();
+        return getPayloadFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      public io.grpc.testing.integration.Messages.PayloadOrBuilder getPayloadOrBuilder() {
+        if (payloadBuilder_ != null) {
+          return payloadBuilder_.getMessageOrBuilder();
+        } else {
+          return payload_ == null ?
+              io.grpc.testing.integration.Messages.Payload.getDefaultInstance() : payload_;
+        }
+      }
+      /**
+       * <pre>
+       * Payload to increase response size.
+       * </pre>
+       *
+       * <code>.grpc.testing.Payload payload = 1;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder> 
+          getPayloadFieldBuilder() {
+        if (payloadBuilder_ == null) {
+          payloadBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              io.grpc.testing.integration.Messages.Payload, io.grpc.testing.integration.Messages.Payload.Builder, io.grpc.testing.integration.Messages.PayloadOrBuilder>(
+                  getPayload(),
+                  getParentForChildren(),
+                  isClean());
+          payload_ = null;
+        }
+        return payloadBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.StreamingOutputCallResponse)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.StreamingOutputCallResponse)
+    private static final io.grpc.testing.integration.Messages.StreamingOutputCallResponse DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Messages.StreamingOutputCallResponse();
+    }
+
+    public static io.grpc.testing.integration.Messages.StreamingOutputCallResponse getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<StreamingOutputCallResponse>
+        PARSER = new com.google.protobuf.AbstractParser<StreamingOutputCallResponse>() {
+      public StreamingOutputCallResponse parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new StreamingOutputCallResponse(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<StreamingOutputCallResponse> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<StreamingOutputCallResponse> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Messages.StreamingOutputCallResponse getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ReconnectParamsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ReconnectParams)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>int32 max_reconnect_backoff_ms = 1;</code>
+     */
+    int getMaxReconnectBackoffMs();
+  }
+  /**
+   * <pre>
+   * For reconnect interop test only.
+   * Client tells server what reconnection parameters it used.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.ReconnectParams}
+   */
+  public  static final class ReconnectParams extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ReconnectParams)
+      ReconnectParamsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ReconnectParams.newBuilder() to construct.
+    private ReconnectParams(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ReconnectParams() {
+      maxReconnectBackoffMs_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ReconnectParams(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              maxReconnectBackoffMs_ = input.readInt32();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ReconnectParams_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ReconnectParams_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Messages.ReconnectParams.class, io.grpc.testing.integration.Messages.ReconnectParams.Builder.class);
+    }
+
+    public static final int MAX_RECONNECT_BACKOFF_MS_FIELD_NUMBER = 1;
+    private int maxReconnectBackoffMs_;
+    /**
+     * <code>int32 max_reconnect_backoff_ms = 1;</code>
+     */
+    public int getMaxReconnectBackoffMs() {
+      return maxReconnectBackoffMs_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (maxReconnectBackoffMs_ != 0) {
+        output.writeInt32(1, maxReconnectBackoffMs_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (maxReconnectBackoffMs_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(1, maxReconnectBackoffMs_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Messages.ReconnectParams)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Messages.ReconnectParams other = (io.grpc.testing.integration.Messages.ReconnectParams) obj;
+
+      boolean result = true;
+      result = result && (getMaxReconnectBackoffMs()
+          == other.getMaxReconnectBackoffMs());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + MAX_RECONNECT_BACKOFF_MS_FIELD_NUMBER;
+      hash = (53 * hash) + getMaxReconnectBackoffMs();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Messages.ReconnectParams parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectParams parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectParams parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectParams parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectParams parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectParams parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectParams parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectParams parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectParams parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectParams parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectParams parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectParams parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Messages.ReconnectParams prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * For reconnect interop test only.
+     * Client tells server what reconnection parameters it used.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.ReconnectParams}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ReconnectParams)
+        io.grpc.testing.integration.Messages.ReconnectParamsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ReconnectParams_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ReconnectParams_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Messages.ReconnectParams.class, io.grpc.testing.integration.Messages.ReconnectParams.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Messages.ReconnectParams.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        maxReconnectBackoffMs_ = 0;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ReconnectParams_descriptor;
+      }
+
+      public io.grpc.testing.integration.Messages.ReconnectParams getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Messages.ReconnectParams.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Messages.ReconnectParams build() {
+        io.grpc.testing.integration.Messages.ReconnectParams result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Messages.ReconnectParams buildPartial() {
+        io.grpc.testing.integration.Messages.ReconnectParams result = new io.grpc.testing.integration.Messages.ReconnectParams(this);
+        result.maxReconnectBackoffMs_ = maxReconnectBackoffMs_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Messages.ReconnectParams) {
+          return mergeFrom((io.grpc.testing.integration.Messages.ReconnectParams)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Messages.ReconnectParams other) {
+        if (other == io.grpc.testing.integration.Messages.ReconnectParams.getDefaultInstance()) return this;
+        if (other.getMaxReconnectBackoffMs() != 0) {
+          setMaxReconnectBackoffMs(other.getMaxReconnectBackoffMs());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Messages.ReconnectParams parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Messages.ReconnectParams) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private int maxReconnectBackoffMs_ ;
+      /**
+       * <code>int32 max_reconnect_backoff_ms = 1;</code>
+       */
+      public int getMaxReconnectBackoffMs() {
+        return maxReconnectBackoffMs_;
+      }
+      /**
+       * <code>int32 max_reconnect_backoff_ms = 1;</code>
+       */
+      public Builder setMaxReconnectBackoffMs(int value) {
+        
+        maxReconnectBackoffMs_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>int32 max_reconnect_backoff_ms = 1;</code>
+       */
+      public Builder clearMaxReconnectBackoffMs() {
+        
+        maxReconnectBackoffMs_ = 0;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ReconnectParams)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ReconnectParams)
+    private static final io.grpc.testing.integration.Messages.ReconnectParams DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Messages.ReconnectParams();
+    }
+
+    public static io.grpc.testing.integration.Messages.ReconnectParams getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ReconnectParams>
+        PARSER = new com.google.protobuf.AbstractParser<ReconnectParams>() {
+      public ReconnectParams parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ReconnectParams(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ReconnectParams> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ReconnectParams> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Messages.ReconnectParams getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface ReconnectInfoOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.ReconnectInfo)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>bool passed = 1;</code>
+     */
+    boolean getPassed();
+
+    /**
+     * <code>repeated int32 backoff_ms = 2;</code>
+     */
+    java.util.List<java.lang.Integer> getBackoffMsList();
+    /**
+     * <code>repeated int32 backoff_ms = 2;</code>
+     */
+    int getBackoffMsCount();
+    /**
+     * <code>repeated int32 backoff_ms = 2;</code>
+     */
+    int getBackoffMs(int index);
+  }
+  /**
+   * <pre>
+   * For reconnect interop test only.
+   * Server tells client whether its reconnects are following the spec and the
+   * reconnect backoffs it saw.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.ReconnectInfo}
+   */
+  public  static final class ReconnectInfo extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.ReconnectInfo)
+      ReconnectInfoOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use ReconnectInfo.newBuilder() to construct.
+    private ReconnectInfo(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private ReconnectInfo() {
+      passed_ = false;
+      backoffMs_ = java.util.Collections.emptyList();
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private ReconnectInfo(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 8: {
+
+              passed_ = input.readBool();
+              break;
+            }
+            case 16: {
+              if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+                backoffMs_ = new java.util.ArrayList<java.lang.Integer>();
+                mutable_bitField0_ |= 0x00000002;
+              }
+              backoffMs_.add(input.readInt32());
+              break;
+            }
+            case 18: {
+              int length = input.readRawVarint32();
+              int limit = input.pushLimit(length);
+              if (!((mutable_bitField0_ & 0x00000002) == 0x00000002) && input.getBytesUntilLimit() > 0) {
+                backoffMs_ = new java.util.ArrayList<java.lang.Integer>();
+                mutable_bitField0_ |= 0x00000002;
+              }
+              while (input.getBytesUntilLimit() > 0) {
+                backoffMs_.add(input.readInt32());
+              }
+              input.popLimit(limit);
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+          backoffMs_ = java.util.Collections.unmodifiableList(backoffMs_);
+        }
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ReconnectInfo_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ReconnectInfo_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Messages.ReconnectInfo.class, io.grpc.testing.integration.Messages.ReconnectInfo.Builder.class);
+    }
+
+    private int bitField0_;
+    public static final int PASSED_FIELD_NUMBER = 1;
+    private boolean passed_;
+    /**
+     * <code>bool passed = 1;</code>
+     */
+    public boolean getPassed() {
+      return passed_;
+    }
+
+    public static final int BACKOFF_MS_FIELD_NUMBER = 2;
+    private java.util.List<java.lang.Integer> backoffMs_;
+    /**
+     * <code>repeated int32 backoff_ms = 2;</code>
+     */
+    public java.util.List<java.lang.Integer>
+        getBackoffMsList() {
+      return backoffMs_;
+    }
+    /**
+     * <code>repeated int32 backoff_ms = 2;</code>
+     */
+    public int getBackoffMsCount() {
+      return backoffMs_.size();
+    }
+    /**
+     * <code>repeated int32 backoff_ms = 2;</code>
+     */
+    public int getBackoffMs(int index) {
+      return backoffMs_.get(index);
+    }
+    private int backoffMsMemoizedSerializedSize = -1;
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      getSerializedSize();
+      if (passed_ != false) {
+        output.writeBool(1, passed_);
+      }
+      if (getBackoffMsList().size() > 0) {
+        output.writeUInt32NoTag(18);
+        output.writeUInt32NoTag(backoffMsMemoizedSerializedSize);
+      }
+      for (int i = 0; i < backoffMs_.size(); i++) {
+        output.writeInt32NoTag(backoffMs_.get(i));
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (passed_ != false) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBoolSize(1, passed_);
+      }
+      {
+        int dataSize = 0;
+        for (int i = 0; i < backoffMs_.size(); i++) {
+          dataSize += com.google.protobuf.CodedOutputStream
+            .computeInt32SizeNoTag(backoffMs_.get(i));
+        }
+        size += dataSize;
+        if (!getBackoffMsList().isEmpty()) {
+          size += 1;
+          size += com.google.protobuf.CodedOutputStream
+              .computeInt32SizeNoTag(dataSize);
+        }
+        backoffMsMemoizedSerializedSize = dataSize;
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Messages.ReconnectInfo)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Messages.ReconnectInfo other = (io.grpc.testing.integration.Messages.ReconnectInfo) obj;
+
+      boolean result = true;
+      result = result && (getPassed()
+          == other.getPassed());
+      result = result && getBackoffMsList()
+          .equals(other.getBackoffMsList());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + PASSED_FIELD_NUMBER;
+      hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+          getPassed());
+      if (getBackoffMsCount() > 0) {
+        hash = (37 * hash) + BACKOFF_MS_FIELD_NUMBER;
+        hash = (53 * hash) + getBackoffMsList().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Messages.ReconnectInfo parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectInfo parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectInfo parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectInfo parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectInfo parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectInfo parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectInfo parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectInfo parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectInfo parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectInfo parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectInfo parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Messages.ReconnectInfo parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Messages.ReconnectInfo prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * For reconnect interop test only.
+     * Server tells client whether its reconnects are following the spec and the
+     * reconnect backoffs it saw.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.ReconnectInfo}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.ReconnectInfo)
+        io.grpc.testing.integration.Messages.ReconnectInfoOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ReconnectInfo_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ReconnectInfo_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Messages.ReconnectInfo.class, io.grpc.testing.integration.Messages.ReconnectInfo.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Messages.ReconnectInfo.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        passed_ = false;
+
+        backoffMs_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000002);
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Messages.internal_static_grpc_testing_ReconnectInfo_descriptor;
+      }
+
+      public io.grpc.testing.integration.Messages.ReconnectInfo getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Messages.ReconnectInfo.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Messages.ReconnectInfo build() {
+        io.grpc.testing.integration.Messages.ReconnectInfo result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Messages.ReconnectInfo buildPartial() {
+        io.grpc.testing.integration.Messages.ReconnectInfo result = new io.grpc.testing.integration.Messages.ReconnectInfo(this);
+        int from_bitField0_ = bitField0_;
+        int to_bitField0_ = 0;
+        result.passed_ = passed_;
+        if (((bitField0_ & 0x00000002) == 0x00000002)) {
+          backoffMs_ = java.util.Collections.unmodifiableList(backoffMs_);
+          bitField0_ = (bitField0_ & ~0x00000002);
+        }
+        result.backoffMs_ = backoffMs_;
+        result.bitField0_ = to_bitField0_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Messages.ReconnectInfo) {
+          return mergeFrom((io.grpc.testing.integration.Messages.ReconnectInfo)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Messages.ReconnectInfo other) {
+        if (other == io.grpc.testing.integration.Messages.ReconnectInfo.getDefaultInstance()) return this;
+        if (other.getPassed() != false) {
+          setPassed(other.getPassed());
+        }
+        if (!other.backoffMs_.isEmpty()) {
+          if (backoffMs_.isEmpty()) {
+            backoffMs_ = other.backoffMs_;
+            bitField0_ = (bitField0_ & ~0x00000002);
+          } else {
+            ensureBackoffMsIsMutable();
+            backoffMs_.addAll(other.backoffMs_);
+          }
+          onChanged();
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Messages.ReconnectInfo parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Messages.ReconnectInfo) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int bitField0_;
+
+      private boolean passed_ ;
+      /**
+       * <code>bool passed = 1;</code>
+       */
+      public boolean getPassed() {
+        return passed_;
+      }
+      /**
+       * <code>bool passed = 1;</code>
+       */
+      public Builder setPassed(boolean value) {
+        
+        passed_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>bool passed = 1;</code>
+       */
+      public Builder clearPassed() {
+        
+        passed_ = false;
+        onChanged();
+        return this;
+      }
+
+      private java.util.List<java.lang.Integer> backoffMs_ = java.util.Collections.emptyList();
+      private void ensureBackoffMsIsMutable() {
+        if (!((bitField0_ & 0x00000002) == 0x00000002)) {
+          backoffMs_ = new java.util.ArrayList<java.lang.Integer>(backoffMs_);
+          bitField0_ |= 0x00000002;
+         }
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public java.util.List<java.lang.Integer>
+          getBackoffMsList() {
+        return java.util.Collections.unmodifiableList(backoffMs_);
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public int getBackoffMsCount() {
+        return backoffMs_.size();
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public int getBackoffMs(int index) {
+        return backoffMs_.get(index);
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public Builder setBackoffMs(
+          int index, int value) {
+        ensureBackoffMsIsMutable();
+        backoffMs_.set(index, value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public Builder addBackoffMs(int value) {
+        ensureBackoffMsIsMutable();
+        backoffMs_.add(value);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public Builder addAllBackoffMs(
+          java.lang.Iterable<? extends java.lang.Integer> values) {
+        ensureBackoffMsIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, backoffMs_);
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>repeated int32 backoff_ms = 2;</code>
+       */
+      public Builder clearBackoffMs() {
+        backoffMs_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000002);
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.ReconnectInfo)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.ReconnectInfo)
+    private static final io.grpc.testing.integration.Messages.ReconnectInfo DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Messages.ReconnectInfo();
+    }
+
+    public static io.grpc.testing.integration.Messages.ReconnectInfo getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<ReconnectInfo>
+        PARSER = new com.google.protobuf.AbstractParser<ReconnectInfo>() {
+      public ReconnectInfo parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new ReconnectInfo(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<ReconnectInfo> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<ReconnectInfo> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Messages.ReconnectInfo getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_Payload_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_Payload_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_EchoStatus_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_EchoStatus_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_SimpleRequest_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_SimpleRequest_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_SimpleResponse_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_SimpleResponse_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_SimpleContext_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_SimpleContext_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_StreamingInputCallRequest_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_StreamingInputCallRequest_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_StreamingInputCallResponse_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_StreamingInputCallResponse_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ResponseParameters_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ResponseParameters_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_StreamingOutputCallRequest_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_StreamingOutputCallRequest_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_StreamingOutputCallResponse_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_StreamingOutputCallResponse_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ReconnectParams_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ReconnectParams_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_ReconnectInfo_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_ReconnectInfo_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\033grpc/testing/messages.proto\022\014grpc.test" +
+      "ing\032\036google/protobuf/wrappers.proto\"@\n\007P" +
+      "ayload\022\'\n\004type\030\001 \001(\0162\031.grpc.testing.Payl" +
+      "oadType\022\014\n\004body\030\002 \001(\014\"+\n\nEchoStatus\022\014\n\004c" +
+      "ode\030\001 \001(\005\022\017\n\007message\030\002 \001(\t\"\324\002\n\rSimpleReq" +
+      "uest\0220\n\rresponse_type\030\001 \001(\0162\031.grpc.testi" +
+      "ng.PayloadType\022\025\n\rresponse_size\030\002 \001(\005\022&\n" +
+      "\007payload\030\003 \001(\0132\025.grpc.testing.Payload\022\025\n" +
+      "\rfill_username\030\004 \001(\010\022\030\n\020fill_oauth_scope" +
+      "\030\005 \001(\010\0227\n\023response_compressed\030\006 \001(\0132\032.go" +
+      "ogle.protobuf.BoolValue\0221\n\017response_stat" +
+      "us\030\007 \001(\0132\030.grpc.testing.EchoStatus\0225\n\021ex" +
+      "pect_compressed\030\010 \001(\0132\032.google.protobuf." +
+      "BoolValue\"_\n\016SimpleResponse\022&\n\007payload\030\001" +
+      " \001(\0132\025.grpc.testing.Payload\022\020\n\010username\030" +
+      "\002 \001(\t\022\023\n\013oauth_scope\030\003 \001(\t\"\036\n\rSimpleCont" +
+      "ext\022\r\n\005value\030\001 \001(\t\"z\n\031StreamingInputCall" +
+      "Request\022&\n\007payload\030\001 \001(\0132\025.grpc.testing." +
+      "Payload\0225\n\021expect_compressed\030\002 \001(\0132\032.goo" +
+      "gle.protobuf.BoolValue\"=\n\032StreamingInput" +
+      "CallResponse\022\037\n\027aggregated_payload_size\030" +
+      "\001 \001(\005\"g\n\022ResponseParameters\022\014\n\004size\030\001 \001(" +
+      "\005\022\023\n\013interval_us\030\002 \001(\005\022.\n\ncompressed\030\003 \001" +
+      "(\0132\032.google.protobuf.BoolValue\"\350\001\n\032Strea" +
+      "mingOutputCallRequest\0220\n\rresponse_type\030\001" +
+      " \001(\0162\031.grpc.testing.PayloadType\022=\n\023respo" +
+      "nse_parameters\030\002 \003(\0132 .grpc.testing.Resp" +
+      "onseParameters\022&\n\007payload\030\003 \001(\0132\025.grpc.t" +
+      "esting.Payload\0221\n\017response_status\030\007 \001(\0132" +
+      "\030.grpc.testing.EchoStatus\"E\n\033StreamingOu" +
+      "tputCallResponse\022&\n\007payload\030\001 \001(\0132\025.grpc" +
+      ".testing.Payload\"3\n\017ReconnectParams\022 \n\030m" +
+      "ax_reconnect_backoff_ms\030\001 \001(\005\"3\n\rReconne" +
+      "ctInfo\022\016\n\006passed\030\001 \001(\010\022\022\n\nbackoff_ms\030\002 \003" +
+      "(\005*?\n\013PayloadType\022\020\n\014COMPRESSABLE\020\000\022\022\n\016U" +
+      "NCOMPRESSABLE\020\001\022\n\n\006RANDOM\020\002B\035\n\033io.grpc.t" +
+      "esting.integrationb\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          com.google.protobuf.WrappersProto.getDescriptor(),
+        }, assigner);
+    internal_static_grpc_testing_Payload_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_testing_Payload_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_Payload_descriptor,
+        new java.lang.String[] { "Type", "Body", });
+    internal_static_grpc_testing_EchoStatus_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_testing_EchoStatus_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_EchoStatus_descriptor,
+        new java.lang.String[] { "Code", "Message", });
+    internal_static_grpc_testing_SimpleRequest_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_testing_SimpleRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_SimpleRequest_descriptor,
+        new java.lang.String[] { "ResponseType", "ResponseSize", "Payload", "FillUsername", "FillOauthScope", "ResponseCompressed", "ResponseStatus", "ExpectCompressed", });
+    internal_static_grpc_testing_SimpleResponse_descriptor =
+      getDescriptor().getMessageTypes().get(3);
+    internal_static_grpc_testing_SimpleResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_SimpleResponse_descriptor,
+        new java.lang.String[] { "Payload", "Username", "OauthScope", });
+    internal_static_grpc_testing_SimpleContext_descriptor =
+      getDescriptor().getMessageTypes().get(4);
+    internal_static_grpc_testing_SimpleContext_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_SimpleContext_descriptor,
+        new java.lang.String[] { "Value", });
+    internal_static_grpc_testing_StreamingInputCallRequest_descriptor =
+      getDescriptor().getMessageTypes().get(5);
+    internal_static_grpc_testing_StreamingInputCallRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_StreamingInputCallRequest_descriptor,
+        new java.lang.String[] { "Payload", "ExpectCompressed", });
+    internal_static_grpc_testing_StreamingInputCallResponse_descriptor =
+      getDescriptor().getMessageTypes().get(6);
+    internal_static_grpc_testing_StreamingInputCallResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_StreamingInputCallResponse_descriptor,
+        new java.lang.String[] { "AggregatedPayloadSize", });
+    internal_static_grpc_testing_ResponseParameters_descriptor =
+      getDescriptor().getMessageTypes().get(7);
+    internal_static_grpc_testing_ResponseParameters_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ResponseParameters_descriptor,
+        new java.lang.String[] { "Size", "IntervalUs", "Compressed", });
+    internal_static_grpc_testing_StreamingOutputCallRequest_descriptor =
+      getDescriptor().getMessageTypes().get(8);
+    internal_static_grpc_testing_StreamingOutputCallRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_StreamingOutputCallRequest_descriptor,
+        new java.lang.String[] { "ResponseType", "ResponseParameters", "Payload", "ResponseStatus", });
+    internal_static_grpc_testing_StreamingOutputCallResponse_descriptor =
+      getDescriptor().getMessageTypes().get(9);
+    internal_static_grpc_testing_StreamingOutputCallResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_StreamingOutputCallResponse_descriptor,
+        new java.lang.String[] { "Payload", });
+    internal_static_grpc_testing_ReconnectParams_descriptor =
+      getDescriptor().getMessageTypes().get(10);
+    internal_static_grpc_testing_ReconnectParams_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ReconnectParams_descriptor,
+        new java.lang.String[] { "MaxReconnectBackoffMs", });
+    internal_static_grpc_testing_ReconnectInfo_descriptor =
+      getDescriptor().getMessageTypes().get(11);
+    internal_static_grpc_testing_ReconnectInfo_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_ReconnectInfo_descriptor,
+        new java.lang.String[] { "Passed", "BackoffMs", });
+    com.google.protobuf.WrappersProto.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/interop-testing/src/generated/main/java/io/grpc/testing/integration/Metrics.java b/interop-testing/src/generated/main/java/io/grpc/testing/integration/Metrics.java
new file mode 100644
index 0000000..8f1385d
--- /dev/null
+++ b/interop-testing/src/generated/main/java/io/grpc/testing/integration/Metrics.java
@@ -0,0 +1,1930 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/testing/metrics.proto
+
+package io.grpc.testing.integration;
+
+public final class Metrics {
+  private Metrics() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  public interface GaugeResponseOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.GaugeResponse)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>string name = 1;</code>
+     */
+    java.lang.String getName();
+    /**
+     * <code>string name = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getNameBytes();
+
+    /**
+     * <code>int64 long_value = 2;</code>
+     */
+    long getLongValue();
+
+    /**
+     * <code>double double_value = 3;</code>
+     */
+    double getDoubleValue();
+
+    /**
+     * <code>string string_value = 4;</code>
+     */
+    java.lang.String getStringValue();
+    /**
+     * <code>string string_value = 4;</code>
+     */
+    com.google.protobuf.ByteString
+        getStringValueBytes();
+
+    public io.grpc.testing.integration.Metrics.GaugeResponse.ValueCase getValueCase();
+  }
+  /**
+   * <pre>
+   * Reponse message containing the gauge name and value
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.GaugeResponse}
+   */
+  public  static final class GaugeResponse extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.GaugeResponse)
+      GaugeResponseOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use GaugeResponse.newBuilder() to construct.
+    private GaugeResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private GaugeResponse() {
+      name_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private GaugeResponse(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              name_ = s;
+              break;
+            }
+            case 16: {
+              valueCase_ = 2;
+              value_ = input.readInt64();
+              break;
+            }
+            case 25: {
+              valueCase_ = 3;
+              value_ = input.readDouble();
+              break;
+            }
+            case 34: {
+              java.lang.String s = input.readStringRequireUtf8();
+              valueCase_ = 4;
+              value_ = s;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_GaugeResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_GaugeResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Metrics.GaugeResponse.class, io.grpc.testing.integration.Metrics.GaugeResponse.Builder.class);
+    }
+
+    private int valueCase_ = 0;
+    private java.lang.Object value_;
+    public enum ValueCase
+        implements com.google.protobuf.Internal.EnumLite {
+      LONG_VALUE(2),
+      DOUBLE_VALUE(3),
+      STRING_VALUE(4),
+      VALUE_NOT_SET(0);
+      private final int value;
+      private ValueCase(int value) {
+        this.value = value;
+      }
+      /**
+       * @deprecated Use {@link #forNumber(int)} instead.
+       */
+      @java.lang.Deprecated
+      public static ValueCase valueOf(int value) {
+        return forNumber(value);
+      }
+
+      public static ValueCase forNumber(int value) {
+        switch (value) {
+          case 2: return LONG_VALUE;
+          case 3: return DOUBLE_VALUE;
+          case 4: return STRING_VALUE;
+          case 0: return VALUE_NOT_SET;
+          default: return null;
+        }
+      }
+      public int getNumber() {
+        return this.value;
+      }
+    };
+
+    public ValueCase
+    getValueCase() {
+      return ValueCase.forNumber(
+          valueCase_);
+    }
+
+    public static final int NAME_FIELD_NUMBER = 1;
+    private volatile java.lang.Object name_;
+    /**
+     * <code>string name = 1;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        name_ = s;
+        return s;
+      }
+    }
+    /**
+     * <code>string name = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int LONG_VALUE_FIELD_NUMBER = 2;
+    /**
+     * <code>int64 long_value = 2;</code>
+     */
+    public long getLongValue() {
+      if (valueCase_ == 2) {
+        return (java.lang.Long) value_;
+      }
+      return 0L;
+    }
+
+    public static final int DOUBLE_VALUE_FIELD_NUMBER = 3;
+    /**
+     * <code>double double_value = 3;</code>
+     */
+    public double getDoubleValue() {
+      if (valueCase_ == 3) {
+        return (java.lang.Double) value_;
+      }
+      return 0D;
+    }
+
+    public static final int STRING_VALUE_FIELD_NUMBER = 4;
+    /**
+     * <code>string string_value = 4;</code>
+     */
+    public java.lang.String getStringValue() {
+      java.lang.Object ref = "";
+      if (valueCase_ == 4) {
+        ref = value_;
+      }
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (valueCase_ == 4) {
+          value_ = s;
+        }
+        return s;
+      }
+    }
+    /**
+     * <code>string string_value = 4;</code>
+     */
+    public com.google.protobuf.ByteString
+        getStringValueBytes() {
+      java.lang.Object ref = "";
+      if (valueCase_ == 4) {
+        ref = value_;
+      }
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        if (valueCase_ == 4) {
+          value_ = b;
+        }
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!getNameBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_);
+      }
+      if (valueCase_ == 2) {
+        output.writeInt64(
+            2, (long)((java.lang.Long) value_));
+      }
+      if (valueCase_ == 3) {
+        output.writeDouble(
+            3, (double)((java.lang.Double) value_));
+      }
+      if (valueCase_ == 4) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 4, value_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!getNameBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_);
+      }
+      if (valueCase_ == 2) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt64Size(
+              2, (long)((java.lang.Long) value_));
+      }
+      if (valueCase_ == 3) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeDoubleSize(
+              3, (double)((java.lang.Double) value_));
+      }
+      if (valueCase_ == 4) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(4, value_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Metrics.GaugeResponse)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Metrics.GaugeResponse other = (io.grpc.testing.integration.Metrics.GaugeResponse) obj;
+
+      boolean result = true;
+      result = result && getName()
+          .equals(other.getName());
+      result = result && getValueCase().equals(
+          other.getValueCase());
+      if (!result) return false;
+      switch (valueCase_) {
+        case 2:
+          result = result && (getLongValue()
+              == other.getLongValue());
+          break;
+        case 3:
+          result = result && (
+              java.lang.Double.doubleToLongBits(getDoubleValue())
+              == java.lang.Double.doubleToLongBits(
+                  other.getDoubleValue()));
+          break;
+        case 4:
+          result = result && getStringValue()
+              .equals(other.getStringValue());
+          break;
+        case 0:
+        default:
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + NAME_FIELD_NUMBER;
+      hash = (53 * hash) + getName().hashCode();
+      switch (valueCase_) {
+        case 2:
+          hash = (37 * hash) + LONG_VALUE_FIELD_NUMBER;
+          hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+              getLongValue());
+          break;
+        case 3:
+          hash = (37 * hash) + DOUBLE_VALUE_FIELD_NUMBER;
+          hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+              java.lang.Double.doubleToLongBits(getDoubleValue()));
+          break;
+        case 4:
+          hash = (37 * hash) + STRING_VALUE_FIELD_NUMBER;
+          hash = (53 * hash) + getStringValue().hashCode();
+          break;
+        case 0:
+        default:
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Metrics.GaugeResponse parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeResponse parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeResponse parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeResponse parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeResponse parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeResponse parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeResponse parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeResponse parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeResponse parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeResponse parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeResponse parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeResponse parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Metrics.GaugeResponse prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Reponse message containing the gauge name and value
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.GaugeResponse}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.GaugeResponse)
+        io.grpc.testing.integration.Metrics.GaugeResponseOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_GaugeResponse_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_GaugeResponse_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Metrics.GaugeResponse.class, io.grpc.testing.integration.Metrics.GaugeResponse.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Metrics.GaugeResponse.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        name_ = "";
+
+        valueCase_ = 0;
+        value_ = null;
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_GaugeResponse_descriptor;
+      }
+
+      public io.grpc.testing.integration.Metrics.GaugeResponse getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Metrics.GaugeResponse.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Metrics.GaugeResponse build() {
+        io.grpc.testing.integration.Metrics.GaugeResponse result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Metrics.GaugeResponse buildPartial() {
+        io.grpc.testing.integration.Metrics.GaugeResponse result = new io.grpc.testing.integration.Metrics.GaugeResponse(this);
+        result.name_ = name_;
+        if (valueCase_ == 2) {
+          result.value_ = value_;
+        }
+        if (valueCase_ == 3) {
+          result.value_ = value_;
+        }
+        if (valueCase_ == 4) {
+          result.value_ = value_;
+        }
+        result.valueCase_ = valueCase_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Metrics.GaugeResponse) {
+          return mergeFrom((io.grpc.testing.integration.Metrics.GaugeResponse)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Metrics.GaugeResponse other) {
+        if (other == io.grpc.testing.integration.Metrics.GaugeResponse.getDefaultInstance()) return this;
+        if (!other.getName().isEmpty()) {
+          name_ = other.name_;
+          onChanged();
+        }
+        switch (other.getValueCase()) {
+          case LONG_VALUE: {
+            setLongValue(other.getLongValue());
+            break;
+          }
+          case DOUBLE_VALUE: {
+            setDoubleValue(other.getDoubleValue());
+            break;
+          }
+          case STRING_VALUE: {
+            valueCase_ = 4;
+            value_ = other.value_;
+            onChanged();
+            break;
+          }
+          case VALUE_NOT_SET: {
+            break;
+          }
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Metrics.GaugeResponse parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Metrics.GaugeResponse) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int valueCase_ = 0;
+      private java.lang.Object value_;
+      public ValueCase
+          getValueCase() {
+        return ValueCase.forNumber(
+            valueCase_);
+      }
+
+      public Builder clearValue() {
+        valueCase_ = 0;
+        value_ = null;
+        onChanged();
+        return this;
+      }
+
+
+      private java.lang.Object name_ = "";
+      /**
+       * <code>string name = 1;</code>
+       */
+      public java.lang.String getName() {
+        java.lang.Object ref = name_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          name_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <code>string name = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getNameBytes() {
+        java.lang.Object ref = name_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          name_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <code>string name = 1;</code>
+       */
+      public Builder setName(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        name_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string name = 1;</code>
+       */
+      public Builder clearName() {
+        
+        name_ = getDefaultInstance().getName();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string name = 1;</code>
+       */
+      public Builder setNameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        name_ = value;
+        onChanged();
+        return this;
+      }
+
+      /**
+       * <code>int64 long_value = 2;</code>
+       */
+      public long getLongValue() {
+        if (valueCase_ == 2) {
+          return (java.lang.Long) value_;
+        }
+        return 0L;
+      }
+      /**
+       * <code>int64 long_value = 2;</code>
+       */
+      public Builder setLongValue(long value) {
+        valueCase_ = 2;
+        value_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>int64 long_value = 2;</code>
+       */
+      public Builder clearLongValue() {
+        if (valueCase_ == 2) {
+          valueCase_ = 0;
+          value_ = null;
+          onChanged();
+        }
+        return this;
+      }
+
+      /**
+       * <code>double double_value = 3;</code>
+       */
+      public double getDoubleValue() {
+        if (valueCase_ == 3) {
+          return (java.lang.Double) value_;
+        }
+        return 0D;
+      }
+      /**
+       * <code>double double_value = 3;</code>
+       */
+      public Builder setDoubleValue(double value) {
+        valueCase_ = 3;
+        value_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>double double_value = 3;</code>
+       */
+      public Builder clearDoubleValue() {
+        if (valueCase_ == 3) {
+          valueCase_ = 0;
+          value_ = null;
+          onChanged();
+        }
+        return this;
+      }
+
+      /**
+       * <code>string string_value = 4;</code>
+       */
+      public java.lang.String getStringValue() {
+        java.lang.Object ref = "";
+        if (valueCase_ == 4) {
+          ref = value_;
+        }
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          if (valueCase_ == 4) {
+            value_ = s;
+          }
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <code>string string_value = 4;</code>
+       */
+      public com.google.protobuf.ByteString
+          getStringValueBytes() {
+        java.lang.Object ref = "";
+        if (valueCase_ == 4) {
+          ref = value_;
+        }
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          if (valueCase_ == 4) {
+            value_ = b;
+          }
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <code>string string_value = 4;</code>
+       */
+      public Builder setStringValue(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  valueCase_ = 4;
+        value_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string string_value = 4;</code>
+       */
+      public Builder clearStringValue() {
+        if (valueCase_ == 4) {
+          valueCase_ = 0;
+          value_ = null;
+          onChanged();
+        }
+        return this;
+      }
+      /**
+       * <code>string string_value = 4;</code>
+       */
+      public Builder setStringValueBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        valueCase_ = 4;
+        value_ = value;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.GaugeResponse)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.GaugeResponse)
+    private static final io.grpc.testing.integration.Metrics.GaugeResponse DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Metrics.GaugeResponse();
+    }
+
+    public static io.grpc.testing.integration.Metrics.GaugeResponse getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<GaugeResponse>
+        PARSER = new com.google.protobuf.AbstractParser<GaugeResponse>() {
+      public GaugeResponse parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new GaugeResponse(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<GaugeResponse> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<GaugeResponse> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Metrics.GaugeResponse getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface GaugeRequestOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.GaugeRequest)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>string name = 1;</code>
+     */
+    java.lang.String getName();
+    /**
+     * <code>string name = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getNameBytes();
+  }
+  /**
+   * <pre>
+   * Request message containing the gauge name
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.GaugeRequest}
+   */
+  public  static final class GaugeRequest extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.GaugeRequest)
+      GaugeRequestOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use GaugeRequest.newBuilder() to construct.
+    private GaugeRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private GaugeRequest() {
+      name_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private GaugeRequest(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              name_ = s;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_GaugeRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_GaugeRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Metrics.GaugeRequest.class, io.grpc.testing.integration.Metrics.GaugeRequest.Builder.class);
+    }
+
+    public static final int NAME_FIELD_NUMBER = 1;
+    private volatile java.lang.Object name_;
+    /**
+     * <code>string name = 1;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        name_ = s;
+        return s;
+      }
+    }
+    /**
+     * <code>string name = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!getNameBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!getNameBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Metrics.GaugeRequest)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Metrics.GaugeRequest other = (io.grpc.testing.integration.Metrics.GaugeRequest) obj;
+
+      boolean result = true;
+      result = result && getName()
+          .equals(other.getName());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + NAME_FIELD_NUMBER;
+      hash = (53 * hash) + getName().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Metrics.GaugeRequest parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeRequest parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeRequest parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeRequest parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeRequest parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeRequest parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeRequest parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeRequest parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeRequest parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeRequest parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeRequest parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Metrics.GaugeRequest parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Metrics.GaugeRequest prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * Request message containing the gauge name
+     * </pre>
+     *
+     * Protobuf type {@code grpc.testing.GaugeRequest}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.GaugeRequest)
+        io.grpc.testing.integration.Metrics.GaugeRequestOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_GaugeRequest_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_GaugeRequest_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Metrics.GaugeRequest.class, io.grpc.testing.integration.Metrics.GaugeRequest.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Metrics.GaugeRequest.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        name_ = "";
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_GaugeRequest_descriptor;
+      }
+
+      public io.grpc.testing.integration.Metrics.GaugeRequest getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Metrics.GaugeRequest.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Metrics.GaugeRequest build() {
+        io.grpc.testing.integration.Metrics.GaugeRequest result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Metrics.GaugeRequest buildPartial() {
+        io.grpc.testing.integration.Metrics.GaugeRequest result = new io.grpc.testing.integration.Metrics.GaugeRequest(this);
+        result.name_ = name_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Metrics.GaugeRequest) {
+          return mergeFrom((io.grpc.testing.integration.Metrics.GaugeRequest)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Metrics.GaugeRequest other) {
+        if (other == io.grpc.testing.integration.Metrics.GaugeRequest.getDefaultInstance()) return this;
+        if (!other.getName().isEmpty()) {
+          name_ = other.name_;
+          onChanged();
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Metrics.GaugeRequest parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Metrics.GaugeRequest) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private java.lang.Object name_ = "";
+      /**
+       * <code>string name = 1;</code>
+       */
+      public java.lang.String getName() {
+        java.lang.Object ref = name_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          name_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <code>string name = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getNameBytes() {
+        java.lang.Object ref = name_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          name_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <code>string name = 1;</code>
+       */
+      public Builder setName(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        name_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string name = 1;</code>
+       */
+      public Builder clearName() {
+        
+        name_ = getDefaultInstance().getName();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string name = 1;</code>
+       */
+      public Builder setNameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        name_ = value;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.GaugeRequest)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.GaugeRequest)
+    private static final io.grpc.testing.integration.Metrics.GaugeRequest DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Metrics.GaugeRequest();
+    }
+
+    public static io.grpc.testing.integration.Metrics.GaugeRequest getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<GaugeRequest>
+        PARSER = new com.google.protobuf.AbstractParser<GaugeRequest>() {
+      public GaugeRequest parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new GaugeRequest(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<GaugeRequest> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<GaugeRequest> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Metrics.GaugeRequest getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface EmptyMessageOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.testing.EmptyMessage)
+      com.google.protobuf.MessageOrBuilder {
+  }
+  /**
+   * Protobuf type {@code grpc.testing.EmptyMessage}
+   */
+  public  static final class EmptyMessage extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.testing.EmptyMessage)
+      EmptyMessageOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use EmptyMessage.newBuilder() to construct.
+    private EmptyMessage(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private EmptyMessage() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private EmptyMessage(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_EmptyMessage_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_EmptyMessage_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.integration.Metrics.EmptyMessage.class, io.grpc.testing.integration.Metrics.EmptyMessage.Builder.class);
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.testing.integration.Metrics.EmptyMessage)) {
+        return super.equals(obj);
+      }
+      io.grpc.testing.integration.Metrics.EmptyMessage other = (io.grpc.testing.integration.Metrics.EmptyMessage) obj;
+
+      boolean result = true;
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.testing.integration.Metrics.EmptyMessage parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Metrics.EmptyMessage parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.EmptyMessage parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Metrics.EmptyMessage parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.EmptyMessage parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.testing.integration.Metrics.EmptyMessage parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.EmptyMessage parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Metrics.EmptyMessage parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.EmptyMessage parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Metrics.EmptyMessage parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.testing.integration.Metrics.EmptyMessage parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.testing.integration.Metrics.EmptyMessage parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.testing.integration.Metrics.EmptyMessage prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.testing.EmptyMessage}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.testing.EmptyMessage)
+        io.grpc.testing.integration.Metrics.EmptyMessageOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_EmptyMessage_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_EmptyMessage_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.testing.integration.Metrics.EmptyMessage.class, io.grpc.testing.integration.Metrics.EmptyMessage.Builder.class);
+      }
+
+      // Construct using io.grpc.testing.integration.Metrics.EmptyMessage.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.testing.integration.Metrics.internal_static_grpc_testing_EmptyMessage_descriptor;
+      }
+
+      public io.grpc.testing.integration.Metrics.EmptyMessage getDefaultInstanceForType() {
+        return io.grpc.testing.integration.Metrics.EmptyMessage.getDefaultInstance();
+      }
+
+      public io.grpc.testing.integration.Metrics.EmptyMessage build() {
+        io.grpc.testing.integration.Metrics.EmptyMessage result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.testing.integration.Metrics.EmptyMessage buildPartial() {
+        io.grpc.testing.integration.Metrics.EmptyMessage result = new io.grpc.testing.integration.Metrics.EmptyMessage(this);
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.testing.integration.Metrics.EmptyMessage) {
+          return mergeFrom((io.grpc.testing.integration.Metrics.EmptyMessage)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.testing.integration.Metrics.EmptyMessage other) {
+        if (other == io.grpc.testing.integration.Metrics.EmptyMessage.getDefaultInstance()) return this;
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.testing.integration.Metrics.EmptyMessage parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.testing.integration.Metrics.EmptyMessage) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.testing.EmptyMessage)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.testing.EmptyMessage)
+    private static final io.grpc.testing.integration.Metrics.EmptyMessage DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.testing.integration.Metrics.EmptyMessage();
+    }
+
+    public static io.grpc.testing.integration.Metrics.EmptyMessage getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<EmptyMessage>
+        PARSER = new com.google.protobuf.AbstractParser<EmptyMessage>() {
+      public EmptyMessage parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new EmptyMessage(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<EmptyMessage> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<EmptyMessage> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.testing.integration.Metrics.EmptyMessage getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_GaugeResponse_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_GaugeResponse_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_GaugeRequest_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_GaugeRequest_fieldAccessorTable;
+  private static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_EmptyMessage_descriptor;
+  private static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_EmptyMessage_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\032grpc/testing/metrics.proto\022\014grpc.testi" +
+      "ng\"l\n\rGaugeResponse\022\014\n\004name\030\001 \001(\t\022\024\n\nlon" +
+      "g_value\030\002 \001(\003H\000\022\026\n\014double_value\030\003 \001(\001H\000\022" +
+      "\026\n\014string_value\030\004 \001(\tH\000B\007\n\005value\"\034\n\014Gaug" +
+      "eRequest\022\014\n\004name\030\001 \001(\t\"\016\n\014EmptyMessage2\240" +
+      "\001\n\016MetricsService\022I\n\014GetAllGauges\022\032.grpc" +
+      ".testing.EmptyMessage\032\033.grpc.testing.Gau" +
+      "geResponse0\001\022C\n\010GetGauge\022\032.grpc.testing." +
+      "GaugeRequest\032\033.grpc.testing.GaugeRespons" +
+      "eB\035\n\033io.grpc.testing.integrationb\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        }, assigner);
+    internal_static_grpc_testing_GaugeResponse_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_testing_GaugeResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_GaugeResponse_descriptor,
+        new java.lang.String[] { "Name", "LongValue", "DoubleValue", "StringValue", "Value", });
+    internal_static_grpc_testing_GaugeRequest_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_testing_GaugeRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_GaugeRequest_descriptor,
+        new java.lang.String[] { "Name", });
+    internal_static_grpc_testing_EmptyMessage_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_testing_EmptyMessage_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_EmptyMessage_descriptor,
+        new java.lang.String[] { });
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/interop-testing/src/generated/main/java/io/grpc/testing/integration/Test.java b/interop-testing/src/generated/main/java/io/grpc/testing/integration/Test.java
new file mode 100644
index 0000000..7d5cd8a
--- /dev/null
+++ b/interop-testing/src/generated/main/java/io/grpc/testing/integration/Test.java
@@ -0,0 +1,73 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/testing/test.proto
+
+package io.grpc.testing.integration;
+
+public final class Test {
+  private Test() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\027grpc/testing/test.proto\022\014grpc.testing\032" +
+      "\030grpc/testing/empty.proto\032\033grpc/testing/" +
+      "messages.proto2\313\005\n\013TestService\0225\n\tEmptyC" +
+      "all\022\023.grpc.testing.Empty\032\023.grpc.testing." +
+      "Empty\022F\n\tUnaryCall\022\033.grpc.testing.Simple" +
+      "Request\032\034.grpc.testing.SimpleResponse\022O\n" +
+      "\022CacheableUnaryCall\022\033.grpc.testing.Simpl" +
+      "eRequest\032\034.grpc.testing.SimpleResponse\022l" +
+      "\n\023StreamingOutputCall\022(.grpc.testing.Str" +
+      "eamingOutputCallRequest\032).grpc.testing.S" +
+      "treamingOutputCallResponse0\001\022i\n\022Streamin" +
+      "gInputCall\022\'.grpc.testing.StreamingInput" +
+      "CallRequest\032(.grpc.testing.StreamingInpu" +
+      "tCallResponse(\001\022i\n\016FullDuplexCall\022(.grpc" +
+      ".testing.StreamingOutputCallRequest\032).gr" +
+      "pc.testing.StreamingOutputCallResponse(\001" +
+      "0\001\022i\n\016HalfDuplexCall\022(.grpc.testing.Stre" +
+      "amingOutputCallRequest\032).grpc.testing.St" +
+      "reamingOutputCallResponse(\0010\001\022=\n\021Unimple" +
+      "mentedCall\022\023.grpc.testing.Empty\032\023.grpc.t" +
+      "esting.Empty2U\n\024UnimplementedService\022=\n\021" +
+      "UnimplementedCall\022\023.grpc.testing.Empty\032\023" +
+      ".grpc.testing.Empty2\177\n\020ReconnectService\022" +
+      "1\n\005Start\022\023.grpc.testing.Empty\032\023.grpc.tes" +
+      "ting.Empty\0228\n\004Stop\022\023.grpc.testing.Empty\032" +
+      "\033.grpc.testing.ReconnectInfoB\035\n\033io.grpc." +
+      "testing.integrationb\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          io.grpc.testing.integration.EmptyProtos.getDescriptor(),
+          io.grpc.testing.integration.Messages.getDescriptor(),
+        }, assigner);
+    io.grpc.testing.integration.EmptyProtos.getDescriptor();
+    io.grpc.testing.integration.Messages.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java
new file mode 100644
index 0000000..ac724f6
--- /dev/null
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.java
@@ -0,0 +1,2126 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.testing.integration.Messages.PayloadType.COMPRESSABLE;
+import static io.opencensus.tags.unsafe.ContextUtils.TAG_CONTEXT_KEY;
+import static io.opencensus.trace.unsafe.ContextUtils.CONTEXT_SPAN_KEY;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.ComputeEngineCredentials;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.OAuth2Credentials;
+import com.google.auth.oauth2.ServiceAccountCredentials;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.io.ByteStreams;
+import com.google.common.util.concurrent.SettableFuture;
+import com.google.protobuf.BoolValue;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.MessageLite;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ClientInterceptors;
+import io.grpc.ClientStreamTracer;
+import io.grpc.Context;
+import io.grpc.Grpc;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Server;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.ServerInterceptors;
+import io.grpc.ServerStreamTracer;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.auth.MoreCallCredentials;
+import io.grpc.internal.AbstractServerImplBuilder;
+import io.grpc.internal.CensusStatsModule;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.testing.StatsTestUtils;
+import io.grpc.internal.testing.StatsTestUtils.FakeStatsRecorder;
+import io.grpc.internal.testing.StatsTestUtils.FakeTagContext;
+import io.grpc.internal.testing.StatsTestUtils.FakeTagContextBinarySerializer;
+import io.grpc.internal.testing.StatsTestUtils.FakeTagger;
+import io.grpc.internal.testing.StatsTestUtils.MetricsRecord;
+import io.grpc.internal.testing.StreamRecorder;
+import io.grpc.internal.testing.TestClientStreamTracer;
+import io.grpc.internal.testing.TestServerStreamTracer;
+import io.grpc.internal.testing.TestStreamTracer;
+import io.grpc.stub.ClientCallStreamObserver;
+import io.grpc.stub.ClientCalls;
+import io.grpc.stub.MetadataUtils;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.TestUtils;
+import io.grpc.testing.integration.EmptyProtos.Empty;
+import io.grpc.testing.integration.Messages.EchoStatus;
+import io.grpc.testing.integration.Messages.Payload;
+import io.grpc.testing.integration.Messages.PayloadType;
+import io.grpc.testing.integration.Messages.ResponseParameters;
+import io.grpc.testing.integration.Messages.SimpleRequest;
+import io.grpc.testing.integration.Messages.SimpleResponse;
+import io.grpc.testing.integration.Messages.StreamingInputCallRequest;
+import io.grpc.testing.integration.Messages.StreamingInputCallResponse;
+import io.grpc.testing.integration.Messages.StreamingOutputCallRequest;
+import io.grpc.testing.integration.Messages.StreamingOutputCallResponse;
+import io.opencensus.contrib.grpc.metrics.RpcMeasureConstants;
+import io.opencensus.tags.TagKey;
+import io.opencensus.tags.TagValue;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.Tracing;
+import io.opencensus.trace.unsafe.ContextUtils;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.SocketAddress;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.verification.VerificationMode;
+
+/**
+ * Abstract base class for all GRPC transport tests.
+ *
+ * <p> New tests should avoid using Mockito to support running on AppEngine.</p>
+ */
+public abstract class AbstractInteropTest {
+  private static Logger logger = Logger.getLogger(AbstractInteropTest.class.getName());
+
+  @Rule public final Timeout globalTimeout = Timeout.seconds(30);
+
+  /** Must be at least {@link #unaryPayloadLength()}, plus some to account for encoding overhead. */
+  public static final int MAX_MESSAGE_SIZE = 16 * 1024 * 1024;
+
+  private static final FakeTagger tagger = new FakeTagger();
+  private static final FakeTagContextBinarySerializer tagContextBinarySerializer =
+      new FakeTagContextBinarySerializer();
+
+  private final AtomicReference<ServerCall<?, ?>> serverCallCapture =
+      new AtomicReference<ServerCall<?, ?>>();
+  private final AtomicReference<Metadata> requestHeadersCapture =
+      new AtomicReference<Metadata>();
+  private final AtomicReference<Context> contextCapture =
+      new AtomicReference<Context>();
+  private final FakeStatsRecorder clientStatsRecorder = new FakeStatsRecorder();
+  private final FakeStatsRecorder serverStatsRecorder = new FakeStatsRecorder();
+
+  private ScheduledExecutorService testServiceExecutor;
+  private Server server;
+
+  private final LinkedBlockingQueue<ServerStreamTracerInfo> serverStreamTracers =
+      new LinkedBlockingQueue<ServerStreamTracerInfo>();
+
+  private static final class ServerStreamTracerInfo {
+    final String fullMethodName;
+    final InteropServerStreamTracer tracer;
+
+    ServerStreamTracerInfo(String fullMethodName, InteropServerStreamTracer tracer) {
+      this.fullMethodName = fullMethodName;
+      this.tracer = tracer;
+    }
+
+    private static final class InteropServerStreamTracer extends TestServerStreamTracer {
+      private volatile Context contextCapture;
+
+      @Override
+      public Context filterContext(Context context) {
+        contextCapture = context;
+        return super.filterContext(context);
+      }
+    }
+  }
+
+  private final ServerStreamTracer.Factory serverStreamTracerFactory =
+      new ServerStreamTracer.Factory() {
+        @Override
+        public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata headers) {
+          ServerStreamTracerInfo.InteropServerStreamTracer tracer
+              = new ServerStreamTracerInfo.InteropServerStreamTracer();
+          serverStreamTracers.add(new ServerStreamTracerInfo(fullMethodName, tracer));
+          return tracer;
+        }
+      };
+
+  protected static final Empty EMPTY = Empty.getDefaultInstance();
+
+  private void startServer() {
+    AbstractServerImplBuilder<?> builder = getServerBuilder();
+    if (builder == null) {
+      server = null;
+      return;
+    }
+    testServiceExecutor = Executors.newScheduledThreadPool(2);
+
+    List<ServerInterceptor> allInterceptors = ImmutableList.<ServerInterceptor>builder()
+        .add(recordServerCallInterceptor(serverCallCapture))
+        .add(TestUtils.recordRequestHeadersInterceptor(requestHeadersCapture))
+        .add(recordContextInterceptor(contextCapture))
+        .addAll(TestServiceImpl.interceptors())
+        .build();
+
+    builder
+        .addService(
+            ServerInterceptors.intercept(
+                new TestServiceImpl(testServiceExecutor),
+                allInterceptors))
+        .addStreamTracerFactory(serverStreamTracerFactory);
+    io.grpc.internal.TestingAccessor.setStatsImplementation(
+        builder,
+        new CensusStatsModule(
+            tagger,
+            tagContextBinarySerializer,
+            serverStatsRecorder,
+            GrpcUtil.STOPWATCH_SUPPLIER,
+            true));
+    try {
+      server = builder.build().start();
+    } catch (IOException ex) {
+      throw new RuntimeException(ex);
+    }
+  }
+
+  private void stopServer() {
+    if (server != null) {
+      server.shutdownNow();
+    }
+    if (testServiceExecutor != null) {
+      testServiceExecutor.shutdown();
+    }
+  }
+
+  @VisibleForTesting
+  final int getPort() {
+    return server.getPort();
+  }
+
+  protected ManagedChannel channel;
+  protected TestServiceGrpc.TestServiceBlockingStub blockingStub;
+  protected TestServiceGrpc.TestServiceStub asyncStub;
+
+  private final LinkedBlockingQueue<TestClientStreamTracer> clientStreamTracers =
+      new LinkedBlockingQueue<TestClientStreamTracer>();
+
+  private final ClientStreamTracer.Factory clientStreamTracerFactory =
+      new ClientStreamTracer.Factory() {
+        @Override
+        public ClientStreamTracer newClientStreamTracer(CallOptions callOptions, Metadata headers) {
+          TestClientStreamTracer tracer = new TestClientStreamTracer();
+          clientStreamTracers.add(tracer);
+          return tracer;
+        }
+      };
+  private final ClientInterceptor tracerSetupInterceptor = new ClientInterceptor() {
+        @Override
+        public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+            MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+          return next.newCall(
+              method, callOptions.withStreamTracerFactory(clientStreamTracerFactory));
+        }
+      };
+
+  /**
+   * Must be called by the subclass setup method if overridden.
+   */
+  @Before
+  public void setUp() {
+    startServer();
+    channel = createChannel();
+
+    blockingStub =
+        TestServiceGrpc.newBlockingStub(channel).withInterceptors(tracerSetupInterceptor);
+    asyncStub = TestServiceGrpc.newStub(channel).withInterceptors(tracerSetupInterceptor);
+
+    ClientInterceptor[] additionalInterceptors = getAdditionalInterceptors();
+    if (additionalInterceptors != null) {
+      blockingStub = blockingStub.withInterceptors(additionalInterceptors);
+      asyncStub = asyncStub.withInterceptors(additionalInterceptors);
+    }
+
+    requestHeadersCapture.set(null);
+  }
+
+  /** Clean up. */
+  @After
+  public void tearDown() {
+    if (channel != null) {
+      channel.shutdownNow();
+      try {
+        channel.awaitTermination(1, TimeUnit.SECONDS);
+      } catch (InterruptedException ie) {
+        logger.log(Level.FINE, "Interrupted while waiting for channel termination", ie);
+        // Best effort. If there is an interruption, we want to continue cleaning up, but quickly
+        Thread.currentThread().interrupt();
+      }
+    }
+    stopServer();
+  }
+
+  protected abstract ManagedChannel createChannel();
+
+  @Nullable
+  protected ClientInterceptor[] getAdditionalInterceptors() {
+    return null;
+  }
+
+  /**
+   * Returns the server builder used to create server for each test run.  Return {@code null} if
+   * it shouldn't start a server in the same process.
+   */
+  @Nullable
+  protected AbstractServerImplBuilder<?> getServerBuilder() {
+    return null;
+  }
+
+  protected final CensusStatsModule createClientCensusStatsModule() {
+    return new CensusStatsModule(
+        tagger, tagContextBinarySerializer, clientStatsRecorder, GrpcUtil.STOPWATCH_SUPPLIER, true);
+  }
+
+  /**
+   * Return true if exact metric values should be checked.
+   */
+  protected boolean metricsExpected() {
+    return true;
+  }
+
+  @Test
+  public void emptyUnary() throws Exception {
+    assertEquals(EMPTY, blockingStub.emptyCall(EMPTY));
+  }
+
+  /** Sends a cacheable unary rpc using GET. Requires that the server is behind a caching proxy. */
+  public void cacheableUnary() {
+    // Set safe to true.
+    MethodDescriptor<SimpleRequest, SimpleResponse> safeCacheableUnaryCallMethod =
+        TestServiceGrpc.getCacheableUnaryCallMethod().toBuilder().setSafe(true).build();
+    // Set fake user IP since some proxies (GFE) won't cache requests from localhost.
+    Metadata.Key<String> userIpKey = Metadata.Key.of("x-user-ip", Metadata.ASCII_STRING_MARSHALLER);
+    Metadata metadata = new Metadata();
+    metadata.put(userIpKey, "1.2.3.4");
+    Channel channelWithUserIpKey =
+        ClientInterceptors.intercept(channel, MetadataUtils.newAttachHeadersInterceptor(metadata));
+    SimpleRequest requests1And2 =
+        SimpleRequest.newBuilder()
+            .setPayload(
+                Payload.newBuilder()
+                    .setBody(ByteString.copyFromUtf8(String.valueOf(System.nanoTime()))))
+            .build();
+    SimpleRequest request3 =
+        SimpleRequest.newBuilder()
+            .setPayload(
+                Payload.newBuilder()
+                    .setBody(ByteString.copyFromUtf8(String.valueOf(System.nanoTime()))))
+            .build();
+
+    SimpleResponse response1 =
+        ClientCalls.blockingUnaryCall(
+            channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, requests1And2);
+    SimpleResponse response2 =
+        ClientCalls.blockingUnaryCall(
+            channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, requests1And2);
+    SimpleResponse response3 =
+        ClientCalls.blockingUnaryCall(
+            channelWithUserIpKey, safeCacheableUnaryCallMethod, CallOptions.DEFAULT, request3);
+
+    assertEquals(response1, response2);
+    assertNotEquals(response1, response3);
+  }
+
+  @Test
+  public void largeUnary() throws Exception {
+    assumeEnoughMemory();
+    final SimpleRequest request = SimpleRequest.newBuilder()
+        .setResponseSize(314159)
+        .setResponseType(PayloadType.COMPRESSABLE)
+        .setPayload(Payload.newBuilder()
+            .setBody(ByteString.copyFrom(new byte[271828])))
+        .build();
+    final SimpleResponse goldenResponse = SimpleResponse.newBuilder()
+        .setPayload(Payload.newBuilder()
+            .setType(PayloadType.COMPRESSABLE)
+            .setBody(ByteString.copyFrom(new byte[314159])))
+        .build();
+
+    assertEquals(goldenResponse, blockingStub.unaryCall(request));
+
+    assertStatsTrace("grpc.testing.TestService/UnaryCall", Status.Code.OK,
+        Collections.singleton(request), Collections.singleton(goldenResponse));
+  }
+
+  /**
+   * Tests client per-message compression for unary calls. The Java API does not support inspecting
+   * a message's compression level, so this is primarily intended to run against a gRPC C++ server.
+   */
+  public void clientCompressedUnary(boolean probe) throws Exception {
+    assumeEnoughMemory();
+    final SimpleRequest expectCompressedRequest =
+        SimpleRequest.newBuilder()
+            .setExpectCompressed(BoolValue.newBuilder().setValue(true))
+            .setResponseSize(314159)
+            .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[271828])))
+            .build();
+    final SimpleRequest expectUncompressedRequest =
+        SimpleRequest.newBuilder()
+            .setExpectCompressed(BoolValue.newBuilder().setValue(false))
+            .setResponseSize(314159)
+            .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[271828])))
+            .build();
+    final SimpleResponse goldenResponse =
+        SimpleResponse.newBuilder()
+            .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[314159])))
+            .build();
+
+    if (probe) {
+      // Send a non-compressed message with expectCompress=true. Servers supporting this test case
+      // should return INVALID_ARGUMENT.
+      try {
+        blockingStub.unaryCall(expectCompressedRequest);
+        fail("expected INVALID_ARGUMENT");
+      } catch (StatusRuntimeException e) {
+        assertEquals(Status.INVALID_ARGUMENT.getCode(), e.getStatus().getCode());
+      }
+      assertStatsTrace("grpc.testing.TestService/UnaryCall", Status.Code.INVALID_ARGUMENT);
+    }
+
+    assertEquals(
+        goldenResponse, blockingStub.withCompression("gzip").unaryCall(expectCompressedRequest));
+    assertStatsTrace(
+        "grpc.testing.TestService/UnaryCall",
+        Status.Code.OK,
+        Collections.singleton(expectCompressedRequest),
+        Collections.singleton(goldenResponse));
+
+    assertEquals(goldenResponse, blockingStub.unaryCall(expectUncompressedRequest));
+    assertStatsTrace(
+        "grpc.testing.TestService/UnaryCall",
+        Status.Code.OK,
+        Collections.singleton(expectUncompressedRequest),
+        Collections.singleton(goldenResponse));
+  }
+
+  /**
+   * Tests if the server can send a compressed unary response. Ideally we would assert that the
+   * responses have the requested compression, but this is not supported by the API. Given a
+   * compliant server, this test will exercise the code path for receiving a compressed response but
+   * cannot itself verify that the response was compressed.
+   */
+  @Test
+  public void serverCompressedUnary() throws Exception {
+    assumeEnoughMemory();
+    final SimpleRequest responseShouldBeCompressed =
+        SimpleRequest.newBuilder()
+            .setResponseCompressed(BoolValue.newBuilder().setValue(true))
+            .setResponseSize(314159)
+            .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[271828])))
+            .build();
+    final SimpleRequest responseShouldBeUncompressed =
+        SimpleRequest.newBuilder()
+            .setResponseCompressed(BoolValue.newBuilder().setValue(false))
+            .setResponseSize(314159)
+            .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[271828])))
+            .build();
+    final SimpleResponse goldenResponse =
+        SimpleResponse.newBuilder()
+            .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[314159])))
+            .build();
+
+    assertEquals(goldenResponse, blockingStub.unaryCall(responseShouldBeCompressed));
+    assertStatsTrace(
+        "grpc.testing.TestService/UnaryCall",
+        Status.Code.OK,
+        Collections.singleton(responseShouldBeCompressed),
+        Collections.singleton(goldenResponse));
+
+    assertEquals(goldenResponse, blockingStub.unaryCall(responseShouldBeUncompressed));
+    assertStatsTrace(
+        "grpc.testing.TestService/UnaryCall",
+        Status.Code.OK,
+        Collections.singleton(responseShouldBeUncompressed),
+        Collections.singleton(goldenResponse));
+  }
+
+  @Test
+  public void serverStreaming() throws Exception {
+    final StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder()
+        .setResponseType(PayloadType.COMPRESSABLE)
+        .addResponseParameters(ResponseParameters.newBuilder()
+            .setSize(31415))
+        .addResponseParameters(ResponseParameters.newBuilder()
+            .setSize(9))
+        .addResponseParameters(ResponseParameters.newBuilder()
+            .setSize(2653))
+        .addResponseParameters(ResponseParameters.newBuilder()
+            .setSize(58979))
+        .build();
+    final List<StreamingOutputCallResponse> goldenResponses = Arrays.asList(
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setType(PayloadType.COMPRESSABLE)
+                .setBody(ByteString.copyFrom(new byte[31415])))
+            .build(),
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setType(PayloadType.COMPRESSABLE)
+                .setBody(ByteString.copyFrom(new byte[9])))
+            .build(),
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setType(PayloadType.COMPRESSABLE)
+                .setBody(ByteString.copyFrom(new byte[2653])))
+            .build(),
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setType(PayloadType.COMPRESSABLE)
+                .setBody(ByteString.copyFrom(new byte[58979])))
+            .build());
+
+    StreamRecorder<StreamingOutputCallResponse> recorder = StreamRecorder.create();
+    asyncStub.streamingOutputCall(request, recorder);
+    recorder.awaitCompletion();
+    assertSuccess(recorder);
+    assertEquals(goldenResponses, recorder.getValues());
+  }
+
+  @Test
+  public void clientStreaming() throws Exception {
+    final List<StreamingInputCallRequest> requests = Arrays.asList(
+        StreamingInputCallRequest.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setBody(ByteString.copyFrom(new byte[27182])))
+            .build(),
+        StreamingInputCallRequest.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setBody(ByteString.copyFrom(new byte[8])))
+            .build(),
+        StreamingInputCallRequest.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setBody(ByteString.copyFrom(new byte[1828])))
+            .build(),
+        StreamingInputCallRequest.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setBody(ByteString.copyFrom(new byte[45904])))
+            .build());
+    final StreamingInputCallResponse goldenResponse = StreamingInputCallResponse.newBuilder()
+        .setAggregatedPayloadSize(74922)
+        .build();
+
+    StreamRecorder<StreamingInputCallResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<StreamingInputCallRequest> requestObserver =
+        asyncStub.streamingInputCall(responseObserver);
+    for (StreamingInputCallRequest request : requests) {
+      requestObserver.onNext(request);
+    }
+    requestObserver.onCompleted();
+
+    assertEquals(goldenResponse, responseObserver.firstValue().get());
+    responseObserver.awaitCompletion();
+    assertThat(responseObserver.getValues()).hasSize(1);
+    Throwable t = responseObserver.getError();
+    if (t != null) {
+      throw new AssertionError(t);
+    }
+  }
+
+  /**
+   * Tests client per-message compression for streaming calls. The Java API does not support
+   * inspecting a message's compression level, so this is primarily intended to run against a gRPC
+   * C++ server.
+   */
+  public void clientCompressedStreaming(boolean probe) throws Exception {
+    final StreamingInputCallRequest expectCompressedRequest =
+        StreamingInputCallRequest.newBuilder()
+            .setExpectCompressed(BoolValue.newBuilder().setValue(true))
+            .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[27182])))
+            .build();
+    final StreamingInputCallRequest expectUncompressedRequest =
+        StreamingInputCallRequest.newBuilder()
+            .setExpectCompressed(BoolValue.newBuilder().setValue(false))
+            .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[45904])))
+            .build();
+    final StreamingInputCallResponse goldenResponse =
+        StreamingInputCallResponse.newBuilder().setAggregatedPayloadSize(73086).build();
+
+    StreamRecorder<StreamingInputCallResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<StreamingInputCallRequest> requestObserver =
+        asyncStub.streamingInputCall(responseObserver);
+
+    if (probe) {
+      // Send a non-compressed message with expectCompress=true. Servers supporting this test case
+      // should return INVALID_ARGUMENT.
+      requestObserver.onNext(expectCompressedRequest);
+      responseObserver.awaitCompletion(operationTimeoutMillis(), TimeUnit.MILLISECONDS);
+      Throwable e = responseObserver.getError();
+      assertNotNull("expected INVALID_ARGUMENT", e);
+      assertEquals(Status.INVALID_ARGUMENT.getCode(), Status.fromThrowable(e).getCode());
+    }
+
+    // Start a new stream
+    responseObserver = StreamRecorder.create();
+    @SuppressWarnings("unchecked")
+    ClientCallStreamObserver<StreamingInputCallRequest> clientCallStreamObserver =
+        (ClientCallStreamObserver)
+            asyncStub.withCompression("gzip").streamingInputCall(responseObserver);
+    clientCallStreamObserver.setMessageCompression(true);
+    clientCallStreamObserver.onNext(expectCompressedRequest);
+    clientCallStreamObserver.setMessageCompression(false);
+    clientCallStreamObserver.onNext(expectUncompressedRequest);
+    clientCallStreamObserver.onCompleted();
+    responseObserver.awaitCompletion();
+    assertSuccess(responseObserver);
+    assertEquals(goldenResponse, responseObserver.firstValue().get());
+  }
+
+  /**
+   * Tests server per-message compression in a streaming response. Ideally we would assert that the
+   * responses have the requested compression, but this is not supported by the API. Given a
+   * compliant server, this test will exercise the code path for receiving a compressed response but
+   * cannot itself verify that the response was compressed.
+   */
+  public void serverCompressedStreaming() throws Exception {
+    final StreamingOutputCallRequest request =
+        StreamingOutputCallRequest.newBuilder()
+            .addResponseParameters(
+                ResponseParameters.newBuilder()
+                    .setCompressed(BoolValue.newBuilder().setValue(true))
+                    .setSize(31415))
+            .addResponseParameters(
+                ResponseParameters.newBuilder()
+                    .setCompressed(BoolValue.newBuilder().setValue(false))
+                    .setSize(92653))
+            .build();
+    final List<StreamingOutputCallResponse> goldenResponses =
+        Arrays.asList(
+            StreamingOutputCallResponse.newBuilder()
+                .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[31415])))
+                .build(),
+            StreamingOutputCallResponse.newBuilder()
+                .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[92653])))
+                .build());
+
+    StreamRecorder<StreamingOutputCallResponse> recorder = StreamRecorder.create();
+    asyncStub.streamingOutputCall(request, recorder);
+    recorder.awaitCompletion();
+    assertSuccess(recorder);
+    assertEquals(goldenResponses, recorder.getValues());
+  }
+
+  @Test
+  public void pingPong() throws Exception {
+    final List<StreamingOutputCallRequest> requests = Arrays.asList(
+        StreamingOutputCallRequest.newBuilder()
+            .addResponseParameters(ResponseParameters.newBuilder()
+                .setSize(31415))
+            .setPayload(Payload.newBuilder()
+                .setBody(ByteString.copyFrom(new byte[27182])))
+            .build(),
+        StreamingOutputCallRequest.newBuilder()
+            .addResponseParameters(ResponseParameters.newBuilder()
+                .setSize(9))
+            .setPayload(Payload.newBuilder()
+                .setBody(ByteString.copyFrom(new byte[8])))
+            .build(),
+        StreamingOutputCallRequest.newBuilder()
+            .addResponseParameters(ResponseParameters.newBuilder()
+                .setSize(2653))
+            .setPayload(Payload.newBuilder()
+                .setBody(ByteString.copyFrom(new byte[1828])))
+            .build(),
+        StreamingOutputCallRequest.newBuilder()
+            .addResponseParameters(ResponseParameters.newBuilder()
+                .setSize(58979))
+            .setPayload(Payload.newBuilder()
+                .setBody(ByteString.copyFrom(new byte[45904])))
+            .build());
+    final List<StreamingOutputCallResponse> goldenResponses = Arrays.asList(
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setType(PayloadType.COMPRESSABLE)
+                .setBody(ByteString.copyFrom(new byte[31415])))
+            .build(),
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setType(PayloadType.COMPRESSABLE)
+                .setBody(ByteString.copyFrom(new byte[9])))
+            .build(),
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setType(PayloadType.COMPRESSABLE)
+                .setBody(ByteString.copyFrom(new byte[2653])))
+            .build(),
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setType(PayloadType.COMPRESSABLE)
+                .setBody(ByteString.copyFrom(new byte[58979])))
+            .build());
+
+    final ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(5);
+    StreamObserver<StreamingOutputCallRequest> requestObserver
+        = asyncStub.fullDuplexCall(new StreamObserver<StreamingOutputCallResponse>() {
+          @Override
+          public void onNext(StreamingOutputCallResponse response) {
+            queue.add(response);
+          }
+
+          @Override
+          public void onError(Throwable t) {
+            queue.add(t);
+          }
+
+          @Override
+          public void onCompleted() {
+            queue.add("Completed");
+          }
+        });
+    for (int i = 0; i < requests.size(); i++) {
+      assertNull(queue.peek());
+      requestObserver.onNext(requests.get(i));
+      Object actualResponse = queue.poll(operationTimeoutMillis(), TimeUnit.MILLISECONDS);
+      assertNotNull("Timed out waiting for response", actualResponse);
+      if (actualResponse instanceof Throwable) {
+        throw new AssertionError((Throwable) actualResponse);
+      }
+      assertEquals(goldenResponses.get(i), actualResponse);
+    }
+    requestObserver.onCompleted();
+    assertEquals("Completed", queue.poll(operationTimeoutMillis(), TimeUnit.MILLISECONDS));
+  }
+
+  @Test
+  public void emptyStream() throws Exception {
+    StreamRecorder<StreamingOutputCallResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<StreamingOutputCallRequest> requestObserver
+        = asyncStub.fullDuplexCall(responseObserver);
+    requestObserver.onCompleted();
+    responseObserver.awaitCompletion(operationTimeoutMillis(), TimeUnit.MILLISECONDS);
+  }
+
+  @Test
+  public void cancelAfterBegin() throws Exception {
+    StreamRecorder<StreamingInputCallResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<StreamingInputCallRequest> requestObserver =
+        asyncStub.streamingInputCall(responseObserver);
+    requestObserver.onError(new RuntimeException());
+    responseObserver.awaitCompletion();
+    assertEquals(Arrays.<StreamingInputCallResponse>asList(), responseObserver.getValues());
+    assertEquals(Status.Code.CANCELLED,
+        Status.fromThrowable(responseObserver.getError()).getCode());
+
+    if (metricsExpected()) {
+      MetricsRecord clientStartRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+      checkStartTags(clientStartRecord, "grpc.testing.TestService/StreamingInputCall");
+      // CensusStreamTracerModule record final status in the interceptor, thus is guaranteed to be
+      // recorded.  The tracer stats rely on the stream being created, which is not always the case
+      // in this test.  Therefore we don't check the tracer stats.
+      MetricsRecord clientEndRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+      checkEndTags(
+          clientEndRecord, "grpc.testing.TestService/StreamingInputCall",
+          Status.CANCELLED.getCode());
+      // Do not check server-side metrics, because the status on the server side is undetermined.
+    }
+  }
+
+  @Test
+  public void cancelAfterFirstResponse() throws Exception {
+    final StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder()
+        .addResponseParameters(ResponseParameters.newBuilder()
+            .setSize(31415))
+        .setPayload(Payload.newBuilder()
+            .setBody(ByteString.copyFrom(new byte[27182])))
+        .build();
+    final StreamingOutputCallResponse goldenResponse = StreamingOutputCallResponse.newBuilder()
+        .setPayload(Payload.newBuilder()
+            .setType(PayloadType.COMPRESSABLE)
+            .setBody(ByteString.copyFrom(new byte[31415])))
+        .build();
+
+    StreamRecorder<StreamingOutputCallResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<StreamingOutputCallRequest> requestObserver
+        = asyncStub.fullDuplexCall(responseObserver);
+    requestObserver.onNext(request);
+    assertEquals(goldenResponse, responseObserver.firstValue().get());
+    requestObserver.onError(new RuntimeException());
+    responseObserver.awaitCompletion(operationTimeoutMillis(), TimeUnit.MILLISECONDS);
+    assertEquals(1, responseObserver.getValues().size());
+    assertEquals(Status.Code.CANCELLED,
+                 Status.fromThrowable(responseObserver.getError()).getCode());
+
+    assertStatsTrace("grpc.testing.TestService/FullDuplexCall", Status.Code.CANCELLED);
+  }
+
+  @Test
+  public void fullDuplexCallShouldSucceed() throws Exception {
+    // Build the request.
+    List<Integer> responseSizes = Arrays.asList(50, 100, 150, 200);
+    StreamingOutputCallRequest.Builder streamingOutputBuilder =
+        StreamingOutputCallRequest.newBuilder();
+    streamingOutputBuilder.setResponseType(COMPRESSABLE);
+    for (Integer size : responseSizes) {
+      streamingOutputBuilder.addResponseParameters(
+          ResponseParameters.newBuilder().setSize(size).setIntervalUs(0));
+    }
+    final StreamingOutputCallRequest request = streamingOutputBuilder.build();
+
+    StreamRecorder<StreamingOutputCallResponse> recorder = StreamRecorder.create();
+    StreamObserver<StreamingOutputCallRequest> requestStream =
+        asyncStub.fullDuplexCall(recorder);
+
+    final int numRequests = 10;
+    List<StreamingOutputCallRequest> requests =
+        new ArrayList<>(numRequests);
+    for (int ix = numRequests; ix > 0; --ix) {
+      requests.add(request);
+      requestStream.onNext(request);
+    }
+    requestStream.onCompleted();
+    recorder.awaitCompletion();
+    assertSuccess(recorder);
+    assertEquals(responseSizes.size() * numRequests, recorder.getValues().size());
+    for (int ix = 0; ix < recorder.getValues().size(); ++ix) {
+      StreamingOutputCallResponse response = recorder.getValues().get(ix);
+      assertEquals(COMPRESSABLE, response.getPayload().getType());
+      int length = response.getPayload().getBody().size();
+      int expectedSize = responseSizes.get(ix % responseSizes.size());
+      assertEquals("comparison failed at index " + ix, expectedSize, length);
+    }
+
+    assertStatsTrace("grpc.testing.TestService/FullDuplexCall", Status.Code.OK, requests,
+        recorder.getValues());
+  }
+
+  @Test
+  public void halfDuplexCallShouldSucceed() throws Exception {
+    // Build the request.
+    List<Integer> responseSizes = Arrays.asList(50, 100, 150, 200);
+    StreamingOutputCallRequest.Builder streamingOutputBuilder =
+        StreamingOutputCallRequest.newBuilder();
+    streamingOutputBuilder.setResponseType(COMPRESSABLE);
+    for (Integer size : responseSizes) {
+      streamingOutputBuilder.addResponseParameters(
+          ResponseParameters.newBuilder().setSize(size).setIntervalUs(0));
+    }
+    final StreamingOutputCallRequest request = streamingOutputBuilder.build();
+
+    StreamRecorder<StreamingOutputCallResponse> recorder = StreamRecorder.create();
+    StreamObserver<StreamingOutputCallRequest> requestStream = asyncStub.halfDuplexCall(recorder);
+
+    final int numRequests = 10;
+    List<StreamingOutputCallRequest> requests =
+        new ArrayList<>(numRequests);
+    for (int ix = numRequests; ix > 0; --ix) {
+      requests.add(request);
+      requestStream.onNext(request);
+    }
+    requestStream.onCompleted();
+    recorder.awaitCompletion();
+    assertSuccess(recorder);
+    assertEquals(responseSizes.size() * numRequests, recorder.getValues().size());
+    for (int ix = 0; ix < recorder.getValues().size(); ++ix) {
+      StreamingOutputCallResponse response = recorder.getValues().get(ix);
+      assertEquals(COMPRESSABLE, response.getPayload().getType());
+      int length = response.getPayload().getBody().size();
+      int expectedSize = responseSizes.get(ix % responseSizes.size());
+      assertEquals("comparison failed at index " + ix, expectedSize, length);
+    }
+  }
+
+  @Test
+  public void serverStreamingShouldBeFlowControlled() throws Exception {
+    final StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder()
+        .setResponseType(COMPRESSABLE)
+        .addResponseParameters(ResponseParameters.newBuilder().setSize(100000))
+        .addResponseParameters(ResponseParameters.newBuilder().setSize(100001))
+        .build();
+    final List<StreamingOutputCallResponse> goldenResponses = Arrays.asList(
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setType(PayloadType.COMPRESSABLE)
+                .setBody(ByteString.copyFrom(new byte[100000]))).build(),
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setType(PayloadType.COMPRESSABLE)
+                .setBody(ByteString.copyFrom(new byte[100001]))).build());
+
+    long start = System.nanoTime();
+
+    final ArrayBlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(10);
+    ClientCall<StreamingOutputCallRequest, StreamingOutputCallResponse> call =
+        channel.newCall(TestServiceGrpc.getStreamingOutputCallMethod(), CallOptions.DEFAULT);
+    call.start(new ClientCall.Listener<StreamingOutputCallResponse>() {
+      @Override
+      public void onHeaders(Metadata headers) {}
+
+      @Override
+      public void onMessage(final StreamingOutputCallResponse message) {
+        queue.add(message);
+      }
+
+      @Override
+      public void onClose(Status status, Metadata trailers) {
+        queue.add(status);
+      }
+    }, new Metadata());
+    call.sendMessage(request);
+    call.halfClose();
+
+    // Time how long it takes to get the first response.
+    call.request(1);
+    assertEquals(goldenResponses.get(0),
+        queue.poll(operationTimeoutMillis(), TimeUnit.MILLISECONDS));
+    long firstCallDuration = System.nanoTime() - start;
+
+    // Without giving additional flow control, make sure that we don't get another response. We wait
+    // until we are comfortable the next message isn't coming. We may have very low nanoTime
+    // resolution (like on Windows) or be using a testing, in-process transport where message
+    // handling is instantaneous. In both cases, firstCallDuration may be 0, so round up sleep time
+    // to at least 1ms.
+    assertNull(queue.poll(Math.max(firstCallDuration * 4, 1 * 1000 * 1000), TimeUnit.NANOSECONDS));
+
+    // Make sure that everything still completes.
+    call.request(1);
+    assertEquals(goldenResponses.get(1),
+        queue.poll(operationTimeoutMillis(), TimeUnit.MILLISECONDS));
+    assertEquals(Status.OK, queue.poll(operationTimeoutMillis(), TimeUnit.MILLISECONDS));
+  }
+
+  @Test
+  public void veryLargeRequest() throws Exception {
+    assumeEnoughMemory();
+    final SimpleRequest request = SimpleRequest.newBuilder()
+        .setPayload(Payload.newBuilder()
+            .setType(PayloadType.COMPRESSABLE)
+            .setBody(ByteString.copyFrom(new byte[unaryPayloadLength()])))
+        .setResponseSize(10)
+        .setResponseType(PayloadType.COMPRESSABLE)
+        .build();
+    final SimpleResponse goldenResponse = SimpleResponse.newBuilder()
+        .setPayload(Payload.newBuilder()
+            .setType(PayloadType.COMPRESSABLE)
+            .setBody(ByteString.copyFrom(new byte[10])))
+        .build();
+
+    assertEquals(goldenResponse, blockingStub.unaryCall(request));
+  }
+
+  @Test
+  public void veryLargeResponse() throws Exception {
+    assumeEnoughMemory();
+    final SimpleRequest request = SimpleRequest.newBuilder()
+        .setResponseSize(unaryPayloadLength())
+        .setResponseType(PayloadType.COMPRESSABLE)
+        .build();
+    final SimpleResponse goldenResponse = SimpleResponse.newBuilder()
+        .setPayload(Payload.newBuilder()
+            .setType(PayloadType.COMPRESSABLE)
+            .setBody(ByteString.copyFrom(new byte[unaryPayloadLength()])))
+        .build();
+
+    assertEquals(goldenResponse, blockingStub.unaryCall(request));
+  }
+
+  @Test
+  public void exchangeMetadataUnaryCall() throws Exception {
+    TestServiceGrpc.TestServiceBlockingStub stub = blockingStub;
+
+    // Capture the metadata exchange
+    Metadata fixedHeaders = new Metadata();
+    // Send a context proto (as it's in the default extension registry)
+    Messages.SimpleContext contextValue =
+        Messages.SimpleContext.newBuilder().setValue("dog").build();
+    fixedHeaders.put(Util.METADATA_KEY, contextValue);
+    stub = MetadataUtils.attachHeaders(stub, fixedHeaders);
+    // .. and expect it to be echoed back in trailers
+    AtomicReference<Metadata> trailersCapture = new AtomicReference<Metadata>();
+    AtomicReference<Metadata> headersCapture = new AtomicReference<Metadata>();
+    stub = MetadataUtils.captureMetadata(stub, headersCapture, trailersCapture);
+
+    assertNotNull(stub.emptyCall(EMPTY));
+
+    // Assert that our side channel object is echoed back in both headers and trailers
+    Assert.assertEquals(contextValue, headersCapture.get().get(Util.METADATA_KEY));
+    Assert.assertEquals(contextValue, trailersCapture.get().get(Util.METADATA_KEY));
+  }
+
+  @Test
+  public void exchangeMetadataStreamingCall() throws Exception {
+    TestServiceGrpc.TestServiceStub stub = asyncStub;
+
+    // Capture the metadata exchange
+    Metadata fixedHeaders = new Metadata();
+    // Send a context proto (as it's in the default extension registry)
+    Messages.SimpleContext contextValue =
+        Messages.SimpleContext.newBuilder().setValue("dog").build();
+    fixedHeaders.put(Util.METADATA_KEY, contextValue);
+    stub = MetadataUtils.attachHeaders(stub, fixedHeaders);
+    // .. and expect it to be echoed back in trailers
+    AtomicReference<Metadata> trailersCapture = new AtomicReference<Metadata>();
+    AtomicReference<Metadata> headersCapture = new AtomicReference<Metadata>();
+    stub = MetadataUtils.captureMetadata(stub, headersCapture, trailersCapture);
+
+    List<Integer> responseSizes = Arrays.asList(50, 100, 150, 200);
+    Messages.StreamingOutputCallRequest.Builder streamingOutputBuilder =
+        Messages.StreamingOutputCallRequest.newBuilder();
+    streamingOutputBuilder.setResponseType(COMPRESSABLE);
+    for (Integer size : responseSizes) {
+      streamingOutputBuilder.addResponseParameters(
+          ResponseParameters.newBuilder().setSize(size).setIntervalUs(0));
+    }
+    final Messages.StreamingOutputCallRequest request = streamingOutputBuilder.build();
+
+    StreamRecorder<Messages.StreamingOutputCallResponse> recorder = StreamRecorder.create();
+    StreamObserver<Messages.StreamingOutputCallRequest> requestStream =
+        stub.fullDuplexCall(recorder);
+
+    final int numRequests = 10;
+    List<StreamingOutputCallRequest> requests =
+        new ArrayList<>(numRequests);
+
+    for (int ix = numRequests; ix > 0; --ix) {
+      requests.add(request);
+      requestStream.onNext(request);
+    }
+    requestStream.onCompleted();
+    recorder.awaitCompletion();
+    assertSuccess(recorder);
+    org.junit.Assert.assertEquals(responseSizes.size() * numRequests, recorder.getValues().size());
+
+    // Assert that our side channel object is echoed back in both headers and trailers
+    Assert.assertEquals(contextValue, headersCapture.get().get(Util.METADATA_KEY));
+    Assert.assertEquals(contextValue, trailersCapture.get().get(Util.METADATA_KEY));
+  }
+
+  @Test
+  public void sendsTimeoutHeader() {
+    Assume.assumeTrue("can not capture request headers on server side", server != null);
+    long configuredTimeoutMinutes = 100;
+    TestServiceGrpc.TestServiceBlockingStub stub =
+        blockingStub.withDeadlineAfter(configuredTimeoutMinutes, TimeUnit.MINUTES);
+    stub.emptyCall(EMPTY);
+    long transferredTimeoutMinutes = TimeUnit.NANOSECONDS.toMinutes(
+        requestHeadersCapture.get().get(GrpcUtil.TIMEOUT_KEY));
+    Assert.assertTrue(
+        "configuredTimeoutMinutes=" + configuredTimeoutMinutes
+            + ", transferredTimeoutMinutes=" + transferredTimeoutMinutes,
+        configuredTimeoutMinutes - transferredTimeoutMinutes >= 0
+            && configuredTimeoutMinutes - transferredTimeoutMinutes <= 1);
+  }
+
+  @Test
+  public void deadlineNotExceeded() {
+    // warm up the channel and JVM
+    blockingStub.emptyCall(Empty.getDefaultInstance());
+    blockingStub
+        .withDeadlineAfter(10, TimeUnit.SECONDS)
+        .streamingOutputCall(StreamingOutputCallRequest.newBuilder()
+            .addResponseParameters(ResponseParameters.newBuilder()
+                .setIntervalUs(0))
+                .build()).next();
+  }
+
+  @Test
+  public void deadlineExceeded() throws Exception {
+    // warm up the channel and JVM
+    blockingStub.emptyCall(Empty.getDefaultInstance());
+    TestServiceGrpc.TestServiceBlockingStub stub =
+        blockingStub.withDeadlineAfter(10, TimeUnit.MILLISECONDS);
+    StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder()
+        .addResponseParameters(ResponseParameters.newBuilder()
+            .setIntervalUs((int) TimeUnit.SECONDS.toMicros(20)))
+        .build();
+    try {
+      stub.streamingOutputCall(request).next();
+      fail("Expected deadline to be exceeded");
+    } catch (StatusRuntimeException ex) {
+      assertEquals(Status.DEADLINE_EXCEEDED.getCode(), ex.getStatus().getCode());
+    }
+
+    assertStatsTrace("grpc.testing.TestService/EmptyCall", Status.Code.OK);
+    if (metricsExpected()) {
+      // Stream may not have been created before deadline is exceeded, thus we don't test the tracer
+      // stats.
+      MetricsRecord clientStartRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+      checkStartTags(clientStartRecord, "grpc.testing.TestService/StreamingOutputCall");
+      MetricsRecord clientEndRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+      checkEndTags(
+          clientEndRecord,
+          "grpc.testing.TestService/StreamingOutputCall",
+          Status.Code.DEADLINE_EXCEEDED);
+      // Do not check server-side metrics, because the status on the server side is undetermined.
+    }
+  }
+
+  @Test
+  public void deadlineExceededServerStreaming() throws Exception {
+    // warm up the channel and JVM
+    blockingStub.emptyCall(Empty.getDefaultInstance());
+    ResponseParameters.Builder responseParameters = ResponseParameters.newBuilder()
+        .setSize(1)
+        .setIntervalUs(10000);
+    StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder()
+        .setResponseType(PayloadType.COMPRESSABLE)
+        .addResponseParameters(responseParameters)
+        .addResponseParameters(responseParameters)
+        .addResponseParameters(responseParameters)
+        .addResponseParameters(responseParameters)
+        .build();
+    StreamRecorder<StreamingOutputCallResponse> recorder = StreamRecorder.create();
+    asyncStub
+        .withDeadlineAfter(30, TimeUnit.MILLISECONDS)
+        .streamingOutputCall(request, recorder);
+    recorder.awaitCompletion();
+    assertEquals(Status.DEADLINE_EXCEEDED.getCode(),
+        Status.fromThrowable(recorder.getError()).getCode());
+    assertStatsTrace("grpc.testing.TestService/EmptyCall", Status.Code.OK);
+    if (metricsExpected()) {
+      // Stream may not have been created when deadline is exceeded, thus we don't check tracer
+      // stats.
+      MetricsRecord clientStartRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+      checkStartTags(clientStartRecord, "grpc.testing.TestService/StreamingOutputCall");
+      MetricsRecord clientEndRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+      checkEndTags(
+          clientEndRecord,
+          "grpc.testing.TestService/StreamingOutputCall",
+          Status.Code.DEADLINE_EXCEEDED);
+      // Do not check server-side metrics, because the status on the server side is undetermined.
+    }
+  }
+
+  @Test
+  public void deadlineInPast() throws Exception {
+    // Test once with idle channel and once with active channel
+    try {
+      blockingStub
+          .withDeadlineAfter(-10, TimeUnit.SECONDS)
+          .emptyCall(Empty.getDefaultInstance());
+      fail("Should have thrown");
+    } catch (StatusRuntimeException ex) {
+      assertEquals(Status.Code.DEADLINE_EXCEEDED, ex.getStatus().getCode());
+    }
+
+    // CensusStreamTracerModule record final status in the interceptor, thus is guaranteed to be
+    // recorded.  The tracer stats rely on the stream being created, which is not the case if
+    // deadline is exceeded before the call is created. Therefore we don't check the tracer stats.
+    if (metricsExpected()) {
+      MetricsRecord clientStartRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+      checkStartTags(clientStartRecord, "grpc.testing.TestService/EmptyCall");
+      MetricsRecord clientEndRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+      checkEndTags(
+          clientEndRecord, "grpc.testing.TestService/EmptyCall",
+          Status.DEADLINE_EXCEEDED.getCode());
+    }
+
+    // warm up the channel
+    blockingStub.emptyCall(Empty.getDefaultInstance());
+    try {
+      blockingStub
+          .withDeadlineAfter(-10, TimeUnit.SECONDS)
+          .emptyCall(Empty.getDefaultInstance());
+      fail("Should have thrown");
+    } catch (StatusRuntimeException ex) {
+      assertEquals(Status.Code.DEADLINE_EXCEEDED, ex.getStatus().getCode());
+    }
+    assertStatsTrace("grpc.testing.TestService/EmptyCall", Status.Code.OK);
+    if (metricsExpected()) {
+      MetricsRecord clientStartRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+      checkStartTags(clientStartRecord, "grpc.testing.TestService/EmptyCall");
+      MetricsRecord clientEndRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+      checkEndTags(
+          clientEndRecord, "grpc.testing.TestService/EmptyCall",
+          Status.DEADLINE_EXCEEDED.getCode());
+    }
+  }
+
+  @Test
+  public void maxInboundSize_exact() {
+    StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder()
+        .addResponseParameters(ResponseParameters.newBuilder().setSize(1))
+        .build();
+
+    MethodDescriptor<StreamingOutputCallRequest, StreamingOutputCallResponse> md =
+        TestServiceGrpc.getStreamingOutputCallMethod();
+    ByteSizeMarshaller<StreamingOutputCallResponse> mar =
+        new ByteSizeMarshaller<StreamingOutputCallResponse>(md.getResponseMarshaller());
+    blockingServerStreamingCall(
+        blockingStub.getChannel(),
+        md.toBuilder(md.getRequestMarshaller(), mar).build(),
+        blockingStub.getCallOptions(),
+        request)
+        .next();
+
+    int size = mar.lastInSize;
+
+    TestServiceGrpc.TestServiceBlockingStub stub =
+        blockingStub.withMaxInboundMessageSize(size);
+
+    stub.streamingOutputCall(request).next();
+  }
+
+  @Test
+  public void maxInboundSize_tooBig() {
+    StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder()
+        .addResponseParameters(ResponseParameters.newBuilder().setSize(1))
+        .build();
+    
+    MethodDescriptor<StreamingOutputCallRequest, StreamingOutputCallResponse> md =
+        TestServiceGrpc.getStreamingOutputCallMethod();
+    ByteSizeMarshaller<StreamingOutputCallRequest> mar =
+        new ByteSizeMarshaller<StreamingOutputCallRequest>(md.getRequestMarshaller());
+    blockingServerStreamingCall(
+        blockingStub.getChannel(),
+        md.toBuilder(mar, md.getResponseMarshaller()).build(),
+        blockingStub.getCallOptions(),
+        request)
+        .next();
+
+    int size = mar.lastOutSize;
+
+    TestServiceGrpc.TestServiceBlockingStub stub =
+        blockingStub.withMaxInboundMessageSize(size - 1);
+
+    try {
+      stub.streamingOutputCall(request).next();
+      fail();
+    } catch (StatusRuntimeException ex) {
+      Status s = ex.getStatus();
+      assertThat(s.getCode()).named(s.toString()).isEqualTo(Status.Code.RESOURCE_EXHAUSTED);
+      assertThat(Throwables.getStackTraceAsString(ex)).contains("exceeds maximum");
+    }
+  }
+
+  @Test
+  public void maxOutboundSize_exact() {
+    StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder()
+        .addResponseParameters(ResponseParameters.newBuilder().setSize(1))
+        .build();
+
+    MethodDescriptor<StreamingOutputCallRequest, StreamingOutputCallResponse> md =
+        TestServiceGrpc.getStreamingOutputCallMethod();
+    ByteSizeMarshaller<StreamingOutputCallRequest> mar =
+        new ByteSizeMarshaller<StreamingOutputCallRequest>(md.getRequestMarshaller());
+    blockingServerStreamingCall(
+        blockingStub.getChannel(),
+        md.toBuilder(mar, md.getResponseMarshaller()).build(),
+        blockingStub.getCallOptions(),
+        request)
+        .next();
+
+    int size = mar.lastOutSize;
+
+    TestServiceGrpc.TestServiceBlockingStub stub =
+        blockingStub.withMaxOutboundMessageSize(size);
+
+    stub.streamingOutputCall(request).next();
+  }
+
+  @Test
+  public void maxOutboundSize_tooBig() {
+    // set at least one field to ensure the size is non-zero.
+    StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder()
+        .addResponseParameters(ResponseParameters.newBuilder().setSize(1))
+        .build();
+
+
+    MethodDescriptor<StreamingOutputCallRequest, StreamingOutputCallResponse> md =
+        TestServiceGrpc.getStreamingOutputCallMethod();
+    ByteSizeMarshaller<StreamingOutputCallRequest> mar =
+        new ByteSizeMarshaller<StreamingOutputCallRequest>(md.getRequestMarshaller());
+    blockingServerStreamingCall(
+        blockingStub.getChannel(),
+        md.toBuilder(mar, md.getResponseMarshaller()).build(),
+        blockingStub.getCallOptions(),
+        request)
+        .next();
+
+    TestServiceGrpc.TestServiceBlockingStub stub =
+        blockingStub.withMaxOutboundMessageSize(mar.lastOutSize - 1);
+    try {
+      stub.streamingOutputCall(request).next();
+      fail();
+    } catch (StatusRuntimeException ex) {
+      Status s = ex.getStatus();
+      assertThat(s.getCode()).named(s.toString()).isEqualTo(Status.Code.CANCELLED);
+      assertThat(Throwables.getStackTraceAsString(ex)).contains("message too large");
+    }
+  }
+
+  protected int unaryPayloadLength() {
+    // 10MiB.
+    return 10485760;
+  }
+
+  @Test
+  public void gracefulShutdown() throws Exception {
+    final List<StreamingOutputCallRequest> requests = Arrays.asList(
+        StreamingOutputCallRequest.newBuilder()
+            .addResponseParameters(ResponseParameters.newBuilder()
+                .setSize(3))
+            .setPayload(Payload.newBuilder()
+                .setBody(ByteString.copyFrom(new byte[2])))
+            .build(),
+        StreamingOutputCallRequest.newBuilder()
+            .addResponseParameters(ResponseParameters.newBuilder()
+                .setSize(1))
+            .setPayload(Payload.newBuilder()
+                .setBody(ByteString.copyFrom(new byte[7])))
+            .build(),
+        StreamingOutputCallRequest.newBuilder()
+            .addResponseParameters(ResponseParameters.newBuilder()
+                .setSize(4))
+            .setPayload(Payload.newBuilder()
+                .setBody(ByteString.copyFrom(new byte[1])))
+            .build());
+    final List<StreamingOutputCallResponse> goldenResponses = Arrays.asList(
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setType(PayloadType.COMPRESSABLE)
+                .setBody(ByteString.copyFrom(new byte[3])))
+            .build(),
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setType(PayloadType.COMPRESSABLE)
+                .setBody(ByteString.copyFrom(new byte[1])))
+            .build(),
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+                .setType(PayloadType.COMPRESSABLE)
+                .setBody(ByteString.copyFrom(new byte[4])))
+            .build());
+
+    final ArrayBlockingQueue<StreamingOutputCallResponse> responses =
+        new ArrayBlockingQueue<StreamingOutputCallResponse>(3);
+    final SettableFuture<Void> completed = SettableFuture.create();
+    final SettableFuture<Void> errorSeen = SettableFuture.create();
+    StreamObserver<StreamingOutputCallResponse> responseObserver =
+        new StreamObserver<StreamingOutputCallResponse>() {
+
+          @Override
+          public void onNext(StreamingOutputCallResponse value) {
+            responses.add(value);
+          }
+
+          @Override
+          public void onError(Throwable t) {
+            errorSeen.set(null);
+          }
+
+          @Override
+          public void onCompleted() {
+            completed.set(null);
+          }
+        };
+    StreamObserver<StreamingOutputCallRequest> requestObserver
+        = asyncStub.fullDuplexCall(responseObserver);
+    requestObserver.onNext(requests.get(0));
+    assertEquals(
+        goldenResponses.get(0), responses.poll(operationTimeoutMillis(), TimeUnit.MILLISECONDS));
+    // Initiate graceful shutdown.
+    channel.shutdown();
+    requestObserver.onNext(requests.get(1));
+    assertEquals(
+        goldenResponses.get(1), responses.poll(operationTimeoutMillis(), TimeUnit.MILLISECONDS));
+    // The previous ping-pong could have raced with the shutdown, but this one certainly shouldn't.
+    requestObserver.onNext(requests.get(2));
+    assertEquals(
+        goldenResponses.get(2), responses.poll(operationTimeoutMillis(), TimeUnit.MILLISECONDS));
+    assertFalse(completed.isDone());
+    requestObserver.onCompleted();
+    completed.get(operationTimeoutMillis(), TimeUnit.MILLISECONDS);
+    assertFalse(errorSeen.isDone());
+  }
+
+  @Test
+  public void customMetadata() throws Exception {
+    final int responseSize = 314159;
+    final int requestSize = 271828;
+    final SimpleRequest request = SimpleRequest.newBuilder()
+        .setResponseSize(responseSize)
+        .setResponseType(PayloadType.COMPRESSABLE)
+        .setPayload(Payload.newBuilder()
+            .setBody(ByteString.copyFrom(new byte[requestSize])))
+        .build();
+    final StreamingOutputCallRequest streamingRequest = StreamingOutputCallRequest.newBuilder()
+        .addResponseParameters(ResponseParameters.newBuilder().setSize(responseSize))
+        .setResponseType(PayloadType.COMPRESSABLE)
+        .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[requestSize])))
+        .build();
+    final SimpleResponse goldenResponse = SimpleResponse.newBuilder()
+        .setPayload(Payload.newBuilder()
+            .setType(PayloadType.COMPRESSABLE)
+            .setBody(ByteString.copyFrom(new byte[responseSize])))
+        .build();
+    final StreamingOutputCallResponse goldenStreamingResponse =
+        StreamingOutputCallResponse.newBuilder()
+            .setPayload(Payload.newBuilder()
+            .setType(PayloadType.COMPRESSABLE)
+            .setBody(ByteString.copyFrom(new byte[responseSize])))
+        .build();
+    final byte[] trailingBytes =
+        {(byte) 0xa, (byte) 0xb, (byte) 0xa, (byte) 0xb, (byte) 0xa, (byte) 0xb};
+
+    // Test UnaryCall
+    Metadata metadata = new Metadata();
+    metadata.put(Util.ECHO_INITIAL_METADATA_KEY, "test_initial_metadata_value");
+    metadata.put(Util.ECHO_TRAILING_METADATA_KEY, trailingBytes);
+    TestServiceGrpc.TestServiceBlockingStub blockingStub = this.blockingStub;
+    blockingStub = MetadataUtils.attachHeaders(blockingStub, metadata);
+    AtomicReference<Metadata> headersCapture = new AtomicReference<Metadata>();
+    AtomicReference<Metadata> trailersCapture = new AtomicReference<Metadata>();
+    blockingStub = MetadataUtils.captureMetadata(blockingStub, headersCapture, trailersCapture);
+    SimpleResponse response = blockingStub.unaryCall(request);
+
+    assertEquals(goldenResponse, response);
+    assertEquals("test_initial_metadata_value",
+        headersCapture.get().get(Util.ECHO_INITIAL_METADATA_KEY));
+    assertTrue(
+        Arrays.equals(trailingBytes, trailersCapture.get().get(Util.ECHO_TRAILING_METADATA_KEY)));
+    assertStatsTrace("grpc.testing.TestService/UnaryCall", Status.Code.OK,
+        Collections.singleton(request), Collections.singleton(goldenResponse));
+
+    // Test FullDuplexCall
+    metadata = new Metadata();
+    metadata.put(Util.ECHO_INITIAL_METADATA_KEY, "test_initial_metadata_value");
+    metadata.put(Util.ECHO_TRAILING_METADATA_KEY, trailingBytes);
+    TestServiceGrpc.TestServiceStub stub = asyncStub;
+    stub = MetadataUtils.attachHeaders(stub, metadata);
+    headersCapture = new AtomicReference<Metadata>();
+    trailersCapture = new AtomicReference<Metadata>();
+    stub = MetadataUtils.captureMetadata(stub, headersCapture, trailersCapture);
+
+    StreamRecorder<Messages.StreamingOutputCallResponse> recorder = StreamRecorder.create();
+    StreamObserver<Messages.StreamingOutputCallRequest> requestStream =
+        stub.fullDuplexCall(recorder);
+    requestStream.onNext(streamingRequest);
+    requestStream.onCompleted();
+    recorder.awaitCompletion();
+
+    assertSuccess(recorder);
+    assertEquals(goldenStreamingResponse, recorder.firstValue().get());
+    assertEquals("test_initial_metadata_value",
+        headersCapture.get().get(Util.ECHO_INITIAL_METADATA_KEY));
+    assertTrue(
+        Arrays.equals(trailingBytes, trailersCapture.get().get(Util.ECHO_TRAILING_METADATA_KEY)));
+    assertStatsTrace("grpc.testing.TestService/FullDuplexCall", Status.Code.OK,
+        Collections.singleton(streamingRequest), Collections.singleton(goldenStreamingResponse));
+  }
+
+  @Test(timeout = 10000)
+  public void censusContextsPropagated() {
+    Assume.assumeTrue("Skip the test because server is not in the same process.", server != null);
+    Span clientParentSpan = Tracing.getTracer().spanBuilder("Test.interopTest").startSpan();
+    // A valid ID is guaranteed to be unique, so we can verify it is actually propagated.
+    assertTrue(clientParentSpan.getContext().getTraceId().isValid());
+    Context ctx =
+        Context.ROOT.withValues(
+            TAG_CONTEXT_KEY,
+            tagger.emptyBuilder().put(
+                StatsTestUtils.EXTRA_TAG, TagValue.create("extra value")).build(),
+            ContextUtils.CONTEXT_SPAN_KEY,
+            clientParentSpan);
+    Context origCtx = ctx.attach();
+    try {
+      blockingStub.unaryCall(SimpleRequest.getDefaultInstance());
+      Context serverCtx = contextCapture.get();
+      assertNotNull(serverCtx);
+
+      FakeTagContext statsCtx = (FakeTagContext) TAG_CONTEXT_KEY.get(serverCtx);
+      assertNotNull(statsCtx);
+      Map<TagKey, TagValue> tags = statsCtx.getTags();
+      boolean tagFound = false;
+      for (Map.Entry<TagKey, TagValue> tag : tags.entrySet()) {
+        if (tag.getKey().equals(StatsTestUtils.EXTRA_TAG)) {
+          assertEquals(TagValue.create("extra value"), tag.getValue());
+          tagFound = true;
+        }
+      }
+      assertTrue("tag not found", tagFound);
+
+      Span span = CONTEXT_SPAN_KEY.get(serverCtx);
+      assertNotNull(span);
+      SpanContext spanContext = span.getContext();
+      assertEquals(clientParentSpan.getContext().getTraceId(), spanContext.getTraceId());
+    } finally {
+      ctx.detach(origCtx);
+    }
+  }
+
+  @Test
+  public void statusCodeAndMessage() throws Exception {
+    int errorCode = 2;
+    String errorMessage = "test status message";
+    EchoStatus responseStatus = EchoStatus.newBuilder()
+        .setCode(errorCode)
+        .setMessage(errorMessage)
+        .build();
+    SimpleRequest simpleRequest = SimpleRequest.newBuilder()
+        .setResponseStatus(responseStatus)
+        .build();
+    StreamingOutputCallRequest streamingRequest = StreamingOutputCallRequest.newBuilder()
+        .setResponseStatus(responseStatus)
+        .build();
+
+    // Test UnaryCall
+    try {
+      blockingStub.unaryCall(simpleRequest);
+      fail();
+    } catch (StatusRuntimeException e) {
+      assertEquals(Status.UNKNOWN.getCode(), e.getStatus().getCode());
+      assertEquals(errorMessage, e.getStatus().getDescription());
+    }
+    assertStatsTrace("grpc.testing.TestService/UnaryCall", Status.Code.UNKNOWN);
+
+    // Test FullDuplexCall
+    @SuppressWarnings("unchecked")
+    StreamObserver<StreamingOutputCallResponse> responseObserver =
+        mock(StreamObserver.class);
+    StreamObserver<StreamingOutputCallRequest> requestObserver
+        = asyncStub.fullDuplexCall(responseObserver);
+    requestObserver.onNext(streamingRequest);
+    requestObserver.onCompleted();
+
+    ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
+    verify(responseObserver, timeout(operationTimeoutMillis())).onError(captor.capture());
+    assertEquals(Status.UNKNOWN.getCode(), Status.fromThrowable(captor.getValue()).getCode());
+    assertEquals(errorMessage, Status.fromThrowable(captor.getValue()).getDescription());
+    verifyNoMoreInteractions(responseObserver);
+    assertStatsTrace("grpc.testing.TestService/FullDuplexCall", Status.Code.UNKNOWN);
+  }
+
+  @Test
+  public void specialStatusMessage() throws Exception {
+    int errorCode = 2;
+    String errorMessage = "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n";
+    SimpleRequest simpleRequest = SimpleRequest.newBuilder()
+        .setResponseStatus(EchoStatus.newBuilder()
+            .setCode(errorCode)
+            .setMessage(errorMessage)
+            .build())
+        .build();
+
+    try {
+      blockingStub.unaryCall(simpleRequest);
+      fail();
+    } catch (StatusRuntimeException e) {
+      assertEquals(Status.UNKNOWN.getCode(), e.getStatus().getCode());
+      assertEquals(errorMessage, e.getStatus().getDescription());
+    }
+    assertStatsTrace("grpc.testing.TestService/UnaryCall", Status.Code.UNKNOWN);
+  }
+
+  /** Sends an rpc to an unimplemented method within TestService. */
+  @Test
+  public void unimplementedMethod() {
+    try {
+      blockingStub.unimplementedCall(Empty.getDefaultInstance());
+      fail();
+    } catch (StatusRuntimeException e) {
+      assertEquals(Status.UNIMPLEMENTED.getCode(), e.getStatus().getCode());
+    }
+
+    assertClientStatsTrace("grpc.testing.TestService/UnimplementedCall",
+        Status.Code.UNIMPLEMENTED);
+  }
+
+  /** Sends an rpc to an unimplemented service on the server. */
+  @Test
+  public void unimplementedService() {
+    UnimplementedServiceGrpc.UnimplementedServiceBlockingStub stub =
+        UnimplementedServiceGrpc.newBlockingStub(channel).withInterceptors(tracerSetupInterceptor);
+    try {
+      stub.unimplementedCall(Empty.getDefaultInstance());
+      fail();
+    } catch (StatusRuntimeException e) {
+      assertEquals(Status.UNIMPLEMENTED.getCode(), e.getStatus().getCode());
+    }
+
+    assertStatsTrace("grpc.testing.UnimplementedService/UnimplementedCall",
+        Status.Code.UNIMPLEMENTED);
+  }
+
+  /** Start a fullDuplexCall which the server will not respond, and verify the deadline expires. */
+  @SuppressWarnings("MissingFail")
+  @Test
+  public void timeoutOnSleepingServer() throws Exception {
+    TestServiceGrpc.TestServiceStub stub =
+        asyncStub.withDeadlineAfter(1, TimeUnit.MILLISECONDS);
+
+    StreamRecorder<StreamingOutputCallResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<StreamingOutputCallRequest> requestObserver
+        = stub.fullDuplexCall(responseObserver);
+
+    StreamingOutputCallRequest request = StreamingOutputCallRequest.newBuilder()
+        .setPayload(Payload.newBuilder()
+            .setBody(ByteString.copyFrom(new byte[27182])))
+        .build();
+    try {
+      requestObserver.onNext(request);
+    } catch (IllegalStateException expected) {
+      // This can happen if the stream has already been terminated due to deadline exceeded.
+    }
+
+    assertTrue(responseObserver.awaitCompletion(operationTimeoutMillis(), TimeUnit.MILLISECONDS));
+    assertEquals(0, responseObserver.getValues().size());
+    assertEquals(Status.DEADLINE_EXCEEDED.getCode(),
+                 Status.fromThrowable(responseObserver.getError()).getCode());
+
+    if (metricsExpected()) {
+      // CensusStreamTracerModule record final status in the interceptor, thus is guaranteed to be
+      // recorded.  The tracer stats rely on the stream being created, which is not always the case
+      // in this test, thus we will not check that.
+      MetricsRecord clientStartRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+      checkStartTags(clientStartRecord, "grpc.testing.TestService/FullDuplexCall");
+      MetricsRecord clientEndRecord = clientStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+      checkEndTags(
+          clientEndRecord,
+          "grpc.testing.TestService/FullDuplexCall",
+          Status.DEADLINE_EXCEEDED.getCode());
+    }
+  }
+
+  /** Sends a large unary rpc with service account credentials. */
+  public void serviceAccountCreds(String jsonKey, InputStream credentialsStream, String authScope)
+      throws Exception {
+    // cast to ServiceAccountCredentials to double-check the right type of object was created.
+    GoogleCredentials credentials =
+        ServiceAccountCredentials.class.cast(GoogleCredentials.fromStream(credentialsStream));
+    credentials = credentials.createScoped(Arrays.<String>asList(authScope));
+    TestServiceGrpc.TestServiceBlockingStub stub = blockingStub
+        .withCallCredentials(MoreCallCredentials.from(credentials));
+    final SimpleRequest request = SimpleRequest.newBuilder()
+        .setFillUsername(true)
+        .setFillOauthScope(true)
+        .setResponseSize(314159)
+        .setResponseType(PayloadType.COMPRESSABLE)
+        .setPayload(Payload.newBuilder()
+            .setBody(ByteString.copyFrom(new byte[271828])))
+        .build();
+
+    final SimpleResponse response = stub.unaryCall(request);
+    assertFalse(response.getUsername().isEmpty());
+    assertTrue("Received username: " + response.getUsername(),
+        jsonKey.contains(response.getUsername()));
+    assertFalse(response.getOauthScope().isEmpty());
+    assertTrue("Received oauth scope: " + response.getOauthScope(),
+        authScope.contains(response.getOauthScope()));
+
+    final SimpleResponse goldenResponse = SimpleResponse.newBuilder()
+        .setOauthScope(response.getOauthScope())
+        .setUsername(response.getUsername())
+        .setPayload(Payload.newBuilder()
+            .setType(PayloadType.COMPRESSABLE)
+            .setBody(ByteString.copyFrom(new byte[314159])))
+        .build();
+    assertEquals(goldenResponse, response);
+  }
+
+  /** Sends a large unary rpc with compute engine credentials. */
+  public void computeEngineCreds(String serviceAccount, String oauthScope) throws Exception {
+    ComputeEngineCredentials credentials = ComputeEngineCredentials.create();
+    TestServiceGrpc.TestServiceBlockingStub stub = blockingStub
+        .withCallCredentials(MoreCallCredentials.from(credentials));
+    final SimpleRequest request = SimpleRequest.newBuilder()
+        .setFillUsername(true)
+        .setFillOauthScope(true)
+        .setResponseSize(314159)
+        .setResponseType(PayloadType.COMPRESSABLE)
+        .setPayload(Payload.newBuilder()
+            .setBody(ByteString.copyFrom(new byte[271828])))
+        .build();
+
+    final SimpleResponse response = stub.unaryCall(request);
+    assertEquals(serviceAccount, response.getUsername());
+    assertFalse(response.getOauthScope().isEmpty());
+    assertTrue("Received oauth scope: " + response.getOauthScope(),
+        oauthScope.contains(response.getOauthScope()));
+
+    final SimpleResponse goldenResponse = SimpleResponse.newBuilder()
+        .setOauthScope(response.getOauthScope())
+        .setUsername(response.getUsername())
+        .setPayload(Payload.newBuilder()
+            .setType(PayloadType.COMPRESSABLE)
+            .setBody(ByteString.copyFrom(new byte[314159])))
+        .build();
+    assertEquals(goldenResponse, response);
+  }
+
+  /** Test JWT-based auth. */
+  public void jwtTokenCreds(InputStream serviceAccountJson) throws Exception {
+    final SimpleRequest request = SimpleRequest.newBuilder()
+        .setResponseType(PayloadType.COMPRESSABLE)
+        .setResponseSize(314159)
+        .setPayload(Payload.newBuilder()
+            .setBody(ByteString.copyFrom(new byte[271828])))
+        .setFillUsername(true)
+        .build();
+
+    ServiceAccountCredentials credentials = (ServiceAccountCredentials)
+        GoogleCredentials.fromStream(serviceAccountJson);
+    TestServiceGrpc.TestServiceBlockingStub stub = blockingStub
+        .withCallCredentials(MoreCallCredentials.from(credentials));
+    SimpleResponse response = stub.unaryCall(request);
+    assertEquals(credentials.getClientEmail(), response.getUsername());
+    assertEquals(314159, response.getPayload().getBody().size());
+  }
+
+  /** Sends a unary rpc with raw oauth2 access token credentials. */
+  public void oauth2AuthToken(String jsonKey, InputStream credentialsStream, String authScope)
+      throws Exception {
+    GoogleCredentials utilCredentials =
+        GoogleCredentials.fromStream(credentialsStream);
+    utilCredentials = utilCredentials.createScoped(Arrays.<String>asList(authScope));
+    AccessToken accessToken = utilCredentials.refreshAccessToken();
+
+    OAuth2Credentials credentials = OAuth2Credentials.create(accessToken);
+
+    TestServiceGrpc.TestServiceBlockingStub stub = blockingStub
+        .withCallCredentials(MoreCallCredentials.from(credentials));
+    final SimpleRequest request = SimpleRequest.newBuilder()
+        .setFillUsername(true)
+        .setFillOauthScope(true)
+        .build();
+
+    final SimpleResponse response = stub.unaryCall(request);
+    assertFalse(response.getUsername().isEmpty());
+    assertTrue("Received username: " + response.getUsername(),
+        jsonKey.contains(response.getUsername()));
+    assertFalse(response.getOauthScope().isEmpty());
+    assertTrue("Received oauth scope: " + response.getOauthScope(),
+        authScope.contains(response.getOauthScope()));
+  }
+
+  /** Sends a unary rpc with "per rpc" raw oauth2 access token credentials. */
+  public void perRpcCreds(String jsonKey, InputStream credentialsStream, String oauthScope)
+      throws Exception {
+    // In gRpc Java, we don't have per Rpc credentials, user can use an intercepted stub only once
+    // for that purpose.
+    // So, this test is identical to oauth2_auth_token test.
+    oauth2AuthToken(jsonKey, credentialsStream, oauthScope);
+  }
+
+  protected static void assertSuccess(StreamRecorder<?> recorder) {
+    if (recorder.getError() != null) {
+      throw new AssertionError(recorder.getError());
+    }
+  }
+
+  /** Helper for getting remote address {@link io.grpc.ServerCall#getAttributes()} */
+  protected SocketAddress obtainRemoteClientAddr() {
+    TestServiceGrpc.TestServiceBlockingStub stub =
+        blockingStub.withDeadlineAfter(5, TimeUnit.SECONDS);
+
+    stub.unaryCall(SimpleRequest.getDefaultInstance());
+
+    return serverCallCapture.get().getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
+  }
+
+  /** Helper for asserting TLS info in SSLSession {@link io.grpc.ServerCall#getAttributes()} */
+  protected void assertX500SubjectDn(String tlsInfo) {
+    TestServiceGrpc.TestServiceBlockingStub stub =
+        blockingStub.withDeadlineAfter(5, TimeUnit.SECONDS);
+
+    stub.unaryCall(SimpleRequest.getDefaultInstance());
+
+    List<Certificate> certificates = Lists.newArrayList();
+    SSLSession sslSession =
+        serverCallCapture.get().getAttributes().get(Grpc.TRANSPORT_ATTR_SSL_SESSION);
+    try {
+      certificates = Arrays.asList(sslSession.getPeerCertificates());
+    } catch (SSLPeerUnverifiedException e) {
+      // Should never happen
+      throw new AssertionError(e);
+    }
+
+    X509Certificate x509cert = (X509Certificate) certificates.get(0);
+
+    assertEquals(1, certificates.size());
+    assertEquals(tlsInfo, x509cert.getSubjectDN().toString());
+  }
+
+  protected int operationTimeoutMillis() {
+    return 5000;
+  }
+
+  /**
+   * Some tests run on memory constrained environments.  Rather than OOM, just give up.  64 is
+   * chosen as a maximum amount of memory a large test would need.
+   */
+  private static void assumeEnoughMemory() {
+    Runtime r = Runtime.getRuntime();
+    long usedMem = r.totalMemory() - r.freeMemory();
+    long actuallyFreeMemory = r.maxMemory() - usedMem;
+    Assume.assumeTrue(
+        actuallyFreeMemory + " is not sufficient to run this test",
+        actuallyFreeMemory >= 64 * 1024 * 1024);
+  }
+
+  /**
+   * Wrapper around {@link Mockito#verify}, to keep log spam down on failure.
+   */
+  private static <T> T verify(T mock, VerificationMode mode) {
+    try {
+      return Mockito.verify(mock, mode);
+    } catch (final AssertionError e) {
+      String msg = e.getMessage();
+      if (msg.length() >= 256) {
+        // AssertionError(String, Throwable) only present in Android API 19+
+        throw new AssertionError(msg.substring(0, 256)) {
+          @Override
+          public synchronized Throwable getCause() {
+            return e;
+          }
+        };
+      }
+      throw e;
+    }
+  }
+
+  /**
+   * Wrapper around {@link Mockito#verify}, to keep log spam down on failure.
+   */
+  private static void verifyNoMoreInteractions(Object... mocks) {
+    try {
+      Mockito.verifyNoMoreInteractions(mocks);
+    } catch (final AssertionError e) {
+      String msg = e.getMessage();
+      if (msg.length() >= 256) {
+        // AssertionError(String, Throwable) only present in Android API 19+
+        throw new AssertionError(msg.substring(0, 256)) {
+          @Override
+          public synchronized Throwable getCause() {
+            return e;
+          }
+        };
+      }
+      throw e;
+    }
+  }
+
+  /**
+   * Poll the next metrics record and check it against the provided information, including the
+   * message sizes.
+   */
+  private void assertStatsTrace(String method, Status.Code status,
+      Collection<? extends MessageLite> requests,
+      Collection<? extends MessageLite> responses) {
+    assertClientStatsTrace(method, status, requests, responses);
+    assertServerStatsTrace(method, status, requests, responses);
+  }
+
+  /**
+   * Poll the next metrics record and check it against the provided information, without checking
+   * the message sizes.
+   */
+  private void assertStatsTrace(String method, Status.Code status) {
+    assertStatsTrace(method, status, null, null);
+  }
+
+  private void assertClientStatsTrace(String method, Status.Code code,
+      Collection<? extends MessageLite> requests, Collection<? extends MessageLite> responses) {
+    // Tracer-based stats
+    TestClientStreamTracer tracer = clientStreamTracers.poll();
+    assertNotNull(tracer);
+    assertTrue(tracer.getOutboundHeaders());
+    // assertClientStatsTrace() is called right after application receives status,
+    // but streamClosed() may be called slightly later than that.  So we need a timeout.
+    try {
+      assertTrue(tracer.await(5, TimeUnit.SECONDS));
+    } catch (InterruptedException e) {
+      throw new AssertionError(e);
+    }
+    assertEquals(code, tracer.getStatus().getCode());
+
+    if (requests != null && responses != null) {
+      checkTracers(tracer, requests, responses);
+    }
+    if (metricsExpected()) {
+      // CensusStreamTracerModule records final status in interceptor, which is guaranteed to be
+      // done before application receives status.
+      MetricsRecord clientStartRecord = clientStatsRecorder.pollRecord();
+      checkStartTags(clientStartRecord, method);
+      MetricsRecord clientEndRecord = clientStatsRecorder.pollRecord();
+      checkEndTags(clientEndRecord, method, code);
+
+      if (requests != null && responses != null) {
+        checkCensus(clientEndRecord, false, requests, responses);
+      }
+    }
+  }
+
+  private void assertClientStatsTrace(String method, Status.Code status) {
+    assertClientStatsTrace(method, status, null, null);
+  }
+
+  @SuppressWarnings("AssertionFailureIgnored") // Failure is checked in the end by the passed flag.
+  private void assertServerStatsTrace(String method, Status.Code code,
+      Collection<? extends MessageLite> requests, Collection<? extends MessageLite> responses) {
+    if (server == null) {
+      // Server is not in the same process.  We can't check server-side stats.
+      return;
+    }
+
+    if (metricsExpected()) {
+      MetricsRecord serverStartRecord;
+      MetricsRecord serverEndRecord;
+      try {
+        // On the server, the stats is finalized in ServerStreamListener.closed(), which can be
+        // run after the client receives the final status.  So we use a timeout.
+        serverStartRecord = serverStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+        serverEndRecord = serverStatsRecorder.pollRecord(5, TimeUnit.SECONDS);
+      } catch (InterruptedException e) {
+        throw new RuntimeException(e);
+      }
+      assertNotNull(serverStartRecord);
+      assertNotNull(serverEndRecord);
+      checkStartTags(serverStartRecord, method);
+      checkEndTags(serverEndRecord, method, code);
+      if (requests != null && responses != null) {
+        checkCensus(serverEndRecord, true, requests, responses);
+      }
+    }
+
+    ServerStreamTracerInfo tracerInfo;
+    tracerInfo = serverStreamTracers.poll();
+    assertNotNull(tracerInfo);
+    assertEquals(method, tracerInfo.fullMethodName);
+    assertNotNull(tracerInfo.tracer.contextCapture);
+    // On the server, streamClosed() may be called after the client receives the final status.
+    // So we use a timeout.
+    try {
+      assertTrue(tracerInfo.tracer.await(1, TimeUnit.SECONDS));
+    } catch (InterruptedException e) {
+      throw new AssertionError(e);
+    }
+    assertEquals(code, tracerInfo.tracer.getStatus().getCode());
+    if (requests != null && responses != null) {
+      checkTracers(tracerInfo.tracer, responses, requests);
+    }
+  }
+
+  private static void checkStartTags(MetricsRecord record, String methodName) {
+    assertNotNull("record is not null", record);
+    TagValue methodNameTag = record.tags.get(RpcMeasureConstants.RPC_METHOD);
+    assertNotNull("method name tagged", methodNameTag);
+    assertEquals("method names match", methodName, methodNameTag.asString());
+  }
+
+  private static void checkEndTags(
+      MetricsRecord record, String methodName, Status.Code status) {
+    assertNotNull("record is not null", record);
+    TagValue methodNameTag = record.tags.get(RpcMeasureConstants.RPC_METHOD);
+    assertNotNull("method name tagged", methodNameTag);
+    assertEquals("method names match", methodName, methodNameTag.asString());
+    TagValue statusTag = record.tags.get(RpcMeasureConstants.RPC_STATUS);
+    assertNotNull("status tagged", statusTag);
+    assertEquals(status.toString(), statusTag.asString());
+  }
+
+  /**
+   * Check information recorded by tracers.
+   */
+  private void checkTracers(
+      TestStreamTracer tracer,
+      Collection<? extends MessageLite> sentMessages,
+      Collection<? extends MessageLite> receivedMessages) {
+    long uncompressedSentSize = 0;
+    int seqNo = 0;
+    for (MessageLite msg : sentMessages) {
+      assertThat(tracer.nextOutboundEvent()).isEqualTo(String.format("outboundMessage(%d)", seqNo));
+      assertThat(tracer.nextOutboundEvent()).matches(
+          String.format("outboundMessageSent\\(%d, -?[0-9]+, -?[0-9]+\\)", seqNo));
+      seqNo++;
+      uncompressedSentSize += msg.getSerializedSize();
+    }
+    assertNull(tracer.nextOutboundEvent());
+    long uncompressedReceivedSize = 0;
+    seqNo = 0;
+    for (MessageLite msg : receivedMessages) {
+      assertThat(tracer.nextInboundEvent()).isEqualTo(String.format("inboundMessage(%d)", seqNo));
+      assertThat(tracer.nextInboundEvent()).matches(
+          String.format("inboundMessageRead\\(%d, -?[0-9]+, -?[0-9]+\\)", seqNo)); 
+      uncompressedReceivedSize += msg.getSerializedSize();
+      seqNo++;
+    }
+    assertNull(tracer.nextInboundEvent());
+    if (metricsExpected()) {
+      assertEquals(uncompressedSentSize, tracer.getOutboundUncompressedSize());
+      assertEquals(uncompressedReceivedSize, tracer.getInboundUncompressedSize());
+    }
+  }
+
+  /**
+   * Check information recorded by Census.
+   */
+  private void checkCensus(MetricsRecord record, boolean isServer,
+      Collection<? extends MessageLite> requests, Collection<? extends MessageLite> responses) {
+    int uncompressedRequestsSize = 0;
+    for (MessageLite request : requests) {
+      uncompressedRequestsSize += request.getSerializedSize();
+    }
+    int uncompressedResponsesSize = 0;
+    for (MessageLite response : responses) {
+      uncompressedResponsesSize += response.getSerializedSize();
+    }
+    if (isServer) {
+      assertEquals(
+          requests.size(),
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_REQUEST_COUNT));
+      assertEquals(
+          responses.size(),
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_RESPONSE_COUNT));
+      assertEquals(
+          uncompressedRequestsSize,
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_UNCOMPRESSED_REQUEST_BYTES));
+      assertEquals(
+          uncompressedResponsesSize,
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_SERVER_UNCOMPRESSED_RESPONSE_BYTES));
+      assertNotNull(record.getMetric(RpcMeasureConstants.RPC_SERVER_SERVER_LATENCY));
+      // It's impossible to get the expected wire sizes because it may be compressed, so we just
+      // check if they are recorded.
+      assertNotNull(record.getMetric(RpcMeasureConstants.RPC_SERVER_REQUEST_BYTES));
+      assertNotNull(record.getMetric(RpcMeasureConstants.RPC_SERVER_RESPONSE_BYTES));
+    } else {
+      assertEquals(
+          requests.size(),
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_REQUEST_COUNT));
+      assertEquals(
+          responses.size(),
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_RESPONSE_COUNT));
+      assertEquals(
+          uncompressedRequestsSize,
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_REQUEST_BYTES));
+      assertEquals(
+          uncompressedResponsesSize,
+          record.getMetricAsLongOrFail(RpcMeasureConstants.RPC_CLIENT_UNCOMPRESSED_RESPONSE_BYTES));
+      assertNotNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_ROUNDTRIP_LATENCY));
+      // It's impossible to get the expected wire sizes because it may be compressed, so we just
+      // check if they are recorded.
+      assertNotNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_REQUEST_BYTES));
+      assertNotNull(record.getMetric(RpcMeasureConstants.RPC_CLIENT_RESPONSE_BYTES));
+    }
+  }
+
+  /**
+   * Captures the request attributes. Useful for testing ServerCalls.
+   * {@link ServerCall#getAttributes()}
+   */
+  private static ServerInterceptor recordServerCallInterceptor(
+      final AtomicReference<ServerCall<?, ?>> serverCallCapture) {
+    return new ServerInterceptor() {
+      @Override
+      public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+          ServerCall<ReqT, RespT> call,
+          Metadata requestHeaders,
+          ServerCallHandler<ReqT, RespT> next) {
+        serverCallCapture.set(call);
+        return next.startCall(call, requestHeaders);
+      }
+    };
+  }
+
+  private static ServerInterceptor recordContextInterceptor(
+      final AtomicReference<Context> contextCapture) {
+    return new ServerInterceptor() {
+      @Override
+      public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+          ServerCall<ReqT, RespT> call,
+          Metadata requestHeaders,
+          ServerCallHandler<ReqT, RespT> next) {
+        contextCapture.set(Context.current());
+        return next.startCall(call, requestHeaders);
+      }
+    };
+  }
+
+  /**
+   * A marshaller that record input and output sizes.
+   */
+  private static final class ByteSizeMarshaller<T> implements MethodDescriptor.Marshaller<T> {
+
+    private final MethodDescriptor.Marshaller<T> delegate;
+    volatile int lastOutSize;
+    volatile int lastInSize;
+
+    ByteSizeMarshaller(MethodDescriptor.Marshaller<T> delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public InputStream stream(T value) {
+      InputStream is = delegate.stream(value);
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      try {
+        lastOutSize = (int) ByteStreams.copy(is, baos);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+      return new ByteArrayInputStream(baos.toByteArray());
+    }
+
+    @Override
+    public T parse(InputStream stream) {
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      try {
+        lastInSize = (int) ByteStreams.copy(stream, baos);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+      return delegate.parse(new ByteArrayInputStream(baos.toByteArray()));
+    }
+  }
+}
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/Http2Client.java b/interop-testing/src/main/java/io/grpc/testing/integration/Http2Client.java
new file mode 100644
index 0000000..edf6b7c
--- /dev/null
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/Http2Client.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static java.util.concurrent.Executors.newFixedThreadPool;
+
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.protobuf.ByteString;
+import io.grpc.ManagedChannel;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.netty.NegotiationType;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.integration.Messages.Payload;
+import io.grpc.testing.integration.Messages.PayloadType;
+import io.grpc.testing.integration.Messages.SimpleRequest;
+import io.grpc.testing.integration.Messages.SimpleResponse;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Client application for the {@link TestServiceGrpc.TestServiceImplBase} that runs through a series
+ * of HTTP/2 interop tests. The tests are designed to simulate incorrect behavior on the part of the
+ * server. Some of the test cases require server-side checks and do not have assertions within the
+ * client code.
+ */
+public final class Http2Client {
+  private static final Logger logger = Logger.getLogger(Http2Client.class.getName());
+
+  /**
+   * The main application allowing this client to be launched from the command line.
+   */
+  public static void main(String[] args) throws Exception {
+    final Http2Client client = new Http2Client();
+    client.parseArgs(args);
+    client.setUp();
+
+    Runtime.getRuntime().addShutdownHook(new Thread() {
+      @Override
+      public void run() {
+        try {
+          client.shutdown();
+        } catch (Exception e) {
+          logger.log(Level.SEVERE, e.getMessage(), e);
+        }
+      }
+    });
+
+    try {
+      client.run();
+    } finally {
+      client.shutdown();
+    }
+  }
+
+  private String serverHost = "localhost";
+  private int serverPort = 8080;
+  private String testCase = Http2TestCases.RST_AFTER_DATA.name();
+
+  private Tester tester = new Tester();
+  private ListeningExecutorService threadpool;
+
+  protected ManagedChannel channel;
+  protected TestServiceGrpc.TestServiceBlockingStub blockingStub;
+  protected TestServiceGrpc.TestServiceStub asyncStub;
+
+  private void parseArgs(String[] args) {
+    boolean usage = false;
+    for (String arg : args) {
+      if (!arg.startsWith("--")) {
+        System.err.println("All arguments must start with '--': " + arg);
+        usage = true;
+        break;
+      }
+      String[] parts = arg.substring(2).split("=", 2);
+      String key = parts[0];
+      if ("help".equals(key)) {
+        usage = true;
+        break;
+      }
+      if (parts.length != 2) {
+        System.err.println("All arguments must be of the form --arg=value");
+        usage = true;
+        break;
+      }
+      String value = parts[1];
+      if ("server_host".equals(key)) {
+        serverHost = value;
+      } else if ("server_port".equals(key)) {
+        serverPort = Integer.parseInt(value);
+      } else if ("test_case".equals(key)) {
+        testCase = value;
+      } else {
+        System.err.println("Unknown argument: " + key);
+        usage = true;
+        break;
+      }
+    }
+    if (usage) {
+      Http2Client c = new Http2Client();
+      System.out.println(
+          "Usage: [ARGS...]"
+              + "\n"
+              + "\n  --server_host=HOST          Server to connect to. Default " + c.serverHost
+              + "\n  --server_port=PORT          Port to connect to. Default " + c.serverPort
+              + "\n  --test_case=TESTCASE        Test case to run. Default " + c.testCase
+              + "\n    Valid options:"
+              + validTestCasesHelpText()
+      );
+      System.exit(1);
+    }
+  }
+
+  private void setUp() {
+    channel = createChannel();
+    blockingStub = TestServiceGrpc.newBlockingStub(channel);
+    asyncStub = TestServiceGrpc.newStub(channel);
+  }
+
+  private void shutdown() {
+    try {
+      if (channel != null) {
+        channel.shutdownNow();
+        channel.awaitTermination(1, TimeUnit.SECONDS);
+      }
+    } catch (Exception ex) {
+      throw new RuntimeException(ex);
+    }
+
+    try {
+      if (threadpool != null) {
+        threadpool.shutdownNow();
+      }
+    } catch (Exception ex) {
+      throw new RuntimeException(ex);
+    }
+  }
+
+  private void run() {
+    logger.info("Running test " + testCase);
+    try {
+      runTest(Http2TestCases.fromString(testCase));
+    } catch (RuntimeException ex) {
+      throw ex;
+    } catch (Exception ex) {
+      throw new RuntimeException(ex);
+    }
+    logger.info("Test completed.");
+  }
+
+  private void runTest(Http2TestCases testCase) throws Exception {
+    switch (testCase) {
+      case RST_AFTER_HEADER:
+        tester.rstAfterHeader();
+        break;
+      case RST_AFTER_DATA:
+        tester.rstAfterData();
+        break;
+      case RST_DURING_DATA:
+        tester.rstDuringData();
+        break;
+      case GOAWAY:
+        tester.goAway();
+        break;
+      case PING:
+        tester.ping();
+        break;
+      case MAX_STREAMS:
+        tester.maxStreams();
+        break;
+      default:
+        throw new IllegalArgumentException("Unknown test case: " + testCase);
+    }
+  }
+
+  private class Tester {
+    private final int timeoutSeconds = 180;
+
+    private final int responseSize = 314159;
+    private final int payloadSize = 271828;
+    private final SimpleRequest simpleRequest = SimpleRequest.newBuilder()
+        .setResponseSize(responseSize)
+        .setResponseType(PayloadType.COMPRESSABLE)
+        .setPayload(Payload.newBuilder().setBody(ByteString.copyFrom(new byte[payloadSize])))
+        .build();
+    final SimpleResponse goldenResponse = SimpleResponse.newBuilder()
+        .setPayload(Payload.newBuilder()
+            .setType(PayloadType.COMPRESSABLE)
+            .setBody(ByteString.copyFrom(new byte[responseSize])))
+        .build();
+
+    private void rstAfterHeader() throws Exception {
+      try {
+        blockingStub.unaryCall(simpleRequest);
+        throw new AssertionError("Expected call to fail");
+      } catch (StatusRuntimeException ex) {
+        assertRstStreamReceived(ex.getStatus());
+      }
+    }
+
+    private void rstAfterData() throws Exception {
+      // Use async stub to verify data is received.
+      RstStreamObserver responseObserver = new RstStreamObserver();
+      asyncStub.unaryCall(simpleRequest, responseObserver);
+      if (!responseObserver.awaitCompletion(timeoutSeconds, TimeUnit.SECONDS)) {
+        throw new AssertionError("Operation timed out");
+      }
+      if (responseObserver.getError() == null) {
+        throw new AssertionError("Expected call to fail");
+      }
+      assertRstStreamReceived(Status.fromThrowable(responseObserver.getError()));
+      if (responseObserver.getResponses().size() != 1) {
+        throw new AssertionError("Expected one response");
+      }
+    }
+
+    private void rstDuringData() throws Exception {
+      // Use async stub to verify no data is received.
+      RstStreamObserver responseObserver = new RstStreamObserver();
+      asyncStub.unaryCall(simpleRequest, responseObserver);
+      if (!responseObserver.awaitCompletion(timeoutSeconds, TimeUnit.SECONDS)) {
+        throw new AssertionError("Operation timed out");
+      }
+      if (responseObserver.getError() == null) {
+        throw new AssertionError("Expected call to fail");
+      }
+      assertRstStreamReceived(Status.fromThrowable(responseObserver.getError()));
+      if (responseObserver.getResponses().size() != 0) {
+        throw new AssertionError("Expected zero responses");
+      }
+    }
+
+    private void goAway() throws Exception {
+      assertResponseEquals(blockingStub.unaryCall(simpleRequest), goldenResponse);
+      TimeUnit.SECONDS.sleep(1);
+      assertResponseEquals(blockingStub.unaryCall(simpleRequest), goldenResponse);
+    }
+
+    private void ping() throws Exception {
+      assertResponseEquals(blockingStub.unaryCall(simpleRequest), goldenResponse);
+    }
+
+    private void maxStreams() throws Exception {
+      final int numThreads = 10;
+
+      // Preliminary call to ensure MAX_STREAMS setting is received by the client.
+      assertResponseEquals(blockingStub.unaryCall(simpleRequest), goldenResponse);
+
+      threadpool = MoreExecutors.listeningDecorator(newFixedThreadPool(numThreads));
+      List<ListenableFuture<?>> workerFutures = new ArrayList<ListenableFuture<?>>();
+      for (int i = 0; i < numThreads; i++) {
+        workerFutures.add(threadpool.submit(new MaxStreamsWorker(i, simpleRequest)));
+      }
+      ListenableFuture<?> f = Futures.allAsList(workerFutures);
+      f.get(timeoutSeconds, TimeUnit.SECONDS);
+    }
+
+    private class RstStreamObserver implements StreamObserver<SimpleResponse> {
+      private final CountDownLatch latch = new CountDownLatch(1);
+      private final List<SimpleResponse> responses = new ArrayList<>();
+      private Throwable error;
+
+      @Override
+      public void onNext(SimpleResponse value) {
+        responses.add(value);
+      }
+
+      @Override
+      public void onError(Throwable t) {
+        error = t;
+        latch.countDown();
+      }
+
+      @Override
+      public void onCompleted() {
+        latch.countDown();
+      }
+
+      public List<SimpleResponse> getResponses() {
+        return responses;
+      }
+
+      public Throwable getError() {
+        return error;
+      }
+
+      public boolean awaitCompletion(long timeout, TimeUnit unit) throws Exception {
+        return latch.await(timeout, unit);
+      }
+    }
+
+    private class MaxStreamsWorker implements Runnable {
+      int threadNum;
+      SimpleRequest request;
+
+      MaxStreamsWorker(int threadNum, SimpleRequest request) {
+        this.threadNum = threadNum;
+        this.request = request;
+      }
+
+      @Override
+      public void run() {
+        Thread.currentThread().setName("thread:" + threadNum);
+        try {
+          TestServiceGrpc.TestServiceBlockingStub blockingStub =
+              TestServiceGrpc.newBlockingStub(channel);
+          assertResponseEquals(blockingStub.unaryCall(simpleRequest), goldenResponse);
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    }
+
+    private void assertRstStreamReceived(Status status) {
+      if (!status.getCode().equals(Status.Code.UNAVAILABLE)) {
+        throw new AssertionError("Wrong status code. Expected: " + Status.Code.UNAVAILABLE
+            + " Received: " + status.getCode());
+      }
+      String http2ErrorPrefix = "HTTP/2 error code: NO_ERROR";
+      if (status.getDescription() == null
+          || !status.getDescription().startsWith(http2ErrorPrefix)) {
+        throw new AssertionError("Wrong HTTP/2 error code. Expected: " + http2ErrorPrefix
+            + " Received: " + status.getDescription());
+      }
+    }
+
+    private void assertResponseEquals(SimpleResponse response, SimpleResponse goldenResponse) {
+      if (!response.equals(goldenResponse)) {
+        throw new AssertionError("Incorrect response received");
+      }
+    }
+  }
+
+  private ManagedChannel createChannel() {
+    InetAddress address;
+    try {
+      address = InetAddress.getByName(serverHost);
+    } catch (UnknownHostException ex) {
+      throw new RuntimeException(ex);
+    }
+    return NettyChannelBuilder.forAddress(new InetSocketAddress(address, serverPort))
+        .negotiationType(NegotiationType.PLAINTEXT)
+        .build();
+  }
+
+  private static String validTestCasesHelpText() {
+    StringBuilder builder = new StringBuilder();
+    for (Http2TestCases testCase : Http2TestCases.values()) {
+      String strTestcase = testCase.name().toLowerCase();
+      builder.append("\n      ")
+          .append(strTestcase)
+          .append(": ")
+          .append(testCase.description());
+    }
+    return builder.toString();
+  }
+}
+
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/Http2TestCases.java b/interop-testing/src/main/java/io/grpc/testing/integration/Http2TestCases.java
new file mode 100644
index 0000000..b064ee7
--- /dev/null
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/Http2TestCases.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Enum of HTTP/2 interop test cases.
+ */
+public enum Http2TestCases {
+  RST_AFTER_HEADER("server resets stream after sending header"),
+  RST_AFTER_DATA("server resets stream after sending data"),
+  RST_DURING_DATA("server resets stream in the middle of sending data"),
+  GOAWAY("server sends goaway after first request and asserts second request uses new connection"),
+  PING("server sends pings during request and verifies client response"),
+  MAX_STREAMS("server verifies that the client respects MAX_STREAMS setting");
+
+  private final String description;
+
+  Http2TestCases(String description) {
+    this.description = description;
+  }
+
+  /**
+   * Returns a description of the test case.
+   */
+  public String description() {
+    return description;
+  }
+
+  /**
+   * Returns the {@link Http2TestCases} matching the string {@code s}. The
+   * matching is case insensitive.
+   */
+  public static Http2TestCases fromString(String s) {
+    Preconditions.checkNotNull(s, "s");
+    try {
+      return Http2TestCases.valueOf(s.toUpperCase());
+    } catch (IllegalArgumentException ex) {
+      throw new IllegalArgumentException("Invalid test case: " + s);
+    }
+  }
+}
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/ReconnectTestClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/ReconnectTestClient.java
new file mode 100644
index 0000000..a89e878
--- /dev/null
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/ReconnectTestClient.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.ManagedChannel;
+import io.grpc.StatusRuntimeException;
+import io.grpc.netty.NegotiationType;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.okhttp.OkHttpChannelBuilder;
+import io.grpc.testing.integration.EmptyProtos.Empty;
+import io.grpc.testing.integration.Messages.ReconnectInfo;
+
+/**
+ * Verifies the client is reconnecting the server with correct backoffs
+ *
+ * <p>See the <a href="https://github.com/grpc/grpc/blob/master/doc/connection-backoff-interop-test-description.md">Test Spec</a>.
+ */
+public class ReconnectTestClient {
+  private static final int TEST_TIME_MS = 540 * 1000;
+
+  private int serverControlPort = 8080;
+  private int serverRetryPort = 8081;
+  private boolean useOkhttp = false;
+  private ManagedChannel controlChannel;
+  private ManagedChannel retryChannel;
+  private ReconnectServiceGrpc.ReconnectServiceBlockingStub controlStub;
+  private ReconnectServiceGrpc.ReconnectServiceBlockingStub retryStub;
+
+  private void parseArgs(String[] args) {
+    for (String arg : args) {
+      if (!arg.startsWith("--")) {
+        System.err.println("All arguments must start with '--': " + arg);
+        System.exit(1);
+      }
+      String[] parts = arg.substring(2).split("=", 2);
+      String key = parts[0];
+      String value = parts[1];
+      if ("server_control_port".equals(key)) {
+        serverControlPort = Integer.parseInt(value);
+      } else if ("server_retry_port".equals(key)) {
+        serverRetryPort = Integer.parseInt(value);
+      } else if ("use_okhttp".equals(key)) {
+        useOkhttp = Boolean.parseBoolean(value);
+      } else {
+        System.err.println("Unknown argument: " + key);
+        System.exit(1);
+      }
+    }
+  }
+
+  private void runTest() throws Exception {
+    try {
+      controlChannel = NettyChannelBuilder.forAddress("127.0.0.1", serverControlPort)
+          .negotiationType(NegotiationType.PLAINTEXT).build();
+      controlStub = ReconnectServiceGrpc.newBlockingStub(controlChannel);
+      if (useOkhttp) {
+        retryChannel =
+            OkHttpChannelBuilder.forAddress("127.0.0.1", serverRetryPort)
+                .useTransportSecurity()
+                .build();
+      } else {
+        retryChannel = NettyChannelBuilder.forAddress("127.0.0.1", serverRetryPort)
+            .negotiationType(NegotiationType.TLS).build();
+      }
+      retryStub = ReconnectServiceGrpc.newBlockingStub(retryChannel);
+      controlStub.start(Empty.getDefaultInstance());
+
+      long startTimeStamp = System.currentTimeMillis();
+      while ((System.currentTimeMillis() - startTimeStamp) < TEST_TIME_MS) {
+        try {
+          retryStub.start(Empty.getDefaultInstance());
+        } catch (StatusRuntimeException expected) {
+          // Make CheckStyle happy.
+        }
+        Thread.sleep(50);
+      }
+      ReconnectInfo info = controlStub.stop(Empty.getDefaultInstance());
+      assertTrue(info.getPassed());
+    } finally {
+      controlChannel.shutdownNow();
+      retryChannel.shutdownNow();
+    }
+  }
+
+  /**
+   * The main application allowing this client to be launched from the command line.
+   */
+  public static void main(String[] args) {
+    ReconnectTestClient client = new ReconnectTestClient();
+    client.parseArgs(args);
+    System.out.println("Starting test:");
+    try {
+      client.runTest();
+      System.out.println("Finished successfully");
+      System.exit(0);
+    } catch (Throwable e) {
+      e.printStackTrace();
+      System.err.println("Test failed!");
+      System.exit(1);
+    }
+  }
+}
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/StressTestClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/StressTestClient.java
new file mode 100644
index 0000000..62f1825
--- /dev/null
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/StressTestClient.java
@@ -0,0 +1,660 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.shuffle;
+import static java.util.Collections.singletonList;
+import static java.util.concurrent.Executors.newFixedThreadPool;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterators;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.Status;
+import io.grpc.StatusException;
+import io.grpc.internal.testing.TestUtils;
+import io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.NegotiationType;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.stub.StreamObserver;
+import io.netty.handler.ssl.SslContext;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A stress test client following the
+ * <a href="https://github.com/grpc/grpc/blob/master/tools/run_tests/stress_test/STRESS_CLIENT_SPEC.md">
+ * specifications</a> of the gRPC stress testing framework.
+ */
+public class StressTestClient {
+
+  private static final Logger log = Logger.getLogger(StressTestClient.class.getName());
+
+  /**
+   * The main application allowing this client to be launched from the command line.
+   */
+  public static void main(String... args) throws Exception {
+    final StressTestClient client = new StressTestClient();
+    client.parseArgs(args);
+
+    // Attempt an orderly shutdown, if the JVM is shutdown via a signal.
+    Runtime.getRuntime().addShutdownHook(new Thread() {
+      @Override
+      public void run() {
+        client.shutdown();
+      }
+    });
+
+    try {
+      client.startMetricsService();
+      client.runStressTest();
+      client.blockUntilStressTestComplete();
+    } catch (Exception e) {
+      log.log(Level.WARNING, "The stress test client encountered an error!", e);
+    } finally {
+      client.shutdown();
+    }
+  }
+
+  private static final int WORKER_GRACE_PERIOD_SECS = 30;
+
+  private List<InetSocketAddress> addresses =
+      singletonList(new InetSocketAddress("localhost", 8080));
+  private List<TestCaseWeightPair> testCaseWeightPairs = new ArrayList<>();
+
+  private String serverHostOverride;
+  private boolean useTls = false;
+  private boolean useTestCa = false;
+  private int durationSecs = -1;
+  private int channelsPerServer = 1;
+  private int stubsPerChannel = 1;
+  private int metricsPort = 8081;
+
+  private Server metricsServer;
+  private final Map<String, Metrics.GaugeResponse> gauges =
+      new ConcurrentHashMap<String, Metrics.GaugeResponse>();
+
+  private volatile boolean shutdown;
+
+  /**
+   * List of futures that {@link #blockUntilStressTestComplete()} waits for.
+   */
+  private final List<ListenableFuture<?>> workerFutures =
+      new ArrayList<ListenableFuture<?>>();
+  private final List<ManagedChannel> channels = new ArrayList<>();
+  private ListeningExecutorService threadpool;
+
+  @VisibleForTesting
+  void parseArgs(String[] args) {
+    boolean usage = false;
+    String serverAddresses = "";
+    for (String arg : args) {
+      if (!arg.startsWith("--")) {
+        System.err.println("All arguments must start with '--': " + arg);
+        usage = true;
+        break;
+      }
+      String[] parts = arg.substring(2).split("=", 2);
+      String key = parts[0];
+      if ("help".equals(key)) {
+        usage = true;
+        break;
+      }
+      if (parts.length != 2) {
+        System.err.println("All arguments must be of the form --arg=value");
+        usage = true;
+        break;
+      }
+      String value = parts[1];
+      if ("server_addresses".equals(key)) {
+        // May need to apply server host overrides to the addresses, so delay processing
+        serverAddresses = value;
+      } else if ("server_host_override".equals(key)) {
+        serverHostOverride = value;
+      } else if ("use_tls".equals(key)) {
+        useTls = Boolean.parseBoolean(value);
+      } else if ("use_test_ca".equals(key)) {
+        useTestCa = Boolean.parseBoolean(value);
+      } else if ("test_cases".equals(key)) {
+        testCaseWeightPairs = parseTestCases(value);
+      } else if ("test_duration_secs".equals(key)) {
+        durationSecs = Integer.valueOf(value);
+      } else if ("num_channels_per_server".equals(key)) {
+        channelsPerServer = Integer.valueOf(value);
+      } else if ("num_stubs_per_channel".equals(key)) {
+        stubsPerChannel = Integer.valueOf(value);
+      } else if ("metrics_port".equals(key)) {
+        metricsPort = Integer.valueOf(value);
+      } else {
+        System.err.println("Unknown argument: " + key);
+        usage = true;
+        break;
+      }
+    }
+
+    if (!usage && !serverAddresses.isEmpty()) {
+      addresses = parseServerAddresses(serverAddresses);
+      usage = addresses.isEmpty();
+    }
+
+    if (usage) {
+      StressTestClient c = new StressTestClient();
+      System.err.println(
+          "Usage: [ARGS...]"
+              + "\n"
+              + "\n  --server_host_override=HOST    Claimed identification expected of server."
+              + "\n                                 Defaults to server host"
+              + "\n  --server_addresses=<name_1>:<port_1>,<name_2>:<port_2>...<name_N>:<port_N>"
+              + "\n    Default: " + serverAddressesToString(c.addresses)
+              + "\n  --test_cases=<testcase_1:w_1>,<testcase_2:w_2>...<testcase_n:w_n>"
+              + "\n    List of <testcase,weight> tuples. Weight is the relative frequency at which"
+              + " testcase is run."
+              + "\n    Valid Testcases:"
+              + validTestCasesHelpText()
+              + "\n  --use_tls=true|false           Whether to use TLS. Default: " + c.useTls
+              + "\n  --use_test_ca=true|false       Whether to trust our fake CA. Requires"
+              + " --use_tls=true"
+              + "\n                                 to have effect. Default: " + c.useTestCa
+              + "\n  --test_duration_secs=SECONDS   '-1' for no limit. Default: " + c.durationSecs
+              + "\n  --num_channels_per_server=INT  Number of connections to each server address."
+              + " Default: " + c.channelsPerServer
+              + "\n  --num_stubs_per_channel=INT    Default: " + c.stubsPerChannel
+              + "\n  --metrics_port=PORT            Listening port of the metrics server."
+              + " Default: " + c.metricsPort
+      );
+      System.exit(1);
+    }
+  }
+
+  @VisibleForTesting
+  void startMetricsService() throws IOException {
+    Preconditions.checkState(!shutdown, "client was shutdown.");
+
+    metricsServer = ServerBuilder.forPort(metricsPort)
+        .addService(new MetricsServiceImpl())
+        .build()
+        .start();
+  }
+
+  @VisibleForTesting
+  void runStressTest() throws Exception {
+    Preconditions.checkState(!shutdown, "client was shutdown.");
+    if (testCaseWeightPairs.isEmpty()) {
+      return;
+    }
+
+    int numChannels = addresses.size() * channelsPerServer;
+    int numThreads = numChannels * stubsPerChannel;
+    threadpool = MoreExecutors.listeningDecorator(newFixedThreadPool(numThreads));
+    int serverIdx = -1;
+    for (InetSocketAddress address : addresses) {
+      serverIdx++;
+      for (int i = 0; i < channelsPerServer; i++) {
+        ManagedChannel channel = createChannel(address);
+        channels.add(channel);
+        for (int j = 0; j < stubsPerChannel; j++) {
+          String gaugeName =
+              String.format("/stress_test/server_%d/channel_%d/stub_%d/qps", serverIdx, i, j);
+          Worker worker =
+              new Worker(channel, testCaseWeightPairs, durationSecs, gaugeName);
+
+          workerFutures.add(threadpool.submit(worker));
+        }
+      }
+    }
+  }
+
+  @VisibleForTesting
+  void blockUntilStressTestComplete() throws Exception {
+    Preconditions.checkState(!shutdown, "client was shutdown.");
+
+    ListenableFuture<?> f = Futures.allAsList(workerFutures);
+    if (durationSecs == -1) {
+      // '-1' indicates that the stress test runs until terminated by the user.
+      f.get();
+    } else {
+      f.get(durationSecs + WORKER_GRACE_PERIOD_SECS, SECONDS);
+    }
+  }
+
+  @VisibleForTesting
+  void shutdown() {
+    if (shutdown) {
+      return;
+    }
+    shutdown = true;
+
+    for (ManagedChannel ch : channels) {
+      try {
+        ch.shutdownNow();
+        ch.awaitTermination(1, SECONDS);
+      } catch (Throwable t) {
+        log.log(Level.WARNING, "Error shutting down channel!", t);
+      }
+    }
+
+    try {
+      metricsServer.shutdownNow();
+    } catch (Throwable t) {
+      log.log(Level.WARNING, "Error shutting down metrics service!", t);
+    }
+
+    try {
+      if (threadpool != null) {
+        threadpool.shutdownNow();
+      }
+    } catch (Throwable t) {
+      log.log(Level.WARNING, "Error shutting down threadpool.", t);
+    }
+  }
+
+  @VisibleForTesting
+  int getMetricServerPort() {
+    return metricsServer.getPort();
+  }
+
+  private List<InetSocketAddress> parseServerAddresses(String addressesStr) {
+    List<InetSocketAddress> addresses = new ArrayList<>();
+
+    for (List<String> namePort : parseCommaSeparatedTuples(addressesStr)) {
+      InetAddress address;
+      String name = namePort.get(0);
+      int port = Integer.valueOf(namePort.get(1));
+      try {
+        address = InetAddress.getByName(name);
+        if (serverHostOverride != null) {
+          // Force the hostname to match the cert the server uses.
+          address = InetAddress.getByAddress(serverHostOverride, address.getAddress());
+        }
+      } catch (UnknownHostException ex) {
+        throw new RuntimeException(ex);
+      }
+      addresses.add(new InetSocketAddress(address, port));
+    }
+
+    return addresses;
+  }
+
+  private static List<TestCaseWeightPair> parseTestCases(String testCasesStr) {
+    List<TestCaseWeightPair> testCaseWeightPairs = new ArrayList<>();
+
+    for (List<String> nameWeight : parseCommaSeparatedTuples(testCasesStr)) {
+      TestCases testCase = TestCases.fromString(nameWeight.get(0));
+      int weight = Integer.valueOf(nameWeight.get(1));
+      testCaseWeightPairs.add(new TestCaseWeightPair(testCase, weight));
+    }
+
+    return testCaseWeightPairs;
+  }
+
+  private static List<List<String>> parseCommaSeparatedTuples(String str) {
+    List<List<String>> tuples = new ArrayList<List<String>>();
+    for (String tupleStr : Splitter.on(',').split(str)) {
+      int splitIdx = tupleStr.lastIndexOf(':');
+      if (splitIdx == -1) {
+        throw new IllegalArgumentException("Illegal tuple format: '" + tupleStr + "'");
+      }
+      String part0 = tupleStr.substring(0, splitIdx);
+      String part1 = tupleStr.substring(splitIdx + 1);
+      tuples.add(asList(part0, part1));
+    }
+    return tuples;
+  }
+
+  private ManagedChannel createChannel(InetSocketAddress address) {
+    SslContext sslContext = null;
+    if (useTestCa) {
+      try {
+        sslContext = GrpcSslContexts.forClient().trustManager(
+            TestUtils.loadCert("ca.pem")).build();
+      } catch (Exception ex) {
+        throw new RuntimeException(ex);
+      }
+    }
+    return NettyChannelBuilder.forAddress(address)
+        .negotiationType(useTls ? NegotiationType.TLS : NegotiationType.PLAINTEXT)
+        .sslContext(sslContext)
+        .build();
+  }
+
+  private static String serverAddressesToString(List<InetSocketAddress> addresses) {
+    List<String> tmp = new ArrayList<>();
+    for (InetSocketAddress address : addresses) {
+      URI uri;
+      try {
+        uri = new URI(null, null, address.getHostName(), address.getPort(), null, null, null);
+      } catch (URISyntaxException e) {
+        throw new RuntimeException(e);
+      }
+      tmp.add(uri.getAuthority());
+    }
+    return Joiner.on(',').join(tmp);
+  }
+
+  private static String validTestCasesHelpText() {
+    StringBuilder builder = new StringBuilder();
+    for (TestCases testCase : TestCases.values()) {
+      String strTestcase = testCase.name().toLowerCase();
+      builder.append("\n      ")
+          .append(strTestcase)
+          .append(": ")
+          .append(testCase.description());
+    }
+    return builder.toString();
+  }
+
+  /**
+   * A stress test worker. Every stub has its own stress test worker.
+   */
+  private class Worker implements Runnable {
+
+    // Interval at which the QPS stats of metrics service are updated.
+    private static final long METRICS_COLLECTION_INTERVAL_SECS = 5;
+
+    private final ManagedChannel channel;
+    private final List<TestCaseWeightPair> testCaseWeightPairs;
+    private final Integer durationSec;
+    private final String gaugeName;
+
+    Worker(ManagedChannel channel, List<TestCaseWeightPair> testCaseWeightPairs,
+        int durationSec, String gaugeName) {
+      Preconditions.checkArgument(durationSec >= -1, "durationSec must be gte -1.");
+      this.channel = Preconditions.checkNotNull(channel, "channel");
+      this.testCaseWeightPairs =
+          Preconditions.checkNotNull(testCaseWeightPairs, "testCaseWeightPairs");
+      this.durationSec = durationSec == -1 ? null : durationSec;
+      this.gaugeName = Preconditions.checkNotNull(gaugeName, "gaugeName");
+    }
+
+    @Override
+    public void run() {
+      // Simplify debugging if the worker crashes / never terminates.
+      Thread.currentThread().setName(gaugeName);
+
+      Tester tester = new Tester();
+      tester.setUp();
+      WeightedTestCaseSelector testCaseSelector = new WeightedTestCaseSelector(testCaseWeightPairs);
+      Long endTime = durationSec == null ? null : System.nanoTime() + SECONDS.toNanos(durationSecs);
+      long lastMetricsCollectionTime = initLastMetricsCollectionTime();
+      // Number of interop testcases run since the last time metrics have been updated.
+      long testCasesSinceLastMetricsCollection = 0;
+
+      while (!Thread.currentThread().isInterrupted() && !shutdown
+          && (endTime == null || endTime - System.nanoTime() > 0)) {
+        try {
+          runTestCase(tester, testCaseSelector.nextTestCase());
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+
+        testCasesSinceLastMetricsCollection++;
+
+        double durationSecs = computeDurationSecs(lastMetricsCollectionTime);
+        if (durationSecs >= METRICS_COLLECTION_INTERVAL_SECS) {
+          long qps = (long) Math.ceil(testCasesSinceLastMetricsCollection / durationSecs);
+
+          Metrics.GaugeResponse gauge = Metrics.GaugeResponse
+              .newBuilder()
+              .setName(gaugeName)
+              .setLongValue(qps)
+              .build();
+
+          gauges.put(gaugeName, gauge);
+
+          lastMetricsCollectionTime = System.nanoTime();
+          testCasesSinceLastMetricsCollection = 0;
+        }
+      }
+    }
+
+    private long initLastMetricsCollectionTime() {
+      return System.nanoTime() - SECONDS.toNanos(METRICS_COLLECTION_INTERVAL_SECS);
+    }
+
+    private double computeDurationSecs(long lastMetricsCollectionTime) {
+      return (System.nanoTime() - lastMetricsCollectionTime) / 1000000000.0;
+    }
+
+    private void runTestCase(Tester tester, TestCases testCase) throws Exception {
+      // TODO(buchgr): Implement tests requiring auth, once C++ supports it.
+      switch (testCase) {
+        case EMPTY_UNARY:
+          tester.emptyUnary();
+          break;
+
+        case LARGE_UNARY:
+          tester.largeUnary();
+          break;
+
+        case CLIENT_STREAMING:
+          tester.clientStreaming();
+          break;
+
+        case SERVER_STREAMING:
+          tester.serverStreaming();
+          break;
+
+        case PING_PONG:
+          tester.pingPong();
+          break;
+
+        case EMPTY_STREAM:
+          tester.emptyStream();
+          break;
+
+        case UNIMPLEMENTED_METHOD: {
+          tester.unimplementedMethod();
+          break;
+        }
+
+        case UNIMPLEMENTED_SERVICE: {
+          tester.unimplementedService();
+          break;
+        }
+
+        case CANCEL_AFTER_BEGIN: {
+          tester.cancelAfterBegin();
+          break;
+        }
+
+        case CANCEL_AFTER_FIRST_RESPONSE: {
+          tester.cancelAfterFirstResponse();
+          break;
+        }
+
+        case TIMEOUT_ON_SLEEPING_SERVER: {
+          tester.timeoutOnSleepingServer();
+          break;
+        }
+
+        default:
+          throw new IllegalArgumentException("Unknown test case: " + testCase);
+      }
+    }
+
+    class Tester extends AbstractInteropTest {
+      @Override
+      protected ManagedChannel createChannel() {
+        return Worker.this.channel;
+      }
+
+      @Override
+      protected int operationTimeoutMillis() {
+        // Don't enforce a timeout when using the interop tests for the stress test client.
+        // Fixes https://github.com/grpc/grpc-java/issues/1812
+        return Integer.MAX_VALUE;
+      }
+
+      @Override
+      protected boolean metricsExpected() {
+        // TODO(zhangkun83): we may want to enable the real google Instrumentation implementation in
+        // stress tests.
+        return false;
+      }
+    }
+
+    class WeightedTestCaseSelector {
+      /**
+       * Randomly shuffled and cyclic sequence that contains each testcase proportionally
+       * to its weight.
+       */
+      final Iterator<TestCases> testCases;
+
+      WeightedTestCaseSelector(List<TestCaseWeightPair> testCaseWeightPairs) {
+        Preconditions.checkNotNull(testCaseWeightPairs, "testCaseWeightPairs");
+        Preconditions.checkArgument(testCaseWeightPairs.size() > 0);
+
+        List<TestCases> testCases = new ArrayList<>();
+        for (TestCaseWeightPair testCaseWeightPair : testCaseWeightPairs) {
+          for (int i = 0; i < testCaseWeightPair.weight; i++) {
+            testCases.add(testCaseWeightPair.testCase);
+          }
+        }
+
+        shuffle(testCases);
+
+        this.testCases = Iterators.cycle(testCases);
+      }
+
+      TestCases nextTestCase() {
+        return testCases.next();
+      }
+    }
+  }
+
+  /**
+   * Service that exports the QPS metrics of the stress test.
+   */
+  private class MetricsServiceImpl extends MetricsServiceGrpc.MetricsServiceImplBase {
+
+    @Override
+    public void getAllGauges(Metrics.EmptyMessage request,
+        StreamObserver<Metrics.GaugeResponse> responseObserver) {
+      for (Metrics.GaugeResponse gauge : gauges.values()) {
+        responseObserver.onNext(gauge);
+      }
+      responseObserver.onCompleted();
+    }
+
+    @Override
+    public void getGauge(Metrics.GaugeRequest request,
+        StreamObserver<Metrics.GaugeResponse> responseObserver) {
+      String gaugeName = request.getName();
+      Metrics.GaugeResponse gauge = gauges.get(gaugeName);
+      if (gauge != null) {
+        responseObserver.onNext(gauge);
+        responseObserver.onCompleted();
+      } else {
+        responseObserver.onError(new StatusException(Status.NOT_FOUND));
+      }
+    }
+  }
+
+  @VisibleForTesting
+  static class TestCaseWeightPair {
+    final TestCases testCase;
+    final int weight;
+
+    TestCaseWeightPair(TestCases testCase, int weight) {
+      Preconditions.checkArgument(weight >= 0, "weight must be positive.");
+      this.testCase = Preconditions.checkNotNull(testCase, "testCase");
+      this.weight = weight;
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (!(other instanceof TestCaseWeightPair)) {
+        return false;
+      }
+      TestCaseWeightPair that = (TestCaseWeightPair) other;
+      return testCase.equals(that.testCase) && weight == that.weight;
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hashCode(testCase, weight);
+    }
+  }
+
+  @VisibleForTesting
+  List<InetSocketAddress> addresses() {
+    return Collections.unmodifiableList(addresses);
+  }
+
+  @VisibleForTesting
+  String serverHostOverride() {
+    return serverHostOverride;
+  }
+
+  @VisibleForTesting
+  boolean useTls() {
+    return useTls;
+  }
+
+  @VisibleForTesting
+  boolean useTestCa() {
+    return useTestCa;
+  }
+
+  @VisibleForTesting
+  List<TestCaseWeightPair> testCaseWeightPairs() {
+    return testCaseWeightPairs;
+  }
+
+  @VisibleForTesting
+  int durationSecs() {
+    return durationSecs;
+  }
+
+  @VisibleForTesting
+  int channelsPerServer() {
+    return channelsPerServer;
+  }
+
+  @VisibleForTesting
+  int stubsPerChannel() {
+    return stubsPerChannel;
+  }
+
+  @VisibleForTesting
+  int metricsPort() {
+    return metricsPort;
+  }
+}
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/TestCases.java b/interop-testing/src/main/java/io/grpc/testing/integration/TestCases.java
new file mode 100644
index 0000000..7ac02ad
--- /dev/null
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/TestCases.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Enum of interop test cases.
+ */
+public enum TestCases {
+  EMPTY_UNARY("empty (zero bytes) request and response"),
+  CACHEABLE_UNARY("cacheable unary rpc sent using GET"),
+  LARGE_UNARY("single request and (large) response"),
+  CLIENT_COMPRESSED_UNARY("client compressed unary request"),
+  CLIENT_COMPRESSED_UNARY_NOPROBE(
+      "client compressed unary request (skip initial feature-probing request)"),
+  SERVER_COMPRESSED_UNARY("server compressed unary response"),
+  CLIENT_STREAMING("request streaming with single response"),
+  CLIENT_COMPRESSED_STREAMING("client per-message compression on stream"),
+  CLIENT_COMPRESSED_STREAMING_NOPROBE(
+      "client per-message compression on stream (skip initial feature-probing request)"),
+  SERVER_STREAMING("single request with response streaming"),
+  SERVER_COMPRESSED_STREAMING("server per-message compression on stream"),
+  PING_PONG("full-duplex ping-pong streaming"),
+  EMPTY_STREAM("A stream that has zero-messages in both directions"),
+  COMPUTE_ENGINE_CREDS("large_unary with service_account auth"),
+  SERVICE_ACCOUNT_CREDS("large_unary with compute engine auth"),
+  JWT_TOKEN_CREDS("JWT-based auth"),
+  OAUTH2_AUTH_TOKEN("raw oauth2 access token auth"),
+  PER_RPC_CREDS("per rpc raw oauth2 access token auth"),
+  CUSTOM_METADATA("unary and full duplex calls with metadata"),
+  STATUS_CODE_AND_MESSAGE("request error code and message"),
+  SPECIAL_STATUS_MESSAGE("special characters in status message"),
+  UNIMPLEMENTED_METHOD("call an unimplemented RPC method"),
+  UNIMPLEMENTED_SERVICE("call an unimplemented RPC service"),
+  CANCEL_AFTER_BEGIN("cancel stream after starting it"),
+  CANCEL_AFTER_FIRST_RESPONSE("cancel on first response"),
+  TIMEOUT_ON_SLEEPING_SERVER("timeout before receiving a response"),
+  VERY_LARGE_REQUEST("very large request");
+
+  private final String description;
+
+  TestCases(String description) {
+    this.description = description;
+  }
+
+  /**
+   * Returns a description of the test case.
+   */
+  public String description() {
+    return description;
+  }
+
+  /**
+   * Returns the {@link TestCases} matching the string {@code s}. The
+   * matching is done case insensitive.
+   */
+  public static TestCases fromString(String s) {
+    Preconditions.checkNotNull(s, "s");
+    return TestCases.valueOf(s.toUpperCase());
+  }
+}
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java
new file mode 100644
index 0000000..7e7531a
--- /dev/null
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceClient.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.io.Files;
+import io.grpc.ManagedChannel;
+import io.grpc.alts.AltsChannelBuilder;
+import io.grpc.alts.GoogleDefaultChannelBuilder;
+import io.grpc.internal.AbstractManagedChannelImplBuilder;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.testing.TestUtils;
+import io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.NegotiationType;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.okhttp.OkHttpChannelBuilder;
+import io.grpc.okhttp.internal.Platform;
+import io.netty.handler.ssl.SslContext;
+import java.io.File;
+import java.io.FileInputStream;
+import java.nio.charset.Charset;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * Application that starts a client for the {@link TestServiceGrpc.TestServiceImplBase} and runs
+ * through a series of tests.
+ */
+public class TestServiceClient {
+
+  private static final Charset UTF_8 = Charset.forName("UTF-8");
+
+  /**
+   * The main application allowing this client to be launched from the command line.
+   */
+  public static void main(String[] args) throws Exception {
+    // Let Netty or OkHttp use Conscrypt if it is available.
+    TestUtils.installConscryptIfAvailable();
+    final TestServiceClient client = new TestServiceClient();
+    client.parseArgs(args);
+    client.setUp();
+
+    Runtime.getRuntime().addShutdownHook(new Thread() {
+      @Override
+      public void run() {
+        System.out.println("Shutting down");
+        try {
+          client.tearDown();
+        } catch (Exception e) {
+          e.printStackTrace();
+        }
+      }
+    });
+
+    try {
+      client.run();
+    } finally {
+      client.tearDown();
+    }
+    System.exit(0);
+  }
+
+  private String serverHost = "localhost";
+  private String serverHostOverride;
+  private int serverPort = 8080;
+  private String testCase = "empty_unary";
+  private boolean useTls = true;
+  private boolean useAlts = false;
+  private String customCredentialsType;
+  private boolean useTestCa;
+  private boolean useOkHttp;
+  private String defaultServiceAccount;
+  private String serviceAccountKeyFile;
+  private String oauthScope;
+  private boolean fullStreamDecompression;
+
+  private Tester tester = new Tester();
+
+  @VisibleForTesting
+  void parseArgs(String[] args) {
+    boolean usage = false;
+    for (String arg : args) {
+      if (!arg.startsWith("--")) {
+        System.err.println("All arguments must start with '--': " + arg);
+        usage = true;
+        break;
+      }
+      String[] parts = arg.substring(2).split("=", 2);
+      String key = parts[0];
+      if ("help".equals(key)) {
+        usage = true;
+        break;
+      }
+      if (parts.length != 2) {
+        System.err.println("All arguments must be of the form --arg=value");
+        usage = true;
+        break;
+      }
+      String value = parts[1];
+      if ("server_host".equals(key)) {
+        serverHost = value;
+      } else if ("server_host_override".equals(key)) {
+        serverHostOverride = value;
+      } else if ("server_port".equals(key)) {
+        serverPort = Integer.parseInt(value);
+      } else if ("test_case".equals(key)) {
+        testCase = value;
+      } else if ("use_tls".equals(key)) {
+        useTls = Boolean.parseBoolean(value);
+      } else if ("use_alts".equals(key)) {
+        useAlts = Boolean.parseBoolean(value);
+      } else if ("custom_credentials_type".equals(key)) {
+        customCredentialsType = value;
+      } else if ("use_test_ca".equals(key)) {
+        useTestCa = Boolean.parseBoolean(value);
+      } else if ("use_okhttp".equals(key)) {
+        useOkHttp = Boolean.parseBoolean(value);
+      } else if ("grpc_version".equals(key)) {
+        if (!"2".equals(value)) {
+          System.err.println("Only grpc version 2 is supported");
+          usage = true;
+          break;
+        }
+      } else if ("default_service_account".equals(key)) {
+        defaultServiceAccount = value;
+      } else if ("service_account_key_file".equals(key)) {
+        serviceAccountKeyFile = value;
+      } else if ("oauth_scope".equals(key)) {
+        oauthScope = value;
+      } else if ("full_stream_decompression".equals(key)) {
+        fullStreamDecompression = Boolean.parseBoolean(value);
+      } else {
+        System.err.println("Unknown argument: " + key);
+        usage = true;
+        break;
+      }
+    }
+    if (useAlts) {
+      useTls = false;
+    }
+    if (usage) {
+      TestServiceClient c = new TestServiceClient();
+      System.out.println(
+          "Usage: [ARGS...]"
+          + "\n"
+          + "\n  --server_host=HOST          Server to connect to. Default " + c.serverHost
+          + "\n  --server_host_override=HOST Claimed identification expected of server."
+          + "\n                              Defaults to server host"
+          + "\n  --server_port=PORT          Port to connect to. Default " + c.serverPort
+          + "\n  --test_case=TESTCASE        Test case to run. Default " + c.testCase
+          + "\n    Valid options:"
+          + validTestCasesHelpText()
+          + "\n  --use_tls=true|false        Whether to use TLS. Default " + c.useTls
+          + "\n  --use_alts=true|false       Whether to use ALTS. Enable ALTS will disable TLS."
+          + "\n                              Default " + c.useAlts
+          + "\n  --custom_credentials_type   Custom credentials type to use. Default "
+            + c.customCredentialsType
+          + "\n  --use_test_ca=true|false    Whether to trust our fake CA. Requires --use_tls=true "
+          + "\n                              to have effect. Default " + c.useTestCa
+          + "\n  --use_okhttp=true|false     Whether to use OkHttp instead of Netty. Default "
+            + c.useOkHttp
+          + "\n  --default_service_account   Email of GCE default service account. Default "
+            + c.defaultServiceAccount
+          + "\n  --service_account_key_file  Path to service account json key file."
+            + c.serviceAccountKeyFile
+          + "\n  --oauth_scope               Scope for OAuth tokens. Default " + c.oauthScope
+          + "\n  --full_stream_decompression Enable full-stream decompression. Default "
+            + c.fullStreamDecompression
+      );
+      System.exit(1);
+    }
+  }
+
+  @VisibleForTesting
+  void setUp() {
+    tester.setUp();
+  }
+
+  private synchronized void tearDown() {
+    try {
+      tester.tearDown();
+    } catch (RuntimeException ex) {
+      throw ex;
+    } catch (Exception ex) {
+      throw new RuntimeException(ex);
+    }
+  }
+
+  private void run() {
+    System.out.println("Running test " + testCase);
+    try {
+      runTest(TestCases.fromString(testCase));
+    } catch (RuntimeException ex) {
+      throw ex;
+    } catch (Exception ex) {
+      throw new RuntimeException(ex);
+    }
+    System.out.println("Test completed.");
+  }
+
+  private void runTest(TestCases testCase) throws Exception {
+    switch (testCase) {
+      case EMPTY_UNARY:
+        tester.emptyUnary();
+        break;
+
+      case CACHEABLE_UNARY: {
+        tester.cacheableUnary();
+        break;
+      }
+
+      case LARGE_UNARY:
+        tester.largeUnary();
+        break;
+
+      case CLIENT_COMPRESSED_UNARY:
+        tester.clientCompressedUnary(true);
+        break;
+
+      case CLIENT_COMPRESSED_UNARY_NOPROBE:
+        tester.clientCompressedUnary(false);
+        break;
+
+      case SERVER_COMPRESSED_UNARY:
+        tester.serverCompressedUnary();
+        break;
+
+      case CLIENT_STREAMING:
+        tester.clientStreaming();
+        break;
+
+      case CLIENT_COMPRESSED_STREAMING:
+        tester.clientCompressedStreaming(true);
+        break;
+
+      case CLIENT_COMPRESSED_STREAMING_NOPROBE:
+        tester.clientCompressedStreaming(false);
+        break;
+
+      case SERVER_STREAMING:
+        tester.serverStreaming();
+        break;
+
+      case SERVER_COMPRESSED_STREAMING:
+        tester.serverCompressedStreaming();
+        break;
+
+      case PING_PONG:
+        tester.pingPong();
+        break;
+
+      case EMPTY_STREAM:
+        tester.emptyStream();
+        break;
+
+      case COMPUTE_ENGINE_CREDS:
+        tester.computeEngineCreds(defaultServiceAccount, oauthScope);
+        break;
+
+      case SERVICE_ACCOUNT_CREDS: {
+        String jsonKey = Files.asCharSource(new File(serviceAccountKeyFile), UTF_8).read();
+        FileInputStream credentialsStream = new FileInputStream(new File(serviceAccountKeyFile));
+        tester.serviceAccountCreds(jsonKey, credentialsStream, oauthScope);
+        break;
+      }
+
+      case JWT_TOKEN_CREDS: {
+        FileInputStream credentialsStream = new FileInputStream(new File(serviceAccountKeyFile));
+        tester.jwtTokenCreds(credentialsStream);
+        break;
+      }
+
+      case OAUTH2_AUTH_TOKEN: {
+        String jsonKey = Files.asCharSource(new File(serviceAccountKeyFile), UTF_8).read();
+        FileInputStream credentialsStream = new FileInputStream(new File(serviceAccountKeyFile));
+        tester.oauth2AuthToken(jsonKey, credentialsStream, oauthScope);
+        break;
+      }
+
+      case PER_RPC_CREDS: {
+        String jsonKey = Files.asCharSource(new File(serviceAccountKeyFile), UTF_8).read();
+        FileInputStream credentialsStream = new FileInputStream(new File(serviceAccountKeyFile));
+        tester.perRpcCreds(jsonKey, credentialsStream, oauthScope);
+        break;
+      }
+
+      case CUSTOM_METADATA: {
+        tester.customMetadata();
+        break;
+      }
+
+      case STATUS_CODE_AND_MESSAGE: {
+        tester.statusCodeAndMessage();
+        break;
+      }
+
+      case SPECIAL_STATUS_MESSAGE:
+        tester.specialStatusMessage();
+        break;
+
+      case UNIMPLEMENTED_METHOD: {
+        tester.unimplementedMethod();
+        break;
+      }
+
+      case UNIMPLEMENTED_SERVICE: {
+        tester.unimplementedService();
+        break;
+      }
+
+      case CANCEL_AFTER_BEGIN: {
+        tester.cancelAfterBegin();
+        break;
+      }
+
+      case CANCEL_AFTER_FIRST_RESPONSE: {
+        tester.cancelAfterFirstResponse();
+        break;
+      }
+
+      case TIMEOUT_ON_SLEEPING_SERVER: {
+        tester.timeoutOnSleepingServer();
+        break;
+      }
+
+      case VERY_LARGE_REQUEST: {
+        tester.veryLargeRequest();
+        break;
+      }
+
+      default:
+        throw new IllegalArgumentException("Unknown test case: " + testCase);
+    }
+  }
+
+  private class Tester extends AbstractInteropTest {
+    @Override
+    protected ManagedChannel createChannel() {
+      if (customCredentialsType != null
+          && customCredentialsType.equals("google_default_credentials")) {
+        return GoogleDefaultChannelBuilder.forAddress(serverHost, serverPort).build();
+      }
+      if (useAlts) {
+        return AltsChannelBuilder.forAddress(serverHost, serverPort).build();
+      }
+      AbstractManagedChannelImplBuilder<?> builder;
+      if (!useOkHttp) {
+        SslContext sslContext = null;
+        if (useTestCa) {
+          try {
+            sslContext = GrpcSslContexts.forClient().trustManager(
+                    TestUtils.loadCert("ca.pem")).build();
+          } catch (Exception ex) {
+            throw new RuntimeException(ex);
+          }
+        }
+        NettyChannelBuilder nettyBuilder =
+            NettyChannelBuilder.forAddress(serverHost, serverPort)
+                .flowControlWindow(65 * 1024)
+                .negotiationType(useTls ? NegotiationType.TLS : NegotiationType.PLAINTEXT)
+                .sslContext(sslContext);
+        if (serverHostOverride != null) {
+          nettyBuilder.overrideAuthority(serverHostOverride);
+        }
+        if (fullStreamDecompression) {
+          nettyBuilder.enableFullStreamDecompression();
+        }
+        builder = nettyBuilder;
+      } else {
+        OkHttpChannelBuilder okBuilder = OkHttpChannelBuilder.forAddress(serverHost, serverPort);
+        if (serverHostOverride != null) {
+          // Force the hostname to match the cert the server uses.
+          okBuilder.overrideAuthority(
+              GrpcUtil.authorityFromHostAndPort(serverHostOverride, serverPort));
+        }
+        if (useTls) {
+          try {
+            SSLSocketFactory factory = useTestCa
+                ? TestUtils.newSslSocketFactoryForCa(Platform.get().getProvider(),
+                    TestUtils.loadCert("ca.pem"))
+                : (SSLSocketFactory) SSLSocketFactory.getDefault();
+            okBuilder.sslSocketFactory(factory);
+          } catch (Exception e) {
+            throw new RuntimeException(e);
+          }
+        } else {
+          okBuilder.usePlaintext();
+        }
+        if (fullStreamDecompression) {
+          okBuilder.enableFullStreamDecompression();
+        }
+        builder = okBuilder;
+      }
+      io.grpc.internal.TestingAccessor.setStatsImplementation(
+          builder, createClientCensusStatsModule());
+      return builder.build();
+    }
+
+    @Override
+    protected boolean metricsExpected() {
+      // Exact message size doesn't match when testing with Go servers:
+      // https://github.com/grpc/grpc-go/issues/1572
+      // TODO(zhangkun83): remove this override once the said issue is fixed.
+      return false;
+    }
+  }
+
+  private static String validTestCasesHelpText() {
+    StringBuilder builder = new StringBuilder();
+    for (TestCases testCase : TestCases.values()) {
+      String strTestcase = testCase.name().toLowerCase();
+      builder.append("\n      ")
+          .append(strTestcase)
+          .append(": ")
+          .append(testCase.description());
+    }
+    return builder.toString();
+  }
+}
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceImpl.java b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceImpl.java
new file mode 100644
index 0000000..8a7709c
--- /dev/null
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceImpl.java
@@ -0,0 +1,585 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Queues;
+import com.google.protobuf.ByteString;
+import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
+import io.grpc.Metadata;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.Status;
+import io.grpc.internal.LogExceptionRunnable;
+import io.grpc.stub.ServerCallStreamObserver;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.integration.Messages.Payload;
+import io.grpc.testing.integration.Messages.PayloadType;
+import io.grpc.testing.integration.Messages.ResponseParameters;
+import io.grpc.testing.integration.Messages.SimpleRequest;
+import io.grpc.testing.integration.Messages.SimpleResponse;
+import io.grpc.testing.integration.Messages.StreamingInputCallRequest;
+import io.grpc.testing.integration.Messages.StreamingInputCallResponse;
+import io.grpc.testing.integration.Messages.StreamingOutputCallRequest;
+import io.grpc.testing.integration.Messages.StreamingOutputCallResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Queue;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Implementation of the business logic for the TestService. Uses an executor to schedule chunks
+ * sent in response streams.
+ */
+public class TestServiceImpl extends TestServiceGrpc.TestServiceImplBase {
+  private static final String UNCOMPRESSABLE_FILE =
+      "/io/grpc/testing/integration/testdata/uncompressable.bin";
+  private final Random random = new Random();
+
+  private final ScheduledExecutorService executor;
+  private final ByteString uncompressableBuffer;
+  private final ByteString compressableBuffer;
+
+  /**
+   * Constructs a controller using the given executor for scheduling response stream chunks.
+   */
+  public TestServiceImpl(ScheduledExecutorService executor) {
+    this.executor = executor;
+    this.compressableBuffer = ByteString.copyFrom(new byte[1024]);
+    this.uncompressableBuffer = createBufferFromFile(UNCOMPRESSABLE_FILE);
+  }
+
+  @Override
+  public void emptyCall(EmptyProtos.Empty empty,
+      StreamObserver<EmptyProtos.Empty> responseObserver) {
+    responseObserver.onNext(EmptyProtos.Empty.getDefaultInstance());
+    responseObserver.onCompleted();
+  }
+
+  /**
+   * Immediately responds with a payload of the type and size specified in the request.
+   */
+  @Override
+  public void unaryCall(SimpleRequest req, StreamObserver<SimpleResponse> responseObserver) {
+    ServerCallStreamObserver<SimpleResponse> obs =
+        (ServerCallStreamObserver<SimpleResponse>) responseObserver;
+    SimpleResponse.Builder responseBuilder = SimpleResponse.newBuilder();
+    try {
+      if (req.hasResponseCompressed() && req.getResponseCompressed().getValue()) {
+        obs.setCompression("gzip");
+      } else {
+        obs.setCompression("identity");
+      }
+    } catch (IllegalArgumentException e) {
+      obs.onError(Status.UNIMPLEMENTED
+          .withDescription("compression not supported.")
+          .withCause(e)
+          .asRuntimeException());
+      return;
+    }
+
+    if (req.getResponseSize() != 0) {
+      boolean compressable = compressableResponse(req.getResponseType());
+      ByteString dataBuffer = compressable ? compressableBuffer : uncompressableBuffer;
+      // For consistency with the c++ TestServiceImpl, use a random offset for unary calls.
+      // TODO(wonderfly): whether or not this is a good approach needs further discussion.
+      int offset = random.nextInt(
+          compressable ? compressableBuffer.size() : uncompressableBuffer.size());
+      ByteString payload = generatePayload(dataBuffer, offset, req.getResponseSize());
+      responseBuilder.setPayload(
+          Payload.newBuilder()
+              .setType(compressable ? PayloadType.COMPRESSABLE : PayloadType.UNCOMPRESSABLE)
+              .setBody(payload));
+    }
+
+    if (req.hasResponseStatus()) {
+      obs.onError(Status.fromCodeValue(req.getResponseStatus().getCode())
+          .withDescription(req.getResponseStatus().getMessage())
+          .asRuntimeException());
+      return;
+    }
+
+    responseObserver.onNext(responseBuilder.build());
+    responseObserver.onCompleted();
+  }
+
+  /**
+   * Given a request that specifies chunk size and interval between responses, creates and schedules
+   * the response stream.
+   */
+  @Override
+  public void streamingOutputCall(StreamingOutputCallRequest request,
+      StreamObserver<StreamingOutputCallResponse> responseObserver) {
+    // Create and start the response dispatcher.
+    new ResponseDispatcher(responseObserver).enqueue(toChunkQueue(request)).completeInput();
+  }
+
+  /**
+   * Waits until we have received all of the request messages and then returns the aggregate payload
+   * size for all of the received requests.
+   */
+  @Override
+  public StreamObserver<Messages.StreamingInputCallRequest> streamingInputCall(
+      final StreamObserver<Messages.StreamingInputCallResponse> responseObserver) {
+    return new StreamObserver<StreamingInputCallRequest>() {
+      private int totalPayloadSize;
+
+      @Override
+      public void onNext(StreamingInputCallRequest message) {
+        totalPayloadSize += message.getPayload().getBody().size();
+      }
+
+      @Override
+      public void onCompleted() {
+        responseObserver.onNext(StreamingInputCallResponse.newBuilder()
+            .setAggregatedPayloadSize(totalPayloadSize).build());
+        responseObserver.onCompleted();
+      }
+
+      @Override
+      public void onError(Throwable cause) {
+        responseObserver.onError(cause);
+      }
+    };
+  }
+
+  /**
+   * True bi-directional streaming. Processes requests as they come in. Begins streaming results
+   * immediately.
+   */
+  @Override
+  public StreamObserver<Messages.StreamingOutputCallRequest> fullDuplexCall(
+      final StreamObserver<Messages.StreamingOutputCallResponse> responseObserver) {
+    final ResponseDispatcher dispatcher = new ResponseDispatcher(responseObserver);
+    return new StreamObserver<StreamingOutputCallRequest>() {
+      @Override
+      public void onNext(StreamingOutputCallRequest request) {
+        if (request.hasResponseStatus()) {
+          dispatcher.cancel();
+          dispatcher.onError(Status.fromCodeValue(request.getResponseStatus().getCode())
+              .withDescription(request.getResponseStatus().getMessage())
+              .asRuntimeException());
+          return;
+        }
+        dispatcher.enqueue(toChunkQueue(request));
+      }
+
+      @Override
+      public void onCompleted() {
+        if (!dispatcher.isCancelled()) {
+          // Tell the dispatcher that all input has been received.
+          dispatcher.completeInput();
+        }
+      }
+
+      @Override
+      public void onError(Throwable cause) {
+        dispatcher.onError(cause);
+      }
+    };
+  }
+
+  /**
+   * Similar to {@link #fullDuplexCall}, except that it waits for all streaming requests to be
+   * received before starting the streaming responses.
+   */
+  @Override
+  public StreamObserver<Messages.StreamingOutputCallRequest> halfDuplexCall(
+      final StreamObserver<Messages.StreamingOutputCallResponse> responseObserver) {
+    final ResponseDispatcher dispatcher = new ResponseDispatcher(responseObserver);
+    final Queue<Chunk> chunks = new ArrayDeque<Chunk>();
+    return new StreamObserver<StreamingOutputCallRequest>() {
+      @Override
+      public void onNext(StreamingOutputCallRequest request) {
+        chunks.addAll(toChunkQueue(request));
+      }
+
+      @Override
+      public void onCompleted() {
+        // Dispatch all of the chunks in one shot.
+        dispatcher.enqueue(chunks).completeInput();
+      }
+
+      @Override
+      public void onError(Throwable cause) {
+        dispatcher.onError(cause);
+      }
+    };
+  }
+
+  /**
+   * Schedules the dispatch of a queue of chunks. Whenever chunks are added or input is completed,
+   * the next response chunk is scheduled for delivery to the client. When no more chunks are
+   * available, the stream is half-closed.
+   */
+  private class ResponseDispatcher {
+    private final Chunk completionChunk = new Chunk(0, 0, 0, false);
+    private final Queue<Chunk> chunks;
+    private final StreamObserver<StreamingOutputCallResponse> responseStream;
+    private boolean scheduled;
+    @GuardedBy("this") private boolean cancelled;
+    private Throwable failure;
+    private Runnable dispatchTask = new Runnable() {
+      @Override
+      public void run() {
+        try {
+
+          // Dispatch the current chunk to the client.
+          try {
+            dispatchChunk();
+          } catch (RuntimeException e) {
+            // Indicate that nothing is scheduled and re-throw.
+            synchronized (ResponseDispatcher.this) {
+              scheduled = false;
+            }
+            throw e;
+          }
+
+          // Schedule the next chunk if there is one.
+          synchronized (ResponseDispatcher.this) {
+            // Indicate that nothing is scheduled.
+            scheduled = false;
+            scheduleNextChunk();
+          }
+        } catch (Throwable t) {
+          t.printStackTrace();
+        }
+      }
+    };
+
+    /**
+     * The {@link StreamObserver} will be used to send the queue of response chunks. Since calls to
+     * {@link StreamObserver} must be synchronized across threads, no further calls should be made
+     * directly on {@code responseStream} after it is provided to the {@link ResponseDispatcher}.
+     */
+    public ResponseDispatcher(StreamObserver<StreamingOutputCallResponse> responseStream) {
+      this.chunks = Queues.newLinkedBlockingQueue();
+      this.responseStream = responseStream;
+    }
+
+    /**
+     * Adds the given chunks to the response stream and schedules the next chunk to be delivered if
+     * needed.
+     */
+    public synchronized ResponseDispatcher enqueue(Queue<Chunk> moreChunks) {
+      assertNotFailed();
+      chunks.addAll(moreChunks);
+      scheduleNextChunk();
+      return this;
+    }
+
+    /**
+     * Indicates that the input is completed and the currently enqueued response chunks are all that
+     * remain to be scheduled for dispatch to the client.
+     */
+    public ResponseDispatcher completeInput() {
+      assertNotFailed();
+      chunks.add(completionChunk);
+      scheduleNextChunk();
+      return this;
+    }
+
+    /**
+     * Allows the service to cancel the remaining responses.
+     */
+    public synchronized void cancel() {
+      Preconditions.checkState(!cancelled, "Dispatcher already cancelled");
+      chunks.clear();
+      cancelled = true;
+    }
+
+    public synchronized boolean isCancelled() {
+      return cancelled;
+    }
+
+    private synchronized void onError(Throwable cause) {
+      responseStream.onError(cause);
+    }
+
+    /**
+     * Dispatches the current response chunk to the client. This is only called by the executor. At
+     * any time, a given dispatch task should only be registered with the executor once.
+     */
+    private synchronized void dispatchChunk() {
+      if (cancelled) {
+        return;
+      }
+      try {
+        // Pop off the next chunk and send it to the client.
+        Chunk chunk = chunks.remove();
+        if (chunk == completionChunk) {
+          responseStream.onCompleted();
+        } else {
+          responseStream.onNext(chunk.toResponse());
+        }
+      } catch (Throwable e) {
+        failure = e;
+        if (Status.fromThrowable(e).getCode() == Status.CANCELLED.getCode()) {
+          // Stream was cancelled by client, responseStream.onError() might be called already or
+          // will be called soon by inbounding StreamObserver.
+          chunks.clear();
+        } else {
+          responseStream.onError(e);
+        }
+      }
+    }
+
+    /**
+     * Schedules the next response chunk to be dispatched. If all input has been received and there
+     * are no more chunks in the queue, the stream is closed.
+     */
+    private void scheduleNextChunk() {
+      synchronized (this) {
+        if (scheduled) {
+          // Dispatch task is already scheduled.
+          return;
+        }
+
+        // Schedule the next response chunk if there is one.
+        Chunk nextChunk = chunks.peek();
+        if (nextChunk != null) {
+          scheduled = true;
+          // TODO(ejona): cancel future if RPC is cancelled
+          Future<?> unused = executor.schedule(new LogExceptionRunnable(dispatchTask),
+              nextChunk.delayMicroseconds, TimeUnit.MICROSECONDS);
+          return;
+        }
+      }
+    }
+
+    private void assertNotFailed() {
+      if (failure != null) {
+        throw new IllegalStateException("Stream already failed", failure);
+      }
+    }
+  }
+
+  /**
+   * Breaks down the request and creates a queue of response chunks for the given request.
+   */
+  public Queue<Chunk> toChunkQueue(StreamingOutputCallRequest request) {
+    Queue<Chunk> chunkQueue = new ArrayDeque<Chunk>();
+    int offset = 0;
+    boolean compressable = compressableResponse(request.getResponseType());
+    for (ResponseParameters params : request.getResponseParametersList()) {
+      chunkQueue.add(new Chunk(params.getIntervalUs(), offset, params.getSize(), compressable));
+
+      // Increment the offset past this chunk.
+      // Both buffers need to be circular.
+      offset = (offset + params.getSize())
+          % (compressable ? compressableBuffer.size() : uncompressableBuffer.size());
+    }
+    return chunkQueue;
+  }
+
+  /**
+   * A single chunk of a response stream. Contains delivery information for the dispatcher and can
+   * be converted to a streaming response proto. A chunk just references it's payload in the
+   * {@link #uncompressableBuffer} array. The payload isn't actually created until {@link
+   * #toResponse()} is called.
+   */
+  private class Chunk {
+    private final int delayMicroseconds;
+    private final int offset;
+    private final int length;
+    private final boolean compressable;
+
+    public Chunk(int delayMicroseconds, int offset, int length, boolean compressable) {
+      this.delayMicroseconds = delayMicroseconds;
+      this.offset = offset;
+      this.length = length;
+      this.compressable = compressable;
+    }
+
+    /**
+     * Convert this chunk into a streaming response proto.
+     */
+    private StreamingOutputCallResponse toResponse() {
+      StreamingOutputCallResponse.Builder responseBuilder =
+          StreamingOutputCallResponse.newBuilder();
+      ByteString dataBuffer = compressable ? compressableBuffer : uncompressableBuffer;
+      ByteString payload = generatePayload(dataBuffer, offset, length);
+      responseBuilder.setPayload(
+          Payload.newBuilder()
+              .setType(compressable ? PayloadType.COMPRESSABLE : PayloadType.UNCOMPRESSABLE)
+              .setBody(payload));
+      return responseBuilder.build();
+    }
+  }
+
+  /**
+   * Creates a buffer with data read from a file.
+   */
+  @SuppressWarnings("Finally") // Not concerned about suppression; expected to be exceedingly rare
+  private ByteString createBufferFromFile(String fileClassPath) {
+    ByteString buffer = ByteString.EMPTY;
+    InputStream inputStream = getClass().getResourceAsStream(fileClassPath);
+    if (inputStream == null) {
+      throw new IllegalArgumentException("Unable to locate file on classpath: " + fileClassPath);
+    }
+
+    try {
+      buffer = ByteString.readFrom(inputStream);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    } finally {
+      try {
+        inputStream.close();
+      } catch (IOException ignorable) {
+        // ignore
+      }
+    }
+    return buffer;
+  }
+
+  /**
+   * Indicates whether or not the response for this type should be compressable. If {@code RANDOM},
+   * picks a random boolean.
+   */
+  private boolean compressableResponse(PayloadType responseType) {
+    switch (responseType) {
+      case COMPRESSABLE:
+        return true;
+      case RANDOM:
+        return random.nextBoolean();
+      case UNCOMPRESSABLE:
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Generates a payload of desired type and size. Reads compressableBuffer or
+   * uncompressableBuffer as a circular buffer.
+   */
+  private ByteString generatePayload(ByteString dataBuffer, int offset, int size) {
+    ByteString payload = ByteString.EMPTY;
+    // This offset would never pass the array boundary.
+    int begin = offset;
+    int end = 0;
+    int bytesLeft = size;
+    while (bytesLeft > 0) {
+      end = Math.min(begin + bytesLeft, dataBuffer.size());
+      // ByteString.substring returns the substring from begin, inclusive, to end, exclusive.
+      payload = payload.concat(dataBuffer.substring(begin, end));
+      bytesLeft -= (end - begin);
+      begin = end % dataBuffer.size();
+    }
+    return payload;
+  }
+
+  /** Returns interceptors necessary for full service implementation. */
+  public static List<ServerInterceptor> interceptors() {
+    return Arrays.asList(
+        echoRequestHeadersInterceptor(Util.METADATA_KEY),
+        echoRequestMetadataInHeaders(Util.ECHO_INITIAL_METADATA_KEY),
+        echoRequestMetadataInTrailers(Util.ECHO_TRAILING_METADATA_KEY));
+  }
+
+  /**
+   * Echo the request headers from a client into response headers and trailers. Useful for
+   * testing end-to-end metadata propagation.
+   */
+  private static ServerInterceptor echoRequestHeadersInterceptor(final Metadata.Key<?>... keys) {
+    final Set<Metadata.Key<?>> keySet = new HashSet<Metadata.Key<?>>(Arrays.asList(keys));
+    return new ServerInterceptor() {
+      @Override
+      public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+          ServerCall<ReqT, RespT> call,
+          final Metadata requestHeaders,
+          ServerCallHandler<ReqT, RespT> next) {
+        return next.startCall(new SimpleForwardingServerCall<ReqT, RespT>(call) {
+              @Override
+              public void sendHeaders(Metadata responseHeaders) {
+                responseHeaders.merge(requestHeaders, keySet);
+                super.sendHeaders(responseHeaders);
+              }
+
+              @Override
+              public void close(Status status, Metadata trailers) {
+                trailers.merge(requestHeaders, keySet);
+                super.close(status, trailers);
+              }
+            }, requestHeaders);
+      }
+    };
+  }
+
+  /**
+   * Echoes request headers with the specified key(s) from a client into response headers only.
+   */
+  private static ServerInterceptor echoRequestMetadataInHeaders(final Metadata.Key<?>... keys) {
+    final Set<Metadata.Key<?>> keySet = new HashSet<Metadata.Key<?>>(Arrays.asList(keys));
+    return new ServerInterceptor() {
+      @Override
+      public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+          ServerCall<ReqT, RespT> call,
+          final Metadata requestHeaders,
+          ServerCallHandler<ReqT, RespT> next) {
+        return next.startCall(new SimpleForwardingServerCall<ReqT, RespT>(call) {
+          @Override
+          public void sendHeaders(Metadata responseHeaders) {
+            responseHeaders.merge(requestHeaders, keySet);
+            super.sendHeaders(responseHeaders);
+          }
+
+          @Override
+          public void close(Status status, Metadata trailers) {
+            super.close(status, trailers);
+          }
+        }, requestHeaders);
+      }
+    };
+  }
+
+  /**
+   * Echoes request headers with the specified key(s) from a client into response trailers only.
+   */
+  private static ServerInterceptor echoRequestMetadataInTrailers(final Metadata.Key<?>... keys) {
+    final Set<Metadata.Key<?>> keySet = new HashSet<Metadata.Key<?>>(Arrays.asList(keys));
+    return new ServerInterceptor() {
+      @Override
+      public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+          ServerCall<ReqT, RespT> call,
+          final Metadata requestHeaders,
+          ServerCallHandler<ReqT, RespT> next) {
+        return next.startCall(new SimpleForwardingServerCall<ReqT, RespT>(call) {
+          @Override
+          public void sendHeaders(Metadata responseHeaders) {
+            super.sendHeaders(responseHeaders);
+          }
+
+          @Override
+          public void close(Status status, Metadata trailers) {
+            trailers.merge(requestHeaders, keySet);
+            super.close(status, trailers);
+          }
+        }, requestHeaders);
+      }
+    };
+  }
+}
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceServer.java b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceServer.java
new file mode 100644
index 0000000..53b1c8f
--- /dev/null
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/TestServiceServer.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.Server;
+import io.grpc.ServerInterceptors;
+import io.grpc.alts.AltsServerBuilder;
+import io.grpc.internal.testing.TestUtils;
+import io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.NettyServerBuilder;
+import io.netty.handler.ssl.SslContext;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/** Server that manages startup/shutdown of a single {@code TestService}. */
+public class TestServiceServer {
+  /** The main application allowing this server to be launched from the command line. */
+  public static void main(String[] args) throws Exception {
+    // Let Netty use Conscrypt if it is available.
+    TestUtils.installConscryptIfAvailable();
+    final TestServiceServer server = new TestServiceServer();
+    server.parseArgs(args);
+    if (server.useTls) {
+      System.out.println(
+          "\nUsing fake CA for TLS certificate. Test clients should expect host\n"
+              + "*.test.google.fr and our test CA. For the Java test client binary, use:\n"
+              + "--server_host_override=foo.test.google.fr --use_test_ca=true\n");
+    }
+
+    Runtime.getRuntime()
+        .addShutdownHook(
+            new Thread() {
+              @Override
+              public void run() {
+                try {
+                  System.out.println("Shutting down");
+                  server.stop();
+                } catch (Exception e) {
+                  e.printStackTrace();
+                }
+              }
+            });
+    server.start();
+    System.out.println("Server started on port " + server.port);
+    server.blockUntilShutdown();
+  }
+
+  private int port = 8080;
+  private boolean useTls = true;
+  private boolean useAlts = false;
+
+  private ScheduledExecutorService executor;
+  private Server server;
+
+  @VisibleForTesting
+  void parseArgs(String[] args) {
+    boolean usage = false;
+    for (String arg : args) {
+      if (!arg.startsWith("--")) {
+        System.err.println("All arguments must start with '--': " + arg);
+        usage = true;
+        break;
+      }
+      String[] parts = arg.substring(2).split("=", 2);
+      String key = parts[0];
+      if ("help".equals(key)) {
+        usage = true;
+        break;
+      }
+      if (parts.length != 2) {
+        System.err.println("All arguments must be of the form --arg=value");
+        usage = true;
+        break;
+      }
+      String value = parts[1];
+      if ("port".equals(key)) {
+        port = Integer.parseInt(value);
+      } else if ("use_tls".equals(key)) {
+        useTls = Boolean.parseBoolean(value);
+      } else if ("use_alts".equals(key)) {
+        useAlts = Boolean.parseBoolean(value);
+      } else if ("grpc_version".equals(key)) {
+        if (!"2".equals(value)) {
+          System.err.println("Only grpc version 2 is supported");
+          usage = true;
+          break;
+        }
+      } else {
+        System.err.println("Unknown argument: " + key);
+        usage = true;
+        break;
+      }
+    }
+    if (useAlts) {
+      useTls = false;
+    }
+    if (usage) {
+      TestServiceServer s = new TestServiceServer();
+      System.out.println(
+          "Usage: [ARGS...]"
+              + "\n"
+              + "\n  --port=PORT           Port to connect to. Default " + s.port
+              + "\n  --use_tls=true|false  Whether to use TLS. Default " + s.useTls
+              + "\n  --use_alts=true|false Whether to use ALTS. Enable ALTS will disable TLS."
+              + "\n                        Default " + s.useAlts
+      );
+      System.exit(1);
+    }
+  }
+
+  @VisibleForTesting
+  void start() throws Exception {
+    executor = Executors.newSingleThreadScheduledExecutor();
+    SslContext sslContext = null;
+    if (useAlts) {
+      server =
+          AltsServerBuilder.forPort(port)
+              .addService(
+                  ServerInterceptors.intercept(
+                      new TestServiceImpl(executor), TestServiceImpl.interceptors()))
+              .build()
+              .start();
+    } else {
+      if (useTls) {
+        sslContext =
+            GrpcSslContexts.forServer(
+                    TestUtils.loadCert("server1.pem"), TestUtils.loadCert("server1.key"))
+                .build();
+      }
+      server =
+          NettyServerBuilder.forPort(port)
+              .sslContext(sslContext)
+              .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE)
+              .addService(
+                  ServerInterceptors.intercept(
+                      new TestServiceImpl(executor), TestServiceImpl.interceptors()))
+              .build()
+              .start();
+    }
+  }
+
+  @VisibleForTesting
+  void stop() throws Exception {
+    server.shutdownNow();
+    if (!server.awaitTermination(5, TimeUnit.SECONDS)) {
+      System.err.println("Timed out waiting for server shutdown");
+    }
+    MoreExecutors.shutdownAndAwaitTermination(executor, 5, TimeUnit.SECONDS);
+  }
+
+  @VisibleForTesting
+  int getPort() {
+    return server.getPort();
+  }
+
+  /** Await termination on the main thread since the grpc library uses daemon threads. */
+  private void blockUntilShutdown() throws InterruptedException {
+    if (server != null) {
+      server.awaitTermination();
+    }
+  }
+}
diff --git a/interop-testing/src/main/java/io/grpc/testing/integration/Util.java b/interop-testing/src/main/java/io/grpc/testing/integration/Util.java
new file mode 100644
index 0000000..0ec1285
--- /dev/null
+++ b/interop-testing/src/main/java/io/grpc/testing/integration/Util.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import com.google.protobuf.MessageLite;
+import io.grpc.Metadata;
+import io.grpc.protobuf.lite.ProtoLiteUtils;
+import java.util.List;
+import org.junit.Assert;
+
+/**
+ * Utility methods to support integration testing.
+ */
+public class Util {
+
+  public static final Metadata.Key<Messages.SimpleContext> METADATA_KEY =
+      Metadata.Key.of(
+          "grpc.testing.SimpleContext" + Metadata.BINARY_HEADER_SUFFIX,
+          ProtoLiteUtils.metadataMarshaller(Messages.SimpleContext.getDefaultInstance()));
+  public static final Metadata.Key<String> ECHO_INITIAL_METADATA_KEY
+      = Metadata.Key.of("x-grpc-test-echo-initial", Metadata.ASCII_STRING_MARSHALLER);
+  public static final Metadata.Key<byte[]> ECHO_TRAILING_METADATA_KEY
+      = Metadata.Key.of("x-grpc-test-echo-trailing-bin", Metadata.BINARY_BYTE_MARSHALLER);
+
+  /** Assert that two messages are equal, producing a useful message if not. */
+  public static void assertEquals(MessageLite expected, MessageLite actual) {
+    if (expected == null || actual == null) {
+      Assert.assertEquals(expected, actual);
+    } else {
+      if (!expected.equals(actual)) {
+        // This assertEquals should always complete.
+        Assert.assertEquals(expected.toString(), actual.toString());
+        // But if it doesn't, then this should.
+        Assert.assertEquals(expected, actual);
+        Assert.fail("Messages not equal, but assertEquals didn't throw");
+      }
+    }
+  }
+
+  /** Assert that two lists of messages are equal, producing a useful message if not. */
+  public static void assertEquals(List<? extends MessageLite> expected,
+      List<? extends MessageLite> actual) {
+    if (expected == null || actual == null) {
+      Assert.assertEquals(expected, actual);
+    } else if (expected.size() != actual.size()) {
+      Assert.assertEquals(expected, actual);
+    } else {
+      for (int i = 0; i < expected.size(); i++) {
+        assertEquals(expected.get(i), actual.get(i));
+      }
+    }
+  }
+}
diff --git a/interop-testing/src/main/proto/grpc/testing/empty.proto b/interop-testing/src/main/proto/grpc/testing/empty.proto
new file mode 100644
index 0000000..bd626ab
--- /dev/null
+++ b/interop-testing/src/main/proto/grpc/testing/empty.proto
@@ -0,0 +1,29 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto2";
+
+package grpc.testing;
+
+option java_package = "io.grpc.testing.integration";
+option java_outer_classname = "EmptyProtos";
+
+// An empty message that you can re-use to avoid defining duplicated empty
+// messages in your project. A typical example is to use it as argument or the
+// return value of a service API. For instance:
+//
+//   service Foo {
+//     rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { };
+//   };
+//
+message Empty {}
diff --git a/interop-testing/src/main/proto/grpc/testing/messages.proto b/interop-testing/src/main/proto/grpc/testing/messages.proto
new file mode 100644
index 0000000..c956e26
--- /dev/null
+++ b/interop-testing/src/main/proto/grpc/testing/messages.proto
@@ -0,0 +1,173 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+// Message definitions to be used by integration test service definitions.
+
+syntax = "proto3";
+
+import "google/protobuf/wrappers.proto";
+
+package grpc.testing;
+
+option java_package = "io.grpc.testing.integration";
+
+// DEPRECATED, don't use. To be removed shortly.
+// The type of payload that should be returned.
+enum PayloadType {
+  // Compressable text format.
+  COMPRESSABLE = 0;
+
+  // Uncompressable binary format.
+  UNCOMPRESSABLE = 1;
+
+  // Randomly chosen from all other formats defined in this enum.
+  RANDOM = 2;
+}
+
+// A block of data, to simply increase gRPC message size.
+message Payload {
+  // DEPRECATED, don't use. To be removed shortly.
+  // The type of data in body.
+  PayloadType type = 1;
+  // Primary contents of payload.
+  bytes body = 2;
+}
+
+// A protobuf representation for grpc status. This is used by test
+// clients to specify a status that the server should attempt to return.
+message EchoStatus {
+  int32 code = 1;
+  string message = 2;
+}
+
+// Unary request.
+message SimpleRequest {
+  // DEPRECATED, don't use. To be removed shortly.
+  // Desired payload type in the response from the server.
+  // If response_type is RANDOM, server randomly chooses one from other formats.
+  PayloadType response_type = 1;
+
+  // Desired payload size in the response from the server.
+  int32 response_size = 2;
+
+  // Optional input payload sent along with the request.
+  Payload payload = 3;
+
+  // Whether SimpleResponse should include username.
+  bool fill_username = 4;
+
+  // Whether SimpleResponse should include OAuth scope.
+  bool fill_oauth_scope = 5;
+
+  // Whether to request the server to compress the response. This field is
+  // "nullable" in order to interoperate seamlessly with clients not able to
+  // implement the full compression tests by introspecting the call to verify
+  // the response's compression status.
+  google.protobuf.BoolValue response_compressed = 6;
+
+  // Whether server should return a given status
+  EchoStatus response_status = 7;
+
+  // Whether the server should expect this request to be compressed.
+  google.protobuf.BoolValue expect_compressed = 8;
+}
+
+// Unary response, as configured by the request.
+message SimpleResponse {
+  // Payload to increase message size.
+  Payload payload = 1;
+  // The user the request came from, for verifying authentication was
+  // successful when the client expected it.
+  string username = 2;
+  // OAuth scope.
+  string oauth_scope = 3;
+}
+
+message SimpleContext {
+  string value = 1;
+}
+
+// Client-streaming request.
+message StreamingInputCallRequest {
+  // Optional input payload sent along with the request.
+  Payload payload = 1;
+
+  // Whether the server should expect this request to be compressed. This field
+  // is "nullable" in order to interoperate seamlessly with servers not able to
+  // implement the full compression tests by introspecting the call to verify
+  // the request's compression status.
+  google.protobuf.BoolValue expect_compressed = 2;
+
+  // Not expecting any payload from the response.
+}
+
+// Client-streaming response.
+message StreamingInputCallResponse {
+  // Aggregated size of payloads received from the client.
+  int32 aggregated_payload_size = 1;
+}
+
+// Configuration for a particular response.
+message ResponseParameters {
+  // Desired payload sizes in responses from the server.
+  int32 size = 1;
+
+  // Desired interval between consecutive responses in the response stream in
+  // microseconds.
+  int32 interval_us = 2;
+
+  // Whether to request the server to compress the response. This field is
+  // "nullable" in order to interoperate seamlessly with clients not able to
+  // implement the full compression tests by introspecting the call to verify
+  // the response's compression status.
+  google.protobuf.BoolValue compressed = 3;
+}
+
+// Server-streaming request.
+message StreamingOutputCallRequest {
+  // DEPRECATED, don't use. To be removed shortly.
+  // Desired payload type in the response from the server.
+  // If response_type is RANDOM, the payload from each response in the stream
+  // might be of different types. This is to simulate a mixed type of payload
+  // stream.
+  PayloadType response_type = 1;
+
+  // Configuration for each expected response message.
+  repeated ResponseParameters response_parameters = 2;
+
+  // Optional input payload sent along with the request.
+  Payload payload = 3;
+
+  // Whether server should return a given status
+  EchoStatus response_status = 7;
+}
+
+// Server-streaming response, as configured by the request and parameters.
+message StreamingOutputCallResponse {
+  // Payload to increase response size.
+  Payload payload = 1;
+}
+
+// For reconnect interop test only.
+// Client tells server what reconnection parameters it used.
+message ReconnectParams {
+  int32 max_reconnect_backoff_ms = 1;
+}
+
+// For reconnect interop test only.
+// Server tells client whether its reconnects are following the spec and the
+// reconnect backoffs it saw.
+message ReconnectInfo {
+  bool passed = 1;
+  repeated int32 backoff_ms = 2;
+}
diff --git a/interop-testing/src/main/proto/grpc/testing/metrics.proto b/interop-testing/src/main/proto/grpc/testing/metrics.proto
new file mode 100644
index 0000000..d6b5b04
--- /dev/null
+++ b/interop-testing/src/main/proto/grpc/testing/metrics.proto
@@ -0,0 +1,50 @@
+// Copyright 2015-2016 The gRPC Authors
+//
+// Licensed 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.
+// Contains the definitions for a metrics service and the type of metrics
+// exposed by the service.
+//
+// Currently, 'Gauge' (i.e a metric that represents the measured value of
+// something at an instant of time) is the only metric type supported by the
+// service.
+syntax = "proto3";
+
+package grpc.testing;
+
+option java_package = "io.grpc.testing.integration";
+
+// Reponse message containing the gauge name and value
+message GaugeResponse {
+  string name = 1;
+  oneof value {
+    int64 long_value = 2;
+    double double_value = 3;
+    string string_value = 4;
+  }
+}
+
+// Request message containing the gauge name
+message GaugeRequest {
+  string name = 1;
+}
+
+message EmptyMessage {}
+
+service MetricsService {
+  // Returns the values of all the gauges that are currently being maintained by
+  // the service
+  rpc GetAllGauges(EmptyMessage) returns (stream GaugeResponse);
+
+  // Returns the value of one gauge
+  rpc GetGauge(GaugeRequest) returns (GaugeResponse);
+}
diff --git a/interop-testing/src/main/proto/grpc/testing/test.proto b/interop-testing/src/main/proto/grpc/testing/test.proto
new file mode 100644
index 0000000..ba47f46
--- /dev/null
+++ b/interop-testing/src/main/proto/grpc/testing/test.proto
@@ -0,0 +1,78 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+// An integration test service that covers all the method signature permutations
+// of unary/streaming requests/responses.
+syntax = "proto3";
+
+import "grpc/testing/empty.proto";
+import "grpc/testing/messages.proto";
+
+package grpc.testing;
+
+option java_package = "io.grpc.testing.integration";
+
+// A simple service to test the various types of RPCs and experiment with
+// performance with various types of payload.
+service TestService {
+  // One empty request followed by one empty response.
+  rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty);
+
+  // One request followed by one response.
+  rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
+
+  // One request followed by one response. Response has cache control
+  // headers set such that a caching HTTP proxy (such as GFE) can
+  // satisfy subsequent requests.
+  rpc CacheableUnaryCall(SimpleRequest) returns (SimpleResponse);
+
+  // One request followed by a sequence of responses (streamed download).
+  // The server returns the payload with client desired type and sizes.
+  rpc StreamingOutputCall(StreamingOutputCallRequest)
+      returns (stream StreamingOutputCallResponse);
+
+  // A sequence of requests followed by one response (streamed upload).
+  // The server returns the aggregated size of client payload as the result.
+  rpc StreamingInputCall(stream StreamingInputCallRequest)
+      returns (StreamingInputCallResponse);
+
+  // A sequence of requests with each request served by the server immediately.
+  // As one request could lead to multiple responses, this interface
+  // demonstrates the idea of full duplexing.
+  rpc FullDuplexCall(stream StreamingOutputCallRequest)
+      returns (stream StreamingOutputCallResponse);
+
+  // A sequence of requests followed by a sequence of responses.
+  // The server buffers all the client requests and then serves them in order. A
+  // stream of responses are returned to the client when the server starts with
+  // first request.
+  rpc HalfDuplexCall(stream StreamingOutputCallRequest)
+      returns (stream StreamingOutputCallResponse);
+
+  // The test server will not implement this method. It will be used
+  // to test the behavior when clients call unimplemented methods.
+  rpc UnimplementedCall(grpc.testing.Empty) returns (grpc.testing.Empty);
+}
+
+// A simple service NOT implemented at servers so clients can test for
+// that case.
+service UnimplementedService {
+  // A call that no server should implement
+  rpc UnimplementedCall(grpc.testing.Empty) returns(grpc.testing.Empty);
+}
+
+// A service used to control reconnect server.
+service ReconnectService {
+  rpc Start(grpc.testing.Empty) returns (grpc.testing.Empty);
+  rpc Stop(grpc.testing.Empty) returns (grpc.testing.ReconnectInfo);
+}
diff --git a/interop-testing/src/main/resources/io/grpc/testing/integration/testdata/uncompressable.bin b/interop-testing/src/main/resources/io/grpc/testing/integration/testdata/uncompressable.bin
new file mode 100644
index 0000000..e5a2456
--- /dev/null
+++ b/interop-testing/src/main/resources/io/grpc/testing/integration/testdata/uncompressable.bin
Binary files differ
diff --git a/interop-testing/src/test/java/io/grpc/ChannelAndServerBuilderTest.java b/interop-testing/src/test/java/io/grpc/ChannelAndServerBuilderTest.java
new file mode 100644
index 0000000..6ac5e6d
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/ChannelAndServerBuilderTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.reflect.ClassPath;
+import com.google.common.reflect.ClassPath.ClassInfo;
+import com.google.common.truth.Truth;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests that Channel and Server builders properly hide the static constructors.
+ *
+ * <p>This test does nothing on Java 9.
+ */
+@RunWith(Parameterized.class)
+public class ChannelAndServerBuilderTest {
+
+  @Parameter
+  public Class<?> builderClass;
+
+  /**
+   * Javadoc.
+   */
+  @Parameters(name = "class={0}")
+  public static Collection<Object[]> params() throws Exception {
+    ClassLoader loader = ChannelAndServerBuilderTest.class.getClassLoader();
+    Collection<ClassInfo> classInfos =
+        ClassPath.from(loader).getTopLevelClassesRecursive("io.grpc");
+    // Java 9 doesn't expose the URLClassLoader, which breaks searching through the classpath
+    if (classInfos.isEmpty()) {
+      return new ArrayList<Object[]>();
+    }
+    List<Object[]> classes = new ArrayList<Object[]>();
+    for (ClassInfo classInfo : classInfos) {
+      Class<?> clazz = Class.forName(classInfo.getName(), false /*initialize*/, loader);
+      if (ServerBuilder.class.isAssignableFrom(clazz) && clazz != ServerBuilder.class) {
+        classes.add(new Object[]{clazz});
+      } else if (ManagedChannelBuilder.class.isAssignableFrom(clazz)
+          && clazz != ManagedChannelBuilder.class) {
+        classes.add(new Object[]{clazz});
+      }
+    }
+    Truth.assertWithMessage("Unable to find any builder classes").that(classes).isNotEmpty();
+    return classes;
+  }
+
+  @Test
+  public void serverBuilderHidesMethod_forPort() throws Exception {
+    Assume.assumeTrue(ServerBuilder.class.isAssignableFrom(builderClass));
+    Method method = builderClass.getMethod("forPort", int.class);
+
+    assertTrue(Modifier.isStatic(method.getModifiers()));
+    assertTrue(ServerBuilder.class.isAssignableFrom(method.getReturnType()));
+    assertSame(builderClass, method.getDeclaringClass());
+  }
+
+  @Test
+  public void channelBuilderHidesMethod_forAddress() throws Exception {
+    Assume.assumeTrue(ManagedChannelBuilder.class.isAssignableFrom(builderClass));
+    Method method = builderClass.getMethod("forAddress", String.class, int.class);
+
+    assertTrue(Modifier.isStatic(method.getModifiers()));
+    assertTrue(ManagedChannelBuilder.class.isAssignableFrom(method.getReturnType()));
+    assertSame(builderClass, method.getDeclaringClass());
+  }
+
+  @Test
+  public void channelBuilderHidesMethod_forTarget() throws Exception {
+    Assume.assumeTrue(ManagedChannelBuilder.class.isAssignableFrom(builderClass));
+    Method method = builderClass.getMethod("forTarget", String.class);
+
+    assertTrue(Modifier.isStatic(method.getModifiers()));
+    assertTrue(ManagedChannelBuilder.class.isAssignableFrom(method.getReturnType()));
+    assertSame(builderClass, method.getDeclaringClass());
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/stub/StubConfigTest.java b/interop-testing/src/test/java/io/grpc/stub/StubConfigTest.java
new file mode 100644
index 0000000..c227d42
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/stub/StubConfigTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.Deadline;
+import io.grpc.MethodDescriptor;
+import io.grpc.internal.NoopClientCall;
+import io.grpc.testing.integration.Messages.SimpleRequest;
+import io.grpc.testing.integration.Messages.SimpleResponse;
+import io.grpc.testing.integration.TestServiceGrpc;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for stub reconfiguration.
+ */
+@RunWith(JUnit4.class)
+public class StubConfigTest {
+
+  @Mock
+  private Channel channel;
+
+  @Mock
+  private StreamObserver<SimpleResponse> responseObserver;
+
+  /**
+   * Sets up mocks.
+   */
+  @Before public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    ClientCall<SimpleRequest, SimpleResponse> call =
+        new NoopClientCall<SimpleRequest, SimpleResponse>();
+    when(channel.newCall(
+        Mockito.<MethodDescriptor<SimpleRequest, SimpleResponse>>any(), any(CallOptions.class)))
+        .thenReturn(call);
+  }
+
+  @Test
+  public void testConfigureDeadline() {
+    Deadline deadline = Deadline.after(2, NANOSECONDS);
+    // Create a default stub
+    TestServiceGrpc.TestServiceBlockingStub stub = TestServiceGrpc.newBlockingStub(channel);
+    assertNull(stub.getCallOptions().getDeadline());
+    // Reconfigure it
+    TestServiceGrpc.TestServiceBlockingStub reconfiguredStub = stub.withDeadline(deadline);
+    // New altered config
+    assertEquals(deadline, reconfiguredStub.getCallOptions().getDeadline());
+    // Default config unchanged
+    assertNull(stub.getCallOptions().getDeadline());
+  }
+
+  @Test
+  public void testStubCallOptionsPopulatedToNewCall() {
+    TestServiceGrpc.TestServiceStub stub = TestServiceGrpc.newStub(channel);
+    CallOptions options1 = stub.getCallOptions();
+    SimpleRequest request = SimpleRequest.getDefaultInstance();
+    stub.unaryCall(request, responseObserver);
+    verify(channel).newCall(same(TestServiceGrpc.getUnaryCallMethod()), same(options1));
+    stub = stub.withDeadlineAfter(2, NANOSECONDS);
+    CallOptions options2 = stub.getCallOptions();
+    assertNotSame(options1, options2);
+    stub.unaryCall(request, responseObserver);
+    verify(channel).newCall(same(TestServiceGrpc.getUnaryCallMethod()), same(options2));
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/AutoWindowSizingOnTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/AutoWindowSizingOnTest.java
new file mode 100644
index 0000000..68b081a
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/AutoWindowSizingOnTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import io.grpc.ManagedChannel;
+import io.grpc.internal.AbstractServerImplBuilder;
+import io.grpc.netty.InternalHandlerSettings;
+import io.grpc.netty.NegotiationType;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.netty.NettyServerBuilder;
+import org.junit.BeforeClass;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class AutoWindowSizingOnTest extends AbstractInteropTest {
+
+  @BeforeClass
+  public static void turnOnAutoWindow() {
+    InternalHandlerSettings.enable(true);
+    InternalHandlerSettings.autoWindowOn(true);
+  }
+
+  @Override
+  protected AbstractServerImplBuilder<?> getServerBuilder() {
+    return NettyServerBuilder.forPort(0)
+        .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE);
+  }
+
+  @Override
+  protected ManagedChannel createChannel() {
+    NettyChannelBuilder builder = NettyChannelBuilder.forAddress("localhost", getPort())
+        .negotiationType(NegotiationType.PLAINTEXT)
+        .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE);
+    io.grpc.internal.TestingAccessor.setStatsImplementation(
+        builder, createClientCensusStatsModule());
+    return builder.build();
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/CascadingTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/CascadingTest.java
new file mode 100644
index 0000000..4b23519
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/CascadingTest.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static com.google.common.truth.Truth.assertAbout;
+import static io.grpc.testing.DeadlineSubject.deadline;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.Context;
+import io.grpc.Context.CancellableContext;
+import io.grpc.Deadline;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.Server;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.ServerInterceptors;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.stub.ServerCallStreamObserver;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.integration.Messages.SimpleRequest;
+import io.grpc.testing.integration.Messages.SimpleResponse;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Integration test for various forms of cancellation and deadline propagation.
+ */
+@RunWith(JUnit4.class)
+public class CascadingTest {
+
+  @Mock
+  TestServiceGrpc.TestServiceImplBase service;
+  private ManagedChannel channel;
+  private Server server;
+  private CountDownLatch observedCancellations;
+  private CountDownLatch receivedCancellations;
+  private TestServiceGrpc.TestServiceBlockingStub blockingStub;
+  private TestServiceGrpc.TestServiceStub asyncStub;
+  private TestServiceGrpc.TestServiceFutureStub futureStub;
+  private ExecutorService otherWork;
+
+  @Before
+  public void setUp() throws Exception {
+    MockitoAnnotations.initMocks(this);
+    // Use a cached thread pool as we need a thread for each blocked call
+    otherWork = Executors.newCachedThreadPool();
+    channel = InProcessChannelBuilder.forName("channel").executor(otherWork).build();
+    blockingStub = TestServiceGrpc.newBlockingStub(channel);
+    asyncStub = TestServiceGrpc.newStub(channel);
+    futureStub = TestServiceGrpc.newFutureStub(channel);
+  }
+
+  @After
+  public void tearDown() {
+    channel.shutdownNow();
+    server.shutdownNow();
+    otherWork.shutdownNow();
+  }
+
+  /**
+   * Test {@link Context} cancellation propagates from the first node in the call chain all the way
+   * to the last.
+   */
+  @Test
+  public void testCascadingCancellationViaOuterContextCancellation() throws Exception {
+    observedCancellations = new CountDownLatch(2);
+    receivedCancellations = new CountDownLatch(3);
+    Future<?> chainReady = startChainingServer(3);
+    CancellableContext context = Context.current().withCancellation();
+    Future<SimpleResponse> future;
+    Context prevContext = context.attach();
+    try {
+      future = futureStub.unaryCall(SimpleRequest.getDefaultInstance());
+    } finally {
+      context.detach(prevContext);
+    }
+    chainReady.get(5, TimeUnit.SECONDS);
+
+    context.cancel(null);
+    try {
+      future.get(5, TimeUnit.SECONDS);
+      fail("Expected cancellation");
+    } catch (ExecutionException ex) {
+      Status status = Status.fromThrowable(ex);
+      assertEquals(Status.Code.CANCELLED, status.getCode());
+
+      // Should have observed 2 cancellations responses from downstream servers
+      if (!observedCancellations.await(5, TimeUnit.SECONDS)) {
+        fail("Expected number of cancellations not observed by clients");
+      }
+      if (!receivedCancellations.await(5, TimeUnit.SECONDS)) {
+        fail("Expected number of cancellations to be received by servers not observed");
+      }
+    }
+  }
+
+  /**
+   * Test that cancellation via call cancellation propagates down the call.
+   */
+  @Test
+  public void testCascadingCancellationViaRpcCancel() throws Exception {
+    observedCancellations = new CountDownLatch(2);
+    receivedCancellations = new CountDownLatch(3);
+    Future<?> chainReady = startChainingServer(3);
+    Future<SimpleResponse> future = futureStub.unaryCall(SimpleRequest.getDefaultInstance());
+    chainReady.get(5, TimeUnit.SECONDS);
+
+    future.cancel(true);
+    assertTrue(future.isCancelled());
+    if (!observedCancellations.await(5, TimeUnit.SECONDS)) {
+      fail("Expected number of cancellations not observed by clients");
+    }
+    if (!receivedCancellations.await(5, TimeUnit.SECONDS)) {
+      fail("Expected number of cancellations to be received by servers not observed");
+    }
+  }
+
+  /**
+   * Test that when RPC cancellation propagates up a call chain, the cancellation of the parent
+   * RPC triggers cancellation of all of its children.
+   */
+  @Test
+  public void testCascadingCancellationViaLeafFailure() throws Exception {
+    // All nodes (15) except one edge of the tree (4) will be cancelled.
+    observedCancellations = new CountDownLatch(11);
+    receivedCancellations = new CountDownLatch(11);
+    startCallTreeServer(3);
+    try {
+      // Use response size limit to control tree nodeCount.
+      blockingStub.unaryCall(Messages.SimpleRequest.newBuilder().setResponseSize(3).build());
+      fail("Expected abort");
+    } catch (StatusRuntimeException sre) {
+      // Wait for the workers to finish
+      Status status = sre.getStatus();
+      // Outermost caller observes ABORTED propagating up from the failing leaf,
+      // The descendant RPCs are cancelled so they receive CANCELLED.
+      assertEquals(Status.Code.ABORTED, status.getCode());
+
+      if (!observedCancellations.await(5, TimeUnit.SECONDS)) {
+        fail("Expected number of cancellations not observed by clients");
+      }
+      if (!receivedCancellations.await(5, TimeUnit.SECONDS)) {
+        fail("Expected number of cancellations to be received by servers not observed");
+      }
+    }
+  }
+
+  @Test
+  public void testDeadlinePropagation() throws Exception {
+    final AtomicInteger recursionDepthRemaining = new AtomicInteger(3);
+    final SettableFuture<Deadline> finalDeadline = SettableFuture.create();
+    class DeadlineSaver extends TestServiceGrpc.TestServiceImplBase {
+      @Override
+      public void unaryCall(final SimpleRequest request,
+          final StreamObserver<SimpleResponse> responseObserver) {
+        Context.currentContextExecutor(otherWork).execute(new Runnable() {
+          @Override
+          public void run() {
+            try {
+              if (recursionDepthRemaining.decrementAndGet() == 0) {
+                finalDeadline.set(Context.current().getDeadline());
+                responseObserver.onNext(SimpleResponse.getDefaultInstance());
+              } else {
+                responseObserver.onNext(blockingStub.unaryCall(request));
+              }
+              responseObserver.onCompleted();
+            } catch (Exception ex) {
+              responseObserver.onError(ex);
+            }
+          }
+        });
+      }
+    }
+
+    server = InProcessServerBuilder.forName("channel").executor(otherWork)
+        .addService(new DeadlineSaver())
+        .build().start();
+
+    Deadline initialDeadline = Deadline.after(1, TimeUnit.MINUTES);
+    blockingStub.withDeadline(initialDeadline).unaryCall(SimpleRequest.getDefaultInstance());
+    assertNotSame(initialDeadline, finalDeadline);
+    // Since deadline is re-calculated at each hop, some variance is acceptable and expected.
+    assertAbout(deadline())
+        .that(finalDeadline.get()).isWithin(1, TimeUnit.SECONDS).of(initialDeadline);
+  }
+
+  /**
+   * Create a chain of client to server calls which can be cancelled top down.
+   *
+   * @return a Future that completes when call chain is created
+   */
+  private Future<?> startChainingServer(final int depthThreshold) throws IOException {
+    final AtomicInteger serversReady = new AtomicInteger();
+    final SettableFuture<Void> chainReady = SettableFuture.create();
+    class ChainingService extends TestServiceGrpc.TestServiceImplBase {
+      @Override
+      public void unaryCall(final SimpleRequest request,
+          final StreamObserver<SimpleResponse> responseObserver) {
+        ((ServerCallStreamObserver) responseObserver).setOnCancelHandler(new Runnable() {
+          @Override
+          public void run() {
+            receivedCancellations.countDown();
+          }
+        });
+        if (serversReady.incrementAndGet() == depthThreshold) {
+          // Stop recursion
+          chainReady.set(null);
+          return;
+        }
+
+        Context.currentContextExecutor(otherWork).execute(new Runnable() {
+          @Override
+          public void run() {
+            try {
+              blockingStub.unaryCall(request);
+            } catch (StatusRuntimeException e) {
+              Status status = e.getStatus();
+              if (status.getCode() == Status.Code.CANCELLED) {
+                observedCancellations.countDown();
+              } else {
+                responseObserver.onError(e);
+              }
+            }
+          }
+        });
+      }
+    }
+
+    server = InProcessServerBuilder.forName("channel").executor(otherWork)
+        .addService(new ChainingService())
+        .build().start();
+    return chainReady;
+  }
+
+  /**
+   * Create a tree of client to server calls where each received call on the server
+   * fans out to two downstream calls. Uses SimpleRequest.response_size to limit the nodeCount
+   * of the tree. One of the leaves will ABORT to trigger cancellation back up to tree.
+   */
+  private void startCallTreeServer(int depthThreshold) throws IOException {
+    final AtomicInteger nodeCount = new AtomicInteger((2 << depthThreshold) - 1);
+    server = InProcessServerBuilder.forName("channel").executor(otherWork).addService(
+        ServerInterceptors.intercept(service,
+            new ServerInterceptor() {
+              @Override
+              public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+                  final ServerCall<ReqT, RespT> call,
+                  Metadata headers,
+                  ServerCallHandler<ReqT, RespT> next) {
+                // Respond with the headers but nothing else.
+                call.sendHeaders(new Metadata());
+                call.request(1);
+                return new ServerCall.Listener<ReqT>() {
+                  @Override
+                  public void onMessage(final ReqT message) {
+                    Messages.SimpleRequest req = (Messages.SimpleRequest) message;
+                    if (nodeCount.decrementAndGet() == 0) {
+                      // we are in the final leaf node so trigger an ABORT upwards
+                      Context.currentContextExecutor(otherWork).execute(new Runnable() {
+                        @Override
+                        public void run() {
+                          call.close(Status.ABORTED, new Metadata());
+                        }
+                      });
+                    } else if (req.getResponseSize() != 0) {
+                      // We are in a non leaf node so fire off two requests
+                      req = req.toBuilder().setResponseSize(req.getResponseSize() - 1).build();
+                      for (int i = 0; i < 2; i++) {
+                        asyncStub.unaryCall(req,
+                            new StreamObserver<Messages.SimpleResponse>() {
+                              @Override
+                              public void onNext(Messages.SimpleResponse value) {
+                              }
+
+                              @Override
+                              public void onError(Throwable t) {
+                                Status status = Status.fromThrowable(t);
+                                if (status.getCode() == Status.Code.CANCELLED) {
+                                  observedCancellations.countDown();
+                                }
+                                // Propagate closure upwards.
+                                try {
+                                  call.close(status, new Metadata());
+                                } catch (IllegalStateException t2) {
+                                  // Ignore error if already closed.
+                                }
+                              }
+
+                              @Override
+                              public void onCompleted() {
+                              }
+                            });
+                      }
+                    }
+                  }
+
+                  @Override
+                  public void onCancel() {
+                    receivedCancellations.countDown();
+                  }
+                };
+              }
+            })
+    ).build();
+    server.start();
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/CompressionTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/CompressionTest.java
new file mode 100644
index 0000000..9ebcb2e
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/CompressionTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.internal.GrpcUtil.MESSAGE_ACCEPT_ENCODING_KEY;
+import static io.grpc.internal.GrpcUtil.MESSAGE_ENCODING_KEY;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.protobuf.ByteString;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientCall.Listener;
+import io.grpc.ClientInterceptor;
+import io.grpc.Codec;
+import io.grpc.CompressorRegistry;
+import io.grpc.DecompressorRegistry;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.ServerInterceptors;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.integration.Messages.Payload;
+import io.grpc.testing.integration.Messages.SimpleRequest;
+import io.grpc.testing.integration.Messages.SimpleResponse;
+import io.grpc.testing.integration.TestServiceGrpc.TestServiceBlockingStub;
+import io.grpc.testing.integration.TransportCompressionTest.Fzip;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests for compression configurations.
+ *
+ * <p>Because of the asymmetry of clients and servers, clients will not know what decompression
+ * methods the server supports.  In cases where the client is willing to encode, and the server
+ * is willing to decode, a second RPC is sent to show that the client has learned and will use
+ * the encoding.
+ *
+ * <p>In cases where compression is negotiated, but either the client or the server doesn't
+ * actually want to encode, a dummy codec is used to record usage.  If compression is not enabled,
+ * the codec will see no data pass through.  This is checked on each test to ensure the code is
+ * doing the right thing.
+ */
+@RunWith(Parameterized.class)
+public class CompressionTest {
+  private static final ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
+  // Ensures that both the request and response messages are more than 0 bytes.  The framer/deframer
+  // may not use the compressor if the message is empty.
+  private static final SimpleRequest REQUEST = SimpleRequest.newBuilder()
+      .setResponseSize(1)
+      .build();
+
+  private Fzip clientCodec = new Fzip("fzip", Codec.Identity.NONE);
+  private Fzip serverCodec = new Fzip("fzip", Codec.Identity.NONE);
+  private DecompressorRegistry clientDecompressors = DecompressorRegistry.emptyInstance();
+  private DecompressorRegistry serverDecompressors = DecompressorRegistry.emptyInstance();
+  private CompressorRegistry clientCompressors = CompressorRegistry.newEmptyInstance();
+  private CompressorRegistry serverCompressors = CompressorRegistry.newEmptyInstance();
+
+  /** The headers received by the server from the client. */
+  private volatile Metadata serverResponseHeaders;
+  /** The headers received by the client from the server. */
+  private volatile Metadata clientResponseHeaders;
+
+  // Params
+  private final boolean enableClientMessageCompression;
+  private final boolean enableServerMessageCompression;
+  private final boolean clientAcceptEncoding;
+  private final boolean clientEncoding;
+  private final boolean serverAcceptEncoding;
+  private final boolean serverEncoding;
+
+  private Server server;
+  private ManagedChannel channel;
+  private TestServiceBlockingStub stub;
+
+  /**
+   * Auto called by test.
+   */
+  public CompressionTest(
+      boolean enableClientMessageCompression,
+      boolean clientAcceptEncoding,
+      boolean clientEncoding,
+      boolean enableServerMessageCompression,
+      boolean serverAcceptEncoding,
+      boolean serverEncoding) {
+    this.enableClientMessageCompression = enableClientMessageCompression;
+    this.clientAcceptEncoding = clientAcceptEncoding;
+    this.clientEncoding = clientEncoding;
+    this.enableServerMessageCompression = enableServerMessageCompression;
+    this.serverAcceptEncoding = serverAcceptEncoding;
+    this.serverEncoding = serverEncoding;
+  }
+
+  @Before
+  public void setUp() throws Exception {
+    clientDecompressors = clientDecompressors.with(Codec.Identity.NONE, false);
+    serverDecompressors = serverDecompressors.with(Codec.Identity.NONE, false);
+  }
+
+  @After
+  public void tearDown() {
+    channel.shutdownNow();
+    server.shutdownNow();
+    executor.shutdownNow();
+  }
+
+  /**
+   * Parameters for test.
+   */
+  @Parameters
+  public static Collection<Object[]> params() {
+    boolean[] bools = new boolean[]{false, true};
+    List<Object[]> combos = new ArrayList<Object[]>(64);
+    for (boolean enableClientMessageCompression : bools) {
+      for (boolean clientAcceptEncoding : bools) {
+        for (boolean clientEncoding : bools) {
+          for (boolean enableServerMessageCompression : bools) {
+            for (boolean serverAcceptEncoding : bools) {
+              for (boolean serverEncoding : bools) {
+                combos.add(new Object[] {
+                    enableClientMessageCompression, clientAcceptEncoding, clientEncoding,
+                    enableServerMessageCompression, serverAcceptEncoding, serverEncoding});
+              }
+            }
+          }
+        }
+      }
+    }
+    return combos;
+  }
+
+  @Test
+  public void compression() throws Exception {
+    if (clientAcceptEncoding) {
+      clientDecompressors = clientDecompressors.with(clientCodec, true);
+    }
+    if (clientEncoding) {
+      clientCompressors.register(clientCodec);
+    }
+    if (serverAcceptEncoding) {
+      serverDecompressors = serverDecompressors.with(serverCodec, true);
+    }
+    if (serverEncoding) {
+      serverCompressors.register(serverCodec);
+    }
+
+    server = ServerBuilder.forPort(0)
+        .addService(
+            ServerInterceptors.intercept(new LocalServer(), new ServerCompressorInterceptor()))
+        .compressorRegistry(serverCompressors)
+        .decompressorRegistry(serverDecompressors)
+        .build()
+        .start();
+
+    channel = ManagedChannelBuilder.forAddress("localhost", server.getPort())
+        .decompressorRegistry(clientDecompressors)
+        .compressorRegistry(clientCompressors)
+        .intercept(new ClientCompressorInterceptor())
+        .usePlaintext()
+        .build();
+    stub = TestServiceGrpc.newBlockingStub(channel);
+
+    stub.unaryCall(REQUEST);
+
+    if (clientAcceptEncoding && serverEncoding) {
+      assertEquals("fzip", clientResponseHeaders.get(MESSAGE_ENCODING_KEY));
+      if (enableServerMessageCompression) {
+        assertTrue(clientCodec.anyRead);
+        assertTrue(serverCodec.anyWritten);
+      } else {
+        assertFalse(clientCodec.anyRead);
+        assertFalse(serverCodec.anyWritten);
+      }
+    } else {
+      // Either identity or null is accepted.
+      assertThat(clientResponseHeaders.get(MESSAGE_ENCODING_KEY))
+          .isAnyOf(Codec.Identity.NONE.getMessageEncoding(), null);
+      assertFalse(clientCodec.anyRead);
+      assertFalse(serverCodec.anyWritten);
+    }
+
+    if (serverAcceptEncoding) {
+      assertEqualsString("fzip", clientResponseHeaders.get(MESSAGE_ACCEPT_ENCODING_KEY));
+    } else {
+      assertNull(clientResponseHeaders.get(MESSAGE_ACCEPT_ENCODING_KEY));
+    }
+
+    if (clientAcceptEncoding) {
+      assertEqualsString("fzip", serverResponseHeaders.get(MESSAGE_ACCEPT_ENCODING_KEY));
+    } else {
+      assertNull(serverResponseHeaders.get(MESSAGE_ACCEPT_ENCODING_KEY));
+    }
+
+    // Second call, once the client knows what the server supports.
+    if (clientEncoding && serverAcceptEncoding) {
+      assertEquals("fzip", serverResponseHeaders.get(MESSAGE_ENCODING_KEY));
+      if (enableClientMessageCompression) {
+        assertTrue(clientCodec.anyWritten);
+        assertTrue(serverCodec.anyRead);
+      } else {
+        assertFalse(clientCodec.anyWritten);
+        assertFalse(serverCodec.anyRead);
+      }
+    } else {
+      assertNull(serverResponseHeaders.get(MESSAGE_ENCODING_KEY));
+      assertFalse(clientCodec.anyWritten);
+      assertFalse(serverCodec.anyRead);
+    }
+  }
+
+  private static final class LocalServer extends TestServiceGrpc.TestServiceImplBase {
+    @Override
+    public void unaryCall(SimpleRequest request, StreamObserver<SimpleResponse> responseObserver) {
+      responseObserver.onNext(SimpleResponse.newBuilder()
+          .setPayload(Payload.newBuilder()
+              .setBody(ByteString.copyFrom(new byte[]{127})))
+          .build());
+      responseObserver.onCompleted();
+    }
+  }
+
+  private class ServerCompressorInterceptor implements ServerInterceptor {
+    @Override
+    public <ReqT, RespT> io.grpc.ServerCall.Listener<ReqT> interceptCall(
+        ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {
+      if (serverEncoding) {
+        call.setCompression("fzip");
+      }
+      call.setMessageCompression(enableServerMessageCompression);
+      Metadata headersCopy = new Metadata();
+      headersCopy.merge(headers);
+      serverResponseHeaders = headersCopy;
+      return next.startCall(call, headers);
+    }
+  }
+
+  private class ClientCompressorInterceptor implements ClientInterceptor {
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+        MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+      if (clientEncoding && serverAcceptEncoding) {
+        callOptions = callOptions.withCompression("fzip");
+      }
+      ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);
+
+      return new ClientCompressor<ReqT, RespT>(call);
+    }
+  }
+
+  private class ClientCompressor<ReqT, RespT> extends SimpleForwardingClientCall<ReqT, RespT> {
+    protected ClientCompressor(ClientCall<ReqT, RespT> delegate) {
+      super(delegate);
+    }
+
+    @Override
+    public void start(io.grpc.ClientCall.Listener<RespT> responseListener, Metadata headers) {
+      super.start(new ClientHeadersCapture<RespT>(responseListener), headers);
+      setMessageCompression(enableClientMessageCompression);
+    }
+  }
+
+  private class ClientHeadersCapture<RespT> extends SimpleForwardingClientCallListener<RespT> {
+    private ClientHeadersCapture(Listener<RespT> delegate) {
+      super(delegate);
+    }
+
+    @Override
+    public void onHeaders(Metadata headers) {
+      super.onHeaders(headers);
+      Metadata headersCopy = new Metadata();
+      headersCopy.merge(headers);
+      clientResponseHeaders = headersCopy;
+    }
+  }
+
+  private static void assertEqualsString(String expected, byte[] actual) {
+    assertEquals(expected, new String(actual, Charset.forName("US-ASCII")));
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/ConcurrencyTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/ConcurrencyTest.java
new file mode 100644
index 0000000..c94bdf7
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/ConcurrencyTest.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.internal.testing.TestUtils;
+import io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.NegotiationType;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.netty.NettyServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.integration.Messages.PayloadType;
+import io.grpc.testing.integration.Messages.ResponseParameters;
+import io.grpc.testing.integration.Messages.StreamingOutputCallRequest;
+import io.grpc.testing.integration.Messages.StreamingOutputCallResponse;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import java.io.File;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+
+/**
+ * Tests that gRPC clients and servers can handle concurrent RPCs.
+ *
+ * <p>These tests use TLS to make them more realistic, and because we'd like to test the thread
+ * safety of the TLS-related code paths as well.
+ */
+// TODO: Consider augmenting this class to perform non-streaming, client streaming, and
+// bidirectional streaming requests also.
+@RunWith(JUnit4.class)
+public class ConcurrencyTest {
+
+  @Rule public final Timeout globalTimeout = Timeout.seconds(10);
+  
+  /**
+   * A response observer that signals a {@code CountDownLatch} when the proper number of responses
+   * arrives and the server signals that the RPC is complete.
+   */
+  private static class SignalingResponseObserver
+      implements StreamObserver<StreamingOutputCallResponse> {
+    public SignalingResponseObserver(CountDownLatch responsesDoneSignal) {
+      this.responsesDoneSignal = responsesDoneSignal;
+    }
+
+    @Override
+    public void onCompleted() {
+      Preconditions.checkState(numResponsesReceived == NUM_RESPONSES_PER_REQUEST);
+      responsesDoneSignal.countDown();
+    }
+
+    @Override
+    public void onError(Throwable error) {
+      // This should never happen. If it does happen, ensure that the error is visible.
+      error.printStackTrace();
+    }
+
+    @Override
+    public void onNext(StreamingOutputCallResponse response) {
+      numResponsesReceived++;
+    }
+
+    private final CountDownLatch responsesDoneSignal;
+    private int numResponsesReceived = 0;
+  }
+
+  /**
+   * A client worker task that waits until all client workers are ready, then sends a request for a
+   * server-streaming RPC and arranges for a {@code CountDownLatch} to be signaled when the RPC is
+   * complete.
+   */
+  private class ClientWorker implements Runnable {
+    public ClientWorker(CyclicBarrier startBarrier, CountDownLatch responsesDoneSignal) {
+      this.startBarrier = startBarrier;
+      this.responsesDoneSignal = responsesDoneSignal;
+    }
+
+    @Override
+    public void run() {
+      try {
+        // Prepare the request.
+        StreamingOutputCallRequest.Builder requestBuilder = StreamingOutputCallRequest.newBuilder()
+            .setResponseType(PayloadType.RANDOM);
+        for (int i = 0; i < NUM_RESPONSES_PER_REQUEST; i++) {
+          requestBuilder.addResponseParameters(ResponseParameters.newBuilder()
+              .setSize(1000)
+              .setIntervalUs(0));  // No delay between responses, for maximum concurrency.
+        }
+        StreamingOutputCallRequest request = requestBuilder.build();
+
+        // Wait until all client worker threads are poised & ready, then send the request. This way
+        // all clients send their requests at approximately the same time.
+        startBarrier.await();
+        clientStub.streamingOutputCall(request, new SignalingResponseObserver(responsesDoneSignal));
+      } catch (Exception e) {
+        throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e);
+      }
+    }
+
+    private final CyclicBarrier startBarrier;
+    private final CountDownLatch responsesDoneSignal;
+  }
+
+  private static final int NUM_SERVER_THREADS = 10;
+  private static final int NUM_CONCURRENT_REQUESTS = 100;
+  private static final int NUM_RESPONSES_PER_REQUEST = 100;
+
+  private Server server;
+  private ManagedChannel clientChannel;
+  private TestServiceGrpc.TestServiceStub clientStub;
+  private ScheduledExecutorService serverExecutor;
+  private ExecutorService clientExecutor;
+
+  @Before
+  public void setUp() throws Exception {
+    serverExecutor = Executors.newScheduledThreadPool(NUM_SERVER_THREADS);
+    clientExecutor = Executors.newFixedThreadPool(NUM_CONCURRENT_REQUESTS);
+
+    server = newServer();
+
+    // Create the client. Keep a reference to its channel so we can shut it down during tearDown().
+    clientChannel = newClientChannel();
+    clientStub = TestServiceGrpc.newStub(clientChannel);
+  }
+
+  @After
+  public void tearDown() {
+    if (server != null) {
+      server.shutdown();
+    }
+    if (clientChannel != null) {
+      clientChannel.shutdown();
+    }
+
+    MoreExecutors.shutdownAndAwaitTermination(serverExecutor, 5, TimeUnit.SECONDS);
+    MoreExecutors.shutdownAndAwaitTermination(clientExecutor, 5, TimeUnit.SECONDS);
+  }
+
+  /**
+   * Tests that gRPC can handle concurrent server-streaming RPCs.
+   */
+  @Test
+  public void serverStreamingTest() throws Exception {
+    CyclicBarrier startBarrier = new CyclicBarrier(NUM_CONCURRENT_REQUESTS);
+    CountDownLatch responsesDoneSignal = new CountDownLatch(NUM_CONCURRENT_REQUESTS);
+
+    for (int i = 0; i < NUM_CONCURRENT_REQUESTS; i++) {
+      clientExecutor.execute(new ClientWorker(startBarrier, responsesDoneSignal));
+    }
+
+    // Wait until the clients all receive their complete RPC response streams.
+    responsesDoneSignal.await();
+  }
+
+  /**
+   * Creates and starts a new {@link TestServiceImpl} server.
+   */
+  private Server newServer() throws CertificateException, IOException {
+    File serverCertChainFile = TestUtils.loadCert("server1.pem");
+    File serverPrivateKeyFile = TestUtils.loadCert("server1.key");
+    X509Certificate[] serverTrustedCaCerts = {
+      TestUtils.loadX509Cert("ca.pem")
+    };
+
+    SslContext sslContext =
+        GrpcSslContexts.forServer(serverCertChainFile, serverPrivateKeyFile)
+                       .trustManager(serverTrustedCaCerts)
+                       .clientAuth(ClientAuth.REQUIRE)
+                       .build();
+
+    return NettyServerBuilder.forPort(0)
+        .sslContext(sslContext)
+        .addService(new TestServiceImpl(serverExecutor))
+        .build()
+        .start();
+  }
+
+  private ManagedChannel newClientChannel() throws CertificateException, IOException {
+    File clientCertChainFile = TestUtils.loadCert("client.pem");
+    File clientPrivateKeyFile = TestUtils.loadCert("client.key");
+    X509Certificate[] clientTrustedCaCerts = {
+      TestUtils.loadX509Cert("ca.pem")
+    };
+
+    SslContext sslContext =
+        GrpcSslContexts.forClient()
+                       .keyManager(clientCertChainFile, clientPrivateKeyFile)
+                       .trustManager(clientTrustedCaCerts)
+                       .build();
+
+    return NettyChannelBuilder.forAddress("localhost", server.getPort())
+        .overrideAuthority(TestUtils.TEST_SERVER_HOST)
+        .negotiationType(NegotiationType.TLS)
+        .sslContext(sslContext)
+        .build();
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/Http2NettyLocalChannelTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/Http2NettyLocalChannelTest.java
new file mode 100644
index 0000000..95f240d
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/Http2NettyLocalChannelTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import io.grpc.ManagedChannel;
+import io.grpc.internal.AbstractServerImplBuilder;
+import io.grpc.netty.NegotiationType;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.netty.NettyServerBuilder;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.channel.local.LocalServerChannel;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Run transport tests over the Netty in-process channel.
+ */
+@RunWith(JUnit4.class)
+public class Http2NettyLocalChannelTest extends AbstractInteropTest {
+
+  @Override
+  protected AbstractServerImplBuilder<?> getServerBuilder() {
+    return NettyServerBuilder
+        .forAddress(new LocalAddress("in-process-1"))
+        .flowControlWindow(65 * 1024)
+        .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE)
+        .channelType(LocalServerChannel.class);
+  }
+
+  @Override
+  protected ManagedChannel createChannel() {
+    NettyChannelBuilder builder = NettyChannelBuilder
+        .forAddress(new LocalAddress("in-process-1"))
+        .negotiationType(NegotiationType.PLAINTEXT)
+        .channelType(LocalChannel.class)
+        .flowControlWindow(65 * 1024)
+        .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE);
+    io.grpc.internal.TestingAccessor.setStatsImplementation(
+        builder, createClientCensusStatsModule());
+    return builder.build();
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/Http2NettyTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/Http2NettyTest.java
new file mode 100644
index 0000000..57b65ea
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/Http2NettyTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import io.grpc.ManagedChannel;
+import io.grpc.internal.AbstractServerImplBuilder;
+import io.grpc.internal.testing.TestUtils;
+import io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.netty.NettyServerBuilder;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SupportedCipherSuiteFilter;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Integration tests for GRPC over HTTP2 using the Netty framework.
+ */
+@RunWith(JUnit4.class)
+public class Http2NettyTest extends AbstractInteropTest {
+
+  @Override
+  protected AbstractServerImplBuilder<?> getServerBuilder() {
+    // Starts the server with HTTPS.
+    try {
+      return NettyServerBuilder.forPort(0)
+          .flowControlWindow(65 * 1024)
+          .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE)
+          .sslContext(GrpcSslContexts
+              .forServer(TestUtils.loadCert("server1.pem"), TestUtils.loadCert("server1.key"))
+              .clientAuth(ClientAuth.REQUIRE)
+              .trustManager(TestUtils.loadCert("ca.pem"))
+              .ciphers(TestUtils.preferredTestCiphers(), SupportedCipherSuiteFilter.INSTANCE)
+              .build());
+    } catch (IOException ex) {
+      throw new RuntimeException(ex);
+    }
+  }
+
+  @Override
+  protected ManagedChannel createChannel() {
+    try {
+      NettyChannelBuilder builder = NettyChannelBuilder
+          .forAddress(TestUtils.testServerAddress(getPort()))
+          .flowControlWindow(65 * 1024)
+          .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE)
+          .sslContext(GrpcSslContexts
+              .forClient()
+              .keyManager(TestUtils.loadCert("client.pem"), TestUtils.loadCert("client.key"))
+              .trustManager(TestUtils.loadX509Cert("ca.pem"))
+              .ciphers(TestUtils.preferredTestCiphers(), SupportedCipherSuiteFilter.INSTANCE)
+              .build());
+      io.grpc.internal.TestingAccessor.setStatsImplementation(
+          builder, createClientCensusStatsModule());
+      return builder.build();
+    } catch (Exception ex) {
+      throw new RuntimeException(ex);
+    }
+  }
+
+  @Test
+  public void remoteAddr() throws Exception {
+    InetSocketAddress isa = (InetSocketAddress) obtainRemoteClientAddr();
+    assertEquals(InetAddress.getLoopbackAddress(), isa.getAddress());
+    // It should not be the same as the server
+    assertNotEquals(getPort(), isa.getPort());
+  }
+
+  @Test
+  public void tlsInfo() {
+    assertX500SubjectDn("CN=testclient, O=Internet Widgits Pty Ltd, ST=Some-State, C=AU");
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/Http2OkHttpTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/Http2OkHttpTest.java
new file mode 100644
index 0000000..0ba5e28
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/Http2OkHttpTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Throwables;
+import com.squareup.okhttp.ConnectionSpec;
+import io.grpc.ManagedChannel;
+import io.grpc.internal.AbstractServerImplBuilder;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.testing.StreamRecorder;
+import io.grpc.internal.testing.TestUtils;
+import io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.NettyServerBuilder;
+import io.grpc.okhttp.OkHttpChannelBuilder;
+import io.grpc.okhttp.internal.Platform;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.integration.EmptyProtos.Empty;
+import io.netty.handler.ssl.OpenSsl;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import io.netty.handler.ssl.SupportedCipherSuiteFilter;
+import java.io.IOException;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Integration tests for GRPC over Http2 using the OkHttp framework.
+ */
+@RunWith(JUnit4.class)
+public class Http2OkHttpTest extends AbstractInteropTest {
+
+  private static final String BAD_HOSTNAME = "I.am.a.bad.hostname";
+
+  @BeforeClass
+  public static void loadConscrypt() throws Exception {
+    // Load conscrypt if it is available. Either Conscrypt or Jetty ALPN needs to be available for
+    // OkHttp to negotiate.
+    TestUtils.installConscryptIfAvailable();
+  }
+
+  @Override
+  protected AbstractServerImplBuilder<?> getServerBuilder() {
+    // Starts the server with HTTPS.
+    try {
+      SslProvider sslProvider = SslContext.defaultServerProvider();
+      if (sslProvider == SslProvider.OPENSSL && !OpenSsl.isAlpnSupported()) {
+        // OkHttp only supports Jetty ALPN on OpenJDK. So if OpenSSL doesn't support ALPN, then we
+        // are forced to use Jetty ALPN for Netty instead of OpenSSL.
+        sslProvider = SslProvider.JDK;
+      }
+      SslContextBuilder contextBuilder = SslContextBuilder
+          .forServer(TestUtils.loadCert("server1.pem"), TestUtils.loadCert("server1.key"));
+      GrpcSslContexts.configure(contextBuilder, sslProvider);
+      contextBuilder.ciphers(TestUtils.preferredTestCiphers(), SupportedCipherSuiteFilter.INSTANCE);
+      return NettyServerBuilder.forPort(0)
+          .flowControlWindow(65 * 1024)
+          .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE)
+          .sslContext(contextBuilder.build());
+    } catch (IOException ex) {
+      throw new RuntimeException(ex);
+    }
+  }
+
+  @Override
+  protected ManagedChannel createChannel() {
+    return createChannelBuilder().build();
+  }
+
+  private OkHttpChannelBuilder createChannelBuilder() {
+    OkHttpChannelBuilder builder = OkHttpChannelBuilder.forAddress("localhost", getPort())
+        .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE)
+        .connectionSpec(new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
+            .cipherSuites(TestUtils.preferredTestCiphers().toArray(new String[0]))
+            .build())
+        .overrideAuthority(GrpcUtil.authorityFromHostAndPort(
+            TestUtils.TEST_SERVER_HOST, getPort()));
+    io.grpc.internal.TestingAccessor.setStatsImplementation(
+        builder, createClientCensusStatsModule());
+    try {
+      builder.sslSocketFactory(TestUtils.newSslSocketFactoryForCa(Platform.get().getProvider(),
+          TestUtils.loadCert("ca.pem")));
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+    return builder;
+  }
+
+  @Test
+  public void receivedDataForFinishedStream() throws Exception {
+    Messages.ResponseParameters.Builder responseParameters =
+        Messages.ResponseParameters.newBuilder()
+        .setSize(1);
+    Messages.StreamingOutputCallRequest.Builder requestBuilder =
+        Messages.StreamingOutputCallRequest.newBuilder()
+            .setResponseType(Messages.PayloadType.COMPRESSABLE);
+    for (int i = 0; i < 1000; i++) {
+      requestBuilder.addResponseParameters(responseParameters);
+    }
+
+    StreamRecorder<Messages.StreamingOutputCallResponse> recorder = StreamRecorder.create();
+    StreamObserver<Messages.StreamingOutputCallRequest> requestStream =
+        asyncStub.fullDuplexCall(recorder);
+    Messages.StreamingOutputCallRequest request = requestBuilder.build();
+    requestStream.onNext(request);
+    recorder.firstValue().get();
+    requestStream.onError(new Exception("failed"));
+
+    recorder.awaitCompletion();
+
+    assertEquals(EMPTY, blockingStub.emptyCall(EMPTY));
+  }
+
+  @Test
+  public void wrongHostNameFailHostnameVerification() throws Exception {
+    ManagedChannel channel = createChannelBuilder()
+        .overrideAuthority(GrpcUtil.authorityFromHostAndPort(
+            BAD_HOSTNAME, getPort()))
+        .build();
+    TestServiceGrpc.TestServiceBlockingStub blockingStub =
+        TestServiceGrpc.newBlockingStub(channel);
+
+    Throwable actualThrown = null;
+    try {
+      blockingStub.emptyCall(Empty.getDefaultInstance());
+    } catch (Throwable t) {
+      actualThrown = t;
+    }
+    assertNotNull("The rpc should have been failed due to hostname verification", actualThrown);
+    Throwable cause = Throwables.getRootCause(actualThrown);
+    assertTrue(
+        "Failed by unexpected exception: " + cause, cause instanceof SSLPeerUnverifiedException);
+    channel.shutdown();
+  }
+
+  @Test
+  public void hostnameVerifierWithBadHostname() throws Exception {
+    ManagedChannel channel = createChannelBuilder()
+        .overrideAuthority(GrpcUtil.authorityFromHostAndPort(
+            BAD_HOSTNAME, getPort()))
+        .hostnameVerifier(new HostnameVerifier() {
+          @Override
+          public boolean verify(String hostname, SSLSession session) {
+            return true;
+          }
+        })
+        .build();
+    TestServiceGrpc.TestServiceBlockingStub blockingStub =
+        TestServiceGrpc.newBlockingStub(channel);
+
+    blockingStub.emptyCall(Empty.getDefaultInstance());
+
+    channel.shutdown();
+  }
+
+  @Test
+  public void hostnameVerifierWithCorrectHostname() throws Exception {
+    ManagedChannel channel = createChannelBuilder()
+        .overrideAuthority(GrpcUtil.authorityFromHostAndPort(
+            TestUtils.TEST_SERVER_HOST, getPort()))
+        .hostnameVerifier(new HostnameVerifier() {
+          @Override
+          public boolean verify(String hostname, SSLSession session) {
+            return false;
+          }
+        })
+        .build();
+    TestServiceGrpc.TestServiceBlockingStub blockingStub =
+        TestServiceGrpc.newBlockingStub(channel);
+
+    Throwable actualThrown = null;
+    try {
+      blockingStub.emptyCall(Empty.getDefaultInstance());
+    } catch (Throwable t) {
+      actualThrown = t;
+    }
+    assertNotNull("The rpc should have been failed due to hostname verification", actualThrown);
+    Throwable cause = Throwables.getRootCause(actualThrown);
+    assertTrue(
+        "Failed by unexpected exception: " + cause, cause instanceof SSLPeerUnverifiedException);
+    channel.shutdown();
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/InProcessTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/InProcessTest.java
new file mode 100644
index 0000000..32c753b
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/InProcessTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import io.grpc.ManagedChannel;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.internal.AbstractServerImplBuilder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link io.grpc.inprocess}. */
+@RunWith(JUnit4.class)
+public class InProcessTest extends AbstractInteropTest {
+
+  private static final String SERVER_NAME = "test";
+
+  @Override
+  protected AbstractServerImplBuilder<?> getServerBuilder() {
+    // Starts the in-process server.
+    return InProcessServerBuilder.forName(SERVER_NAME);
+  }
+
+  @Override
+  protected ManagedChannel createChannel() {
+    InProcessChannelBuilder builder = InProcessChannelBuilder.forName(SERVER_NAME);
+    io.grpc.internal.TestingAccessor.setStatsImplementation(
+        builder, createClientCensusStatsModule());
+    return builder.build();
+  }
+
+  @Override
+  protected boolean metricsExpected() {
+    // TODO(zhangkun83): InProcessTransport by-passes framer and deframer, thus message sizes are
+    // not counted. (https://github.com/grpc/grpc-java/issues/2284)
+    return false;
+  }
+
+  @Override
+  public void maxInboundSize_tooBig() {
+    // noop, not enforced.
+  }
+
+  @Override
+  public void maxOutboundSize_tooBig() {
+    // noop, not enforced.
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/MoreInProcessTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/MoreInProcessTest.java
new file mode 100644
index 0000000..a1c0185
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/MoreInProcessTest.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.integration.Messages.StreamingInputCallRequest;
+import io.grpc.testing.integration.Messages.StreamingInputCallResponse;
+import io.grpc.testing.integration.TestServiceGrpc.TestServiceImplBase;
+import io.grpc.util.MutableHandlerRegistry;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link io.grpc.inprocess}.
+ * Test more corner usecases, client not playing by the rules, server not playing by the rules, etc.
+ */
+@RunWith(JUnit4.class)
+public class MoreInProcessTest {
+  private static final String UNIQUE_SERVER_NAME =
+      "in-process server for " + MoreInProcessTest.class;
+  @Rule
+  public final Timeout globalTimeout = new Timeout(1, TimeUnit.SECONDS);
+  // use a mutable service registry for later registering the service impl for each test case.
+  private final MutableHandlerRegistry serviceRegistry = new MutableHandlerRegistry();
+  private final Server inProcessServer = InProcessServerBuilder.forName(UNIQUE_SERVER_NAME)
+      .fallbackHandlerRegistry(serviceRegistry).directExecutor().build();
+  private final ManagedChannel inProcessChannel =
+      InProcessChannelBuilder.forName(UNIQUE_SERVER_NAME).directExecutor().build();
+
+  @Before
+  public void setUp() throws Exception {
+    inProcessServer.start();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    inProcessChannel.shutdown();
+    inProcessServer.shutdown();
+    assertTrue(inProcessChannel.awaitTermination(900, TimeUnit.MILLISECONDS));
+    assertTrue(inProcessServer.awaitTermination(900, TimeUnit.MILLISECONDS));
+  }
+
+  @Test
+  public void asyncClientStreaming_serverResponsePriorToRequest() throws Exception {
+    // implement a service
+    final StreamingInputCallResponse fakeResponse =
+        StreamingInputCallResponse.newBuilder().setAggregatedPayloadSize(100).build();
+    TestServiceImplBase clientStreamingImpl = new TestServiceImplBase() {
+      @Override
+      public StreamObserver<StreamingInputCallRequest> streamingInputCall(
+          StreamObserver<StreamingInputCallResponse> responseObserver) {
+        // send response directly
+        responseObserver.onNext(fakeResponse);
+        responseObserver.onCompleted();
+        return new StreamObserver<StreamingInputCallRequest>() {
+          @Override
+          public void onNext(StreamingInputCallRequest value) {
+          }
+
+          @Override
+          public void onError(Throwable t) {
+          }
+
+          @Override
+          public void onCompleted() {
+          }
+        };
+      }
+    };
+    serviceRegistry.addService(clientStreamingImpl);
+    // implement a client
+    final CountDownLatch finishLatch = new CountDownLatch(1);
+    final AtomicReference<StreamingInputCallResponse> responseRef =
+        new AtomicReference<StreamingInputCallResponse>();
+    final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
+    StreamObserver<StreamingInputCallResponse> responseObserver =
+        new StreamObserver<StreamingInputCallResponse>() {
+          @Override
+          public void onNext(StreamingInputCallResponse response) {
+            responseRef.set(response);
+          }
+
+          @Override
+          public void onError(Throwable t) {
+            throwableRef.set(t);
+            finishLatch.countDown();
+          }
+
+          @Override
+          public void onCompleted() {
+            finishLatch.countDown();
+          }
+        };
+
+    // make a gRPC call
+    TestServiceGrpc.newStub(inProcessChannel).streamingInputCall(responseObserver);
+
+    assertTrue(finishLatch.await(900, TimeUnit.MILLISECONDS));
+    assertEquals(fakeResponse, responseRef.get());
+    assertNull(throwableRef.get());
+  }
+
+  @Test
+  public void asyncClientStreaming_serverErrorPriorToRequest() throws Exception {
+    // implement a service
+    final Status fakeError = Status.INVALID_ARGUMENT;
+    TestServiceImplBase clientStreamingImpl = new TestServiceImplBase() {
+      @Override
+      public StreamObserver<StreamingInputCallRequest> streamingInputCall(
+          StreamObserver<StreamingInputCallResponse> responseObserver) {
+        // send error directly
+        responseObserver.onError(new StatusRuntimeException(fakeError));
+        responseObserver.onCompleted();
+        return new StreamObserver<StreamingInputCallRequest>() {
+          @Override
+          public void onNext(StreamingInputCallRequest value) {
+          }
+
+          @Override
+          public void onError(Throwable t) {
+          }
+
+          @Override
+          public void onCompleted() {
+          }
+        };
+      }
+    };
+    serviceRegistry.addService(clientStreamingImpl);
+    // implement a client
+    final CountDownLatch finishLatch = new CountDownLatch(1);
+    final AtomicReference<StreamingInputCallResponse> responseRef =
+        new AtomicReference<StreamingInputCallResponse>();
+    final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
+    StreamObserver<StreamingInputCallResponse> responseObserver =
+        new StreamObserver<StreamingInputCallResponse>() {
+          @Override
+          public void onNext(StreamingInputCallResponse response) {
+            responseRef.set(response);
+          }
+
+          @Override
+          public void onError(Throwable t) {
+            throwableRef.set(t);
+            finishLatch.countDown();
+          }
+
+          @Override
+          public void onCompleted() {
+            finishLatch.countDown();
+          }
+        };
+
+    // make a gRPC call
+    TestServiceGrpc.newStub(inProcessChannel).streamingInputCall(responseObserver);
+
+    assertTrue(finishLatch.await(900, TimeUnit.MILLISECONDS));
+    assertEquals(fakeError.getCode(), Status.fromThrowable(throwableRef.get()).getCode());
+    assertNull(responseRef.get());
+  }
+
+  @Test
+  public void asyncClientStreaming_erroneousServiceImpl() throws Exception {
+    // implement a service
+    TestServiceImplBase clientStreamingImpl = new TestServiceImplBase() {
+      @Override
+      public StreamObserver<StreamingInputCallRequest> streamingInputCall(
+          StreamObserver<StreamingInputCallResponse> responseObserver) {
+        StreamObserver<StreamingInputCallRequest> requestObserver =
+            new StreamObserver<StreamingInputCallRequest>() {
+              @Override
+              public void onNext(StreamingInputCallRequest value) {
+                throw new RuntimeException(
+                    "unexpected error due to careless implementation of serviceImpl");
+              }
+
+              @Override
+              public void onError(Throwable t) {
+              }
+
+              @Override
+              public void onCompleted() {
+              }
+            };
+        return requestObserver;
+      }
+    };
+    serviceRegistry.addService(clientStreamingImpl);
+    // implement a client
+    final CountDownLatch finishLatch = new CountDownLatch(1);
+    final AtomicReference<StreamingInputCallResponse> responseRef =
+        new AtomicReference<StreamingInputCallResponse>();
+    final AtomicReference<Throwable> throwableRef = new AtomicReference<Throwable>();
+    StreamObserver<StreamingInputCallResponse> responseObserver =
+        new StreamObserver<StreamingInputCallResponse>() {
+          @Override
+          public void onNext(StreamingInputCallResponse response) {
+            responseRef.set(response);
+          }
+
+          @Override
+          public void onError(Throwable t) {
+            throwableRef.set(t);
+            finishLatch.countDown();
+          }
+
+          @Override
+          public void onCompleted() {
+            finishLatch.countDown();
+          }
+        };
+
+    // make a gRPC call
+    TestServiceGrpc.newStub(inProcessChannel).streamingInputCall(responseObserver)
+        .onNext(StreamingInputCallRequest.getDefaultInstance());
+
+    assertTrue(finishLatch.await(900, TimeUnit.MILLISECONDS));
+    assertEquals(Status.UNKNOWN, Status.fromThrowable(throwableRef.get()));
+    assertNull(responseRef.get());
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/NettyFlowControlTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/NettyFlowControlTest.java
new file mode 100644
index 0000000..c0d35b0
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/NettyFlowControlTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableList;
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.ServerInterceptor;
+import io.grpc.ServerInterceptors;
+import io.grpc.netty.InternalHandlerSettings;
+import io.grpc.netty.NegotiationType;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.netty.NettyServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.integration.Messages.ResponseParameters;
+import io.grpc.testing.integration.Messages.StreamingOutputCallRequest;
+import io.grpc.testing.integration.Messages.StreamingOutputCallResponse;
+import io.netty.util.concurrent.DefaultThreadFactory;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class NettyFlowControlTest {
+
+  // in bytes
+  private static final int LOW_BAND = 2 * 1024 * 1024;
+  private static final int HIGH_BAND = 30 * 1024 * 1024;
+
+  // in milliseconds
+  private static final int MED_LAT = 10;
+
+  // in bytes
+  private static final int TINY_WINDOW = 1;
+  private static final int REGULAR_WINDOW = 64 * 1024;
+  private static final int MAX_WINDOW = 8 * 1024 * 1024;
+
+  private static ManagedChannel channel;
+  private static Server server;
+  private static TrafficControlProxy proxy;
+
+  private int proxyPort;
+  private int serverPort;
+
+  private static final ThreadPoolExecutor executor =
+      new ThreadPoolExecutor(1, 10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
+          new DefaultThreadFactory("flowcontrol-test-pool", true));
+
+  @BeforeClass
+  public static void setUp() {
+    InternalHandlerSettings.enable(true);
+    InternalHandlerSettings.autoWindowOn(true);
+  }
+
+  @AfterClass
+  public static void shutDownTests() {
+    executor.shutdown();
+  }
+
+  @Before
+  public void initTest() {
+    startServer(REGULAR_WINDOW);
+    serverPort = server.getPort();
+  }
+
+  @After
+  public void endTest() throws IOException {
+    proxy.shutDown();
+    server.shutdown();
+  }
+
+  @Test
+  public void largeBdp() throws InterruptedException, IOException {
+    proxy = new TrafficControlProxy(serverPort, HIGH_BAND, MED_LAT, TimeUnit.MILLISECONDS);
+    proxy.start();
+    proxyPort = proxy.getPort();
+    resetConnection(REGULAR_WINDOW);
+    doTest(HIGH_BAND, MED_LAT);
+  }
+
+  @Test
+  public void smallBdp() throws InterruptedException, IOException {
+    proxy = new TrafficControlProxy(serverPort, LOW_BAND, MED_LAT, TimeUnit.MILLISECONDS);
+    proxy.start();
+    proxyPort = proxy.getPort();
+    resetConnection(REGULAR_WINDOW);
+    doTest(LOW_BAND, MED_LAT);
+  }
+
+  @Test
+  public void verySmallWindowMakesProgress() throws InterruptedException, IOException {
+    proxy = new TrafficControlProxy(serverPort, HIGH_BAND, MED_LAT, TimeUnit.MILLISECONDS);
+    proxy.start();
+    proxyPort = proxy.getPort();
+    resetConnection(TINY_WINDOW);
+    doTest(HIGH_BAND, MED_LAT);
+  }
+
+  /**
+   * Main testing method. Streams 2 MB of data from a server and records the final window and
+   * average bandwidth usage.
+   */
+  private void doTest(int bandwidth, int latency) throws InterruptedException {
+
+    int streamSize = 1 * 1024 * 1024;
+    long expectedWindow = (latency * (bandwidth / TimeUnit.SECONDS.toMillis(1)));
+
+    TestServiceGrpc.TestServiceStub stub = TestServiceGrpc.newStub(channel);
+    StreamingOutputCallRequest.Builder builder = StreamingOutputCallRequest.newBuilder()
+        .addResponseParameters(ResponseParameters.newBuilder().setSize(streamSize / 16))
+        .addResponseParameters(ResponseParameters.newBuilder().setSize(streamSize / 16))
+        .addResponseParameters(ResponseParameters.newBuilder().setSize(streamSize / 8))
+        .addResponseParameters(ResponseParameters.newBuilder().setSize(streamSize / 4))
+        .addResponseParameters(ResponseParameters.newBuilder().setSize(streamSize / 2));
+    StreamingOutputCallRequest request = builder.build();
+
+    TestStreamObserver observer = new TestStreamObserver(expectedWindow);
+    stub.streamingOutputCall(request, observer);
+    int lastWindow = observer.waitFor();
+
+    // deal with cases that either don't cause a window update or hit max window
+    expectedWindow = Math.min(MAX_WINDOW, (Math.max(expectedWindow, REGULAR_WINDOW)));
+
+    // Range looks large, but this allows for only one extra/missed window update
+    // (one extra update causes a 2x difference and one missed update causes a .5x difference)
+    assertTrue("Window was " + lastWindow + " expecting " + expectedWindow,
+        lastWindow < 2 * expectedWindow);
+    assertTrue("Window was " + lastWindow + " expecting " + expectedWindow,
+        expectedWindow < 2 * lastWindow);
+  }
+
+  /**
+   * Resets client/server and their flow control windows.
+   */
+  private void resetConnection(int clientFlowControlWindow)
+      throws InterruptedException {
+    if (channel != null) {
+      if (!channel.isShutdown()) {
+        channel.shutdown();
+        channel.awaitTermination(100, TimeUnit.MILLISECONDS);
+      }
+    }
+    channel = NettyChannelBuilder.forAddress(new InetSocketAddress("localhost", proxyPort))
+        .flowControlWindow(clientFlowControlWindow)
+        .negotiationType(NegotiationType.PLAINTEXT)
+        .build();
+  }
+
+  private void startServer(int serverFlowControlWindow) {
+    ServerBuilder<?> builder =
+        NettyServerBuilder.forAddress(new InetSocketAddress("localhost", 0))
+        .flowControlWindow(serverFlowControlWindow);
+    builder.addService(ServerInterceptors.intercept(
+        new TestServiceImpl(Executors.newScheduledThreadPool(2)),
+        ImmutableList.<ServerInterceptor>of()));
+    try {
+      server = builder.build().start();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Simple stream observer to measure elapsed time of the call.
+   */
+  private static class TestStreamObserver implements StreamObserver<StreamingOutputCallResponse> {
+
+    long startRequestNanos;
+    long endRequestNanos;
+    private final CountDownLatch latch = new CountDownLatch(1);
+    long expectedWindow;
+    int lastWindow;
+
+    public TestStreamObserver(long window) {
+      startRequestNanos = System.nanoTime();
+      expectedWindow = window;
+    }
+
+    @Override
+    public void onNext(StreamingOutputCallResponse value) {
+      lastWindow = InternalHandlerSettings.getLatestClientWindow();
+      if (lastWindow >= expectedWindow) {
+        onCompleted();
+      }
+    }
+
+    @Override
+    public void onError(Throwable t) {
+      latch.countDown();
+      throw new RuntimeException(t);
+    }
+
+    @Override
+    public void onCompleted() {
+      latch.countDown();
+    }
+
+    public long getElapsedTime() {
+      return endRequestNanos - startRequestNanos;
+    }
+
+    public int waitFor() throws InterruptedException {
+      latch.await();
+      return lastWindow;
+    }
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/ProxyTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/ProxyTest.java
new file mode 100644
index 0000000..16f0238
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/ProxyTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static org.junit.Assert.assertEquals;
+
+import io.netty.util.concurrent.DefaultThreadFactory;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ProxyTest {
+
+  private static ThreadPoolExecutor executor =
+      new ThreadPoolExecutor(8, 8, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
+          new DefaultThreadFactory("proxy-test-pool", true));
+
+  private TrafficControlProxy proxy;
+  private Socket client;
+  private Server server;
+
+  @AfterClass
+  public static void stopExecutor() {
+    executor.shutdown();
+  }
+
+  @After
+  public void shutdownTest() throws IOException {
+    proxy.shutDown();
+    server.shutDown();
+    client.close();
+  }
+
+  @Test
+  public void smallLatency() throws Exception {
+    server = new Server();
+    int serverPort = server.init();
+    executor.execute(server);
+
+    int latency = (int) TimeUnit.MILLISECONDS.toNanos(50);
+    proxy = new TrafficControlProxy(serverPort, 1024 * 1024, latency, TimeUnit.NANOSECONDS);
+    startProxy(proxy).get();
+    client = new Socket("localhost", proxy.getPort());
+    client.setReuseAddress(true);
+    DataOutputStream clientOut = new DataOutputStream(client.getOutputStream());
+    DataInputStream clientIn = new DataInputStream(client.getInputStream());
+    byte[] message = new byte[1];
+
+    // warmup
+    for (int i = 0; i < 5; i++) {
+      clientOut.write(message, 0, 1);
+    }
+    clientIn.readFully(new byte[5]);
+
+    // test
+    long start = System.nanoTime();
+    clientOut.write(message, 0, 1);
+    clientIn.read(message);
+    long stop = System.nanoTime();
+
+    long rtt = (stop - start);
+    assertEquals(latency, rtt, latency);
+  }
+
+  @Test
+  public void bigLatency() throws Exception {
+    server = new Server();
+    int serverPort = server.init();
+    executor.execute(server);
+
+    int latency = (int) TimeUnit.MILLISECONDS.toNanos(250);
+    proxy = new TrafficControlProxy(serverPort, 1024 * 1024, latency, TimeUnit.NANOSECONDS);
+    startProxy(proxy).get();
+    client = new Socket("localhost", proxy.getPort());
+    DataOutputStream clientOut = new DataOutputStream(client.getOutputStream());
+    DataInputStream clientIn = new DataInputStream(client.getInputStream());
+    byte[] message = new byte[1];
+
+    // warmup
+    for (int i = 0; i < 5; i++) {
+      clientOut.write(message, 0, 1);
+    }
+    clientIn.readFully(new byte[5]);
+
+    // test
+    long start = System.nanoTime();
+    clientOut.write(message, 0, 1);
+    clientIn.read(message);
+    long stop = System.nanoTime();
+
+    long rtt = (stop - start);
+    assertEquals(latency, rtt, latency);
+  }
+
+  @Test
+  public void smallBandwidth() throws Exception {
+    server = new Server();
+    int serverPort = server.init();
+    server.setMode("stream");
+    executor.execute(server);
+    assertEquals("stream", server.mode());
+
+    int bandwidth = 64 * 1024;
+    proxy = new TrafficControlProxy(serverPort, bandwidth, 200, TimeUnit.MILLISECONDS);
+    startProxy(proxy).get();
+    client = new Socket("localhost", proxy.getPort());
+    DataOutputStream clientOut = new DataOutputStream(client.getOutputStream());
+    DataInputStream clientIn = new DataInputStream(client.getInputStream());
+
+    clientOut.write(new byte[1]);
+    clientIn.readFully(new byte[100 * 1024]);
+    long start = System.nanoTime();
+    clientIn.readFully(new byte[5 * bandwidth]);
+    long stop = System.nanoTime();
+
+    long bandUsed = ((5 * bandwidth) / ((stop - start) / TimeUnit.SECONDS.toNanos(1)));
+    assertEquals(bandwidth, bandUsed, .5 * bandwidth);
+  }
+
+  @Test
+  public void largeBandwidth() throws Exception {
+    server = new Server();
+    int serverPort = server.init();
+    server.setMode("stream");
+    executor.execute(server);
+    assertEquals("stream", server.mode());
+    int bandwidth = 10 * 1024 * 1024;
+    proxy = new TrafficControlProxy(serverPort, bandwidth, 200, TimeUnit.MILLISECONDS);
+    startProxy(proxy).get();
+    client = new Socket("localhost", proxy.getPort());
+    DataOutputStream clientOut = new DataOutputStream(client.getOutputStream());
+    DataInputStream clientIn = new DataInputStream(client.getInputStream());
+
+    clientOut.write(new byte[1]);
+    clientIn.readFully(new byte[100 * 1024]);
+    long start = System.nanoTime();
+    clientIn.readFully(new byte[5 * bandwidth]);
+    long stop = System.nanoTime();
+
+    long bandUsed = ((5 * bandwidth) / ((stop - start) / TimeUnit.SECONDS.toNanos(1)));
+    assertEquals(bandwidth, bandUsed, .5 * bandwidth);
+  }
+
+  private Future<?> startProxy(final TrafficControlProxy p) {
+    return executor.submit(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          p.start();
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    });
+  }
+
+  // server with echo and streaming modes
+  private static class Server implements Runnable {
+    private ServerSocket server;
+    private Socket rcv;
+    private boolean shutDown;
+    private String mode = "echo";
+
+    public void setMode(String mode) {
+      this.mode = mode;
+    }
+
+    public String mode() {
+      return mode;
+    }
+
+    /**
+     * Initializes server and returns its listening port.
+     */
+    public int init() throws IOException {
+      server = new ServerSocket(0);
+      return server.getLocalPort();
+    }
+
+    public void shutDown() {
+      try {
+        server.close();
+        rcv.close();
+        shutDown = true;
+      } catch (IOException e) {
+        shutDown = true;
+      }
+    }
+
+    @Override
+    public void run() {
+      try {
+        rcv = server.accept();
+        DataInputStream serverIn = new DataInputStream(rcv.getInputStream());
+        DataOutputStream serverOut = new DataOutputStream(rcv.getOutputStream());
+        byte[] response = new byte[1024];
+        if (mode.equals("echo")) {
+          while (!shutDown) {
+            int readable = serverIn.read(response);
+            serverOut.write(response, 0, readable);
+          }
+        } else if (mode.equals("stream")) {
+          serverIn.read(response);
+          byte[] message = new byte[16 * 1024];
+          while (!shutDown) {
+            serverOut.write(message, 0, message.length);
+          }
+          serverIn.close();
+          serverOut.close();
+          rcv.close();
+        } else {
+          System.out.println("Unknown mode: use 'echo' or 'stream'");
+        }
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/StressTestClientTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/StressTestClientTest.java
new file mode 100644
index 0000000..52ded41
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/StressTestClientTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static java.util.Collections.singletonList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableList;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.testing.integration.Metrics.EmptyMessage;
+import io.grpc.testing.integration.Metrics.GaugeResponse;
+import io.grpc.testing.integration.StressTestClient.TestCaseWeightPair;
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.LockSupport;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link StressTestClient}. */
+@RunWith(JUnit4.class)
+public class StressTestClientTest {
+
+  @Rule
+  public final Timeout globalTimeout = Timeout.seconds(5);
+
+  @Test
+  public void ipv6AddressesShouldBeSupported() {
+    StressTestClient client = new StressTestClient();
+    client.parseArgs(new String[] {"--server_addresses=[0:0:0:0:0:0:0:1]:8080,"
+        + "[1:2:3:4:f:e:a:b]:8083"});
+
+    assertEquals(2, client.addresses().size());
+    assertEquals(new InetSocketAddress("0:0:0:0:0:0:0:1", 8080), client.addresses().get(0));
+    assertEquals(new InetSocketAddress("1:2:3:4:f:e:a:b", 8083), client.addresses().get(1));
+  }
+
+  @Test
+  public void defaults() {
+    StressTestClient client = new StressTestClient();
+    assertEquals(singletonList(new InetSocketAddress("localhost", 8080)), client.addresses());
+    assertTrue(client.testCaseWeightPairs().isEmpty());
+    assertEquals(-1, client.durationSecs());
+    assertEquals(1, client.channelsPerServer());
+    assertEquals(1, client.stubsPerChannel());
+    assertEquals(8081, client.metricsPort());
+  }
+
+  @Test
+  public void allCommandlineSwitchesAreSupported() {
+    StressTestClient client = new StressTestClient();
+    client.parseArgs(new String[] {
+        "--server_addresses=localhost:8080,localhost:8081,localhost:8082",
+        "--test_cases=empty_unary:20,large_unary:50,server_streaming:30",
+        "--test_duration_secs=20",
+        "--num_channels_per_server=10",
+        "--num_stubs_per_channel=5",
+        "--metrics_port=9090",
+        "--server_host_override=foo.test.google.fr",
+        "--use_tls=true",
+        "--use_test_ca=true"
+    });
+
+    List<InetSocketAddress> addresses = Arrays.asList(new InetSocketAddress("localhost", 8080),
+        new InetSocketAddress("localhost", 8081), new InetSocketAddress("localhost", 8082));
+    assertEquals(addresses, client.addresses());
+
+    List<TestCaseWeightPair> testCases = Arrays.asList(
+        new TestCaseWeightPair(TestCases.EMPTY_UNARY, 20),
+        new TestCaseWeightPair(TestCases.LARGE_UNARY, 50),
+        new TestCaseWeightPair(TestCases.SERVER_STREAMING, 30));
+    assertEquals(testCases, client.testCaseWeightPairs());
+
+    assertEquals("foo.test.google.fr", client.serverHostOverride());
+    assertTrue(client.useTls());
+    assertTrue(client.useTestCa());
+    assertEquals(20, client.durationSecs());
+    assertEquals(10, client.channelsPerServer());
+    assertEquals(5, client.stubsPerChannel());
+    assertEquals(9090, client.metricsPort());
+  }
+
+  @Test
+  public void serverHostOverrideShouldBeApplied() {
+    StressTestClient client = new StressTestClient();
+    client.parseArgs(new String[] {
+        "--server_addresses=localhost:8080",
+        "--server_host_override=foo.test.google.fr",
+    });
+
+    assertEquals("foo.test.google.fr", client.addresses().get(0).getHostName());
+  }
+
+  @Test
+  public void gaugesShouldBeExported() throws Exception {
+
+    TestServiceServer server = new TestServiceServer();
+    server.parseArgs(new String[]{"--port=" + 0, "--use_tls=false"});
+    server.start();
+
+    StressTestClient client = new StressTestClient();
+    client.parseArgs(new String[] {"--test_cases=empty_unary:1",
+        "--server_addresses=localhost:" + server.getPort(), "--metrics_port=" + 0,
+        "--num_stubs_per_channel=2"});
+    client.startMetricsService();
+    client.runStressTest();
+
+    // Connect to the metrics service
+    ManagedChannel ch = ManagedChannelBuilder.forAddress("localhost", client.getMetricServerPort())
+        .usePlaintext()
+        .build();
+
+    MetricsServiceGrpc.MetricsServiceBlockingStub stub = MetricsServiceGrpc.newBlockingStub(ch);
+
+    // Wait until gauges have been exported
+    Set<String> gaugeNames = newHashSet("/stress_test/server_0/channel_0/stub_0/qps",
+        "/stress_test/server_0/channel_0/stub_1/qps");
+
+    List<GaugeResponse> allGauges =
+        ImmutableList.copyOf(stub.getAllGauges(EmptyMessage.getDefaultInstance()));
+    while (allGauges.size() < gaugeNames.size()) {
+      LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
+      allGauges = ImmutableList.copyOf(stub.getAllGauges(EmptyMessage.getDefaultInstance()));
+    }
+
+    for (GaugeResponse gauge : allGauges) {
+      String gaugeName = gauge.getName();
+
+      assertTrue("gaugeName: " + gaugeName, gaugeNames.contains(gaugeName));
+      assertTrue("qps: " + gauge.getLongValue(), gauge.getLongValue() > 0);
+      gaugeNames.remove(gauge.getName());
+
+      GaugeResponse gauge1 =
+          stub.getGauge(Metrics.GaugeRequest.newBuilder().setName(gaugeName).build());
+      assertEquals(gaugeName, gauge1.getName());
+      assertTrue("qps: " + gauge1.getLongValue(), gauge1.getLongValue() > 0);
+    }
+
+    assertTrue("gauges: " + gaugeNames, gaugeNames.isEmpty());
+
+    client.shutdown();
+    server.stop();
+  }
+
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/TestCasesTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/TestCasesTest.java
new file mode 100644
index 0000000..cec02dd
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/TestCasesTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static io.grpc.testing.integration.TestCases.fromString;
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link TestCases}.
+ */
+@RunWith(JUnit4.class)
+public class TestCasesTest {
+
+  @Test(expected = IllegalArgumentException.class)
+  public void unknownStringThrowsException() {
+    fromString("does_not_exist_1234");
+  }
+
+  @Test
+  public void testCaseNamesShouldMapToEnums() {
+    // names of testcases as defined in the interop spec
+    String[] testCases = {
+      "empty_unary",
+      "cacheable_unary",
+      "large_unary",
+      "client_compressed_unary",
+      "server_compressed_unary",
+      "client_streaming",
+      "client_compressed_streaming",
+      "server_streaming",
+      "server_compressed_streaming",
+      "ping_pong",
+      "empty_stream",
+      "compute_engine_creds",
+      "service_account_creds",
+      "jwt_token_creds",
+      "oauth2_auth_token",
+      "per_rpc_creds",
+      "custom_metadata",
+      "status_code_and_message",
+      "special_status_message",
+      "unimplemented_method",
+      "unimplemented_service",
+      "cancel_after_begin",
+      "cancel_after_first_response",
+      "timeout_on_sleeping_server"
+    };
+
+    // additional test cases
+    String[] additionalTestCases = {
+      "client_compressed_unary_noprobe",
+      "client_compressed_streaming_noprobe",
+      "very_large_request"
+    };
+
+    assertEquals(testCases.length + additionalTestCases.length, TestCases.values().length);
+
+    Set<TestCases> testCaseSet = new HashSet<TestCases>(testCases.length);
+    for (String testCase : testCases) {
+      testCaseSet.add(TestCases.fromString(testCase));
+    }
+    for (String testCase : additionalTestCases) {
+      testCaseSet.add(TestCases.fromString(testCase));
+    }
+
+    assertEquals(TestCases.values().length, testCaseSet.size());
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/TestServiceClientTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/TestServiceClientTest.java
new file mode 100644
index 0000000..97e8501
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/TestServiceClientTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link TestServiceClient}. */
+@RunWith(JUnit4.class)
+public class TestServiceClientTest {
+
+  @Test
+  public void emptyArgumentListShouldNotThrowException() {
+    TestServiceClient client = new TestServiceClient();
+    client.parseArgs(new String[0]);
+    client.setUp();
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/TrafficControlProxy.java b/interop-testing/src/test/java/io/grpc/testing/integration/TrafficControlProxy.java
new file mode 100644
index 0000000..b94ecbf
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/TrafficControlProxy.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import io.netty.util.concurrent.DefaultThreadFactory;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+public final class TrafficControlProxy {
+
+  private static final int DEFAULT_BAND_BPS = 1024 * 1024;
+  private static final int DEFAULT_DELAY_NANOS = 200 * 1000 * 1000;
+  private static final Logger logger = Logger.getLogger(TrafficControlProxy.class.getName());
+
+  // TODO: make host and ports arguments
+  private final String localhost = "localhost";
+  private final int serverPort;
+  private final int queueLength;
+  private final int chunkSize;
+  private final int bandwidth;
+  private final long latency;
+  private volatile boolean shutDown;
+  private ServerSocket clientAcceptor;
+  private Socket serverSock;
+  private Socket clientSock;
+  private final ThreadPoolExecutor executor =
+      new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
+          new DefaultThreadFactory("proxy-pool", true));
+
+  /**
+   * Returns a new TrafficControlProxy with default bandwidth and latency.
+   */
+  public TrafficControlProxy(int serverPort) {
+    this(serverPort, DEFAULT_BAND_BPS, DEFAULT_DELAY_NANOS, TimeUnit.NANOSECONDS);
+  }
+
+  /**
+   * Returns a new TrafficControlProxy with bandwidth set to targetBPS, and latency set to
+   * targetLatency in latencyUnits.
+   */
+  public TrafficControlProxy(int serverPort, int targetBps, int targetLatency,
+      TimeUnit latencyUnits) {
+    checkArgument(targetBps > 0);
+    checkArgument(targetLatency > 0);
+    this.serverPort = serverPort;
+    bandwidth = targetBps;
+    // divide by 2 because latency is applied in both directions
+    latency = latencyUnits.toNanos(targetLatency) / 2;
+    queueLength = (int) Math.max(bandwidth * latency / TimeUnit.SECONDS.toNanos(1), 1);
+    chunkSize = Math.max(1, queueLength);
+  }
+
+  /**
+   * Starts a new thread that waits for client and server and start reader/writer threads.
+   */
+  public void start() throws IOException {
+    // ClientAcceptor uses a ServerSocket server so that the client can connect to the proxy as it
+    // normally would a server. serverSock then connects the server using a regular Socket as a
+    // client normally would.
+    clientAcceptor = new ServerSocket();
+    clientAcceptor.bind(new InetSocketAddress(localhost, 0));
+    executor.execute(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          clientSock = clientAcceptor.accept();
+          serverSock = new Socket();
+          serverSock.connect(new InetSocketAddress(localhost, serverPort));
+          startWorkers();
+        } catch (IOException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    });
+    logger.info("Started new proxy on port " + clientAcceptor.getLocalPort()
+        + " with Queue Length " + queueLength);
+  }
+
+  public int getPort() {
+    return clientAcceptor.getLocalPort();
+  }
+
+  /** Interrupt all workers and close sockets. */
+  public void shutDown() throws IOException {
+    // TODO: Handle case where a socket fails to close, therefore blocking the others from closing
+    logger.info("Proxy shutting down... ");
+    shutDown = true;
+    executor.shutdown();
+    clientAcceptor.close();
+    clientSock.close();
+    serverSock.close();
+    logger.info("Shutdown Complete");
+  }
+
+  private void startWorkers() throws IOException {
+    DataInputStream clientIn = new DataInputStream(clientSock.getInputStream());
+    DataOutputStream clientOut = new DataOutputStream(serverSock.getOutputStream());
+    DataInputStream serverIn = new DataInputStream(serverSock.getInputStream());
+    DataOutputStream serverOut = new DataOutputStream(clientSock.getOutputStream());
+
+    MessageQueue clientPipe = new MessageQueue(clientIn, clientOut);
+    MessageQueue serverPipe = new MessageQueue(serverIn, serverOut);
+
+    executor.execute(new Reader(clientPipe));
+    executor.execute(new Writer(clientPipe));
+    executor.execute(new Reader(serverPipe));
+    executor.execute(new Writer(serverPipe));
+  }
+
+  private final class Reader implements Runnable {
+
+    private final MessageQueue queue;
+
+    Reader(MessageQueue queue) {
+      this.queue = queue;
+    }
+
+    @Override
+    public void run() {
+      while (!shutDown) {
+        try {
+          queue.readIn();
+        } catch (IOException e) {
+          shutDown = true;
+        } catch (InterruptedException e) {
+          shutDown = true;
+        }
+      }
+    }
+
+  }
+
+  private final class Writer implements Runnable {
+
+    private final MessageQueue queue;
+
+    Writer(MessageQueue queue) {
+      this.queue = queue;
+    }
+
+    @Override
+    public void run() {
+      while (!shutDown) {
+        try {
+          queue.writeOut();
+        } catch (IOException e) {
+          shutDown = true;
+        } catch (InterruptedException e) {
+          shutDown = true;
+        }
+      }
+    }
+  }
+
+  /**
+   * A Delay Queue that counts by number of bytes instead of the number of elements.
+   */
+  private class MessageQueue {
+    DataInputStream inStream;
+    DataOutputStream outStream;
+    int bytesQueued;
+    BlockingQueue<Message> queue = new DelayQueue<Message>();
+
+    MessageQueue(DataInputStream inputStream, DataOutputStream outputStream) {
+      inStream = inputStream;
+      outStream = outputStream;
+    }
+
+    /**
+     * Take a message off the queue and write it to an endpoint. Blocks until a message becomes
+     * available.
+     */
+    void writeOut() throws InterruptedException, IOException {
+      Message next = queue.take();
+      outStream.write(next.message, 0, next.messageLength);
+      incrementBytes(-next.messageLength);
+    }
+
+    /**
+     * Read bytes from an endpoint and add them as a message to the queue. Blocks if the queue is
+     * full.
+     */
+    void readIn() throws InterruptedException, IOException {
+      byte[] request = new byte[getNextChunk()];
+      int readableBytes = inStream.read(request);
+      long sendTime = System.nanoTime() + latency;
+      queue.put(new Message(sendTime, request, readableBytes));
+      incrementBytes(readableBytes);
+    }
+
+    /**
+     * Block until space on the queue becomes available. Returns how many bytes can be read on to
+     * the queue
+     */
+    synchronized int getNextChunk() throws InterruptedException {
+      while (bytesQueued == queueLength) {
+        wait();
+      }
+      return Math.max(0, Math.min(chunkSize, queueLength - bytesQueued));
+    }
+
+    synchronized void incrementBytes(int delta) {
+      bytesQueued += delta;
+      if (bytesQueued < queueLength) {
+        notifyAll();
+      }
+    }
+  }
+
+  private static class Message implements Delayed {
+    long sendTime;
+    byte[] message;
+    int messageLength;
+
+    Message(long sendTime, byte[] message, int messageLength) {
+      this.sendTime = sendTime;
+      this.message = message;
+      this.messageLength = messageLength;
+    }
+
+    @Override
+    public int compareTo(Delayed o) {
+      return ((Long) sendTime).compareTo(((Message) o).sendTime);
+    }
+
+    @Override
+    public long getDelay(TimeUnit unit) {
+      return unit.convert(sendTime - System.nanoTime(), TimeUnit.NANOSECONDS);
+    }
+  }
+}
diff --git a/interop-testing/src/test/java/io/grpc/testing/integration/TransportCompressionTest.java b/interop-testing/src/test/java/io/grpc/testing/integration/TransportCompressionTest.java
new file mode 100644
index 0000000..7c24591
--- /dev/null
+++ b/interop-testing/src/test/java/io/grpc/testing/integration/TransportCompressionTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.integration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.protobuf.BoolValue;
+import com.google.protobuf.ByteString;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.Codec;
+import io.grpc.CompressorRegistry;
+import io.grpc.DecompressorRegistry;
+import io.grpc.ForwardingClientCall;
+import io.grpc.ForwardingClientCallListener;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerCall;
+import io.grpc.ServerCall.Listener;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.internal.AbstractServerImplBuilder;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.netty.NettyChannelBuilder;
+import io.grpc.netty.NettyServerBuilder;
+import io.grpc.testing.integration.Messages.Payload;
+import io.grpc.testing.integration.Messages.PayloadType;
+import io.grpc.testing.integration.Messages.SimpleRequest;
+import io.grpc.testing.integration.Messages.SimpleResponse;
+import java.io.FilterInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests that compression is turned on.
+ */
+@RunWith(JUnit4.class)
+public class TransportCompressionTest extends AbstractInteropTest {
+
+  // Masquerade as identity.
+  private static final Fzip FZIPPER = new Fzip("gzip", new Codec.Gzip());
+  private volatile boolean expectFzip;
+
+  private static final DecompressorRegistry decompressors = DecompressorRegistry.emptyInstance()
+      .with(Codec.Identity.NONE, false)
+      .with(FZIPPER, true);
+  private static final CompressorRegistry compressors = CompressorRegistry.newEmptyInstance();
+
+  @Before
+  public void beforeTests() {
+    FZIPPER.anyRead = false;
+    FZIPPER.anyWritten = false;
+  }
+
+  @BeforeClass
+  public static void registerCompressors() {
+    compressors.register(FZIPPER);
+    compressors.register(Codec.Identity.NONE);
+  }
+
+  @Override
+  protected AbstractServerImplBuilder<?> getServerBuilder() {
+    return NettyServerBuilder.forPort(0)
+        .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE)
+        .compressorRegistry(compressors)
+        .decompressorRegistry(decompressors)
+        .intercept(new ServerInterceptor() {
+            @Override
+            public <ReqT, RespT> Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
+                Metadata headers, ServerCallHandler<ReqT, RespT> next) {
+              Listener<ReqT> listener = next.startCall(call, headers);
+              // TODO(carl-mastrangelo): check that encoding was set.
+              call.setMessageCompression(true);
+              return listener;
+            }
+          });
+  }
+
+  @Test
+  public void compresses() {
+    expectFzip = true;
+    final SimpleRequest request = SimpleRequest.newBuilder()
+        .setResponseSize(314159)
+        .setResponseCompressed(BoolValue.newBuilder().setValue(true))
+        .setResponseType(PayloadType.COMPRESSABLE)
+        .setPayload(Payload.newBuilder()
+            .setBody(ByteString.copyFrom(new byte[271828])))
+        .build();
+    final SimpleResponse goldenResponse = SimpleResponse.newBuilder()
+        .setPayload(Payload.newBuilder()
+            .setType(PayloadType.COMPRESSABLE)
+            .setBody(ByteString.copyFrom(new byte[314159])))
+        .build();
+
+
+    assertEquals(goldenResponse, blockingStub.unaryCall(request));
+    // Assert that compression took place
+    assertTrue(FZIPPER.anyRead);
+    assertTrue(FZIPPER.anyWritten);
+  }
+
+  @Override
+  protected ManagedChannel createChannel() {
+    NettyChannelBuilder builder = NettyChannelBuilder.forAddress("localhost", getPort())
+        .maxInboundMessageSize(AbstractInteropTest.MAX_MESSAGE_SIZE)
+        .decompressorRegistry(decompressors)
+        .compressorRegistry(compressors)
+        .intercept(new ClientInterceptor() {
+          @Override
+          public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+              MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+            final ClientCall<ReqT, RespT> call = next.newCall(method, callOptions);
+            return new ForwardingClientCall<ReqT, RespT>() {
+
+              @Override
+              protected ClientCall<ReqT, RespT> delegate() {
+                return call;
+              }
+
+              @Override
+              public void start(
+                  final ClientCall.Listener<RespT> responseListener, Metadata headers) {
+                ClientCall.Listener<RespT> listener = new ForwardingClientCallListener<RespT>() {
+
+                  @Override
+                  protected io.grpc.ClientCall.Listener<RespT> delegate() {
+                    return responseListener;
+                  }
+
+                  @Override
+                  public void onHeaders(Metadata headers) {
+                    super.onHeaders(headers);
+                    if (expectFzip) {
+                      String encoding = headers.get(GrpcUtil.MESSAGE_ENCODING_KEY);
+                      assertEquals(encoding, FZIPPER.getMessageEncoding());
+                    }
+                  }
+                };
+                super.start(listener, headers);
+                setMessageCompression(true);
+              }
+            };
+          }
+        })
+        .usePlaintext();
+    io.grpc.internal.TestingAccessor.setStatsImplementation(
+        builder, createClientCensusStatsModule());
+    return builder.build();
+  }
+
+  /**
+   * Fzip is a custom compressor.
+   */
+  static class Fzip implements Codec {
+    volatile boolean anyRead;
+    volatile boolean anyWritten;
+    volatile Codec delegate;
+
+    private final String actualName;
+
+    public Fzip(String actualName, Codec delegate) {
+      this.actualName = actualName;
+      this.delegate = delegate;
+    }
+
+    @Override
+    public String getMessageEncoding() {
+      return actualName;
+    }
+
+    @Override
+    public OutputStream compress(OutputStream os) throws IOException {
+      return new FilterOutputStream(delegate.compress(os)) {
+        @Override
+        public void write(int b) throws IOException {
+          super.write(b);
+          anyWritten = true;
+        }
+      };
+    }
+
+    @Override
+    public InputStream decompress(InputStream is) throws IOException {
+      return new FilterInputStream(delegate.decompress(is)) {
+        @Override
+        public int read() throws IOException {
+          int val = super.read();
+          anyRead = true;
+          return val;
+        }
+
+        @Override
+        public int read(byte[] b, int off, int len) throws IOException {
+          int total = super.read(b, off, len);
+          anyRead = true;
+          return total;
+        }
+      };
+    }
+  }
+}
diff --git a/java_grpc_library.bzl b/java_grpc_library.bzl
new file mode 100644
index 0000000..9c271a8
--- /dev/null
+++ b/java_grpc_library.bzl
@@ -0,0 +1,133 @@
+def _path_ignoring_repository(f):
+  if (len(f.owner.workspace_root) == 0):
+    return f.short_path
+  return f.path[f.path.find(f.owner.workspace_root)+len(f.owner.workspace_root)+1:]
+
+def _gensource_impl(ctx):
+  if len(ctx.attr.srcs) > 1:
+    fail("Only one src value supported", "srcs")
+  for s in ctx.attr.srcs:
+    if s.label.package != ctx.label.package:
+      print(("in srcs attribute of {0}: Proto source with label {1} should be in "
+             + "same package as consuming rule").format(ctx.label, s.label))
+  # Use .jar since .srcjar makes protoc think output will be a directory
+  srcdotjar = ctx.new_file(ctx.label.name + "_src.jar")
+
+  srcs = [f for dep in ctx.attr.srcs for f in dep.proto.direct_sources]
+  includes = [f for dep in ctx.attr.srcs for f in dep.proto.transitive_imports]
+
+  flavor = ctx.attr.flavor
+  if flavor == "normal":
+    flavor = ""
+  ctx.action(
+      inputs = [ctx.executable._java_plugin] + srcs + includes,
+      outputs = [srcdotjar],
+      executable = ctx.executable._protoc,
+      arguments = [
+          "--plugin=protoc-gen-grpc-java=" + ctx.executable._java_plugin.path,
+          "--grpc-java_out={0},enable_deprecated={1}:{2}"
+            .format(flavor, str(ctx.attr.enable_deprecated).lower(), srcdotjar.path)]
+          + ["-I{0}={1}".format(_path_ignoring_repository(include), include.path) for include in includes]
+          + [_path_ignoring_repository(src) for src in srcs])
+  ctx.action(
+      command = "cp $1 $2",
+      inputs = [srcdotjar],
+      outputs = [ctx.outputs.srcjar],
+      arguments = [srcdotjar.path, ctx.outputs.srcjar.path])
+
+_gensource = rule(
+    attrs = {
+        "srcs": attr.label_list(
+            mandatory = True,
+            non_empty = True,
+            providers = ["proto"],
+        ),
+        "flavor": attr.string(
+            values = [
+                "normal",
+                "lite",  # Not currently supported
+            ],
+            default = "normal",
+        ),
+        "enable_deprecated": attr.bool(
+            default = False,
+        ),
+        "_protoc": attr.label(
+            default = Label("@com_google_protobuf//:protoc"),
+            executable = True,
+            cfg = "host",
+        ),
+        "_java_plugin": attr.label(
+            default = Label("//compiler:grpc_java_plugin"),
+            executable = True,
+            cfg = "host",
+        ),
+    },
+    outputs = {
+        "srcjar": "%{name}.srcjar",
+    },
+    implementation = _gensource_impl,
+)
+
+def java_grpc_library(name, srcs, deps, flavor=None,
+                      enable_deprecated=None, visibility=None,
+                      **kwargs):
+  """Generates and compiles gRPC Java sources for services defined in a proto
+  file. This rule is compatible with java_proto_library and java_lite_proto_library.
+
+  Do note that this rule only scans through the proto file for RPC services. It
+  does not generate Java classes for proto messages. You will need a separate
+  java_proto_library or java_lite_proto_library rule.
+
+  Args:
+    name: (str) A unique name for this rule. Required.
+    srcs: (list) a single proto_library target that contains the schema of the
+        service. Required.
+    deps: (list) a single java_proto_library target for the proto_library in
+        srcs.  Required.
+    flavor: (str) "normal" (default) for normal proto runtime. "lite"
+        for the lite runtime.
+    visibility: (list) the visibility list
+    **kwargs: Passed through to generated targets
+  """
+  if flavor == None:
+    flavor = "normal"
+
+  if len(deps) > 1:
+    print("Multiple values in 'deps' is deprecated in " + name)
+
+  gensource_name = name + "__do_not_reference__srcjar"
+  _gensource(
+      name = gensource_name,
+      srcs = srcs,
+      flavor = flavor,
+      enable_deprecated = enable_deprecated,
+      visibility = ["//visibility:private"],
+      **kwargs
+  )
+
+  added_deps = [
+      "@io_grpc_grpc_java//core",
+      "@io_grpc_grpc_java//stub",
+      "@io_grpc_grpc_java//stub:javax_annotation",
+      "@com_google_guava_guava//jar",
+  ]
+  if flavor == "normal":
+    added_deps += [
+        "@com_google_protobuf//:protobuf_java",
+        "@io_grpc_grpc_java//protobuf",
+    ]
+  elif flavor == "lite":
+    added_deps += ["@io_grpc_grpc_java//protobuf-lite"]
+  else:
+    fail("Unknown flavor type", "flavor")
+
+  native.java_library(
+      name = name,
+      srcs = [gensource_name],
+      visibility = visibility,
+      deps = [
+          "@com_google_code_findbugs_jsr305//jar",
+      ] + deps + added_deps,
+      **kwargs
+  )
diff --git a/netty/BUILD.bazel b/netty/BUILD.bazel
new file mode 100644
index 0000000..101844b
--- /dev/null
+++ b/netty/BUILD.bazel
@@ -0,0 +1,28 @@
+java_library(
+    name = "netty",
+    srcs = glob([
+        "src/main/java/**/*.java",
+        "third_party/netty/java/**/*.java",
+    ]),
+    resources = glob([
+        "src/main/resources/**",
+    ]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//core",
+        "//core:internal",
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_errorprone_error_prone_annotations//jar",
+        "@com_google_guava_guava//jar",
+        "@io_netty_netty_buffer//jar",
+        "@io_netty_netty_codec//jar",
+        "@io_netty_netty_codec_http//jar",
+        "@io_netty_netty_codec_http2//jar",
+        "@io_netty_netty_codec_socks//jar",
+        "@io_netty_netty_common//jar",
+        "@io_netty_netty_handler//jar",
+        "@io_netty_netty_handler_proxy//jar",
+        "@io_netty_netty_resolver//jar",
+        "@io_netty_netty_transport//jar",
+    ],
+)
diff --git a/netty/build.gradle b/netty/build.gradle
new file mode 100644
index 0000000..82f5085
--- /dev/null
+++ b/netty/build.gradle
@@ -0,0 +1,41 @@
+description = "gRPC: Netty"
+dependencies {
+    compile project(':grpc-core'),
+            libraries.netty,
+            libraries.netty_proxy_handler
+
+    // Tests depend on base class defined by core module.
+    testCompile project(':grpc-core').sourceSets.test.output,
+            project(':grpc-testing'),
+            project(':grpc-testing-proto')
+    testRuntime libraries.netty_tcnative,
+            libraries.conscrypt
+    signature "org.codehaus.mojo.signature:java17:1.0@signature"
+}
+
+[compileJava, compileTestJava].each() {
+    // Netty retuns a lot of futures that we mostly don't care about.
+    it.options.compilerArgs += [
+        "-Xep:FutureReturnValueIgnored:OFF"
+    ]
+}
+
+javadoc {
+    options.links 'http://netty.io/4.1/api/'
+    exclude 'io/grpc/netty/Internal*'
+}
+
+project.sourceSets {
+    main { java { srcDir "${projectDir}/third_party/netty/java" } }
+}
+
+test {
+    // Allow testing Jetty ALPN in TlsTest
+    jvmArgs "-javaagent:" + configurations.alpnagent.asPath
+}
+
+jmh {
+    // Workaround
+    // https://github.com/melix/jmh-gradle-plugin/issues/97#issuecomment-316664026
+    includeTests = true
+}
diff --git a/netty/shaded/build.gradle b/netty/shaded/build.gradle
new file mode 100644
index 0000000..434b7fb
--- /dev/null
+++ b/netty/shaded/build.gradle
@@ -0,0 +1,64 @@
+buildscript {
+    repositories { jcenter() }
+    dependencies { classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4' }
+}
+
+apply plugin: 'com.github.johnrengelman.shadow'
+
+description = "gRPC: Netty Shaded"
+
+sourceSets { testShadow {} }
+
+dependencies {
+    compile project(':grpc-netty')
+    runtime libraries.netty_tcnative
+    testShadowCompile files(shadowJar),
+            configurations.shadow,
+            project(':grpc-testing-proto'),
+            project(':grpc-testing'),
+            libraries.truth
+    shadow project(':grpc-core')
+}
+
+artifacts { // We want uploadArchives to handle the shadowJar; we don't care about
+    // uploadShadow
+    archives shadowJar }
+
+jar {
+    // Must use a different classifier to avoid conflicting with shadowJar
+    classifier = 'original'
+}
+configurations.archives.artifacts.removeAll { it.classifier == "original" }
+
+shadowJar {
+    classifier = null
+    dependencies {
+        include(project(':grpc-netty'))
+        include(dependency('io.netty:'))
+    }
+    relocate 'io.grpc.netty', 'io.grpc.netty.shaded.io.grpc.netty'
+    relocate 'io.netty', 'io.grpc.netty.shaded.io.netty'
+    // We have to be careful with these replacements as they must not match any
+    // string in NativeLibraryLoader, else they cause corruption. Note that
+    // this includes concatenation of string literals and constants.
+    relocate 'META-INF/native/libnetty', 'META-INF/native/libio_grpc_netty_shaded_netty'
+    relocate 'META-INF/native/netty', 'META-INF/native/io_grpc_netty_shaded_netty'
+    mergeServiceFiles()
+}
+
+// This is a hack to have shadow plugin modify the uploadArchives POM's
+// dependencies. If we delete the uploadShadow task, then the plugin will no
+// longer modify the POM. This probably can break horribly with parallel build,
+// but that's broken anyway with install/uploadArchives
+uploadShadow.repositories.addAll(uploadArchives.repositories)
+// And then we use a further hack to share that POM with install
+install.repositories.mavenInstaller.pom = uploadArchives.repositories.mavenDeployer.pom
+
+task testShadow(type: Test) {
+    testClassesDirs = sourceSets.testShadow.output.classesDirs
+    classpath = sourceSets.testShadow.runtimeClasspath
+}
+compileTestShadowJava.options.compilerArgs = compileTestJava.options.compilerArgs
+compileTestShadowJava.options.encoding = compileTestJava.options.encoding
+
+test.dependsOn testShadow
diff --git a/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java b/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java
new file mode 100644
index 0000000..8527ff4
--- /dev/null
+++ b/netty/shaded/src/testShadow/java/io/grpc/netty/shaded/ShadingTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty.shaded;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.internal.testing.TestUtils;
+import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
+import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
+import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
+import io.grpc.netty.shaded.io.netty.handler.ssl.SslContextBuilder;
+import io.grpc.netty.shaded.io.netty.handler.ssl.SslProvider;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.protobuf.SimpleRequest;
+import io.grpc.testing.protobuf.SimpleResponse;
+import io.grpc.testing.protobuf.SimpleServiceGrpc;
+import io.grpc.testing.protobuf.SimpleServiceGrpc.SimpleServiceBlockingStub;
+import io.grpc.testing.protobuf.SimpleServiceGrpc.SimpleServiceImplBase;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Shading}. */
+@RunWith(JUnit4.class)
+public final class ShadingTest {
+  private ManagedChannel channel;
+  private Server server;
+
+  @After
+  public void tearDown() throws Exception {
+    if (channel != null) {
+      channel.shutdownNow();
+      channel.awaitTermination(1, TimeUnit.SECONDS);
+    }
+    if (server != null) {
+      server.shutdownNow();
+      server.awaitTermination(1, TimeUnit.SECONDS);
+    }
+  }
+
+  /** Verify that normal Netty didn't leak into the test runtime. */
+  @Test(expected = ClassNotFoundException.class)
+  public void noNormalNetty() throws Exception {
+    Class.forName("io.grpc.netty.NettyServerBuilder");
+  }
+
+  @Test
+  public void serviceLoaderFindsNetty() throws Exception {
+    assertThat(ServerBuilder.forPort(0)).isInstanceOf(NettyServerBuilder.class);
+    assertThat(ManagedChannelBuilder.forAddress("localhost", 1234))
+        .isInstanceOf(NettyChannelBuilder.class);
+  }
+
+  @Test
+  public void basic() throws Exception {
+    server = ServerBuilder.forPort(0)
+        .addService(new SimpleServiceImpl())
+        .build().start();
+    channel = ManagedChannelBuilder
+        .forAddress("localhost", server.getPort())
+        .usePlaintext()
+        .build();
+    SimpleServiceBlockingStub stub = SimpleServiceGrpc.newBlockingStub(channel);
+    assertThat(SimpleResponse.getDefaultInstance())
+        .isEqualTo(stub.unaryRpc(SimpleRequest.getDefaultInstance()));
+  }
+
+  @Test
+  public void tcnative() throws Exception {
+    server = NettyServerBuilder.forPort(0)
+        .useTransportSecurity(TestUtils.loadCert("server1.pem"), TestUtils.loadCert("server1.key"))
+        .addService(new SimpleServiceImpl())
+        .build().start();
+    channel = NettyChannelBuilder
+        .forAddress("localhost", server.getPort())
+        .sslContext(
+            GrpcSslContexts.configure(SslContextBuilder.forClient(), SslProvider.OPENSSL)
+                .trustManager(TestUtils.loadCert("ca.pem")).build())
+        .overrideAuthority("foo.test.google.fr")
+        .build();
+    SimpleServiceBlockingStub stub = SimpleServiceGrpc.newBlockingStub(channel);
+    assertThat(SimpleResponse.getDefaultInstance())
+        .isEqualTo(stub.unaryRpc(SimpleRequest.getDefaultInstance()));
+  }
+
+  private static class SimpleServiceImpl extends SimpleServiceImplBase {
+    @Override public void unaryRpc(SimpleRequest req, StreamObserver<SimpleResponse> obs) {
+      obs.onNext(SimpleResponse.getDefaultInstance());
+      obs.onCompleted();
+    }
+  }
+}
diff --git a/netty/src/jmh/java/io/grpc/netty/InboundHeadersBenchmark.java b/netty/src/jmh/java/io/grpc/netty/InboundHeadersBenchmark.java
new file mode 100644
index 0000000..4a7515a
--- /dev/null
+++ b/netty/src/jmh/java/io/grpc/netty/InboundHeadersBenchmark.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static io.grpc.netty.Utils.CONTENT_TYPE_HEADER;
+import static io.grpc.netty.Utils.TE_TRAILERS;
+import static io.netty.util.AsciiString.of;
+
+import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2RequestHeaders;
+import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2ResponseHeaders;
+import io.netty.handler.codec.http2.DefaultHttp2Headers;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.util.AsciiString;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.CompilerControl;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+
+/**
+ * Benchmarks for {@link GrpcHttp2RequestHeaders} and {@link GrpcHttp2ResponseHeaders}.
+ */
+@State(Scope.Thread)
+public class InboundHeadersBenchmark {
+
+  private static AsciiString[] requestHeaders;
+  private static AsciiString[] responseHeaders;
+
+  static {
+    setupRequestHeaders();
+    setupResponseHeaders();
+  }
+
+  // Headers taken from the gRPC spec.
+  private static void setupRequestHeaders() {
+    requestHeaders = new AsciiString[18];
+    int i = 0;
+    requestHeaders[i++] = of(":method");
+    requestHeaders[i++] = of("POST");
+    requestHeaders[i++] = of(":scheme");
+    requestHeaders[i++] = of("http");
+    requestHeaders[i++] = of(":path");
+    requestHeaders[i++] = of("/google.pubsub.v2.PublisherService/CreateTopic");
+    requestHeaders[i++] = of(":authority");
+    requestHeaders[i++] = of("pubsub.googleapis.com");
+    requestHeaders[i++] = of("te");
+    requestHeaders[i++] = of("trailers");
+    requestHeaders[i++] = of("grpc-timeout");
+    requestHeaders[i++] = of("1S");
+    requestHeaders[i++] = of("content-type");
+    requestHeaders[i++] = of("application/grpc+proto");
+    requestHeaders[i++] = of("grpc-encoding");
+    requestHeaders[i++] = of("gzip");
+    requestHeaders[i++] = of("authorization");
+    requestHeaders[i] = of("Bearer y235.wef315yfh138vh31hv93hv8h3v");
+  }
+
+  private static void setupResponseHeaders() {
+    responseHeaders = new AsciiString[4];
+    int i = 0;
+    responseHeaders[i++] = of(":status");
+    responseHeaders[i++] = of("200");
+    responseHeaders[i++] = of("grpc-encoding");
+    responseHeaders[i] = of("gzip");
+  }
+
+  /**
+   * Checkstyle.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.AverageTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public void grpcHeaders_serverHandler(Blackhole bh) {
+    serverHandler(bh, new GrpcHttp2RequestHeaders(4));
+  }
+
+  /**
+   * Checkstyle.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.AverageTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public void defaultHeaders_serverHandler(Blackhole bh) {
+    serverHandler(bh, new DefaultHttp2Headers(true, 9));
+  }
+
+  /**
+   *  Checkstyle.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.AverageTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public void grpcHeaders_clientHandler(Blackhole bh) {
+    clientHandler(bh, new GrpcHttp2ResponseHeaders(2));
+  }
+
+  /**
+   * Checkstyle.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.AverageTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public void defaultHeaders_clientHandler(Blackhole bh) {
+    clientHandler(bh, new DefaultHttp2Headers(true, 2));
+  }
+
+  @CompilerControl(CompilerControl.Mode.INLINE)
+  private static void serverHandler(Blackhole bh, Http2Headers headers) {
+    for (int i = 0; i < requestHeaders.length; i += 2) {
+      bh.consume(headers.add(requestHeaders[i], requestHeaders[i + 1]));
+    }
+
+    // Sequence of headers accessed in NettyServerHandler
+    bh.consume(headers.get(TE_TRAILERS));
+    bh.consume(headers.get(CONTENT_TYPE_HEADER));
+    bh.consume(headers.method());
+    bh.consume(headers.get(CONTENT_TYPE_HEADER));
+    bh.consume(headers.path());
+
+    bh.consume(Utils.convertHeaders(headers));
+  }
+
+  @CompilerControl(CompilerControl.Mode.INLINE)
+  private static void clientHandler(Blackhole bh, Http2Headers headers) {
+    // NettyClientHandler does not directly access headers, but convert to Metadata immediately.
+
+    bh.consume(headers.add(responseHeaders[0], responseHeaders[1]));
+    bh.consume(headers.add(responseHeaders[2], responseHeaders[3]));
+
+    bh.consume(Utils.convertHeaders(headers));
+  }
+
+//  /**
+//   * Prints the size of the header objects in bytes. Needs JOL (Java Object Layout) as a
+//   * dependency.
+//   */
+//  public static void main(String... args) {
+//    Http2Headers grpcRequestHeaders = new GrpcHttp2RequestHeaders(4);
+//    Http2Headers defaultRequestHeaders = new DefaultHttp2Headers(true, 9);
+//    for (int i = 0; i < requestHeaders.length; i += 2) {
+//      grpcRequestHeaders.add(requestHeaders[i], requestHeaders[i + 1]);
+//      defaultRequestHeaders.add(requestHeaders[i], requestHeaders[i + 1]);
+//    }
+//    long c = 10L;
+//    int m = ((int) c) / 20;
+//
+//    long grpcRequestHeadersBytes = GraphLayout.parseInstance(grpcRequestHeaders).totalSize();
+//    long defaultRequestHeadersBytes =
+//        GraphLayout.parseInstance(defaultRequestHeaders).totalSize();
+//
+//    System.out.printf("gRPC Request Headers: %d bytes%nNetty Request Headers: %d bytes%n",
+//        grpcRequestHeadersBytes, defaultRequestHeadersBytes);
+//
+//    Http2Headers grpcResponseHeaders = new GrpcHttp2RequestHeaders(4);
+//    Http2Headers defaultResponseHeaders = new DefaultHttp2Headers(true, 9);
+//    for (int i = 0; i < responseHeaders.length; i += 2) {
+//      grpcResponseHeaders.add(responseHeaders[i], responseHeaders[i + 1]);
+//      defaultResponseHeaders.add(responseHeaders[i], responseHeaders[i + 1]);
+//    }
+//
+//    long grpcResponseHeadersBytes = GraphLayout.parseInstance(grpcResponseHeaders).totalSize();
+//    long defaultResponseHeadersBytes =
+//        GraphLayout.parseInstance(defaultResponseHeaders).totalSize();
+//
+//    System.out.printf("gRPC Response Headers: %d bytes%nNetty Response Headers: %d bytes%n",
+//        grpcResponseHeadersBytes, defaultResponseHeadersBytes);
+//  }
+}
diff --git a/netty/src/jmh/java/io/grpc/netty/MethodDescriptorBenchmark.java b/netty/src/jmh/java/io/grpc/netty/MethodDescriptorBenchmark.java
new file mode 100644
index 0000000..9212996
--- /dev/null
+++ b/netty/src/jmh/java/io/grpc/netty/MethodDescriptorBenchmark.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.InternalKnownTransport;
+import io.grpc.InternalMethodDescriptor;
+import io.grpc.MethodDescriptor;
+import io.netty.util.AsciiString;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Benchmark for Method Descriptors.
+ */
+@State(Scope.Benchmark)
+public class MethodDescriptorBenchmark {
+
+  private static final MethodDescriptor.Marshaller<Void> marshaller =
+      new MethodDescriptor.Marshaller<Void>() {
+    @Override
+    public InputStream stream(Void value) {
+      return new ByteArrayInputStream(new byte[]{});
+    }
+
+    @Override
+    public Void parse(InputStream stream) {
+      return null;
+    }
+  };
+
+  MethodDescriptor<Void, Void> method = MethodDescriptor.<Void, Void>newBuilder()
+      .setType(MethodDescriptor.MethodType.UNARY)
+      .setFullMethodName("Service/Method")
+      .setRequestMarshaller(marshaller)
+      .setResponseMarshaller(marshaller)
+      .build();
+
+  InternalMethodDescriptor imd = new InternalMethodDescriptor(InternalKnownTransport.NETTY);
+
+  byte[] directBytes = new AsciiString("/" + method.getFullMethodName()).toByteArray();
+
+  /** Foo bar. */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public AsciiString old() {
+    return new AsciiString("/" + method.getFullMethodName());
+  }
+
+  /** Foo bar. */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public AsciiString transportSpecific() {
+    AsciiString path;
+    if ((path = (AsciiString) imd.geRawMethodName(method)) != null) {
+      path = new AsciiString("/" + method.getFullMethodName());
+      imd.setRawMethodName(method, path);
+    }
+    return path;
+  }
+
+  /** Foo bar. */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public AsciiString direct() {
+    return new AsciiString(directBytes, false);
+  }
+}
+
diff --git a/netty/src/jmh/java/io/grpc/netty/OutboundHeadersBenchmark.java b/netty/src/jmh/java/io/grpc/netty/OutboundHeadersBenchmark.java
new file mode 100644
index 0000000..5bfc739
--- /dev/null
+++ b/netty/src/jmh/java/io/grpc/netty/OutboundHeadersBenchmark.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.Metadata;
+import io.grpc.Metadata.AsciiMarshaller;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.handler.codec.http2.DefaultHttp2HeadersEncoder;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.handler.codec.http2.Http2HeadersEncoder;
+import io.netty.util.AsciiString;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+
+/**
+ * Header encoding benchmark.
+ */
+@State(Scope.Benchmark)
+public class OutboundHeadersBenchmark {
+  @Param({"1", "5", "10", "20"})
+  public int headerCount;
+
+  private final AsciiMarshaller<String> keyMarshaller = new AsciiMarshaller<String>() {
+    @Override
+    public String toAsciiString(String value) {
+      return value;
+    }
+
+    @Override
+    public String parseAsciiString(String serialized) {
+      return serialized;
+    }
+  };
+
+  private final Metadata metadata = new Metadata();
+  private final AsciiString scheme = new AsciiString("https");
+  private final AsciiString defaultPath = new AsciiString("/Service.MethodMethodMethod");
+  private final AsciiString authority = new AsciiString("authority.googleapis.bogus");
+  private final AsciiString userAgent = new AsciiString("grpc-java-netty");
+  private final Http2HeadersEncoder headersEncoder = new DefaultHttp2HeadersEncoder();
+  private final ByteBuf scratchBuffer = UnpooledByteBufAllocator.DEFAULT.buffer(4096);
+
+  @Setup
+  public void setUp() throws Exception {
+    for (int i = 0; i < headerCount; i++) {
+      metadata.put(Metadata.Key.of("key-" + i, keyMarshaller), UUID.randomUUID().toString());
+    }
+  }
+
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public Http2Headers convertClientHeaders() {
+    return Utils.convertClientHeaders(metadata, scheme, defaultPath, authority, Utils.HTTP_METHOD,
+        userAgent);
+  }
+
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public Http2Headers convertServerHeaders() {
+    return Utils.convertServerHeaders(metadata);
+  }
+
+  /**
+   * This will encode the random metadata fields, and repeatedly lookup the default other headers.
+   */
+  @Benchmark
+  @BenchmarkMode(Mode.SampleTime)
+  @OutputTimeUnit(TimeUnit.NANOSECONDS)
+  public ByteBuf encodeClientHeaders() throws Exception {
+    scratchBuffer.clear();
+    Http2Headers headers =
+        Utils.convertClientHeaders(metadata, scheme, defaultPath, authority, Utils.HTTP_METHOD,
+            userAgent);
+    headersEncoder.encodeHeaders(1, headers, scratchBuffer);
+    return scratchBuffer;
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/AbstractHttp2Headers.java b/netty/src/main/java/io/grpc/netty/AbstractHttp2Headers.java
new file mode 100644
index 0000000..1dc3f11
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/AbstractHttp2Headers.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.netty.handler.codec.Headers;
+import io.netty.handler.codec.http2.Http2Headers;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+
+abstract class AbstractHttp2Headers implements Http2Headers {
+
+  @Override
+  public Iterator<CharSequence> valueIterator(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public int size() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean isEmpty() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Set<CharSequence> names() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public CharSequence get(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public CharSequence get(CharSequence name, CharSequence defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public CharSequence getAndRemove(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public CharSequence getAndRemove(CharSequence name, CharSequence defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public List<CharSequence> getAll(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public List<CharSequence> getAllAndRemove(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean getBoolean(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean getBoolean(CharSequence name, boolean defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Byte getByte(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public byte getByte(CharSequence name, byte defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Character getChar(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public char getChar(CharSequence name, char defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Short getShort(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public short getShort(CharSequence name, short defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Integer getInt(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public int getInt(CharSequence name, int defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Long getLong(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public long getLong(CharSequence name, long defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Float getFloat(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public float getFloat(CharSequence name, float defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Double getDouble(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public double getDouble(CharSequence name, double defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Long getTimeMillis(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public long getTimeMillis(CharSequence name, long defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Boolean getBooleanAndRemove(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean getBooleanAndRemove(CharSequence name, boolean defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Byte getByteAndRemove(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public byte getByteAndRemove(CharSequence name, byte defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Character getCharAndRemove(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public char getCharAndRemove(CharSequence name, char defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Short getShortAndRemove(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public short getShortAndRemove(CharSequence name, short defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Integer getIntAndRemove(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public int getIntAndRemove(CharSequence name, int defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Long getLongAndRemove(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public long getLongAndRemove(CharSequence name, long defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Float getFloatAndRemove(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public float getFloatAndRemove(CharSequence name, float defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Double getDoubleAndRemove(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public double getDoubleAndRemove(CharSequence name, double defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Long getTimeMillisAndRemove(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public long getTimeMillisAndRemove(CharSequence name, long defaultValue) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean contains(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean contains(CharSequence name, CharSequence value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean contains(CharSequence name, CharSequence value, boolean caseInsensitive) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean containsObject(CharSequence name, Object value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean containsBoolean(CharSequence name, boolean value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean containsByte(CharSequence name, byte value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean containsChar(CharSequence name, char value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean containsShort(CharSequence name, short value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean containsInt(CharSequence name, int value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean containsLong(CharSequence name, long value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean containsFloat(CharSequence name, float value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean containsDouble(CharSequence name, double value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean containsTimeMillis(CharSequence name, long value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers add(CharSequence name, CharSequence... values) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers add(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers add(CharSequence name, CharSequence value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers add(CharSequence name, Iterable<? extends CharSequence> values) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers addObject(CharSequence name, Object value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers addObject(CharSequence name, Iterable<?> values) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers addObject(CharSequence name, Object... values) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers addBoolean(CharSequence name, boolean value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers addByte(CharSequence name, byte value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers addChar(CharSequence name, char value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers addShort(CharSequence name, short value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers addInt(CharSequence name, int value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers addLong(CharSequence name, long value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers addFloat(CharSequence name, float value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers addDouble(CharSequence name, double value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers addTimeMillis(CharSequence name, long value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers set(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers set(CharSequence name, CharSequence value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers set(CharSequence name, Iterable<? extends CharSequence> values) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers set(CharSequence name, CharSequence... values) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers setObject(CharSequence name, Object value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers setObject(CharSequence name, Iterable<?> values) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers setObject(CharSequence name, Object... values) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers setBoolean(CharSequence name, boolean value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers setByte(CharSequence name, byte value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers setChar(CharSequence name, char value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers setShort(CharSequence name, short value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers setInt(CharSequence name, int value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers setLong(CharSequence name, long value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers setFloat(CharSequence name, float value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers setDouble(CharSequence name, double value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers setTimeMillis(CharSequence name, long value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers setAll(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean remove(CharSequence name) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers clear() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Iterator<Entry<CharSequence, CharSequence>> iterator() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers method(CharSequence value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public CharSequence method() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers scheme(CharSequence value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public CharSequence scheme() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers authority(CharSequence value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public CharSequence authority() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers path(CharSequence value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public CharSequence path() {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public Http2Headers status(CharSequence value) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public CharSequence status() {
+    throw new UnsupportedOperationException();
+  }
+}
+
diff --git a/netty/src/main/java/io/grpc/netty/AbstractNettyHandler.java b/netty/src/main/java/io/grpc/netty/AbstractNettyHandler.java
new file mode 100644
index 0000000..cf1e73e
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/AbstractNettyHandler.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static io.netty.handler.codec.http2.Http2CodecUtil.getEmbeddedHttp2Exception;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.codec.http2.Http2ConnectionDecoder;
+import io.netty.handler.codec.http2.Http2ConnectionEncoder;
+import io.netty.handler.codec.http2.Http2Exception;
+import io.netty.handler.codec.http2.Http2LocalFlowController;
+import io.netty.handler.codec.http2.Http2Settings;
+import io.netty.handler.codec.http2.Http2Stream;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Base class for all Netty gRPC handlers. This class standardizes exception handling (always
+ * shutdown the connection) as well as sending the initial connection window at startup.
+ */
+abstract class AbstractNettyHandler extends GrpcHttp2ConnectionHandler {
+  private static final long GRACEFUL_SHUTDOWN_NO_TIMEOUT = -1;
+  private boolean autoTuneFlowControlOn = false;
+  private int initialConnectionWindow;
+  private ChannelHandlerContext ctx;
+  private final FlowControlPinger flowControlPing = new FlowControlPinger();
+
+  private static final long BDP_MEASUREMENT_PING = 1234;
+
+  AbstractNettyHandler(
+      ChannelPromise channelUnused,
+      Http2ConnectionDecoder decoder,
+      Http2ConnectionEncoder encoder,
+      Http2Settings initialSettings) {
+    super(channelUnused, decoder, encoder, initialSettings);
+
+    // During a graceful shutdown, wait until all streams are closed.
+    gracefulShutdownTimeoutMillis(GRACEFUL_SHUTDOWN_NO_TIMEOUT);
+
+    // Extract the connection window from the settings if it was set.
+    this.initialConnectionWindow = initialSettings.initialWindowSize() == null ? -1 :
+            initialSettings.initialWindowSize();
+  }
+
+  @Override
+  public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+    this.ctx = ctx;
+    // Sends the connection preface if we haven't already.
+    super.handlerAdded(ctx);
+    sendInitialConnectionWindow();
+  }
+
+  @Override
+  public void channelActive(ChannelHandlerContext ctx) throws Exception {
+    // Sends connection preface if we haven't already.
+    super.channelActive(ctx);
+    sendInitialConnectionWindow();
+  }
+
+  @Override
+  public final void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+    Http2Exception embedded = getEmbeddedHttp2Exception(cause);
+    if (embedded == null) {
+      // There was no embedded Http2Exception, assume it's a connection error. Subclasses are
+      // responsible for storing the appropriate status and shutting down the connection.
+      onError(ctx, /* outbound= */ false, cause);
+    } else {
+      super.exceptionCaught(ctx, cause);
+    }
+  }
+
+  protected final ChannelHandlerContext ctx() {
+    return ctx;
+  }
+
+  /**
+   * Sends initial connection window to the remote endpoint if necessary.
+   */
+  private void sendInitialConnectionWindow() throws Http2Exception {
+    if (ctx.channel().isActive() && initialConnectionWindow > 0) {
+      Http2Stream connectionStream = connection().connectionStream();
+      int currentSize = connection().local().flowController().windowSize(connectionStream);
+      int delta = initialConnectionWindow - currentSize;
+      decoder().flowController().incrementWindowSize(connectionStream, delta);
+      initialConnectionWindow = -1;
+      ctx.flush();
+    }
+  }
+
+  @VisibleForTesting
+  FlowControlPinger flowControlPing() {
+    return flowControlPing;
+  }
+
+  @VisibleForTesting
+  void setAutoTuneFlowControl(boolean isOn) {
+    autoTuneFlowControlOn = isOn;
+  }
+
+  /**
+   * Class for handling flow control pinging and flow control window updates as necessary.
+   */
+  final class FlowControlPinger {
+
+    private static final int MAX_WINDOW_SIZE = 8 * 1024 * 1024;
+    private int pingCount;
+    private int pingReturn;
+    private boolean pinging;
+    private int dataSizeSincePing;
+    private float lastBandwidth; // bytes per second
+    private long lastPingTime;
+
+    public long payload() {
+      return BDP_MEASUREMENT_PING;
+    }
+
+    public int maxWindow() {
+      return MAX_WINDOW_SIZE;
+    }
+
+    public void onDataRead(int dataLength, int paddingLength) {
+      if (!autoTuneFlowControlOn) {
+        return;
+      }
+      if (!isPinging()) {
+        setPinging(true);
+        sendPing(ctx());
+      }
+      incrementDataSincePing(dataLength + paddingLength);
+    }
+
+    public void updateWindow() throws Http2Exception {
+      if (!autoTuneFlowControlOn) {
+        return;
+      }
+      pingReturn++;
+      long elapsedTime = (System.nanoTime() - lastPingTime);
+      if (elapsedTime == 0) {
+        elapsedTime = 1;
+      }
+      long bandwidth = (getDataSincePing() * TimeUnit.SECONDS.toNanos(1)) / elapsedTime;
+      Http2LocalFlowController fc = decoder().flowController();
+      // Calculate new window size by doubling the observed BDP, but cap at max window
+      int targetWindow = Math.min(getDataSincePing() * 2, MAX_WINDOW_SIZE);
+      setPinging(false);
+      int currentWindow = fc.initialWindowSize(connection().connectionStream());
+      if (targetWindow > currentWindow && bandwidth > lastBandwidth) {
+        lastBandwidth = bandwidth;
+        int increase = targetWindow - currentWindow;
+        fc.incrementWindowSize(connection().connectionStream(), increase);
+        fc.initialWindowSize(targetWindow);
+        Http2Settings settings = new Http2Settings();
+        settings.initialWindowSize(targetWindow);
+        frameWriter().writeSettings(ctx(), settings, ctx().newPromise());
+      }
+
+    }
+
+    private boolean isPinging() {
+      return pinging;
+    }
+
+    private void setPinging(boolean pingOut) {
+      pinging = pingOut;
+    }
+
+    private void sendPing(ChannelHandlerContext ctx) {
+      setDataSizeSincePing(0);
+      lastPingTime = System.nanoTime();
+      encoder().writePing(ctx, false, BDP_MEASUREMENT_PING, ctx.newPromise());
+      pingCount++;
+    }
+
+    private void incrementDataSincePing(int increase) {
+      int currentSize = getDataSincePing();
+      setDataSizeSincePing(currentSize + increase);
+    }
+
+    @VisibleForTesting
+    int getPingCount() {
+      return pingCount;
+    }
+
+    @VisibleForTesting
+    int getPingReturn() {
+      return pingReturn;
+    }
+
+    @VisibleForTesting
+    int getDataSincePing() {
+      return dataSizeSincePing;
+    }
+
+    @VisibleForTesting
+    void setDataSizeSincePing(int dataSize) {
+      dataSizeSincePing = dataSize;
+    }
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/CancelClientStreamCommand.java b/netty/src/main/java/io/grpc/netty/CancelClientStreamCommand.java
new file mode 100644
index 0000000..8ccb3fa
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/CancelClientStreamCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import com.google.common.base.Preconditions;
+import io.grpc.Status;
+import javax.annotation.Nullable;
+
+/**
+ * Command sent from a Netty client stream to the handler to cancel the stream.
+ */
+class CancelClientStreamCommand extends WriteQueue.AbstractQueuedCommand {
+  private final NettyClientStream.TransportState stream;
+  @Nullable private final Status reason;
+
+  CancelClientStreamCommand(NettyClientStream.TransportState stream, Status reason) {
+    this.stream = Preconditions.checkNotNull(stream, "stream");
+    Preconditions.checkArgument(
+        reason == null || !reason.isOk(), "Should not cancel with OK status");
+    this.reason = reason;
+  }
+
+  NettyClientStream.TransportState stream() {
+    return stream;
+  }
+
+  @Nullable
+  Status reason() {
+    return reason;
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/CancelServerStreamCommand.java b/netty/src/main/java/io/grpc/netty/CancelServerStreamCommand.java
new file mode 100644
index 0000000..13955c3
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/CancelServerStreamCommand.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import io.grpc.Status;
+
+/**
+ * Command sent from a Netty server stream to the handler to cancel the stream.
+ */
+class CancelServerStreamCommand extends WriteQueue.AbstractQueuedCommand {
+  private final NettyServerStream.TransportState stream;
+  private final Status reason;
+
+  CancelServerStreamCommand(NettyServerStream.TransportState stream, Status reason) {
+    this.stream = Preconditions.checkNotNull(stream, "stream");
+    this.reason = Preconditions.checkNotNull(reason, "reason");
+  }
+
+  NettyServerStream.TransportState stream() {
+    return stream;
+  }
+
+  Status reason() {
+    return reason;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    CancelServerStreamCommand that = (CancelServerStreamCommand) o;
+
+    return Objects.equal(this.stream, that.stream)
+        && Objects.equal(this.reason, that.reason);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(stream, reason);
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("stream", stream)
+        .add("reason", reason)
+        .toString();
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/ClientTransportLifecycleManager.java b/netty/src/main/java/io/grpc/netty/ClientTransportLifecycleManager.java
new file mode 100644
index 0000000..e9f7040
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/ClientTransportLifecycleManager.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.Status;
+import io.grpc.internal.ManagedClientTransport;
+
+/** Maintainer of transport lifecycle status. */
+final class ClientTransportLifecycleManager {
+  private final ManagedClientTransport.Listener listener;
+  private boolean transportReady;
+  private boolean transportShutdown;
+  private boolean transportInUse;
+  /** null iff !transportShutdown. */
+  private Status shutdownStatus;
+  /** null iff !transportShutdown. */
+  private Throwable shutdownThrowable;
+  private boolean transportTerminated;
+
+  public ClientTransportLifecycleManager(ManagedClientTransport.Listener listener) {
+    this.listener = listener;
+  }
+
+  public void notifyReady() {
+    if (transportReady || transportShutdown) {
+      return;
+    }
+    transportReady = true;
+    listener.transportReady();
+  }
+
+  public void notifyShutdown(Status s) {
+    if (transportShutdown) {
+      return;
+    }
+    transportShutdown = true;
+    shutdownStatus = s;
+    shutdownThrowable = s.asException();
+    listener.transportShutdown(s);
+  }
+
+  public void notifyInUse(boolean inUse) {
+    if (inUse == transportInUse) {
+      return;
+    }
+    transportInUse = inUse;
+    listener.transportInUse(inUse);
+  }
+
+  public void notifyTerminated(Status s) {
+    if (transportTerminated) {
+      return;
+    }
+    transportTerminated = true;
+    notifyShutdown(s);
+    listener.transportTerminated();
+  }
+
+  public Status getShutdownStatus() {
+    return shutdownStatus;
+  }
+
+  public Throwable getShutdownThrowable() {
+    return shutdownThrowable;
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/CreateStreamCommand.java b/netty/src/main/java/io/grpc/netty/CreateStreamCommand.java
new file mode 100644
index 0000000..9a758e6
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/CreateStreamCommand.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import com.google.common.base.Preconditions;
+import io.netty.handler.codec.http2.Http2Headers;
+
+/**
+ * A command to create a new stream. This is created by {@link NettyClientStream} and passed to the
+ * {@link NettyClientHandler} for processing in the Channel thread.
+ */
+class CreateStreamCommand extends WriteQueue.AbstractQueuedCommand {
+  private final Http2Headers headers;
+  private final NettyClientStream.TransportState stream;
+  private final boolean get;
+
+  CreateStreamCommand(Http2Headers headers,
+                      NettyClientStream.TransportState stream) {
+    this(headers, stream, false);
+  }
+
+  CreateStreamCommand(Http2Headers headers,
+                      NettyClientStream.TransportState stream, boolean get) {
+    this.stream = Preconditions.checkNotNull(stream, "stream");
+    this.headers = Preconditions.checkNotNull(headers, "headers");
+    this.get = get;
+  }
+
+  NettyClientStream.TransportState stream() {
+    return stream;
+  }
+
+  Http2Headers headers() {
+    return headers;
+  }
+
+  boolean isGet() {
+    return get;
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/ForcefulCloseCommand.java b/netty/src/main/java/io/grpc/netty/ForcefulCloseCommand.java
new file mode 100644
index 0000000..94bc088
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/ForcefulCloseCommand.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.Status;
+
+/**
+ * A command to trigger close and close all streams. It is buffered differently than normal close
+ * and also includes reason for closure.
+ */
+class ForcefulCloseCommand extends WriteQueue.AbstractQueuedCommand {
+  private final Status status;
+
+  public ForcefulCloseCommand(Status status) {
+    this.status = status;
+  }
+
+  public Status getStatus() {
+    return status;
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/GracefulCloseCommand.java b/netty/src/main/java/io/grpc/netty/GracefulCloseCommand.java
new file mode 100644
index 0000000..7c87aa7
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/GracefulCloseCommand.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.Status;
+
+/**
+ * A command to trigger close. It is buffered differently than normal close and also includes
+ * reason for closure.
+ */
+class GracefulCloseCommand extends WriteQueue.AbstractQueuedCommand {
+  private final Status status;
+
+  public GracefulCloseCommand(Status status) {
+    this.status = status;
+  }
+
+  public Status getStatus() {
+    return status;
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java
new file mode 100644
index 0000000..2c127b1
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2ConnectionHandler.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.Attributes;
+import io.grpc.Internal;
+import io.grpc.InternalChannelz;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.codec.http2.Http2ConnectionDecoder;
+import io.netty.handler.codec.http2.Http2ConnectionEncoder;
+import io.netty.handler.codec.http2.Http2ConnectionHandler;
+import io.netty.handler.codec.http2.Http2Settings;
+import javax.annotation.Nullable;
+
+/**
+ * gRPC wrapper for {@link Http2ConnectionHandler}.
+ */
+@Internal
+public abstract class GrpcHttp2ConnectionHandler extends Http2ConnectionHandler {
+
+  @Nullable
+  protected final ChannelPromise channelUnused;
+
+  public GrpcHttp2ConnectionHandler(
+      ChannelPromise channelUnused,
+      Http2ConnectionDecoder decoder,
+      Http2ConnectionEncoder encoder,
+      Http2Settings initialSettings) {
+    super(decoder, encoder, initialSettings);
+    this.channelUnused = channelUnused;
+  }
+
+  /**
+   * Same as {@link #handleProtocolNegotiationCompleted(
+   *   Attributes, io.grpc.InternalChannelz.Security)}
+   * but with no {@link io.grpc.InternalChannelz.Security}.
+   *
+   * @deprecated Use the two argument method instead.
+   */
+  @Deprecated
+  public void handleProtocolNegotiationCompleted(Attributes attrs) {
+    handleProtocolNegotiationCompleted(attrs, /*securityInfo=*/ null);
+  }
+
+  /**
+   * Triggered on protocol negotiation completion.
+   *
+   * <p>It must me called after negotiation is completed but before given handler is added to the
+   * channel.
+   *
+   * @param attrs arbitrary attributes passed after protocol negotiation (eg. SSLSession).
+   * @param securityInfo informs channelz about the security protocol.
+   */
+  public void handleProtocolNegotiationCompleted(
+      Attributes attrs, InternalChannelz.Security securityInfo) {
+  }
+
+  /**
+   * Calling this method indicates that the channel will no longer be used.  This method is roughly
+   * the same as calling {@link #close} on the channel, but leaving the channel alive.  This is
+   * useful if the channel will soon be deregistered from the executor and used in a non-Netty
+   * context.
+   */
+  @SuppressWarnings("FutureReturnValueIgnored")
+  public void notifyUnused() {
+    channelUnused.setSuccess(null);
+  }
+
+  /** Get the attributes of the EquivalentAddressGroup used to create this transport. */
+  public Attributes getEagAttributes() {
+    return Attributes.EMPTY;
+  }
+
+  /**
+   * Returns the authority of the server. Only available on the client-side.
+   *
+   * @throws UnsupportedOperationException if on server-side
+   */
+  public String getAuthority() {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersUtils.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersUtils.java
new file mode 100644
index 0000000..920afca
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2HeadersUtils.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Charsets.US_ASCII;
+import static com.google.common.base.Preconditions.checkArgument;
+import static io.grpc.netty.Utils.TE_HEADER;
+import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
+import static io.netty.handler.codec.http2.Http2Exception.connectionError;
+import static io.netty.util.AsciiString.isUpperCase;
+
+import com.google.common.io.BaseEncoding;
+import io.grpc.Metadata;
+import io.netty.handler.codec.http2.DefaultHttp2HeadersDecoder;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.util.AsciiString;
+import io.netty.util.internal.PlatformDependent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A headers utils providing custom gRPC implementations of {@link DefaultHttp2HeadersDecoder}.
+ */
+class GrpcHttp2HeadersUtils {
+  static final class GrpcHttp2ServerHeadersDecoder extends DefaultHttp2HeadersDecoder {
+
+    GrpcHttp2ServerHeadersDecoder(long maxHeaderListSize) {
+      super(true, maxHeaderListSize);
+    }
+
+    @Override
+    protected GrpcHttp2InboundHeaders newHeaders() {
+      return new GrpcHttp2RequestHeaders(numberOfHeadersGuess());
+    }
+  }
+
+  static final class GrpcHttp2ClientHeadersDecoder extends DefaultHttp2HeadersDecoder {
+
+    GrpcHttp2ClientHeadersDecoder(long maxHeaderListSize) {
+      super(true, maxHeaderListSize);
+    }
+
+    @Override
+    protected GrpcHttp2InboundHeaders newHeaders() {
+      return new GrpcHttp2ResponseHeaders(numberOfHeadersGuess());
+    }
+  }
+
+  /**
+   * A {@link Http2Headers} implementation optimized for inbound/received headers.
+   *
+   * <p>Header names and values are stored in simple arrays, which makes insert run in O(1)
+   * and retrievial a O(n). Header name equality is not determined by the equals implementation of
+   * {@link CharSequence} type, but by comparing two names byte to byte.
+   *
+   * <p>All {@link CharSequence} input parameters and return values are required to be of type
+   * {@link AsciiString}.
+   */
+  abstract static class GrpcHttp2InboundHeaders extends AbstractHttp2Headers {
+
+    private static final AsciiString binaryHeaderSuffix =
+        new AsciiString(Metadata.BINARY_HEADER_SUFFIX.getBytes(US_ASCII));
+
+    private byte[][] namesAndValues;
+    private AsciiString[] values;
+    private int namesAndValuesIdx;
+
+    GrpcHttp2InboundHeaders(int numHeadersGuess) {
+      checkArgument(numHeadersGuess > 0, "numHeadersGuess needs to be gt zero.");
+      namesAndValues = new byte[numHeadersGuess * 2][];
+      values = new AsciiString[numHeadersGuess];
+    }
+
+    protected Http2Headers add(AsciiString name, AsciiString value) {
+      if (namesAndValuesIdx == namesAndValues.length) {
+        expandHeadersAndValues();
+      }
+      byte[] nameBytes = bytes(name);
+      byte[] valueBytes = toBinaryValue(name, value);
+      values[namesAndValuesIdx / 2] = value;
+      namesAndValues[namesAndValuesIdx] = nameBytes;
+      namesAndValuesIdx++;
+      namesAndValues[namesAndValuesIdx] = valueBytes;
+      namesAndValuesIdx++;
+      return this;
+    }
+
+    protected CharSequence get(AsciiString name) {
+      for (int i = 0; i < namesAndValuesIdx; i += 2) {
+        if (equals(name, namesAndValues[i])) {
+          return values[i / 2];
+        }
+      }
+      return null;
+    }
+
+    @Override
+    public CharSequence status() {
+      return get(Http2Headers.PseudoHeaderName.STATUS.value());
+    }
+
+    @Override
+    public List<CharSequence> getAll(CharSequence csName) {
+      AsciiString name = requireAsciiString(csName);
+      List<CharSequence> returnValues = new ArrayList<>(4);
+      for (int i = 0; i < namesAndValuesIdx; i += 2) {
+        if (equals(name, namesAndValues[i])) {
+          returnValues.add(values[i / 2]);
+        }
+      }
+      return returnValues;
+    }
+
+    /**
+     * Returns the header names and values as bytes. An even numbered index contains the
+     * {@code byte[]} representation of a header name (in insertion order), and the subsequent
+     * odd index number contains the corresponding header value.
+     *
+     * <p>The values of binary headers (with a -bin suffix), are already base64 decoded.
+     *
+     * <p>The array may contain several {@code null} values at the end. A {@code null} value an
+     * index means that all higher numbered indices also contain {@code null} values.
+     */
+    byte[][] namesAndValues() {
+      return namesAndValues;
+    }
+
+    /**
+     * Returns the number of none-null headers in {@link #namesAndValues()}.
+     */
+    protected int numHeaders() {
+      return namesAndValuesIdx / 2;
+    }
+
+    protected static boolean equals(AsciiString str0, byte[] str1) {
+      return equals(str0.array(), str0.arrayOffset(), str0.length(), str1, 0, str1.length);
+    }
+
+    protected static boolean equals(AsciiString str0, AsciiString str1) {
+      return equals(str0.array(), str0.arrayOffset(), str0.length(), str1.array(),
+          str1.arrayOffset(), str1.length());
+    }
+
+    protected static boolean equals(byte[] bytes0, int offset0, int length0, byte[] bytes1,
+        int offset1, int length1) {
+      if (length0 != length1) {
+        return false;
+      }
+      return PlatformDependent.equals(bytes0, offset0, bytes1, offset1, length0);
+    }
+
+    @SuppressWarnings("BetaApi") // BaseEncoding is stable in Guava 20.0
+    private static byte[] toBinaryValue(AsciiString name, AsciiString value) {
+      return name.endsWith(binaryHeaderSuffix)
+          ? BaseEncoding.base64().decode(value)
+          : bytes(value);
+    }
+
+    protected static byte[] bytes(AsciiString str) {
+      return str.isEntireArrayUsed() ? str.array() : str.toByteArray();
+    }
+
+    protected static AsciiString requireAsciiString(CharSequence cs) {
+      if (!(cs instanceof AsciiString)) {
+        throw new IllegalArgumentException("AsciiString expected. Was: " + cs.getClass().getName());
+      }
+      return (AsciiString) cs;
+    }
+
+    protected static boolean isPseudoHeader(AsciiString str) {
+      return !str.isEmpty() && str.charAt(0) == ':';
+    }
+
+    protected AsciiString validateName(AsciiString str) {
+      int offset = str.arrayOffset();
+      int length = str.length();
+      final byte[] data = str.array();
+      for (int i = offset; i < offset + length; i++) {
+        if (isUpperCase(data[i])) {
+          PlatformDependent.throwException(connectionError(PROTOCOL_ERROR,
+              "invalid header name '%s'", str));
+        }
+      }
+      return str;
+    }
+
+    private void expandHeadersAndValues() {
+      int newValuesLen = Math.max(2, values.length + values.length / 2);
+      int newNamesAndValuesLen = newValuesLen * 2;
+
+      byte[][] newNamesAndValues = new byte[newNamesAndValuesLen][];
+      AsciiString[] newValues = new AsciiString[newValuesLen];
+      System.arraycopy(namesAndValues, 0, newNamesAndValues, 0, namesAndValues.length);
+      System.arraycopy(values, 0, newValues, 0, values.length);
+      namesAndValues = newNamesAndValues;
+      values = newValues;
+    }
+
+    @Override
+    public int size() {
+      return numHeaders();
+    }
+
+    protected static void appendNameAndValue(StringBuilder builder, CharSequence name,
+        CharSequence value, boolean prependSeparator) {
+      if (prependSeparator) {
+        builder.append(", ");
+      }
+      builder.append(name).append(": ").append(value);
+    }
+
+    protected final String namesAndValuesToString() {
+      StringBuilder builder = new StringBuilder();
+      boolean prependSeparator = false;
+      for (int i = 0; i < namesAndValuesIdx; i += 2) {
+        String name = new String(namesAndValues[i], US_ASCII);
+        // If binary headers, the value is base64 encoded.
+        AsciiString value = values[i / 2];
+        appendNameAndValue(builder, name, value, prependSeparator);
+        prependSeparator = true;
+      }
+      return builder.toString();
+    }
+  }
+
+  /**
+   * A {@link GrpcHttp2InboundHeaders} implementation, optimized for HTTP/2 request headers. That
+   * is, HTTP/2 request pseudo headers are stored in dedicated fields and are NOT part of the
+   * array returned by {@link #namesAndValues()}.
+   *
+   * <p>This class only implements the methods used by {@link NettyServerHandler} and tests. All
+   * other methods throw an {@link UnsupportedOperationException}.
+   */
+  static final class GrpcHttp2RequestHeaders extends GrpcHttp2InboundHeaders {
+
+    private static final AsciiString PATH_HEADER = AsciiString.of(":path");
+    private static final AsciiString AUTHORITY_HEADER = AsciiString.of(":authority");
+    private static final AsciiString METHOD_HEADER = AsciiString.of(":method");
+    private static final AsciiString SCHEME_HEADER = AsciiString.of(":scheme");
+
+    private AsciiString path;
+    private AsciiString authority;
+    private AsciiString method;
+    private AsciiString scheme;
+    private AsciiString te;
+
+    GrpcHttp2RequestHeaders(int numHeadersGuess) {
+      super(numHeadersGuess);
+    }
+
+    @Override
+    public Http2Headers add(CharSequence csName, CharSequence csValue) {
+      AsciiString name = validateName(requireAsciiString(csName));
+      AsciiString value = requireAsciiString(csValue);
+      if (isPseudoHeader(name)) {
+        addPseudoHeader(name, value);
+        return this;
+      }
+      if (equals(TE_HEADER, name)) {
+        te = value;
+        return this;
+      }
+      return add(name, value);
+    }
+
+    @Override
+    public CharSequence get(CharSequence csName) {
+      AsciiString name = requireAsciiString(csName);
+      checkArgument(!isPseudoHeader(name), "Use direct accessor methods for pseudo headers.");
+      if (equals(TE_HEADER, name)) {
+        return te;
+      }
+      return get(name);
+    }
+
+    private void addPseudoHeader(CharSequence csName, CharSequence csValue) {
+      AsciiString name = requireAsciiString(csName);
+      AsciiString value = requireAsciiString(csValue);
+
+      if (equals(PATH_HEADER, name)) {
+        path = value;
+      } else if (equals(AUTHORITY_HEADER, name)) {
+        authority = value;
+      } else if (equals(METHOD_HEADER, name)) {
+        method = value;
+      } else if (equals(SCHEME_HEADER, name)) {
+        scheme = value;
+      } else {
+        PlatformDependent.throwException(
+            connectionError(PROTOCOL_ERROR, "Illegal pseudo-header '%s' in request.", name));
+      }
+    }
+
+    @Override
+    public CharSequence path() {
+      return path;
+    }
+
+    @Override
+    public CharSequence authority() {
+      return authority;
+    }
+
+    @Override
+    public CharSequence method() {
+      return method;
+    }
+
+    @Override
+    public CharSequence scheme() {
+      return scheme;
+    }
+
+    /**
+     * This method is called in tests only.
+     */
+    @Override
+    public List<CharSequence> getAll(CharSequence csName) {
+      AsciiString name = requireAsciiString(csName);
+      if (isPseudoHeader(name)) {
+        // This code should never be reached.
+        throw new IllegalArgumentException("Use direct accessor methods for pseudo headers.");
+      }
+      if (equals(TE_HEADER, name)) {
+        return Collections.singletonList((CharSequence) te);
+      }
+      return super.getAll(csName);
+    }
+
+    /**
+     * This method is called in tests only.
+     */
+    @Override
+    public int size() {
+      int size = 0;
+      if (path != null) {
+        size++;
+      }
+      if (authority != null) {
+        size++;
+      }
+      if (method != null) {
+        size++;
+      }
+      if (scheme != null) {
+        size++;
+      }
+      if (te != null) {
+        size++;
+      }
+      size += super.size();
+      return size;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('[');
+      boolean prependSeparator = false;
+
+      if (path != null) {
+        appendNameAndValue(builder, PATH_HEADER, path, prependSeparator);
+        prependSeparator = true;
+      }
+      if (authority != null) {
+        appendNameAndValue(builder, AUTHORITY_HEADER, authority, prependSeparator);
+        prependSeparator = true;
+      }
+      if (method != null) {
+        appendNameAndValue(builder, METHOD_HEADER, method, prependSeparator);
+        prependSeparator = true;
+      }
+      if (scheme != null) {
+        appendNameAndValue(builder, SCHEME_HEADER, scheme, prependSeparator);
+        prependSeparator = true;
+      }
+      if (te != null) {
+        appendNameAndValue(builder, TE_HEADER, te, prependSeparator);
+      }
+
+      String namesAndValues = namesAndValuesToString();
+
+      if (builder.length() > 0 && namesAndValues.length() > 0) {
+        builder.append(", ");
+      }
+
+      builder.append(namesAndValues);
+      builder.append(']');
+
+      return builder.toString();
+    }
+  }
+
+  /**
+   * This class only implements the methods used by {@link NettyClientHandler} and tests. All
+   * other methods throw an {@link UnsupportedOperationException}.
+   *
+   * <p>Unlike in {@link GrpcHttp2ResponseHeaders} the {@code :status} pseudo-header is not treated
+   * special and is part of {@link #namesAndValues}.
+   */
+  static final class GrpcHttp2ResponseHeaders extends GrpcHttp2InboundHeaders {
+
+    GrpcHttp2ResponseHeaders(int numHeadersGuess) {
+      super(numHeadersGuess);
+    }
+
+    @Override
+    public Http2Headers add(CharSequence csName, CharSequence csValue) {
+      AsciiString name = validateName(requireAsciiString(csName));
+      AsciiString value = requireAsciiString(csValue);
+      return add(name, value);
+    }
+
+    @Override
+    public CharSequence get(CharSequence csName) {
+      AsciiString name = requireAsciiString(csName);
+      return get(name);
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('[');
+      builder.append(namesAndValuesToString()).append(']');
+      return builder.toString();
+    }
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java
new file mode 100644
index 0000000..1efbf23
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/GrpcHttp2OutboundHeaders.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.util.AsciiString;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+
+/**
+ * A custom implementation of Http2Headers that only includes methods used by gRPC.
+ */
+final class GrpcHttp2OutboundHeaders extends AbstractHttp2Headers {
+
+  private final AsciiString[] normalHeaders;
+  private final AsciiString[] preHeaders;
+  private static final AsciiString[] EMPTY = new AsciiString[]{};
+
+  static GrpcHttp2OutboundHeaders clientRequestHeaders(byte[][] serializedMetadata,
+      AsciiString authority, AsciiString path, AsciiString method, AsciiString scheme,
+      AsciiString userAgent) {
+    AsciiString[] preHeaders = new AsciiString[] {
+        Http2Headers.PseudoHeaderName.AUTHORITY.value(), authority,
+        Http2Headers.PseudoHeaderName.PATH.value(), path,
+        Http2Headers.PseudoHeaderName.METHOD.value(), method,
+        Http2Headers.PseudoHeaderName.SCHEME.value(), scheme,
+        Utils.CONTENT_TYPE_HEADER, Utils.CONTENT_TYPE_GRPC,
+        Utils.TE_HEADER, Utils.TE_TRAILERS,
+        Utils.USER_AGENT, userAgent,
+    };
+    return new GrpcHttp2OutboundHeaders(preHeaders, serializedMetadata);
+  }
+
+  static GrpcHttp2OutboundHeaders serverResponseHeaders(byte[][] serializedMetadata) {
+    AsciiString[] preHeaders = new AsciiString[] {
+        Http2Headers.PseudoHeaderName.STATUS.value(), Utils.STATUS_OK,
+        Utils.CONTENT_TYPE_HEADER, Utils.CONTENT_TYPE_GRPC,
+    };
+    return new GrpcHttp2OutboundHeaders(preHeaders, serializedMetadata);
+  }
+
+  static GrpcHttp2OutboundHeaders serverResponseTrailers(byte[][] serializedMetadata) {
+    return new GrpcHttp2OutboundHeaders(EMPTY, serializedMetadata);
+  }
+
+  private GrpcHttp2OutboundHeaders(AsciiString[] preHeaders, byte[][] serializedMetadata) {
+    normalHeaders = new AsciiString[serializedMetadata.length];
+    for (int i = 0; i < normalHeaders.length; i++) {
+      normalHeaders[i] = new AsciiString(serializedMetadata[i], false);
+    }
+    this.preHeaders = preHeaders;
+  }
+
+  @Override
+  @SuppressWarnings("ReferenceEquality") // STATUS.value() never changes.
+  public CharSequence status() {
+    // preHeaders is never null.  It has status as the first element or not at all.
+    if (preHeaders.length >= 2 && preHeaders[0] == Http2Headers.PseudoHeaderName.STATUS.value()) {
+      return preHeaders[1];
+    }
+    return null;
+  }
+
+  @Override
+  public Iterator<Entry<CharSequence, CharSequence>> iterator() {
+    return new Itr();
+  }
+
+  @Override
+  public int size() {
+    return (normalHeaders.length + preHeaders.length) / 2;
+  }
+
+  private class Itr implements Entry<CharSequence, CharSequence>,
+      Iterator<Entry<CharSequence, CharSequence>> {
+    private int idx;
+    private AsciiString[] current = preHeaders.length != 0 ? preHeaders : normalHeaders;
+    private AsciiString key;
+    private AsciiString value;
+
+    @Override
+    public boolean hasNext() {
+      return idx < current.length;
+    }
+
+    /**
+     * This function is ordered specifically to get ideal performance on OpenJDK.  If you decide to
+     * change it, even in ways that don't seem possible to affect performance, please benchmark
+     * speeds before and after.
+     */
+    @Override
+    public Entry<CharSequence, CharSequence> next() {
+      if (hasNext()) {
+        key = current[idx];
+        value = current[idx + 1];
+        idx += 2;
+        if (idx >= current.length && current == preHeaders) {
+          current = normalHeaders;
+          idx = 0;
+        }
+        return this;
+      } else {
+        throw new NoSuchElementException();
+      }
+    }
+
+    @Override
+    public CharSequence getKey() {
+      return key;
+    }
+
+    @Override
+    public CharSequence getValue() {
+      return value;
+    }
+
+    @Override
+    public CharSequence setValue(CharSequence value) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('[');
+    String separator = "";
+    for (Entry<CharSequence, CharSequence> e : this) {
+      CharSequence name = e.getKey();
+      CharSequence value = e.getValue();
+      builder.append(separator);
+      builder.append(name).append(": ").append(value);
+      separator = ", ";
+    }
+    builder.append(']');
+    return builder.toString();
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/GrpcSslContexts.java b/netty/src/main/java/io/grpc/netty/GrpcSslContexts.java
new file mode 100644
index 0000000..b6aed29
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/GrpcSslContexts.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import io.grpc.ExperimentalApi;
+import io.grpc.internal.MoreThrowables;
+import io.netty.handler.codec.http2.Http2SecurityUtil;
+import io.netty.handler.ssl.ApplicationProtocolConfig;
+import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
+import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
+import io.netty.handler.ssl.ApplicationProtocolConfig.SelectorFailureBehavior;
+import io.netty.handler.ssl.OpenSsl;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import io.netty.handler.ssl.SupportedCipherSuiteFilter;
+import java.io.File;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.Provider;
+import java.security.Security;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Utility for configuring SslContext for gRPC.
+ */
+@SuppressWarnings("deprecation")
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784")
+public class GrpcSslContexts {
+  private static final Logger logger = Logger.getLogger(GrpcSslContexts.class.getName());
+
+  private GrpcSslContexts() {}
+
+  /*
+   * The experimental "grpc-exp" string identifies gRPC (and by implication
+   * HTTP/2) when used over TLS. This indicates to the server that the client
+   * will only send gRPC traffic on the h2 connection and is negotiated in
+   * preference to h2 when the client and server support it, but is not
+   * standardized. Support for this may be removed at any time.
+   */
+  private static final String GRPC_EXP_VERSION = "grpc-exp";
+
+  // The "h2" string identifies HTTP/2 when used over TLS
+  private static final String HTTP2_VERSION = "h2";
+
+  /*
+   * List of ALPN/NPN protocols in order of preference. GRPC_EXP_VERSION
+   * requires that HTTP2_VERSION be present and that GRPC_EXP_VERSION should be
+   * preferenced over HTTP2_VERSION.
+   */
+  static final List<String> NEXT_PROTOCOL_VERSIONS =
+      Collections.unmodifiableList(Arrays.asList(GRPC_EXP_VERSION, HTTP2_VERSION));
+
+  /*
+   * These configs use ACCEPT due to limited support in OpenSSL.  Actual protocol enforcement is
+   * done in ProtocolNegotiators.
+   */
+  private static final ApplicationProtocolConfig ALPN = new ApplicationProtocolConfig(
+      Protocol.ALPN,
+      SelectorFailureBehavior.NO_ADVERTISE,
+      SelectedListenerFailureBehavior.ACCEPT,
+      NEXT_PROTOCOL_VERSIONS);
+
+  private static final ApplicationProtocolConfig NPN = new ApplicationProtocolConfig(
+      Protocol.NPN,
+      SelectorFailureBehavior.NO_ADVERTISE,
+      SelectedListenerFailureBehavior.ACCEPT,
+      NEXT_PROTOCOL_VERSIONS);
+
+  private static final ApplicationProtocolConfig NPN_AND_ALPN = new ApplicationProtocolConfig(
+      Protocol.NPN_AND_ALPN,
+      SelectorFailureBehavior.NO_ADVERTISE,
+      SelectedListenerFailureBehavior.ACCEPT,
+      NEXT_PROTOCOL_VERSIONS);
+
+  private static final String SUN_PROVIDER_NAME = "SunJSSE";
+  private static final Method IS_CONSCRYPT_PROVIDER;
+
+  static {
+    Method method = null;
+    try {
+      Class<?> conscryptClass = Class.forName("org.conscrypt.Conscrypt");
+      method = conscryptClass.getMethod("isConscrypt", Provider.class);
+    } catch (ClassNotFoundException ex) {
+      logger.log(Level.FINE, "Conscrypt class not found. Not using Conscrypt", ex);
+    } catch (NoSuchMethodException ex) {
+      throw new AssertionError(ex);
+    }
+    IS_CONSCRYPT_PROVIDER = method;
+  }
+
+  /**
+   * Creates a SslContextBuilder with ciphers and APN appropriate for gRPC.
+   *
+   * @see SslContextBuilder#forClient()
+   * @see #configure(SslContextBuilder)
+   */
+  public static SslContextBuilder forClient() {
+    return configure(SslContextBuilder.forClient());
+  }
+
+  /**
+   * Creates a SslContextBuilder with ciphers and APN appropriate for gRPC.
+   *
+   * @see SslContextBuilder#forServer(File, File)
+   * @see #configure(SslContextBuilder)
+   */
+  public static SslContextBuilder forServer(File keyCertChainFile, File keyFile) {
+    return configure(SslContextBuilder.forServer(keyCertChainFile, keyFile));
+  }
+
+  /**
+   * Creates a SslContextBuilder with ciphers and APN appropriate for gRPC.
+   *
+   * @see SslContextBuilder#forServer(File, File, String)
+   * @see #configure(SslContextBuilder)
+   */
+  public static SslContextBuilder forServer(
+      File keyCertChainFile, File keyFile, String keyPassword) {
+    return configure(SslContextBuilder.forServer(keyCertChainFile, keyFile, keyPassword));
+  }
+
+  /**
+   * Creates a SslContextBuilder with ciphers and APN appropriate for gRPC.
+   *
+   * @see SslContextBuilder#forServer(InputStream, InputStream)
+   * @see #configure(SslContextBuilder)
+   */
+  public static SslContextBuilder forServer(InputStream keyCertChain, InputStream key) {
+    return configure(SslContextBuilder.forServer(keyCertChain, key));
+  }
+
+  /**
+   * Creates a SslContextBuilder with ciphers and APN appropriate for gRPC.
+   *
+   * @see SslContextBuilder#forServer(InputStream, InputStream, String)
+   * @see #configure(SslContextBuilder)
+   */
+  public static SslContextBuilder forServer(
+      InputStream keyCertChain, InputStream key, String keyPassword) {
+    return configure(SslContextBuilder.forServer(keyCertChain, key, keyPassword));
+  }
+
+  /**
+   * Set ciphers and APN appropriate for gRPC. Precisely what is set is permitted to change, so if
+   * an application requires particular settings it should override the options set here.
+   */
+  @CanIgnoreReturnValue
+  public static SslContextBuilder configure(SslContextBuilder builder) {
+    return configure(builder, defaultSslProvider());
+  }
+
+  /**
+   * Set ciphers and APN appropriate for gRPC. Precisely what is set is permitted to change, so if
+   * an application requires particular settings it should override the options set here.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784")
+  @CanIgnoreReturnValue
+  public static SslContextBuilder configure(SslContextBuilder builder, SslProvider provider) {
+    switch (provider) {
+      case JDK:
+      {
+        Provider jdkProvider = findJdkProvider();
+        if (jdkProvider == null) {
+          throw new IllegalArgumentException(
+              "Could not find Jetty NPN/ALPN or Conscrypt as installed JDK providers");
+        }
+        return configure(builder, jdkProvider);
+      }
+      case OPENSSL:
+      {
+        ApplicationProtocolConfig apc;
+        if (OpenSsl.isAlpnSupported()) {
+          apc = NPN_AND_ALPN;
+        } else {
+          apc = NPN;
+        }
+        return builder
+            .sslProvider(SslProvider.OPENSSL)
+            .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
+            .applicationProtocolConfig(apc);
+      }
+      default:
+        throw new IllegalArgumentException("Unsupported provider: " + provider);
+    }
+  }
+
+  /**
+   * Set ciphers and APN appropriate for gRPC. Precisely what is set is permitted to change, so if
+   * an application requires particular settings it should override the options set here.
+   */
+  @CanIgnoreReturnValue
+  public static SslContextBuilder configure(SslContextBuilder builder, Provider jdkProvider) {
+    ApplicationProtocolConfig apc;
+    if (SUN_PROVIDER_NAME.equals(jdkProvider.getName())) {
+      // Jetty ALPN/NPN only supports one of NPN or ALPN
+      if (JettyTlsUtil.isJettyAlpnConfigured()) {
+        apc = ALPN;
+      } else if (JettyTlsUtil.isJettyNpnConfigured()) {
+        apc = NPN;
+      } else if (JettyTlsUtil.isJava9AlpnAvailable()) {
+        apc = ALPN;
+      } else {
+        throw new IllegalArgumentException(
+            SUN_PROVIDER_NAME + " selected, but Jetty NPN/ALPN unavailable");
+      }
+    } else if (isConscrypt(jdkProvider)) {
+      apc = ALPN;
+    } else {
+      throw new IllegalArgumentException("Unknown provider; can't configure: " + jdkProvider);
+    }
+    return builder
+        .sslProvider(SslProvider.JDK)
+        .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
+        .applicationProtocolConfig(apc)
+        .sslContextProvider(jdkProvider);
+  }
+
+  /**
+   * Returns OpenSSL if available, otherwise returns the JDK provider.
+   */
+  private static SslProvider defaultSslProvider() {
+    if (OpenSsl.isAvailable()) {
+      logger.log(Level.FINE, "Selecting OPENSSL");
+      return SslProvider.OPENSSL;
+    }
+    Provider provider = findJdkProvider();
+    if (provider != null) {
+      logger.log(Level.FINE, "Selecting JDK with provider {0}", provider);
+      return SslProvider.JDK;
+    }
+    logger.log(Level.INFO, "netty-tcnative unavailable (this may be normal)",
+        OpenSsl.unavailabilityCause());
+    logger.log(Level.INFO, "Conscrypt not found (this may be normal)");
+    logger.log(Level.INFO, "Jetty ALPN unavailable (this may be normal)",
+        JettyTlsUtil.getJettyAlpnUnavailabilityCause());
+    throw new IllegalStateException(
+        "Could not find TLS ALPN provider; "
+        + "no working netty-tcnative, Conscrypt, or Jetty NPN/ALPN available");
+  }
+
+  private static Provider findJdkProvider() {
+    for (Provider provider : Security.getProviders("SSLContext.TLS")) {
+      if (SUN_PROVIDER_NAME.equals(provider.getName())) {
+        if (JettyTlsUtil.isJettyAlpnConfigured()
+            || JettyTlsUtil.isJettyNpnConfigured()
+            || JettyTlsUtil.isJava9AlpnAvailable()) {
+          return provider;
+        }
+      } else if (isConscrypt(provider)) {
+        return provider;
+      }
+    }
+    return null;
+  }
+
+  private static boolean isConscrypt(Provider provider) {
+    if (IS_CONSCRYPT_PROVIDER == null) {
+      return false;
+    }
+    try {
+      return (Boolean) IS_CONSCRYPT_PROVIDER.invoke(null, provider);
+    } catch (IllegalAccessException ex) {
+      throw new AssertionError(ex);
+    } catch (InvocationTargetException ex) {
+      if (ex.getCause() != null) {
+        MoreThrowables.throwIfUnchecked(ex.getCause());
+        // If checked, just wrap up everything.
+      }
+      throw new AssertionError(ex);
+    }
+  }
+
+  @SuppressWarnings("deprecation")
+  static void ensureAlpnAndH2Enabled(
+      io.netty.handler.ssl.ApplicationProtocolNegotiator alpnNegotiator) {
+    checkArgument(alpnNegotiator != null, "ALPN must be configured");
+    checkArgument(alpnNegotiator.protocols() != null && !alpnNegotiator.protocols().isEmpty(),
+        "ALPN must be enabled and list HTTP/2 as a supported protocol.");
+    checkArgument(
+        alpnNegotiator.protocols().contains(HTTP2_VERSION),
+        "This ALPN config does not support HTTP/2. Expected %s, but got %s'.",
+        HTTP2_VERSION,
+        alpnNegotiator.protocols());
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/HandlerSettings.java b/netty/src/main/java/io/grpc/netty/HandlerSettings.java
new file mode 100644
index 0000000..553554f
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/HandlerSettings.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.Internal;
+
+/**
+ * Allows autoFlowControl to be turned on and off from interop testing and flow control windows to
+ * be accessed. For internal use only.
+ *
+ * @deprecated renamed to {@link InternalHandlerSettings} and should not be used externally
+ */
+@VisibleForTesting // Visible for tests in other packages.
+@Internal
+@Deprecated
+public final class HandlerSettings {
+  public static void enable(boolean enable) {
+    NettyHandlerSettings.enable(enable);
+  }
+
+  public static synchronized void autoWindowOn(boolean autoFlowControl) {
+    NettyHandlerSettings.autoWindowOn(autoFlowControl);
+  }
+
+  public static synchronized int getLatestClientWindow() {
+    return NettyHandlerSettings.getLatestServerWindow();
+  }
+
+  public static synchronized int getLatestServerWindow() {
+    return NettyHandlerSettings.getLatestServerWindow();
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/InternalHandlerSettings.java b/netty/src/main/java/io/grpc/netty/InternalHandlerSettings.java
new file mode 100644
index 0000000..ed6a552
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/InternalHandlerSettings.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.Internal;
+
+/**
+ * Controlled accessor to {@link NettyHandlerSettings}.
+ */
+@VisibleForTesting // Visible for tests in other packages.
+@Internal
+public final class InternalHandlerSettings {
+
+  public static void enable(boolean enable) {
+    NettyHandlerSettings.enable(enable);
+  }
+
+  public static synchronized void autoWindowOn(boolean autoFlowControl) {
+    NettyHandlerSettings.autoWindowOn(autoFlowControl);
+  }
+
+  public static synchronized int getLatestClientWindow() {
+    return NettyHandlerSettings.getLatestClientWindow();
+  }
+
+  public static synchronized int getLatestServerWindow() {
+    return NettyHandlerSettings.getLatestServerWindow();
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/InternalNettyChannelBuilder.java b/netty/src/main/java/io/grpc/netty/InternalNettyChannelBuilder.java
new file mode 100644
index 0000000..4711af9
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/InternalNettyChannelBuilder.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.Internal;
+import io.grpc.internal.ClientTransportFactory;
+import io.grpc.internal.ProxyParameters;
+import java.net.SocketAddress;
+
+/**
+ * Internal {@link NettyChannelBuilder} accessor.  This is intended for usage internal to the gRPC
+ * team.  If you *really* think you need to use this, contact the gRPC team first.
+ */
+@Internal
+public final class InternalNettyChannelBuilder {
+
+  /**
+   * Checks authority upon channel construction.  The purpose of this interface is to raise the
+   * visibility of {@link NettyChannelBuilder.OverrideAuthorityChecker}.
+   */
+  public interface OverrideAuthorityChecker extends NettyChannelBuilder.OverrideAuthorityChecker {}
+
+  public static void overrideAuthorityChecker(
+      NettyChannelBuilder channelBuilder, OverrideAuthorityChecker authorityChecker) {
+    channelBuilder.overrideAuthorityChecker(authorityChecker);
+  }
+
+  /**
+   * Interface to create netty dynamic parameters.
+   */
+  public interface TransportCreationParamsFilterFactory
+      extends NettyChannelBuilder.TransportCreationParamsFilterFactory {
+    @Override
+    TransportCreationParamsFilter create(
+        SocketAddress targetServerAddress, String authority, String userAgent,
+        ProxyParameters proxy);
+  }
+
+  /**
+   * {@link TransportCreationParamsFilter} are those that may depend on late-known information about
+   * a client transport.  This interface can be used to dynamically alter params based on the
+   * params of {@code ClientTransportFactory#newClientTransport}.
+   */
+  public interface TransportCreationParamsFilter
+      extends NettyChannelBuilder.TransportCreationParamsFilter {}
+
+  public static void setDynamicTransportParamsFactory(
+      NettyChannelBuilder builder, TransportCreationParamsFilterFactory factory) {
+    builder.setDynamicParamsFactory(factory);
+  }
+
+  public static void setStatsEnabled(NettyChannelBuilder builder, boolean value) {
+    builder.setStatsEnabled(value);
+  }
+
+  public static void setTracingEnabled(NettyChannelBuilder builder, boolean value) {
+    builder.setTracingEnabled(value);
+  }
+
+  public static void setStatsRecordStartedRpcs(NettyChannelBuilder builder, boolean value) {
+    builder.setStatsRecordStartedRpcs(value);
+  }
+
+  public static ClientTransportFactory buildTransportFactory(NettyChannelBuilder builder) {
+    return builder.buildTransportFactory();
+  }
+
+  private InternalNettyChannelBuilder() {}
+}
diff --git a/netty/src/main/java/io/grpc/netty/InternalNettyServerBuilder.java b/netty/src/main/java/io/grpc/netty/InternalNettyServerBuilder.java
new file mode 100644
index 0000000..0637b8e
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/InternalNettyServerBuilder.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.Internal;
+
+/**
+ * Internal {@link InternalNettyServerBuilder} accessor.  This is intended for usage internal to
+ * the gRPC team.  If you *really* think you need to use this, contact the gRPC team first.
+ */
+@Internal
+public final class InternalNettyServerBuilder {
+
+  public static void setStatsEnabled(NettyServerBuilder builder, boolean value) {
+    builder.setStatsEnabled(value);
+  }
+
+  public static void setStatsRecordStartedRpcs(NettyServerBuilder builder, boolean value) {
+    builder.setStatsRecordStartedRpcs(value);
+  }
+
+  public static void setTracingEnabled(NettyServerBuilder builder, boolean value) {
+    builder.setTracingEnabled(value);
+  }
+
+  private InternalNettyServerBuilder() {}
+}
diff --git a/netty/src/main/java/io/grpc/netty/InternalNettySocketSupport.java b/netty/src/main/java/io/grpc/netty/InternalNettySocketSupport.java
new file mode 100644
index 0000000..2c34cfe
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/InternalNettySocketSupport.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.Internal;
+import io.grpc.InternalChannelz.TcpInfo;
+import java.util.Map;
+
+/**
+ * An internal accessor. Do not use.
+ */
+@Internal
+public final class InternalNettySocketSupport {
+
+  public interface InternalHelper extends NettySocketSupport.Helper {
+    @Override
+    InternalNativeSocketOptions getNativeSocketOptions(io.netty.channel.Channel ch);
+  }
+
+  public static final class InternalNativeSocketOptions
+      extends NettySocketSupport.NativeSocketOptions {
+    public InternalNativeSocketOptions(TcpInfo tcpInfo, Map<String, String> otherInfo) {
+      super(tcpInfo, otherInfo);
+    }
+  }
+
+  public static void setHelper(InternalHelper helper) {
+    NettySocketSupport.setHelper(helper);
+  }
+
+  private InternalNettySocketSupport() {}
+}
diff --git a/netty/src/main/java/io/grpc/netty/JettyTlsUtil.java b/netty/src/main/java/io/grpc/netty/JettyTlsUtil.java
new file mode 100644
index 0000000..ef31756
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/JettyTlsUtil.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Utility class for determining support for Jetty TLS ALPN/NPN.
+ */
+final class JettyTlsUtil {
+  private JettyTlsUtil() {
+  }
+
+  private static Throwable jettyAlpnUnavailabilityCause;
+  private static Throwable jettyNpnUnavailabilityCause;
+
+  private static class Java9AlpnUnavailabilityCauseHolder {
+
+    static final Throwable cause = checkAlpnAvailability();
+
+    static Throwable checkAlpnAvailability() {
+      try {
+        SSLContext context = SSLContext.getInstance("TLS");
+        context.init(null, null, null);
+        SSLEngine engine = context.createSSLEngine();
+        Method getApplicationProtocol =
+            AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
+              @Override
+              public Method run() throws Exception {
+                return SSLEngine.class.getMethod("getApplicationProtocol");
+              }
+            });
+        getApplicationProtocol.invoke(engine);
+        return null;
+      } catch (Throwable t) {
+        return t;
+      }
+    }
+  }
+
+  /**
+   * Indicates whether or not the Jetty ALPN jar is installed in the boot classloader.
+   */
+  static synchronized boolean isJettyAlpnConfigured() {
+    try {
+      Class.forName("org.eclipse.jetty.alpn.ALPN", true, null);
+      return true;
+    } catch (ClassNotFoundException e) {
+      jettyAlpnUnavailabilityCause = e;
+      return false;
+    }
+  }
+
+  static synchronized Throwable getJettyAlpnUnavailabilityCause() {
+    // This case should be unlikely
+    if (jettyAlpnUnavailabilityCause == null) {
+      boolean discard = isJettyAlpnConfigured();
+    }
+    return jettyAlpnUnavailabilityCause;
+  }
+
+  /**
+   * Indicates whether or not the Jetty NPN jar is installed in the boot classloader.
+   */
+  static synchronized boolean isJettyNpnConfigured() {
+    try {
+      Class.forName("org.eclipse.jetty.npn.NextProtoNego", true, null);
+      return true;
+    } catch (ClassNotFoundException e) {
+      jettyNpnUnavailabilityCause = e;
+      return false;
+    }
+  }
+
+  static synchronized Throwable getJettyNpnUnavailabilityCause() {
+    // This case should be unlikely
+    if (jettyNpnUnavailabilityCause == null) {
+      boolean discard = isJettyNpnConfigured();
+    }
+    return jettyNpnUnavailabilityCause;
+  }
+
+  /**
+   * Indicates whether Java 9 ALPN is available.
+   */
+  static boolean isJava9AlpnAvailable() {
+    return getJava9AlpnUnavailabilityCause() == null;
+  }
+
+  static Throwable getJava9AlpnUnavailabilityCause() {
+    return Java9AlpnUnavailabilityCauseHolder.cause;
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/KeepAliveEnforcer.java b/netty/src/main/java/io/grpc/netty/KeepAliveEnforcer.java
new file mode 100644
index 0000000..43671fb
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/KeepAliveEnforcer.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.CheckReturnValue;
+
+/** Monitors the client's PING usage to make sure the rate is permitted. */
+class KeepAliveEnforcer {
+  @VisibleForTesting
+  static final int MAX_PING_STRIKES = 2;
+  @VisibleForTesting
+  static final long IMPLICIT_PERMIT_TIME_NANOS = TimeUnit.HOURS.toNanos(2);
+
+  private final boolean permitWithoutCalls;
+  private final long minTimeNanos;
+  private final Ticker ticker;
+  private final long epoch;
+
+  private long lastValidPingTime;
+  private boolean hasOutstandingCalls;
+  private int pingStrikes;
+
+  public KeepAliveEnforcer(boolean permitWithoutCalls, long minTime, TimeUnit unit) {
+    this(permitWithoutCalls, minTime, unit, SystemTicker.INSTANCE);
+  }
+
+  @VisibleForTesting
+  KeepAliveEnforcer(boolean permitWithoutCalls, long minTime, TimeUnit unit, Ticker ticker) {
+    Preconditions.checkArgument(minTime >= 0, "minTime must be non-negative");
+
+    this.permitWithoutCalls = permitWithoutCalls;
+    this.minTimeNanos = Math.min(unit.toNanos(minTime), IMPLICIT_PERMIT_TIME_NANOS);
+    this.ticker = ticker;
+    this.epoch = ticker.nanoTime();
+    lastValidPingTime = epoch;
+  }
+
+  /** Returns {@code false} when client is misbehaving and should be disconnected. */
+  @CheckReturnValue
+  public boolean pingAcceptable() {
+    long now = ticker.nanoTime();
+    boolean valid;
+    if (!hasOutstandingCalls && !permitWithoutCalls) {
+      valid = compareNanos(lastValidPingTime + IMPLICIT_PERMIT_TIME_NANOS, now) <= 0;
+    } else {
+      valid = compareNanos(lastValidPingTime + minTimeNanos, now) <= 0;
+    }
+    if (!valid) {
+      pingStrikes++;
+      return !(pingStrikes > MAX_PING_STRIKES);
+    } else {
+      lastValidPingTime = now;
+      return true;
+    }
+  }
+
+  /**
+   * Reset any counters because PINGs are allowed in response to something sent. Typically called
+   * when sending HEADERS and DATA frames.
+   */
+  public void resetCounters() {
+    lastValidPingTime = epoch;
+    pingStrikes = 0;
+  }
+
+  /** There are outstanding RPCs on the transport. */
+  public void onTransportActive() {
+    hasOutstandingCalls = true;
+  }
+
+  /** There are no outstanding RPCs on the transport. */
+  public void onTransportIdle() {
+    hasOutstandingCalls = false;
+  }
+
+  /**
+   * Positive when time1 is greater; negative when time2 is greater; 0 when equal. It is important
+   * to use something like this instead of directly comparing nano times. See {@link
+   * System#nanoTime}.
+   */
+  private static long compareNanos(long time1, long time2) {
+    // Possibility of overflow/underflow is on purpose and necessary for correctness
+    return time1 - time2;
+  }
+
+  @VisibleForTesting
+  interface Ticker {
+    long nanoTime();
+  }
+
+  @VisibleForTesting
+  static class SystemTicker implements Ticker {
+    public static final SystemTicker INSTANCE = new SystemTicker();
+
+    @Override
+    public long nanoTime() {
+      return System.nanoTime();
+    }
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/MaxConnectionIdleManager.java b/netty/src/main/java/io/grpc/netty/MaxConnectionIdleManager.java
new file mode 100644
index 0000000..964ae44
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/MaxConnectionIdleManager.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.internal.LogExceptionRunnable;
+import io.netty.channel.ChannelHandlerContext;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.CheckForNull;
+
+/**
+ * Monitors connection idle time; shutdowns the connection if the max connection idle is reached.
+ */
+abstract class MaxConnectionIdleManager {
+  private static final Ticker systemTicker = new Ticker() {
+    @Override
+    public long nanoTime() {
+      return System.nanoTime();
+    }
+  };
+
+  private final long maxConnectionIdleInNanos;
+  private final Ticker ticker;
+
+  @CheckForNull
+  private ScheduledFuture<?> shutdownFuture;
+  private Runnable shutdownTask;
+  private ScheduledExecutorService scheduler;
+  private long nextIdleMonitorTime;
+  private boolean shutdownDelayed;
+  private boolean isActive;
+
+  MaxConnectionIdleManager(long maxConnectionIdleInNanos) {
+    this(maxConnectionIdleInNanos, systemTicker);
+  }
+
+  @VisibleForTesting
+  MaxConnectionIdleManager(long maxConnectionIdleInNanos, Ticker ticker) {
+    this.maxConnectionIdleInNanos = maxConnectionIdleInNanos;
+    this.ticker = ticker;
+  }
+
+  /** A {@link NettyServerHandler} was added to the transport. */
+  void start(ChannelHandlerContext ctx) {
+    start(ctx, ctx.executor());
+  }
+
+  @VisibleForTesting
+  void start(final ChannelHandlerContext ctx, final ScheduledExecutorService scheduler) {
+    this.scheduler = scheduler;
+    nextIdleMonitorTime = ticker.nanoTime() + maxConnectionIdleInNanos;
+
+    shutdownTask = new LogExceptionRunnable(new Runnable() {
+      @Override
+      public void run() {
+        if (shutdownDelayed) {
+          if (!isActive) {
+            // delay shutdown
+            shutdownFuture = scheduler.schedule(
+                shutdownTask, nextIdleMonitorTime - ticker.nanoTime(), TimeUnit.NANOSECONDS);
+            shutdownDelayed = false;
+          }
+          // if isActive, exit. Will schedule a new shutdownFuture once onTransportIdle
+        } else {
+          close(ctx);
+          shutdownFuture = null;
+        }
+      }
+    });
+
+    shutdownFuture =
+        scheduler.schedule(shutdownTask, maxConnectionIdleInNanos, TimeUnit.NANOSECONDS);
+  }
+
+  /**
+   * Closes the connection by sending GO_AWAY with status code NO_ERROR and ASCII debug data
+   * max_idle and then doing the graceful connection termination.
+   */
+  abstract void close(ChannelHandlerContext ctx);
+
+  /** There are outstanding RPCs on the transport. */
+  void onTransportActive() {
+    isActive = true;
+    shutdownDelayed = true;
+  }
+
+  /** There are no outstanding RPCs on the transport. */
+  void onTransportIdle() {
+    isActive = false;
+    if (shutdownFuture == null) {
+      return;
+    }
+    if (shutdownFuture.isDone()) {
+      shutdownDelayed = false;
+      shutdownFuture = scheduler
+          .schedule(shutdownTask, maxConnectionIdleInNanos, TimeUnit.NANOSECONDS);
+    } else {
+      nextIdleMonitorTime = ticker.nanoTime() + maxConnectionIdleInNanos;
+    }
+  }
+
+  /** Transport is being terminated. */
+  void onTransportTermination() {
+    if (shutdownFuture != null) {
+      shutdownFuture.cancel(false);
+      shutdownFuture = null;
+    }
+  }
+
+  @VisibleForTesting
+  interface Ticker {
+    long nanoTime();
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NegotiationType.java b/netty/src/main/java/io/grpc/netty/NegotiationType.java
new file mode 100644
index 0000000..e6d86b9
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NegotiationType.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.ExperimentalApi;
+
+/**
+ * Identifies the negotiation used for starting up HTTP/2.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784")
+public enum NegotiationType {
+  /**
+   * Uses TLS ALPN/NPN negotiation, assumes an SSL connection.
+   */
+  TLS,
+
+  /**
+   * Use the HTTP UPGRADE protocol for a plaintext (non-SSL) upgrade from HTTP/1.1 to HTTP/2.
+   */
+  PLAINTEXT_UPGRADE,
+
+  /**
+   * Just assume the connection is plaintext (non-SSL) and the remote endpoint supports HTTP/2
+   * directly without an upgrade.
+   */
+  PLAINTEXT
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java b/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java
new file mode 100644
index 0000000..633cb32
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyChannelBuilder.java
@@ -0,0 +1,611 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
+import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIME_NANOS;
+import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import io.grpc.Attributes;
+import io.grpc.ExperimentalApi;
+import io.grpc.Internal;
+import io.grpc.NameResolver;
+import io.grpc.internal.AbstractManagedChannelImplBuilder;
+import io.grpc.internal.AtomicBackoff;
+import io.grpc.internal.ClientTransportFactory;
+import io.grpc.internal.ConnectionClientTransport;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.KeepAliveManager;
+import io.grpc.internal.ProxyParameters;
+import io.grpc.internal.SharedResourceHolder;
+import io.grpc.internal.TransportTracer;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.ssl.SslContext;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nullable;
+import javax.net.ssl.SSLException;
+
+/**
+ * A builder to help simplify construction of channels using the Netty transport.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784")
+@CanIgnoreReturnValue
+public final class NettyChannelBuilder
+    extends AbstractManagedChannelImplBuilder<NettyChannelBuilder> {
+  public static final int DEFAULT_FLOW_CONTROL_WINDOW = 1048576; // 1MiB
+
+  private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L);
+
+  private final Map<ChannelOption<?>, Object> channelOptions =
+      new HashMap<ChannelOption<?>, Object>();
+
+  private NegotiationType negotiationType = NegotiationType.TLS;
+  private OverrideAuthorityChecker authorityChecker;
+  private Class<? extends Channel> channelType = NioSocketChannel.class;
+
+  @Nullable
+  private EventLoopGroup eventLoopGroup;
+  private SslContext sslContext;
+  private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW;
+  private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
+  private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
+  private long keepAliveTimeoutNanos = DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
+  private boolean keepAliveWithoutCalls;
+  private TransportCreationParamsFilterFactory dynamicParamsFactory;
+
+  /**
+   * Creates a new builder with the given server address. This factory method is primarily intended
+   * for using Netty Channel types other than SocketChannel. {@link #forAddress(String, int)} should
+   * generally be preferred over this method, since that API permits delaying DNS lookups and
+   * noticing changes to DNS.
+   */
+  @CheckReturnValue
+  public static NettyChannelBuilder forAddress(SocketAddress serverAddress) {
+    return new NettyChannelBuilder(serverAddress);
+  }
+
+  /**
+   * Creates a new builder with the given host and port.
+   */
+  @CheckReturnValue
+  public static NettyChannelBuilder forAddress(String host, int port) {
+    return new NettyChannelBuilder(host, port);
+  }
+
+  /**
+   * Creates a new builder with the given target string that will be resolved by
+   * {@link io.grpc.NameResolver}.
+   */
+  @CheckReturnValue
+  public static NettyChannelBuilder forTarget(String target) {
+    return new NettyChannelBuilder(target);
+  }
+
+  @CheckReturnValue
+  NettyChannelBuilder(String host, int port) {
+    this(GrpcUtil.authorityFromHostAndPort(host, port));
+  }
+
+  @CheckReturnValue
+  NettyChannelBuilder(String target) {
+    super(target);
+  }
+
+  @CheckReturnValue
+  NettyChannelBuilder(SocketAddress address) {
+    super(address, getAuthorityFromAddress(address));
+  }
+
+  @CheckReturnValue
+  private static String getAuthorityFromAddress(SocketAddress address) {
+    if (address instanceof InetSocketAddress) {
+      InetSocketAddress inetAddress = (InetSocketAddress) address;
+      return GrpcUtil.authorityFromHostAndPort(inetAddress.getHostString(), inetAddress.getPort());
+    } else {
+      return address.toString();
+    }
+  }
+
+  /**
+   * Specifies the channel type to use, by default we use {@link NioSocketChannel}.
+   */
+  public NettyChannelBuilder channelType(Class<? extends Channel> channelType) {
+    this.channelType = Preconditions.checkNotNull(channelType, "channelType");
+    return this;
+  }
+
+  /**
+   * Specifies a channel option. As the underlying channel as well as network implementation may
+   * ignore this value applications should consider it a hint.
+   */
+  public <T> NettyChannelBuilder withOption(ChannelOption<T> option, T value) {
+    channelOptions.put(option, value);
+    return this;
+  }
+
+  /**
+   * Sets the negotiation type for the HTTP/2 connection.
+   *
+   * <p>Default: <code>TLS</code>
+   */
+  public NettyChannelBuilder negotiationType(NegotiationType type) {
+    negotiationType = type;
+    return this;
+  }
+
+  /**
+   * Provides an EventGroupLoop to be used by the netty transport.
+   *
+   * <p>It's an optional parameter. If the user has not provided an EventGroupLoop when the channel
+   * is built, the builder will use the default one which is static.
+   *
+   * <p>The channel won't take ownership of the given EventLoopGroup. It's caller's responsibility
+   * to shut it down when it's desired.
+   */
+  public NettyChannelBuilder eventLoopGroup(@Nullable EventLoopGroup eventLoopGroup) {
+    this.eventLoopGroup = eventLoopGroup;
+    return this;
+  }
+
+  /**
+   * SSL/TLS context to use instead of the system default. It must have been configured with {@link
+   * GrpcSslContexts}, but options could have been overridden.
+   */
+  public NettyChannelBuilder sslContext(SslContext sslContext) {
+    if (sslContext != null) {
+      checkArgument(sslContext.isClient(),
+          "Server SSL context can not be used for client channel");
+      GrpcSslContexts.ensureAlpnAndH2Enabled(sslContext.applicationProtocolNegotiator());
+    }
+    this.sslContext = sslContext;
+    return this;
+  }
+
+  /**
+   * Sets the flow control window in bytes. If not called, the default value
+   * is {@link #DEFAULT_FLOW_CONTROL_WINDOW}).
+   */
+  public NettyChannelBuilder flowControlWindow(int flowControlWindow) {
+    checkArgument(flowControlWindow > 0, "flowControlWindow must be positive");
+    this.flowControlWindow = flowControlWindow;
+    return this;
+  }
+
+  /**
+   * Sets the max message size.
+   *
+   * @deprecated Use {@link #maxInboundMessageSize} instead
+   */
+  @Deprecated
+  public NettyChannelBuilder maxMessageSize(int maxMessageSize) {
+    maxInboundMessageSize(maxMessageSize);
+    return this;
+  }
+
+  /**
+   * Sets the maximum size of header list allowed to be received. This is cumulative size of the
+   * headers with some overhead, as defined for
+   * <a href="http://httpwg.org/specs/rfc7540.html#rfc.section.6.5.2">
+   * HTTP/2's SETTINGS_MAX_HEADER_LIST_SIZE</a>. The default is 8 KiB.
+   */
+  public NettyChannelBuilder maxHeaderListSize(int maxHeaderListSize) {
+    checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be > 0");
+    this.maxHeaderListSize = maxHeaderListSize;
+    return this;
+  }
+
+  /**
+   * Equivalent to using {@link #negotiationType(NegotiationType)} with {@code PLAINTEXT} or
+   * {@code PLAINTEXT_UPGRADE}.
+   *
+   * @deprecated use {@link #usePlaintext()} instead.
+   */
+  @Override
+  @Deprecated
+  public NettyChannelBuilder usePlaintext(boolean skipNegotiation) {
+    if (skipNegotiation) {
+      negotiationType(NegotiationType.PLAINTEXT);
+    } else {
+      negotiationType(NegotiationType.PLAINTEXT_UPGRADE);
+    }
+    return this;
+  }
+
+  /**
+   * Equivalent to using {@link #negotiationType(NegotiationType)} with {@code PLAINTEXT}.
+   */
+  @Override
+  public NettyChannelBuilder usePlaintext() {
+    negotiationType(NegotiationType.PLAINTEXT);
+    return this;
+  }
+
+  /**
+   * Equivalent to using {@link #negotiationType(NegotiationType)} with {@code TLS}.
+   */
+  @Override
+  public NettyChannelBuilder useTransportSecurity() {
+    negotiationType(NegotiationType.TLS);
+    return this;
+  }
+
+  /**
+   * Enable keepalive with default delay and timeout.
+   *
+   * @deprecated Please use {@link #keepAliveTime} and {@link #keepAliveTimeout} instead
+   */
+  @Deprecated
+  public final NettyChannelBuilder enableKeepAlive(boolean enable) {
+    if (enable) {
+      return keepAliveTime(DEFAULT_KEEPALIVE_TIME_NANOS, TimeUnit.NANOSECONDS);
+    }
+    return keepAliveTime(KEEPALIVE_TIME_NANOS_DISABLED, TimeUnit.NANOSECONDS);
+  }
+
+  /**
+   * Enable keepalive with custom delay and timeout.
+   *
+   * @deprecated Please use {@link #keepAliveTime} and {@link #keepAliveTimeout} instead
+   */
+  @Deprecated
+  public final NettyChannelBuilder enableKeepAlive(boolean enable, long keepAliveTime,
+      TimeUnit delayUnit, long keepAliveTimeout, TimeUnit timeoutUnit) {
+    if (enable) {
+      return keepAliveTime(keepAliveTime, delayUnit)
+          .keepAliveTimeout(keepAliveTimeout, timeoutUnit);
+    }
+    return keepAliveTime(KEEPALIVE_TIME_NANOS_DISABLED, TimeUnit.NANOSECONDS);
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * @since 1.3.0
+   */
+  @Override
+  public NettyChannelBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
+    checkArgument(keepAliveTime > 0L, "keepalive time must be positive");
+    keepAliveTimeNanos = timeUnit.toNanos(keepAliveTime);
+    keepAliveTimeNanos = KeepAliveManager.clampKeepAliveTimeInNanos(keepAliveTimeNanos);
+    if (keepAliveTimeNanos >= AS_LARGE_AS_INFINITE) {
+      // Bump keepalive time to infinite. This disables keepalive.
+      keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
+    }
+    return this;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * @since 1.3.0
+   */
+  @Override
+  public NettyChannelBuilder keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
+    checkArgument(keepAliveTimeout > 0L, "keepalive timeout must be positive");
+    keepAliveTimeoutNanos = timeUnit.toNanos(keepAliveTimeout);
+    keepAliveTimeoutNanos = KeepAliveManager.clampKeepAliveTimeoutInNanos(keepAliveTimeoutNanos);
+    return this;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * @since 1.3.0
+   */
+  @Override
+  public NettyChannelBuilder keepAliveWithoutCalls(boolean enable) {
+    keepAliveWithoutCalls = enable;
+    return this;
+  }
+
+  @Override
+  @CheckReturnValue
+  @Internal
+  protected ClientTransportFactory buildTransportFactory() {
+    TransportCreationParamsFilterFactory transportCreationParamsFilterFactory =
+        dynamicParamsFactory;
+    if (transportCreationParamsFilterFactory == null) {
+      SslContext localSslContext = sslContext;
+      if (negotiationType == NegotiationType.TLS && localSslContext == null) {
+        try {
+          localSslContext = GrpcSslContexts.forClient().build();
+        } catch (SSLException ex) {
+          throw new RuntimeException(ex);
+        }
+      }
+      ProtocolNegotiator negotiator =
+          createProtocolNegotiatorByType(negotiationType, localSslContext);
+      transportCreationParamsFilterFactory =
+          new DefaultNettyTransportCreationParamsFilterFactory(negotiator);
+    }
+    return new NettyTransportFactory(
+        transportCreationParamsFilterFactory, channelType, channelOptions,
+        eventLoopGroup, flowControlWindow, maxInboundMessageSize(),
+        maxHeaderListSize, keepAliveTimeNanos, keepAliveTimeoutNanos, keepAliveWithoutCalls,
+        transportTracerFactory.create());
+  }
+
+  @Override
+  @CheckReturnValue
+  protected Attributes getNameResolverParams() {
+    int defaultPort;
+    switch (negotiationType) {
+      case PLAINTEXT:
+      case PLAINTEXT_UPGRADE:
+        defaultPort = GrpcUtil.DEFAULT_PORT_PLAINTEXT;
+        break;
+      case TLS:
+        defaultPort = GrpcUtil.DEFAULT_PORT_SSL;
+        break;
+      default:
+        throw new AssertionError(negotiationType + " not handled");
+    }
+    return Attributes.newBuilder()
+        .set(NameResolver.Factory.PARAMS_DEFAULT_PORT, defaultPort).build();
+  }
+
+  void overrideAuthorityChecker(@Nullable OverrideAuthorityChecker authorityChecker) {
+    this.authorityChecker = authorityChecker;
+  }
+
+  @VisibleForTesting
+  @CheckReturnValue
+  static ProtocolNegotiator createProtocolNegotiatorByType(
+      NegotiationType negotiationType,
+      SslContext sslContext) {
+    switch (negotiationType) {
+      case PLAINTEXT:
+        return ProtocolNegotiators.plaintext();
+      case PLAINTEXT_UPGRADE:
+        return ProtocolNegotiators.plaintextUpgrade();
+      case TLS:
+        return ProtocolNegotiators.tls(sslContext);
+      default:
+        throw new IllegalArgumentException("Unsupported negotiationType: " + negotiationType);
+    }
+  }
+
+  @CheckReturnValue
+  interface OverrideAuthorityChecker {
+    String checkAuthority(String authority);
+  }
+
+  @Override
+  @CheckReturnValue
+  @Internal
+  protected String checkAuthority(String authority) {
+    if (authorityChecker != null) {
+      return authorityChecker.checkAuthority(authority);
+    }
+    return super.checkAuthority(authority);
+  }
+
+  void setDynamicParamsFactory(TransportCreationParamsFilterFactory factory) {
+    this.dynamicParamsFactory = checkNotNull(factory, "factory");
+  }
+
+  @Override
+  protected void setTracingEnabled(boolean value) {
+    super.setTracingEnabled(value);
+  }
+
+  @Override
+  protected void setStatsEnabled(boolean value) {
+    super.setStatsEnabled(value);
+  }
+
+  @Override
+  protected void setStatsRecordStartedRpcs(boolean value) {
+    super.setStatsRecordStartedRpcs(value);
+  }
+
+  @VisibleForTesting
+  NettyChannelBuilder setTransportTracerFactory(TransportTracer.Factory transportTracerFactory) {
+    this.transportTracerFactory = transportTracerFactory;
+    return this;
+  }
+
+  interface TransportCreationParamsFilterFactory {
+    @CheckReturnValue
+    TransportCreationParamsFilter create(
+        SocketAddress targetServerAddress,
+        String authority,
+        @Nullable String userAgent,
+        @Nullable ProxyParameters proxy);
+  }
+
+  @CheckReturnValue
+  interface TransportCreationParamsFilter {
+    SocketAddress getTargetServerAddress();
+
+    String getAuthority();
+
+    @Nullable String getUserAgent();
+
+    ProtocolNegotiator getProtocolNegotiator();
+  }
+
+  /**
+   * Creates Netty transports. Exposed for internal use, as it should be private.
+   */
+  @CheckReturnValue
+  private static final class NettyTransportFactory implements ClientTransportFactory {
+    private final TransportCreationParamsFilterFactory transportCreationParamsFilterFactory;
+    private final Class<? extends Channel> channelType;
+    private final Map<ChannelOption<?>, ?> channelOptions;
+    private final EventLoopGroup group;
+    private final boolean usingSharedGroup;
+    private final int flowControlWindow;
+    private final int maxMessageSize;
+    private final int maxHeaderListSize;
+    private final AtomicBackoff keepAliveTimeNanos;
+    private final long keepAliveTimeoutNanos;
+    private final boolean keepAliveWithoutCalls;
+    private final TransportTracer transportTracer;
+
+    private boolean closed;
+
+    NettyTransportFactory(TransportCreationParamsFilterFactory transportCreationParamsFilterFactory,
+        Class<? extends Channel> channelType, Map<ChannelOption<?>, ?> channelOptions,
+        EventLoopGroup group, int flowControlWindow, int maxMessageSize, int maxHeaderListSize,
+        long keepAliveTimeNanos, long keepAliveTimeoutNanos, boolean keepAliveWithoutCalls,
+        TransportTracer transportTracer) {
+      this.transportCreationParamsFilterFactory = transportCreationParamsFilterFactory;
+      this.channelType = channelType;
+      this.channelOptions = new HashMap<ChannelOption<?>, Object>(channelOptions);
+      this.flowControlWindow = flowControlWindow;
+      this.maxMessageSize = maxMessageSize;
+      this.maxHeaderListSize = maxHeaderListSize;
+      this.keepAliveTimeNanos = new AtomicBackoff("keepalive time nanos", keepAliveTimeNanos);
+      this.keepAliveTimeoutNanos = keepAliveTimeoutNanos;
+      this.keepAliveWithoutCalls = keepAliveWithoutCalls;
+      this.transportTracer = transportTracer;
+
+      usingSharedGroup = group == null;
+      if (usingSharedGroup) {
+        // The group was unspecified, using the shared group.
+        this.group = SharedResourceHolder.get(Utils.DEFAULT_WORKER_EVENT_LOOP_GROUP);
+      } else {
+        this.group = group;
+      }
+    }
+
+    @Override
+    public ConnectionClientTransport newClientTransport(
+        SocketAddress serverAddress, ClientTransportOptions options) {
+      checkState(!closed, "The transport factory is closed.");
+
+      TransportCreationParamsFilter dparams =
+          transportCreationParamsFilterFactory.create(
+              serverAddress,
+              options.getAuthority(),
+              options.getUserAgent(),
+              options.getProxyParameters());
+
+      final AtomicBackoff.State keepAliveTimeNanosState = keepAliveTimeNanos.getState();
+      Runnable tooManyPingsRunnable = new Runnable() {
+        @Override
+        public void run() {
+          keepAliveTimeNanosState.backoff();
+        }
+      };
+      NettyClientTransport transport = new NettyClientTransport(
+          dparams.getTargetServerAddress(), channelType, channelOptions, group,
+          dparams.getProtocolNegotiator(), flowControlWindow,
+          maxMessageSize, maxHeaderListSize, keepAliveTimeNanosState.get(), keepAliveTimeoutNanos,
+          keepAliveWithoutCalls, dparams.getAuthority(), dparams.getUserAgent(),
+          tooManyPingsRunnable, transportTracer, options.getEagAttributes());
+      return transport;
+    }
+
+    @Override
+    public ScheduledExecutorService getScheduledExecutorService() {
+      return group;
+    }
+
+    @Override
+    public void close() {
+      if (closed) {
+        return;
+      }
+      closed = true;
+
+      if (usingSharedGroup) {
+        SharedResourceHolder.release(Utils.DEFAULT_WORKER_EVENT_LOOP_GROUP, group);
+      }
+    }
+  }
+
+  private static final class DefaultNettyTransportCreationParamsFilterFactory
+      implements TransportCreationParamsFilterFactory {
+    final ProtocolNegotiator negotiator;
+
+    DefaultNettyTransportCreationParamsFilterFactory(ProtocolNegotiator negotiator) {
+      this.negotiator = negotiator;
+    }
+
+    @Override
+    public TransportCreationParamsFilter create(
+        SocketAddress targetServerAddress,
+        String authority,
+        String userAgent,
+        ProxyParameters proxyParams) {
+      ProtocolNegotiator localNegotiator = negotiator;
+      if (proxyParams != null) {
+        localNegotiator = ProtocolNegotiators.httpProxy(
+            proxyParams.proxyAddress, proxyParams.username, proxyParams.password, negotiator);
+      }
+      return new DynamicNettyTransportParams(
+          targetServerAddress, authority, userAgent, localNegotiator);
+    }
+  }
+
+  @CheckReturnValue
+  private static final class DynamicNettyTransportParams implements TransportCreationParamsFilter {
+
+    private final SocketAddress targetServerAddress;
+    private final String authority;
+    @Nullable private final String userAgent;
+    private final ProtocolNegotiator protocolNegotiator;
+
+    private DynamicNettyTransportParams(
+        SocketAddress targetServerAddress,
+        String authority,
+        String userAgent,
+        ProtocolNegotiator protocolNegotiator) {
+      this.targetServerAddress = targetServerAddress;
+      this.authority = authority;
+      this.userAgent = userAgent;
+      this.protocolNegotiator = protocolNegotiator;
+    }
+
+    @Override
+    public SocketAddress getTargetServerAddress() {
+      return targetServerAddress;
+    }
+
+    @Override
+    public String getAuthority() {
+      return authority;
+    }
+
+    @Override
+    public String getUserAgent() {
+      return userAgent;
+    }
+
+    @Override
+    public ProtocolNegotiator getProtocolNegotiator() {
+      return protocolNegotiator;
+    }
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyChannelProvider.java b/netty/src/main/java/io/grpc/netty/NettyChannelProvider.java
new file mode 100644
index 0000000..95f58c0
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyChannelProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.Internal;
+import io.grpc.ManagedChannelProvider;
+
+/** Provider for {@link NettyChannelBuilder} instances. */
+@Internal
+public final class NettyChannelProvider extends ManagedChannelProvider {
+  @Override
+  public boolean isAvailable() {
+    return true;
+  }
+
+  @Override
+  public int priority() {
+    return 5;
+  }
+
+  @Override
+  public NettyChannelBuilder builderForAddress(String name, int port) {
+    return NettyChannelBuilder.forAddress(name, port);
+  }
+
+  @Override
+  public NettyChannelBuilder builderForTarget(String target) {
+    return NettyChannelBuilder.forTarget(target);
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyClientHandler.java b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java
new file mode 100644
index 0000000..e059af9
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyClientHandler.java
@@ -0,0 +1,806 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static io.netty.handler.codec.http2.DefaultHttp2LocalFlowController.DEFAULT_WINDOW_UPDATE_RATIO;
+import static io.netty.util.CharsetUtil.UTF_8;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Supplier;
+import io.grpc.Attributes;
+import io.grpc.InternalChannelz;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.StatusException;
+import io.grpc.internal.ClientStreamListener.RpcProgress;
+import io.grpc.internal.ClientTransport.PingCallback;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.Http2Ping;
+import io.grpc.internal.KeepAliveManager;
+import io.grpc.internal.TransportTracer;
+import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2ClientHeadersDecoder;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.codec.http2.DefaultHttp2Connection;
+import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder;
+import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder;
+import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
+import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
+import io.netty.handler.codec.http2.DefaultHttp2LocalFlowController;
+import io.netty.handler.codec.http2.DefaultHttp2RemoteFlowController;
+import io.netty.handler.codec.http2.Http2Connection;
+import io.netty.handler.codec.http2.Http2ConnectionAdapter;
+import io.netty.handler.codec.http2.Http2ConnectionDecoder;
+import io.netty.handler.codec.http2.Http2Error;
+import io.netty.handler.codec.http2.Http2Exception;
+import io.netty.handler.codec.http2.Http2FlowController;
+import io.netty.handler.codec.http2.Http2FrameAdapter;
+import io.netty.handler.codec.http2.Http2FrameLogger;
+import io.netty.handler.codec.http2.Http2FrameReader;
+import io.netty.handler.codec.http2.Http2FrameWriter;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.handler.codec.http2.Http2HeadersDecoder;
+import io.netty.handler.codec.http2.Http2InboundFrameLogger;
+import io.netty.handler.codec.http2.Http2OutboundFrameLogger;
+import io.netty.handler.codec.http2.Http2Settings;
+import io.netty.handler.codec.http2.Http2Stream;
+import io.netty.handler.codec.http2.Http2StreamVisitor;
+import io.netty.handler.codec.http2.StreamBufferingEncoder;
+import io.netty.handler.codec.http2.WeightedFairQueueByteDistributor;
+import io.netty.handler.logging.LogLevel;
+import java.nio.channels.ClosedChannelException;
+import java.util.concurrent.Executor;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Client-side Netty handler for GRPC processing. All event handlers are executed entirely within
+ * the context of the Netty Channel thread.
+ */
+class NettyClientHandler extends AbstractNettyHandler {
+  private static final Logger logger = Logger.getLogger(NettyClientHandler.class.getName());
+
+  /**
+   * A message that simply passes through the channel without any real processing. It is useful to
+   * check if buffers have been drained and test the health of the channel in a single operation.
+   */
+  static final Object NOOP_MESSAGE = new Object();
+
+  /**
+   * Status used when the transport has exhausted the number of streams.
+   */
+  private static final Status EXHAUSTED_STREAMS_STATUS =
+          Status.UNAVAILABLE.withDescription("Stream IDs have been exhausted");
+  private static final long USER_PING_PAYLOAD = 1111;
+
+  private final Http2Connection.PropertyKey streamKey;
+  private final ClientTransportLifecycleManager lifecycleManager;
+  private final KeepAliveManager keepAliveManager;
+  // Returns new unstarted stopwatches
+  private final Supplier<Stopwatch> stopwatchFactory;
+  private final TransportTracer transportTracer;
+  private final Attributes eagAttributes;
+  private final String authority;
+  private WriteQueue clientWriteQueue;
+  private Http2Ping ping;
+  private Attributes attributes = Attributes.EMPTY;
+  private InternalChannelz.Security securityInfo;
+
+  static NettyClientHandler newHandler(
+      ClientTransportLifecycleManager lifecycleManager,
+      @Nullable KeepAliveManager keepAliveManager,
+      int flowControlWindow,
+      int maxHeaderListSize,
+      Supplier<Stopwatch> stopwatchFactory,
+      Runnable tooManyPingsRunnable,
+      TransportTracer transportTracer,
+      Attributes eagAttributes,
+      String authority) {
+    Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive");
+    Http2HeadersDecoder headersDecoder = new GrpcHttp2ClientHeadersDecoder(maxHeaderListSize);
+    Http2FrameReader frameReader = new DefaultHttp2FrameReader(headersDecoder);
+    Http2FrameWriter frameWriter = new DefaultHttp2FrameWriter();
+    Http2Connection connection = new DefaultHttp2Connection(false);
+    WeightedFairQueueByteDistributor dist = new WeightedFairQueueByteDistributor(connection);
+    dist.allocationQuantum(16 * 1024); // Make benchmarks fast again.
+    DefaultHttp2RemoteFlowController controller =
+        new DefaultHttp2RemoteFlowController(connection, dist);
+    connection.remote().flowController(controller);
+
+    return newHandler(
+        connection,
+        frameReader,
+        frameWriter,
+        lifecycleManager,
+        keepAliveManager,
+        flowControlWindow,
+        maxHeaderListSize,
+        stopwatchFactory,
+        tooManyPingsRunnable,
+        transportTracer,
+        eagAttributes,
+        authority);
+  }
+
+  @VisibleForTesting
+  static NettyClientHandler newHandler(
+      final Http2Connection connection,
+      Http2FrameReader frameReader,
+      Http2FrameWriter frameWriter,
+      ClientTransportLifecycleManager lifecycleManager,
+      KeepAliveManager keepAliveManager,
+      int flowControlWindow,
+      int maxHeaderListSize,
+      Supplier<Stopwatch> stopwatchFactory,
+      Runnable tooManyPingsRunnable,
+      TransportTracer transportTracer,
+      Attributes eagAttributes,
+      String authority) {
+    Preconditions.checkNotNull(connection, "connection");
+    Preconditions.checkNotNull(frameReader, "frameReader");
+    Preconditions.checkNotNull(lifecycleManager, "lifecycleManager");
+    Preconditions.checkArgument(flowControlWindow > 0, "flowControlWindow must be positive");
+    Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive");
+    Preconditions.checkNotNull(stopwatchFactory, "stopwatchFactory");
+    Preconditions.checkNotNull(tooManyPingsRunnable, "tooManyPingsRunnable");
+    Preconditions.checkNotNull(eagAttributes, "eagAttributes");
+    Preconditions.checkNotNull(authority, "authority");
+
+    Http2FrameLogger frameLogger = new Http2FrameLogger(LogLevel.DEBUG, NettyClientHandler.class);
+    frameReader = new Http2InboundFrameLogger(frameReader, frameLogger);
+    frameWriter = new Http2OutboundFrameLogger(frameWriter, frameLogger);
+
+    StreamBufferingEncoder encoder = new StreamBufferingEncoder(
+        new DefaultHttp2ConnectionEncoder(connection, frameWriter));
+
+    // Create the local flow controller configured to auto-refill the connection window.
+    connection.local().flowController(
+        new DefaultHttp2LocalFlowController(connection, DEFAULT_WINDOW_UPDATE_RATIO, true));
+
+    Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder,
+        frameReader);
+
+    transportTracer.setFlowControlWindowReader(new TransportTracer.FlowControlReader() {
+      final Http2FlowController local = connection.local().flowController();
+      final Http2FlowController remote = connection.remote().flowController();
+
+      @Override
+      public TransportTracer.FlowControlWindows read() {
+        return new TransportTracer.FlowControlWindows(
+            local.windowSize(connection.connectionStream()),
+            remote.windowSize(connection.connectionStream()));
+      }
+    });
+
+    Http2Settings settings = new Http2Settings();
+    settings.pushEnabled(false);
+    settings.initialWindowSize(flowControlWindow);
+    settings.maxConcurrentStreams(0);
+    settings.maxHeaderListSize(maxHeaderListSize);
+
+    return new NettyClientHandler(
+        decoder,
+        encoder,
+        settings,
+        lifecycleManager,
+        keepAliveManager,
+        stopwatchFactory,
+        tooManyPingsRunnable,
+        transportTracer,
+        eagAttributes,
+        authority);
+  }
+
+  private NettyClientHandler(
+      Http2ConnectionDecoder decoder,
+      StreamBufferingEncoder encoder,
+      Http2Settings settings,
+      ClientTransportLifecycleManager lifecycleManager,
+      KeepAliveManager keepAliveManager,
+      Supplier<Stopwatch> stopwatchFactory,
+      final Runnable tooManyPingsRunnable,
+      TransportTracer transportTracer,
+      Attributes eagAttributes,
+      String authority) {
+    super(/* channelUnused= */ null, decoder, encoder, settings);
+    this.lifecycleManager = lifecycleManager;
+    this.keepAliveManager = keepAliveManager;
+    this.stopwatchFactory = stopwatchFactory;
+    this.transportTracer = Preconditions.checkNotNull(transportTracer);
+    this.eagAttributes = eagAttributes;
+    this.authority = authority;
+
+    // Set the frame listener on the decoder.
+    decoder().frameListener(new FrameListener());
+
+    Http2Connection connection = encoder.connection();
+    streamKey = connection.newKey();
+
+    connection.addListener(new Http2ConnectionAdapter() {
+      @Override
+      public void onGoAwayReceived(int lastStreamId, long errorCode, ByteBuf debugData) {
+        byte[] debugDataBytes = ByteBufUtil.getBytes(debugData);
+        goingAway(statusFromGoAway(errorCode, debugDataBytes));
+        if (errorCode == Http2Error.ENHANCE_YOUR_CALM.code()) {
+          String data = new String(debugDataBytes, UTF_8);
+          logger.log(
+              Level.WARNING, "Received GOAWAY with ENHANCE_YOUR_CALM. Debug data: {1}", data);
+          if ("too_many_pings".equals(data)) {
+            tooManyPingsRunnable.run();
+          }
+        }
+      }
+
+      @Override
+      public void onStreamActive(Http2Stream stream) {
+        if (connection().numActiveStreams() != 1) {
+          return;
+        }
+
+        NettyClientHandler.this.lifecycleManager.notifyInUse(true);
+
+        if (NettyClientHandler.this.keepAliveManager != null) {
+          NettyClientHandler.this.keepAliveManager.onTransportActive();
+        }
+      }
+
+      @Override
+      public void onStreamClosed(Http2Stream stream) {
+        if (connection().numActiveStreams() != 0) {
+          return;
+        }
+
+        NettyClientHandler.this.lifecycleManager.notifyInUse(false);
+
+        if (NettyClientHandler.this.keepAliveManager != null) {
+          NettyClientHandler.this.keepAliveManager.onTransportIdle();
+        }
+      }
+    });
+  }
+
+  /**
+   * The protocol negotiation attributes, available once the protocol negotiation completes;
+   * otherwise returns {@code Attributes.EMPTY}.
+   */
+  Attributes getAttributes() {
+    return attributes;
+  }
+
+  /**
+   * Handler for commands sent from the stream.
+   */
+  @Override
+  public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
+          throws Exception {
+    if (msg instanceof CreateStreamCommand) {
+      createStream((CreateStreamCommand) msg, promise);
+    } else if (msg instanceof SendGrpcFrameCommand) {
+      sendGrpcFrame(ctx, (SendGrpcFrameCommand) msg, promise);
+    } else if (msg instanceof CancelClientStreamCommand) {
+      cancelStream(ctx, (CancelClientStreamCommand) msg, promise);
+    } else if (msg instanceof SendPingCommand) {
+      sendPingFrame(ctx, (SendPingCommand) msg, promise);
+    } else if (msg instanceof GracefulCloseCommand) {
+      gracefulClose(ctx, (GracefulCloseCommand) msg, promise);
+    } else if (msg instanceof ForcefulCloseCommand) {
+      forcefulClose(ctx, (ForcefulCloseCommand) msg, promise);
+    } else if (msg == NOOP_MESSAGE) {
+      ctx.write(Unpooled.EMPTY_BUFFER, promise);
+    } else {
+      throw new AssertionError("Write called for unexpected type: " + msg.getClass().getName());
+    }
+  }
+
+  void startWriteQueue(Channel channel) {
+    clientWriteQueue = new WriteQueue(channel);
+  }
+
+  WriteQueue getWriteQueue() {
+    return clientWriteQueue;
+  }
+
+  ClientTransportLifecycleManager getLifecycleManager() {
+    return lifecycleManager;
+  }
+
+  /**
+   * Returns the given processed bytes back to inbound flow control.
+   */
+  void returnProcessedBytes(Http2Stream stream, int bytes) {
+    try {
+      decoder().flowController().consumeBytes(stream, bytes);
+    } catch (Http2Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private void onHeadersRead(int streamId, Http2Headers headers, boolean endStream) {
+    NettyClientStream.TransportState stream = clientStream(requireHttp2Stream(streamId));
+    stream.transportHeadersReceived(headers, endStream);
+    if (keepAliveManager != null) {
+      keepAliveManager.onDataReceived();
+    }
+  }
+
+  /**
+   * Handler for an inbound HTTP/2 DATA frame.
+   */
+  private void onDataRead(int streamId, ByteBuf data, int padding, boolean endOfStream) {
+    flowControlPing().onDataRead(data.readableBytes(), padding);
+    NettyClientStream.TransportState stream = clientStream(requireHttp2Stream(streamId));
+    stream.transportDataReceived(data, endOfStream);
+    if (keepAliveManager != null) {
+      keepAliveManager.onDataReceived();
+    }
+  }
+
+
+  /**
+   * Handler for an inbound HTTP/2 RST_STREAM frame, terminating a stream.
+   */
+  private void onRstStreamRead(int streamId, long errorCode) {
+    NettyClientStream.TransportState stream = clientStream(connection().stream(streamId));
+    if (stream != null) {
+      Status status = GrpcUtil.Http2Error.statusForCode((int) errorCode)
+          .augmentDescription("Received Rst Stream");
+      stream.transportReportStatus(
+          status,
+          errorCode == Http2Error.REFUSED_STREAM.code()
+              ? RpcProgress.REFUSED : RpcProgress.PROCESSED,
+          false /*stop delivery*/,
+          new Metadata());
+      if (keepAliveManager != null) {
+        keepAliveManager.onDataReceived();
+      }
+    }
+  }
+
+  @Override
+  public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
+    logger.fine("Network channel being closed by the application.");
+    if (ctx.channel().isActive()) { // Ignore notification that the socket was closed
+      lifecycleManager.notifyShutdown(
+          Status.UNAVAILABLE.withDescription("Transport closed for unknown reason"));
+    }
+    super.close(ctx, promise);
+  }
+
+  /**
+   * Handler for the Channel shutting down.
+   */
+  @Override
+  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+    try {
+      logger.fine("Network channel is closed");
+      Status status = Status.UNAVAILABLE.withDescription("Network closed for unknown reason");
+      lifecycleManager.notifyShutdown(status);
+      try {
+        cancelPing(lifecycleManager.getShutdownThrowable());
+        // Report status to the application layer for any open streams
+        connection().forEachActiveStream(new Http2StreamVisitor() {
+          @Override
+          public boolean visit(Http2Stream stream) throws Http2Exception {
+            NettyClientStream.TransportState clientStream = clientStream(stream);
+            if (clientStream != null) {
+              clientStream.transportReportStatus(
+                  lifecycleManager.getShutdownStatus(), false, new Metadata());
+            }
+            return true;
+          }
+        });
+      } finally {
+        lifecycleManager.notifyTerminated(status);
+      }
+    } finally {
+      // Close any open streams
+      super.channelInactive(ctx);
+      if (keepAliveManager != null) {
+        keepAliveManager.onTransportTermination();
+      }
+    }
+  }
+
+  @Override
+  public void handleProtocolNegotiationCompleted(
+      Attributes attributes, InternalChannelz.Security securityInfo) {
+    this.attributes = attributes;
+    this.securityInfo = securityInfo;
+    super.handleProtocolNegotiationCompleted(attributes, securityInfo);
+  }
+
+  @Override
+  public Attributes getEagAttributes() {
+    return eagAttributes;
+  }
+
+  @Override
+  public String getAuthority() {
+    return authority;
+  }
+
+  InternalChannelz.Security getSecurityInfo() {
+    return securityInfo;
+  }
+
+
+  @Override
+  protected void onConnectionError(ChannelHandlerContext ctx,  boolean outbound, Throwable cause,
+      Http2Exception http2Ex) {
+    logger.log(Level.FINE, "Caught a connection error", cause);
+    lifecycleManager.notifyShutdown(Utils.statusFromThrowable(cause));
+    // Parent class will shut down the Channel
+    super.onConnectionError(ctx, outbound, cause, http2Ex);
+  }
+
+  @Override
+  protected void onStreamError(ChannelHandlerContext ctx, boolean outbound, Throwable cause,
+      Http2Exception.StreamException http2Ex) {
+    // Close the stream with a status that contains the cause.
+    NettyClientStream.TransportState stream = clientStream(connection().stream(http2Ex.streamId()));
+    if (stream != null) {
+      stream.transportReportStatus(Utils.statusFromThrowable(cause), false, new Metadata());
+    } else {
+      logger.log(Level.FINE, "Stream error for unknown stream " + http2Ex.streamId(), cause);
+    }
+
+    // Delegate to the base class to send a RST_STREAM.
+    super.onStreamError(ctx, outbound, cause, http2Ex);
+  }
+
+  @Override
+  protected boolean isGracefulShutdownComplete() {
+    // Only allow graceful shutdown to complete after all pending streams have completed.
+    return super.isGracefulShutdownComplete()
+        && ((StreamBufferingEncoder) encoder()).numBufferedStreams() == 0;
+  }
+
+  /**
+   * Attempts to create a new stream from the given command. If there are too many active streams,
+   * the creation request is queued.
+   */
+  private void createStream(CreateStreamCommand command, final ChannelPromise promise)
+          throws Exception {
+    if (lifecycleManager.getShutdownThrowable() != null) {
+      // The connection is going away (it is really the GOAWAY case),
+      // just terminate the stream now.
+      command.stream().transportReportStatus(
+          lifecycleManager.getShutdownStatus(), RpcProgress.REFUSED, true, new Metadata());
+      promise.setFailure(lifecycleManager.getShutdownThrowable());
+      return;
+    }
+
+    // Get the stream ID for the new stream.
+    final int streamId;
+    try {
+      streamId = incrementAndGetNextStreamId();
+    } catch (StatusException e) {
+      // Stream IDs have been exhausted for this connection. Fail the promise immediately.
+      promise.setFailure(e);
+
+      // Initiate a graceful shutdown if we haven't already.
+      if (!connection().goAwaySent()) {
+        logger.fine("Stream IDs have been exhausted for this connection. "
+                + "Initiating graceful shutdown of the connection.");
+        lifecycleManager.notifyShutdown(e.getStatus());
+        close(ctx(), ctx().newPromise());
+      }
+      return;
+    }
+
+    final NettyClientStream.TransportState stream = command.stream();
+    final Http2Headers headers = command.headers();
+    stream.setId(streamId);
+
+    // Create an intermediate promise so that we can intercept the failure reported back to the
+    // application.
+    ChannelPromise tempPromise = ctx().newPromise();
+    encoder().writeHeaders(ctx(), streamId, headers, 0, command.isGet(), tempPromise)
+            .addListener(new ChannelFutureListener() {
+              @Override
+              public void operationComplete(ChannelFuture future) throws Exception {
+                if (future.isSuccess()) {
+                  // The http2Stream will be null in case a stream buffered in the encoder
+                  // was canceled via RST_STREAM.
+                  Http2Stream http2Stream = connection().stream(streamId);
+                  if (http2Stream != null) {
+                    stream.getStatsTraceContext().clientOutboundHeaders();
+                    http2Stream.setProperty(streamKey, stream);
+
+                    // Attach the client stream to the HTTP/2 stream object as user data.
+                    stream.setHttp2Stream(http2Stream);
+                  }
+                  // Otherwise, the stream has been cancelled and Netty is sending a
+                  // RST_STREAM frame which causes it to purge pending writes from the
+                  // flow-controller and delete the http2Stream. The stream listener has already
+                  // been notified of cancellation so there is nothing to do.
+
+                  // Just forward on the success status to the original promise.
+                  promise.setSuccess();
+                } else {
+                  final Throwable cause = future.cause();
+                  if (cause instanceof StreamBufferingEncoder.Http2GoAwayException) {
+                    StreamBufferingEncoder.Http2GoAwayException e =
+                        (StreamBufferingEncoder.Http2GoAwayException) cause;
+                    lifecycleManager.notifyShutdown(statusFromGoAway(e.errorCode(), e.debugData()));
+                    promise.setFailure(lifecycleManager.getShutdownThrowable());
+                  } else {
+                    promise.setFailure(cause);
+                  }
+                }
+              }
+            });
+  }
+
+  /**
+   * Cancels this stream.
+   */
+  private void cancelStream(ChannelHandlerContext ctx, CancelClientStreamCommand cmd,
+      ChannelPromise promise) {
+    NettyClientStream.TransportState stream = cmd.stream();
+    Status reason = cmd.reason();
+    if (reason != null) {
+      stream.transportReportStatus(reason, true, new Metadata());
+    }
+    encoder().writeRstStream(ctx, stream.id(), Http2Error.CANCEL.code(), promise);
+  }
+
+  /**
+   * Sends the given GRPC frame for the stream.
+   */
+  private void sendGrpcFrame(ChannelHandlerContext ctx, SendGrpcFrameCommand cmd,
+      ChannelPromise promise) {
+    // Call the base class to write the HTTP/2 DATA frame.
+    // Note: no need to flush since this is handled by the outbound flow controller.
+    encoder().writeData(ctx, cmd.streamId(), cmd.content(), 0, cmd.endStream(), promise);
+  }
+
+  /**
+   * Sends a PING frame. If a ping operation is already outstanding, the callback in the message is
+   * registered to be called when the existing operation completes, and no new frame is sent.
+   */
+  private void sendPingFrame(ChannelHandlerContext ctx, SendPingCommand msg,
+      ChannelPromise promise) {
+    // Don't check lifecycleManager.getShutdownStatus() since we want to allow pings after shutdown
+    // but before termination. After termination, messages will no longer arrive because the
+    // pipeline clears all handlers on channel close.
+
+    PingCallback callback = msg.callback();
+    Executor executor = msg.executor();
+    // we only allow one outstanding ping at a time, so just add the callback to
+    // any outstanding operation
+    if (ping != null) {
+      promise.setSuccess();
+      ping.addCallback(callback, executor);
+      return;
+    }
+
+    // Use a new promise to prevent calling the callback twice on write failure: here and in
+    // NettyClientTransport.ping(). It may appear strange, but it will behave the same as if
+    // ping != null above.
+    promise.setSuccess();
+    promise = ctx().newPromise();
+    // set outstanding operation
+    long data = USER_PING_PAYLOAD;
+    Stopwatch stopwatch = stopwatchFactory.get();
+    stopwatch.start();
+    ping = new Http2Ping(data, stopwatch);
+    ping.addCallback(callback, executor);
+    // and then write the ping
+    encoder().writePing(ctx, false, USER_PING_PAYLOAD, promise);
+    ctx.flush();
+    final Http2Ping finalPing = ping;
+    promise.addListener(new ChannelFutureListener() {
+      @Override
+      public void operationComplete(ChannelFuture future) throws Exception {
+        if (future.isSuccess()) {
+          transportTracer.reportKeepAliveSent();
+        } else {
+          Throwable cause = future.cause();
+          if (cause instanceof ClosedChannelException) {
+            cause = lifecycleManager.getShutdownThrowable();
+            if (cause == null) {
+              cause = Status.UNKNOWN.withDescription("Ping failed but for unknown reason.")
+                  .withCause(future.cause()).asException();
+            }
+          }
+          finalPing.failed(cause);
+          if (ping == finalPing) {
+            ping = null;
+          }
+        }
+      }
+    });
+  }
+
+  private void gracefulClose(ChannelHandlerContext ctx, GracefulCloseCommand msg,
+      ChannelPromise promise) throws Exception {
+    lifecycleManager.notifyShutdown(msg.getStatus());
+    // Explicitly flush to create any buffered streams before sending GOAWAY.
+    // TODO(ejona): determine if the need to flush is a bug in Netty
+    flush(ctx);
+    close(ctx, promise);
+  }
+
+  private void forcefulClose(final ChannelHandlerContext ctx, final ForcefulCloseCommand msg,
+      ChannelPromise promise) throws Exception {
+    // close() already called by NettyClientTransport, so just need to clean up streams
+    connection().forEachActiveStream(new Http2StreamVisitor() {
+      @Override
+      public boolean visit(Http2Stream stream) throws Http2Exception {
+        NettyClientStream.TransportState clientStream = clientStream(stream);
+        if (clientStream != null) {
+          clientStream.transportReportStatus(msg.getStatus(), true, new Metadata());
+          resetStream(ctx, stream.id(), Http2Error.CANCEL.code(), ctx.newPromise());
+        }
+        stream.close();
+        return true;
+      }
+    });
+    promise.setSuccess();
+  }
+
+  /**
+   * Handler for a GOAWAY being received. Fails any streams created after the
+   * last known stream.
+   */
+  private void goingAway(Status status) {
+    lifecycleManager.notifyShutdown(status);
+    final Status goAwayStatus = lifecycleManager.getShutdownStatus();
+    final int lastKnownStream = connection().local().lastStreamKnownByPeer();
+    try {
+      connection().forEachActiveStream(new Http2StreamVisitor() {
+        @Override
+        public boolean visit(Http2Stream stream) throws Http2Exception {
+          if (stream.id() > lastKnownStream) {
+            NettyClientStream.TransportState clientStream = clientStream(stream);
+            if (clientStream != null) {
+              clientStream.transportReportStatus(
+                  goAwayStatus, RpcProgress.REFUSED, false, new Metadata());
+            }
+            stream.close();
+          }
+          return true;
+        }
+      });
+    } catch (Http2Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private void cancelPing(Throwable t) {
+    if (ping != null) {
+      ping.failed(t);
+      ping = null;
+    }
+  }
+
+  private Status statusFromGoAway(long errorCode, byte[] debugData) {
+    Status status = GrpcUtil.Http2Error.statusForCode((int) errorCode)
+        .augmentDescription("Received Goaway");
+    if (debugData != null && debugData.length > 0) {
+      // If a debug message was provided, use it.
+      String msg = new String(debugData, UTF_8);
+      status = status.augmentDescription(msg);
+    }
+    return status;
+  }
+
+  /**
+   * Gets the client stream associated to the given HTTP/2 stream object.
+   */
+  private NettyClientStream.TransportState clientStream(Http2Stream stream) {
+    return stream == null ? null : (NettyClientStream.TransportState) stream.getProperty(streamKey);
+  }
+
+  private int incrementAndGetNextStreamId() throws StatusException {
+    int nextStreamId = connection().local().incrementAndGetNextStreamId();
+    if (nextStreamId < 0) {
+      logger.fine("Stream IDs have been exhausted for this connection. "
+              + "Initiating graceful shutdown of the connection.");
+      throw EXHAUSTED_STREAMS_STATUS.asException();
+    }
+    return nextStreamId;
+  }
+
+  private Http2Stream requireHttp2Stream(int streamId) {
+    Http2Stream stream = connection().stream(streamId);
+    if (stream == null) {
+      // This should never happen.
+      throw new AssertionError("Stream does not exist: " + streamId);
+    }
+    return stream;
+  }
+
+  private class FrameListener extends Http2FrameAdapter {
+    private boolean firstSettings = true;
+
+    @Override
+    public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
+      if (firstSettings) {
+        firstSettings = false;
+        lifecycleManager.notifyReady();
+      }
+    }
+
+    @Override
+    public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
+        boolean endOfStream) throws Http2Exception {
+      NettyClientHandler.this.onDataRead(streamId, data, padding, endOfStream);
+      return padding;
+    }
+
+    @Override
+    public void onHeadersRead(ChannelHandlerContext ctx,
+        int streamId,
+        Http2Headers headers,
+        int streamDependency,
+        short weight,
+        boolean exclusive,
+        int padding,
+        boolean endStream) throws Http2Exception {
+      NettyClientHandler.this.onHeadersRead(streamId, headers, endStream);
+    }
+
+    @Override
+    public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode)
+        throws Http2Exception {
+      NettyClientHandler.this.onRstStreamRead(streamId, errorCode);
+    }
+
+    @Override
+    public void onPingAckRead(ChannelHandlerContext ctx, long ackPayload) throws Http2Exception {
+      Http2Ping p = ping;
+      if (ackPayload == flowControlPing().payload()) {
+        flowControlPing().updateWindow();
+        if (logger.isLoggable(Level.FINE)) {
+          logger.log(Level.FINE, String.format("Window: %d",
+              decoder().flowController().initialWindowSize(connection().connectionStream())));
+        }
+      } else if (p != null) {
+        if (p.payload() == ackPayload) {
+          p.complete();
+          ping = null;
+        } else {
+          logger.log(Level.WARNING, String.format(
+              "Received unexpected ping ack. Expecting %d, got %d", p.payload(), ackPayload));
+        }
+      } else {
+        logger.warning("Received unexpected ping ack. No ping outstanding");
+      }
+      if (keepAliveManager != null) {
+        keepAliveManager.onDataReceived();
+      }
+    }
+
+    @Override
+    public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
+      if (keepAliveManager != null) {
+        keepAliveManager.onDataReceived();
+      }
+    }
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyClientStream.java b/netty/src/main/java/io/grpc/netty/NettyClientStream.java
new file mode 100644
index 0000000..2170e1f
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyClientStream.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
+
+import com.google.common.base.Preconditions;
+import com.google.common.io.BaseEncoding;
+import io.grpc.Attributes;
+import io.grpc.InternalKnownTransport;
+import io.grpc.InternalMethodDescriptor;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.internal.AbstractClientStream;
+import io.grpc.internal.Http2ClientStreamTransportState;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.TransportTracer;
+import io.grpc.internal.WritableBuffer;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.EventLoop;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.handler.codec.http2.Http2Stream;
+import io.netty.util.AsciiString;
+import javax.annotation.Nullable;
+
+/**
+ * Client stream for a Netty transport. Must only be called from the sending application
+ * thread.
+ */
+class NettyClientStream extends AbstractClientStream {
+  private static final InternalMethodDescriptor methodDescriptorAccessor =
+      new InternalMethodDescriptor(InternalKnownTransport.NETTY);
+
+  private final Sink sink = new Sink();
+  private final TransportState state;
+  private final WriteQueue writeQueue;
+  private final MethodDescriptor<?, ?> method;
+  private final Channel channel;
+  private AsciiString authority;
+  private final AsciiString scheme;
+  private final AsciiString userAgent;
+
+  NettyClientStream(
+      TransportState state,
+      MethodDescriptor<?, ?> method,
+      Metadata headers,
+      Channel channel,
+      AsciiString authority,
+      AsciiString scheme,
+      AsciiString userAgent,
+      StatsTraceContext statsTraceCtx,
+      TransportTracer transportTracer) {
+    super(
+        new NettyWritableBufferAllocator(channel.alloc()),
+        statsTraceCtx,
+        transportTracer,
+        headers,
+        useGet(method));
+    this.state = checkNotNull(state, "transportState");
+    this.writeQueue = state.handler.getWriteQueue();
+    this.method = checkNotNull(method, "method");
+    this.channel = checkNotNull(channel, "channel");
+    this.authority = checkNotNull(authority, "authority");
+    this.scheme = checkNotNull(scheme, "scheme");
+    this.userAgent = userAgent;
+  }
+
+  @Override
+  protected TransportState transportState() {
+    return state;
+  }
+
+  @Override
+  protected Sink abstractClientStreamSink() {
+    return sink;
+  }
+
+  @Override
+  public void setAuthority(String authority) {
+    this.authority = AsciiString.of(checkNotNull(authority, "authority"));
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    return state.handler.getAttributes();
+  }
+
+  private static boolean useGet(MethodDescriptor<?, ?> method) {
+    return method.isSafe();
+  }
+
+  private class Sink implements AbstractClientStream.Sink {
+    @SuppressWarnings("BetaApi") // BaseEncoding is stable in Guava 20.0
+    @Override
+    public void writeHeaders(Metadata headers, byte[] requestPayload) {
+      // Convert the headers into Netty HTTP/2 headers.
+      AsciiString defaultPath = (AsciiString) methodDescriptorAccessor.geRawMethodName(method);
+      if (defaultPath == null) {
+        defaultPath = new AsciiString("/" + method.getFullMethodName());
+        methodDescriptorAccessor.setRawMethodName(method, defaultPath);
+      }
+      boolean get = (requestPayload != null);
+      AsciiString httpMethod;
+      if (get) {
+        // Forge the query string
+        // TODO(ericgribkoff) Add the key back to the query string
+        defaultPath =
+            new AsciiString(defaultPath + "?" + BaseEncoding.base64().encode(requestPayload));
+        httpMethod = Utils.HTTP_GET_METHOD;
+      } else {
+        httpMethod = Utils.HTTP_METHOD;
+      }
+      Http2Headers http2Headers = Utils.convertClientHeaders(headers, scheme, defaultPath,
+          authority, httpMethod, userAgent);
+
+      ChannelFutureListener failureListener = new ChannelFutureListener() {
+        @Override
+        public void operationComplete(ChannelFuture future) throws Exception {
+          if (!future.isSuccess()) {
+            // Stream creation failed. Close the stream if not already closed.
+            // When the channel is shutdown, the lifecycle manager has a better view of the failure,
+            // especially before negotiation completes (because the negotiator commonly doesn't
+            // receive the execeptionCaught because NettyClientHandler does not propagate it).
+            Status s = transportState().handler.getLifecycleManager().getShutdownStatus();
+            if (s == null) {
+              s = transportState().statusFromFailedFuture(future);
+            }
+            transportState().transportReportStatus(s, true, new Metadata());
+          }
+        }
+      };
+
+      // Write the command requesting the creation of the stream.
+      writeQueue.enqueue(new CreateStreamCommand(http2Headers, transportState(), get),
+          !method.getType().clientSendsOneMessage() || get).addListener(failureListener);
+    }
+
+    @Override
+    public void writeFrame(
+        WritableBuffer frame, boolean endOfStream, boolean flush, final int numMessages) {
+      Preconditions.checkArgument(numMessages >= 0);
+      ByteBuf bytebuf = frame == null ? EMPTY_BUFFER : ((NettyWritableBuffer) frame).bytebuf();
+      final int numBytes = bytebuf.readableBytes();
+      if (numBytes > 0) {
+        // Add the bytes to outbound flow control.
+        onSendingBytes(numBytes);
+        writeQueue.enqueue(new SendGrpcFrameCommand(transportState(), bytebuf, endOfStream), flush)
+            .addListener(new ChannelFutureListener() {
+              @Override
+              public void operationComplete(ChannelFuture future) throws Exception {
+                // If the future succeeds when http2stream is null, the stream has been cancelled
+                // before it began and Netty is purging pending writes from the flow-controller.
+                if (future.isSuccess() && transportState().http2Stream() != null) {
+                  // Remove the bytes from outbound flow control, optionally notifying
+                  // the client that they can send more bytes.
+                  transportState().onSentBytes(numBytes);
+                  NettyClientStream.this.getTransportTracer().reportMessageSent(numMessages);
+                }
+              }
+            });
+      } else {
+        // The frame is empty and will not impact outbound flow control. Just send it.
+        writeQueue.enqueue(new SendGrpcFrameCommand(transportState(), bytebuf, endOfStream), flush);
+      }
+    }
+
+    @Override
+    public void request(final int numMessages) {
+      if (channel.eventLoop().inEventLoop()) {
+        // Processing data read in the event loop so can call into the deframer immediately
+        transportState().requestMessagesFromDeframer(numMessages);
+      } else {
+        channel.eventLoop().execute(new Runnable() {
+          @Override
+          public void run() {
+            transportState().requestMessagesFromDeframer(numMessages);
+          }
+        });
+      }
+    }
+
+    @Override
+    public void cancel(Status status) {
+      writeQueue.enqueue(new CancelClientStreamCommand(transportState(), status), true);
+    }
+  }
+
+  /** This should only called from the transport thread. */
+  public abstract static class TransportState extends Http2ClientStreamTransportState
+      implements StreamIdHolder {
+    private final NettyClientHandler handler;
+    private final EventLoop eventLoop;
+    private int id;
+    private Http2Stream http2Stream;
+
+    public TransportState(
+        NettyClientHandler handler,
+        EventLoop eventLoop,
+        int maxMessageSize,
+        StatsTraceContext statsTraceCtx,
+        TransportTracer transportTracer) {
+      super(maxMessageSize, statsTraceCtx, transportTracer);
+      this.handler = checkNotNull(handler, "handler");
+      this.eventLoop = checkNotNull(eventLoop, "eventLoop");
+    }
+
+    @Override
+    public int id() {
+      return id;
+    }
+
+    public void setId(int id) {
+      checkArgument(id > 0, "id must be positive");
+      this.id = id;
+    }
+
+    /**
+     * Sets the underlying Netty {@link Http2Stream} for this stream. This must be called in the
+     * context of the transport thread.
+     */
+    public void setHttp2Stream(Http2Stream http2Stream) {
+      checkNotNull(http2Stream, "http2Stream");
+      checkState(this.http2Stream == null, "Can only set http2Stream once");
+      this.http2Stream = http2Stream;
+
+      // Now that the stream has actually been initialized, call the listener's onReady callback if
+      // appropriate.
+      onStreamAllocated();
+      getTransportTracer().reportLocalStreamStarted();
+    }
+
+    /**
+     * Gets the underlying Netty {@link Http2Stream} for this stream.
+     */
+    @Nullable
+    public Http2Stream http2Stream() {
+      return http2Stream;
+    }
+
+    /**
+     * Intended to be overridden by NettyClientTransport, which has more information about failures.
+     * May only be called from event loop.
+     */
+    protected abstract Status statusFromFailedFuture(ChannelFuture f);
+
+    @Override
+    protected void http2ProcessingFailed(Status status, boolean stopDelivery, Metadata trailers) {
+      transportReportStatus(status, stopDelivery, trailers);
+      handler.getWriteQueue().enqueue(new CancelClientStreamCommand(this, status), true);
+    }
+
+    @Override
+    public void runOnTransportThread(final Runnable r) {
+      if (eventLoop.inEventLoop()) {
+        r.run();
+      } else {
+        eventLoop.execute(r);
+      }
+    }
+
+    @Override
+    public void bytesRead(int processedBytes) {
+      handler.returnProcessedBytes(http2Stream, processedBytes);
+      handler.getWriteQueue().scheduleFlush();
+    }
+
+    @Override
+    public void deframeFailed(Throwable cause) {
+      http2ProcessingFailed(Status.fromThrowable(cause), true, new Metadata());
+    }
+
+    void transportHeadersReceived(Http2Headers headers, boolean endOfStream) {
+      if (endOfStream) {
+        if (!isOutboundClosed()) {
+          handler.getWriteQueue().enqueue(new CancelClientStreamCommand(this, null), true);
+        }
+        transportTrailersReceived(Utils.convertTrailers(headers));
+      } else {
+        transportHeadersReceived(Utils.convertHeaders(headers));
+      }
+    }
+
+    void transportDataReceived(ByteBuf frame, boolean endOfStream) {
+      transportDataReceived(new NettyReadableBuffer(frame.retain()), endOfStream);
+    }
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyClientTransport.java b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java
new file mode 100644
index 0000000..6141db2
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyClientTransport.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED;
+import static io.netty.channel.ChannelOption.SO_KEEPALIVE;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.Attributes;
+import io.grpc.CallOptions;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalLogId;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.internal.ClientStream;
+import io.grpc.internal.ConnectionClientTransport;
+import io.grpc.internal.FailingClientStream;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.Http2Ping;
+import io.grpc.internal.KeepAliveManager;
+import io.grpc.internal.KeepAliveManager.ClientKeepAlivePinger;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.TransportTracer;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoop;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.http2.StreamBufferingEncoder.Http2ChannelClosedException;
+import io.netty.util.AsciiString;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import java.net.SocketAddress;
+import java.nio.channels.ClosedChannelException;
+import java.util.Map;
+import java.util.concurrent.Executor;
+import javax.annotation.Nullable;
+
+/**
+ * A Netty-based {@link ConnectionClientTransport} implementation.
+ */
+class NettyClientTransport implements ConnectionClientTransport {
+  private final InternalLogId logId = InternalLogId.allocate(getClass().getName());
+  private final Map<ChannelOption<?>, ?> channelOptions;
+  private final SocketAddress address;
+  private final Class<? extends Channel> channelType;
+  private final EventLoopGroup group;
+  private final ProtocolNegotiator negotiator;
+  private final String authorityString;
+  private final AsciiString authority;
+  private final AsciiString userAgent;
+  private final int flowControlWindow;
+  private final int maxMessageSize;
+  private final int maxHeaderListSize;
+  private KeepAliveManager keepAliveManager;
+  private final long keepAliveTimeNanos;
+  private final long keepAliveTimeoutNanos;
+  private final boolean keepAliveWithoutCalls;
+  private final Runnable tooManyPingsRunnable;
+  private ProtocolNegotiator.Handler negotiationHandler;
+  private NettyClientHandler handler;
+  // We should not send on the channel until negotiation completes. This is a hard requirement
+  // by SslHandler but is appropriate for HTTP/1.1 Upgrade as well.
+  private Channel channel;
+  /** If {@link #start} has been called, non-{@code null} if channel is {@code null}. */
+  private Status statusExplainingWhyTheChannelIsNull;
+  /** Since not thread-safe, may only be used from event loop. */
+  private ClientTransportLifecycleManager lifecycleManager;
+  /** Since not thread-safe, may only be used from event loop. */
+  private final TransportTracer transportTracer;
+  private final Attributes eagAttributes;
+
+  NettyClientTransport(
+      SocketAddress address, Class<? extends Channel> channelType,
+      Map<ChannelOption<?>, ?> channelOptions, EventLoopGroup group,
+      ProtocolNegotiator negotiator, int flowControlWindow, int maxMessageSize,
+      int maxHeaderListSize, long keepAliveTimeNanos, long keepAliveTimeoutNanos,
+      boolean keepAliveWithoutCalls, String authority, @Nullable String userAgent,
+      Runnable tooManyPingsRunnable, TransportTracer transportTracer, Attributes eagAttributes) {
+    this.negotiator = Preconditions.checkNotNull(negotiator, "negotiator");
+    this.address = Preconditions.checkNotNull(address, "address");
+    this.group = Preconditions.checkNotNull(group, "group");
+    this.channelType = Preconditions.checkNotNull(channelType, "channelType");
+    this.channelOptions = Preconditions.checkNotNull(channelOptions, "channelOptions");
+    this.flowControlWindow = flowControlWindow;
+    this.maxMessageSize = maxMessageSize;
+    this.maxHeaderListSize = maxHeaderListSize;
+    this.keepAliveTimeNanos = keepAliveTimeNanos;
+    this.keepAliveTimeoutNanos = keepAliveTimeoutNanos;
+    this.keepAliveWithoutCalls = keepAliveWithoutCalls;
+    this.authorityString = authority;
+    this.authority = new AsciiString(authority);
+    this.userAgent = new AsciiString(GrpcUtil.getGrpcUserAgent("netty", userAgent));
+    this.tooManyPingsRunnable =
+        Preconditions.checkNotNull(tooManyPingsRunnable, "tooManyPingsRunnable");
+    this.transportTracer = Preconditions.checkNotNull(transportTracer, "transportTracer");
+    this.eagAttributes = Preconditions.checkNotNull(eagAttributes, "eagAttributes");
+  }
+
+  @Override
+  public void ping(final PingCallback callback, final Executor executor) {
+    if (channel == null) {
+      executor.execute(new Runnable() {
+        @Override
+        public void run() {
+          callback.onFailure(statusExplainingWhyTheChannelIsNull.asException());
+        }
+      });
+      return;
+    }
+    // The promise and listener always succeed in NettyClientHandler. So this listener handles the
+    // error case, when the channel is closed and the NettyClientHandler no longer in the pipeline.
+    ChannelFutureListener failureListener = new ChannelFutureListener() {
+      @Override
+      public void operationComplete(ChannelFuture future) throws Exception {
+        if (!future.isSuccess()) {
+          Status s = statusFromFailedFuture(future);
+          Http2Ping.notifyFailed(callback, executor, s.asException());
+        }
+      }
+    };
+    // Write the command requesting the ping
+    handler.getWriteQueue().enqueue(new SendPingCommand(callback, executor), true)
+        .addListener(failureListener);
+  }
+
+  @Override
+  public ClientStream newStream(
+      MethodDescriptor<?, ?> method, Metadata headers, CallOptions callOptions) {
+    Preconditions.checkNotNull(method, "method");
+    Preconditions.checkNotNull(headers, "headers");
+    if (channel == null) {
+      return new FailingClientStream(statusExplainingWhyTheChannelIsNull);
+    }
+    StatsTraceContext statsTraceCtx = StatsTraceContext.newClientContext(callOptions, headers);
+    return new NettyClientStream(
+        new NettyClientStream.TransportState(
+            handler,
+            channel.eventLoop(),
+            maxMessageSize,
+            statsTraceCtx,
+            transportTracer) {
+          @Override
+          protected Status statusFromFailedFuture(ChannelFuture f) {
+            return NettyClientTransport.this.statusFromFailedFuture(f);
+          }
+        },
+        method,
+        headers,
+        channel,
+        authority,
+        negotiationHandler.scheme(),
+        userAgent,
+        statsTraceCtx,
+        transportTracer);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public Runnable start(Listener transportListener) {
+    lifecycleManager = new ClientTransportLifecycleManager(
+        Preconditions.checkNotNull(transportListener, "listener"));
+    EventLoop eventLoop = group.next();
+    if (keepAliveTimeNanos != KEEPALIVE_TIME_NANOS_DISABLED) {
+      keepAliveManager = new KeepAliveManager(
+          new ClientKeepAlivePinger(this), eventLoop, keepAliveTimeNanos, keepAliveTimeoutNanos,
+          keepAliveWithoutCalls);
+    }
+
+    handler = NettyClientHandler.newHandler(
+        lifecycleManager,
+        keepAliveManager,
+        flowControlWindow,
+        maxHeaderListSize,
+        GrpcUtil.STOPWATCH_SUPPLIER,
+        tooManyPingsRunnable,
+        transportTracer,
+        eagAttributes,
+        authorityString);
+    NettyHandlerSettings.setAutoWindow(handler);
+
+    negotiationHandler = negotiator.newHandler(handler);
+
+    Bootstrap b = new Bootstrap();
+    b.group(eventLoop);
+    b.channel(channelType);
+    if (NioSocketChannel.class.isAssignableFrom(channelType)) {
+      b.option(SO_KEEPALIVE, true);
+    }
+    for (Map.Entry<ChannelOption<?>, ?> entry : channelOptions.entrySet()) {
+      // Every entry in the map is obtained from
+      // NettyChannelBuilder#withOption(ChannelOption<T> option, T value)
+      // so it is safe to pass the key-value pair to b.option().
+      b.option((ChannelOption<Object>) entry.getKey(), entry.getValue());
+    }
+
+    /**
+     * We don't use a ChannelInitializer in the client bootstrap because its "initChannel" method
+     * is executed in the event loop and we need this handler to be in the pipeline immediately so
+     * that it may begin buffering writes.
+     */
+    b.handler(negotiationHandler);
+    ChannelFuture regFuture = b.register();
+    if (regFuture.isDone() && !regFuture.isSuccess()) {
+      channel = null;
+      // Initialization has failed badly. All new streams should be made to fail.
+      Throwable t = regFuture.cause();
+      if (t == null) {
+        t = new IllegalStateException("Channel is null, but future doesn't have a cause");
+      }
+      statusExplainingWhyTheChannelIsNull = Utils.statusFromThrowable(t);
+      // Use a Runnable since lifecycleManager calls transportListener
+      return new Runnable() {
+        @Override
+        public void run() {
+          // NOTICE: we not are calling lifecycleManager from the event loop. But there isn't really
+          // an event loop in this case, so nothing should be accessing the lifecycleManager. We
+          // could use GlobalEventExecutor (which is what regFuture would use for notifying
+          // listeners in this case), but avoiding on-demand thread creation in an error case seems
+          // a good idea and is probably clearer threading.
+          lifecycleManager.notifyTerminated(statusExplainingWhyTheChannelIsNull);
+        }
+      };
+    }
+    channel = regFuture.channel();
+    // Start the write queue as soon as the channel is constructed
+    handler.startWriteQueue(channel);
+    // This write will have no effect, yet it will only complete once the negotiationHandler
+    // flushes any pending writes. We need it to be staged *before* the `connect` so that
+    // the channel can't have been closed yet, removing all handlers. This write will sit in the
+    // AbstractBufferingHandler's buffer, and will either be flushed on a successful connection,
+    // or failed if the connection fails.
+    channel.writeAndFlush(NettyClientHandler.NOOP_MESSAGE).addListener(new ChannelFutureListener() {
+      @Override
+      public void operationComplete(ChannelFuture future) throws Exception {
+        if (!future.isSuccess()) {
+          // Need to notify of this failure, because NettyClientHandler may not have been added to
+          // the pipeline before the error occurred.
+          lifecycleManager.notifyTerminated(Utils.statusFromThrowable(future.cause()));
+        }
+      }
+    });
+    // Start the connection operation to the server.
+    channel.connect(address);
+
+    if (keepAliveManager != null) {
+      keepAliveManager.onTransportStarted();
+    }
+
+    return null;
+  }
+
+  @Override
+  public void shutdown(Status reason) {
+    // start() could have failed
+    if (channel == null) {
+      return;
+    }
+    // Notifying of termination is automatically done when the channel closes.
+    if (channel.isOpen()) {
+      handler.getWriteQueue().enqueue(new GracefulCloseCommand(reason), true);
+    }
+  }
+
+  @Override
+  public void shutdownNow(final Status reason) {
+    // Notifying of termination is automatically done when the channel closes.
+    if (channel != null && channel.isOpen()) {
+      handler.getWriteQueue().enqueue(new Runnable() {
+        @Override
+        public void run() {
+          lifecycleManager.notifyShutdown(reason);
+          // Call close() directly since negotiation may not have completed, such that a write would
+          // be queued.
+          channel.close();
+          channel.write(new ForcefulCloseCommand(reason));
+        }
+      }, true);
+    }
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("logId", logId.getId())
+        .add("address", address)
+        .add("channel", channel)
+        .toString();
+  }
+
+  @Override
+  public InternalLogId getLogId() {
+    return logId;
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    return handler.getAttributes();
+  }
+
+  @Override
+  public ListenableFuture<SocketStats> getStats() {
+    final SettableFuture<SocketStats> result = SettableFuture.create();
+    if (channel.eventLoop().inEventLoop()) {
+      // This is necessary, otherwise we will block forever if we get the future from inside
+      // the event loop.
+      result.set(getStatsHelper(channel));
+      return result;
+    }
+    channel.eventLoop().submit(
+        new Runnable() {
+          @Override
+          public void run() {
+            result.set(getStatsHelper(channel));
+          }
+        })
+        .addListener(
+            new GenericFutureListener<Future<Object>>() {
+              @Override
+              public void operationComplete(Future<Object> future) throws Exception {
+                if (!future.isSuccess()) {
+                  result.setException(future.cause());
+                }
+              }
+            });
+    return result;
+  }
+
+  private SocketStats getStatsHelper(Channel ch) {
+    assert ch.eventLoop().inEventLoop();
+    return new SocketStats(
+        transportTracer.getStats(),
+        channel.localAddress(),
+        channel.remoteAddress(),
+        Utils.getSocketOptions(ch),
+        handler == null ? null : handler.getSecurityInfo());
+  }
+
+  @VisibleForTesting
+  Channel channel() {
+    return channel;
+  }
+
+  @VisibleForTesting
+  KeepAliveManager keepAliveManager() {
+    return keepAliveManager;
+  }
+
+  /**
+   * Convert ChannelFuture.cause() to a Status, taking into account that all handlers are removed
+   * from the pipeline when the channel is closed. Since handlers are removed, you may get an
+   * unhelpful exception like ClosedChannelException.
+   *
+   * <p>This method must only be called on the event loop.
+   */
+  private Status statusFromFailedFuture(ChannelFuture f) {
+    Throwable t = f.cause();
+    if (t instanceof ClosedChannelException
+        // Exception thrown by the StreamBufferingEncoder if the channel is closed while there
+        // are still streams buffered. This exception is not helpful. Replace it by the real
+        // cause of the shutdown (if available).
+        || t instanceof Http2ChannelClosedException) {
+      Status shutdownStatus = lifecycleManager.getShutdownStatus();
+      if (shutdownStatus == null) {
+        return Status.UNKNOWN.withDescription("Channel closed but for unknown reason")
+            .withCause(new ClosedChannelException().initCause(t));
+      }
+      return shutdownStatus;
+    }
+    return Utils.statusFromThrowable(t);
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyHandlerSettings.java b/netty/src/main/java/io/grpc/netty/NettyHandlerSettings.java
new file mode 100644
index 0000000..a977bb7
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyHandlerSettings.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Allows autoFlowControl to be turned on and off from interop testing and flow control windows to
+ * be accessed.
+ */
+final class NettyHandlerSettings {
+
+  private static volatile boolean enabled;
+
+  private static boolean autoFlowControlOn;
+  // These will be the most recently created handlers created using NettyClientTransport and
+  // NettyServerTransport
+  private static AbstractNettyHandler clientHandler;
+  private static AbstractNettyHandler serverHandler;
+
+  static void setAutoWindow(AbstractNettyHandler handler) {
+    if (!enabled) {
+      return;
+    }
+    synchronized (NettyHandlerSettings.class) {
+      handler.setAutoTuneFlowControl(autoFlowControlOn);
+      if (handler instanceof NettyClientHandler) {
+        clientHandler = handler;
+      } else if (handler instanceof NettyServerHandler) {
+        serverHandler = handler;
+      } else {
+        throw new RuntimeException("Expecting NettyClientHandler or NettyServerHandler");
+      }
+    }
+  }
+
+  public static void enable(boolean enable) {
+    enabled = enable;
+  }
+
+  public static synchronized void autoWindowOn(boolean autoFlowControl) {
+    autoFlowControlOn = autoFlowControl;
+  }
+
+  public static synchronized int getLatestClientWindow() {
+    return getLatestWindow(clientHandler);
+  }
+
+  public static synchronized int getLatestServerWindow() {
+    return getLatestWindow(serverHandler);
+  }
+
+  private static synchronized int getLatestWindow(AbstractNettyHandler handler) {
+    Preconditions.checkNotNull(handler);
+    return handler.decoder().flowController()
+        .initialWindowSize(handler.connection().connectionStream());
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyReadableBuffer.java b/netty/src/main/java/io/grpc/netty/NettyReadableBuffer.java
new file mode 100644
index 0000000..37caccb
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyReadableBuffer.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import com.google.common.base.Preconditions;
+import io.grpc.internal.AbstractReadableBuffer;
+import io.netty.buffer.ByteBuf;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * A {@link java.nio.Buffer} implementation that is backed by a Netty {@link ByteBuf}. This class
+ * does not call {@link ByteBuf#retain}, so if that is needed it should be called prior to creating
+ * this buffer.
+ */
+class NettyReadableBuffer extends AbstractReadableBuffer {
+  private final ByteBuf buffer;
+  private boolean closed;
+
+  NettyReadableBuffer(ByteBuf buffer) {
+    this.buffer = Preconditions.checkNotNull(buffer, "buffer");
+  }
+
+  ByteBuf buffer() {
+    return buffer;
+  }
+
+  @Override
+  public int readableBytes() {
+    return buffer.readableBytes();
+  }
+
+  @Override
+  public void skipBytes(int length) {
+    buffer.skipBytes(length);
+  }
+
+  @Override
+  public int readUnsignedByte() {
+    return buffer.readUnsignedByte();
+  }
+
+  @Override
+  public void readBytes(byte[] dest, int index, int length) {
+    buffer.readBytes(dest, index, length);
+  }
+
+  @Override
+  public void readBytes(ByteBuffer dest) {
+    buffer.readBytes(dest);
+  }
+
+  @Override
+  public void readBytes(OutputStream dest, int length) {
+    try {
+      buffer.readBytes(dest, length);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public NettyReadableBuffer readBytes(int length) {
+    return new NettyReadableBuffer(buffer.readRetainedSlice(length));
+  }
+
+  @Override
+  public boolean hasArray() {
+    return buffer.hasArray();
+  }
+
+  @Override
+  public byte[] array() {
+    return buffer.array();
+  }
+
+  @Override
+  public int arrayOffset() {
+    return buffer.arrayOffset() + buffer.readerIndex();
+  }
+
+  /**
+   * If the first call to close, calls {@link ByteBuf#release} to release the internal Netty buffer.
+   */
+  @Override
+  public void close() {
+    // Don't allow slices to close. Also, only allow close to be called once.
+    if (!closed) {
+      closed = true;
+      buffer.release();
+    }
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyServer.java b/netty/src/main/java/io/grpc/netty/NettyServer.java
new file mode 100644
index 0000000..ad6fff7
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyServer.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_AGE_NANOS_DISABLED;
+import static io.netty.channel.ChannelOption.SO_BACKLOG;
+import static io.netty.channel.ChannelOption.SO_KEEPALIVE;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.InternalLogId;
+import io.grpc.InternalWithLogId;
+import io.grpc.ServerStreamTracer;
+import io.grpc.internal.InternalServer;
+import io.grpc.internal.ServerListener;
+import io.grpc.internal.ServerTransportListener;
+import io.grpc.internal.SharedResourceHolder;
+import io.grpc.internal.TransportTracer;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.util.AbstractReferenceCounted;
+import io.netty.util.ReferenceCounted;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Netty-based server implementation.
+ */
+class NettyServer implements InternalServer, InternalWithLogId {
+  private static final Logger log = Logger.getLogger(InternalServer.class.getName());
+
+  private final InternalLogId logId = InternalLogId.allocate(getClass().getName());
+  private final SocketAddress address;
+  private final Class<? extends ServerChannel> channelType;
+  private final Map<ChannelOption<?>, ?> channelOptions;
+  private final ProtocolNegotiator protocolNegotiator;
+  private final int maxStreamsPerConnection;
+  private final boolean usingSharedBossGroup;
+  private final boolean usingSharedWorkerGroup;
+  private EventLoopGroup bossGroup;
+  private EventLoopGroup workerGroup;
+  private ServerListener listener;
+  private Channel channel;
+  private final int flowControlWindow;
+  private final int maxMessageSize;
+  private final int maxHeaderListSize;
+  private final long keepAliveTimeInNanos;
+  private final long keepAliveTimeoutInNanos;
+  private final long maxConnectionIdleInNanos;
+  private final long maxConnectionAgeInNanos;
+  private final long maxConnectionAgeGraceInNanos;
+  private final boolean permitKeepAliveWithoutCalls;
+  private final long permitKeepAliveTimeInNanos;
+  private final ReferenceCounted eventLoopReferenceCounter = new EventLoopReferenceCounter();
+  private final List<ServerStreamTracer.Factory> streamTracerFactories;
+  private final TransportTracer.Factory transportTracerFactory;
+  private final InternalChannelz channelz;
+  // Only modified in event loop but safe to read any time. Set at startup and unset at shutdown.
+  // In the future we may have >1 listen socket.
+  private volatile ImmutableList<InternalInstrumented<SocketStats>> listenSockets
+      = ImmutableList.of();
+
+  NettyServer(
+      SocketAddress address, Class<? extends ServerChannel> channelType,
+      Map<ChannelOption<?>, ?> channelOptions,
+      @Nullable EventLoopGroup bossGroup, @Nullable EventLoopGroup workerGroup,
+      ProtocolNegotiator protocolNegotiator, List<ServerStreamTracer.Factory> streamTracerFactories,
+      TransportTracer.Factory transportTracerFactory,
+      int maxStreamsPerConnection, int flowControlWindow, int maxMessageSize, int maxHeaderListSize,
+      long keepAliveTimeInNanos, long keepAliveTimeoutInNanos,
+      long maxConnectionIdleInNanos,
+      long maxConnectionAgeInNanos, long maxConnectionAgeGraceInNanos,
+      boolean permitKeepAliveWithoutCalls, long permitKeepAliveTimeInNanos,
+      InternalChannelz channelz) {
+    this.address = address;
+    this.channelType = checkNotNull(channelType, "channelType");
+    checkNotNull(channelOptions, "channelOptions");
+    this.channelOptions = new HashMap<ChannelOption<?>, Object>(channelOptions);
+    this.bossGroup = bossGroup;
+    this.workerGroup = workerGroup;
+    this.protocolNegotiator = checkNotNull(protocolNegotiator, "protocolNegotiator");
+    this.streamTracerFactories = checkNotNull(streamTracerFactories, "streamTracerFactories");
+    this.usingSharedBossGroup = bossGroup == null;
+    this.usingSharedWorkerGroup = workerGroup == null;
+    this.transportTracerFactory = transportTracerFactory;
+    this.maxStreamsPerConnection = maxStreamsPerConnection;
+    this.flowControlWindow = flowControlWindow;
+    this.maxMessageSize = maxMessageSize;
+    this.maxHeaderListSize = maxHeaderListSize;
+    this.keepAliveTimeInNanos = keepAliveTimeInNanos;
+    this.keepAliveTimeoutInNanos = keepAliveTimeoutInNanos;
+    this.maxConnectionIdleInNanos = maxConnectionIdleInNanos;
+    this.maxConnectionAgeInNanos = maxConnectionAgeInNanos;
+    this.maxConnectionAgeGraceInNanos = maxConnectionAgeGraceInNanos;
+    this.permitKeepAliveWithoutCalls = permitKeepAliveWithoutCalls;
+    this.permitKeepAliveTimeInNanos = permitKeepAliveTimeInNanos;
+    this.channelz = Preconditions.checkNotNull(channelz);
+  }
+
+  @Override
+  public int getPort() {
+    if (channel == null) {
+      return -1;
+    }
+    SocketAddress localAddr = channel.localAddress();
+    if (!(localAddr instanceof InetSocketAddress)) {
+      return -1;
+    }
+    return ((InetSocketAddress) localAddr).getPort();
+  }
+
+  @Override
+  public List<InternalInstrumented<SocketStats>> getListenSockets() {
+    return listenSockets;
+  }
+
+  @Override
+  public void start(ServerListener serverListener) throws IOException {
+    listener = checkNotNull(serverListener, "serverListener");
+
+    // If using the shared groups, get references to them.
+    allocateSharedGroups();
+
+    ServerBootstrap b = new ServerBootstrap();
+    b.group(bossGroup, workerGroup);
+    b.channel(channelType);
+    if (NioServerSocketChannel.class.isAssignableFrom(channelType)) {
+      b.option(SO_BACKLOG, 128);
+      b.childOption(SO_KEEPALIVE, true);
+    }
+
+    if (channelOptions != null) {
+      for (Map.Entry<ChannelOption<?>, ?> entry : channelOptions.entrySet()) {
+        @SuppressWarnings("unchecked")
+        ChannelOption<Object> key = (ChannelOption<Object>) entry.getKey();
+        b.childOption(key, entry.getValue());
+      }
+    }
+
+    b.childHandler(new ChannelInitializer<Channel>() {
+      @Override
+      public void initChannel(Channel ch) throws Exception {
+
+        ChannelPromise channelDone = ch.newPromise();
+
+        long maxConnectionAgeInNanos = NettyServer.this.maxConnectionAgeInNanos;
+        if (maxConnectionAgeInNanos != MAX_CONNECTION_AGE_NANOS_DISABLED) {
+          // apply a random jitter of +/-10% to max connection age
+          maxConnectionAgeInNanos =
+              (long) ((.9D + Math.random() * .2D) * maxConnectionAgeInNanos);
+        }
+
+        NettyServerTransport transport =
+            new NettyServerTransport(
+                ch,
+                channelDone,
+                protocolNegotiator,
+                streamTracerFactories,
+                transportTracerFactory.create(),
+                maxStreamsPerConnection,
+                flowControlWindow,
+                maxMessageSize,
+                maxHeaderListSize,
+                keepAliveTimeInNanos,
+                keepAliveTimeoutInNanos,
+                maxConnectionIdleInNanos,
+                maxConnectionAgeInNanos,
+                maxConnectionAgeGraceInNanos,
+                permitKeepAliveWithoutCalls,
+                permitKeepAliveTimeInNanos);
+        ServerTransportListener transportListener;
+        // This is to order callbacks on the listener, not to guard access to channel.
+        synchronized (NettyServer.this) {
+          if (channel != null && !channel.isOpen()) {
+            // Server already shutdown.
+            ch.close();
+            return;
+          }
+          // `channel` shutdown can race with `ch` initialization, so this is only safe to increment
+          // inside the lock.
+          eventLoopReferenceCounter.retain();
+          transportListener = listener.transportCreated(transport);
+        }
+
+        /**
+         * Releases the event loop if the channel is "done", possibly due to the channel closing.
+         */
+        final class LoopReleaser implements ChannelFutureListener {
+          boolean done;
+
+          @Override
+          public void operationComplete(ChannelFuture future) throws Exception {
+            if (!done) {
+              done = true;
+              eventLoopReferenceCounter.release();
+            }
+          }
+        }
+
+        transport.start(transportListener);
+        ChannelFutureListener loopReleaser = new LoopReleaser();
+        channelDone.addListener(loopReleaser);
+        ch.closeFuture().addListener(loopReleaser);
+      }
+    });
+    // Bind and start to accept incoming connections.
+    ChannelFuture future = b.bind(address);
+    try {
+      future.await();
+    } catch (InterruptedException ex) {
+      Thread.currentThread().interrupt();
+      throw new RuntimeException("Interrupted waiting for bind");
+    }
+    if (!future.isSuccess()) {
+      throw new IOException("Failed to bind", future.cause());
+    }
+    channel = future.channel();
+    Future<?> channelzFuture = channel.eventLoop().submit(new Runnable() {
+      @Override
+      public void run() {
+        InternalInstrumented<SocketStats> listenSocket = new ListenSocket(channel);
+        listenSockets = ImmutableList.of(listenSocket);
+        channelz.addListenSocket(listenSocket);
+      }
+    });
+    try {
+      channelzFuture.await();
+    } catch (InterruptedException ex) {
+      throw new RuntimeException("Interrupted while registering listen socket to channelz", ex);
+    }
+  }
+
+  @Override
+  public void shutdown() {
+    if (channel == null || !channel.isOpen()) {
+      // Already closed.
+      return;
+    }
+    channel.close().addListener(new ChannelFutureListener() {
+      @Override
+      public void operationComplete(ChannelFuture future) throws Exception {
+        if (!future.isSuccess()) {
+          log.log(Level.WARNING, "Error shutting down server", future.cause());
+        }
+        for (InternalInstrumented<SocketStats> listenSocket : listenSockets) {
+          channelz.removeListenSocket(listenSocket);
+        }
+        listenSockets = null;
+        synchronized (NettyServer.this) {
+          listener.serverShutdown();
+        }
+        eventLoopReferenceCounter.release();
+      }
+    });
+  }
+
+  private void allocateSharedGroups() {
+    if (bossGroup == null) {
+      bossGroup = SharedResourceHolder.get(Utils.DEFAULT_BOSS_EVENT_LOOP_GROUP);
+    }
+    if (workerGroup == null) {
+      workerGroup = SharedResourceHolder.get(Utils.DEFAULT_WORKER_EVENT_LOOP_GROUP);
+    }
+  }
+
+  @Override
+  public InternalLogId getLogId() {
+    return logId;
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("logId", logId.getId())
+        .add("address", address)
+        .toString();
+  }
+
+  class EventLoopReferenceCounter extends AbstractReferenceCounted {
+    @Override
+    protected void deallocate() {
+      try {
+        if (usingSharedBossGroup && bossGroup != null) {
+          SharedResourceHolder.release(Utils.DEFAULT_BOSS_EVENT_LOOP_GROUP, bossGroup);
+        }
+      } finally {
+        bossGroup = null;
+        try {
+          if (usingSharedWorkerGroup && workerGroup != null) {
+            SharedResourceHolder.release(Utils.DEFAULT_WORKER_EVENT_LOOP_GROUP, workerGroup);
+          }
+        } finally {
+          workerGroup = null;
+        }
+      }
+    }
+
+    @Override
+    public ReferenceCounted touch(Object hint) {
+      return this;
+    }
+  }
+
+  /**
+   * A class that can answer channelz queries about the server listen sockets.
+   */
+  private static final class ListenSocket implements InternalInstrumented<SocketStats> {
+    private final InternalLogId id = InternalLogId.allocate(getClass().getName());
+    private final Channel ch;
+
+    ListenSocket(Channel ch) {
+      this.ch = ch;
+    }
+
+    @Override
+    public ListenableFuture<SocketStats> getStats() {
+      final SettableFuture<SocketStats> ret = SettableFuture.create();
+      if (ch.eventLoop().inEventLoop()) {
+        // This is necessary, otherwise we will block forever if we get the future from inside
+        // the event loop.
+        ret.set(new SocketStats(
+            /*data=*/ null,
+            ch.localAddress(),
+            /*remote=*/ null,
+            Utils.getSocketOptions(ch),
+            /*security=*/ null));
+        return ret;
+      }
+      ch.eventLoop()
+          .submit(
+              new Runnable() {
+                @Override
+                public void run() {
+                  ret.set(new SocketStats(
+                      /*data=*/ null,
+                      ch.localAddress(),
+                      /*remote=*/ null,
+                      Utils.getSocketOptions(ch),
+                      /*security=*/ null));
+                }
+              })
+          .addListener(
+              new GenericFutureListener<Future<Object>>() {
+                @Override
+                public void operationComplete(Future<Object> future) throws Exception {
+                  if (!future.isSuccess()) {
+                    ret.setException(future.cause());
+                  }
+                }
+              });
+      return ret;
+    }
+
+    @Override
+    public InternalLogId getLogId() {
+      return id;
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("logId", id.getId())
+          .add("channel", ch)
+          .toString();
+    }
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyServerBuilder.java b/netty/src/main/java/io/grpc/netty/NettyServerBuilder.java
new file mode 100644
index 0000000..a45ef74
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyServerBuilder.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
+import static io.grpc.internal.GrpcUtil.DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS;
+import static io.grpc.internal.GrpcUtil.DEFAULT_SERVER_KEEPALIVE_TIME_NANOS;
+import static io.grpc.internal.GrpcUtil.SERVER_KEEPALIVE_TIME_NANOS_DISABLED;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import io.grpc.ExperimentalApi;
+import io.grpc.Internal;
+import io.grpc.ServerStreamTracer;
+import io.grpc.internal.AbstractServerImplBuilder;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.KeepAliveManager;
+import io.grpc.internal.TransportTracer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.ServerChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.ssl.SslContext;
+import java.io.File;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nullable;
+import javax.net.ssl.SSLException;
+
+/**
+ * A builder to help simplify the construction of a Netty-based GRPC server.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1784")
+@CanIgnoreReturnValue
+public final class NettyServerBuilder extends AbstractServerImplBuilder<NettyServerBuilder> {
+  public static final int DEFAULT_FLOW_CONTROL_WINDOW = 1048576; // 1MiB
+
+  static final long MAX_CONNECTION_IDLE_NANOS_DISABLED = Long.MAX_VALUE;
+  static final long MAX_CONNECTION_AGE_NANOS_DISABLED = Long.MAX_VALUE;
+  static final long MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE = Long.MAX_VALUE;
+
+  private static final long MIN_KEEPALIVE_TIME_NANO = TimeUnit.MILLISECONDS.toNanos(1L);
+  private static final long MIN_KEEPALIVE_TIMEOUT_NANO = TimeUnit.MICROSECONDS.toNanos(499L);
+  private static final long MIN_MAX_CONNECTION_IDLE_NANO = TimeUnit.SECONDS.toNanos(1L);
+  private static final long MIN_MAX_CONNECTION_AGE_NANO = TimeUnit.SECONDS.toNanos(1L);
+  private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L);
+
+  private final SocketAddress address;
+  private Class<? extends ServerChannel> channelType = NioServerSocketChannel.class;
+  private final Map<ChannelOption<?>, Object> channelOptions =
+      new HashMap<ChannelOption<?>, Object>();
+  @Nullable
+  private EventLoopGroup bossEventLoopGroup;
+  @Nullable
+  private EventLoopGroup workerEventLoopGroup;
+  private SslContext sslContext;
+  private ProtocolNegotiator protocolNegotiator;
+  private int maxConcurrentCallsPerConnection = Integer.MAX_VALUE;
+  private int flowControlWindow = DEFAULT_FLOW_CONTROL_WINDOW;
+  private int maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE;
+  private int maxHeaderListSize = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
+  private long keepAliveTimeInNanos =  DEFAULT_SERVER_KEEPALIVE_TIME_NANOS;
+  private long keepAliveTimeoutInNanos = DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS;
+  private long maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED;
+  private long maxConnectionAgeInNanos = MAX_CONNECTION_AGE_NANOS_DISABLED;
+  private long maxConnectionAgeGraceInNanos = MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE;
+  private boolean permitKeepAliveWithoutCalls;
+  private long permitKeepAliveTimeInNanos = TimeUnit.MINUTES.toNanos(5);
+
+  /**
+   * Creates a server builder that will bind to the given port.
+   *
+   * @param port the port on which the server is to be bound.
+   * @return the server builder.
+   */
+  @CheckReturnValue
+  public static NettyServerBuilder forPort(int port) {
+    return new NettyServerBuilder(port);
+  }
+
+  /**
+   * Creates a server builder configured with the given {@link SocketAddress}.
+   *
+   * @param address the socket address on which the server is to be bound.
+   * @return the server builder
+   */
+  @CheckReturnValue
+  public static NettyServerBuilder forAddress(SocketAddress address) {
+    return new NettyServerBuilder(address);
+  }
+
+  @CheckReturnValue
+  private NettyServerBuilder(int port) {
+    this.address = new InetSocketAddress(port);
+  }
+
+  @CheckReturnValue
+  private NettyServerBuilder(SocketAddress address) {
+    this.address = address;
+  }
+
+  /**
+   * Specify the channel type to use, by default we use {@link NioServerSocketChannel}.
+   */
+  public NettyServerBuilder channelType(Class<? extends ServerChannel> channelType) {
+    this.channelType = Preconditions.checkNotNull(channelType, "channelType");
+    return this;
+  }
+
+  /**
+   * Specifies a channel option. As the underlying channel as well as network implementation may
+   * ignore this value applications should consider it a hint.
+   *
+   * @since 1.9.0
+   */
+  public <T> NettyServerBuilder withChildOption(ChannelOption<T> option, T value) {
+    this.channelOptions.put(option, value);
+    return this;
+  }
+
+  /**
+   * Provides the boss EventGroupLoop to the server.
+   *
+   * <p>It's an optional parameter. If the user has not provided one when the server is built, the
+   * builder will use the default one which is static.
+   *
+   * <p>The server won't take ownership of the given EventLoopGroup. It's caller's responsibility
+   * to shut it down when it's desired.
+   *
+   * <p>Grpc uses non-daemon {@link Thread}s by default and thus a {@link io.grpc.Server} will
+   * continue to run even after the main thread has terminated. However, users have to be cautious
+   * when providing their own {@link EventLoopGroup}s.
+   * For example, Netty's {@link EventLoopGroup}s use daemon threads by default
+   * and thus an application with only daemon threads running besides the main thread will exit as
+   * soon as the main thread completes.
+   * A simple solution to this problem is to call {@link io.grpc.Server#awaitTermination()} to
+   * keep the main thread alive until the server has terminated.
+   */
+  public NettyServerBuilder bossEventLoopGroup(EventLoopGroup group) {
+    this.bossEventLoopGroup = group;
+    return this;
+  }
+
+  /**
+   * Provides the worker EventGroupLoop to the server.
+   *
+   * <p>It's an optional parameter. If the user has not provided one when the server is built, the
+   * builder will create one.
+   *
+   * <p>The server won't take ownership of the given EventLoopGroup. It's caller's responsibility
+   * to shut it down when it's desired.
+   *
+   * <p>Grpc uses non-daemon {@link Thread}s by default and thus a {@link io.grpc.Server} will
+   * continue to run even after the main thread has terminated. However, users have to be cautious
+   * when providing their own {@link EventLoopGroup}s.
+   * For example, Netty's {@link EventLoopGroup}s use daemon threads by default
+   * and thus an application with only daemon threads running besides the main thread will exit as
+   * soon as the main thread completes.
+   * A simple solution to this problem is to call {@link io.grpc.Server#awaitTermination()} to
+   * keep the main thread alive until the server has terminated.
+   */
+  public NettyServerBuilder workerEventLoopGroup(EventLoopGroup group) {
+    this.workerEventLoopGroup = group;
+    return this;
+  }
+
+  /**
+   * Sets the TLS context to use for encryption. Providing a context enables encryption. It must
+   * have been configured with {@link GrpcSslContexts}, but options could have been overridden.
+   */
+  public NettyServerBuilder sslContext(SslContext sslContext) {
+    if (sslContext != null) {
+      checkArgument(sslContext.isServer(),
+          "Client SSL context can not be used for server");
+      GrpcSslContexts.ensureAlpnAndH2Enabled(sslContext.applicationProtocolNegotiator());
+    }
+    this.sslContext = sslContext;
+    return this;
+  }
+
+  /**
+   * Sets the {@link ProtocolNegotiator} to be used. If non-{@code null}, overrides the value
+   * specified in {@link #sslContext(SslContext)}.
+   *
+   * <p>Default: {@code null}.
+   */
+  @Internal
+  public final NettyServerBuilder protocolNegotiator(
+          @Nullable ProtocolNegotiator protocolNegotiator) {
+    this.protocolNegotiator = protocolNegotiator;
+    return this;
+  }
+
+  @Override
+  protected void setTracingEnabled(boolean value) {
+    super.setTracingEnabled(value);
+  }
+
+  @Override
+  protected void setStatsEnabled(boolean value) {
+    super.setStatsEnabled(value);
+  }
+
+  @Override
+  protected void setStatsRecordStartedRpcs(boolean value) {
+    super.setStatsRecordStartedRpcs(value);
+  }
+
+  @VisibleForTesting
+  NettyServerBuilder setTransportTracerFactory(TransportTracer.Factory transportTracerFactory) {
+    this.transportTracerFactory = transportTracerFactory;
+    return this;
+  }
+
+  /**
+   * The maximum number of concurrent calls permitted for each incoming connection. Defaults to no
+   * limit.
+   */
+  public NettyServerBuilder maxConcurrentCallsPerConnection(int maxCalls) {
+    checkArgument(maxCalls > 0, "max must be positive: %s", maxCalls);
+    this.maxConcurrentCallsPerConnection = maxCalls;
+    return this;
+  }
+
+  /**
+   * Sets the HTTP/2 flow control window. If not called, the default value
+   * is {@link #DEFAULT_FLOW_CONTROL_WINDOW}).
+   */
+  public NettyServerBuilder flowControlWindow(int flowControlWindow) {
+    checkArgument(flowControlWindow > 0, "flowControlWindow must be positive");
+    this.flowControlWindow = flowControlWindow;
+    return this;
+  }
+
+  /**
+   * Sets the maximum message size allowed to be received on the server. If not called,
+   * defaults to 4 MiB. The default provides protection to services who haven't considered the
+   * possibility of receiving large messages while trying to be large enough to not be hit in normal
+   * usage.
+   *
+   * @deprecated Call {@link #maxInboundMessageSize} instead. This method will be removed in a
+   *     future release.
+   */
+  @Deprecated
+  public NettyServerBuilder maxMessageSize(int maxMessageSize) {
+    return maxInboundMessageSize(maxMessageSize);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public NettyServerBuilder maxInboundMessageSize(int bytes) {
+    checkArgument(bytes >= 0, "bytes must be >= 0");
+    this.maxMessageSize = bytes;
+    return this;
+  }
+
+  /**
+   * Sets the maximum size of header list allowed to be received. This is cumulative size of the
+   * headers with some overhead, as defined for
+   * <a href="http://httpwg.org/specs/rfc7540.html#rfc.section.6.5.2">
+   * HTTP/2's SETTINGS_MAX_HEADER_LIST_SIZE</a>. The default is 8 KiB.
+   */
+  public NettyServerBuilder maxHeaderListSize(int maxHeaderListSize) {
+    checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be > 0");
+    this.maxHeaderListSize = maxHeaderListSize;
+    return this;
+  }
+
+  /**
+   * Sets a custom keepalive time, the delay time for sending next keepalive ping. An unreasonably
+   * small value might be increased, and {@code Long.MAX_VALUE} nano seconds or an unreasonably
+   * large value will disable keepalive.
+   *
+   * @since 1.3.0
+   */
+  public NettyServerBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
+    checkArgument(keepAliveTime > 0L, "keepalive time must be positive");
+    keepAliveTimeInNanos = timeUnit.toNanos(keepAliveTime);
+    keepAliveTimeInNanos = KeepAliveManager.clampKeepAliveTimeInNanos(keepAliveTimeInNanos);
+    if (keepAliveTimeInNanos >= AS_LARGE_AS_INFINITE) {
+      // Bump keepalive time to infinite. This disables keep alive.
+      keepAliveTimeInNanos = SERVER_KEEPALIVE_TIME_NANOS_DISABLED;
+    }
+    if (keepAliveTimeInNanos < MIN_KEEPALIVE_TIME_NANO) {
+      // Bump keepalive time.
+      keepAliveTimeInNanos = MIN_KEEPALIVE_TIME_NANO;
+    }
+    return this;
+  }
+
+  /**
+   * Sets a custom keepalive timeout, the timeout for keepalive ping requests. An unreasonably small
+   * value might be increased.
+   *
+   * @since 1.3.0
+   */
+  public NettyServerBuilder keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
+    checkArgument(keepAliveTimeout > 0L, "keepalive timeout must be positive");
+    keepAliveTimeoutInNanos = timeUnit.toNanos(keepAliveTimeout);
+    keepAliveTimeoutInNanos =
+        KeepAliveManager.clampKeepAliveTimeoutInNanos(keepAliveTimeoutInNanos);
+    if (keepAliveTimeoutInNanos < MIN_KEEPALIVE_TIMEOUT_NANO) {
+      // Bump keepalive timeout.
+      keepAliveTimeoutInNanos = MIN_KEEPALIVE_TIMEOUT_NANO;
+    }
+    return this;
+  }
+
+  /**
+   * Sets a custom max connection idle time, connection being idle for longer than which will be
+   * gracefully terminated. Idleness duration is defined since the most recent time the number of
+   * outstanding RPCs became zero or the connection establishment. An unreasonably small value might
+   * be increased. {@code Long.MAX_VALUE} nano seconds or an unreasonably large value will disable
+   * max connection idle.
+   *
+   * @since 1.4.0
+   */
+  public NettyServerBuilder maxConnectionIdle(long maxConnectionIdle, TimeUnit timeUnit) {
+    checkArgument(maxConnectionIdle > 0L, "max connection idle must be positive");
+    maxConnectionIdleInNanos = timeUnit.toNanos(maxConnectionIdle);
+    if (maxConnectionIdleInNanos >= AS_LARGE_AS_INFINITE) {
+      maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED;
+    }
+    if (maxConnectionIdleInNanos < MIN_MAX_CONNECTION_IDLE_NANO) {
+      maxConnectionIdleInNanos = MIN_MAX_CONNECTION_IDLE_NANO;
+    }
+    return this;
+  }
+
+  /**
+   * Sets a custom max connection age, connection lasting longer than which will be gracefully
+   * terminated. An unreasonably small value might be increased.  A random jitter of +/-10% will be
+   * added to it. {@code Long.MAX_VALUE} nano seconds or an unreasonably large value will disable
+   * max connection age.
+   *
+   * @since 1.3.0
+   */
+  public NettyServerBuilder maxConnectionAge(long maxConnectionAge, TimeUnit timeUnit) {
+    checkArgument(maxConnectionAge > 0L, "max connection age must be positive");
+    maxConnectionAgeInNanos = timeUnit.toNanos(maxConnectionAge);
+    if (maxConnectionAgeInNanos >= AS_LARGE_AS_INFINITE) {
+      maxConnectionAgeInNanos = MAX_CONNECTION_AGE_NANOS_DISABLED;
+    }
+    if (maxConnectionAgeInNanos < MIN_MAX_CONNECTION_AGE_NANO) {
+      maxConnectionAgeInNanos = MIN_MAX_CONNECTION_AGE_NANO;
+    }
+    return this;
+  }
+
+  /**
+   * Sets a custom grace time for the graceful connection termination. Once the max connection age
+   * is reached, RPCs have the grace time to complete. RPCs that do not complete in time will be
+   * cancelled, allowing the connection to terminate. {@code Long.MAX_VALUE} nano seconds or an
+   * unreasonably large value are considered infinite.
+   *
+   * @see #maxConnectionAge(long, TimeUnit)
+   * @since 1.3.0
+   */
+  public NettyServerBuilder maxConnectionAgeGrace(long maxConnectionAgeGrace, TimeUnit timeUnit) {
+    checkArgument(maxConnectionAgeGrace >= 0L, "max connection age grace must be non-negative");
+    maxConnectionAgeGraceInNanos = timeUnit.toNanos(maxConnectionAgeGrace);
+    if (maxConnectionAgeGraceInNanos >= AS_LARGE_AS_INFINITE) {
+      maxConnectionAgeGraceInNanos = MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE;
+    }
+    return this;
+  }
+
+  /**
+   * Specify the most aggressive keep-alive time clients are permitted to configure. The server will
+   * try to detect clients exceeding this rate and when detected will forcefully close the
+   * connection. The default is 5 minutes.
+   *
+   * <p>Even though a default is defined that allows some keep-alives, clients must not use
+   * keep-alive without approval from the service owner. Otherwise, they may experience failures in
+   * the future if the service becomes more restrictive. When unthrottled, keep-alives can cause a
+   * significant amount of traffic and CPU usage, so clients and servers should be conservative in
+   * what they use and accept.
+   *
+   * @see #permitKeepAliveWithoutCalls(boolean)
+   * @since 1.3.0
+   */
+  public NettyServerBuilder permitKeepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
+    checkArgument(keepAliveTime >= 0, "permit keepalive time must be non-negative");
+    permitKeepAliveTimeInNanos = timeUnit.toNanos(keepAliveTime);
+    return this;
+  }
+
+  /**
+   * Sets whether to allow clients to send keep-alive HTTP/2 PINGs even if there are no outstanding
+   * RPCs on the connection. Defaults to {@code false}.
+   *
+   * @see #permitKeepAliveTime(long, TimeUnit)
+   * @since 1.3.0
+   */
+  public NettyServerBuilder permitKeepAliveWithoutCalls(boolean permit) {
+    permitKeepAliveWithoutCalls = permit;
+    return this;
+  }
+
+  @Override
+  @CheckReturnValue
+  protected NettyServer buildTransportServer(
+      List<ServerStreamTracer.Factory> streamTracerFactories) {
+    ProtocolNegotiator negotiator = protocolNegotiator;
+    if (negotiator == null) {
+      negotiator = sslContext != null ? ProtocolNegotiators.serverTls(sslContext) :
+              ProtocolNegotiators.serverPlaintext();
+    }
+
+    return new NettyServer(
+        address, channelType, channelOptions, bossEventLoopGroup, workerEventLoopGroup,
+        negotiator, streamTracerFactories, transportTracerFactory,
+        maxConcurrentCallsPerConnection, flowControlWindow,
+        maxMessageSize, maxHeaderListSize, keepAliveTimeInNanos, keepAliveTimeoutInNanos,
+        maxConnectionIdleInNanos,
+        maxConnectionAgeInNanos, maxConnectionAgeGraceInNanos,
+        permitKeepAliveWithoutCalls, permitKeepAliveTimeInNanos, channelz);
+  }
+
+  @Override
+  public NettyServerBuilder useTransportSecurity(File certChain, File privateKey) {
+    try {
+      sslContext = GrpcSslContexts.forServer(certChain, privateKey).build();
+    } catch (SSLException e) {
+      // This should likely be some other, easier to catch exception.
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+
+  @Override
+  public NettyServerBuilder useTransportSecurity(InputStream certChain, InputStream privateKey) {
+    try {
+      sslContext = GrpcSslContexts.forServer(certChain, privateKey).build();
+    } catch (SSLException e) {
+      // This should likely be some other, easier to catch exception.
+      throw new RuntimeException(e);
+    }
+    return this;
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyServerHandler.java b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java
new file mode 100644
index 0000000..c39da3d
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyServerHandler.java
@@ -0,0 +1,952 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.internal.GrpcUtil.SERVER_KEEPALIVE_TIME_NANOS_DISABLED;
+import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_AGE_NANOS_DISABLED;
+import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_IDLE_NANOS_DISABLED;
+import static io.grpc.netty.Utils.CONTENT_TYPE_HEADER;
+import static io.grpc.netty.Utils.HTTP_METHOD;
+import static io.grpc.netty.Utils.TE_HEADER;
+import static io.grpc.netty.Utils.TE_TRAILERS;
+import static io.netty.handler.codec.http2.DefaultHttp2LocalFlowController.DEFAULT_WINDOW_UPDATE_RATIO;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import io.grpc.Attributes;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalMetadata;
+import io.grpc.InternalStatus;
+import io.grpc.Metadata;
+import io.grpc.ServerStreamTracer;
+import io.grpc.Status;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.KeepAliveManager;
+import io.grpc.internal.LogExceptionRunnable;
+import io.grpc.internal.ServerTransportListener;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.TransportTracer;
+import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2ServerHeadersDecoder;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.codec.http2.DecoratingHttp2FrameWriter;
+import io.netty.handler.codec.http2.DefaultHttp2Connection;
+import io.netty.handler.codec.http2.DefaultHttp2ConnectionDecoder;
+import io.netty.handler.codec.http2.DefaultHttp2ConnectionEncoder;
+import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
+import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
+import io.netty.handler.codec.http2.DefaultHttp2Headers;
+import io.netty.handler.codec.http2.DefaultHttp2LocalFlowController;
+import io.netty.handler.codec.http2.DefaultHttp2RemoteFlowController;
+import io.netty.handler.codec.http2.Http2Connection;
+import io.netty.handler.codec.http2.Http2ConnectionAdapter;
+import io.netty.handler.codec.http2.Http2ConnectionDecoder;
+import io.netty.handler.codec.http2.Http2ConnectionEncoder;
+import io.netty.handler.codec.http2.Http2Error;
+import io.netty.handler.codec.http2.Http2Exception;
+import io.netty.handler.codec.http2.Http2Exception.StreamException;
+import io.netty.handler.codec.http2.Http2FlowController;
+import io.netty.handler.codec.http2.Http2FrameAdapter;
+import io.netty.handler.codec.http2.Http2FrameLogger;
+import io.netty.handler.codec.http2.Http2FrameReader;
+import io.netty.handler.codec.http2.Http2FrameWriter;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.handler.codec.http2.Http2HeadersDecoder;
+import io.netty.handler.codec.http2.Http2InboundFrameLogger;
+import io.netty.handler.codec.http2.Http2OutboundFrameLogger;
+import io.netty.handler.codec.http2.Http2Settings;
+import io.netty.handler.codec.http2.Http2Stream;
+import io.netty.handler.codec.http2.Http2StreamVisitor;
+import io.netty.handler.codec.http2.WeightedFairQueueByteDistributor;
+import io.netty.handler.logging.LogLevel;
+import io.netty.util.AsciiString;
+import io.netty.util.ReferenceCountUtil;
+import java.util.List;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+/**
+ * Server-side Netty handler for GRPC processing. All event handlers are executed entirely within
+ * the context of the Netty Channel thread.
+ */
+class NettyServerHandler extends AbstractNettyHandler {
+  private static final Logger logger = Logger.getLogger(NettyServerHandler.class.getName());
+  private static final long KEEPALIVE_PING = 0xDEADL;
+  private static final long GRACEFUL_SHUTDOWN_PING = 0x97ACEF001L;
+  private static final long GRACEFUL_SHUTDOWN_PING_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(10);
+
+  private final Http2Connection.PropertyKey streamKey;
+  private final ServerTransportListener transportListener;
+  private final int maxMessageSize;
+  private final long keepAliveTimeInNanos;
+  private final long keepAliveTimeoutInNanos;
+  private final long maxConnectionAgeInNanos;
+  private final long maxConnectionAgeGraceInNanos;
+  private final List<ServerStreamTracer.Factory> streamTracerFactories;
+  private final TransportTracer transportTracer;
+  private final KeepAliveEnforcer keepAliveEnforcer;
+  /** Incomplete attributes produced by negotiator. */
+  private Attributes negotiationAttributes;
+  private InternalChannelz.Security securityInfo;
+  /** Completed attributes produced by transportReady. */
+  private Attributes attributes;
+  private Throwable connectionError;
+  private boolean teWarningLogged;
+  private WriteQueue serverWriteQueue;
+  private AsciiString lastKnownAuthority;
+  @CheckForNull
+  private KeepAliveManager keepAliveManager;
+  @CheckForNull
+  private MaxConnectionIdleManager maxConnectionIdleManager;
+  @CheckForNull
+  private ScheduledFuture<?> maxConnectionAgeMonitor;
+  @CheckForNull
+  private GracefulShutdown gracefulShutdown;
+
+  static NettyServerHandler newHandler(
+      ServerTransportListener transportListener,
+      ChannelPromise channelUnused,
+      List<ServerStreamTracer.Factory> streamTracerFactories,
+      TransportTracer transportTracer,
+      int maxStreams,
+      int flowControlWindow,
+      int maxHeaderListSize,
+      int maxMessageSize,
+      long keepAliveTimeInNanos,
+      long keepAliveTimeoutInNanos,
+      long maxConnectionIdleInNanos,
+      long maxConnectionAgeInNanos,
+      long maxConnectionAgeGraceInNanos,
+      boolean permitKeepAliveWithoutCalls,
+      long permitKeepAliveTimeInNanos) {
+    Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive");
+    Http2FrameLogger frameLogger = new Http2FrameLogger(LogLevel.DEBUG, NettyServerHandler.class);
+    Http2HeadersDecoder headersDecoder = new GrpcHttp2ServerHeadersDecoder(maxHeaderListSize);
+    Http2FrameReader frameReader = new Http2InboundFrameLogger(
+        new DefaultHttp2FrameReader(headersDecoder), frameLogger);
+    Http2FrameWriter frameWriter =
+        new Http2OutboundFrameLogger(new DefaultHttp2FrameWriter(), frameLogger);
+    return newHandler(
+        channelUnused,
+        frameReader,
+        frameWriter,
+        transportListener,
+        streamTracerFactories,
+        transportTracer,
+        maxStreams,
+        flowControlWindow,
+        maxHeaderListSize,
+        maxMessageSize,
+        keepAliveTimeInNanos,
+        keepAliveTimeoutInNanos,
+        maxConnectionIdleInNanos,
+        maxConnectionAgeInNanos,
+        maxConnectionAgeGraceInNanos,
+        permitKeepAliveWithoutCalls,
+        permitKeepAliveTimeInNanos);
+  }
+
+  @VisibleForTesting
+  static NettyServerHandler newHandler(
+      ChannelPromise channelUnused,
+      Http2FrameReader frameReader,
+      Http2FrameWriter frameWriter,
+      ServerTransportListener transportListener,
+      List<ServerStreamTracer.Factory> streamTracerFactories,
+      TransportTracer transportTracer,
+      int maxStreams,
+      int flowControlWindow,
+      int maxHeaderListSize,
+      int maxMessageSize,
+      long keepAliveTimeInNanos,
+      long keepAliveTimeoutInNanos,
+      long maxConnectionIdleInNanos,
+      long maxConnectionAgeInNanos,
+      long maxConnectionAgeGraceInNanos,
+      boolean permitKeepAliveWithoutCalls,
+      long permitKeepAliveTimeInNanos) {
+    Preconditions.checkArgument(maxStreams > 0, "maxStreams must be positive");
+    Preconditions.checkArgument(flowControlWindow > 0, "flowControlWindow must be positive");
+    Preconditions.checkArgument(maxHeaderListSize > 0, "maxHeaderListSize must be positive");
+    Preconditions.checkArgument(maxMessageSize > 0, "maxMessageSize must be positive");
+
+    final Http2Connection connection = new DefaultHttp2Connection(true);
+    WeightedFairQueueByteDistributor dist = new WeightedFairQueueByteDistributor(connection);
+    dist.allocationQuantum(16 * 1024); // Make benchmarks fast again.
+    DefaultHttp2RemoteFlowController controller =
+        new DefaultHttp2RemoteFlowController(connection, dist);
+    connection.remote().flowController(controller);
+    final KeepAliveEnforcer keepAliveEnforcer = new KeepAliveEnforcer(
+        permitKeepAliveWithoutCalls, permitKeepAliveTimeInNanos, TimeUnit.NANOSECONDS);
+
+    // Create the local flow controller configured to auto-refill the connection window.
+    connection.local().flowController(
+        new DefaultHttp2LocalFlowController(connection, DEFAULT_WINDOW_UPDATE_RATIO, true));
+    frameWriter = new WriteMonitoringFrameWriter(frameWriter, keepAliveEnforcer);
+    Http2ConnectionEncoder encoder = new DefaultHttp2ConnectionEncoder(connection, frameWriter);
+    Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder,
+        frameReader);
+
+    Http2Settings settings = new Http2Settings();
+    settings.initialWindowSize(flowControlWindow);
+    settings.maxConcurrentStreams(maxStreams);
+    settings.maxHeaderListSize(maxHeaderListSize);
+
+    return new NettyServerHandler(
+        channelUnused,
+        connection,
+        transportListener,
+        streamTracerFactories,
+        transportTracer,
+        decoder, encoder, settings,
+        maxMessageSize,
+        keepAliveTimeInNanos, keepAliveTimeoutInNanos,
+        maxConnectionIdleInNanos,
+        maxConnectionAgeInNanos, maxConnectionAgeGraceInNanos,
+        keepAliveEnforcer);
+  }
+
+  private NettyServerHandler(
+      ChannelPromise channelUnused,
+      final Http2Connection connection,
+      ServerTransportListener transportListener,
+      List<ServerStreamTracer.Factory> streamTracerFactories,
+      TransportTracer transportTracer,
+      Http2ConnectionDecoder decoder,
+      Http2ConnectionEncoder encoder,
+      Http2Settings settings,
+      int maxMessageSize,
+      long keepAliveTimeInNanos,
+      long keepAliveTimeoutInNanos,
+      long maxConnectionIdleInNanos,
+      long maxConnectionAgeInNanos,
+      long maxConnectionAgeGraceInNanos,
+      final KeepAliveEnforcer keepAliveEnforcer) {
+    super(channelUnused, decoder, encoder, settings);
+
+    final MaxConnectionIdleManager maxConnectionIdleManager;
+    if (maxConnectionIdleInNanos == MAX_CONNECTION_IDLE_NANOS_DISABLED) {
+      maxConnectionIdleManager = null;
+    } else {
+      maxConnectionIdleManager = new MaxConnectionIdleManager(maxConnectionIdleInNanos) {
+        @Override
+        void close(ChannelHandlerContext ctx) {
+          if (gracefulShutdown == null) {
+            gracefulShutdown = new GracefulShutdown("max_idle", null);
+            gracefulShutdown.start(ctx);
+            ctx.flush();
+          }
+        }
+      };
+    }
+
+    connection.addListener(new Http2ConnectionAdapter() {
+      @Override
+      public void onStreamActive(Http2Stream stream) {
+        if (connection.numActiveStreams() == 1) {
+          keepAliveEnforcer.onTransportActive();
+          if (maxConnectionIdleManager != null) {
+            maxConnectionIdleManager.onTransportActive();
+          }
+        }
+      }
+
+      @Override
+      public void onStreamClosed(Http2Stream stream) {
+        if (connection.numActiveStreams() == 0) {
+          keepAliveEnforcer.onTransportIdle();
+          if (maxConnectionIdleManager != null) {
+            maxConnectionIdleManager.onTransportIdle();
+          }
+        }
+      }
+    });
+
+    checkArgument(maxMessageSize >= 0, "maxMessageSize must be >= 0");
+    this.maxMessageSize = maxMessageSize;
+    this.keepAliveTimeInNanos = keepAliveTimeInNanos;
+    this.keepAliveTimeoutInNanos = keepAliveTimeoutInNanos;
+    this.maxConnectionIdleManager = maxConnectionIdleManager;
+    this.maxConnectionAgeInNanos = maxConnectionAgeInNanos;
+    this.maxConnectionAgeGraceInNanos = maxConnectionAgeGraceInNanos;
+    this.keepAliveEnforcer = checkNotNull(keepAliveEnforcer, "keepAliveEnforcer");
+
+    streamKey = encoder.connection().newKey();
+    this.transportListener = checkNotNull(transportListener, "transportListener");
+    this.streamTracerFactories = checkNotNull(streamTracerFactories, "streamTracerFactories");
+    this.transportTracer = checkNotNull(transportTracer, "transportTracer");
+
+    // Set the frame listener on the decoder.
+    decoder().frameListener(new FrameListener());
+  }
+
+  @Nullable
+  Throwable connectionError() {
+    return connectionError;
+  }
+
+  @Override
+  public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
+    serverWriteQueue = new WriteQueue(ctx.channel());
+
+    // init max connection age monitor
+    if (maxConnectionAgeInNanos != MAX_CONNECTION_AGE_NANOS_DISABLED) {
+      maxConnectionAgeMonitor = ctx.executor().schedule(
+          new LogExceptionRunnable(new Runnable() {
+            @Override
+            public void run() {
+              if (gracefulShutdown == null) {
+                gracefulShutdown = new GracefulShutdown("max_age", maxConnectionAgeGraceInNanos);
+                gracefulShutdown.start(ctx);
+                ctx.flush();
+              }
+            }
+          }),
+          maxConnectionAgeInNanos,
+          TimeUnit.NANOSECONDS);
+    }
+
+    if (maxConnectionIdleManager != null) {
+      maxConnectionIdleManager.start(ctx);
+    }
+
+    if (keepAliveTimeInNanos != SERVER_KEEPALIVE_TIME_NANOS_DISABLED) {
+      keepAliveManager = new KeepAliveManager(new KeepAlivePinger(ctx), ctx.executor(),
+          keepAliveTimeInNanos, keepAliveTimeoutInNanos, true /* keepAliveDuringTransportIdle */);
+      keepAliveManager.onTransportStarted();
+    }
+
+
+    if (transportTracer != null) {
+      assert encoder().connection().equals(decoder().connection());
+      final Http2Connection connection = encoder().connection();
+      transportTracer.setFlowControlWindowReader(new TransportTracer.FlowControlReader() {
+        private final Http2FlowController local = connection.local().flowController();
+        private final Http2FlowController remote = connection.remote().flowController();
+
+        @Override
+        public TransportTracer.FlowControlWindows read() {
+          assert ctx.executor().inEventLoop();
+          return new TransportTracer.FlowControlWindows(
+              local.windowSize(connection.connectionStream()),
+              remote.windowSize(connection.connectionStream()));
+        }
+      });
+    }
+
+    super.handlerAdded(ctx);
+  }
+
+  private void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers)
+      throws Http2Exception {
+    if (!teWarningLogged && !TE_TRAILERS.equals(headers.get(TE_HEADER))) {
+      logger.warning(String.format("Expected header TE: %s, but %s is received. This means "
+              + "some intermediate proxy may not support trailers",
+          TE_TRAILERS, headers.get(TE_HEADER)));
+      teWarningLogged = true;
+    }
+
+    try {
+
+      // Remove the leading slash of the path and get the fully qualified method name
+      CharSequence path = headers.path();
+
+      if (path == null) {
+        respondWithHttpError(ctx, streamId, 404, Status.Code.UNIMPLEMENTED,
+            "Expected path but is missing");
+        return;
+      }
+
+      if (path.charAt(0) != '/') {
+        respondWithHttpError(ctx, streamId, 404, Status.Code.UNIMPLEMENTED,
+            String.format("Expected path to start with /: %s", path));
+        return;
+      }
+
+      String method = path.subSequence(1, path.length()).toString();
+
+      // Verify that the Content-Type is correct in the request.
+      CharSequence contentType = headers.get(CONTENT_TYPE_HEADER);
+      if (contentType == null) {
+        respondWithHttpError(
+            ctx, streamId, 415, Status.Code.INTERNAL, "Content-Type is missing from the request");
+        return;
+      }
+      String contentTypeString = contentType.toString();
+      if (!GrpcUtil.isGrpcContentType(contentTypeString)) {
+        respondWithHttpError(ctx, streamId, 415, Status.Code.INTERNAL,
+            String.format("Content-Type '%s' is not supported", contentTypeString));
+        return;
+      }
+
+      if (!HTTP_METHOD.equals(headers.method())) {
+        respondWithHttpError(ctx, streamId, 405, Status.Code.INTERNAL,
+            String.format("Method '%s' is not supported", headers.method()));
+        return;
+      }
+
+      // The Http2Stream object was put by AbstractHttp2ConnectionHandler before calling this
+      // method.
+      Http2Stream http2Stream = requireHttp2Stream(streamId);
+
+      Metadata metadata = Utils.convertHeaders(headers);
+      StatsTraceContext statsTraceCtx =
+          StatsTraceContext.newServerContext(streamTracerFactories, method, metadata);
+
+      NettyServerStream.TransportState state = new NettyServerStream.TransportState(
+          this,
+          ctx.channel().eventLoop(),
+          http2Stream,
+          maxMessageSize,
+          statsTraceCtx,
+          transportTracer);
+      String authority = getOrUpdateAuthority((AsciiString) headers.authority());
+      NettyServerStream stream = new NettyServerStream(
+          ctx.channel(),
+          state,
+          attributes,
+          authority,
+          statsTraceCtx,
+          transportTracer);
+      transportListener.streamCreated(stream, method, metadata);
+      state.onStreamAllocated();
+      http2Stream.setProperty(streamKey, state);
+    } catch (Exception e) {
+      logger.log(Level.WARNING, "Exception in onHeadersRead()", e);
+      // Throw an exception that will get handled by onStreamError.
+      throw newStreamException(streamId, e);
+    }
+  }
+
+  private String getOrUpdateAuthority(AsciiString authority) {
+    if (authority == null) {
+      return null;
+    } else if (!authority.equals(lastKnownAuthority)) {
+      lastKnownAuthority = authority;
+    }
+
+    // AsciiString.toString() is internally cached, so subsequent calls will not
+    // result in recomputing the String representation of lastKnownAuthority.
+    return lastKnownAuthority.toString();
+  }
+
+  private void onDataRead(int streamId, ByteBuf data, int padding, boolean endOfStream)
+      throws Http2Exception {
+    flowControlPing().onDataRead(data.readableBytes(), padding);
+    try {
+      NettyServerStream.TransportState stream = serverStream(requireHttp2Stream(streamId));
+      stream.inboundDataReceived(data, endOfStream);
+    } catch (Throwable e) {
+      logger.log(Level.WARNING, "Exception in onDataRead()", e);
+      // Throw an exception that will get handled by onStreamError.
+      throw newStreamException(streamId, e);
+    }
+  }
+
+  private void onRstStreamRead(int streamId, long errorCode) throws Http2Exception {
+    try {
+      NettyServerStream.TransportState stream = serverStream(connection().stream(streamId));
+      if (stream != null) {
+        stream.transportReportStatus(
+            Status.CANCELLED.withDescription("RST_STREAM received for code " + errorCode));
+      }
+    } catch (Throwable e) {
+      logger.log(Level.WARNING, "Exception in onRstStreamRead()", e);
+      // Throw an exception that will get handled by onStreamError.
+      throw newStreamException(streamId, e);
+    }
+  }
+
+  @Override
+  protected void onConnectionError(ChannelHandlerContext ctx, boolean outbound, Throwable cause,
+      Http2Exception http2Ex) {
+    logger.log(Level.FINE, "Connection Error", cause);
+    connectionError = cause;
+    super.onConnectionError(ctx, outbound, cause, http2Ex);
+  }
+
+  @Override
+  protected void onStreamError(ChannelHandlerContext ctx, boolean outbound, Throwable cause,
+      StreamException http2Ex) {
+    logger.log(Level.WARNING, "Stream Error", cause);
+    NettyServerStream.TransportState serverStream = serverStream(
+        connection().stream(Http2Exception.streamId(http2Ex)));
+    if (serverStream != null) {
+      serverStream.transportReportStatus(Utils.statusFromThrowable(cause));
+    }
+    // TODO(ejona): Abort the stream by sending headers to help the client with debugging.
+    // Delegate to the base class to send a RST_STREAM.
+    super.onStreamError(ctx, outbound, cause, http2Ex);
+  }
+
+  @Override
+  public void handleProtocolNegotiationCompleted(
+      Attributes attrs, InternalChannelz.Security securityInfo) {
+    negotiationAttributes = attrs;
+    this.securityInfo = securityInfo;
+  }
+
+  InternalChannelz.Security getSecurityInfo() {
+    return securityInfo;
+  }
+
+  @VisibleForTesting
+  KeepAliveManager getKeepAliveManagerForTest() {
+    return keepAliveManager;
+  }
+
+  @VisibleForTesting
+  void setKeepAliveManagerForTest(KeepAliveManager keepAliveManager) {
+    this.keepAliveManager = keepAliveManager;
+  }
+
+  /**
+   * Handler for the Channel shutting down.
+   */
+  @Override
+  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+    try {
+      if (keepAliveManager != null) {
+        keepAliveManager.onTransportTermination();
+      }
+      if (maxConnectionIdleManager != null) {
+        maxConnectionIdleManager.onTransportTermination();
+      }
+      if (maxConnectionAgeMonitor != null) {
+        maxConnectionAgeMonitor.cancel(false);
+      }
+      final Status status =
+          Status.UNAVAILABLE.withDescription("connection terminated for unknown reason");
+      // Any streams that are still active must be closed
+      connection().forEachActiveStream(new Http2StreamVisitor() {
+        @Override
+        public boolean visit(Http2Stream stream) throws Http2Exception {
+          NettyServerStream.TransportState serverStream = serverStream(stream);
+          if (serverStream != null) {
+            serverStream.transportReportStatus(status);
+          }
+          return true;
+        }
+      });
+    } finally {
+      super.channelInactive(ctx);
+    }
+  }
+
+  WriteQueue getWriteQueue() {
+    return serverWriteQueue;
+  }
+
+  /**
+   * Handler for commands sent from the stream.
+   */
+  @Override
+  public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
+      throws Exception {
+    if (msg instanceof SendGrpcFrameCommand) {
+      sendGrpcFrame(ctx, (SendGrpcFrameCommand) msg, promise);
+    } else if (msg instanceof SendResponseHeadersCommand) {
+      sendResponseHeaders(ctx, (SendResponseHeadersCommand) msg, promise);
+    } else if (msg instanceof CancelServerStreamCommand) {
+      cancelStream(ctx, (CancelServerStreamCommand) msg, promise);
+    } else if (msg instanceof ForcefulCloseCommand) {
+      forcefulClose(ctx, (ForcefulCloseCommand) msg, promise);
+    } else {
+      AssertionError e =
+          new AssertionError("Write called for unexpected type: " + msg.getClass().getName());
+      ReferenceCountUtil.release(msg);
+      promise.setFailure(e);
+      throw e;
+    }
+  }
+
+  /**
+   * Returns the given processed bytes back to inbound flow control.
+   */
+  void returnProcessedBytes(Http2Stream http2Stream, int bytes) {
+    try {
+      decoder().flowController().consumeBytes(http2Stream, bytes);
+    } catch (Http2Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private void closeStreamWhenDone(ChannelPromise promise, int streamId) throws Http2Exception {
+    final NettyServerStream.TransportState stream = serverStream(requireHttp2Stream(streamId));
+    promise.addListener(new ChannelFutureListener() {
+      @Override
+      public void operationComplete(ChannelFuture future) {
+        stream.complete();
+      }
+    });
+  }
+
+  /**
+   * Sends the given gRPC frame to the client.
+   */
+  private void sendGrpcFrame(ChannelHandlerContext ctx, SendGrpcFrameCommand cmd,
+      ChannelPromise promise) throws Http2Exception {
+    if (cmd.endStream()) {
+      closeStreamWhenDone(promise, cmd.streamId());
+    }
+    // Call the base class to write the HTTP/2 DATA frame.
+    encoder().writeData(ctx, cmd.streamId(), cmd.content(), 0, cmd.endStream(), promise);
+  }
+
+  /**
+   * Sends the response headers to the client.
+   */
+  private void sendResponseHeaders(ChannelHandlerContext ctx, SendResponseHeadersCommand cmd,
+      ChannelPromise promise) throws Http2Exception {
+    // TODO(carl-mastrangelo): remove this check once https://github.com/netty/netty/issues/6296 is
+    // fixed.
+    int streamId = cmd.stream().id();
+    Http2Stream stream = connection().stream(streamId);
+    if (stream == null) {
+      resetStream(ctx, streamId, Http2Error.CANCEL.code(), promise);
+      return;
+    }
+    if (cmd.endOfStream()) {
+      closeStreamWhenDone(promise, streamId);
+    }
+    encoder().writeHeaders(ctx, streamId, cmd.headers(), 0, cmd.endOfStream(), promise);
+  }
+
+  private void cancelStream(ChannelHandlerContext ctx, CancelServerStreamCommand cmd,
+      ChannelPromise promise) {
+    // Notify the listener if we haven't already.
+    cmd.stream().transportReportStatus(cmd.reason());
+    // Terminate the stream.
+    encoder().writeRstStream(ctx, cmd.stream().id(), Http2Error.CANCEL.code(), promise);
+  }
+
+  private void forcefulClose(final ChannelHandlerContext ctx, final ForcefulCloseCommand msg,
+      ChannelPromise promise) throws Exception {
+    close(ctx, promise);
+    connection().forEachActiveStream(new Http2StreamVisitor() {
+      @Override
+      public boolean visit(Http2Stream stream) throws Http2Exception {
+        NettyServerStream.TransportState serverStream = serverStream(stream);
+        if (serverStream != null) {
+          serverStream.transportReportStatus(msg.getStatus());
+          resetStream(ctx, stream.id(), Http2Error.CANCEL.code(), ctx.newPromise());
+        }
+        stream.close();
+        return true;
+      }
+    });
+  }
+
+  private void respondWithHttpError(
+      ChannelHandlerContext ctx, int streamId, int code, Status.Code statusCode, String msg) {
+    Metadata metadata = new Metadata();
+    metadata.put(InternalStatus.CODE_KEY, statusCode.toStatus());
+    metadata.put(InternalStatus.MESSAGE_KEY, msg);
+    byte[][] serialized = InternalMetadata.serialize(metadata);
+
+    Http2Headers headers = new DefaultHttp2Headers(true, serialized.length / 2)
+        .status("" + code)
+        .set(CONTENT_TYPE_HEADER, "text/plain; encoding=utf-8");
+    for (int i = 0; i < serialized.length; i += 2) {
+      headers.add(new AsciiString(serialized[i], false), new AsciiString(serialized[i + 1], false));
+    }
+    encoder().writeHeaders(ctx, streamId, headers, 0, false, ctx.newPromise());
+    ByteBuf msgBuf = ByteBufUtil.writeUtf8(ctx.alloc(), msg);
+    encoder().writeData(ctx, streamId, msgBuf, 0, true, ctx.newPromise());
+  }
+
+  private Http2Stream requireHttp2Stream(int streamId) {
+    Http2Stream stream = connection().stream(streamId);
+    if (stream == null) {
+      // This should never happen.
+      throw new AssertionError("Stream does not exist: " + streamId);
+    }
+    return stream;
+  }
+
+  /**
+   * Returns the server stream associated to the given HTTP/2 stream object.
+   */
+  private NettyServerStream.TransportState serverStream(Http2Stream stream) {
+    return stream == null ? null : (NettyServerStream.TransportState) stream.getProperty(streamKey);
+  }
+
+  private Http2Exception newStreamException(int streamId, Throwable cause) {
+    return Http2Exception.streamError(
+        streamId, Http2Error.INTERNAL_ERROR, cause, Strings.nullToEmpty(cause.getMessage()));
+  }
+
+  private class FrameListener extends Http2FrameAdapter {
+    private boolean firstSettings = true;
+
+    @Override
+    public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
+      if (firstSettings) {
+        firstSettings = false;
+        // Delay transportReady until we see the client's HTTP handshake, for coverage with
+        // handshakeTimeout
+        attributes = transportListener.transportReady(negotiationAttributes);
+      }
+    }
+
+    @Override
+    public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
+        boolean endOfStream) throws Http2Exception {
+      if (keepAliveManager != null) {
+        keepAliveManager.onDataReceived();
+      }
+      NettyServerHandler.this.onDataRead(streamId, data, padding, endOfStream);
+      return padding;
+    }
+
+    @Override
+    public void onHeadersRead(ChannelHandlerContext ctx,
+        int streamId,
+        Http2Headers headers,
+        int streamDependency,
+        short weight,
+        boolean exclusive,
+        int padding,
+        boolean endStream) throws Http2Exception {
+      if (keepAliveManager != null) {
+        keepAliveManager.onDataReceived();
+      }
+      NettyServerHandler.this.onHeadersRead(ctx, streamId, headers);
+    }
+
+    @Override
+    public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode)
+        throws Http2Exception {
+      if (keepAliveManager != null) {
+        keepAliveManager.onDataReceived();
+      }
+      NettyServerHandler.this.onRstStreamRead(streamId, errorCode);
+    }
+
+    @Override
+    public void onPingRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
+      if (keepAliveManager != null) {
+        keepAliveManager.onDataReceived();
+      }
+      if (!keepAliveEnforcer.pingAcceptable()) {
+        ByteBuf debugData = ByteBufUtil.writeAscii(ctx.alloc(), "too_many_pings");
+        goAway(ctx, connection().remote().lastStreamCreated(), Http2Error.ENHANCE_YOUR_CALM.code(),
+            debugData, ctx.newPromise());
+        Status status = Status.RESOURCE_EXHAUSTED.withDescription("Too many pings from client");
+        try {
+          forcefulClose(ctx, new ForcefulCloseCommand(status), ctx.newPromise());
+        } catch (Exception ex) {
+          onError(ctx, /* outbound= */ true, ex);
+        }
+      }
+    }
+
+    @Override
+    public void onPingAckRead(ChannelHandlerContext ctx, long data) throws Http2Exception {
+      if (keepAliveManager != null) {
+        keepAliveManager.onDataReceived();
+      }
+      if (data == flowControlPing().payload()) {
+        flowControlPing().updateWindow();
+        if (logger.isLoggable(Level.FINE)) {
+          logger.log(Level.FINE, String.format("Window: %d",
+              decoder().flowController().initialWindowSize(connection().connectionStream())));
+        }
+      } else if (data == GRACEFUL_SHUTDOWN_PING) {
+        if (gracefulShutdown == null) {
+          // this should never happen
+          logger.warning("Received GRACEFUL_SHUTDOWN_PING Ack but gracefulShutdown is null");
+        } else {
+          gracefulShutdown.secondGoAwayAndClose(ctx);
+        }
+      } else if (data != KEEPALIVE_PING) {
+        logger.warning("Received unexpected ping ack. No ping outstanding");
+      }
+    }
+  }
+
+  private final class KeepAlivePinger implements KeepAliveManager.KeepAlivePinger {
+    final ChannelHandlerContext ctx;
+
+    KeepAlivePinger(ChannelHandlerContext ctx) {
+      this.ctx = ctx;
+    }
+
+    @Override
+    public void ping() {
+      ChannelFuture pingFuture = encoder().writePing(
+          ctx, false /* isAck */, KEEPALIVE_PING, ctx.newPromise());
+      ctx.flush();
+      if (transportTracer != null) {
+        pingFuture.addListener(new ChannelFutureListener() {
+          @Override
+          public void operationComplete(ChannelFuture future) throws Exception {
+            if (future.isSuccess()) {
+              transportTracer.reportKeepAliveSent();
+            }
+          }
+        });
+      }
+    }
+
+    @Override
+    public void onPingTimeout() {
+      try {
+        forcefulClose(
+            ctx,
+            new ForcefulCloseCommand(Status.UNAVAILABLE
+                .withDescription("Keepalive failed. The connection is likely gone")),
+            ctx.newPromise());
+      } catch (Exception ex) {
+        try {
+          exceptionCaught(ctx, ex);
+        } catch (Exception ex2) {
+          logger.log(Level.WARNING, "Exception while propagating exception", ex2);
+          logger.log(Level.WARNING, "Original failure", ex);
+        }
+      }
+    }
+  }
+
+  private final class GracefulShutdown {
+    String goAwayMessage;
+
+    /**
+     * The grace time between starting graceful shutdown and closing the netty channel,
+     * {@code null} is unspecified.
+     */
+    @CheckForNull
+    Long graceTimeInNanos;
+
+    /**
+     * True if ping is Acked or ping is timeout.
+     */
+    boolean pingAckedOrTimeout;
+
+    Future<?> pingFuture;
+
+    GracefulShutdown(String goAwayMessage,
+        @Nullable Long graceTimeInNanos) {
+      this.goAwayMessage = goAwayMessage;
+      this.graceTimeInNanos = graceTimeInNanos;
+    }
+
+    /**
+     * Sends out first GOAWAY and ping, and schedules second GOAWAY and close.
+     */
+    void start(final ChannelHandlerContext ctx) {
+      goAway(
+          ctx,
+          Integer.MAX_VALUE,
+          Http2Error.NO_ERROR.code(),
+          ByteBufUtil.writeAscii(ctx.alloc(), goAwayMessage),
+          ctx.newPromise());
+
+      pingFuture = ctx.executor().schedule(
+          new Runnable() {
+            @Override
+            public void run() {
+              secondGoAwayAndClose(ctx);
+            }
+          },
+          GRACEFUL_SHUTDOWN_PING_TIMEOUT_NANOS,
+          TimeUnit.NANOSECONDS);
+
+      encoder().writePing(ctx, false /* isAck */, GRACEFUL_SHUTDOWN_PING, ctx.newPromise());
+    }
+
+    void secondGoAwayAndClose(ChannelHandlerContext ctx) {
+      if (pingAckedOrTimeout) {
+        return;
+      }
+      pingAckedOrTimeout = true;
+
+      checkNotNull(pingFuture, "pingFuture");
+      pingFuture.cancel(false);
+
+      // send the second GOAWAY with last stream id
+      goAway(
+          ctx,
+          connection().remote().lastStreamCreated(),
+          Http2Error.NO_ERROR.code(),
+          ByteBufUtil.writeAscii(ctx.alloc(), goAwayMessage),
+          ctx.newPromise());
+
+      // gracefully shutdown with specified grace time
+      long savedGracefulShutdownTimeMillis = gracefulShutdownTimeoutMillis();
+      long gracefulShutdownTimeoutMillis = savedGracefulShutdownTimeMillis;
+      if (graceTimeInNanos != null) {
+        gracefulShutdownTimeoutMillis = TimeUnit.NANOSECONDS.toMillis(graceTimeInNanos);
+      }
+      try {
+        gracefulShutdownTimeoutMillis(gracefulShutdownTimeoutMillis);
+        close(ctx, ctx.newPromise());
+      } catch (Exception e) {
+        onError(ctx, /* outbound= */ true, e);
+      } finally {
+        gracefulShutdownTimeoutMillis(savedGracefulShutdownTimeMillis);
+      }
+    }
+  }
+
+  // Use a frame writer so that we know when frames are through flow control and actually being
+  // written.
+  private static class WriteMonitoringFrameWriter extends DecoratingHttp2FrameWriter {
+    private final KeepAliveEnforcer keepAliveEnforcer;
+
+    public WriteMonitoringFrameWriter(Http2FrameWriter delegate,
+        KeepAliveEnforcer keepAliveEnforcer) {
+      super(delegate);
+      this.keepAliveEnforcer = keepAliveEnforcer;
+    }
+
+    @Override
+    public ChannelFuture writeData(ChannelHandlerContext ctx, int streamId, ByteBuf data,
+        int padding, boolean endStream, ChannelPromise promise) {
+      keepAliveEnforcer.resetCounters();
+      return super.writeData(ctx, streamId, data, padding, endStream, promise);
+    }
+
+    @Override
+    public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
+        int padding, boolean endStream, ChannelPromise promise) {
+      keepAliveEnforcer.resetCounters();
+      return super.writeHeaders(ctx, streamId, headers, padding, endStream, promise);
+    }
+
+    @Override
+    public ChannelFuture writeHeaders(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
+        int streamDependency, short weight, boolean exclusive, int padding, boolean endStream,
+        ChannelPromise promise) {
+      keepAliveEnforcer.resetCounters();
+      return super.writeHeaders(ctx, streamId, headers, streamDependency, weight, exclusive,
+          padding, endStream, promise);
+    }
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyServerProvider.java b/netty/src/main/java/io/grpc/netty/NettyServerProvider.java
new file mode 100644
index 0000000..23b1e66
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyServerProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.Internal;
+import io.grpc.ServerProvider;
+
+
+/** Provider for {@link NettyChannelBuilder} instances. */
+@Internal
+public final class NettyServerProvider extends ServerProvider {
+
+  @Override
+  protected boolean isAvailable() {
+    return true;
+  }
+
+  @Override
+  protected int priority() {
+    return 5;
+  }
+
+  @Override
+  protected NettyServerBuilder builderForPort(int port) {
+    return NettyServerBuilder.forPort(port);
+  }
+}
+
diff --git a/netty/src/main/java/io/grpc/netty/NettyServerStream.java b/netty/src/main/java/io/grpc/netty/NettyServerStream.java
new file mode 100644
index 0000000..6c0515a
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyServerStream.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Preconditions;
+import io.grpc.Attributes;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.AbstractServerStream;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.TransportTracer;
+import io.grpc.internal.WritableBuffer;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.EventLoop;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.handler.codec.http2.Http2Stream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Server stream for a Netty HTTP2 transport. Must only be called from the sending application
+ * thread.
+ */
+class NettyServerStream extends AbstractServerStream {
+  private static final Logger log = Logger.getLogger(NettyServerStream.class.getName());
+
+  private final Sink sink = new Sink();
+  private final TransportState state;
+  private final Channel channel;
+  private final WriteQueue writeQueue;
+  private final Attributes attributes;
+  private final String authority;
+  private final TransportTracer transportTracer;
+
+  public NettyServerStream(
+      Channel channel,
+      TransportState state,
+      Attributes transportAttrs,
+      String authority,
+      StatsTraceContext statsTraceCtx,
+      TransportTracer transportTracer) {
+    super(new NettyWritableBufferAllocator(channel.alloc()), statsTraceCtx);
+    this.state = checkNotNull(state, "transportState");
+    this.channel = checkNotNull(channel, "channel");
+    this.writeQueue = state.handler.getWriteQueue();
+    this.attributes = checkNotNull(transportAttrs);
+    this.authority = authority;
+    this.transportTracer = checkNotNull(transportTracer, "transportTracer");
+  }
+
+  @Override
+  protected TransportState transportState() {
+    return state;
+  }
+
+  @Override
+  protected Sink abstractServerStreamSink() {
+    return sink;
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    return attributes;
+  }
+
+  @Override
+  public String getAuthority() {
+    return authority;
+  }
+
+  private class Sink implements AbstractServerStream.Sink {
+    @Override
+    public void request(final int numMessages) {
+      if (channel.eventLoop().inEventLoop()) {
+        // Processing data read in the event loop so can call into the deframer immediately
+        transportState().requestMessagesFromDeframer(numMessages);
+      } else {
+        channel.eventLoop().execute(new Runnable() {
+          @Override
+          public void run() {
+            transportState().requestMessagesFromDeframer(numMessages);
+          }
+        });
+      }
+    }
+
+    @Override
+    public void writeHeaders(Metadata headers) {
+      writeQueue.enqueue(
+          SendResponseHeadersCommand.createHeaders(
+              transportState(),
+              Utils.convertServerHeaders(headers)),
+          true);
+    }
+
+    @Override
+    public void writeFrame(WritableBuffer frame, boolean flush, final int numMessages) {
+      Preconditions.checkArgument(numMessages >= 0);
+      if (frame == null) {
+        writeQueue.scheduleFlush();
+        return;
+      }
+      ByteBuf bytebuf = ((NettyWritableBuffer) frame).bytebuf();
+      final int numBytes = bytebuf.readableBytes();
+      // Add the bytes to outbound flow control.
+      onSendingBytes(numBytes);
+      writeQueue.enqueue(new SendGrpcFrameCommand(transportState(), bytebuf, false), flush)
+          .addListener(new ChannelFutureListener() {
+            @Override
+            public void operationComplete(ChannelFuture future) throws Exception {
+              // Remove the bytes from outbound flow control, optionally notifying
+              // the client that they can send more bytes.
+              transportState().onSentBytes(numBytes);
+              if (future.isSuccess()) {
+                transportTracer.reportMessageSent(numMessages);
+              }
+            }
+          });
+    }
+
+    @Override
+    public void writeTrailers(Metadata trailers, boolean headersSent, Status status) {
+      Http2Headers http2Trailers = Utils.convertTrailers(trailers, headersSent);
+      writeQueue.enqueue(
+          SendResponseHeadersCommand.createTrailers(transportState(), http2Trailers, status),
+          true);
+    }
+
+    @Override
+    public void cancel(Status status) {
+      writeQueue.enqueue(new CancelServerStreamCommand(transportState(), status), true);
+    }
+  }
+
+  /** This should only called from the transport thread. */
+  public static class TransportState extends AbstractServerStream.TransportState
+      implements StreamIdHolder {
+    private final Http2Stream http2Stream;
+    private final NettyServerHandler handler;
+    private final EventLoop eventLoop;
+
+    public TransportState(
+        NettyServerHandler handler,
+        EventLoop eventLoop,
+        Http2Stream http2Stream,
+        int maxMessageSize,
+        StatsTraceContext statsTraceCtx,
+        TransportTracer transportTracer) {
+      super(maxMessageSize, statsTraceCtx, transportTracer);
+      this.http2Stream = checkNotNull(http2Stream, "http2Stream");
+      this.handler = checkNotNull(handler, "handler");
+      this.eventLoop = eventLoop;
+    }
+
+    @Override
+    public void runOnTransportThread(final Runnable r) {
+      if (eventLoop.inEventLoop()) {
+        r.run();
+      } else {
+        eventLoop.execute(r);
+      }
+    }
+
+    @Override
+    public void bytesRead(int processedBytes) {
+      handler.returnProcessedBytes(http2Stream, processedBytes);
+      handler.getWriteQueue().scheduleFlush();
+    }
+
+    @Override
+    public void deframeFailed(Throwable cause) {
+      log.log(Level.WARNING, "Exception processing message", cause);
+      Status status = Status.fromThrowable(cause);
+      transportReportStatus(status);
+      handler.getWriteQueue().enqueue(new CancelServerStreamCommand(this, status), true);
+    }
+
+    void inboundDataReceived(ByteBuf frame, boolean endOfStream) {
+      super.inboundDataReceived(new NettyReadableBuffer(frame.retain()), endOfStream);
+    }
+
+    @Override
+    public int id() {
+      return http2Stream.id();
+    }
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyServerTransport.java b/netty/src/main/java/io/grpc/netty/NettyServerTransport.java
new file mode 100644
index 0000000..197601b
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyServerTransport.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalLogId;
+import io.grpc.ServerStreamTracer;
+import io.grpc.Status;
+import io.grpc.internal.ServerTransport;
+import io.grpc.internal.ServerTransportListener;
+import io.grpc.internal.TransportTracer;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelPromise;
+import io.netty.util.concurrent.Future;
+import io.netty.util.concurrent.GenericFutureListener;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The Netty-based server transport.
+ */
+class NettyServerTransport implements ServerTransport {
+  private static final Logger log = Logger.getLogger(NettyServerTransport.class.getName());
+  // connectionLog is for connection related messages only
+  private static final Logger connectionLog = Logger.getLogger(
+      String.format("%s.connections", NettyServerTransport.class.getName()));
+  // Some exceptions are not very useful and add too much noise to the log
+  private static final ImmutableList<String> QUIET_ERRORS = ImmutableList.of(
+      "Connection reset by peer",
+      "An existing connection was forcibly closed by the remote host");
+
+  private final InternalLogId logId = InternalLogId.allocate(getClass().getName());
+  private final Channel channel;
+  private final ChannelPromise channelUnused;
+  private final ProtocolNegotiator protocolNegotiator;
+  private final int maxStreams;
+  // only accessed from channel event loop
+  private NettyServerHandler grpcHandler;
+  private ServerTransportListener listener;
+  private boolean terminated;
+  private final int flowControlWindow;
+  private final int maxMessageSize;
+  private final int maxHeaderListSize;
+  private final long keepAliveTimeInNanos;
+  private final long keepAliveTimeoutInNanos;
+  private final long maxConnectionIdleInNanos;
+  private final long maxConnectionAgeInNanos;
+  private final long maxConnectionAgeGraceInNanos;
+  private final boolean permitKeepAliveWithoutCalls;
+  private final long permitKeepAliveTimeInNanos;
+  private final List<ServerStreamTracer.Factory> streamTracerFactories;
+  private final TransportTracer transportTracer;
+
+  NettyServerTransport(
+      Channel channel,
+      ChannelPromise channelUnused,
+      ProtocolNegotiator protocolNegotiator,
+      List<ServerStreamTracer.Factory> streamTracerFactories,
+      TransportTracer transportTracer,
+      int maxStreams,
+      int flowControlWindow,
+      int maxMessageSize,
+      int maxHeaderListSize,
+      long keepAliveTimeInNanos,
+      long keepAliveTimeoutInNanos,
+      long maxConnectionIdleInNanos,
+      long maxConnectionAgeInNanos,
+      long maxConnectionAgeGraceInNanos,
+      boolean permitKeepAliveWithoutCalls,
+      long permitKeepAliveTimeInNanos) {
+    this.channel = Preconditions.checkNotNull(channel, "channel");
+    this.channelUnused = channelUnused;
+    this.protocolNegotiator = Preconditions.checkNotNull(protocolNegotiator, "protocolNegotiator");
+    this.streamTracerFactories =
+        Preconditions.checkNotNull(streamTracerFactories, "streamTracerFactories");
+    this.transportTracer = Preconditions.checkNotNull(transportTracer, "transportTracer");
+    this.maxStreams = maxStreams;
+    this.flowControlWindow = flowControlWindow;
+    this.maxMessageSize = maxMessageSize;
+    this.maxHeaderListSize = maxHeaderListSize;
+    this.keepAliveTimeInNanos = keepAliveTimeInNanos;
+    this.keepAliveTimeoutInNanos = keepAliveTimeoutInNanos;
+    this.maxConnectionIdleInNanos = maxConnectionIdleInNanos;
+    this.maxConnectionAgeInNanos = maxConnectionAgeInNanos;
+    this.maxConnectionAgeGraceInNanos = maxConnectionAgeGraceInNanos;
+    this.permitKeepAliveWithoutCalls = permitKeepAliveWithoutCalls;
+    this.permitKeepAliveTimeInNanos = permitKeepAliveTimeInNanos;
+  }
+
+  public void start(ServerTransportListener listener) {
+    Preconditions.checkState(this.listener == null, "Handler already registered");
+    this.listener = listener;
+
+    // Create the Netty handler for the pipeline.
+    grpcHandler = createHandler(listener, channelUnused);
+    NettyHandlerSettings.setAutoWindow(grpcHandler);
+
+    // Notify when the channel closes.
+    final class TerminationNotifier implements ChannelFutureListener {
+      boolean done;
+
+      @Override
+      public void operationComplete(ChannelFuture future) throws Exception {
+        if (!done) {
+          done = true;
+          notifyTerminated(grpcHandler.connectionError());
+        }
+      }
+    }
+
+    ChannelFutureListener terminationNotifier = new TerminationNotifier();
+    channelUnused.addListener(terminationNotifier);
+    channel.closeFuture().addListener(terminationNotifier);
+
+    ChannelHandler negotiationHandler = protocolNegotiator.newHandler(grpcHandler);
+    channel.pipeline().addLast(negotiationHandler);
+  }
+
+  @Override
+  public ScheduledExecutorService getScheduledExecutorService() {
+    return channel.eventLoop();
+  }
+
+  @Override
+  public void shutdown() {
+    if (channel.isOpen()) {
+      channel.close();
+    }
+  }
+
+  @Override
+  public void shutdownNow(Status reason) {
+    if (channel.isOpen()) {
+      channel.writeAndFlush(new ForcefulCloseCommand(reason));
+    }
+  }
+
+  @Override
+  public InternalLogId getLogId() {
+    return logId;
+  }
+
+  /**
+   * For testing purposes only.
+   */
+  Channel channel() {
+    return channel;
+  }
+
+  /**
+   * Accepts a throwable and returns the appropriate logging level. Uninteresting exceptions
+   * should not clutter the log.
+   */
+  @VisibleForTesting
+  static Level getLogLevel(Throwable t) {
+    if (t instanceof IOException && t.getMessage() != null) {
+      for (String msg : QUIET_ERRORS) {
+        if (t.getMessage().equals(msg)) {
+          return Level.FINE;
+        }
+      }
+    }
+    return Level.INFO;
+  }
+
+  private void notifyTerminated(Throwable t) {
+    if (t != null) {
+      connectionLog.log(getLogLevel(t), "Transport failed", t);
+    }
+    if (!terminated) {
+      terminated = true;
+      listener.transportTerminated();
+    }
+  }
+
+  @Override
+  public ListenableFuture<SocketStats> getStats() {
+    final SettableFuture<SocketStats> result = SettableFuture.create();
+    if (channel.eventLoop().inEventLoop()) {
+      // This is necessary, otherwise we will block forever if we get the future from inside
+      // the event loop.
+      result.set(getStatsHelper(channel));
+      return result;
+    }
+    channel.eventLoop().submit(
+        new Runnable() {
+          @Override
+          public void run() {
+            result.set(getStatsHelper(channel));
+          }
+        })
+        .addListener(
+            new GenericFutureListener<Future<Object>>() {
+              @Override
+              public void operationComplete(Future<Object> future) throws Exception {
+                if (!future.isSuccess()) {
+                  result.setException(future.cause());
+                }
+              }
+            });
+    return result;
+  }
+
+  private SocketStats getStatsHelper(Channel ch) {
+    Preconditions.checkState(ch.eventLoop().inEventLoop());
+    return new SocketStats(
+        transportTracer.getStats(),
+        channel.localAddress(),
+        channel.remoteAddress(),
+        Utils.getSocketOptions(ch),
+        grpcHandler == null ? null : grpcHandler.getSecurityInfo());
+
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("logId", logId.getId())
+        .add("channel", channel)
+        .toString();
+  }
+
+  /**
+   * Creates the Netty handler to be used in the channel pipeline.
+   */
+  private NettyServerHandler createHandler(
+      ServerTransportListener transportListener, ChannelPromise channelUnused) {
+    return NettyServerHandler.newHandler(
+        transportListener,
+        channelUnused,
+        streamTracerFactories,
+        transportTracer,
+        maxStreams,
+        flowControlWindow,
+        maxHeaderListSize,
+        maxMessageSize,
+        keepAliveTimeInNanos,
+        keepAliveTimeoutInNanos,
+        maxConnectionIdleInNanos,
+        maxConnectionAgeInNanos,
+        maxConnectionAgeGraceInNanos,
+        permitKeepAliveWithoutCalls,
+        permitKeepAliveTimeInNanos);
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettySocketSupport.java b/netty/src/main/java/io/grpc/netty/NettySocketSupport.java
new file mode 100644
index 0000000..528a397
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettySocketSupport.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import io.grpc.InternalChannelz.TcpInfo;
+import io.netty.channel.Channel;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/**
+ * An class for getting low level socket info.
+ */
+final class NettySocketSupport {
+  private static volatile Helper instance = new NettySocketHelperImpl();
+
+  interface Helper {
+    /**
+     * Returns the info on the socket if possible. Returns null if the info can not be discovered.
+     */
+    @Nullable
+    NativeSocketOptions getNativeSocketOptions(Channel ch);
+  }
+
+  /**
+   * A TcpInfo and additional other info that will be turned into channelz socket options.
+   */
+  public static class NativeSocketOptions {
+    @Nullable
+    public final TcpInfo tcpInfo;
+    public final ImmutableMap<String, String> otherInfo;
+
+    /** Creates an instance. */
+    public NativeSocketOptions(
+        TcpInfo tcpInfo,
+        Map<String, String> otherInfo) {
+      Preconditions.checkNotNull(otherInfo);
+      this.tcpInfo = tcpInfo;
+      this.otherInfo = ImmutableMap.copyOf(otherInfo);
+    }
+  }
+
+  public static NativeSocketOptions getNativeSocketOptions(Channel ch) {
+    return instance.getNativeSocketOptions(ch);
+  }
+
+  static void setHelper(Helper helper) {
+    instance = Preconditions.checkNotNull(helper);
+  }
+
+  private static final class NettySocketHelperImpl implements Helper {
+    @Override
+    public NativeSocketOptions getNativeSocketOptions(Channel ch) {
+      // TODO(zpencer): if netty-epoll, use reflection to call EpollSocketChannel.tcpInfo()
+      // And/or if some other low level socket support library is available, call it now.
+      return null;
+    }
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyWritableBuffer.java b/netty/src/main/java/io/grpc/netty/NettyWritableBuffer.java
new file mode 100644
index 0000000..b274057
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyWritableBuffer.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.internal.WritableBuffer;
+import io.netty.buffer.ByteBuf;
+
+/**
+ * The {@link WritableBuffer} used by the Netty transport.
+ */
+class NettyWritableBuffer implements WritableBuffer {
+
+  private final ByteBuf bytebuf;
+
+  NettyWritableBuffer(ByteBuf bytebuf) {
+    this.bytebuf = bytebuf;
+  }
+
+  @Override
+  public void write(byte[] src, int srcIndex, int length) {
+    bytebuf.writeBytes(src, srcIndex, length);
+  }
+
+  @Override
+  public void write(byte b) {
+    bytebuf.writeByte(b);
+  }
+
+  @Override
+  public int writableBytes() {
+    return bytebuf.writableBytes();
+  }
+
+  @Override
+  public int readableBytes() {
+    return bytebuf.readableBytes();
+  }
+
+  @Override
+  public void release() {
+    bytebuf.release();
+  }
+
+  ByteBuf bytebuf() {
+    return bytebuf;
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/NettyWritableBufferAllocator.java b/netty/src/main/java/io/grpc/netty/NettyWritableBufferAllocator.java
new file mode 100644
index 0000000..d0db923
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/NettyWritableBufferAllocator.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.internal.WritableBuffer;
+import io.grpc.internal.WritableBufferAllocator;
+import io.netty.buffer.ByteBufAllocator;
+
+/**
+ * The default allocator for {@link NettyWritableBuffer}s used by the Netty transport. We set a
+ * minimum bound to avoid unnecessary re-allocation for small follow-on writes and to facilitate
+ * Netty's caching of buffer objects for small writes. We set an upper-bound to avoid allocations
+ * outside of the arena-pool which are orders of magnitude slower. The Netty transport can receive
+ * buffers of arbitrary size and will chunk them based on flow-control so there is no transport
+ * requirement for an upper bound.
+ *
+ * <p>Note: It is assumed that most applications will be using Netty's direct buffer pools for
+ * maximum performance.
+ */
+class NettyWritableBufferAllocator implements WritableBufferAllocator {
+
+  // Use 4k as our minimum buffer size.
+  private static final int MIN_BUFFER = 4096;
+
+  // Set the maximum buffer size to 1MB
+  private static final int MAX_BUFFER = 1024 * 1024;
+
+  private final ByteBufAllocator allocator;
+
+  NettyWritableBufferAllocator(ByteBufAllocator allocator) {
+    this.allocator = allocator;
+  }
+
+  @Override
+  public WritableBuffer allocate(int capacityHint) {
+    capacityHint = Math.min(MAX_BUFFER, Math.max(MIN_BUFFER, capacityHint));
+    return new NettyWritableBuffer(allocator.buffer(capacityHint, capacityHint));
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java
new file mode 100644
index 0000000..132e96a
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.Internal;
+import io.netty.channel.ChannelHandler;
+import io.netty.util.AsciiString;
+
+/**
+ * A class that provides a Netty handler to control protocol negotiation.
+ */
+@Internal
+public interface ProtocolNegotiator {
+
+  /**
+   * The Netty handler to control the protocol negotiation.
+   */
+  interface Handler extends ChannelHandler {
+    /**
+     * The HTTP/2 scheme to be used when sending {@code HEADERS}.
+     */
+    AsciiString scheme();
+  }
+
+  /**
+   * Creates a new handler to control the protocol negotiation. Once the negotiation has completed
+   * successfully, the provided handler is installed. Must call {@code
+   * grpcHandler.onHandleProtocolNegotiationCompleted()} at certain point if the negotiation has
+   * completed successfully.
+   */
+  Handler newHandler(GrpcHttp2ConnectionHandler grpcHandler);
+}
diff --git a/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java
new file mode 100644
index 0000000..3a13207
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/ProtocolNegotiators.java
@@ -0,0 +1,754 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.netty.GrpcSslContexts.NEXT_PROTOCOL_VERSIONS;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import io.grpc.Attributes;
+import io.grpc.CallCredentials;
+import io.grpc.Grpc;
+import io.grpc.Internal;
+import io.grpc.InternalChannelz;
+import io.grpc.SecurityLevel;
+import io.grpc.Status;
+import io.grpc.internal.GrpcUtil;
+import io.netty.channel.ChannelDuplexHandler;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerAdapter;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandler;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.codec.http.DefaultHttpRequest;
+import io.netty.handler.codec.http.HttpClientCodec;
+import io.netty.handler.codec.http.HttpClientUpgradeHandler;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpVersion;
+import io.netty.handler.codec.http2.Http2ClientUpgradeCodec;
+import io.netty.handler.proxy.HttpProxyHandler;
+import io.netty.handler.proxy.ProxyConnectionEvent;
+import io.netty.handler.proxy.ProxyHandler;
+import io.netty.handler.ssl.OpenSsl;
+import io.netty.handler.ssl.OpenSslEngine;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.handler.ssl.SslHandshakeCompletionEvent;
+import io.netty.util.AsciiString;
+import io.netty.util.ReferenceCountUtil;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Queue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSession;
+
+/**
+ * Common {@link ProtocolNegotiator}s used by gRPC.
+ */
+@Internal
+public final class ProtocolNegotiators {
+  private static final Logger log = Logger.getLogger(ProtocolNegotiators.class.getName());
+
+  private ProtocolNegotiators() {
+  }
+
+  /**
+   * Create a server plaintext handler for gRPC.
+   */
+  public static ProtocolNegotiator serverPlaintext() {
+    return new ProtocolNegotiator() {
+      @Override
+      public Handler newHandler(final GrpcHttp2ConnectionHandler handler) {
+        class PlaintextHandler extends ChannelHandlerAdapter implements Handler {
+          @Override
+          public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+            // Set sttributes before replace to be sure we pass it before accepting any requests.
+            handler.handleProtocolNegotiationCompleted(Attributes.newBuilder()
+                .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress())
+                .build(),
+                /*securityInfo=*/ null);
+            // Just replace this handler with the gRPC handler.
+            ctx.pipeline().replace(this, null, handler);
+          }
+
+          @Override
+          public AsciiString scheme() {
+            return Utils.HTTP;
+          }
+        }
+
+        return new PlaintextHandler();
+      }
+    };
+  }
+
+  /**
+   * Create a server TLS handler for HTTP/2 capable of using ALPN/NPN.
+   */
+  public static ProtocolNegotiator serverTls(final SslContext sslContext) {
+    Preconditions.checkNotNull(sslContext, "sslContext");
+    return new ProtocolNegotiator() {
+      @Override
+      public Handler newHandler(GrpcHttp2ConnectionHandler handler) {
+        return new ServerTlsHandler(sslContext, handler);
+      }
+    };
+  }
+
+  @VisibleForTesting
+  static final class ServerTlsHandler extends ChannelInboundHandlerAdapter
+      implements ProtocolNegotiator.Handler {
+    private final GrpcHttp2ConnectionHandler grpcHandler;
+    private final SslContext sslContext;
+
+    ServerTlsHandler(SslContext sslContext, GrpcHttp2ConnectionHandler grpcHandler) {
+      this.sslContext = sslContext;
+      this.grpcHandler = grpcHandler;
+    }
+
+    @Override
+    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+      super.handlerAdded(ctx);
+
+      SSLEngine sslEngine = sslContext.newEngine(ctx.alloc());
+      ctx.pipeline().addFirst(new SslHandler(sslEngine, false));
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+      fail(ctx, cause);
+    }
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+      if (evt instanceof SslHandshakeCompletionEvent) {
+        SslHandshakeCompletionEvent handshakeEvent = (SslHandshakeCompletionEvent) evt;
+        if (handshakeEvent.isSuccess()) {
+          if (NEXT_PROTOCOL_VERSIONS.contains(sslHandler(ctx.pipeline()).applicationProtocol())) {
+            SSLSession session = sslHandler(ctx.pipeline()).engine().getSession();
+            // Successfully negotiated the protocol.
+            // Notify about completion and pass down SSLSession in attributes.
+            grpcHandler.handleProtocolNegotiationCompleted(
+                Attributes.newBuilder()
+                    .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, session)
+                    .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress())
+                    .build(),
+                new InternalChannelz.Security(new InternalChannelz.Tls(session)));
+            // Replace this handler with the GRPC handler.
+            ctx.pipeline().replace(this, null, grpcHandler);
+          } else {
+            fail(ctx, new Exception(
+                "Failed protocol negotiation: Unable to find compatible protocol."));
+          }
+        } else {
+          fail(ctx, handshakeEvent.cause());
+        }
+      }
+      super.userEventTriggered(ctx, evt);
+    }
+
+    private SslHandler sslHandler(ChannelPipeline pipeline) {
+      return pipeline.get(SslHandler.class);
+    }
+
+    private void fail(ChannelHandlerContext ctx, Throwable exception) {
+      logSslEngineDetails(Level.FINE, ctx, "TLS negotiation failed for new client.", exception);
+      ctx.close();
+    }
+
+    @Override
+    public AsciiString scheme() {
+      return Utils.HTTPS;
+    }
+  }
+
+  /**
+   * Returns a {@link ProtocolNegotiator} that does HTTP CONNECT proxy negotiation.
+   */
+  public static ProtocolNegotiator httpProxy(final SocketAddress proxyAddress,
+      final @Nullable String proxyUsername, final @Nullable String proxyPassword,
+      final ProtocolNegotiator negotiator) {
+    Preconditions.checkNotNull(proxyAddress, "proxyAddress");
+    Preconditions.checkNotNull(negotiator, "negotiator");
+    class ProxyNegotiator implements ProtocolNegotiator {
+      @Override
+      public Handler newHandler(GrpcHttp2ConnectionHandler http2Handler) {
+        HttpProxyHandler proxyHandler;
+        if (proxyUsername == null || proxyPassword == null) {
+          proxyHandler = new HttpProxyHandler(proxyAddress);
+        } else {
+          proxyHandler = new HttpProxyHandler(proxyAddress, proxyUsername, proxyPassword);
+        }
+        return new BufferUntilProxyTunnelledHandler(
+            proxyHandler, negotiator.newHandler(http2Handler));
+      }
+    }
+
+    return new ProxyNegotiator();
+  }
+
+  /**
+   * Buffers all writes until the HTTP CONNECT tunnel is established.
+   */
+  static final class BufferUntilProxyTunnelledHandler extends AbstractBufferingHandler
+      implements ProtocolNegotiator.Handler {
+    private final ProtocolNegotiator.Handler originalHandler;
+
+    public BufferUntilProxyTunnelledHandler(
+        ProxyHandler proxyHandler, ProtocolNegotiator.Handler handler) {
+      super(proxyHandler, handler);
+      this.originalHandler = handler;
+    }
+
+
+    @Override
+    public AsciiString scheme() {
+      return originalHandler.scheme();
+    }
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+      if (evt instanceof ProxyConnectionEvent) {
+        writeBufferedAndRemove(ctx);
+      }
+      super.userEventTriggered(ctx, evt);
+    }
+
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+      fail(ctx, unavailableException("Connection broken while trying to CONNECT through proxy"));
+      super.channelInactive(ctx);
+    }
+
+    @Override
+    public void close(ChannelHandlerContext ctx, ChannelPromise future) throws Exception {
+      if (ctx.channel().isActive()) { // This may be a notification that the socket was closed
+        fail(ctx, unavailableException("Channel closed while trying to CONNECT through proxy"));
+      }
+      super.close(ctx, future);
+    }
+  }
+
+  /**
+   * Returns a {@link ProtocolNegotiator} that ensures the pipeline is set up so that TLS will
+   * be negotiated, the {@code handler} is added and writes to the {@link io.netty.channel.Channel}
+   * may happen immediately, even before the TLS Handshake is complete.
+   */
+  public static ProtocolNegotiator tls(SslContext sslContext) {
+    return new TlsNegotiator(sslContext);
+  }
+
+  @VisibleForTesting
+  static final class TlsNegotiator implements ProtocolNegotiator {
+    private final SslContext sslContext;
+
+    TlsNegotiator(SslContext sslContext) {
+      this.sslContext = checkNotNull(sslContext, "sslContext");
+    }
+
+    @VisibleForTesting
+    HostPort parseAuthority(String authority) {
+      URI uri = GrpcUtil.authorityToUri(Preconditions.checkNotNull(authority, "authority"));
+      String host;
+      int port;
+      if (uri.getHost() != null) {
+        host = uri.getHost();
+        port = uri.getPort();
+      } else {
+        /*
+         * Implementation note: We pick -1 as the port here rather than deriving it from the
+         * original socket address.  The SSL engine doens't use this port number when contacting the
+         * remote server, but rather it is used for other things like SSL Session caching.  When an
+         * invalid authority is provided (like "bad_cert"), picking the original port and passing it
+         * in would mean that the port might used under the assumption that it was correct.   By
+         * using -1 here, it forces the SSL implementation to treat it as invalid.
+         */
+        host = authority;
+        port = -1;
+      }
+      return new HostPort(host, port);
+    }
+
+    @Override
+    public Handler newHandler(GrpcHttp2ConnectionHandler handler) {
+      final HostPort hostPort = parseAuthority(handler.getAuthority());
+
+      ChannelHandler sslBootstrap = new ChannelHandlerAdapter() {
+        @Override
+        public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+          SSLEngine sslEngine = sslContext.newEngine(ctx.alloc(), hostPort.host, hostPort.port);
+          SSLParameters sslParams = sslEngine.getSSLParameters();
+          sslParams.setEndpointIdentificationAlgorithm("HTTPS");
+          sslEngine.setSSLParameters(sslParams);
+          ctx.pipeline().replace(this, null, new SslHandler(sslEngine, false));
+        }
+      };
+      return new BufferUntilTlsNegotiatedHandler(sslBootstrap, handler);
+    }
+  }
+
+  /** A tuple of (host, port). */
+  @VisibleForTesting
+  static final class HostPort {
+    final String host;
+    final int port;
+
+    public HostPort(String host, int port) {
+      this.host = host;
+      this.port = port;
+    }
+  }
+
+  /**
+   * Returns a {@link ProtocolNegotiator} used for upgrading to HTTP/2 from HTTP/1.x.
+   */
+  public static ProtocolNegotiator plaintextUpgrade() {
+    return new PlaintextUpgradeNegotiator();
+  }
+
+  static final class PlaintextUpgradeNegotiator implements ProtocolNegotiator {
+    @Override
+    public Handler newHandler(GrpcHttp2ConnectionHandler handler) {
+      // Register the plaintext upgrader
+      Http2ClientUpgradeCodec upgradeCodec = new Http2ClientUpgradeCodec(handler);
+      HttpClientCodec httpClientCodec = new HttpClientCodec();
+      final HttpClientUpgradeHandler upgrader =
+          new HttpClientUpgradeHandler(httpClientCodec, upgradeCodec, 1000);
+      return new BufferingHttp2UpgradeHandler(upgrader, handler);
+    }
+  }
+
+  /**
+   * Returns a {@link ChannelHandler} that ensures that the {@code handler} is added to the
+   * pipeline writes to the {@link io.netty.channel.Channel} may happen immediately, even before it
+   * is active.
+   */
+  public static ProtocolNegotiator plaintext() {
+    return new PlaintextNegotiator();
+  }
+
+  static final class PlaintextNegotiator implements ProtocolNegotiator {
+    @Override
+    public Handler newHandler(GrpcHttp2ConnectionHandler handler) {
+      return new BufferUntilChannelActiveHandler(handler);
+    }
+  }
+
+  private static RuntimeException unavailableException(String msg) {
+    return Status.UNAVAILABLE.withDescription(msg).asRuntimeException();
+  }
+
+  @VisibleForTesting
+  static void logSslEngineDetails(Level level, ChannelHandlerContext ctx, String msg,
+                                                @Nullable Throwable t) {
+    if (!log.isLoggable(level)) {
+      return;
+    }
+
+    SslHandler sslHandler = ctx.pipeline().get(SslHandler.class);
+    SSLEngine engine = sslHandler.engine();
+
+    StringBuilder builder = new StringBuilder(msg);
+    builder.append("\nSSLEngine Details: [\n");
+    if (engine instanceof OpenSslEngine) {
+      builder.append("    OpenSSL, ");
+      builder.append("Version: 0x").append(Integer.toHexString(OpenSsl.version()));
+      builder.append(" (").append(OpenSsl.versionString()).append("), ");
+      builder.append("ALPN supported: ").append(OpenSsl.isAlpnSupported());
+    } else if (JettyTlsUtil.isJettyAlpnConfigured()) {
+      builder.append("    Jetty ALPN");
+    } else if (JettyTlsUtil.isJettyNpnConfigured()) {
+      builder.append("    Jetty NPN");
+    } else if (JettyTlsUtil.isJava9AlpnAvailable()) {
+      builder.append("    JDK9 ALPN");
+    }
+    builder.append("\n    TLS Protocol: ");
+    builder.append(engine.getSession().getProtocol());
+    builder.append("\n    Application Protocol: ");
+    builder.append(sslHandler.applicationProtocol());
+    builder.append("\n    Need Client Auth: " );
+    builder.append(engine.getNeedClientAuth());
+    builder.append("\n    Want Client Auth: ");
+    builder.append(engine.getWantClientAuth());
+    builder.append("\n    Supported protocols=");
+    builder.append(Arrays.toString(engine.getSupportedProtocols()));
+    builder.append("\n    Enabled protocols=");
+    builder.append(Arrays.toString(engine.getEnabledProtocols()));
+    builder.append("\n    Supported ciphers=");
+    builder.append(Arrays.toString(engine.getSupportedCipherSuites()));
+    builder.append("\n    Enabled ciphers=");
+    builder.append(Arrays.toString(engine.getEnabledCipherSuites()));
+    builder.append("\n]");
+
+    log.log(level, builder.toString(), t);
+  }
+
+  /**
+   * Buffers all writes until either {@link #writeBufferedAndRemove(ChannelHandlerContext)} or
+   * {@link #fail(ChannelHandlerContext, Throwable)} is called. This handler allows us to
+   * write to a {@link io.netty.channel.Channel} before we are allowed to write to it officially
+   * i.e.  before it's active or the TLS Handshake is complete.
+   */
+  public abstract static class AbstractBufferingHandler extends ChannelDuplexHandler {
+
+    private ChannelHandler[] handlers;
+    private Queue<ChannelWrite> bufferedWrites = new ArrayDeque<ChannelWrite>();
+    private boolean writing;
+    private boolean flushRequested;
+    private Throwable failCause;
+
+    /**
+     * @param handlers the ChannelHandlers are added to the pipeline on channelRegistered and
+     *                 before this handler.
+     */
+    protected AbstractBufferingHandler(ChannelHandler... handlers) {
+      this.handlers = handlers;
+    }
+
+    /**
+     * When this channel is registered, we will add all the ChannelHandlers passed into our
+     * constructor to the pipeline.
+     */
+    @Override
+    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
+      /**
+       * This check is necessary as a channel may be registered with different event loops during it
+       * lifetime and we only want to configure it once.
+       */
+      if (handlers != null) {
+        for (ChannelHandler handler : handlers) {
+          ctx.pipeline().addBefore(ctx.name(), null, handler);
+        }
+        ChannelHandler handler0 = handlers[0];
+        ChannelHandlerContext handler0Ctx = ctx.pipeline().context(handlers[0]);
+        handlers = null;
+        if (handler0Ctx != null) { // The handler may have removed itself immediately
+          if (handler0 instanceof ChannelInboundHandler) {
+            ((ChannelInboundHandler) handler0).channelRegistered(handler0Ctx);
+          } else {
+            handler0Ctx.fireChannelRegistered();
+          }
+        }
+      } else {
+        super.channelRegistered(ctx);
+      }
+    }
+
+    /**
+     * Do not rely on channel handlers to propagate exceptions to us.
+     * {@link NettyClientHandler} is an example of a class that does not propagate exceptions.
+     * Add a listener to the connect future directly and do appropriate error handling.
+     */
+    @Override
+    public void connect(final ChannelHandlerContext ctx, SocketAddress remoteAddress,
+        SocketAddress localAddress, ChannelPromise promise) throws Exception {
+      super.connect(ctx, remoteAddress, localAddress, promise);
+      promise.addListener(new ChannelFutureListener() {
+        @Override
+        public void operationComplete(ChannelFuture future) throws Exception {
+          if (!future.isSuccess()) {
+            fail(ctx, future.cause());
+          }
+        }
+      });
+    }
+
+    /**
+     * If we encounter an exception, then notify all buffered writes that we failed.
+     */
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+      fail(ctx, cause);
+    }
+
+    /**
+     * If this channel becomes inactive, then notify all buffered writes that we failed.
+     */
+    @Override
+    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
+      fail(ctx, unavailableException("Connection broken while performing protocol negotiation"));
+      super.channelInactive(ctx);
+    }
+
+    /**
+     * Buffers the write until either {@link #writeBufferedAndRemove(ChannelHandlerContext)} is
+     * called, or we have somehow failed. If we have already failed in the past, then the write
+     * will fail immediately.
+     */
+    @Override
+    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
+        throws Exception {
+      /**
+       * This check handles a race condition between Channel.write (in the calling thread) and the
+       * removal of this handler (in the event loop thread).
+       * The problem occurs in e.g. this sequence:
+       * 1) [caller thread] The write method identifies the context for this handler
+       * 2) [event loop] This handler removes itself from the pipeline
+       * 3) [caller thread] The write method delegates to the invoker to call the write method in
+       *    the event loop thread. When this happens, we identify that this handler has been
+       *    removed with "bufferedWrites == null".
+       */
+      if (failCause != null) {
+        promise.setFailure(failCause);
+        ReferenceCountUtil.release(msg);
+      } else if (bufferedWrites == null) {
+        super.write(ctx, msg, promise);
+      } else {
+        bufferedWrites.add(new ChannelWrite(msg, promise));
+      }
+    }
+
+    /**
+     * Calls to this method will not trigger an immediate flush. The flush will be deferred until
+     * {@link #writeBufferedAndRemove(ChannelHandlerContext)}.
+     */
+    @Override
+    public void flush(ChannelHandlerContext ctx) {
+      /**
+       * Swallowing any flushes is not only an optimization but also required
+       * for the SslHandler to work correctly. If the SslHandler receives multiple
+       * flushes while the handshake is still ongoing, then the handshake "randomly"
+       * times out. Not sure at this point why this is happening. Doing a single flush
+       * seems to work but multiple flushes don't ...
+       */
+      if (bufferedWrites == null) {
+        ctx.flush();
+      } else {
+        flushRequested = true;
+      }
+    }
+
+    /**
+     * If we are still performing protocol negotiation, then this will propagate failures to all
+     * buffered writes.
+     */
+    @Override
+    public void close(ChannelHandlerContext ctx, ChannelPromise future) throws Exception {
+      if (ctx.channel().isActive()) { // This may be a notification that the socket was closed
+        fail(ctx, unavailableException("Channel closed while performing protocol negotiation"));
+      }
+      super.close(ctx, future);
+    }
+
+    /**
+     * Propagate failures to all buffered writes.
+     */
+    protected final void fail(ChannelHandlerContext ctx, Throwable cause) {
+      if (failCause == null) {
+        failCause = cause;
+      }
+      if (bufferedWrites != null) {
+        while (!bufferedWrites.isEmpty()) {
+          ChannelWrite write = bufferedWrites.poll();
+          write.promise.setFailure(cause);
+          ReferenceCountUtil.release(write.msg);
+        }
+        bufferedWrites = null;
+      }
+
+      /**
+       * In case something goes wrong ensure that the channel gets closed as the
+       * NettyClientTransport relies on the channel's close future to get completed.
+       */
+      ctx.close();
+    }
+
+    protected final void writeBufferedAndRemove(ChannelHandlerContext ctx) {
+      if (!ctx.channel().isActive() || writing) {
+        return;
+      }
+      // Make sure that method can't be reentered, so that the ordering
+      // in the queue can't be messed up.
+      writing = true;
+      while (!bufferedWrites.isEmpty()) {
+        ChannelWrite write = bufferedWrites.poll();
+        ctx.write(write.msg, write.promise);
+      }
+      assert bufferedWrites.isEmpty();
+      bufferedWrites = null;
+      if (flushRequested) {
+        ctx.flush();
+      }
+      // Removal has to happen last as the above writes will likely trigger
+      // new writes that have to be added to the end of queue in order to not
+      // mess up the ordering.
+      ctx.pipeline().remove(this);
+    }
+
+    private static class ChannelWrite {
+      Object msg;
+      ChannelPromise promise;
+
+      ChannelWrite(Object msg, ChannelPromise promise) {
+        this.msg = msg;
+        this.promise = promise;
+      }
+    }
+  }
+
+  /**
+   * Buffers all writes until the TLS Handshake is complete.
+   */
+  private static class BufferUntilTlsNegotiatedHandler extends AbstractBufferingHandler
+      implements ProtocolNegotiator.Handler {
+
+    private final GrpcHttp2ConnectionHandler grpcHandler;
+
+    BufferUntilTlsNegotiatedHandler(
+        ChannelHandler bootstrapHandler, GrpcHttp2ConnectionHandler grpcHandler) {
+      super(bootstrapHandler);
+      this.grpcHandler = grpcHandler;
+    }
+
+    @Override
+    public AsciiString scheme() {
+      return Utils.HTTPS;
+    }
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+      if (evt instanceof SslHandshakeCompletionEvent) {
+        SslHandshakeCompletionEvent handshakeEvent = (SslHandshakeCompletionEvent) evt;
+        if (handshakeEvent.isSuccess()) {
+          SslHandler handler = ctx.pipeline().get(SslHandler.class);
+          if (NEXT_PROTOCOL_VERSIONS.contains(handler.applicationProtocol())) {
+            // Successfully negotiated the protocol.
+            logSslEngineDetails(Level.FINER, ctx, "TLS negotiation succeeded.", null);
+
+            // Wait until negotiation is complete to add gRPC.   If added too early, HTTP/2 writes
+            // will fail before we see the userEvent, and the channel is closed down prematurely.
+            ctx.pipeline().addBefore(ctx.name(), null, grpcHandler);
+
+            SSLSession session = handler.engine().getSession();
+            // Successfully negotiated the protocol.
+            // Notify about completion and pass down SSLSession in attributes.
+            grpcHandler.handleProtocolNegotiationCompleted(
+                Attributes.newBuilder()
+                    .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, session)
+                    .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress())
+                    .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.PRIVACY_AND_INTEGRITY)
+                    .build(),
+                new InternalChannelz.Security(new InternalChannelz.Tls(session)));
+            writeBufferedAndRemove(ctx);
+          } else {
+            Exception ex = new Exception(
+                "Failed ALPN negotiation: Unable to find compatible protocol.");
+            logSslEngineDetails(Level.FINE, ctx, "TLS negotiation failed.", ex);
+            fail(ctx, ex);
+          }
+        } else {
+          fail(ctx, handshakeEvent.cause());
+        }
+      }
+      super.userEventTriggered(ctx, evt);
+    }
+  }
+
+  /**
+   * Buffers all writes until the {@link io.netty.channel.Channel} is active.
+   */
+  private static class BufferUntilChannelActiveHandler extends AbstractBufferingHandler
+      implements ProtocolNegotiator.Handler {
+
+    private final GrpcHttp2ConnectionHandler handler;
+
+    BufferUntilChannelActiveHandler(GrpcHttp2ConnectionHandler handler) {
+      super(handler);
+      this.handler = handler;
+    }
+
+    @Override
+    public AsciiString scheme() {
+      return Utils.HTTP;
+    }
+
+    @Override
+    public void handlerAdded(ChannelHandlerContext ctx) {
+      writeBufferedAndRemove(ctx);
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+      writeBufferedAndRemove(ctx);
+      handler.handleProtocolNegotiationCompleted(
+          Attributes
+              .newBuilder()
+              .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress())
+              .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.NONE)
+              .build(),
+          /*securityInfo=*/ null);
+      super.channelActive(ctx);
+    }
+  }
+
+  /**
+   * Buffers all writes until the HTTP to HTTP/2 upgrade is complete.
+   */
+  private static class BufferingHttp2UpgradeHandler extends AbstractBufferingHandler
+      implements ProtocolNegotiator.Handler {
+
+    private final GrpcHttp2ConnectionHandler grpcHandler;
+
+    BufferingHttp2UpgradeHandler(ChannelHandler handler, GrpcHttp2ConnectionHandler grpcHandler) {
+      super(handler);
+      this.grpcHandler = grpcHandler;
+    }
+
+    @Override
+    public AsciiString scheme() {
+      return Utils.HTTP;
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) throws Exception {
+      // Trigger the HTTP/1.1 plaintext upgrade protocol by issuing an HTTP request
+      // which causes the upgrade headers to be added
+      DefaultHttpRequest upgradeTrigger =
+          new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
+      ctx.writeAndFlush(upgradeTrigger);
+      super.channelActive(ctx);
+    }
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+      if (evt == HttpClientUpgradeHandler.UpgradeEvent.UPGRADE_SUCCESSFUL) {
+        writeBufferedAndRemove(ctx);
+        grpcHandler.handleProtocolNegotiationCompleted(
+            Attributes
+                .newBuilder()
+                .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, ctx.channel().remoteAddress())
+                .set(CallCredentials.ATTR_SECURITY_LEVEL, SecurityLevel.NONE)
+                .build(),
+            /*securityInfo=*/ null);
+      } else if (evt == HttpClientUpgradeHandler.UpgradeEvent.UPGRADE_REJECTED) {
+        fail(ctx, unavailableException("HTTP/2 upgrade rejected"));
+      }
+      super.userEventTriggered(ctx, evt);
+    }
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/SendGrpcFrameCommand.java b/netty/src/main/java/io/grpc/netty/SendGrpcFrameCommand.java
new file mode 100644
index 0000000..a6e829e
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/SendGrpcFrameCommand.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufHolder;
+import io.netty.buffer.DefaultByteBufHolder;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelPromise;
+
+/**
+ * Command sent from the transport to the Netty channel to send a GRPC frame to the remote endpoint.
+ */
+class SendGrpcFrameCommand extends DefaultByteBufHolder implements WriteQueue.QueuedCommand {
+  private final StreamIdHolder stream;
+  private final boolean endStream;
+
+  private ChannelPromise promise;
+
+  SendGrpcFrameCommand(StreamIdHolder stream, ByteBuf content, boolean endStream) {
+    super(content);
+    this.stream = stream;
+    this.endStream = endStream;
+  }
+
+  int streamId() {
+    return stream.id();
+  }
+
+  boolean endStream() {
+    return endStream;
+  }
+
+  @Override
+  public ByteBufHolder copy() {
+    return new SendGrpcFrameCommand(stream, content().copy(), endStream);
+  }
+
+  @Override
+  public ByteBufHolder duplicate() {
+    return new SendGrpcFrameCommand(stream, content().duplicate(), endStream);
+  }
+
+  @Override
+  public SendGrpcFrameCommand retain() {
+    super.retain();
+    return this;
+  }
+
+  @Override
+  public SendGrpcFrameCommand retain(int increment) {
+    super.retain(increment);
+    return this;
+  }
+
+  @Override
+  public SendGrpcFrameCommand touch() {
+    super.touch();
+    return this;
+  }
+
+  @Override
+  public SendGrpcFrameCommand touch(Object hint) {
+    super.touch(hint);
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null || !that.getClass().equals(SendGrpcFrameCommand.class)) {
+      return false;
+    }
+    SendGrpcFrameCommand thatCmd = (SendGrpcFrameCommand) that;
+    return thatCmd.stream.equals(stream) && thatCmd.endStream == endStream
+        && thatCmd.content().equals(content());
+  }
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName() + "(streamId=" + streamId()
+        + ", endStream=" + endStream + ", content=" + content()
+        + ")";
+  }
+
+  @Override
+  public int hashCode() {
+    int hash = content().hashCode();
+    hash = hash * 31 + stream.hashCode();
+    if (endStream) {
+      hash = -hash;
+    }
+    return hash;
+  }
+
+  @Override
+  public ChannelPromise promise() {
+    return promise;
+  }
+
+  @Override
+  public void promise(ChannelPromise promise) {
+    this.promise = promise;
+  }
+
+  @Override
+  public final void run(Channel channel) {
+    channel.write(this, promise);
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/SendPingCommand.java b/netty/src/main/java/io/grpc/netty/SendPingCommand.java
new file mode 100644
index 0000000..44545e0
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/SendPingCommand.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.internal.ClientTransport.PingCallback;
+import java.util.concurrent.Executor;
+
+/**
+ * Command sent from the transport to the Netty channel to send a PING frame.
+ */
+class SendPingCommand extends WriteQueue.AbstractQueuedCommand {
+  private final PingCallback callback;
+  private final Executor executor;
+
+  SendPingCommand(PingCallback callback, Executor executor) {
+    this.callback = callback;
+    this.executor = executor;
+  }
+
+  PingCallback callback() {
+    return callback;
+  }
+
+  Executor executor() {
+    return executor;
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/SendResponseHeadersCommand.java b/netty/src/main/java/io/grpc/netty/SendResponseHeadersCommand.java
new file mode 100644
index 0000000..c2e66b2
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/SendResponseHeadersCommand.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import io.grpc.Status;
+import io.netty.handler.codec.http2.Http2Headers;
+
+/**
+ * Command sent from the transport to the Netty channel to send response headers to the client.
+ */
+class SendResponseHeadersCommand extends WriteQueue.AbstractQueuedCommand {
+  private final StreamIdHolder stream;
+  private final Http2Headers headers;
+  private final Status status;
+
+  private SendResponseHeadersCommand(StreamIdHolder stream, Http2Headers headers, Status status) {
+    this.stream = Preconditions.checkNotNull(stream, "stream");
+    this.headers = Preconditions.checkNotNull(headers, "headers");
+    this.status = status;
+  }
+
+  static SendResponseHeadersCommand createHeaders(StreamIdHolder stream, Http2Headers headers) {
+    return new SendResponseHeadersCommand(stream, headers, null);
+  }
+
+  static SendResponseHeadersCommand createTrailers(
+      StreamIdHolder stream, Http2Headers headers, Status status) {
+    return new SendResponseHeadersCommand(
+        stream, headers, Preconditions.checkNotNull(status, "status"));
+  }
+
+  StreamIdHolder stream() {
+    return stream;
+  }
+
+  Http2Headers headers() {
+    return headers;
+  }
+
+  boolean endOfStream() {
+    return status != null;
+  }
+
+  Status status() {
+    return status;
+  }
+
+  @Override
+  public boolean equals(Object that) {
+    if (that == null || !that.getClass().equals(SendResponseHeadersCommand.class)) {
+      return false;
+    }
+    SendResponseHeadersCommand thatCmd = (SendResponseHeadersCommand) that;
+    return thatCmd.stream.equals(stream)
+        && thatCmd.headers.equals(headers)
+        && thatCmd.status.equals(status);
+  }
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName() + "(stream=" + stream.id() + ", headers=" + headers
+        + ", status=" + status + ")";
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(stream, status);
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/StreamIdHolder.java b/netty/src/main/java/io/grpc/netty/StreamIdHolder.java
new file mode 100644
index 0000000..80e0449
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/StreamIdHolder.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+/** Container for stream ids. */
+interface StreamIdHolder {
+  /**
+   * Returns the id.
+   */
+  int id();
+}
diff --git a/netty/src/main/java/io/grpc/netty/Utils.java b/netty/src/main/java/io/grpc/netty/Utils.java
new file mode 100644
index 0000000..3276592
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/Utils.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static io.grpc.internal.GrpcUtil.CONTENT_TYPE_KEY;
+import static io.grpc.internal.TransportFrameUtil.toHttp2Headers;
+import static io.grpc.internal.TransportFrameUtil.toRawSerializedHeaders;
+import static io.netty.channel.ChannelOption.SO_LINGER;
+import static io.netty.channel.ChannelOption.SO_TIMEOUT;
+import static io.netty.util.CharsetUtil.UTF_8;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalMetadata;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.SharedResourceHolder.Resource;
+import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2InboundHeaders;
+import io.grpc.netty.NettySocketSupport.NativeSocketOptions;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelConfig;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.handler.codec.http2.Http2Exception;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.util.AsciiString;
+import io.netty.util.concurrent.DefaultThreadFactory;
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Common utility methods.
+ */
+@VisibleForTesting
+class Utils {
+
+  public static final AsciiString STATUS_OK = AsciiString.of("200");
+  public static final AsciiString HTTP_METHOD = AsciiString.of(GrpcUtil.HTTP_METHOD);
+  public static final AsciiString HTTP_GET_METHOD = AsciiString.of("GET");
+  public static final AsciiString HTTPS = AsciiString.of("https");
+  public static final AsciiString HTTP = AsciiString.of("http");
+  public static final AsciiString CONTENT_TYPE_HEADER = AsciiString.of(CONTENT_TYPE_KEY.name());
+  public static final AsciiString CONTENT_TYPE_GRPC = AsciiString.of(GrpcUtil.CONTENT_TYPE_GRPC);
+  public static final AsciiString TE_HEADER = AsciiString.of(GrpcUtil.TE_HEADER.name());
+  public static final AsciiString TE_TRAILERS = AsciiString.of(GrpcUtil.TE_TRAILERS);
+  public static final AsciiString USER_AGENT = AsciiString.of(GrpcUtil.USER_AGENT_KEY.name());
+
+  public static final Resource<EventLoopGroup> DEFAULT_BOSS_EVENT_LOOP_GROUP =
+      new DefaultEventLoopGroupResource(1, "grpc-default-boss-ELG");
+
+  public static final Resource<EventLoopGroup> DEFAULT_WORKER_EVENT_LOOP_GROUP =
+      new DefaultEventLoopGroupResource(0, "grpc-default-worker-ELG");
+
+  @VisibleForTesting
+  static boolean validateHeaders = false;
+
+  public static Metadata convertHeaders(Http2Headers http2Headers) {
+    if (http2Headers instanceof GrpcHttp2InboundHeaders) {
+      GrpcHttp2InboundHeaders h = (GrpcHttp2InboundHeaders) http2Headers;
+      return InternalMetadata.newMetadata(h.numHeaders(), h.namesAndValues());
+    }
+    return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers));
+  }
+
+  private static byte[][] convertHeadersToArray(Http2Headers http2Headers) {
+    // The Netty AsciiString class is really just a wrapper around a byte[] and supports
+    // arbitrary binary data, not just ASCII.
+    byte[][] headerValues = new byte[http2Headers.size() * 2][];
+    int i = 0;
+    for (Map.Entry<CharSequence, CharSequence> entry : http2Headers) {
+      headerValues[i++] = bytes(entry.getKey());
+      headerValues[i++] = bytes(entry.getValue());
+    }
+    return toRawSerializedHeaders(headerValues);
+  }
+
+  private static byte[] bytes(CharSequence seq) {
+    if (seq instanceof AsciiString) {
+      // Fast path - sometimes copy.
+      AsciiString str = (AsciiString) seq;
+      return str.isEntireArrayUsed() ? str.array() : str.toByteArray();
+    }
+    // Slow path - copy.
+    return seq.toString().getBytes(UTF_8);
+  }
+
+  public static Http2Headers convertClientHeaders(Metadata headers,
+      AsciiString scheme,
+      AsciiString defaultPath,
+      AsciiString authority,
+      AsciiString method,
+      AsciiString userAgent) {
+    Preconditions.checkNotNull(defaultPath, "defaultPath");
+    Preconditions.checkNotNull(authority, "authority");
+    Preconditions.checkNotNull(method, "method");
+
+    // Discard any application supplied duplicates of the reserved headers
+    headers.discardAll(CONTENT_TYPE_KEY);
+    headers.discardAll(GrpcUtil.TE_HEADER);
+    headers.discardAll(GrpcUtil.USER_AGENT_KEY);
+
+    return GrpcHttp2OutboundHeaders.clientRequestHeaders(
+        toHttp2Headers(headers),
+        authority,
+        defaultPath,
+        method,
+        scheme,
+        userAgent);
+  }
+
+  public static Http2Headers convertServerHeaders(Metadata headers) {
+    // Discard any application supplied duplicates of the reserved headers
+    headers.discardAll(CONTENT_TYPE_KEY);
+    headers.discardAll(GrpcUtil.TE_HEADER);
+    headers.discardAll(GrpcUtil.USER_AGENT_KEY);
+
+    return GrpcHttp2OutboundHeaders.serverResponseHeaders(toHttp2Headers(headers));
+  }
+
+  public static Metadata convertTrailers(Http2Headers http2Headers) {
+    if (http2Headers instanceof GrpcHttp2InboundHeaders) {
+      GrpcHttp2InboundHeaders h = (GrpcHttp2InboundHeaders) http2Headers;
+      return InternalMetadata.newMetadata(h.numHeaders(), h.namesAndValues());
+    }
+    return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers));
+  }
+
+  public static Http2Headers convertTrailers(Metadata trailers, boolean headersSent) {
+    if (!headersSent) {
+      return convertServerHeaders(trailers);
+    }
+    return GrpcHttp2OutboundHeaders.serverResponseTrailers(toHttp2Headers(trailers));
+  }
+
+  public static Status statusFromThrowable(Throwable t) {
+    Status s = Status.fromThrowable(t);
+    if (s.getCode() != Status.Code.UNKNOWN) {
+      return s;
+    }
+    if (t instanceof ClosedChannelException) {
+      // ClosedChannelException is used any time the Netty channel is closed. Proper error
+      // processing requires remembering the error that occurred before this one and using it
+      // instead.
+      //
+      // Netty uses an exception that has no stack trace, while we would never hope to show this to
+      // users, if it happens having the extra information may provide a small hint of where to
+      // look.
+      ClosedChannelException extraT = new ClosedChannelException();
+      extraT.initCause(t);
+      return Status.UNKNOWN.withDescription("channel closed").withCause(extraT);
+    }
+    if (t instanceof IOException) {
+      return Status.UNAVAILABLE.withDescription("io exception").withCause(t);
+    }
+    if (t instanceof Http2Exception) {
+      return Status.INTERNAL.withDescription("http2 exception").withCause(t);
+    }
+    return s;
+  }
+
+  private static class DefaultEventLoopGroupResource implements Resource<EventLoopGroup> {
+    private final String name;
+    private final int numEventLoops;
+
+    DefaultEventLoopGroupResource(int numEventLoops, String name) {
+      this.name = name;
+      this.numEventLoops = numEventLoops;
+    }
+
+    @Override
+    public EventLoopGroup create() {
+      // Use Netty's DefaultThreadFactory in order to get the benefit of FastThreadLocal.
+      boolean useDaemonThreads = true;
+      ThreadFactory threadFactory = new DefaultThreadFactory(name, useDaemonThreads);
+      int parallelism = numEventLoops == 0
+          ? Runtime.getRuntime().availableProcessors() * 2 : numEventLoops;
+      return new NioEventLoopGroup(parallelism, threadFactory);
+    }
+
+    @Override
+    public void close(EventLoopGroup instance) {
+      instance.shutdownGracefully(0, 0, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public String toString() {
+      return name;
+    }
+  }
+
+  static InternalChannelz.SocketOptions getSocketOptions(Channel channel) {
+    ChannelConfig config = channel.config();
+    InternalChannelz.SocketOptions.Builder b = new InternalChannelz.SocketOptions.Builder();
+
+    // The API allows returning null but not sure if it can happen in practice.
+    // Let's be paranoid and do null checking just in case.
+    Integer lingerSeconds = config.getOption(SO_LINGER);
+    if (lingerSeconds != null) {
+      b.setSocketOptionLingerSeconds(lingerSeconds);
+    }
+
+    Integer timeoutMillis = config.getOption(SO_TIMEOUT);
+    if (timeoutMillis != null) {
+      // in java, SO_TIMEOUT only applies to receiving
+      b.setSocketOptionTimeoutMillis(timeoutMillis);
+    }
+
+    for (Entry<ChannelOption<?>, Object> opt : config.getOptions().entrySet()) {
+      ChannelOption<?> key = opt.getKey();
+      // Constants are pooled, so there should only be one instance of each constant
+      if (key.equals(SO_LINGER) || key.equals(SO_TIMEOUT)) {
+        continue;
+      }
+      Object value = opt.getValue();
+      // zpencer: Can a netty option be null?
+      b.addOption(key.name(), String.valueOf(value));
+    }
+
+    NativeSocketOptions nativeOptions
+        = NettySocketSupport.getNativeSocketOptions(channel);
+    if (nativeOptions != null) {
+      b.setTcpInfo(nativeOptions.tcpInfo); // may be null
+      for (Entry<String, String> entry : nativeOptions.otherInfo.entrySet()) {
+        b.addOption(entry.getKey(), entry.getValue());
+      }
+    }
+    return b.build();
+  }
+
+  private Utils() {
+    // Prevents instantiation
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/WriteQueue.java b/netty/src/main/java/io/grpc/netty/WriteQueue.java
new file mode 100644
index 0000000..8e48cf4
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/WriteQueue.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelPromise;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A queue of pending writes to a {@link Channel} that is flushed as a single unit.
+ */
+class WriteQueue {
+
+  // Dequeue in chunks, so we don't have to acquire the queue's log too often.
+  @VisibleForTesting
+  static final int DEQUE_CHUNK_SIZE = 128;
+
+  /**
+   * {@link Runnable} used to schedule work onto the tail of the event loop.
+   */
+  private final Runnable later = new Runnable() {
+    @Override
+    public void run() {
+      flush();
+    }
+  };
+
+  private final Channel channel;
+  private final Queue<QueuedCommand> queue;
+  private final AtomicBoolean scheduled = new AtomicBoolean();
+
+  public WriteQueue(Channel channel) {
+    this.channel = Preconditions.checkNotNull(channel, "channel");
+    queue = new ConcurrentLinkedQueue<QueuedCommand>();
+  }
+
+  /**
+   * Schedule a flush on the channel.
+   */
+  void scheduleFlush() {
+    if (scheduled.compareAndSet(false, true)) {
+      // Add the queue to the tail of the event loop so writes will be executed immediately
+      // inside the event loop. Note DO NOT do channel.write outside the event loop as
+      // it will not wake up immediately without a flush.
+      channel.eventLoop().execute(later);
+    }
+  }
+
+  /**
+   * Enqueue a write command on the channel.
+   *
+   * @param command a write to be executed on the channel.
+   * @param flush true if a flush of the write should be schedule, false if a later call to
+   *              enqueue will schedule the flush.
+   */
+  @CanIgnoreReturnValue
+  ChannelFuture enqueue(QueuedCommand command, boolean flush) {
+    // Detect erroneous code that tries to reuse command objects.
+    Preconditions.checkArgument(command.promise() == null, "promise must not be set on command");
+
+    ChannelPromise promise = channel.newPromise();
+    command.promise(promise);
+    queue.add(command);
+    if (flush) {
+      scheduleFlush();
+    }
+    return promise;
+  }
+
+  /**
+   * Enqueue the runnable. It is not safe for another thread to queue an Runnable directly to the
+   * event loop, because it will be out-of-order with writes. This method allows the Runnable to be
+   * processed in-order with writes.
+   */
+  void enqueue(Runnable runnable, boolean flush) {
+    queue.add(new RunnableCommand(runnable));
+    if (flush) {
+      scheduleFlush();
+    }
+  }
+
+  /**
+   * Process the queue of commands and dispatch them to the stream. This method is only
+   * called in the event loop
+   */
+  private void flush() {
+    try {
+      QueuedCommand cmd;
+      int i = 0;
+      boolean flushedOnce = false;
+      while ((cmd = queue.poll()) != null) {
+        cmd.run(channel);
+        if (++i == DEQUE_CHUNK_SIZE) {
+          i = 0;
+          // Flush each chunk so we are releasing buffers periodically. In theory this loop
+          // might never end as new events are continuously added to the queue, if we never
+          // flushed in that case we would be guaranteed to OOM.
+          channel.flush();
+          flushedOnce = true;
+        }
+      }
+      // Must flush at least once, even if there were no writes.
+      if (i != 0 || !flushedOnce) {
+        channel.flush();
+      }
+    } finally {
+      // Mark the write as done, if the queue is non-empty after marking trigger a new write.
+      scheduled.set(false);
+      if (!queue.isEmpty()) {
+        scheduleFlush();
+      }
+    }
+  }
+
+  private static class RunnableCommand implements QueuedCommand {
+    private final Runnable runnable;
+
+    public RunnableCommand(Runnable runnable) {
+      this.runnable = runnable;
+    }
+
+    @Override
+    public final void promise(ChannelPromise promise) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final ChannelPromise promise() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public final void run(Channel channel) {
+      runnable.run();
+    }
+  }
+
+  abstract static class AbstractQueuedCommand implements QueuedCommand {
+
+    private ChannelPromise promise;
+
+    @Override
+    public final void promise(ChannelPromise promise) {
+      this.promise = promise;
+    }
+
+    @Override
+    public final ChannelPromise promise() {
+      return promise;
+    }
+
+    @Override
+    public final void run(Channel channel) {
+      channel.write(this, promise);
+    }
+  }
+
+  /**
+   * Simple wrapper type around a command and its optional completion listener.
+   */
+  interface QueuedCommand {
+    /**
+     * Returns the promise beeing notified of the success/failure of the write.
+     */
+    ChannelPromise promise();
+
+    /**
+     * Sets the promise.
+     */
+    void promise(ChannelPromise promise);
+
+    void run(Channel channel);
+  }
+}
diff --git a/netty/src/main/java/io/grpc/netty/package-info.java b/netty/src/main/java/io/grpc/netty/package-info.java
new file mode 100644
index 0000000..54595b3
--- /dev/null
+++ b/netty/src/main/java/io/grpc/netty/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/**
+ * The main transport implementation based on <a target="_blank" href="http://netty.io">Netty</a>,
+ * for both the client and the server.
+ */
+@javax.annotation.CheckReturnValue
+package io.grpc.netty;
diff --git a/netty/src/main/resources/META-INF/services/io.grpc.ManagedChannelProvider b/netty/src/main/resources/META-INF/services/io.grpc.ManagedChannelProvider
new file mode 100644
index 0000000..ebd1bcd
--- /dev/null
+++ b/netty/src/main/resources/META-INF/services/io.grpc.ManagedChannelProvider
@@ -0,0 +1 @@
+io.grpc.netty.NettyChannelProvider
diff --git a/netty/src/main/resources/META-INF/services/io.grpc.ServerProvider b/netty/src/main/resources/META-INF/services/io.grpc.ServerProvider
new file mode 100644
index 0000000..d87ba18
--- /dev/null
+++ b/netty/src/main/resources/META-INF/services/io.grpc.ServerProvider
@@ -0,0 +1 @@
+io.grpc.netty.NettyServerProvider
diff --git a/netty/src/test/java/io/grpc/netty/AbstractHttp2HeadersTest.java b/netty/src/test/java/io/grpc/netty/AbstractHttp2HeadersTest.java
new file mode 100644
index 0000000..b9b7ffc
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/AbstractHttp2HeadersTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Defaults;
+import io.netty.handler.codec.http2.Http2Headers;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link AbstractHttp2Headers}. */
+@RunWith(JUnit4.class)
+public class AbstractHttp2HeadersTest {
+  @Test
+  public void allMethodsAreUnsupported() {
+    Http2Headers headers = new AbstractHttp2Headers() {};
+    for (Method method : Http2Headers.class.getMethods()) {
+      // Avoid Java 8 default methods, without requiring Java 8 with isDefault()
+      if (!Modifier.isAbstract(method.getModifiers())) {
+        continue;
+      }
+      Class<?>[] params = method.getParameterTypes();
+      Object[] args = new Object[params.length];
+      for (int i = 0; i < params.length; i++) {
+        args[i] = Defaults.defaultValue(params[i]);
+      }
+      try {
+        method.invoke(headers, args);
+        fail("Expected exception for method: " + method);
+      } catch (InvocationTargetException ex) {
+        assertEquals("For method: " + method,
+            UnsupportedOperationException.class, ex.getCause().getClass());
+      } catch (Exception ex) {
+        throw new AssertionError("Failure with method: " + method, ex);
+      }
+    }
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/GrpcHttp2HeadersUtilsTest.java b/netty/src/test/java/io/grpc/netty/GrpcHttp2HeadersUtilsTest.java
new file mode 100644
index 0000000..99806f7
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/GrpcHttp2HeadersUtilsTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE;
+import static io.netty.util.AsciiString.of;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2ClientHeadersDecoder;
+import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2ServerHeadersDecoder;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.handler.codec.http2.DefaultHttp2Headers;
+import io.netty.handler.codec.http2.DefaultHttp2HeadersEncoder;
+import io.netty.handler.codec.http2.Http2Exception;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.handler.codec.http2.Http2HeadersDecoder;
+import io.netty.handler.codec.http2.Http2HeadersEncoder;
+import io.netty.handler.codec.http2.Http2HeadersEncoder.SensitivityDetector;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link GrpcHttp2HeadersUtils}.
+ */
+@RunWith(JUnit4.class)
+public class GrpcHttp2HeadersUtilsTest {
+
+  private static final SensitivityDetector NEVER_SENSITIVE = new SensitivityDetector() {
+    @Override
+    public boolean isSensitive(CharSequence name, CharSequence value) {
+      return false;
+    }
+  };
+
+  private ByteBuf encodedHeaders;
+
+  @After
+  public void tearDown() {
+    if (encodedHeaders != null) {
+      encodedHeaders.release();
+    }
+  }
+
+  @Test
+  public void decode_requestHeaders() throws Http2Exception {
+    Http2HeadersDecoder decoder = new GrpcHttp2ServerHeadersDecoder(DEFAULT_MAX_HEADER_LIST_SIZE);
+    Http2HeadersEncoder encoder =
+        new DefaultHttp2HeadersEncoder(NEVER_SENSITIVE);
+
+    Http2Headers headers = new DefaultHttp2Headers(false);
+    headers.add(of(":scheme"), of("https")).add(of(":method"), of("GET"))
+        .add(of(":path"), of("index.html")).add(of(":authority"), of("foo.grpc.io"))
+        .add(of("custom"), of("header"));
+    encodedHeaders = Unpooled.buffer();
+    encoder.encodeHeaders(1 /* randomly chosen */, headers, encodedHeaders);
+
+    Http2Headers decodedHeaders = decoder.decodeHeaders(3 /* randomly chosen */, encodedHeaders);
+    assertEquals(headers.get(of(":scheme")), decodedHeaders.scheme());
+    assertEquals(headers.get(of(":method")), decodedHeaders.method());
+    assertEquals(headers.get(of(":path")), decodedHeaders.path());
+    assertEquals(headers.get(of(":authority")), decodedHeaders.authority());
+    assertEquals(headers.get(of("custom")), decodedHeaders.get(of("custom")));
+    assertEquals(headers.size(), decodedHeaders.size());
+
+    String toString = decodedHeaders.toString();
+    assertContainsKeyAndValue(toString, ":scheme", decodedHeaders.scheme());
+    assertContainsKeyAndValue(toString, ":method", decodedHeaders.method());
+    assertContainsKeyAndValue(toString, ":path", decodedHeaders.path());
+    assertContainsKeyAndValue(toString, ":authority", decodedHeaders.authority());
+    assertContainsKeyAndValue(toString, "custom", decodedHeaders.get(of("custom")));
+  }
+
+  @Test
+  public void decode_responseHeaders() throws Http2Exception {
+    Http2HeadersDecoder decoder = new GrpcHttp2ClientHeadersDecoder(DEFAULT_MAX_HEADER_LIST_SIZE);
+    Http2HeadersEncoder encoder =
+        new DefaultHttp2HeadersEncoder(NEVER_SENSITIVE);
+
+    Http2Headers headers = new DefaultHttp2Headers(false);
+    headers.add(of(":status"), of("200")).add(of("custom"), of("header"));
+    encodedHeaders = Unpooled.buffer();
+    encoder.encodeHeaders(1 /* randomly chosen */, headers, encodedHeaders);
+
+    Http2Headers decodedHeaders = decoder.decodeHeaders(3 /* randomly chosen */, encodedHeaders);
+    assertEquals(headers.get(of(":status")), decodedHeaders.get(of(":status")));
+    assertEquals(headers.get(of("custom")), decodedHeaders.get(of("custom")));
+    assertEquals(headers.size(), decodedHeaders.size());
+
+    String toString = decodedHeaders.toString();
+    assertContainsKeyAndValue(toString, ":status", decodedHeaders.get(of(":status")));
+    assertContainsKeyAndValue(toString, "custom", decodedHeaders.get(of("custom")));
+  }
+
+  @Test
+  public void decode_emptyHeaders() throws Http2Exception {
+    Http2HeadersDecoder decoder = new GrpcHttp2ClientHeadersDecoder(8192);
+    Http2HeadersEncoder encoder =
+        new DefaultHttp2HeadersEncoder(NEVER_SENSITIVE);
+
+    ByteBuf encodedHeaders = Unpooled.buffer();
+    encoder.encodeHeaders(1 /* randomly chosen */, new DefaultHttp2Headers(false), encodedHeaders);
+
+    Http2Headers decodedHeaders = decoder.decodeHeaders(3 /* randomly chosen */, encodedHeaders);
+    assertEquals(0, decodedHeaders.size());
+    assertThat(decodedHeaders.toString(), containsString("[]"));
+  }
+
+  private static void assertContainsKeyAndValue(String str, CharSequence key, CharSequence value) {
+    assertThat(str, containsString(key.toString()));
+    assertThat(str, containsString(value.toString()));
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/GrpcHttp2InboundHeadersTest.java b/netty/src/test/java/io/grpc/netty/GrpcHttp2InboundHeadersTest.java
new file mode 100644
index 0000000..6279ed1
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/GrpcHttp2InboundHeadersTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static io.netty.util.AsciiString.of;
+import static junit.framework.TestCase.assertNotSame;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.io.BaseEncoding;
+import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2InboundHeaders;
+import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2RequestHeaders;
+import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2ResponseHeaders;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.util.AsciiString;
+import java.util.Random;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link GrpcHttp2RequestHeaders} and {@link GrpcHttp2ResponseHeaders}.
+ */
+@RunWith(JUnit4.class)
+public class GrpcHttp2InboundHeadersTest {
+
+  @Test
+  public void basicCorrectness() {
+    Http2Headers headers = new GrpcHttp2RequestHeaders(1);
+    headers.add(of(":method"), of("POST"));
+    headers.add(of("content-type"), of("application/grpc+proto"));
+    headers.add(of(":path"), of("/google.pubsub.v2.PublisherService/CreateTopic"));
+    headers.add(of(":scheme"), of("https"));
+    headers.add(of("te"), of("trailers"));
+    headers.add(of(":authority"), of("pubsub.googleapis.com"));
+    headers.add(of("foo"), of("bar"));
+
+    assertEquals(7, headers.size());
+    // Number of headers without the pseudo headers and 'te' header.
+    assertEquals(2, ((GrpcHttp2InboundHeaders)headers).numHeaders());
+
+    assertEquals(of("application/grpc+proto"), headers.get(of("content-type")));
+    assertEquals(of("/google.pubsub.v2.PublisherService/CreateTopic"), headers.path());
+    assertEquals(of("https"), headers.scheme());
+    assertEquals(of("POST"), headers.method());
+    assertEquals(of("pubsub.googleapis.com"), headers.authority());
+    assertEquals(of("trailers"), headers.get(of("te")));
+    assertEquals(of("bar"), headers.get(of("foo")));
+  }
+
+  @Test
+  public void binaryHeadersShouldBeBase64Decoded() {
+    Http2Headers headers = new GrpcHttp2RequestHeaders(1);
+
+    byte[] data = new byte[100];
+    new Random().nextBytes(data);
+    headers.add(of("foo-bin"), of(BaseEncoding.base64().encode(data)));
+
+    assertEquals(1, headers.size());
+
+    byte[][] namesAndValues = ((GrpcHttp2InboundHeaders)headers).namesAndValues();
+
+    assertEquals(of("foo-bin"), new AsciiString(namesAndValues[0]));
+    assertNotSame(data, namesAndValues[1]);
+    assertArrayEquals(data, namesAndValues[1]);
+  }
+
+}
diff --git a/netty/src/test/java/io/grpc/netty/GrpcSslContextsTest.java b/netty/src/test/java/io/grpc/netty/GrpcSslContextsTest.java
new file mode 100644
index 0000000..c74fead
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/GrpcSslContextsTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link io.grpc.netty.GrpcSslContexts}. */
+@RunWith(JUnit4.class)
+public class GrpcSslContextsTest {
+  @Test public void selectApplicationProtocolConfig_grpcExp() {
+    assertTrue(
+        GrpcSslContexts.NEXT_PROTOCOL_VERSIONS.indexOf("grpc-exp") == -1
+            || GrpcSslContexts.NEXT_PROTOCOL_VERSIONS.indexOf("grpc-exp")
+                < GrpcSslContexts.NEXT_PROTOCOL_VERSIONS.indexOf("h2"));
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/KeepAliveEnforcerTest.java b/netty/src/test/java/io/grpc/netty/KeepAliveEnforcerTest.java
new file mode 100644
index 0000000..33c4039
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/KeepAliveEnforcerTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link KeepAliveEnforcer}. */
+@RunWith(JUnit4.class)
+public class KeepAliveEnforcerTest {
+  private static final int LARGE_NUMBER = KeepAliveEnforcer.MAX_PING_STRIKES * 5;
+
+  private FakeTicker ticker = new FakeTicker();
+
+  @Test(expected = IllegalArgumentException.class)
+  public void negativeTime() {
+    new KeepAliveEnforcer(true, -1, TimeUnit.NANOSECONDS);
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void nullTimeUnit() {
+    new KeepAliveEnforcer(true, 1, null);
+  }
+
+  @Test
+  public void permitLimitless() {
+    KeepAliveEnforcer enforcer = new KeepAliveEnforcer(true, 0, TimeUnit.NANOSECONDS, ticker);
+    for (int i = 0; i < LARGE_NUMBER; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+    }
+    enforcer.onTransportActive();
+    for (int i = 0; i < LARGE_NUMBER; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+    }
+    enforcer.onTransportIdle();
+    for (int i = 0; i < LARGE_NUMBER; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+    }
+    enforcer.resetCounters();
+    for (int i = 0; i < LARGE_NUMBER; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+    }
+  }
+
+  @Test
+  public void strikeOutBecauseNoOutstandingCalls() {
+    KeepAliveEnforcer enforcer = new KeepAliveEnforcer(false, 0, TimeUnit.NANOSECONDS, ticker);
+    enforcer.onTransportIdle();
+    for (int i = 0; i < KeepAliveEnforcer.MAX_PING_STRIKES; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+    }
+    assertThat(enforcer.pingAcceptable()).isFalse();
+  }
+
+  @Test
+  public void startsIdle() {
+    KeepAliveEnforcer enforcer = new KeepAliveEnforcer(false, 0, TimeUnit.NANOSECONDS, ticker);
+    for (int i = 0; i < KeepAliveEnforcer.MAX_PING_STRIKES; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+    }
+    assertThat(enforcer.pingAcceptable()).isFalse();
+  }
+
+  @Test
+  public void strikeOutBecauseRateTooHighWhileActive() {
+    KeepAliveEnforcer enforcer = new KeepAliveEnforcer(true, 1, TimeUnit.NANOSECONDS, ticker);
+    enforcer.onTransportActive();
+    for (int i = 0; i < KeepAliveEnforcer.MAX_PING_STRIKES; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+    }
+    assertThat(enforcer.pingAcceptable()).isFalse();
+  }
+
+  @Test
+  public void strikeOutBecauseRateTooHighWhileIdle() {
+    KeepAliveEnforcer enforcer = new KeepAliveEnforcer(true, 1, TimeUnit.NANOSECONDS, ticker);
+    enforcer.onTransportIdle();
+    for (int i = 0; i < KeepAliveEnforcer.MAX_PING_STRIKES; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+    }
+    assertThat(enforcer.pingAcceptable()).isFalse();
+  }
+
+  @Test
+  public void permitInRateWhileActive() {
+    KeepAliveEnforcer enforcer = new KeepAliveEnforcer(false, 1, TimeUnit.NANOSECONDS, ticker);
+    enforcer.onTransportActive();
+    for (int i = 0; i < LARGE_NUMBER; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+      ticker.nanoTime += 1;
+    }
+  }
+
+  @Test
+  public void permitInRateWhileIdle() {
+    KeepAliveEnforcer enforcer = new KeepAliveEnforcer(true, 1, TimeUnit.NANOSECONDS, ticker);
+    enforcer.onTransportIdle();
+    for (int i = 0; i < LARGE_NUMBER; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+      ticker.nanoTime += 1;
+    }
+  }
+
+  @Test
+  public void implicitPermittedWhileIdle() {
+    KeepAliveEnforcer enforcer = new KeepAliveEnforcer(
+        false, KeepAliveEnforcer.IMPLICIT_PERMIT_TIME_NANOS * 10, TimeUnit.NANOSECONDS, ticker);
+    enforcer.onTransportIdle();
+    for (int i = 0; i < LARGE_NUMBER; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+      ticker.nanoTime += KeepAliveEnforcer.IMPLICIT_PERMIT_TIME_NANOS;
+    }
+  }
+
+  @Test
+  public void implicitOverridesWhileActive() {
+    KeepAliveEnforcer enforcer = new KeepAliveEnforcer(
+        false, KeepAliveEnforcer.IMPLICIT_PERMIT_TIME_NANOS * 10, TimeUnit.NANOSECONDS, ticker);
+    enforcer.onTransportActive();
+    for (int i = 0; i < LARGE_NUMBER; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+      ticker.nanoTime += KeepAliveEnforcer.IMPLICIT_PERMIT_TIME_NANOS;
+    }
+  }
+
+  @Test
+  public void implicitOverridesWhileIdle() {
+    KeepAliveEnforcer enforcer = new KeepAliveEnforcer(
+        true, KeepAliveEnforcer.IMPLICIT_PERMIT_TIME_NANOS * 10, TimeUnit.NANOSECONDS, ticker);
+    enforcer.onTransportIdle();
+    for (int i = 0; i < LARGE_NUMBER; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+      ticker.nanoTime += KeepAliveEnforcer.IMPLICIT_PERMIT_TIME_NANOS;
+    }
+  }
+
+  @Test
+  public void permitsWhenTimeOverflows() {
+    ticker.nanoTime = Long.MAX_VALUE;
+    KeepAliveEnforcer enforcer = new KeepAliveEnforcer(false, 1, TimeUnit.NANOSECONDS, ticker);
+    enforcer.onTransportActive();
+    for (int i = 0; i < KeepAliveEnforcer.MAX_PING_STRIKES; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+    }
+    // Should have the maximum number of strikes now
+    ticker.nanoTime++;
+    assertThat(enforcer.pingAcceptable()).isTrue();
+  }
+
+  @Test
+  public void resetCounters_resetsStrikes() {
+    KeepAliveEnforcer enforcer = new KeepAliveEnforcer(false, 1, TimeUnit.NANOSECONDS, ticker);
+    enforcer.onTransportActive();
+    for (int i = 0; i < KeepAliveEnforcer.MAX_PING_STRIKES; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+    }
+    // Should have the maximum number of strikes now
+    enforcer.resetCounters();
+    for (int i = 0; i < KeepAliveEnforcer.MAX_PING_STRIKES; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+    }
+    assertThat(enforcer.pingAcceptable()).isFalse();
+  }
+
+  @Test
+  public void resetCounters_resetsPingTime() {
+    KeepAliveEnforcer enforcer = new KeepAliveEnforcer(false, 1, TimeUnit.NANOSECONDS, ticker);
+    enforcer.onTransportActive();
+    ticker.nanoTime += 1;
+    assertThat(enforcer.pingAcceptable()).isTrue();
+    enforcer.resetCounters();
+    // Should not cause a strike
+    assertThat(enforcer.pingAcceptable()).isTrue();
+    for (int i = 0; i < KeepAliveEnforcer.MAX_PING_STRIKES; i++) {
+      assertThat(enforcer.pingAcceptable()).isTrue();
+    }
+  }
+
+  @Test
+  public void systemTickerIsSystemNanoTime() {
+    long before = System.nanoTime();
+    long returned = KeepAliveEnforcer.SystemTicker.INSTANCE.nanoTime();
+    long after = System.nanoTime();
+    assertThat(returned).isAtLeast(before);
+    assertThat(returned).isAtMost(after);
+  }
+
+  private static class FakeTicker implements KeepAliveEnforcer.Ticker {
+    long nanoTime;
+
+    @Override
+    public long nanoTime() {
+      return nanoTime;
+    }
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/MaxConnectionIdleManagerTest.java b/netty/src/test/java/io/grpc/netty/MaxConnectionIdleManagerTest.java
new file mode 100644
index 0000000..7d8df30
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/MaxConnectionIdleManagerTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.internal.FakeClock;
+import io.grpc.netty.MaxConnectionIdleManager.Ticker;
+import io.netty.channel.ChannelHandlerContext;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link MaxConnectionIdleManager}. */
+@RunWith(JUnit4.class)
+public class MaxConnectionIdleManagerTest {
+  private final FakeClock fakeClock = new FakeClock();
+  private final Ticker ticker = new Ticker() {
+    @Override
+    public long nanoTime() {
+      return fakeClock.getTicker().read();
+    }
+  };
+
+  @Mock
+  private ChannelHandlerContext ctx;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+  }
+
+  @Test
+  public void maxIdleReached() {
+    MaxConnectionIdleManager maxConnectionIdleManager =
+        spy(new TestMaxConnectionIdleManager(123L, ticker));
+
+    maxConnectionIdleManager.start(ctx, fakeClock.getScheduledExecutorService());
+    maxConnectionIdleManager.onTransportIdle();
+    fakeClock.forwardNanos(123L);
+
+    verify(maxConnectionIdleManager).close(eq(ctx));
+  }
+
+  @Test
+  public void maxIdleNotReachedAndReached() {
+    MaxConnectionIdleManager maxConnectionIdleManager =
+        spy(new TestMaxConnectionIdleManager(123L, ticker));
+
+    maxConnectionIdleManager.start(ctx, fakeClock.getScheduledExecutorService());
+    maxConnectionIdleManager.onTransportIdle();
+    fakeClock.forwardNanos(100L);
+    // max idle not reached
+    maxConnectionIdleManager.onTransportActive();
+    maxConnectionIdleManager.onTransportIdle();
+    fakeClock.forwardNanos(100L);
+    // max idle not reached although accumulative idle time exceeds max idle time
+    maxConnectionIdleManager.onTransportActive();
+    fakeClock.forwardNanos(100L);
+
+    verify(maxConnectionIdleManager, never()).close(any(ChannelHandlerContext.class));
+
+    // max idle reached
+    maxConnectionIdleManager.onTransportIdle();
+    fakeClock.forwardNanos(123L);
+
+    verify(maxConnectionIdleManager).close(eq(ctx));
+  }
+
+  @Test
+  public void shutdownThenMaxIdleReached() {
+    MaxConnectionIdleManager maxConnectionIdleManager =
+        spy(new TestMaxConnectionIdleManager(123L, ticker));
+
+    maxConnectionIdleManager.start(ctx, fakeClock.getScheduledExecutorService());
+    maxConnectionIdleManager.onTransportIdle();
+    maxConnectionIdleManager.onTransportTermination();
+    fakeClock.forwardNanos(123L);
+
+    verify(maxConnectionIdleManager, never()).close(any(ChannelHandlerContext.class));
+  }
+
+  private static class TestMaxConnectionIdleManager extends MaxConnectionIdleManager {
+    TestMaxConnectionIdleManager(long maxConnectionIdleInNanos, Ticker ticker) {
+      super(maxConnectionIdleInNanos, ticker);
+    }
+
+    @Override
+    void close(ChannelHandlerContext ctx) {
+    }
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyChannelBuilderTest.java b/netty/src/test/java/io/grpc/netty/NettyChannelBuilderTest.java
new file mode 100644
index 0000000..2d653a4
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyChannelBuilderTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import io.grpc.ManagedChannel;
+import io.grpc.internal.ProxyParameters;
+import io.grpc.netty.InternalNettyChannelBuilder.OverrideAuthorityChecker;
+import io.grpc.netty.ProtocolNegotiators.TlsNegotiator;
+import io.netty.handler.ssl.SslContext;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLException;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class NettyChannelBuilderTest {
+
+  @Rule public final ExpectedException thrown = ExpectedException.none();
+  private final SslContext noSslContext = null;
+  private final ProxyParameters noProxy = null;
+
+  private void shutdown(ManagedChannel mc) throws Exception {
+    mc.shutdownNow();
+    assertTrue(mc.awaitTermination(1, TimeUnit.SECONDS));
+  }
+
+  @Test
+  public void authorityIsReadable() throws Exception {
+    NettyChannelBuilder builder = NettyChannelBuilder.forAddress("original", 1234);
+
+    ManagedChannel b = builder.build();
+    try {
+      assertEquals("original:1234", b.authority());
+    } finally {
+      shutdown(b);
+    }
+  }
+
+  @Test
+  public void overrideAuthorityIsReadableForAddress() throws Exception {
+    NettyChannelBuilder builder = NettyChannelBuilder.forAddress("original", 1234);
+    overrideAuthorityIsReadableHelper(builder, "override:5678");
+  }
+
+  @Test
+  public void overrideAuthorityIsReadableForTarget() throws Exception {
+    NettyChannelBuilder builder = NettyChannelBuilder.forTarget("original:1234");
+    overrideAuthorityIsReadableHelper(builder, "override:5678");
+  }
+
+  @Test
+  public void overrideAuthorityIsReadableForSocketAddress() throws Exception {
+    NettyChannelBuilder builder = NettyChannelBuilder.forAddress(new SocketAddress(){});
+    overrideAuthorityIsReadableHelper(builder, "override:5678");
+  }
+
+  private void overrideAuthorityIsReadableHelper(NettyChannelBuilder builder,
+      String overrideAuthority) throws Exception {
+    builder.overrideAuthority(overrideAuthority);
+    ManagedChannel channel = builder.build();
+    try {
+      assertEquals(overrideAuthority, channel.authority());
+    } finally {
+      shutdown(channel);
+    }
+  }
+
+  @Test
+  public void overrideAllowsInvalidAuthority() {
+    NettyChannelBuilder builder = new NettyChannelBuilder(new SocketAddress(){});
+    InternalNettyChannelBuilder.overrideAuthorityChecker(builder, new OverrideAuthorityChecker() {
+      @Override
+      public String checkAuthority(String authority) {
+        return authority;
+      }
+    });
+    Object unused = builder.overrideAuthority("[invalidauthority")
+        .negotiationType(NegotiationType.PLAINTEXT)
+        .buildTransportFactory();
+  }
+
+  @Test
+  public void failOverrideInvalidAuthority() {
+    NettyChannelBuilder builder = new NettyChannelBuilder(new SocketAddress(){});
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Invalid authority:");
+
+    builder.overrideAuthority("[invalidauthority");
+  }
+
+  @Test
+  public void failInvalidAuthority() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Invalid host or port");
+
+    Object unused =
+        NettyChannelBuilder.forAddress(new InetSocketAddress("invalid_authority", 1234));
+  }
+
+  @Test
+  public void sslContextCanBeNull() {
+    NettyChannelBuilder builder = new NettyChannelBuilder(new SocketAddress(){});
+    builder.sslContext(null);
+  }
+
+  @Test
+  public void failIfSslContextIsNotClient() {
+    SslContext sslContext = mock(SslContext.class);
+    NettyChannelBuilder builder = new NettyChannelBuilder(new SocketAddress(){});
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Server SSL context can not be used for client channel");
+
+    builder.sslContext(sslContext);
+  }
+
+  @Test
+  public void createProtocolNegotiatorByType_plaintext() {
+    ProtocolNegotiator negotiator = NettyChannelBuilder.createProtocolNegotiatorByType(
+        NegotiationType.PLAINTEXT,
+        noSslContext);
+    // just check that the classes are the same, and that negotiator is not null.
+    assertTrue(negotiator instanceof ProtocolNegotiators.PlaintextNegotiator);
+  }
+
+  @Test
+  public void createProtocolNegotiatorByType_plaintextUpgrade() {
+    ProtocolNegotiator negotiator = NettyChannelBuilder.createProtocolNegotiatorByType(
+        NegotiationType.PLAINTEXT_UPGRADE,
+        noSslContext);
+    // just check that the classes are the same, and that negotiator is not null.
+    assertTrue(negotiator instanceof ProtocolNegotiators.PlaintextUpgradeNegotiator);
+  }
+
+  @Test
+  public void createProtocolNegotiatorByType_tlsWithNoContext() {
+    thrown.expect(NullPointerException.class);
+    NettyChannelBuilder.createProtocolNegotiatorByType(
+        NegotiationType.TLS,
+        noSslContext);
+  }
+
+  @Test
+  public void createProtocolNegotiatorByType_tlsWithClientContext() throws SSLException {
+    ProtocolNegotiator negotiator = NettyChannelBuilder.createProtocolNegotiatorByType(
+        NegotiationType.TLS,
+        GrpcSslContexts.forClient().build());
+
+    assertTrue(negotiator instanceof ProtocolNegotiators.TlsNegotiator);
+    ProtocolNegotiators.TlsNegotiator n = (TlsNegotiator) negotiator;
+    ProtocolNegotiators.HostPort hostPort = n.parseAuthority("authority:1234");
+
+    assertEquals("authority", hostPort.host);
+    assertEquals(1234, hostPort.port);
+  }
+
+  @Test
+  public void createProtocolNegotiatorByType_tlsWithAuthorityFallback() throws SSLException {
+    ProtocolNegotiator negotiator = NettyChannelBuilder.createProtocolNegotiatorByType(
+        NegotiationType.TLS,
+        GrpcSslContexts.forClient().build());
+
+    assertTrue(negotiator instanceof ProtocolNegotiators.TlsNegotiator);
+    ProtocolNegotiators.TlsNegotiator n = (TlsNegotiator) negotiator;
+    ProtocolNegotiators.HostPort hostPort = n.parseAuthority("bad_authority");
+
+    assertEquals("bad_authority", hostPort.host);
+    assertEquals(-1, hostPort.port);
+  }
+
+  @Test
+  public void negativeKeepAliveTime() {
+    NettyChannelBuilder builder = NettyChannelBuilder.forTarget("fakeTarget");
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("keepalive time must be positive");
+    builder.keepAliveTime(-1L, TimeUnit.HOURS);
+  }
+
+  @Test
+  public void negativeKeepAliveTimeout() {
+    NettyChannelBuilder builder = NettyChannelBuilder.forTarget("fakeTarget");
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("keepalive timeout must be positive");
+    builder.keepAliveTimeout(-1L, TimeUnit.HOURS);
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyChannelProviderTest.java b/netty/src/test/java/io/grpc/netty/NettyChannelProviderTest.java
new file mode 100644
index 0000000..26dd679
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyChannelProviderTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import io.grpc.InternalManagedChannelProvider;
+import io.grpc.InternalServiceProviders;
+import io.grpc.ManagedChannelProvider;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link NettyChannelProvider}. */
+@RunWith(JUnit4.class)
+public class NettyChannelProviderTest {
+  private NettyChannelProvider provider = new NettyChannelProvider();
+
+  @Test
+  public void provided() {
+    for (ManagedChannelProvider current
+        : InternalServiceProviders.getCandidatesViaServiceLoader(
+            ManagedChannelProvider.class, getClass().getClassLoader())) {
+      if (current instanceof NettyChannelProvider) {
+        return;
+      }
+    }
+    fail("ServiceLoader unable to load NettyChannelProvider");
+  }
+
+  @Test
+  public void providedHardCoded() {
+    for (ManagedChannelProvider current : InternalServiceProviders.getCandidatesViaHardCoded(
+        ManagedChannelProvider.class, InternalManagedChannelProvider.HARDCODED_CLASSES)) {
+      if (current instanceof NettyChannelProvider) {
+        return;
+      }
+    }
+    fail("Hard coded unable to load NettyChannelProvider");
+  }
+
+  @Test
+  public void basicMethods() {
+    assertTrue(provider.isAvailable());
+    assertEquals(5, provider.priority());
+  }
+
+  @Test
+  public void builderIsANettyBuilder() {
+    assertSame(NettyChannelBuilder.class, provider.builderForAddress("localhost", 443).getClass());
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java
new file mode 100644
index 0000000..1e02d27
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyClientHandlerTest.java
@@ -0,0 +1,771 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED;
+import static io.grpc.internal.ClientStreamListener.RpcProgress.REFUSED;
+import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
+import static io.grpc.netty.Utils.CONTENT_TYPE_GRPC;
+import static io.grpc.netty.Utils.CONTENT_TYPE_HEADER;
+import static io.grpc.netty.Utils.HTTPS;
+import static io.grpc.netty.Utils.HTTP_METHOD;
+import static io.grpc.netty.Utils.STATUS_OK;
+import static io.grpc.netty.Utils.TE_HEADER;
+import static io.grpc.netty.Utils.TE_TRAILERS;
+import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.notNull;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Supplier;
+import com.google.common.base.Ticker;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import io.grpc.Attributes;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.StatusException;
+import io.grpc.internal.ClientStreamListener;
+import io.grpc.internal.ClientStreamListener.RpcProgress;
+import io.grpc.internal.ClientTransport;
+import io.grpc.internal.ClientTransport.PingCallback;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.KeepAliveManager;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.StreamListener;
+import io.grpc.internal.TransportTracer;
+import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2ClientHeadersDecoder;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.EventLoop;
+import io.netty.handler.codec.http2.DefaultHttp2Connection;
+import io.netty.handler.codec.http2.DefaultHttp2Headers;
+import io.netty.handler.codec.http2.Http2Connection;
+import io.netty.handler.codec.http2.Http2Error;
+import io.netty.handler.codec.http2.Http2Exception;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.handler.codec.http2.Http2LocalFlowController;
+import io.netty.handler.codec.http2.Http2Settings;
+import io.netty.handler.codec.http2.Http2Stream;
+import io.netty.util.AsciiString;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Tests for {@link NettyClientHandler}.
+ */
+@RunWith(JUnit4.class)
+public class NettyClientHandlerTest extends NettyHandlerTestBase<NettyClientHandler> {
+
+  private NettyClientStream.TransportState streamTransportState;
+  private Http2Headers grpcHeaders;
+  private long nanoTime; // backs a ticker, for testing ping round-trip time measurement
+  private int maxHeaderListSize = Integer.MAX_VALUE;
+  private int streamId = 3;
+  private ClientTransportLifecycleManager lifecycleManager;
+  private KeepAliveManager mockKeepAliveManager = null;
+  private List<String> setKeepaliveManagerFor = ImmutableList.of("cancelShouldSucceed",
+      "sendFrameShouldSucceed", "channelShutdownShouldCancelBufferedStreams",
+      "createIncrementsIdsForActualAndBufferdStreams", "dataPingAckIsRecognized");
+  private Runnable tooManyPingsRunnable = new Runnable() {
+    @Override public void run() {}
+  };
+
+  @Rule
+  public TestName testNameRule = new TestName();
+  @Mock
+  private NettyClientTransport.Listener listener;
+  @Mock
+  private ClientStreamListener streamListener;
+
+  private final Queue<InputStream> streamListenerMessageQueue = new LinkedList<InputStream>();
+
+  @Override
+  protected void manualSetUp() throws Exception {
+    setUp();
+  }
+
+  /**
+   * Set up for test.
+   */
+  @Before
+  public void setUp() throws Exception {
+    MockitoAnnotations.initMocks(this);
+
+    doAnswer(
+          new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+              StreamListener.MessageProducer producer =
+                  (StreamListener.MessageProducer) invocation.getArguments()[0];
+              InputStream message;
+              while ((message = producer.next()) != null) {
+                streamListenerMessageQueue.add(message);
+              }
+              return null;
+            }
+          })
+      .when(streamListener)
+      .messagesAvailable(Matchers.<StreamListener.MessageProducer>any());
+
+    lifecycleManager = new ClientTransportLifecycleManager(listener);
+    // This mocks the keepalive manager only for there's in which we verify it. For other tests
+    // it'll be null which will be testing if we behave correctly when it's not present.
+    if (setKeepaliveManagerFor.contains(testNameRule.getMethodName())) {
+      mockKeepAliveManager = mock(KeepAliveManager.class);
+    }
+
+    initChannel(new GrpcHttp2ClientHeadersDecoder(GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE));
+    streamTransportState = new TransportStateImpl(
+        handler(),
+        channel().eventLoop(),
+        DEFAULT_MAX_MESSAGE_SIZE,
+        transportTracer);
+    streamTransportState.setListener(streamListener);
+
+    grpcHeaders = new DefaultHttp2Headers()
+        .scheme(HTTPS)
+        .authority(as("www.fake.com"))
+        .path(as("/fakemethod"))
+        .method(HTTP_METHOD)
+        .add(as("auth"), as("sometoken"))
+        .add(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
+        .add(TE_HEADER, TE_TRAILERS);
+
+    // Simulate receipt of initial remote settings.
+    ByteBuf serializedSettings = serializeSettings(new Http2Settings());
+    channelRead(serializedSettings);
+  }
+
+  @Test
+  public void cancelBufferedStreamShouldChangeClientStreamStatus() throws Exception {
+    // Force the stream to be buffered.
+    receiveMaxConcurrentStreams(0);
+    // Create a new stream with id 3.
+    ChannelFuture createFuture = enqueue(
+        new CreateStreamCommand(grpcHeaders, streamTransportState));
+    assertEquals(3, streamTransportState.id());
+    // Cancel the stream.
+    cancelStream(Status.CANCELLED);
+
+    assertTrue(createFuture.isSuccess());
+    verify(streamListener).closed(eq(Status.CANCELLED), same(PROCESSED), any(Metadata.class));
+  }
+
+  @Test
+  public void createStreamShouldSucceed() throws Exception {
+    createStream();
+    verifyWrite().writeHeaders(eq(ctx()), eq(3), eq(grpcHeaders), eq(0),
+        eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), any(ChannelPromise.class));
+  }
+
+  @Test
+  public void cancelShouldSucceed() throws Exception {
+    createStream();
+    cancelStream(Status.CANCELLED);
+
+    verifyWrite().writeRstStream(eq(ctx()), eq(3), eq(Http2Error.CANCEL.code()),
+        any(ChannelPromise.class));
+    verify(mockKeepAliveManager, times(1)).onTransportActive(); // onStreamActive
+    verify(mockKeepAliveManager, times(1)).onTransportIdle(); // onStreamClosed
+    verifyNoMoreInteractions(mockKeepAliveManager);
+  }
+
+  @Test
+  public void cancelDeadlineExceededShouldSucceed() throws Exception {
+    createStream();
+    cancelStream(Status.DEADLINE_EXCEEDED);
+
+    verifyWrite().writeRstStream(eq(ctx()), eq(3), eq(Http2Error.CANCEL.code()),
+        any(ChannelPromise.class));
+  }
+
+  @Test
+  public void cancelWhileBufferedShouldSucceed() throws Exception {
+    // Force the stream to be buffered.
+    receiveMaxConcurrentStreams(0);
+
+    ChannelFuture createFuture = createStream();
+    assertFalse(createFuture.isDone());
+
+    ChannelFuture cancelFuture = cancelStream(Status.CANCELLED);
+    assertTrue(cancelFuture.isSuccess());
+    assertTrue(createFuture.isDone());
+    assertTrue(createFuture.isSuccess());
+  }
+
+  /**
+   * Although nobody is listening to an exception should it occur during cancel(), we don't want an
+   * exception to be thrown because it would negatively impact performance, and we don't want our
+   * users working around around such performance issues.
+   */
+  @Test
+  public void cancelTwiceShouldSucceed() throws Exception {
+    createStream();
+
+    cancelStream(Status.CANCELLED);
+
+    verifyWrite().writeRstStream(any(ChannelHandlerContext.class), eq(3),
+        eq(Http2Error.CANCEL.code()), any(ChannelPromise.class));
+
+    ChannelFuture future = cancelStream(Status.CANCELLED);
+    assertTrue(future.isSuccess());
+  }
+
+  @Test
+  public void cancelTwiceDifferentReasons() throws Exception {
+    createStream();
+
+    cancelStream(Status.DEADLINE_EXCEEDED);
+
+    verifyWrite().writeRstStream(eq(ctx()), eq(3), eq(Http2Error.CANCEL.code()),
+        any(ChannelPromise.class));
+
+    ChannelFuture future = cancelStream(Status.CANCELLED);
+    assertTrue(future.isSuccess());
+  }
+
+  @Test
+  public void sendFrameShouldSucceed() throws Exception {
+    createStream();
+
+    // Send a frame and verify that it was written.
+    ChannelFuture future
+        = enqueue(new SendGrpcFrameCommand(streamTransportState, content(), true));
+
+    assertTrue(future.isSuccess());
+    verifyWrite().writeData(eq(ctx()), eq(3), eq(content()), eq(0), eq(true),
+        any(ChannelPromise.class));
+    verify(mockKeepAliveManager, times(1)).onTransportActive(); // onStreamActive
+    verifyNoMoreInteractions(mockKeepAliveManager);
+  }
+
+  @Test
+  public void sendForUnknownStreamShouldFail() throws Exception {
+    ChannelFuture future
+        = enqueue(new SendGrpcFrameCommand(streamTransportState, content(), true));
+    assertTrue(future.isDone());
+    assertFalse(future.isSuccess());
+  }
+
+  @Test
+  public void inboundShouldForwardToStream() throws Exception {
+    createStream();
+
+    // Read a headers frame first.
+    Http2Headers headers = new DefaultHttp2Headers().status(STATUS_OK)
+        .set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
+        .set(as("magic"), as("value"));
+    ByteBuf headersFrame = headersFrame(3, headers);
+    channelRead(headersFrame);
+    ArgumentCaptor<Metadata> captor = ArgumentCaptor.forClass(Metadata.class);
+    verify(streamListener).headersRead(captor.capture());
+    assertEquals("value",
+        captor.getValue().get(Metadata.Key.of("magic", Metadata.ASCII_STRING_MARSHALLER)));
+
+    streamTransportState.requestMessagesFromDeframer(1);
+
+    // Create a data frame and then trigger the handler to read it.
+    ByteBuf frame = grpcDataFrame(3, false, contentAsArray());
+    channelRead(frame);
+    InputStream message = streamListenerMessageQueue.poll();
+    assertArrayEquals(ByteBufUtil.getBytes(content()), ByteStreams.toByteArray(message));
+    message.close();
+    assertNull("no additional message expected", streamListenerMessageQueue.poll());
+  }
+
+  @Test
+  public void receivedGoAwayShouldCancelBufferedStream() throws Exception {
+    // Force the stream to be buffered.
+    receiveMaxConcurrentStreams(0);
+    ChannelFuture future = enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+    channelRead(goAwayFrame(0));
+    assertTrue(future.isDone());
+    assertFalse(future.isSuccess());
+    Status status = Status.fromThrowable(future.cause());
+    assertEquals(Status.Code.UNAVAILABLE, status.getCode());
+    assertEquals("HTTP/2 error code: NO_ERROR\nReceived Goaway", status.getDescription());
+  }
+
+  @Test
+  public void receivedGoAwayShouldRefuseLaterStreamId() throws Exception {
+    ChannelFuture future = enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+    channelRead(goAwayFrame(streamId - 1));
+    verify(streamListener).closed(any(Status.class), eq(REFUSED), any(Metadata.class));
+    assertTrue(future.isDone());
+  }
+
+  @Test
+  public void receivedGoAwayShouldNotAffectEarlyStreamId() throws Exception {
+    ChannelFuture future = enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+    channelRead(goAwayFrame(streamId));
+    verify(streamListener, never())
+        .closed(any(Status.class), any(Metadata.class));
+    verify(streamListener, never())
+        .closed(any(Status.class), any(RpcProgress.class), any(Metadata.class));
+    assertTrue(future.isDone());
+  }
+
+  @Test
+  public void receivedResetWithRefuseCode() throws Exception {
+    ChannelFuture future = enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+    channelRead(rstStreamFrame(streamId, (int) Http2Error.REFUSED_STREAM.code() ));
+    verify(streamListener).closed(any(Status.class), eq(REFUSED), any(Metadata.class));
+    assertTrue(future.isDone());
+  }
+
+  @Test
+  public void receivedResetWithCanceCode() throws Exception {
+    ChannelFuture future = enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+    channelRead(rstStreamFrame(streamId, (int) Http2Error.CANCEL.code()));
+    verify(streamListener).closed(any(Status.class), eq(PROCESSED), any(Metadata.class));
+    assertTrue(future.isDone());
+  }
+
+  @Test
+  public void receivedGoAwayShouldFailUnknownStreams() throws Exception {
+    enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+
+    // Read a GOAWAY that indicates our stream was never processed by the server.
+    channelRead(goAwayFrame(0, 8 /* Cancel */, Unpooled.copiedBuffer("this is a test", UTF_8)));
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
+    verify(streamListener).closed(captor.capture(), same(REFUSED), notNull(Metadata.class));
+    assertEquals(Status.CANCELLED.getCode(), captor.getValue().getCode());
+    assertEquals("HTTP/2 error code: CANCEL\nReceived Goaway\nthis is a test",
+        captor.getValue().getDescription());
+  }
+
+  @Test
+  public void receivedGoAwayShouldFailUnknownBufferedStreams() throws Exception {
+    receiveMaxConcurrentStreams(0);
+
+    ChannelFuture future = enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+
+    // Read a GOAWAY that indicates our stream was never processed by the server.
+    channelRead(goAwayFrame(0, 8 /* Cancel */, Unpooled.copiedBuffer("this is a test", UTF_8)));
+    assertTrue(future.isDone());
+    assertFalse(future.isSuccess());
+    Status status = Status.fromThrowable(future.cause());
+    assertEquals(Status.CANCELLED.getCode(), status.getCode());
+    assertEquals("HTTP/2 error code: CANCEL\nReceived Goaway\nthis is a test",
+        status.getDescription());
+  }
+
+  @Test
+  public void receivedGoAwayShouldFailNewStreams() throws Exception {
+    // Read a GOAWAY that indicates our stream was never processed by the server.
+    channelRead(goAwayFrame(0, 8 /* Cancel */, Unpooled.copiedBuffer("this is a test", UTF_8)));
+
+    // Now try to create a stream.
+    ChannelFuture future = enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+    assertTrue(future.isDone());
+    assertFalse(future.isSuccess());
+    Status status = Status.fromThrowable(future.cause());
+    assertEquals(Status.CANCELLED.getCode(), status.getCode());
+    assertEquals("HTTP/2 error code: CANCEL\nReceived Goaway\nthis is a test",
+        status.getDescription());
+  }
+
+  // This test is not as useful as it looks, because the HTTP/2 Netty code catches and doesn't
+  // propagate exceptions during the onGoAwayReceived callback.
+  @Test
+  public void receivedGoAway_notUtf8() throws Exception {
+    // 0xFF is never permitted in UTF-8. 0xF0 should have 3 continuations following, and 0x0a isn't
+    // a continuation.
+    channelRead(goAwayFrame(0, 11 /* ENHANCE_YOUR_CALM */,
+          Unpooled.copiedBuffer(new byte[] {(byte) 0xFF, (byte) 0xF0, (byte) 0x0a})));
+  }
+
+  @Test
+  public void receivedGoAway_enhanceYourCalmWithoutTooManyPings() throws Exception {
+    final AtomicBoolean b = new AtomicBoolean();
+    tooManyPingsRunnable = new Runnable() {
+      @Override
+      public void run() {
+        b.set(true);
+      }
+    };
+    setUp();
+
+    channelRead(goAwayFrame(0, 11 /* ENHANCE_YOUR_CALM */,
+          Unpooled.copiedBuffer("not_many_pings", UTF_8)));
+    assertFalse(b.get());
+  }
+
+  @Test
+  public void receivedGoAway_enhanceYourCalmWithTooManyPings() throws Exception {
+    final AtomicBoolean b = new AtomicBoolean();
+    tooManyPingsRunnable = new Runnable() {
+      @Override
+      public void run() {
+        b.set(true);
+      }
+    };
+    setUp();
+
+    channelRead(goAwayFrame(0, 11 /* ENHANCE_YOUR_CALM */,
+          Unpooled.copiedBuffer("too_many_pings", UTF_8)));
+    assertTrue(b.get());
+  }
+
+  @Test
+  public void cancelStreamShouldCreateAndThenFailBufferedStream() throws Exception {
+    receiveMaxConcurrentStreams(0);
+    enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+    assertEquals(3, streamTransportState.id());
+    cancelStream(Status.CANCELLED);
+    verify(streamListener).closed(eq(Status.CANCELLED), same(PROCESSED), any(Metadata.class));
+  }
+
+  @Test
+  public void channelShutdownShouldCancelBufferedStreams() throws Exception {
+    // Force a stream to get added to the pending queue.
+    receiveMaxConcurrentStreams(0);
+    ChannelFuture future = enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+
+    handler().channelInactive(ctx());
+    assertTrue(future.isDone());
+    assertFalse(future.isSuccess());
+    verify(mockKeepAliveManager, times(1)).onTransportTermination(); // channelInactive
+    verifyNoMoreInteractions(mockKeepAliveManager);
+  }
+
+  @Test
+  public void channelShutdownShouldFailInFlightStreams() throws Exception {
+    createStream();
+
+    handler().channelInactive(ctx());
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
+    verify(streamListener).closed(captor.capture(), same(PROCESSED), notNull(Metadata.class));
+    assertEquals(Status.UNAVAILABLE.getCode(), captor.getValue().getCode());
+  }
+
+  @Test
+  public void connectionWindowShouldBeOverridden() throws Exception {
+    flowControlWindow = 1048576; // 1MiB
+    setUp();
+
+    Http2Stream connectionStream = connection().connectionStream();
+    Http2LocalFlowController localFlowController = connection().local().flowController();
+    int actualInitialWindowSize = localFlowController.initialWindowSize(connectionStream);
+    int actualWindowSize = localFlowController.windowSize(connectionStream);
+    assertEquals(flowControlWindow, actualWindowSize);
+    assertEquals(flowControlWindow, actualInitialWindowSize);
+    assertEquals(1048576, actualWindowSize);
+  }
+
+  @Test
+  public void createIncrementsIdsForActualAndBufferdStreams() throws Exception {
+    receiveMaxConcurrentStreams(2);
+    enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+    assertEquals(3, streamTransportState.id());
+
+    streamTransportState = new TransportStateImpl(
+        handler(),
+        channel().eventLoop(),
+        DEFAULT_MAX_MESSAGE_SIZE,
+        transportTracer);
+    streamTransportState.setListener(streamListener);
+    enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+    assertEquals(5, streamTransportState.id());
+
+    streamTransportState = new TransportStateImpl(
+        handler(),
+        channel().eventLoop(),
+        DEFAULT_MAX_MESSAGE_SIZE,
+        transportTracer);
+    streamTransportState.setListener(streamListener);
+    enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+    assertEquals(7, streamTransportState.id());
+
+    verify(mockKeepAliveManager, times(1)).onTransportActive(); // onStreamActive
+    verifyNoMoreInteractions(mockKeepAliveManager);
+  }
+
+  @Test
+  public void exhaustedStreamsShouldFail() throws Exception {
+    streamId = Integer.MAX_VALUE;
+    setUp();
+
+    assertNull(lifecycleManager.getShutdownStatus());
+    // Create the MAX_INT stream.
+    ChannelFuture future = createStream();
+    assertTrue(future.isSuccess());
+
+    // This should fail - out of stream IDs.
+    future = createStream();
+    assertTrue(future.isDone());
+    assertFalse(future.isSuccess());
+    Status status = lifecycleManager.getShutdownStatus();
+    assertNotNull(status);
+    assertTrue("status does not reference 'exhausted': " + status,
+        status.getDescription().contains("exhausted"));
+  }
+
+  @Test
+  public void ping() throws Exception {
+    PingCallbackImpl callback1 = new PingCallbackImpl();
+    assertEquals(0, transportTracer.getStats().keepAlivesSent);
+    sendPing(callback1);
+    assertEquals(1, transportTracer.getStats().keepAlivesSent);
+    // add'l ping will be added as listener to outstanding operation
+    PingCallbackImpl callback2 = new PingCallbackImpl();
+    sendPing(callback2);
+    assertEquals(1, transportTracer.getStats().keepAlivesSent);
+
+    ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(long.class);
+    verifyWrite().writePing(eq(ctx()), eq(false), captor.capture(),
+        any(ChannelPromise.class));
+
+    // getting a bad ack won't cause the callback to be invoked
+    long pingPayload = captor.getValue();
+    // to compute bad payload, read the good payload and subtract one
+    long badPingPayload = pingPayload - 1;
+
+    channelRead(pingFrame(true, badPingPayload));
+    // operation not complete because ack was wrong
+    assertEquals(0, callback1.invocationCount);
+    assertEquals(0, callback2.invocationCount);
+
+    nanoTime += 10101;
+
+    // reading the proper response should complete the future
+    channelRead(pingFrame(true, pingPayload));
+    assertEquals(1, callback1.invocationCount);
+    assertEquals(10101, callback1.roundTripTime);
+    assertNull(callback1.failureCause);
+    // callback2 piggy-backed on same operation
+    assertEquals(1, callback2.invocationCount);
+    assertEquals(10101, callback2.roundTripTime);
+    assertNull(callback2.failureCause);
+
+    // now that previous ping is done, next request starts a new operation
+    callback1 = new PingCallbackImpl();
+    assertEquals(1, transportTracer.getStats().keepAlivesSent);
+    sendPing(callback1);
+    assertEquals(2, transportTracer.getStats().keepAlivesSent);
+    assertEquals(0, callback1.invocationCount);
+  }
+
+  @Test
+  public void ping_failsWhenChannelCloses() throws Exception {
+    PingCallbackImpl callback = new PingCallbackImpl();
+    sendPing(callback);
+    assertEquals(0, callback.invocationCount);
+
+    handler().channelInactive(ctx());
+    // ping failed on channel going inactive
+    assertEquals(1, callback.invocationCount);
+    assertTrue(callback.failureCause instanceof StatusException);
+    assertEquals(Status.Code.UNAVAILABLE,
+        ((StatusException) callback.failureCause).getStatus().getCode());
+    // A failed ping is still counted
+    assertEquals(1, transportTracer.getStats().keepAlivesSent);
+  }
+
+  @Test
+  public void oustandingUserPingShouldNotInteractWithDataPing() throws Exception {
+    createStream();
+    handler().setAutoTuneFlowControl(true);
+
+    PingCallbackImpl callback = new PingCallbackImpl();
+    assertEquals(0, transportTracer.getStats().keepAlivesSent);
+    sendPing(callback);
+    assertEquals(1, transportTracer.getStats().keepAlivesSent);
+    ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(long.class);
+    verifyWrite().writePing(eq(ctx()), eq(false), captor.capture(), any(ChannelPromise.class));
+    long payload = captor.getValue();
+    channelRead(grpcDataFrame(3, false, contentAsArray()));
+    long pingData = handler().flowControlPing().payload();
+    channelRead(pingFrame(true, pingData));
+
+    assertEquals(1, handler().flowControlPing().getPingReturn());
+    assertEquals(0, callback.invocationCount);
+
+    channelRead(pingFrame(true, payload));
+
+    assertEquals(1, handler().flowControlPing().getPingReturn());
+    assertEquals(1, callback.invocationCount);
+    assertEquals(1, transportTracer.getStats().keepAlivesSent);
+  }
+
+  @Override
+  public void dataPingAckIsRecognized() throws Exception {
+    super.dataPingAckIsRecognized();
+    verify(mockKeepAliveManager, times(1)).onTransportActive(); // onStreamActive
+    // onHeadersRead, onDataRead, onPingAckRead
+    verify(mockKeepAliveManager, times(3)).onDataReceived();
+    verifyNoMoreInteractions(mockKeepAliveManager);
+  }
+
+  @Test
+  public void exceptionCaughtShouldCloseConnection() throws Exception {
+    handler().exceptionCaught(ctx(), new RuntimeException("fake exception"));
+
+    // TODO(nmittler): EmbeddedChannel does not currently invoke the channelInactive processing,
+    // so exceptionCaught() will not close streams properly in this test.
+    // Once https://github.com/netty/netty/issues/4316 is resolved, we should also verify that
+    // any open streams are closed properly.
+    assertFalse(channel().isOpen());
+  }
+
+  @Override
+  protected void makeStream() throws Exception {
+    createStream();
+    // The tests in NettyServerHandlerTest expect the header to already be read, since they work on
+    // both client- and server-side.
+    Http2Headers headers = new DefaultHttp2Headers().status(STATUS_OK)
+        .set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC);
+    ByteBuf headersFrame = headersFrame(3, headers);
+    channelRead(headersFrame);
+  }
+
+  @CanIgnoreReturnValue
+  private ChannelFuture sendPing(PingCallback callback) {
+    return enqueue(new SendPingCommand(callback, MoreExecutors.directExecutor()));
+  }
+
+  private void receiveMaxConcurrentStreams(int max) throws Exception {
+    ByteBuf serializedSettings = serializeSettings(new Http2Settings().maxConcurrentStreams(max));
+    channelRead(serializedSettings);
+  }
+
+  @CanIgnoreReturnValue
+  private ChannelFuture createStream() throws Exception {
+    ChannelFuture future = enqueue(new CreateStreamCommand(grpcHeaders, streamTransportState));
+    return future;
+  }
+
+  @CanIgnoreReturnValue
+  private ChannelFuture cancelStream(Status status) throws Exception {
+    return enqueue(new CancelClientStreamCommand(streamTransportState, status));
+  }
+
+  @Override
+  protected NettyClientHandler newHandler() throws Http2Exception {
+    Http2Connection connection = new DefaultHttp2Connection(false);
+
+    // Create and close a stream previous to the nextStreamId.
+    Http2Stream stream = connection.local().createStream(streamId - 2, true);
+    stream.close();
+
+    final Ticker ticker = new Ticker() {
+      @Override
+      public long read() {
+        return nanoTime;
+      }
+    };
+    Supplier<Stopwatch> stopwatchSupplier = new Supplier<Stopwatch>() {
+      @Override
+      public Stopwatch get() {
+        return Stopwatch.createUnstarted(ticker);
+      }
+    };
+    return NettyClientHandler.newHandler(
+        connection,
+        frameReader(),
+        frameWriter(),
+        lifecycleManager,
+        mockKeepAliveManager,
+        flowControlWindow,
+        maxHeaderListSize,
+        stopwatchSupplier,
+        tooManyPingsRunnable,
+        transportTracer,
+        Attributes.EMPTY,
+        "someauthority");
+  }
+
+  @Override
+  protected WriteQueue initWriteQueue() {
+    handler().startWriteQueue(channel());
+    return handler().getWriteQueue();
+  }
+
+  private AsciiString as(String string) {
+    return new AsciiString(string);
+  }
+
+  private static class PingCallbackImpl implements ClientTransport.PingCallback {
+    int invocationCount;
+    long roundTripTime;
+    Throwable failureCause;
+
+    @Override
+    public void onSuccess(long roundTripTimeNanos) {
+      invocationCount++;
+      this.roundTripTime = roundTripTimeNanos;
+    }
+
+    @Override
+    public void onFailure(Throwable cause) {
+      invocationCount++;
+      this.failureCause = cause;
+    }
+  }
+
+  private static class TransportStateImpl extends NettyClientStream.TransportState {
+    public TransportStateImpl(
+        NettyClientHandler handler,
+        EventLoop eventLoop,
+        int maxMessageSize,
+        TransportTracer transportTracer) {
+      super(handler, eventLoop, maxMessageSize, StatsTraceContext.NOOP, transportTracer);
+    }
+
+    @Override
+    protected Status statusFromFailedFuture(ChannelFuture f) {
+      return Utils.statusFromThrowable(f.cause());
+    }
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyClientStreamTest.java b/netty/src/test/java/io/grpc/netty/NettyClientStreamTest.java
new file mode 100644
index 0000000..4694ef4
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyClientStreamTest.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED;
+import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
+import static io.grpc.netty.NettyTestUtil.messageFrame;
+import static io.grpc.netty.Utils.CONTENT_TYPE_GRPC;
+import static io.grpc.netty.Utils.CONTENT_TYPE_HEADER;
+import static io.grpc.netty.Utils.STATUS_OK;
+import static io.netty.util.CharsetUtil.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.io.BaseEncoding;
+import io.grpc.InternalStatus;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.internal.ClientStreamListener;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.StreamListener;
+import io.grpc.internal.TransportTracer;
+import io.grpc.netty.WriteQueue.QueuedCommand;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.DefaultChannelPromise;
+import io.netty.handler.codec.http2.DefaultHttp2Headers;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.util.AsciiString;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.Queue;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Tests for {@link NettyClientStream}.
+ */
+@RunWith(JUnit4.class)
+public class NettyClientStreamTest extends NettyStreamTestBase<NettyClientStream> {
+  @Mock
+  protected ClientStreamListener listener;
+
+  @Mock
+  protected NettyClientHandler handler;
+
+  @SuppressWarnings("unchecked")
+  private MethodDescriptor.Marshaller<Void> marshaller = mock(MethodDescriptor.Marshaller.class);
+  private final Queue<InputStream> listenerMessageQueue = new LinkedList<InputStream>();
+
+  // Must be initialized before @Before, because it is used by createStream()
+  private MethodDescriptor<?, ?> methodDescriptor = MethodDescriptor.<Void, Void>newBuilder()
+      .setType(MethodDescriptor.MethodType.UNARY)
+      .setFullMethodName("testService/test")
+      .setRequestMarshaller(marshaller)
+      .setResponseMarshaller(marshaller)
+      .build();
+
+  private final TransportTracer transportTracer = new TransportTracer();
+
+  /** Set up for test. */
+  @Before
+  @Override
+  public void setUp() {
+    super.setUp();
+
+    doAnswer(
+          new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+              StreamListener.MessageProducer producer =
+                  (StreamListener.MessageProducer) invocation.getArguments()[0];
+              InputStream message;
+              while ((message = producer.next()) != null) {
+                listenerMessageQueue.add(message);
+              }
+              return null;
+            }
+          })
+      .when(listener)
+      .messagesAvailable(Matchers.<StreamListener.MessageProducer>any());
+  }
+
+  @Override
+  protected ClientStreamListener listener() {
+    return listener;
+  }
+
+  @Override
+  protected Queue<InputStream> listenerMessageQueue() {
+    return listenerMessageQueue;
+  }
+
+  @Test
+  public void closeShouldSucceed() {
+    // Force stream creation.
+    stream().transportState().setId(STREAM_ID);
+    stream().halfClose();
+    verifyNoMoreInteractions(listener);
+  }
+
+  @Test
+  public void cancelShouldSendCommand() {
+    // Set stream id to indicate it has been created
+    stream().transportState().setId(STREAM_ID);
+    stream().cancel(Status.CANCELLED);
+    ArgumentCaptor<CancelClientStreamCommand> commandCaptor =
+        ArgumentCaptor.forClass(CancelClientStreamCommand.class);
+    verify(writeQueue).enqueue(commandCaptor.capture(), eq(true));
+    assertEquals(commandCaptor.getValue().reason(), Status.CANCELLED);
+  }
+
+  @Test
+  public void deadlineExceededCancelShouldSendCommand() {
+    // Set stream id to indicate it has been created
+    stream().transportState().setId(STREAM_ID);
+    stream().cancel(Status.DEADLINE_EXCEEDED);
+    ArgumentCaptor<CancelClientStreamCommand> commandCaptor =
+        ArgumentCaptor.forClass(CancelClientStreamCommand.class);
+    verify(writeQueue).enqueue(commandCaptor.capture(), eq(true));
+    assertEquals(commandCaptor.getValue().reason(), Status.DEADLINE_EXCEEDED);
+  }
+
+  @Test
+  public void cancelShouldStillSendCommandIfStreamNotCreatedToCancelCreation() {
+    stream().cancel(Status.CANCELLED);
+    verify(writeQueue).enqueue(isA(CancelClientStreamCommand.class), eq(true));
+  }
+
+  @Test
+  public void writeMessageShouldSendRequest() throws Exception {
+    // Force stream creation.
+    stream().transportState().setId(STREAM_ID);
+    byte[] msg = smallMessage();
+    stream.writeMessage(new ByteArrayInputStream(msg));
+    stream.flush();
+    verify(writeQueue).enqueue(
+        eq(new SendGrpcFrameCommand(stream.transportState(), messageFrame(MESSAGE), false)),
+        eq(true));
+  }
+
+  @Test
+  public void writeMessageShouldSendRequestUnknownLength() throws Exception {
+    // Force stream creation.
+    stream().transportState().setId(STREAM_ID);
+    byte[] msg = smallMessage();
+    stream.writeMessage(new BufferedInputStream(new ByteArrayInputStream(msg)));
+    stream.flush();
+    // Two writes occur, one for the GRPC frame header and the second with the payload
+    // The framer reports the message count when the payload is completely written
+    verify(writeQueue).enqueue(
+            eq(new SendGrpcFrameCommand(
+                stream.transportState(), messageFrame(MESSAGE).slice(0, 5), false)),
+            eq(false));
+    verify(writeQueue).enqueue(
+        eq(new SendGrpcFrameCommand(
+            stream.transportState(), messageFrame(MESSAGE).slice(5, 11), false)),
+        eq(true));
+  }
+
+  @Test
+  public void setStatusWithOkShouldCloseStream() {
+    stream().transportState().setId(STREAM_ID);
+    stream().transportState().transportReportStatus(Status.OK, true, new Metadata());
+    verify(listener).closed(same(Status.OK), same(PROCESSED), any(Metadata.class));
+  }
+
+  @Test
+  public void setStatusWithErrorShouldCloseStream() {
+    Status errorStatus = Status.INTERNAL;
+    stream().transportState().transportReportStatus(errorStatus, true, new Metadata());
+    verify(listener).closed(eq(errorStatus), same(PROCESSED), any(Metadata.class));
+  }
+
+  @Test
+  public void setStatusWithOkShouldNotOverrideError() {
+    Status errorStatus = Status.INTERNAL;
+    stream().transportState().transportReportStatus(errorStatus, true, new Metadata());
+    stream().transportState().transportReportStatus(Status.OK, true, new Metadata());
+    verify(listener).closed(any(Status.class), same(PROCESSED), any(Metadata.class));
+  }
+
+  @Test
+  public void setStatusWithErrorShouldNotOverridePreviousError() {
+    Status errorStatus = Status.INTERNAL;
+    stream().transportState().transportReportStatus(errorStatus, true, new Metadata());
+    stream().transportState().transportReportStatus(
+        Status.fromThrowable(new RuntimeException("fake")), true, new Metadata());
+    verify(listener).closed(any(Status.class), same(PROCESSED), any(Metadata.class));
+  }
+
+  @Override
+  @Test
+  public void inboundMessageShouldCallListener() throws Exception {
+    // Receive headers first so that it's a valid GRPC response.
+    stream().transportState().setId(STREAM_ID);
+    stream().transportState().transportHeadersReceived(grpcResponseHeaders(), false);
+    super.inboundMessageShouldCallListener();
+  }
+
+  @Test
+  public void inboundHeadersShouldCallListenerHeadersRead() throws Exception {
+    stream().transportState().setId(STREAM_ID);
+    Http2Headers headers = grpcResponseHeaders();
+    stream().transportState().transportHeadersReceived(headers, false);
+    verify(listener).headersRead(any(Metadata.class));
+  }
+
+  @Test
+  public void inboundTrailersClosesCall() throws Exception {
+    stream().transportState().setId(STREAM_ID);
+    stream().transportState().transportHeadersReceived(grpcResponseHeaders(), false);
+    super.inboundMessageShouldCallListener();
+    stream().transportState().transportHeadersReceived(grpcResponseTrailers(Status.OK), true);
+  }
+
+  @Test
+  public void inboundTrailersBeforeHalfCloseSendsRstStream() {
+    stream().transportState().setId(STREAM_ID);
+    stream().transportState().transportHeadersReceived(grpcResponseHeaders(), false);
+    stream().transportState().transportHeadersReceived(grpcResponseTrailers(Status.OK), true);
+
+    // Verify a cancel stream with reason=null is sent to the handler.
+    ArgumentCaptor<CancelClientStreamCommand> captor = ArgumentCaptor
+        .forClass(CancelClientStreamCommand.class);
+    verify(writeQueue).enqueue(captor.capture(), eq(true));
+    assertNull(captor.getValue().reason());
+  }
+
+  @Test
+  public void inboundTrailersAfterHalfCloseDoesNotSendRstStream() {
+    stream().transportState().setId(STREAM_ID);
+    stream().transportState().transportHeadersReceived(grpcResponseHeaders(), false);
+    stream.halfClose();
+    stream().transportState().transportHeadersReceived(grpcResponseTrailers(Status.OK), true);
+    verify(writeQueue, never()).enqueue(isA(CancelClientStreamCommand.class), eq(true));
+  }
+
+  @Test
+  public void inboundStatusShouldSetStatus() throws Exception {
+    stream().transportState().setId(STREAM_ID);
+
+    // Receive headers first so that it's a valid GRPC response.
+    stream().transportState().transportHeadersReceived(grpcResponseHeaders(), false);
+
+    stream().transportState().transportHeadersReceived(grpcResponseTrailers(Status.INTERNAL), true);
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
+    verify(listener).closed(captor.capture(), same(PROCESSED), any(Metadata.class));
+    assertEquals(Status.INTERNAL.getCode(), captor.getValue().getCode());
+  }
+
+  @Test
+  public void invalidInboundHeadersCancelStream() throws Exception {
+    stream().transportState().setId(STREAM_ID);
+    Http2Headers headers = grpcResponseHeaders();
+    headers.set("random", "4");
+    headers.remove(CONTENT_TYPE_HEADER);
+    // Remove once b/16290036 is fixed.
+    headers.status(new AsciiString("500"));
+    stream().transportState().transportHeadersReceived(headers, false);
+    verify(listener, never()).closed(any(Status.class), any(Metadata.class));
+
+    // We are now waiting for 100 bytes of error context on the stream, cancel has not yet been
+    // sent
+    verify(channel, never()).writeAndFlush(any(CancelClientStreamCommand.class));
+    stream().transportState().transportDataReceived(Unpooled.buffer(100).writeZero(100), false);
+    verify(channel, never()).writeAndFlush(any(CancelClientStreamCommand.class));
+    stream().transportState().transportDataReceived(Unpooled.buffer(1000).writeZero(1000), false);
+
+    // Now verify that cancel is sent and an error is reported to the listener
+    verify(writeQueue).enqueue(isA(CancelClientStreamCommand.class), eq(true));
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
+    ArgumentCaptor<Metadata> metadataCaptor = ArgumentCaptor.forClass(Metadata.class);
+    verify(listener).closed(captor.capture(), same(PROCESSED), metadataCaptor.capture());
+    assertEquals(Status.UNKNOWN.getCode(), captor.getValue().getCode());
+    assertEquals("4", metadataCaptor.getValue()
+        .get(Metadata.Key.of("random", Metadata.ASCII_STRING_MARSHALLER)));
+
+  }
+
+  @Test
+  public void invalidInboundContentTypeShouldCancelStream() {
+    // Set stream id to indicate it has been created
+    stream().transportState().setId(STREAM_ID);
+    Http2Headers headers = new DefaultHttp2Headers().status(STATUS_OK).set(CONTENT_TYPE_HEADER,
+            new AsciiString("application/bad", UTF_8));
+    stream().transportState().transportHeadersReceived(headers, false);
+    Http2Headers trailers = new DefaultHttp2Headers()
+        .set(new AsciiString("grpc-status", UTF_8), new AsciiString("0", UTF_8));
+    stream().transportState().transportHeadersReceived(trailers, true);
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
+    ArgumentCaptor<Metadata> metadataCaptor = ArgumentCaptor.forClass(Metadata.class);
+    verify(listener).closed(captor.capture(), same(PROCESSED), metadataCaptor.capture());
+    Status status = captor.getValue();
+    assertEquals(Status.Code.UNKNOWN, status.getCode());
+    assertTrue(status.getDescription().contains("content-type"));
+    assertEquals("application/bad", metadataCaptor.getValue()
+        .get(Metadata.Key.of("Content-Type", Metadata.ASCII_STRING_MARSHALLER)));
+  }
+
+  @Test
+  public void nonGrpcResponseShouldSetStatus() throws Exception {
+    stream().transportState().transportDataReceived(Unpooled.copiedBuffer(MESSAGE, UTF_8), true);
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
+    verify(listener).closed(captor.capture(), same(PROCESSED), any(Metadata.class));
+    assertEquals(Status.Code.INTERNAL, captor.getValue().getCode());
+  }
+
+  @Test
+  public void deframedDataAfterCancelShouldBeIgnored() throws Exception {
+    stream().transportState().setId(STREAM_ID);
+    // Receive headers first so that it's a valid GRPC response.
+    stream().transportState().transportHeadersReceived(grpcResponseHeaders(), false);
+
+    // Receive 2 consecutive empty frames. Only one is delivered at a time to the listener.
+    stream().transportState().transportDataReceived(simpleGrpcFrame(), false);
+    stream().transportState().transportDataReceived(simpleGrpcFrame(), false);
+
+    // Only allow the first to be delivered.
+    stream().request(1);
+
+    // Receive error trailers. The server status will not be processed until after all of the
+    // data frames have been processed. Since cancellation will interrupt message delivery,
+    // this status will never be processed and the listener will instead only see the
+    // cancellation.
+    stream().transportState().transportHeadersReceived(grpcResponseTrailers(Status.INTERNAL), true);
+
+    // Verify that the first was delivered.
+    assertNotNull("message expected", listenerMessageQueue.poll());
+    assertNull("no additional message expected", listenerMessageQueue.poll());
+
+    // Now set the error status.
+    Metadata trailers = Utils.convertTrailers(grpcResponseTrailers(Status.CANCELLED));
+    stream().transportState().transportReportStatus(Status.CANCELLED, true, trailers);
+
+    // Now allow the delivery of the second.
+    stream().request(1);
+
+    // Verify that the listener was only notified of the first message, not the second.
+    assertNull("no additional message expected", listenerMessageQueue.poll());
+    verify(listener).closed(eq(Status.CANCELLED), same(PROCESSED), eq(trailers));
+  }
+
+  @Test
+  public void dataFrameWithEosShouldDeframeAndThenFail() {
+    stream().transportState().setId(STREAM_ID);
+    stream().request(1);
+
+    // Receive headers first so that it's a valid GRPC response.
+    stream().transportState().transportHeadersReceived(grpcResponseHeaders(), false);
+
+    // Receive a DATA frame with EOS set.
+    stream().transportState().transportDataReceived(simpleGrpcFrame(), true);
+
+    // Verify that the message was delivered.
+    assertNotNull("message expected", listenerMessageQueue.poll());
+    assertNull("no additional message expected", listenerMessageQueue.poll());
+
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
+    verify(listener).closed(captor.capture(), same(PROCESSED), any(Metadata.class));
+    assertEquals(Status.Code.INTERNAL, captor.getValue().getCode());
+  }
+
+  @Test
+  public void setHttp2StreamShouldNotifyReady() {
+    listener = mock(ClientStreamListener.class);
+
+    stream = new NettyClientStream(new TransportStateImpl(handler, DEFAULT_MAX_MESSAGE_SIZE),
+        methodDescriptor,
+        new Metadata(),
+        channel,
+        AsciiString.of("localhost"),
+        AsciiString.of("http"),
+        AsciiString.of("agent"),
+        StatsTraceContext.NOOP,
+        transportTracer);
+    stream.start(listener);
+    stream().transportState().setId(STREAM_ID);
+    verify(listener, never()).onReady();
+    assertFalse(stream.isReady());
+    stream().transportState().setHttp2Stream(http2Stream);
+    verify(listener).onReady();
+    assertTrue(stream.isReady());
+  }
+
+  @Test
+  public void removeUserAgentFromApplicationHeaders() {
+    Metadata metadata = new Metadata();
+    metadata.put(GrpcUtil.USER_AGENT_KEY, "bad agent");
+    listener = mock(ClientStreamListener.class);
+    Mockito.reset(writeQueue);
+    ChannelPromise completedPromise = new DefaultChannelPromise(channel)
+        .setSuccess();
+    when(writeQueue.enqueue(any(QueuedCommand.class), any(boolean.class)))
+        .thenReturn(completedPromise);
+
+    stream = new NettyClientStream(
+        new TransportStateImpl(handler, DEFAULT_MAX_MESSAGE_SIZE),
+        methodDescriptor,
+        new Metadata(),
+        channel,
+        AsciiString.of("localhost"),
+        AsciiString.of("http"),
+        AsciiString.of("good agent"),
+        StatsTraceContext.NOOP,
+        transportTracer);
+    stream.start(listener);
+
+    ArgumentCaptor<CreateStreamCommand> cmdCap = ArgumentCaptor.forClass(CreateStreamCommand.class);
+    verify(writeQueue).enqueue(cmdCap.capture(), eq(false));
+    assertThat(ImmutableListMultimap.copyOf(cmdCap.getValue().headers()))
+        .containsEntry(Utils.USER_AGENT, AsciiString.of("good agent"));
+  }
+
+  @Test
+  public void getRequestSentThroughHeader() {
+    // Creating a GET method
+    MethodDescriptor<?, ?> descriptor = MethodDescriptor.<Void, Void>newBuilder()
+        .setType(MethodDescriptor.MethodType.UNARY)
+        .setFullMethodName("testService/test")
+        .setRequestMarshaller(marshaller)
+        .setResponseMarshaller(marshaller)
+        .setIdempotent(true)
+        .setSafe(true)
+        .build();
+    NettyClientStream stream = new NettyClientStream(
+        new TransportStateImpl(handler, DEFAULT_MAX_MESSAGE_SIZE),
+        descriptor,
+        new Metadata(),
+        channel,
+        AsciiString.of("localhost"),
+        AsciiString.of("http"),
+        AsciiString.of("agent"),
+        StatsTraceContext.NOOP,
+        transportTracer);
+    stream.start(listener);
+    stream.transportState().setId(STREAM_ID);
+    stream.transportState().setHttp2Stream(http2Stream);
+
+    byte[] msg = smallMessage();
+    stream.writeMessage(new ByteArrayInputStream(msg));
+    stream.flush();
+    stream.halfClose();
+    ArgumentCaptor<CreateStreamCommand> cmdCap = ArgumentCaptor.forClass(CreateStreamCommand.class);
+    verify(writeQueue).enqueue(cmdCap.capture(), eq(true));
+    ImmutableListMultimap<CharSequence, CharSequence> headers =
+        ImmutableListMultimap.copyOf(cmdCap.getValue().headers());
+    assertThat(headers).containsEntry(AsciiString.of(":method"), Utils.HTTP_GET_METHOD);
+    assertThat(headers)
+        .containsEntry(
+            AsciiString.of(":path"),
+            AsciiString.of("/testService/test?" + BaseEncoding.base64().encode(msg)));
+  }
+
+  @Override
+  protected NettyClientStream createStream() {
+    when(handler.getWriteQueue()).thenReturn(writeQueue);
+    NettyClientStream stream = new NettyClientStream(
+        new TransportStateImpl(handler, DEFAULT_MAX_MESSAGE_SIZE),
+        methodDescriptor,
+        new Metadata(),
+        channel,
+        AsciiString.of("localhost"),
+        AsciiString.of("http"),
+        AsciiString.of("agent"),
+        StatsTraceContext.NOOP,
+        transportTracer);
+    stream.start(listener);
+    stream.transportState().setId(STREAM_ID);
+    stream.transportState().setHttp2Stream(http2Stream);
+    reset(listener);
+    return stream;
+  }
+
+  @Override
+  protected void sendHeadersIfServer() {}
+
+  @Override
+  protected void closeStream() {
+    stream().cancel(Status.CANCELLED);
+  }
+
+  private ByteBuf simpleGrpcFrame() {
+    return Unpooled.wrappedBuffer(new byte[] {0, 0, 0, 0, 2, 3, 14});
+  }
+
+  private NettyClientStream stream() {
+    return stream;
+  }
+
+  private Http2Headers grpcResponseHeaders() {
+    return new DefaultHttp2Headers()
+        .status(STATUS_OK)
+        .set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC);
+  }
+
+  private Http2Headers grpcResponseTrailers(Status status) {
+    Metadata trailers = new Metadata();
+    trailers.put(InternalStatus.CODE_KEY, status);
+    return Utils.convertTrailers(trailers, true);
+  }
+
+  private class TransportStateImpl extends NettyClientStream.TransportState {
+    public TransportStateImpl(NettyClientHandler handler, int maxMessageSize) {
+      super(handler, channel.eventLoop(), maxMessageSize, StatsTraceContext.NOOP, transportTracer);
+    }
+
+    @Override
+    protected Status statusFromFailedFuture(ChannelFuture f) {
+      return Utils.statusFromThrowable(f.cause());
+    }
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportFactoryTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportFactoryTest.java
new file mode 100644
index 0000000..d512271
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportFactoryTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.internal.ClientTransportFactory;
+import io.grpc.internal.testing.AbstractClientTransportFactoryTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class NettyClientTransportFactoryTest extends AbstractClientTransportFactoryTest {
+  @Override protected ClientTransportFactory newClientTransportFactory() {
+    return NettyChannelBuilder
+        .forAddress("localhost", 0)
+        .buildTransportFactory();
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java
new file mode 100644
index 0000000..682af1b
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyClientTransportTest.java
@@ -0,0 +1,825 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
+import static io.grpc.internal.GrpcUtil.DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS;
+import static io.grpc.internal.GrpcUtil.DEFAULT_SERVER_KEEPALIVE_TIME_NANOS;
+import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED;
+import static io.grpc.internal.GrpcUtil.USER_AGENT_KEY;
+import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE;
+import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_AGE_NANOS_DISABLED;
+import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_IDLE_NANOS_DISABLED;
+import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.io.ByteStreams;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.Attributes;
+import io.grpc.CallOptions;
+import io.grpc.Grpc;
+import io.grpc.InternalChannelz;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.ServerStreamTracer;
+import io.grpc.Status;
+import io.grpc.Status.Code;
+import io.grpc.StatusException;
+import io.grpc.internal.ClientStream;
+import io.grpc.internal.ClientStreamListener;
+import io.grpc.internal.ClientTransport;
+import io.grpc.internal.FakeClock;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.ManagedClientTransport;
+import io.grpc.internal.ServerListener;
+import io.grpc.internal.ServerStream;
+import io.grpc.internal.ServerStreamListener;
+import io.grpc.internal.ServerTransport;
+import io.grpc.internal.ServerTransportListener;
+import io.grpc.internal.TransportTracer;
+import io.grpc.internal.testing.TestUtils;
+import io.netty.channel.ChannelConfig;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannelConfig;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.http2.StreamBufferingEncoder;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SupportedCipherSuiteFilter;
+import io.netty.util.AsciiString;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import javax.net.ssl.SSLHandshakeException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link NettyClientTransport}.
+ */
+@RunWith(JUnit4.class)
+public class NettyClientTransportTest {
+  private static final SslContext SSL_CONTEXT = createSslContext();
+
+  @Mock
+  private ManagedClientTransport.Listener clientTransportListener;
+
+  private final List<NettyClientTransport> transports = new ArrayList<>();
+  private final NioEventLoopGroup group = new NioEventLoopGroup(1);
+  private final EchoServerListener serverListener = new EchoServerListener();
+  private final InternalChannelz channelz = new InternalChannelz();
+  private Runnable tooManyPingsRunnable = new Runnable() {
+    // Throwing is useless in this method, because Netty doesn't propagate the exception
+    @Override public void run() {}
+  };
+  private Attributes eagAttributes = Attributes.EMPTY;
+
+  private ProtocolNegotiator negotiator = ProtocolNegotiators.serverTls(SSL_CONTEXT);
+
+  private InetSocketAddress address;
+  private String authority;
+  private NettyServer server;
+
+  @Before
+  public void setup() {
+    MockitoAnnotations.initMocks(this);
+  }
+
+  @After
+  public void teardown() throws Exception {
+    for (NettyClientTransport transport : transports) {
+      transport.shutdown(Status.UNAVAILABLE);
+    }
+
+    if (server != null) {
+      server.shutdown();
+    }
+
+    group.shutdownGracefully(0, 10, TimeUnit.SECONDS);
+  }
+
+  @Test
+  public void testToString() throws Exception {
+    address = TestUtils.testServerAddress(12345);
+    authority = GrpcUtil.authorityFromHostAndPort(address.getHostString(), address.getPort());
+    String s = newTransport(newNegotiator()).toString();
+    transports.clear();
+    assertTrue("Unexpected: " + s, s.contains("NettyClientTransport"));
+    assertTrue("Unexpected: " + s, s.contains(address.toString()));
+  }
+
+  @Test
+  public void addDefaultUserAgent() throws Exception {
+    startServer();
+    NettyClientTransport transport = newTransport(newNegotiator());
+    callMeMaybe(transport.start(clientTransportListener));
+
+    // Send a single RPC and wait for the response.
+    new Rpc(transport).halfClose().waitForResponse();
+
+    // Verify that the received headers contained the User-Agent.
+    assertEquals(1, serverListener.streamListeners.size());
+
+    Metadata headers = serverListener.streamListeners.get(0).headers;
+    assertEquals(GrpcUtil.getGrpcUserAgent("netty", null), headers.get(USER_AGENT_KEY));
+  }
+
+  @Test
+  public void setSoLingerChannelOption() throws IOException {
+    startServer();
+    Map<ChannelOption<?>, Object> channelOptions = new HashMap<ChannelOption<?>, Object>();
+    // set SO_LINGER option
+    int soLinger = 123;
+    channelOptions.put(ChannelOption.SO_LINGER, soLinger);
+    NettyClientTransport transport = new NettyClientTransport(
+        address, NioSocketChannel.class, channelOptions, group, newNegotiator(),
+        DEFAULT_WINDOW_SIZE, DEFAULT_MAX_MESSAGE_SIZE, GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE,
+        KEEPALIVE_TIME_NANOS_DISABLED, 1L, false, authority, null /* user agent */,
+        tooManyPingsRunnable, new TransportTracer(), Attributes.EMPTY);
+    transports.add(transport);
+    callMeMaybe(transport.start(clientTransportListener));
+
+    // verify SO_LINGER has been set
+    ChannelConfig config = transport.channel().config();
+    assertTrue(config instanceof SocketChannelConfig);
+    assertEquals(soLinger, ((SocketChannelConfig) config).getSoLinger());
+  }
+
+  @Test
+  public void overrideDefaultUserAgent() throws Exception {
+    startServer();
+    NettyClientTransport transport = newTransport(newNegotiator(),
+        DEFAULT_MAX_MESSAGE_SIZE, GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, "testUserAgent", true);
+    callMeMaybe(transport.start(clientTransportListener));
+
+    new Rpc(transport, new Metadata()).halfClose().waitForResponse();
+
+    // Verify that the received headers contained the User-Agent.
+    assertEquals(1, serverListener.streamListeners.size());
+    Metadata receivedHeaders = serverListener.streamListeners.get(0).headers;
+    assertEquals(GrpcUtil.getGrpcUserAgent("netty", "testUserAgent"),
+        receivedHeaders.get(USER_AGENT_KEY));
+  }
+
+  @Test
+  public void maxMessageSizeShouldBeEnforced() throws Throwable {
+    startServer();
+    // Allow the response payloads of up to 1 byte.
+    NettyClientTransport transport = newTransport(newNegotiator(),
+        1, GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, null, true);
+    callMeMaybe(transport.start(clientTransportListener));
+
+    try {
+      // Send a single RPC and wait for the response.
+      new Rpc(transport).halfClose().waitForResponse();
+      fail("Expected the stream to fail.");
+    } catch (ExecutionException e) {
+      Status status = Status.fromThrowable(e);
+      assertEquals(Code.RESOURCE_EXHAUSTED, status.getCode());
+      assertTrue("Missing exceeds maximum from: " + status.getDescription(),
+          status.getDescription().contains("exceeds maximum"));
+    }
+  }
+
+  /**
+   * Verifies that we can create multiple TLS client transports from the same builder.
+   */
+  @Test
+  public void creatingMultipleTlsTransportsShouldSucceed() throws Exception {
+    startServer();
+
+    // Create a couple client transports.
+    ProtocolNegotiator negotiator = newNegotiator();
+    for (int index = 0; index < 2; ++index) {
+      NettyClientTransport transport = newTransport(negotiator);
+      callMeMaybe(transport.start(clientTransportListener));
+    }
+
+    // Send a single RPC on each transport.
+    final List<Rpc> rpcs = new ArrayList<>(transports.size());
+    for (NettyClientTransport transport : transports) {
+      rpcs.add(new Rpc(transport).halfClose());
+    }
+
+    // Wait for the RPCs to complete.
+    for (Rpc rpc : rpcs) {
+      rpc.waitForResponse();
+    }
+  }
+
+  @Test
+  public void negotiationFailurePropagatesToStatus() throws Exception {
+    negotiator = ProtocolNegotiators.serverPlaintext();
+    startServer();
+
+    final NoopProtocolNegotiator negotiator = new NoopProtocolNegotiator();
+    final NettyClientTransport transport = newTransport(negotiator);
+    callMeMaybe(transport.start(clientTransportListener));
+    final Status failureStatus = Status.UNAVAILABLE.withDescription("oh noes!");
+    transport.channel().eventLoop().execute(new Runnable() {
+      @Override
+      public void run() {
+        negotiator.handler.fail(transport.channel().pipeline().context(negotiator.handler),
+            failureStatus.asRuntimeException());
+      }
+    });
+
+    Rpc rpc = new Rpc(transport).halfClose();
+    try {
+      rpc.waitForClose();
+      fail("expected exception");
+    } catch (ExecutionException ex) {
+      assertSame(failureStatus, ((StatusException) ex.getCause()).getStatus());
+    }
+  }
+
+  @Test
+  public void tlsNegotiationFailurePropagatesToStatus() throws Exception {
+    File serverCert = TestUtils.loadCert("server1.pem");
+    File serverKey = TestUtils.loadCert("server1.key");
+    // Don't trust ca.pem, so that client auth fails
+    SslContext sslContext = GrpcSslContexts.forServer(serverCert, serverKey)
+        .ciphers(TestUtils.preferredTestCiphers(), SupportedCipherSuiteFilter.INSTANCE)
+        .clientAuth(ClientAuth.REQUIRE)
+        .build();
+    negotiator = ProtocolNegotiators.serverTls(sslContext);
+    startServer();
+
+    File caCert = TestUtils.loadCert("ca.pem");
+    File clientCert = TestUtils.loadCert("client.pem");
+    File clientKey = TestUtils.loadCert("client.key");
+    SslContext clientContext = GrpcSslContexts.forClient()
+        .trustManager(caCert)
+        .ciphers(TestUtils.preferredTestCiphers(), SupportedCipherSuiteFilter.INSTANCE)
+        .keyManager(clientCert, clientKey)
+        .build();
+    ProtocolNegotiator negotiator = ProtocolNegotiators.tls(clientContext);
+    final NettyClientTransport transport = newTransport(negotiator);
+    callMeMaybe(transport.start(clientTransportListener));
+
+    Rpc rpc = new Rpc(transport).halfClose();
+    try {
+      rpc.waitForClose();
+      fail("expected exception");
+    } catch (ExecutionException ex) {
+      StatusException sre = (StatusException) ex.getCause();
+      assertEquals(Status.Code.UNAVAILABLE, sre.getStatus().getCode());
+      assertThat(sre.getCause()).isInstanceOf(SSLHandshakeException.class);
+      assertThat(sre.getCause().getMessage()).contains("SSLV3_ALERT_HANDSHAKE_FAILURE");
+    }
+  }
+
+  @Test
+  public void channelExceptionDuringNegotiatonPropagatesToStatus() throws Exception {
+    negotiator = ProtocolNegotiators.serverPlaintext();
+    startServer();
+
+    NoopProtocolNegotiator negotiator = new NoopProtocolNegotiator();
+    NettyClientTransport transport = newTransport(negotiator);
+    callMeMaybe(transport.start(clientTransportListener));
+    final Status failureStatus = Status.UNAVAILABLE.withDescription("oh noes!");
+    transport.channel().pipeline().fireExceptionCaught(failureStatus.asRuntimeException());
+
+    Rpc rpc = new Rpc(transport).halfClose();
+    try {
+      rpc.waitForClose();
+      fail("expected exception");
+    } catch (ExecutionException ex) {
+      assertSame(failureStatus, ((StatusException) ex.getCause()).getStatus());
+    }
+  }
+
+  @Test
+  public void handlerExceptionDuringNegotiatonPropagatesToStatus() throws Exception {
+    negotiator = ProtocolNegotiators.serverPlaintext();
+    startServer();
+
+    final NoopProtocolNegotiator negotiator = new NoopProtocolNegotiator();
+    final NettyClientTransport transport = newTransport(negotiator);
+    callMeMaybe(transport.start(clientTransportListener));
+    final Status failureStatus = Status.UNAVAILABLE.withDescription("oh noes!");
+    transport.channel().eventLoop().execute(new Runnable() {
+      @Override
+      public void run() {
+        try {
+          negotiator.handler.exceptionCaught(
+              transport.channel().pipeline().context(negotiator.handler),
+              failureStatus.asRuntimeException());
+        } catch (Exception ex) {
+          throw new RuntimeException(ex);
+        }
+      }
+    });
+
+    Rpc rpc = new Rpc(transport).halfClose();
+    try {
+      rpc.waitForClose();
+      fail("expected exception");
+    } catch (ExecutionException ex) {
+      assertSame(failureStatus, ((StatusException) ex.getCause()).getStatus());
+    }
+  }
+
+  @Test
+  public void bufferedStreamsShouldBeClosedWhenConnectionTerminates() throws Exception {
+    // Only allow a single stream active at a time.
+    startServer(1, GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE);
+
+    NettyClientTransport transport = newTransport(newNegotiator());
+    callMeMaybe(transport.start(clientTransportListener));
+
+    // Send a dummy RPC in order to ensure that the updated SETTINGS_MAX_CONCURRENT_STREAMS
+    // has been received by the remote endpoint.
+    new Rpc(transport).halfClose().waitForResponse();
+
+    // Create 3 streams, but don't half-close. The transport will buffer the second and third.
+    Rpc[] rpcs = new Rpc[] { new Rpc(transport), new Rpc(transport), new Rpc(transport) };
+
+    // Wait for the response for the stream that was actually created.
+    rpcs[0].waitForResponse();
+
+    // Now forcibly terminate the connection from the server side.
+    serverListener.transports.get(0).channel().pipeline().firstContext().close();
+
+    // Now wait for both listeners to be closed.
+    for (int i = 1; i < rpcs.length; i++) {
+      try {
+        rpcs[i].waitForClose();
+        fail("Expected the RPC to fail");
+      } catch (ExecutionException e) {
+        // Expected.
+        Throwable t = getRootCause(e);
+        // Make sure that the Http2ChannelClosedException got replaced with the real cause of
+        // the shutdown.
+        assertFalse(t instanceof StreamBufferingEncoder.Http2ChannelClosedException);
+      }
+    }
+  }
+
+  public static class CantConstructChannel extends NioSocketChannel {
+    /** Constructor. It doesn't work. Feel free to try. But it doesn't work. */
+    public CantConstructChannel() {
+      // Use an Error because we've seen cases of channels failing to construct due to classloading
+      // problems (like mixing different versions of Netty), and those involve Errors.
+      throw new CantConstructChannelError();
+    }
+  }
+
+  private static class CantConstructChannelError extends Error {}
+
+  @Test
+  public void failingToConstructChannelShouldFailGracefully() throws Exception {
+    address = TestUtils.testServerAddress(12345);
+    authority = GrpcUtil.authorityFromHostAndPort(address.getHostString(), address.getPort());
+    NettyClientTransport transport = new NettyClientTransport(
+        address, CantConstructChannel.class, new HashMap<ChannelOption<?>, Object>(), group,
+        newNegotiator(), DEFAULT_WINDOW_SIZE, DEFAULT_MAX_MESSAGE_SIZE,
+        GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, KEEPALIVE_TIME_NANOS_DISABLED, 1, false, authority,
+        null, tooManyPingsRunnable, new TransportTracer(), Attributes.EMPTY);
+    transports.add(transport);
+
+    // Should not throw
+    callMeMaybe(transport.start(clientTransportListener));
+
+    // And RPCs and PINGs should fail cleanly, reporting the failure
+    Rpc rpc = new Rpc(transport);
+    try {
+      rpc.waitForResponse();
+      fail("Expected exception");
+    } catch (Exception ex) {
+      if (!(getRootCause(ex) instanceof CantConstructChannelError)) {
+        throw new AssertionError("Could not find expected error", ex);
+      }
+    }
+
+    final SettableFuture<Object> pingResult = SettableFuture.create();
+    FakeClock clock = new FakeClock();
+    ClientTransport.PingCallback pingCallback = new ClientTransport.PingCallback() {
+      @Override
+      public void onSuccess(long roundTripTimeNanos) {
+        pingResult.set(roundTripTimeNanos);
+      }
+
+      @Override
+      public void onFailure(Throwable cause) {
+        pingResult.setException(cause);
+      }
+    };
+    transport.ping(pingCallback, clock.getScheduledExecutorService());
+    assertFalse(pingResult.isDone());
+    clock.runDueTasks();
+    assertTrue(pingResult.isDone());
+    try {
+      pingResult.get();
+      fail("Expected exception");
+    } catch (Exception ex) {
+      if (!(getRootCause(ex) instanceof CantConstructChannelError)) {
+        throw new AssertionError("Could not find expected error", ex);
+      }
+    }
+  }
+
+  @Test
+  public void maxHeaderListSizeShouldBeEnforcedOnClient() throws Exception {
+    startServer();
+
+    NettyClientTransport transport =
+        newTransport(newNegotiator(), DEFAULT_MAX_MESSAGE_SIZE, 1, null, true);
+    callMeMaybe(transport.start(clientTransportListener));
+
+    try {
+      // Send a single RPC and wait for the response.
+      new Rpc(transport, new Metadata()).halfClose().waitForResponse();
+      fail("The stream should have been failed due to client received header exceeds header list"
+          + " size limit!");
+    } catch (Exception e) {
+      Throwable rootCause = getRootCause(e);
+      Status status = ((StatusException) rootCause).getStatus();
+      assertEquals(Status.Code.INTERNAL, status.getCode());
+      assertEquals("HTTP/2 error code: PROTOCOL_ERROR\nReceived Rst Stream",
+          status.getDescription());
+    }
+  }
+
+  @Test
+  public void maxHeaderListSizeShouldBeEnforcedOnServer() throws Exception {
+    startServer(100, 1);
+
+    NettyClientTransport transport = newTransport(newNegotiator());
+    callMeMaybe(transport.start(clientTransportListener));
+
+    try {
+      // Send a single RPC and wait for the response.
+      new Rpc(transport, new Metadata()).halfClose().waitForResponse();
+      fail("The stream should have been failed due to server received header exceeds header list"
+          + " size limit!");
+    } catch (Exception e) {
+      Status status = Status.fromThrowable(e);
+      assertEquals(status.toString(), Status.Code.INTERNAL, status.getCode());
+    }
+  }
+
+  @Test
+  public void getAttributes_negotiatorHandler() throws Exception {
+    address = TestUtils.testServerAddress(12345);
+    authority = GrpcUtil.authorityFromHostAndPort(address.getHostString(), address.getPort());
+
+    NettyClientTransport transport = newTransport(new NoopProtocolNegotiator());
+    callMeMaybe(transport.start(clientTransportListener));
+
+    assertEquals(Attributes.EMPTY, transport.getAttributes());
+  }
+
+  @Test
+  public void getEagAttributes_negotiatorHandler() throws Exception {
+    address = TestUtils.testServerAddress(12345);
+    authority = GrpcUtil.authorityFromHostAndPort(address.getHostString(), address.getPort());
+
+    NoopProtocolNegotiator npn = new NoopProtocolNegotiator();
+    eagAttributes = Attributes.newBuilder()
+        .set(Attributes.Key.create("trash"), "value")
+        .build();
+    NettyClientTransport transport = newTransport(npn);
+    callMeMaybe(transport.start(clientTransportListener));
+
+    // EAG Attributes are available before the negotiation is complete
+    assertSame(eagAttributes, npn.grpcHandler.getEagAttributes());
+  }
+
+  @Test
+  public void clientStreamGetsAttributes() throws Exception {
+    startServer();
+    NettyClientTransport transport = newTransport(newNegotiator());
+    callMeMaybe(transport.start(clientTransportListener));
+    Rpc rpc = new Rpc(transport).halfClose();
+    rpc.waitForResponse();
+
+    assertNotNull(rpc.stream.getAttributes().get(Grpc.TRANSPORT_ATTR_SSL_SESSION));
+    assertEquals(address, rpc.stream.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
+  }
+
+  @Test
+  public void keepAliveEnabled() throws Exception {
+    startServer();
+    NettyClientTransport transport = newTransport(newNegotiator(), DEFAULT_MAX_MESSAGE_SIZE,
+        GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, null /* user agent */, true /* keep alive */);
+    callMeMaybe(transport.start(clientTransportListener));
+    Rpc rpc = new Rpc(transport).halfClose();
+    rpc.waitForResponse();
+
+    assertNotNull(transport.keepAliveManager());
+  }
+
+  @Test
+  public void keepAliveDisabled() throws Exception {
+    startServer();
+    NettyClientTransport transport = newTransport(newNegotiator(), DEFAULT_MAX_MESSAGE_SIZE,
+        GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, null /* user agent */, false /* keep alive */);
+    callMeMaybe(transport.start(clientTransportListener));
+    Rpc rpc = new Rpc(transport).halfClose();
+    rpc.waitForResponse();
+
+    assertNull(transport.keepAliveManager());
+  }
+
+  private Throwable getRootCause(Throwable t) {
+    if (t.getCause() == null) {
+      return t;
+    }
+    return getRootCause(t.getCause());
+  }
+
+  private ProtocolNegotiator newNegotiator() throws IOException {
+    File caCert = TestUtils.loadCert("ca.pem");
+    SslContext clientContext = GrpcSslContexts.forClient().trustManager(caCert)
+        .ciphers(TestUtils.preferredTestCiphers(), SupportedCipherSuiteFilter.INSTANCE).build();
+    return ProtocolNegotiators.tls(clientContext);
+  }
+
+  private NettyClientTransport newTransport(ProtocolNegotiator negotiator) {
+    return newTransport(negotiator, DEFAULT_MAX_MESSAGE_SIZE, GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE,
+        null /* user agent */, true /* keep alive */);
+  }
+
+  private NettyClientTransport newTransport(ProtocolNegotiator negotiator, int maxMsgSize,
+      int maxHeaderListSize, String userAgent, boolean enableKeepAlive) {
+    long keepAliveTimeNano = KEEPALIVE_TIME_NANOS_DISABLED;
+    long keepAliveTimeoutNano = TimeUnit.SECONDS.toNanos(1L);
+    if (enableKeepAlive) {
+      keepAliveTimeNano = TimeUnit.SECONDS.toNanos(10L);
+    }
+    NettyClientTransport transport = new NettyClientTransport(
+        address, NioSocketChannel.class, new HashMap<ChannelOption<?>, Object>(), group, negotiator,
+        DEFAULT_WINDOW_SIZE, maxMsgSize, maxHeaderListSize,
+        keepAliveTimeNano, keepAliveTimeoutNano,
+        false, authority, userAgent, tooManyPingsRunnable,
+        new TransportTracer(), eagAttributes);
+    transports.add(transport);
+    return transport;
+  }
+
+  private void startServer() throws IOException {
+    startServer(100, GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE);
+  }
+
+  private void startServer(int maxStreamsPerConnection, int maxHeaderListSize) throws IOException {
+    server = new NettyServer(
+        TestUtils.testServerAddress(0),
+        NioServerSocketChannel.class,
+        new HashMap<ChannelOption<?>, Object>(),
+        group, group, negotiator,
+        Collections.<ServerStreamTracer.Factory>emptyList(),
+        TransportTracer.getDefaultFactory(),
+        maxStreamsPerConnection,
+        DEFAULT_WINDOW_SIZE, DEFAULT_MAX_MESSAGE_SIZE, maxHeaderListSize,
+        DEFAULT_SERVER_KEEPALIVE_TIME_NANOS, DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS,
+        MAX_CONNECTION_IDLE_NANOS_DISABLED,
+        MAX_CONNECTION_AGE_NANOS_DISABLED, MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE, true, 0,
+        channelz);
+    server.start(serverListener);
+    address = TestUtils.testServerAddress(server.getPort());
+    authority = GrpcUtil.authorityFromHostAndPort(address.getHostString(), address.getPort());
+  }
+
+  private void callMeMaybe(Runnable r) {
+    if (r != null) {
+      r.run();
+    }
+  }
+
+  private static SslContext createSslContext() {
+    try {
+      File serverCert = TestUtils.loadCert("server1.pem");
+      File key = TestUtils.loadCert("server1.key");
+      return GrpcSslContexts.forServer(serverCert, key)
+          .ciphers(TestUtils.preferredTestCiphers(), SupportedCipherSuiteFilter.INSTANCE).build();
+    } catch (IOException ex) {
+      throw new RuntimeException(ex);
+    }
+  }
+
+  private static class Rpc {
+    static final String MESSAGE = "hello";
+    static final MethodDescriptor<String, String> METHOD =
+        MethodDescriptor.<String, String>newBuilder()
+            .setType(MethodDescriptor.MethodType.UNARY)
+            .setFullMethodName("testService/test")
+            .setRequestMarshaller(StringMarshaller.INSTANCE)
+            .setResponseMarshaller(StringMarshaller.INSTANCE)
+            .build();
+
+    final ClientStream stream;
+    final TestClientStreamListener listener = new TestClientStreamListener();
+
+    Rpc(NettyClientTransport transport) {
+      this(transport, new Metadata());
+    }
+
+    Rpc(NettyClientTransport transport, Metadata headers) {
+      stream = transport.newStream(METHOD, headers, CallOptions.DEFAULT);
+      stream.start(listener);
+      stream.request(1);
+      stream.writeMessage(new ByteArrayInputStream(MESSAGE.getBytes(UTF_8)));
+      stream.flush();
+    }
+
+    Rpc halfClose() {
+      stream.halfClose();
+      return this;
+    }
+
+    void waitForResponse() throws InterruptedException, ExecutionException, TimeoutException {
+      listener.responseFuture.get(10, TimeUnit.SECONDS);
+    }
+
+    void waitForClose() throws InterruptedException, ExecutionException, TimeoutException {
+      listener.closedFuture.get(10, TimeUnit.SECONDS);
+    }
+  }
+
+  private static final class TestClientStreamListener implements ClientStreamListener {
+    final SettableFuture<Void> closedFuture = SettableFuture.create();
+    final SettableFuture<Void> responseFuture = SettableFuture.create();
+
+    @Override
+    public void headersRead(Metadata headers) {
+    }
+
+    @Override
+    public void closed(Status status, Metadata trailers) {
+      closed(status, RpcProgress.PROCESSED, trailers);
+    }
+
+    @Override
+    public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) {
+      if (status.isOk()) {
+        closedFuture.set(null);
+      } else {
+        StatusException e = status.asException();
+        closedFuture.setException(e);
+        responseFuture.setException(e);
+      }
+    }
+
+    @Override
+    public void messagesAvailable(MessageProducer producer) {
+      if (producer.next() != null) {
+        responseFuture.set(null);
+      }
+    }
+
+    @Override
+    public void onReady() {
+    }
+  }
+
+  private static final class EchoServerStreamListener implements ServerStreamListener {
+    final ServerStream stream;
+    final String method;
+    final Metadata headers;
+
+    EchoServerStreamListener(ServerStream stream, String method, Metadata headers) {
+      this.stream = stream;
+      this.method = method;
+      this.headers = headers;
+    }
+
+    @Override
+    public void messagesAvailable(MessageProducer producer) {
+      InputStream message;
+      while ((message = producer.next()) != null) {
+        // Just echo back the message.
+        stream.writeMessage(message);
+        stream.flush();
+      }
+    }
+
+    @Override
+    public void onReady() {
+    }
+
+    @Override
+    public void halfClosed() {
+      // Just close when the client closes.
+      stream.close(Status.OK, new Metadata());
+    }
+
+    @Override
+    public void closed(Status status) {
+    }
+  }
+
+  private static final class EchoServerListener implements ServerListener {
+    final List<NettyServerTransport> transports = new ArrayList<>();
+    final List<EchoServerStreamListener> streamListeners =
+            Collections.synchronizedList(new ArrayList<EchoServerStreamListener>());
+
+    @Override
+    public ServerTransportListener transportCreated(final ServerTransport transport) {
+      transports.add((NettyServerTransport) transport);
+      return new ServerTransportListener() {
+        @Override
+        public void streamCreated(ServerStream stream, String method, Metadata headers) {
+          EchoServerStreamListener listener = new EchoServerStreamListener(stream, method, headers);
+          stream.setListener(listener);
+          stream.writeHeaders(new Metadata());
+          stream.request(1);
+          streamListeners.add(listener);
+        }
+
+        @Override
+        public Attributes transportReady(Attributes transportAttrs) {
+          return transportAttrs;
+        }
+
+        @Override
+        public void transportTerminated() {}
+      };
+    }
+
+    @Override
+    public void serverShutdown() {
+    }
+  }
+
+  private static final class StringMarshaller implements Marshaller<String> {
+    static final StringMarshaller INSTANCE = new StringMarshaller();
+
+    @Override
+    public InputStream stream(String value) {
+      return new ByteArrayInputStream(value.getBytes(UTF_8));
+    }
+
+    @Override
+    public String parse(InputStream stream) {
+      try {
+        return new String(ByteStreams.toByteArray(stream), UTF_8);
+      } catch (IOException ex) {
+        throw new RuntimeException(ex);
+      }
+    }
+  }
+
+  private static class NoopHandler extends ProtocolNegotiators.AbstractBufferingHandler
+      implements ProtocolNegotiator.Handler {
+    public NoopHandler(GrpcHttp2ConnectionHandler grpcHandler) {
+      super(grpcHandler);
+    }
+
+    @Override
+    public AsciiString scheme() {
+      return Utils.HTTP;
+    }
+  }
+
+  private static class NoopProtocolNegotiator implements ProtocolNegotiator {
+    GrpcHttp2ConnectionHandler grpcHandler;
+    NoopHandler handler;
+
+    @Override
+    public Handler newHandler(final GrpcHttp2ConnectionHandler grpcHandler) {
+      this.grpcHandler = grpcHandler;
+      return handler = new NoopHandler(grpcHandler);
+    }
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyHandlerTestBase.java b/netty/src/test/java/io/grpc/netty/NettyHandlerTestBase.java
new file mode 100644
index 0000000..260af1c
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyHandlerTestBase.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_WINDOW_SIZE;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import io.grpc.InternalChannelz.TransportStats;
+import io.grpc.internal.FakeClock;
+import io.grpc.internal.MessageFramer;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.TransportTracer;
+import io.grpc.internal.WritableBuffer;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.CompositeByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.EventLoop;
+import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
+import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
+import io.netty.handler.codec.http2.Http2CodecUtil;
+import io.netty.handler.codec.http2.Http2Connection;
+import io.netty.handler.codec.http2.Http2ConnectionHandler;
+import io.netty.handler.codec.http2.Http2Exception;
+import io.netty.handler.codec.http2.Http2FrameReader;
+import io.netty.handler.codec.http2.Http2FrameWriter;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.handler.codec.http2.Http2HeadersDecoder;
+import io.netty.handler.codec.http2.Http2LocalFlowController;
+import io.netty.handler.codec.http2.Http2Settings;
+import io.netty.handler.codec.http2.Http2Stream;
+import io.netty.util.concurrent.DefaultPromise;
+import io.netty.util.concurrent.Promise;
+import io.netty.util.concurrent.ScheduledFuture;
+import java.io.ByteArrayInputStream;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.mockito.verification.VerificationMode;
+
+/**
+ * Base class for Netty handler unit tests.
+ */
+@RunWith(JUnit4.class)
+public abstract class NettyHandlerTestBase<T extends Http2ConnectionHandler> {
+
+  private ByteBuf content;
+
+  private EmbeddedChannel channel;
+
+  private ChannelHandlerContext ctx;
+
+  private Http2FrameWriter frameWriter;
+
+  private Http2FrameReader frameReader;
+
+  private T handler;
+
+  private WriteQueue writeQueue;
+
+  /**
+   * Does additional setup jobs. Call it manually when necessary.
+   */
+  protected void manualSetUp() throws Exception {}
+
+  protected final TransportTracer transportTracer = new TransportTracer();
+  protected int flowControlWindow = DEFAULT_WINDOW_SIZE;
+
+  private final FakeClock fakeClock = new FakeClock();
+
+  FakeClock fakeClock() {
+    return fakeClock;
+  }
+
+  /**
+   * Must be called by subclasses to initialize the handler and channel.
+   */
+  protected final void initChannel(Http2HeadersDecoder headersDecoder) throws Exception {
+    content = Unpooled.copiedBuffer("hello world", UTF_8);
+    frameWriter = mock(Http2FrameWriter.class, delegatesTo(new DefaultHttp2FrameWriter()));
+    frameReader = new DefaultHttp2FrameReader(headersDecoder);
+
+    channel = new FakeClockSupportedChanel();
+    handler = newHandler();
+    channel.pipeline().addLast(handler);
+    ctx = channel.pipeline().context(handler);
+
+    writeQueue = initWriteQueue();
+  }
+
+  private final class FakeClockSupportedChanel extends EmbeddedChannel {
+    EventLoop eventLoop;
+
+    FakeClockSupportedChanel(ChannelHandler... handlers) {
+      super(handlers);
+    }
+
+    @Override
+    public EventLoop eventLoop() {
+      if (eventLoop == null) {
+        createEventLoop();
+      }
+      return eventLoop;
+    }
+
+    void createEventLoop() {
+      EventLoop realEventLoop = super.eventLoop();
+      if (realEventLoop == null) {
+        return;
+      }
+      eventLoop = mock(EventLoop.class, delegatesTo(realEventLoop));
+      doAnswer(
+          new Answer<ScheduledFuture<Void>>() {
+            @Override
+            public ScheduledFuture<Void> answer(InvocationOnMock invocation) throws Throwable {
+              Runnable command = (Runnable) invocation.getArguments()[0];
+              Long delay = (Long) invocation.getArguments()[1];
+              TimeUnit timeUnit = (TimeUnit) invocation.getArguments()[2];
+              return new FakeClockScheduledNettyFuture(eventLoop, command, delay, timeUnit);
+            }
+          }).when(eventLoop).schedule(any(Runnable.class), anyLong(), any(TimeUnit.class));
+    }
+  }
+
+  private final class FakeClockScheduledNettyFuture extends DefaultPromise<Void>
+      implements ScheduledFuture<Void> {
+    final java.util.concurrent.ScheduledFuture<?> future;
+
+    FakeClockScheduledNettyFuture(
+        EventLoop eventLoop, final Runnable command, long delay, TimeUnit timeUnit) {
+      super(eventLoop);
+      Runnable wrap = new Runnable() {
+        @Override
+        public void run() {
+          try {
+            command.run();
+          } catch (Throwable t) {
+            setFailure(t);
+            return;
+          }
+          if (!isDone()) {
+            Promise<Void> unused = setSuccess(null);
+          }
+          // else: The command itself, such as a shutdown task, might have cancelled all the
+          // scheduled tasks already.
+        }
+      };
+      future = fakeClock.getScheduledExecutorService().schedule(wrap, delay, timeUnit);
+    }
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning) {
+      if (future.cancel(mayInterruptIfRunning)) {
+        return super.cancel(mayInterruptIfRunning);
+      }
+      return false;
+    }
+
+    @Override
+    public long getDelay(TimeUnit unit) {
+      return Math.max(future.getDelay(unit), 1L); // never return zero or negative delay.
+    }
+
+    @Override
+    public int compareTo(Delayed o) {
+      return future.compareTo(o);
+    }
+  }
+
+  protected final T handler() {
+    return handler;
+  }
+
+  protected final EmbeddedChannel channel() {
+    return channel;
+  }
+
+  protected final ChannelHandlerContext ctx() {
+    return ctx;
+  }
+
+  protected final Http2FrameWriter frameWriter() {
+    return frameWriter;
+  }
+
+  protected final Http2FrameReader frameReader() {
+    return frameReader;
+  }
+
+  protected final ByteBuf content() {
+    return content;
+  }
+
+  protected final byte[] contentAsArray() {
+    return ByteBufUtil.getBytes(content());
+  }
+
+  protected final Http2FrameWriter verifyWrite() {
+    return verify(frameWriter);
+  }
+
+  protected final Http2FrameWriter verifyWrite(VerificationMode verificationMode) {
+    return verify(frameWriter, verificationMode);
+  }
+
+  protected final void channelRead(Object obj) throws Exception {
+    channel.writeInbound(obj);
+  }
+
+  protected ByteBuf grpcDataFrame(int streamId, boolean endStream, byte[] content) {
+    final ByteBuf compressionFrame = Unpooled.buffer(content.length);
+    MessageFramer framer = new MessageFramer(
+        new MessageFramer.Sink() {
+          @Override
+          public void deliverFrame(
+              WritableBuffer frame, boolean endOfStream, boolean flush, int numMessages) {
+            if (frame != null) {
+              ByteBuf bytebuf = ((NettyWritableBuffer) frame).bytebuf();
+              compressionFrame.writeBytes(bytebuf);
+            }
+          }
+        },
+        new NettyWritableBufferAllocator(ByteBufAllocator.DEFAULT),
+        StatsTraceContext.NOOP);
+    framer.writePayload(new ByteArrayInputStream(content));
+    framer.flush();
+    ChannelHandlerContext ctx = newMockContext();
+    new DefaultHttp2FrameWriter().writeData(ctx, streamId, compressionFrame, 0, endStream,
+        newPromise());
+    return captureWrite(ctx);
+  }
+
+  protected final ByteBuf dataFrame(int streamId, boolean endStream, ByteBuf content) {
+    // Need to retain the content since the frameWriter releases it.
+    content.retain();
+
+    ChannelHandlerContext ctx = newMockContext();
+    new DefaultHttp2FrameWriter().writeData(ctx, streamId, content, 0, endStream, newPromise());
+    return captureWrite(ctx);
+  }
+
+  protected final ByteBuf pingFrame(boolean ack, long payload) {
+    ChannelHandlerContext ctx = newMockContext();
+    new DefaultHttp2FrameWriter().writePing(ctx, ack, payload, newPromise());
+    return captureWrite(ctx);
+  }
+
+  protected final ByteBuf headersFrame(int streamId, Http2Headers headers) {
+    ChannelHandlerContext ctx = newMockContext();
+    new DefaultHttp2FrameWriter().writeHeaders(ctx, streamId, headers, 0, false, newPromise());
+    return captureWrite(ctx);
+  }
+
+  protected final ByteBuf goAwayFrame(int lastStreamId) {
+    return goAwayFrame(lastStreamId, 0, Unpooled.EMPTY_BUFFER);
+  }
+
+  protected final ByteBuf goAwayFrame(int lastStreamId, int errorCode, ByteBuf data) {
+    ChannelHandlerContext ctx = newMockContext();
+    new DefaultHttp2FrameWriter().writeGoAway(ctx, lastStreamId, errorCode, data, newPromise());
+    return captureWrite(ctx);
+  }
+
+  protected final ByteBuf rstStreamFrame(int streamId, int errorCode) {
+    ChannelHandlerContext ctx = newMockContext();
+    new DefaultHttp2FrameWriter().writeRstStream(ctx, streamId, errorCode, newPromise());
+    return captureWrite(ctx);
+  }
+
+  protected final ByteBuf serializeSettings(Http2Settings settings) {
+    ChannelHandlerContext ctx = newMockContext();
+    new DefaultHttp2FrameWriter().writeSettings(ctx, settings, newPromise());
+    return captureWrite(ctx);
+  }
+
+  protected final ByteBuf windowUpdate(int streamId, int delta) {
+    ChannelHandlerContext ctx = newMockContext();
+    new DefaultHttp2FrameWriter().writeWindowUpdate(ctx, 0, delta, newPromise());
+    return captureWrite(ctx);
+  }
+
+  protected final ChannelPromise newPromise() {
+    return channel.newPromise();
+  }
+
+  protected final Http2Connection connection() {
+    return handler().connection();
+  }
+
+  @CanIgnoreReturnValue
+  protected final ChannelFuture enqueue(WriteQueue.QueuedCommand command) {
+    ChannelFuture future = writeQueue.enqueue(command, true);
+    channel.runPendingTasks();
+    return future;
+  }
+
+  protected final ChannelHandlerContext newMockContext() {
+    ChannelHandlerContext ctx = mock(ChannelHandlerContext.class);
+    when(ctx.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT);
+    EventLoop eventLoop = mock(EventLoop.class);
+    when(ctx.executor()).thenReturn(eventLoop);
+    when(ctx.channel()).thenReturn(channel);
+    return ctx;
+  }
+
+  protected final ByteBuf captureWrite(ChannelHandlerContext ctx) {
+    ArgumentCaptor<ByteBuf> captor = ArgumentCaptor.forClass(ByteBuf.class);
+    verify(ctx, atLeastOnce()).write(captor.capture(), any(ChannelPromise.class));
+    CompositeByteBuf composite = Unpooled.compositeBuffer();
+    for (ByteBuf buf : captor.getAllValues()) {
+      composite.addComponent(buf);
+      composite.writerIndex(composite.writerIndex() + buf.readableBytes());
+    }
+    return composite;
+  }
+
+  protected abstract T newHandler() throws Http2Exception;
+
+  protected abstract WriteQueue initWriteQueue();
+
+  protected abstract void makeStream() throws Exception;
+
+  @Test
+  public void dataPingSentOnHeaderRecieved() throws Exception {
+    manualSetUp();
+    makeStream();
+    AbstractNettyHandler handler = (AbstractNettyHandler) handler();
+    handler.setAutoTuneFlowControl(true);
+
+    channelRead(dataFrame(3, false, content()));
+
+    assertEquals(1, handler.flowControlPing().getPingCount());
+  }
+
+  @Test
+  public void dataPingAckIsRecognized() throws Exception {
+    manualSetUp();
+    makeStream();
+    AbstractNettyHandler handler = (AbstractNettyHandler) handler();
+    handler.setAutoTuneFlowControl(true);
+
+    channelRead(dataFrame(3, false, content()));
+    long pingData = handler.flowControlPing().payload();
+    channelRead(pingFrame(true, pingData));
+
+    assertEquals(1, handler.flowControlPing().getPingCount());
+    assertEquals(1, handler.flowControlPing().getPingReturn());
+  }
+
+  @Test
+  public void dataSizeSincePingAccumulates() throws Exception {
+    manualSetUp();
+    makeStream();
+    AbstractNettyHandler handler = (AbstractNettyHandler) handler();
+    handler.setAutoTuneFlowControl(true);
+    long frameData = 123456;
+    ByteBuf buff = ctx().alloc().buffer(16);
+    buff.writeLong(frameData);
+    int length = buff.readableBytes();
+
+    channelRead(dataFrame(3, false, buff.copy()));
+    channelRead(dataFrame(3, false, buff.copy()));
+    channelRead(dataFrame(3, false, buff.copy()));
+
+    assertEquals(length * 3, handler.flowControlPing().getDataSincePing());
+  }
+
+  @Test
+  public void windowUpdateMatchesTarget() throws Exception {
+    manualSetUp();
+    Http2Stream connectionStream = connection().connectionStream();
+    Http2LocalFlowController localFlowController = connection().local().flowController();
+    makeStream();
+    AbstractNettyHandler handler = (AbstractNettyHandler) handler();
+    handler.setAutoTuneFlowControl(true);
+
+    ByteBuf data = ctx().alloc().buffer(1024);
+    while (data.isWritable()) {
+      data.writeLong(1111);
+    }
+    int length = data.readableBytes();
+    ByteBuf frame = dataFrame(3, false, data.copy());
+    channelRead(frame);
+    int accumulator = length;
+    // 40 is arbitrary, any number large enough to trigger a window update would work
+    for (int i = 0; i < 40; i++) {
+      channelRead(dataFrame(3, false, data.copy()));
+      accumulator += length;
+    }
+    long pingData = handler.flowControlPing().payload();
+    channelRead(pingFrame(true, pingData));
+
+    assertEquals(accumulator, handler.flowControlPing().getDataSincePing());
+    assertEquals(2 * accumulator, localFlowController.initialWindowSize(connectionStream));
+  }
+
+  @Test
+  public void windowShouldNotExceedMaxWindowSize() throws Exception {
+    manualSetUp();
+    makeStream();
+    AbstractNettyHandler handler = (AbstractNettyHandler) handler();
+    handler.setAutoTuneFlowControl(true);
+    Http2Stream connectionStream = connection().connectionStream();
+    Http2LocalFlowController localFlowController = connection().local().flowController();
+    int maxWindow = handler.flowControlPing().maxWindow();
+
+    handler.flowControlPing().setDataSizeSincePing(maxWindow);
+    long payload = handler.flowControlPing().payload();
+    channelRead(pingFrame(true, payload));
+
+    assertEquals(maxWindow, localFlowController.initialWindowSize(connectionStream));
+  }
+
+  @Test
+  public void transportTracer_windowSizeDefault() throws Exception {
+    manualSetUp();
+    TransportStats transportStats = transportTracer.getStats();
+    assertEquals(Http2CodecUtil.DEFAULT_WINDOW_SIZE, transportStats.remoteFlowControlWindow);
+    assertEquals(flowControlWindow, transportStats.localFlowControlWindow);
+  }
+
+  @Test
+  public void transportTracer_windowSize() throws Exception {
+    flowControlWindow = 1024 * 1024;
+    manualSetUp();
+    TransportStats transportStats = transportTracer.getStats();
+    assertEquals(Http2CodecUtil.DEFAULT_WINDOW_SIZE, transportStats.remoteFlowControlWindow);
+    assertEquals(flowControlWindow, transportStats.localFlowControlWindow);
+  }
+
+  @Test
+  public void transportTracer_windowUpdate_remote() throws Exception {
+    manualSetUp();
+    TransportStats before = transportTracer.getStats();
+    assertEquals(Http2CodecUtil.DEFAULT_WINDOW_SIZE, before.remoteFlowControlWindow);
+    assertEquals(Http2CodecUtil.DEFAULT_WINDOW_SIZE, before.localFlowControlWindow);
+
+    ByteBuf serializedSettings = windowUpdate(0, 1000);
+    channelRead(serializedSettings);
+    TransportStats after = transportTracer.getStats();
+    assertEquals(Http2CodecUtil.DEFAULT_WINDOW_SIZE + 1000,
+        after.remoteFlowControlWindow);
+    assertEquals(flowControlWindow, after.localFlowControlWindow);
+  }
+
+  @Test
+  public void transportTracer_windowUpdate_local() throws Exception {
+    manualSetUp();
+    TransportStats before = transportTracer.getStats();
+    assertEquals(Http2CodecUtil.DEFAULT_WINDOW_SIZE, before.remoteFlowControlWindow);
+    assertEquals(flowControlWindow, before.localFlowControlWindow);
+
+    // If the window size is below a certain threshold, netty will wait to apply the update.
+    // Use a large increment to be sure that it exceeds the threshold.
+    connection().local().flowController().incrementWindowSize(
+        connection().connectionStream(), 8 * Http2CodecUtil.DEFAULT_WINDOW_SIZE);
+
+    TransportStats after = transportTracer.getStats();
+    assertEquals(Http2CodecUtil.DEFAULT_WINDOW_SIZE, after.remoteFlowControlWindow);
+    assertEquals(flowControlWindow + 8 * Http2CodecUtil.DEFAULT_WINDOW_SIZE,
+        connection().local().flowController().windowSize(connection().connectionStream()));
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyReadableBufferTest.java b/netty/src/test/java/io/grpc/netty/NettyReadableBufferTest.java
new file mode 100644
index 0000000..8090e60
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyReadableBufferTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+
+import io.grpc.internal.ReadableBuffer;
+import io.grpc.internal.ReadableBufferTestBase;
+import io.netty.buffer.Unpooled;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link NettyReadableBuffer}.
+ */
+@RunWith(JUnit4.class)
+public class NettyReadableBufferTest extends ReadableBufferTestBase {
+  private NettyReadableBuffer buffer;
+
+  @Before
+  public void setup() {
+    buffer = new NettyReadableBuffer(Unpooled.copiedBuffer(msg, UTF_8));
+  }
+
+  @Test
+  public void closeShouldReleaseBuffer() {
+    buffer.close();
+    assertEquals(0, buffer.buffer().refCnt());
+  }
+
+  @Test
+  public void closeMultipleTimesShouldReleaseBufferOnce() {
+    buffer.close();
+    buffer.close();
+    assertEquals(0, buffer.buffer().refCnt());
+  }
+
+  @Override
+  protected ReadableBuffer buffer() {
+    return buffer;
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyServerBuilderTest.java b/netty/src/test/java/io/grpc/netty/NettyServerBuilderTest.java
new file mode 100644
index 0000000..f32868c
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyServerBuilderTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import io.netty.handler.ssl.SslContext;
+import java.util.concurrent.TimeUnit;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+
+/**
+ * Unit tests for {@link NettyServerBuilder}.
+ */
+@RunWith(JUnit4.class)
+public class NettyServerBuilderTest {
+
+  @Rule public final ExpectedException thrown = ExpectedException.none();
+
+  private NettyServerBuilder builder = NettyServerBuilder.forPort(8080);
+
+  @Test
+  public void sslContextCanBeNull() {
+    builder.sslContext(null);
+  }
+
+  @Test
+  public void failIfSslContextIsNotServer() {
+    SslContext sslContext = mock(SslContext.class);
+    when(sslContext.isClient()).thenReturn(true);
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Client SSL context can not be used for server");
+    builder.sslContext(sslContext);
+  }
+
+  @Test
+  public void failIfKeepAliveTimeNegative() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("keepalive time must be positive");
+
+    builder.keepAliveTime(-10L, TimeUnit.HOURS);
+  }
+
+  @Test
+  public void failIfKeepAliveTimeoutNegative() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("keepalive timeout must be positive");
+
+    builder.keepAliveTimeout(-10L, TimeUnit.HOURS);
+  }
+
+  @Test
+  public void failIfMaxConcurrentCallsPerConnectionNegative() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("max must be positive");
+
+    builder.maxConcurrentCallsPerConnection(0);
+  }
+
+  @Test
+  public void failIfMaxHeaderListSizeNegative() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("maxHeaderListSize must be > 0");
+
+    builder.maxHeaderListSize(0);
+  }
+
+  @Test
+  public void failIfMaxConnectionIdleNegative() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("max connection idle must be positive");
+
+    builder.maxConnectionIdle(-1, TimeUnit.HOURS);
+  }
+
+  @Test
+  public void failIfMaxConnectionAgeNegative() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("max connection age must be positive");
+
+    builder.maxConnectionAge(-1, TimeUnit.HOURS);
+  }
+
+  @Test
+  public void failIfMaxConnectionAgeGraceNegative() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("max connection age grace must be non-negative");
+
+    builder.maxConnectionAgeGrace(-1, TimeUnit.HOURS);
+  }
+
+  @Test
+  public void failIfPermitKeepAliveTimeNegative() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("permit keepalive time must be non-negative");
+
+    builder.permitKeepAliveTime(-1, TimeUnit.HOURS);
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java b/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java
new file mode 100644
index 0000000..1b1349e
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyServerHandlerTest.java
@@ -0,0 +1,1090 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
+import static io.grpc.internal.GrpcUtil.DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS;
+import static io.grpc.internal.GrpcUtil.DEFAULT_SERVER_KEEPALIVE_TIME_NANOS;
+import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE;
+import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_AGE_NANOS_DISABLED;
+import static io.grpc.netty.NettyServerBuilder.MAX_CONNECTION_IDLE_NANOS_DISABLED;
+import static io.grpc.netty.Utils.CONTENT_TYPE_GRPC;
+import static io.grpc.netty.Utils.CONTENT_TYPE_HEADER;
+import static io.grpc.netty.Utils.HTTP_METHOD;
+import static io.grpc.netty.Utils.TE_HEADER;
+import static io.grpc.netty.Utils.TE_TRAILERS;
+import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.io.ByteStreams;
+import com.google.common.truth.Truth;
+import io.grpc.Attributes;
+import io.grpc.InternalStatus;
+import io.grpc.Metadata;
+import io.grpc.ServerStreamTracer;
+import io.grpc.Status;
+import io.grpc.Status.Code;
+import io.grpc.StreamTracer;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.KeepAliveManager;
+import io.grpc.internal.ServerStream;
+import io.grpc.internal.ServerStreamListener;
+import io.grpc.internal.ServerTransportListener;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.StreamListener;
+import io.grpc.internal.testing.TestServerStreamTracer;
+import io.grpc.netty.GrpcHttp2HeadersUtils.GrpcHttp2ServerHeadersDecoder;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.handler.codec.http2.DefaultHttp2Headers;
+import io.netty.handler.codec.http2.Http2CodecUtil;
+import io.netty.handler.codec.http2.Http2Error;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.handler.codec.http2.Http2LocalFlowController;
+import io.netty.handler.codec.http2.Http2Settings;
+import io.netty.handler.codec.http2.Http2Stream;
+import io.netty.util.AsciiString;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Unit tests for {@link NettyServerHandler}.
+ */
+@RunWith(JUnit4.class)
+public class NettyServerHandlerTest extends NettyHandlerTestBase<NettyServerHandler> {
+
+  @Rule
+  public final TestRule globalTimeout = new DisableOnDebug(Timeout.seconds(10));
+
+  private static final int STREAM_ID = 3;
+
+  private static final AsciiString HTTP_FAKE_METHOD = AsciiString.of("FAKE");
+
+
+  @Mock
+  private ServerStreamListener streamListener;
+
+  @Mock
+  private ServerStreamTracer.Factory streamTracerFactory;
+
+  private final ServerTransportListener transportListener =
+      mock(ServerTransportListener.class, delegatesTo(new ServerTransportListenerImpl()));
+  private final TestServerStreamTracer streamTracer = new TestServerStreamTracer();
+
+  private NettyServerStream stream;
+  private KeepAliveManager spyKeepAliveManager;
+
+  final Queue<InputStream> streamListenerMessageQueue = new LinkedList<InputStream>();
+
+  private int maxConcurrentStreams = Integer.MAX_VALUE;
+  private int maxHeaderListSize = Integer.MAX_VALUE;
+  private boolean permitKeepAliveWithoutCalls = true;
+  private long permitKeepAliveTimeInNanos = 0;
+  private long maxConnectionIdleInNanos = MAX_CONNECTION_IDLE_NANOS_DISABLED;
+  private long maxConnectionAgeInNanos = MAX_CONNECTION_AGE_NANOS_DISABLED;
+  private long maxConnectionAgeGraceInNanos = MAX_CONNECTION_AGE_GRACE_NANOS_INFINITE;
+  private long keepAliveTimeInNanos = DEFAULT_SERVER_KEEPALIVE_TIME_NANOS;
+  private long keepAliveTimeoutInNanos = DEFAULT_SERVER_KEEPALIVE_TIMEOUT_NANOS;
+
+  private class ServerTransportListenerImpl implements ServerTransportListener {
+
+    @Override
+    public void streamCreated(ServerStream stream, String method, Metadata headers) {
+      stream.setListener(streamListener);
+    }
+
+    @Override
+    public Attributes transportReady(Attributes attributes) {
+      return Attributes.EMPTY;
+    }
+
+    @Override
+    public void transportTerminated() {
+    }
+  }
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+
+    when(streamTracerFactory.newServerStreamTracer(anyString(), any(Metadata.class)))
+        .thenReturn(streamTracer);
+
+    doAnswer(
+          new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+              StreamListener.MessageProducer producer =
+                  (StreamListener.MessageProducer) invocation.getArguments()[0];
+              InputStream message;
+              while ((message = producer.next()) != null) {
+                streamListenerMessageQueue.add(message);
+              }
+              return null;
+            }
+          })
+      .when(streamListener)
+      .messagesAvailable(any(StreamListener.MessageProducer.class));
+  }
+
+  @Override
+  protected void manualSetUp() throws Exception {
+    assertNull("manualSetUp should not run more than once", handler());
+
+    initChannel(new GrpcHttp2ServerHeadersDecoder(GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE));
+
+    // replace the keepAliveManager with spyKeepAliveManager
+    spyKeepAliveManager =
+        mock(KeepAliveManager.class, delegatesTo(handler().getKeepAliveManagerForTest()));
+    handler().setKeepAliveManagerForTest(spyKeepAliveManager);
+
+    // Simulate receipt of the connection preface
+    handler().handleProtocolNegotiationCompleted(Attributes.EMPTY, /*securityInfo=*/ null);
+    channelRead(Http2CodecUtil.connectionPrefaceBuf());
+    // Simulate receipt of initial remote settings.
+    ByteBuf serializedSettings = serializeSettings(new Http2Settings());
+    channelRead(serializedSettings);
+  }
+
+  @Test
+  public void transportReadyDelayedUntilConnectionPreface() throws Exception {
+    initChannel(new GrpcHttp2ServerHeadersDecoder(GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE));
+
+    handler().handleProtocolNegotiationCompleted(Attributes.EMPTY, /*securityInfo=*/ null);
+    verify(transportListener, never()).transportReady(any(Attributes.class));
+
+    // Simulate receipt of the connection preface
+    channelRead(Http2CodecUtil.connectionPrefaceBuf());
+    channelRead(serializeSettings(new Http2Settings()));
+    verify(transportListener).transportReady(any(Attributes.class));
+  }
+
+  @Test
+  public void sendFrameShouldSucceed() throws Exception {
+    manualSetUp();
+    createStream();
+
+    // Send a frame and verify that it was written.
+    ChannelFuture future = enqueue(
+        new SendGrpcFrameCommand(stream.transportState(), content(), false));
+    assertTrue(future.isSuccess());
+    verifyWrite().writeData(eq(ctx()), eq(STREAM_ID), eq(content()), eq(0), eq(false),
+        any(ChannelPromise.class));
+  }
+
+  @Test
+  public void streamTracerCreated() throws Exception {
+    manualSetUp();
+    createStream();
+
+    verify(streamTracerFactory).newServerStreamTracer(eq("foo/bar"), any(Metadata.class));
+    StatsTraceContext statsTraceCtx = stream.statsTraceContext();
+    List<StreamTracer> tracers = statsTraceCtx.getTracersForTest();
+    assertEquals(1, tracers.size());
+    assertSame(streamTracer, tracers.get(0));
+  }
+
+  @Test
+  public void inboundDataWithEndStreamShouldForwardToStreamListener() throws Exception {
+    manualSetUp();
+    inboundDataShouldForwardToStreamListener(true);
+  }
+
+  @Test
+  public void inboundDataShouldForwardToStreamListener() throws Exception {
+    manualSetUp();
+    inboundDataShouldForwardToStreamListener(false);
+  }
+
+  private void inboundDataShouldForwardToStreamListener(boolean endStream) throws Exception {
+    createStream();
+    stream.request(1);
+
+    // Create a data frame and then trigger the handler to read it.
+    ByteBuf frame = grpcDataFrame(STREAM_ID, endStream, contentAsArray());
+    channelRead(frame);
+    verify(streamListener, atLeastOnce())
+        .messagesAvailable(any(StreamListener.MessageProducer.class));
+    InputStream message = streamListenerMessageQueue.poll();
+    assertArrayEquals(ByteBufUtil.getBytes(content()), ByteStreams.toByteArray(message));
+    message.close();
+    assertNull("no additional message expected", streamListenerMessageQueue.poll());
+
+    if (endStream) {
+      verify(streamListener).halfClosed();
+    }
+    verify(streamListener, atLeastOnce()).onReady();
+    verifyNoMoreInteractions(streamListener);
+  }
+
+  @Test
+  public void clientHalfCloseShouldForwardToStreamListener() throws Exception {
+    manualSetUp();
+    createStream();
+    stream.request(1);
+
+    channelRead(emptyGrpcFrame(STREAM_ID, true));
+
+    verify(streamListener, atLeastOnce())
+        .messagesAvailable(any(StreamListener.MessageProducer.class));
+    InputStream message = streamListenerMessageQueue.poll();
+    assertArrayEquals(new byte[0], ByteStreams.toByteArray(message));
+    assertNull("no additional message expected", streamListenerMessageQueue.poll());
+    verify(streamListener).halfClosed();
+    verify(streamListener, atLeastOnce()).onReady();
+    verifyNoMoreInteractions(streamListener);
+  }
+
+  @Test
+  public void clientCancelShouldForwardToStreamListener() throws Exception {
+    manualSetUp();
+    createStream();
+
+    channelRead(rstStreamFrame(STREAM_ID, (int) Http2Error.CANCEL.code()));
+
+    ArgumentCaptor<Status> statusCap = ArgumentCaptor.forClass(Status.class);
+    verify(streamListener).closed(statusCap.capture());
+    assertEquals(Code.CANCELLED, statusCap.getValue().getCode());
+    Truth.assertThat(statusCap.getValue().getDescription()).contains("RST_STREAM");
+    verify(streamListener, atLeastOnce()).onReady();
+    assertNull("no messages expected", streamListenerMessageQueue.poll());
+  }
+
+  @Test
+  public void streamErrorShouldNotCloseChannel() throws Exception {
+    manualSetUp();
+    createStream();
+    stream.request(1);
+
+    // When a DATA frame is read, throw an exception. It will be converted into an
+    // Http2StreamException.
+    RuntimeException e = new RuntimeException("Fake Exception");
+    doThrow(e).when(streamListener).messagesAvailable(any(StreamListener.MessageProducer.class));
+
+    // Read a DATA frame to trigger the exception.
+    channelRead(emptyGrpcFrame(STREAM_ID, true));
+
+    // Verify that the channel was NOT closed.
+    assertTrue(channel().isOpen());
+
+    // Verify the stream was closed.
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
+    verify(streamListener).closed(captor.capture());
+    assertEquals(e, captor.getValue().asException().getCause());
+    assertEquals(Code.UNKNOWN, captor.getValue().getCode());
+  }
+
+  @Test
+  public void closeShouldCloseChannel() throws Exception {
+    manualSetUp();
+    handler().close(ctx(), newPromise());
+
+    verifyWrite().writeGoAway(eq(ctx()), eq(0), eq(Http2Error.NO_ERROR.code()),
+        eq(Unpooled.EMPTY_BUFFER), any(ChannelPromise.class));
+
+    // Verify that the channel was closed.
+    assertFalse(channel().isOpen());
+  }
+
+  @Test
+  public void exceptionCaughtShouldCloseConnection() throws Exception {
+    manualSetUp();
+    handler().exceptionCaught(ctx(), new RuntimeException("fake exception"));
+
+    // TODO(nmittler): EmbeddedChannel does not currently invoke the channelInactive processing,
+    // so exceptionCaught() will not close streams properly in this test.
+    // Once https://github.com/netty/netty/issues/4316 is resolved, we should also verify that
+    // any open streams are closed properly.
+    assertFalse(channel().isOpen());
+  }
+
+  @Test
+  public void channelInactiveShouldCloseStreams() throws Exception {
+    manualSetUp();
+    createStream();
+    handler().channelInactive(ctx());
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
+    verify(streamListener).closed(captor.capture());
+    assertFalse(captor.getValue().isOk());
+  }
+
+  @Test
+  public void shouldAdvertiseMaxConcurrentStreams() throws Exception {
+    maxConcurrentStreams = 314;
+    manualSetUp();
+
+    ArgumentCaptor<Http2Settings> captor = ArgumentCaptor.forClass(Http2Settings.class);
+    verifyWrite().writeSettings(
+        any(ChannelHandlerContext.class), captor.capture(), any(ChannelPromise.class));
+
+    assertEquals(maxConcurrentStreams, captor.getValue().maxConcurrentStreams().intValue());
+  }
+
+  @Test
+  public void shouldAdvertiseMaxHeaderListSize() throws Exception {
+    maxHeaderListSize = 123;
+    manualSetUp();
+
+    ArgumentCaptor<Http2Settings> captor = ArgumentCaptor.forClass(Http2Settings.class);
+    verifyWrite().writeSettings(
+        any(ChannelHandlerContext.class), captor.capture(), any(ChannelPromise.class));
+
+    assertEquals(maxHeaderListSize, captor.getValue().maxHeaderListSize().intValue());
+  }
+
+  @Test
+  public void connectionWindowShouldBeOverridden() throws Exception {
+    flowControlWindow = 1048576; // 1MiB
+    manualSetUp();
+
+    Http2Stream connectionStream = connection().connectionStream();
+    Http2LocalFlowController localFlowController = connection().local().flowController();
+    int actualInitialWindowSize = localFlowController.initialWindowSize(connectionStream);
+    int actualWindowSize = localFlowController.windowSize(connectionStream);
+    assertEquals(flowControlWindow, actualWindowSize);
+    assertEquals(flowControlWindow, actualInitialWindowSize);
+  }
+
+  @Test
+  public void cancelShouldSendRstStream() throws Exception {
+    manualSetUp();
+    createStream();
+    enqueue(new CancelServerStreamCommand(stream.transportState(), Status.DEADLINE_EXCEEDED));
+    verifyWrite().writeRstStream(eq(ctx()), eq(stream.transportState().id()),
+        eq(Http2Error.CANCEL.code()), any(ChannelPromise.class));
+  }
+
+  @Test
+  public void headersWithInvalidContentTypeShouldFail() throws Exception {
+    manualSetUp();
+    Http2Headers headers = new DefaultHttp2Headers()
+        .method(HTTP_METHOD)
+        .set(CONTENT_TYPE_HEADER, new AsciiString("application/bad", UTF_8))
+        .set(TE_HEADER, TE_TRAILERS)
+        .path(new AsciiString("/foo/bar"));
+    ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
+    channelRead(headersFrame);
+    Http2Headers responseHeaders = new DefaultHttp2Headers()
+        .set(InternalStatus.CODE_KEY.name(), String.valueOf(Code.INTERNAL.value()))
+        .set(InternalStatus.MESSAGE_KEY.name(), "Content-Type 'application/bad' is not supported")
+        .status("" + 415)
+        .set(CONTENT_TYPE_HEADER, "text/plain; encoding=utf-8");
+
+    verifyWrite().writeHeaders(eq(ctx()), eq(STREAM_ID), eq(responseHeaders), eq(0),
+        eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), any(ChannelPromise.class));
+  }
+
+  @Test
+  public void headersWithInvalidMethodShouldFail() throws Exception {
+    manualSetUp();
+    Http2Headers headers = new DefaultHttp2Headers()
+        .method(HTTP_FAKE_METHOD)
+        .set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
+        .path(new AsciiString("/foo/bar"));
+    ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
+    channelRead(headersFrame);
+    Http2Headers responseHeaders = new DefaultHttp2Headers()
+        .set(InternalStatus.CODE_KEY.name(), String.valueOf(Code.INTERNAL.value()))
+        .set(InternalStatus.MESSAGE_KEY.name(), "Method 'FAKE' is not supported")
+        .status("" + 405)
+        .set(CONTENT_TYPE_HEADER, "text/plain; encoding=utf-8");
+
+    verifyWrite().writeHeaders(eq(ctx()), eq(STREAM_ID), eq(responseHeaders), eq(0),
+        eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), any(ChannelPromise.class));
+  }
+
+  @Test
+  public void headersWithMissingPathShouldFail() throws Exception {
+    manualSetUp();
+    Http2Headers headers = new DefaultHttp2Headers()
+        .method(HTTP_METHOD)
+        .set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC);
+    ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
+    channelRead(headersFrame);
+    Http2Headers responseHeaders = new DefaultHttp2Headers()
+        .set(InternalStatus.CODE_KEY.name(), String.valueOf(Code.UNIMPLEMENTED.value()))
+        .set(InternalStatus.MESSAGE_KEY.name(), "Expected path but is missing")
+        .status("" + 404)
+        .set(CONTENT_TYPE_HEADER, "text/plain; encoding=utf-8");
+
+    verifyWrite().writeHeaders(eq(ctx()), eq(STREAM_ID), eq(responseHeaders), eq(0),
+        eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), any(ChannelPromise.class));
+  }
+
+  @Test
+  public void headersWithInvalidPathShouldFail() throws Exception {
+    manualSetUp();
+    Http2Headers headers = new DefaultHttp2Headers()
+        .method(HTTP_METHOD)
+        .set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
+        .path(new AsciiString("foo/bar"));
+    ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
+    channelRead(headersFrame);
+    Http2Headers responseHeaders = new DefaultHttp2Headers()
+        .set(InternalStatus.CODE_KEY.name(), String.valueOf(Code.UNIMPLEMENTED.value()))
+        .set(InternalStatus.MESSAGE_KEY.name(), "Expected path to start with /: foo/bar")
+        .status("" + 404)
+        .set(CONTENT_TYPE_HEADER, "text/plain; encoding=utf-8");
+
+    verifyWrite().writeHeaders(eq(ctx()), eq(STREAM_ID), eq(responseHeaders), eq(0),
+        eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false), any(ChannelPromise.class));
+  }
+
+  @Test
+  public void headersSupportExtensionContentType() throws Exception {
+    manualSetUp();
+    Http2Headers headers = new DefaultHttp2Headers()
+        .method(HTTP_METHOD)
+        .set(CONTENT_TYPE_HEADER, new AsciiString("application/grpc+json", UTF_8))
+        .set(TE_HEADER, TE_TRAILERS)
+        .path(new AsciiString("/foo/bar"));
+    ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
+    channelRead(headersFrame);
+
+    ArgumentCaptor<NettyServerStream> streamCaptor =
+        ArgumentCaptor.forClass(NettyServerStream.class);
+    ArgumentCaptor<String> methodCaptor = ArgumentCaptor.forClass(String.class);
+    verify(transportListener).streamCreated(streamCaptor.capture(), methodCaptor.capture(),
+        any(Metadata.class));
+    stream = streamCaptor.getValue();
+  }
+
+  @Test
+  public void keepAliveManagerOnDataReceived_headersRead() throws Exception {
+    manualSetUp();
+    ByteBuf headersFrame = headersFrame(STREAM_ID, new DefaultHttp2Headers());
+    channelRead(headersFrame);
+
+    verify(spyKeepAliveManager).onDataReceived();
+    verify(spyKeepAliveManager, never()).onTransportTermination();
+  }
+
+  @Test
+  public void keepAliveManagerOnDataReceived_dataRead() throws Exception {
+    manualSetUp();
+    createStream();
+    verify(spyKeepAliveManager).onDataReceived(); // received headers
+
+    channelRead(grpcDataFrame(STREAM_ID, false, contentAsArray()));
+
+    verify(spyKeepAliveManager, times(2)).onDataReceived();
+
+    channelRead(grpcDataFrame(STREAM_ID, false, contentAsArray()));
+
+    verify(spyKeepAliveManager, times(3)).onDataReceived();
+    verify(spyKeepAliveManager, never()).onTransportTermination();
+  }
+
+  @Test
+  public void keepAliveManagerOnDataReceived_rstStreamRead() throws Exception {
+    manualSetUp();
+    createStream();
+    verify(spyKeepAliveManager).onDataReceived(); // received headers
+
+    channelRead(rstStreamFrame(STREAM_ID, (int) Http2Error.CANCEL.code()));
+
+    verify(spyKeepAliveManager, times(2)).onDataReceived();
+    verify(spyKeepAliveManager, never()).onTransportTermination();
+  }
+
+  @Test
+  public void keepAliveManagerOnDataReceived_pingRead() throws Exception {
+    manualSetUp();
+    channelRead(pingFrame(false /* isAck */, 1234L));
+
+    verify(spyKeepAliveManager).onDataReceived();
+    verify(spyKeepAliveManager, never()).onTransportTermination();
+  }
+
+  @Test
+  public void keepAliveManagerOnDataReceived_pingActRead() throws Exception {
+    manualSetUp();
+    channelRead(pingFrame(true /* isAck */, 1234L));
+
+    verify(spyKeepAliveManager).onDataReceived();
+    verify(spyKeepAliveManager, never()).onTransportTermination();
+  }
+
+  @Test
+  public void keepAliveManagerOnTransportTermination() throws Exception {
+    manualSetUp();
+    handler().channelInactive(handler().ctx());
+
+    verify(spyKeepAliveManager).onTransportTermination();
+  }
+
+  @Test
+  public void keepAliveManager_pingSent() throws Exception {
+    keepAliveTimeInNanos = TimeUnit.MILLISECONDS.toNanos(10L);
+    keepAliveTimeoutInNanos = TimeUnit.MINUTES.toNanos(30L);
+    manualSetUp();
+
+    assertEquals(0, transportTracer.getStats().keepAlivesSent);
+    fakeClock().forwardNanos(keepAliveTimeInNanos);
+    assertEquals(1, transportTracer.getStats().keepAlivesSent);
+
+    verifyWrite().writePing(eq(ctx()), eq(false), eq(0xDEADL), any(ChannelPromise.class));
+
+    spyKeepAliveManager.onDataReceived();
+    fakeClock().forwardTime(10L, TimeUnit.MILLISECONDS);
+
+    verifyWrite(times(2))
+        .writePing(eq(ctx()), eq(false), eq(0xDEADL), any(ChannelPromise.class));
+    assertTrue(channel().isOpen());
+  }
+
+  @Test
+  public void keepAliveManager_pingTimeout() throws Exception {
+    keepAliveTimeInNanos = 123L /* nanoseconds */;
+    keepAliveTimeoutInNanos = 456L /* nanoseconds */;
+    manualSetUp();
+
+    fakeClock().forwardNanos(keepAliveTimeInNanos);
+
+    assertTrue(channel().isOpen());
+
+    fakeClock().forwardNanos(keepAliveTimeoutInNanos);
+
+    assertTrue(!channel().isOpen());
+  }
+
+  @Test
+  public void keepAliveEnforcer_enforcesPings() throws Exception {
+    permitKeepAliveWithoutCalls = false;
+    permitKeepAliveTimeInNanos = TimeUnit.HOURS.toNanos(1);
+    manualSetUp();
+
+    for (int i = 0; i < KeepAliveEnforcer.MAX_PING_STRIKES + 1; i++) {
+      channelRead(pingFrame(false /* isAck */, 1L));
+    }
+    verifyWrite().writeGoAway(eq(ctx()), eq(0), eq(Http2Error.ENHANCE_YOUR_CALM.code()),
+        any(ByteBuf.class), any(ChannelPromise.class));
+    assertFalse(channel().isActive());
+  }
+
+  @Test
+  public void keepAliveEnforcer_sendingDataResetsCounters() throws Exception {
+    permitKeepAliveWithoutCalls = false;
+    permitKeepAliveTimeInNanos = TimeUnit.HOURS.toNanos(1);
+    manualSetUp();
+
+    createStream();
+    Http2Headers headers = Utils.convertServerHeaders(new Metadata());
+    ChannelFuture future = enqueue(
+        SendResponseHeadersCommand.createHeaders(stream.transportState(), headers));
+    future.get();
+    for (int i = 0; i < 10; i++) {
+      future = enqueue(
+          new SendGrpcFrameCommand(stream.transportState(), content().retainedSlice(), false));
+      future.get();
+      channel().releaseOutbound();
+      channelRead(pingFrame(false /* isAck */, 1L));
+    }
+    verifyWrite(never()).writeGoAway(eq(ctx()), eq(STREAM_ID),
+        eq(Http2Error.ENHANCE_YOUR_CALM.code()), any(ByteBuf.class), any(ChannelPromise.class));
+  }
+
+  @Test
+  public void keepAliveEnforcer_initialIdle() throws Exception {
+    permitKeepAliveWithoutCalls = false;
+    permitKeepAliveTimeInNanos = 0;
+    manualSetUp();
+
+    for (int i = 0; i < KeepAliveEnforcer.MAX_PING_STRIKES + 1; i++) {
+      channelRead(pingFrame(false /* isAck */, 1L));
+    }
+    verifyWrite().writeGoAway(eq(ctx()), eq(0),
+        eq(Http2Error.ENHANCE_YOUR_CALM.code()), any(ByteBuf.class), any(ChannelPromise.class));
+    assertFalse(channel().isActive());
+  }
+
+  @Test
+  public void keepAliveEnforcer_noticesActive() throws Exception {
+    permitKeepAliveWithoutCalls = false;
+    permitKeepAliveTimeInNanos = 0;
+    manualSetUp();
+
+    createStream();
+    for (int i = 0; i < 10; i++) {
+      channelRead(pingFrame(false /* isAck */, 1L));
+    }
+    verifyWrite(never()).writeGoAway(eq(ctx()), eq(STREAM_ID),
+        eq(Http2Error.ENHANCE_YOUR_CALM.code()), any(ByteBuf.class), any(ChannelPromise.class));
+  }
+
+  @Test
+  public void keepAliveEnforcer_noticesInactive() throws Exception {
+    permitKeepAliveWithoutCalls = false;
+    permitKeepAliveTimeInNanos = 0;
+    manualSetUp();
+
+    createStream();
+    channelRead(rstStreamFrame(STREAM_ID, (int) Http2Error.CANCEL.code()));
+    for (int i = 0; i < KeepAliveEnforcer.MAX_PING_STRIKES + 1; i++) {
+      channelRead(pingFrame(false /* isAck */, 1L));
+    }
+    verifyWrite().writeGoAway(eq(ctx()), eq(STREAM_ID),
+        eq(Http2Error.ENHANCE_YOUR_CALM.code()), any(ByteBuf.class), any(ChannelPromise.class));
+    assertFalse(channel().isActive());
+  }
+
+  @Test
+  public void noGoAwaySentBeforeMaxConnectionIdleReached() throws Exception {
+    maxConnectionIdleInNanos = TimeUnit.MINUTES.toNanos(30L);
+    manualSetUp();
+
+    fakeClock().forwardTime(20, TimeUnit.MINUTES);
+
+    // GO_AWAY not sent yet
+    verifyWrite(never()).writeGoAway(
+        any(ChannelHandlerContext.class), any(Integer.class), any(Long.class), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    assertTrue(channel().isOpen());
+  }
+
+  @Test
+  public void maxConnectionIdle_goAwaySent_pingAck() throws Exception {
+    maxConnectionIdleInNanos = TimeUnit.MILLISECONDS.toNanos(10L);
+    manualSetUp();
+    assertTrue(channel().isOpen());
+
+    fakeClock().forwardNanos(maxConnectionIdleInNanos);
+
+    // first GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(Integer.MAX_VALUE), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // ping sent
+    verifyWrite().writePing(
+        eq(ctx()), eq(false), eq(0x97ACEF001L), any(ChannelPromise.class));
+    verifyWrite(never()).writeGoAway(
+        eq(ctx()), eq(0), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+
+    channelRead(pingFrame(true /* isAck */, 0xDEADL)); // irrelevant ping Ack
+    verifyWrite(never()).writeGoAway(
+        eq(ctx()), eq(0), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    assertTrue(channel().isOpen());
+
+    channelRead(pingFrame(true /* isAck */, 0x97ACEF001L));
+
+    // second GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(0), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // channel closed
+    assertTrue(!channel().isOpen());
+  }
+
+  @Test
+  public void maxConnectionIdle_goAwaySent_pingTimeout() throws Exception {
+    maxConnectionIdleInNanos = TimeUnit.MILLISECONDS.toNanos(10L);
+    manualSetUp();
+    assertTrue(channel().isOpen());
+
+    fakeClock().forwardNanos(maxConnectionIdleInNanos);
+
+    // first GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(Integer.MAX_VALUE), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // ping sent
+    verifyWrite().writePing(
+        eq(ctx()), eq(false), eq(0x97ACEF001L), any(ChannelPromise.class));
+    verifyWrite(never()).writeGoAway(
+        eq(ctx()), eq(0), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    assertTrue(channel().isOpen());
+
+    fakeClock().forwardTime(10, TimeUnit.SECONDS);
+
+    // second GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(0), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // channel closed
+    assertTrue(!channel().isOpen());
+  }
+
+  @Test
+  public void maxConnectionIdle_activeThenRst_pingAck() throws Exception {
+    maxConnectionIdleInNanos = TimeUnit.MILLISECONDS.toNanos(10L);
+    manualSetUp();
+    createStream();
+
+    fakeClock().forwardNanos(maxConnectionIdleInNanos);
+
+    // GO_AWAY not sent when active
+    verifyWrite(never()).writeGoAway(
+        any(ChannelHandlerContext.class), any(Integer.class), any(Long.class), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    assertTrue(channel().isOpen());
+
+    channelRead(rstStreamFrame(STREAM_ID, (int) Http2Error.CANCEL.code()));
+
+    fakeClock().forwardNanos(maxConnectionIdleInNanos);
+
+    // first GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(Integer.MAX_VALUE), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // ping sent
+    verifyWrite().writePing(
+        eq(ctx()), eq(false), eq(0x97ACEF001L), any(ChannelPromise.class));
+    verifyWrite(never()).writeGoAway(
+        eq(ctx()), eq(STREAM_ID), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    assertTrue(channel().isOpen());
+
+    fakeClock().forwardTime(10, TimeUnit.SECONDS);
+
+    // second GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(STREAM_ID), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // channel closed
+    assertTrue(!channel().isOpen());
+  }
+
+  @Test
+  public void maxConnectionIdle_activeThenRst_pingTimeoutk() throws Exception {
+    maxConnectionIdleInNanos = TimeUnit.MILLISECONDS.toNanos(10L);
+    manualSetUp();
+    createStream();
+
+    fakeClock().forwardNanos(maxConnectionIdleInNanos);
+
+    // GO_AWAY not sent when active
+    verifyWrite(never()).writeGoAway(
+        any(ChannelHandlerContext.class), any(Integer.class), any(Long.class), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    assertTrue(channel().isOpen());
+
+    channelRead(rstStreamFrame(STREAM_ID, (int) Http2Error.CANCEL.code()));
+
+    fakeClock().forwardNanos(maxConnectionIdleInNanos);
+
+    // first GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(Integer.MAX_VALUE), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // ping sent
+    verifyWrite().writePing(
+        eq(ctx()), eq(false), eq(0x97ACEF001L), any(ChannelPromise.class));
+    verifyWrite(never()).writeGoAway(
+        eq(ctx()), eq(STREAM_ID), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    assertTrue(channel().isOpen());
+
+    channelRead(pingFrame(true /* isAck */, 0x97ACEF001L));
+
+    // second GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(STREAM_ID), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // channel closed
+    assertTrue(!channel().isOpen());
+  }
+
+  @Test
+  public void noGoAwaySentBeforeMaxConnectionAgeReached() throws Exception {
+    maxConnectionAgeInNanos = TimeUnit.MINUTES.toNanos(30L);
+    manualSetUp();
+
+    fakeClock().forwardTime(20, TimeUnit.MINUTES);
+
+    // GO_AWAY not sent yet
+    verifyWrite(never()).writeGoAway(
+        any(ChannelHandlerContext.class), any(Integer.class), any(Long.class), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    assertTrue(channel().isOpen());
+  }
+
+  @Test
+  public void maxConnectionAge_goAwaySent_pingAck() throws Exception {
+
+    maxConnectionAgeInNanos = TimeUnit.MILLISECONDS.toNanos(10L);
+    manualSetUp();
+    assertTrue(channel().isOpen());
+
+    fakeClock().forwardNanos(maxConnectionAgeInNanos);
+
+    // first GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(Integer.MAX_VALUE), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // ping sent
+    verifyWrite().writePing(
+        eq(ctx()), eq(false), eq(0x97ACEF001L), any(ChannelPromise.class));
+    verifyWrite(never()).writeGoAway(
+        eq(ctx()), eq(0), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+
+    channelRead(pingFrame(true /* isAck */, 0xDEADL)); // irrelevant ping Ack
+    verifyWrite(never()).writeGoAway(
+        eq(ctx()), eq(0), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    assertTrue(channel().isOpen());
+
+    channelRead(pingFrame(true /* isAck */, 0x97ACEF001L));
+
+    // second GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(0), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // channel closed
+    assertTrue(!channel().isOpen());
+  }
+
+  @Test
+  public void maxConnectionAge_goAwaySent_pingTimeout() throws Exception {
+
+    maxConnectionAgeInNanos = TimeUnit.MILLISECONDS.toNanos(10L);
+    manualSetUp();
+    assertTrue(channel().isOpen());
+
+    fakeClock().forwardNanos(maxConnectionAgeInNanos);
+
+    // first GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(Integer.MAX_VALUE), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // ping sent
+    verifyWrite().writePing(
+        eq(ctx()), eq(false), eq(0x97ACEF001L), any(ChannelPromise.class));
+    verifyWrite(never()).writeGoAway(
+        eq(ctx()), eq(0), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    assertTrue(channel().isOpen());
+
+    fakeClock().forwardTime(10, TimeUnit.SECONDS);
+
+    // second GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(0), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // channel closed
+    assertTrue(!channel().isOpen());
+  }
+
+  @Test
+  public void maxConnectionAgeGrace_channelStillOpenDuringGracePeriod() throws Exception {
+    maxConnectionAgeInNanos = TimeUnit.MILLISECONDS.toNanos(10L);
+    maxConnectionAgeGraceInNanos = TimeUnit.MINUTES.toNanos(30L);
+    manualSetUp();
+    createStream();
+
+    fakeClock().forwardNanos(maxConnectionAgeInNanos);
+
+    // first GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(Integer.MAX_VALUE), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // ping sent
+    verifyWrite().writePing(
+        eq(ctx()), eq(false), eq(0x97ACEF001L), any(ChannelPromise.class));
+    verifyWrite(never()).writeGoAway(
+        eq(ctx()), eq(STREAM_ID), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+
+    fakeClock().forwardTime(20, TimeUnit.MINUTES);
+
+    // second GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(STREAM_ID), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // channel not closed yet
+    assertTrue(channel().isOpen());
+  }
+
+  @Test
+  public void maxConnectionAgeGrace_channelClosedAfterGracePeriod_withPingTimeout()
+      throws Exception {
+    maxConnectionAgeInNanos = TimeUnit.MILLISECONDS.toNanos(10L);
+    maxConnectionAgeGraceInNanos = TimeUnit.MINUTES.toNanos(30L); // greater than ping timeout
+    manualSetUp();
+    createStream();
+
+    fakeClock().forwardNanos(maxConnectionAgeInNanos);
+
+    // first GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(Integer.MAX_VALUE), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // ping sent
+    verifyWrite().writePing(
+        eq(ctx()), eq(false), eq(0x97ACEF001L), any(ChannelPromise.class));
+    verifyWrite(never()).writeGoAway(
+        eq(ctx()), eq(STREAM_ID), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+
+    fakeClock().forwardNanos(TimeUnit.SECONDS.toNanos(10));
+
+    // second GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(STREAM_ID), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+
+    fakeClock().forwardNanos(maxConnectionAgeGraceInNanos - 2);
+
+    assertTrue(channel().isOpen());
+
+    fakeClock().forwardTime(2, TimeUnit.MILLISECONDS);
+
+    // channel closed
+    assertTrue(!channel().isOpen());
+  }
+
+  @Test
+  public void maxConnectionAgeGrace_channelClosedAfterGracePeriod_withPingAck()
+      throws Exception {
+    maxConnectionAgeInNanos = TimeUnit.MILLISECONDS.toNanos(10L);
+    maxConnectionAgeGraceInNanos = TimeUnit.MINUTES.toNanos(30L); // greater than ping timeout
+    manualSetUp();
+    createStream();
+
+    fakeClock().forwardNanos(maxConnectionAgeInNanos);
+
+    // first GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(Integer.MAX_VALUE), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+    // ping sent
+    verifyWrite().writePing(
+        eq(ctx()), eq(false), eq(0x97ACEF001L), any(ChannelPromise.class));
+    verifyWrite(never()).writeGoAway(
+        eq(ctx()), eq(STREAM_ID), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+
+    long pingRoundTripMillis = 100;  // less than ping timeout
+    fakeClock().forwardTime(pingRoundTripMillis, TimeUnit.MILLISECONDS);
+    channelRead(pingFrame(true /* isAck */, 0x97ACEF001L));
+
+    // second GO_AWAY sent
+    verifyWrite().writeGoAway(
+        eq(ctx()), eq(STREAM_ID), eq(Http2Error.NO_ERROR.code()), any(ByteBuf.class),
+        any(ChannelPromise.class));
+
+    fakeClock().forwardNanos(maxConnectionAgeGraceInNanos - TimeUnit.MILLISECONDS.toNanos(2));
+
+    assertTrue(channel().isOpen());
+
+    fakeClock().forwardTime(2, TimeUnit.MILLISECONDS);
+
+    // channel closed
+    assertTrue(!channel().isOpen());
+  }
+
+  private void createStream() throws Exception {
+    Http2Headers headers = new DefaultHttp2Headers()
+        .method(HTTP_METHOD)
+        .set(CONTENT_TYPE_HEADER, CONTENT_TYPE_GRPC)
+        .set(TE_HEADER, TE_TRAILERS)
+        .path(new AsciiString("/foo/bar"));
+    ByteBuf headersFrame = headersFrame(STREAM_ID, headers);
+    channelRead(headersFrame);
+
+    ArgumentCaptor<NettyServerStream> streamCaptor =
+        ArgumentCaptor.forClass(NettyServerStream.class);
+    ArgumentCaptor<String> methodCaptor = ArgumentCaptor.forClass(String.class);
+    verify(transportListener).streamCreated(streamCaptor.capture(), methodCaptor.capture(),
+        any(Metadata.class));
+    stream = streamCaptor.getValue();
+  }
+
+  private ByteBuf emptyGrpcFrame(int streamId, boolean endStream) throws Exception {
+    ByteBuf buf = NettyTestUtil.messageFrame("");
+    try {
+      return dataFrame(streamId, endStream, buf);
+    } finally {
+      buf.release();
+    }
+  }
+
+  @Override
+  protected NettyServerHandler newHandler() {
+    return NettyServerHandler.newHandler(
+        /* channelUnused= */ channel().newPromise(),
+        frameReader(),
+        frameWriter(),
+        transportListener,
+        Arrays.asList(streamTracerFactory),
+        transportTracer,
+        maxConcurrentStreams,
+        flowControlWindow,
+        maxHeaderListSize,
+        DEFAULT_MAX_MESSAGE_SIZE,
+        keepAliveTimeInNanos,
+        keepAliveTimeoutInNanos,
+        maxConnectionIdleInNanos,
+        maxConnectionAgeInNanos,
+        maxConnectionAgeGraceInNanos,
+        permitKeepAliveWithoutCalls,
+        permitKeepAliveTimeInNanos);
+  }
+
+  @Override
+  protected WriteQueue initWriteQueue() {
+    return handler().getWriteQueue();
+  }
+
+  @Override
+  protected void makeStream() throws Exception {
+    createStream();
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyServerProviderTest.java b/netty/src/test/java/io/grpc/netty/NettyServerProviderTest.java
new file mode 100644
index 0000000..70dbd64
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyServerProviderTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.ServerProvider;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link NettyServerProvider}. */
+@RunWith(JUnit4.class)
+public class NettyServerProviderTest {
+  private NettyServerProvider provider = new NettyServerProvider();
+
+  @Test
+  public void provided() {
+    assertSame(NettyServerProvider.class, ServerProvider.provider().getClass());
+  }
+
+  @Test
+  public void basicMethods() {
+    assertTrue(provider.isAvailable());
+    assertEquals(5, provider.priority());
+  }
+
+  @Test
+  public void builderIsANettyBuilder() {
+    assertSame(NettyServerBuilder.class, provider.builderForPort(443).getClass());
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyServerStreamTest.java b/netty/src/test/java/io/grpc/netty/NettyServerStreamTest.java
new file mode 100644
index 0000000..1a174cd
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyServerStreamTest.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
+import static io.grpc.netty.NettyTestUtil.messageFrame;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ListMultimap;
+import io.grpc.Attributes;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.ServerStreamListener;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.StreamListener;
+import io.grpc.internal.TransportTracer;
+import io.netty.buffer.EmptyByteBuf;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.handler.codec.http2.DefaultHttp2Headers;
+import io.netty.util.AsciiString;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.Queue;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/** Unit tests for {@link NettyServerStream}. */
+@RunWith(JUnit4.class)
+public class NettyServerStreamTest extends NettyStreamTestBase<NettyServerStream> {
+  @Mock
+  protected ServerStreamListener serverListener;
+
+  @Mock
+  private NettyServerHandler handler;
+
+  private Metadata trailers = new Metadata();
+  private final Queue<InputStream> listenerMessageQueue = new LinkedList<InputStream>();
+
+  @Before
+  @Override
+  public void setUp() {
+    super.setUp();
+
+    // Verify onReady notification and then reset it.
+    verify(listener()).onReady();
+    reset(listener());
+
+    doAnswer(
+          new Answer<Void>() {
+            @Override
+            public Void answer(InvocationOnMock invocation) throws Throwable {
+              StreamListener.MessageProducer producer =
+                  (StreamListener.MessageProducer) invocation.getArguments()[0];
+              InputStream message;
+              while ((message = producer.next()) != null) {
+                listenerMessageQueue.add(message);
+              }
+              return null;
+            }
+          })
+      .when(serverListener)
+      .messagesAvailable(Matchers.<StreamListener.MessageProducer>any());
+  }
+
+  @Test
+  public void writeMessageShouldSendResponse() throws Exception {
+    ListMultimap<CharSequence, CharSequence> expectedHeaders =
+        ImmutableListMultimap.copyOf(new DefaultHttp2Headers()
+            .status(Utils.STATUS_OK)
+            .set(Utils.CONTENT_TYPE_HEADER, Utils.CONTENT_TYPE_GRPC));
+
+    stream.writeHeaders(new Metadata());
+
+    ArgumentCaptor<SendResponseHeadersCommand> sendHeadersCap =
+        ArgumentCaptor.forClass(SendResponseHeadersCommand.class);
+    verify(writeQueue).enqueue(sendHeadersCap.capture(), eq(true));
+    SendResponseHeadersCommand sendHeaders = sendHeadersCap.getValue();
+    assertThat(sendHeaders.stream()).isSameAs(stream.transportState());
+    assertThat(ImmutableListMultimap.copyOf(sendHeaders.headers()))
+        .containsExactlyEntriesIn(expectedHeaders);
+    assertThat(sendHeaders.endOfStream()).isFalse();
+
+    byte[] msg = smallMessage();
+    stream.writeMessage(new ByteArrayInputStream(msg));
+    stream.flush();
+
+    verify(writeQueue).enqueue(
+        eq(new SendGrpcFrameCommand(stream.transportState(), messageFrame(MESSAGE), false)),
+        eq(true));
+  }
+
+  @Test
+  public void writeHeadersShouldSendHeaders() throws Exception {
+    Metadata headers = new Metadata();
+    ListMultimap<CharSequence, CharSequence> expectedHeaders =
+        ImmutableListMultimap.copyOf(Utils.convertServerHeaders(headers));
+
+    stream().writeHeaders(headers);
+
+    ArgumentCaptor<SendResponseHeadersCommand> sendHeadersCap =
+        ArgumentCaptor.forClass(SendResponseHeadersCommand.class);
+    verify(writeQueue).enqueue(sendHeadersCap.capture(), eq(true));
+    SendResponseHeadersCommand sendHeaders = sendHeadersCap.getValue();
+    assertThat(sendHeaders.stream()).isSameAs(stream.transportState());
+    assertThat(ImmutableListMultimap.copyOf(sendHeaders.headers()))
+        .containsExactlyEntriesIn(expectedHeaders);
+    assertThat(sendHeaders.endOfStream()).isFalse();
+  }
+
+  @Test
+  public void closeBeforeClientHalfCloseShouldSucceed() throws Exception {
+    ListMultimap<CharSequence, CharSequence> expectedHeaders =
+        ImmutableListMultimap.copyOf(new DefaultHttp2Headers()
+            .status(new AsciiString("200"))
+            .set(new AsciiString("content-type"), new AsciiString("application/grpc"))
+            .set(new AsciiString("grpc-status"), new AsciiString("0")));
+
+    stream().close(Status.OK, new Metadata());
+
+    ArgumentCaptor<SendResponseHeadersCommand> sendHeadersCap =
+        ArgumentCaptor.forClass(SendResponseHeadersCommand.class);
+    verify(writeQueue).enqueue(sendHeadersCap.capture(), eq(true));
+    SendResponseHeadersCommand sendHeaders = sendHeadersCap.getValue();
+    assertThat(sendHeaders.stream()).isSameAs(stream.transportState());
+    assertThat(ImmutableListMultimap.copyOf(sendHeaders.headers()))
+        .containsExactlyEntriesIn(expectedHeaders);
+    assertThat(sendHeaders.endOfStream()).isTrue();
+    verifyZeroInteractions(serverListener);
+
+    // Sending complete. Listener gets closed()
+    stream().transportState().complete();
+
+    verify(serverListener).closed(Status.OK);
+    assertNull("no message expected", listenerMessageQueue.poll());
+  }
+
+  @Test
+  public void closeWithErrorBeforeClientHalfCloseShouldSucceed() throws Exception {
+    ListMultimap<CharSequence, CharSequence> expectedHeaders =
+        ImmutableListMultimap.copyOf(new DefaultHttp2Headers()
+            .status(new AsciiString("200"))
+            .set(new AsciiString("content-type"), new AsciiString("application/grpc"))
+            .set(new AsciiString("grpc-status"), new AsciiString("1")));
+
+    // Error is sent on wire and ends the stream
+    stream().close(Status.CANCELLED, trailers);
+
+    ArgumentCaptor<SendResponseHeadersCommand> sendHeadersCap =
+        ArgumentCaptor.forClass(SendResponseHeadersCommand.class);
+    verify(writeQueue).enqueue(sendHeadersCap.capture(), eq(true));
+    SendResponseHeadersCommand sendHeaders = sendHeadersCap.getValue();
+    assertThat(sendHeaders.stream()).isSameAs(stream.transportState());
+    assertThat(ImmutableListMultimap.copyOf(sendHeaders.headers()))
+        .containsExactlyEntriesIn(expectedHeaders);
+    assertThat(sendHeaders.endOfStream()).isTrue();
+    verifyZeroInteractions(serverListener);
+
+    // Sending complete. Listener gets closed()
+    stream().transportState().complete();
+    verify(serverListener).closed(Status.OK);
+    assertNull("no message expected", listenerMessageQueue.poll());
+  }
+
+  @Test
+  public void closeAfterClientHalfCloseShouldSucceed() throws Exception {
+    ListMultimap<CharSequence, CharSequence> expectedHeaders =
+        ImmutableListMultimap.copyOf(new DefaultHttp2Headers()
+            .status(new AsciiString("200"))
+            .set(new AsciiString("content-type"), new AsciiString("application/grpc"))
+            .set(new AsciiString("grpc-status"), new AsciiString("0")));
+
+    // Client half-closes. Listener gets halfClosed()
+    stream().transportState()
+        .inboundDataReceived(new EmptyByteBuf(UnpooledByteBufAllocator.DEFAULT), true);
+
+    verify(serverListener).halfClosed();
+
+    // Server closes. Status sent
+    stream().close(Status.OK, trailers);
+    assertNull("no message expected", listenerMessageQueue.poll());
+
+    ArgumentCaptor<SendResponseHeadersCommand> cmdCap =
+        ArgumentCaptor.forClass(SendResponseHeadersCommand.class);
+    verify(writeQueue).enqueue(cmdCap.capture(), eq(true));
+    SendResponseHeadersCommand cmd = cmdCap.getValue();
+    assertThat(cmd.stream()).isSameAs(stream.transportState());
+    assertThat(ImmutableListMultimap.copyOf(cmd.headers()))
+        .containsExactlyEntriesIn(expectedHeaders);
+    assertThat(cmd.endOfStream()).isTrue();
+
+    // Sending and receiving complete. Listener gets closed()
+    stream().transportState().complete();
+    verify(serverListener).closed(Status.OK);
+    assertNull("no message expected", listenerMessageQueue.poll());
+  }
+
+  @Test
+  public void abortStreamAndNotSendStatus() throws Exception {
+    Status status = Status.INTERNAL.withCause(new Throwable());
+    stream().transportState().transportReportStatus(status);
+    verify(serverListener).closed(same(status));
+    verify(channel, never()).writeAndFlush(any(SendResponseHeadersCommand.class));
+    verify(channel, never()).writeAndFlush(any(SendGrpcFrameCommand.class));
+    assertNull("no message expected", listenerMessageQueue.poll());
+  }
+
+  @Test
+  public void abortStreamAfterClientHalfCloseShouldCallClose() {
+    Status status = Status.INTERNAL.withCause(new Throwable());
+    // Client half-closes. Listener gets halfClosed()
+    stream().transportState().inboundDataReceived(
+        new EmptyByteBuf(UnpooledByteBufAllocator.DEFAULT), true);
+    verify(serverListener).halfClosed();
+    // Abort from the transport layer
+    stream().transportState().transportReportStatus(status);
+    verify(serverListener).closed(same(status));
+    assertNull("no message expected", listenerMessageQueue.poll());
+  }
+
+  @Test
+  public void emptyFramerShouldSendNoPayload() {
+    ListMultimap<CharSequence, CharSequence> expectedHeaders =
+        ImmutableListMultimap.copyOf(new DefaultHttp2Headers()
+            .status(new AsciiString("200"))
+            .set(new AsciiString("content-type"), new AsciiString("application/grpc"))
+            .set(new AsciiString("grpc-status"), new AsciiString("0")));
+    ArgumentCaptor<SendResponseHeadersCommand> cmdCap =
+        ArgumentCaptor.forClass(SendResponseHeadersCommand.class);
+
+    stream().close(Status.OK, new Metadata());
+
+    verify(writeQueue).enqueue(cmdCap.capture(), eq(true));
+    SendResponseHeadersCommand cmd = cmdCap.getValue();
+    assertThat(cmd.stream()).isSameAs(stream.transportState());
+    assertThat(ImmutableListMultimap.copyOf(cmd.headers()))
+        .containsExactlyEntriesIn(expectedHeaders);
+    assertThat(cmd.endOfStream()).isTrue();
+  }
+
+  @Test
+  public void cancelStreamShouldSucceed() {
+    stream().cancel(Status.DEADLINE_EXCEEDED);
+    verify(writeQueue).enqueue(
+        new CancelServerStreamCommand(stream().transportState(), Status.DEADLINE_EXCEEDED),
+        true);
+  }
+
+  @Override
+  protected NettyServerStream createStream() {
+    when(handler.getWriteQueue()).thenReturn(writeQueue);
+    StatsTraceContext statsTraceCtx = StatsTraceContext.NOOP;
+    TransportTracer transportTracer = new TransportTracer();
+    NettyServerStream.TransportState state = new NettyServerStream.TransportState(
+        handler, channel.eventLoop(), http2Stream, DEFAULT_MAX_MESSAGE_SIZE, statsTraceCtx,
+        transportTracer);
+    NettyServerStream stream = new NettyServerStream(channel, state, Attributes.EMPTY,
+        "test-authority", statsTraceCtx, transportTracer);
+    stream.transportState().setListener(serverListener);
+    state.onStreamAllocated();
+    verify(serverListener, atLeastOnce()).onReady();
+    verifyNoMoreInteractions(serverListener);
+    return stream;
+  }
+
+  @Override
+  protected void sendHeadersIfServer() {
+    stream.writeHeaders(new Metadata());
+  }
+
+  @Override
+  protected void closeStream() {
+    stream().close(Status.ABORTED, new Metadata());
+  }
+
+  @Override
+  protected ServerStreamListener listener() {
+    return serverListener;
+  }
+
+  @Override
+  protected Queue<InputStream> listenerMessageQueue() {
+    return listenerMessageQueue;
+  }
+
+  private NettyServerStream stream() {
+    return stream;
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyServerTest.java b/netty/src/test/java/io/grpc/netty/NettyServerTest.java
new file mode 100644
index 0000000..539f135
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyServerTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.InternalChannelz.id;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.ServerStreamTracer;
+import io.grpc.internal.ServerListener;
+import io.grpc.internal.ServerTransport;
+import io.grpc.internal.ServerTransportListener;
+import io.grpc.internal.TransportTracer;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.WriteBufferWaterMark;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class NettyServerTest {
+  private final InternalChannelz channelz = new InternalChannelz();
+
+  @Test
+  public void getPort() throws Exception {
+    InetSocketAddress addr = new InetSocketAddress(0);
+    NettyServer ns = new NettyServer(
+        addr,
+        NioServerSocketChannel.class,
+        new HashMap<ChannelOption<?>, Object>(),
+        null, // no boss group
+        null, // no event group
+        new ProtocolNegotiators.PlaintextNegotiator(),
+        Collections.<ServerStreamTracer.Factory>emptyList(),
+        TransportTracer.getDefaultFactory(),
+        1, // ignore
+        1, // ignore
+        1, // ignore
+        1, // ignore
+        1, // ignore
+        1, 1, // ignore
+        1, 1, // ignore
+        true, 0, // ignore
+        channelz);
+    ns.start(new ServerListener() {
+      @Override
+      public ServerTransportListener transportCreated(ServerTransport transport) {
+        return null;
+      }
+
+      @Override
+      public void serverShutdown() {}
+    });
+
+    // Check that we got an actual port.
+    assertThat(ns.getPort()).isGreaterThan(0);
+
+    // Cleanup
+    ns.shutdown();
+  }
+
+  @Test
+  public void getPort_notStarted() throws Exception {
+    InetSocketAddress addr = new InetSocketAddress(0);
+    NettyServer ns = new NettyServer(
+        addr,
+        NioServerSocketChannel.class,
+        new HashMap<ChannelOption<?>, Object>(),
+        null, // no boss group
+        null, // no event group
+        new ProtocolNegotiators.PlaintextNegotiator(),
+        Collections.<ServerStreamTracer.Factory>emptyList(),
+        TransportTracer.getDefaultFactory(),
+        1, // ignore
+        1, // ignore
+        1, // ignore
+        1, // ignore
+        1, // ignore
+        1, 1, // ignore
+        1, 1, // ignore
+        true, 0, // ignore
+        channelz);
+
+    assertThat(ns.getPort()).isEqualTo(-1);
+  }
+
+  @Test(timeout = 60000)
+  public void childChannelOptions() throws Exception {
+    final int originalLowWaterMark = 2097169;
+    final int originalHighWaterMark = 2097211;
+
+    Map<ChannelOption<?>, Object> channelOptions = new HashMap<ChannelOption<?>, Object>();
+
+    channelOptions.put(ChannelOption.WRITE_BUFFER_WATER_MARK,
+        new WriteBufferWaterMark(originalLowWaterMark, originalHighWaterMark));
+
+    final AtomicInteger lowWaterMark = new AtomicInteger(0);
+    final AtomicInteger highWaterMark = new AtomicInteger(0);
+
+    final CountDownLatch countDownLatch = new CountDownLatch(1);
+
+    InetSocketAddress addr = new InetSocketAddress(0);
+    NettyServer ns = new NettyServer(
+        addr,
+        NioServerSocketChannel.class,
+        channelOptions,
+        null, // no boss group
+        null, // no event group
+        new ProtocolNegotiators.PlaintextNegotiator(),
+        Collections.<ServerStreamTracer.Factory>emptyList(),
+        TransportTracer.getDefaultFactory(),
+        1, // ignore
+        1, // ignore
+        1, // ignore
+        1, // ignore
+        1, // ignore
+        1, 1, // ignore
+        1, 1, // ignore
+        true, 0, // ignore
+        channelz);
+    ns.start(new ServerListener() {
+      @Override
+      public ServerTransportListener transportCreated(ServerTransport transport) {
+        Channel channel = ((NettyServerTransport)transport).channel();
+        WriteBufferWaterMark writeBufferWaterMark = channel.config()
+            .getOption(ChannelOption.WRITE_BUFFER_WATER_MARK);
+        lowWaterMark.set(writeBufferWaterMark.low());
+        highWaterMark.set(writeBufferWaterMark.high());
+
+        countDownLatch.countDown();
+
+        return null;
+      }
+
+      @Override
+      public void serverShutdown() {}
+    });
+
+    Socket socket = new Socket();
+    socket.connect(new InetSocketAddress("localhost", ns.getPort()), /* timeout= */ 8000);
+    countDownLatch.await();
+    socket.close();
+
+    assertThat(lowWaterMark.get()).isEqualTo(originalLowWaterMark);
+    assertThat(highWaterMark.get()).isEqualTo(originalHighWaterMark);
+
+    ns.shutdown();
+  }
+
+  @Test
+  public void channelzListenSocket() throws Exception {
+    InetSocketAddress addr = new InetSocketAddress(0);
+    NettyServer ns = new NettyServer(
+        addr,
+        NioServerSocketChannel.class,
+        new HashMap<ChannelOption<?>, Object>(),
+        null, // no boss group
+        null, // no event group
+        new ProtocolNegotiators.PlaintextNegotiator(),
+        Collections.<ServerStreamTracer.Factory>emptyList(),
+        TransportTracer.getDefaultFactory(),
+        1, // ignore
+        1, // ignore
+        1, // ignore
+        1, // ignore
+        1, // ignore
+        1, 1, // ignore
+        1, 1, // ignore
+        true, 0, // ignore
+        channelz);
+    final SettableFuture<Void> shutdownCompleted = SettableFuture.create();
+    ns.start(new ServerListener() {
+      @Override
+      public ServerTransportListener transportCreated(ServerTransport transport) {
+        return null;
+      }
+
+      @Override
+      public void serverShutdown() {
+        shutdownCompleted.set(null);
+      }
+    });
+    assertThat(ns.getPort()).isGreaterThan(0);
+
+    InternalInstrumented<SocketStats> listenSocket = getOnlyElement(ns.getListenSockets());
+    assertSame(listenSocket, channelz.getSocket(id(listenSocket)));
+
+    // very basic sanity check of the contents
+    SocketStats socketStats = listenSocket.getStats().get();
+    assertEquals(ns.getPort(), ((InetSocketAddress) socketStats.local).getPort());
+    assertNull(socketStats.remote);
+
+    // TODO(zpencer): uncomment when sock options are exposed
+    // by default, there are some socket options set on the listen socket
+    // assertThat(socketStats.socketOptions.additional).isNotEmpty();
+
+    // Cleanup
+    ns.shutdown();
+    shutdownCompleted.get();
+
+    // listen socket is removed
+    assertNull(channelz.getSocket(id(listenSocket)));
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyServerTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyServerTransportTest.java
new file mode 100644
index 0000000..26b54d3
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyServerTransportTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static io.grpc.netty.NettyServerTransport.getLogLevel;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class NettyServerTransportTest {
+  @Test
+  public void unknownException() {
+    assertEquals(Level.INFO, getLogLevel(new Exception()));
+  }
+
+  @Test
+  public void quiet() {
+    assertEquals(Level.FINE, getLogLevel(new IOException("Connection reset by peer")));
+    assertEquals(Level.FINE, getLogLevel(new IOException(
+        "An existing connection was forcibly closed by the remote host")));
+  }
+
+  @Test
+  public void nonquiet() {
+    assertEquals(Level.INFO, getLogLevel(new IOException("foo")));
+  }
+
+  @Test
+  public void nullMessage() {
+    IOException e = new IOException();
+    assertNull(e.getMessage());
+    assertEquals(Level.INFO, getLogLevel(e));
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyStreamTestBase.java b/netty/src/test/java/io/grpc/netty/NettyStreamTestBase.java
new file mode 100644
index 0000000..ec4ad6a
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyStreamTestBase.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Charsets.US_ASCII;
+import static io.grpc.netty.NettyTestUtil.messageFrame;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.grpc.internal.Stream;
+import io.grpc.internal.StreamListener;
+import io.grpc.netty.WriteQueue.QueuedCommand;
+import io.netty.buffer.UnpooledByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.DefaultChannelPromise;
+import io.netty.channel.EventLoop;
+import io.netty.handler.codec.http2.Http2Stream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Queue;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Base class for Netty stream unit tests.
+ */
+public abstract class NettyStreamTestBase<T extends Stream> {
+  protected static final String MESSAGE = "hello world";
+  protected static final int STREAM_ID = 1;
+
+  @Mock
+  protected Channel channel;
+
+  @Mock
+  private ChannelHandlerContext ctx;
+
+  @Mock
+  private ChannelPipeline pipeline;
+
+  @Mock
+  protected EventLoop eventLoop;
+
+  // ChannelPromise has too many methods to implement; we stubbed all necessary methods of Future.
+  @SuppressWarnings("DoNotMock")
+  @Mock
+  protected ChannelPromise promise;
+
+  @Mock
+  protected Http2Stream http2Stream;
+
+  @Mock
+  protected WriteQueue writeQueue;
+
+  protected T stream;
+
+  /** Set up for test. */
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+
+    when(channel.alloc()).thenReturn(UnpooledByteBufAllocator.DEFAULT);
+    when(channel.pipeline()).thenReturn(pipeline);
+    when(channel.eventLoop()).thenReturn(eventLoop);
+    when(channel.newPromise()).thenReturn(new DefaultChannelPromise(channel));
+    when(channel.voidPromise()).thenReturn(new DefaultChannelPromise(channel));
+    ChannelPromise completedPromise = new DefaultChannelPromise(channel)
+        .setSuccess();
+    when(channel.write(any())).thenReturn(completedPromise);
+    when(channel.writeAndFlush(any())).thenReturn(completedPromise);
+    when(writeQueue.enqueue(any(QueuedCommand.class), anyBoolean())).thenReturn(completedPromise);
+    when(pipeline.firstContext()).thenReturn(ctx);
+    when(eventLoop.inEventLoop()).thenReturn(true);
+    when(http2Stream.id()).thenReturn(STREAM_ID);
+
+    doAnswer(new Answer<Void>() {
+      @Override
+      public Void answer(InvocationOnMock invocation) throws Throwable {
+        Runnable runnable = (Runnable) invocation.getArguments()[0];
+        runnable.run();
+        return null;
+      }
+    }).when(eventLoop).execute(any(Runnable.class));
+
+    stream = createStream();
+  }
+
+  @Test
+  public void inboundMessageShouldCallListener() throws Exception {
+    stream.request(1);
+
+    if (stream instanceof NettyServerStream) {
+      ((NettyServerStream) stream).transportState()
+          .inboundDataReceived(messageFrame(MESSAGE), false);
+    } else {
+      ((NettyClientStream) stream).transportState()
+          .transportDataReceived(messageFrame(MESSAGE), false);
+    }
+
+    InputStream message = listenerMessageQueue().poll();
+
+    // Verify that inbound flow control window update has been disabled for the stream.
+    assertEquals(MESSAGE, NettyTestUtil.toString(message));
+    assertNull("no additional message expected", listenerMessageQueue().poll());
+  }
+
+  @Test
+  public void shouldBeImmediatelyReadyForData() {
+    assertTrue(stream.isReady());
+  }
+
+  @Test
+  public void closedShouldNotBeReady() throws IOException {
+    assertTrue(stream.isReady());
+    closeStream();
+    assertFalse(stream.isReady());
+  }
+
+  @Test
+  public void notifiedOnReadyAfterWriteCompletes() throws IOException {
+    sendHeadersIfServer();
+    assertTrue(stream.isReady());
+    byte[] msg = largeMessage();
+    // The channel.write future is set up to automatically complete, indicating that the write is
+    // done.
+    stream.writeMessage(new ByteArrayInputStream(msg));
+    stream.flush();
+    assertTrue(stream.isReady());
+    verify(listener()).onReady();
+  }
+
+  @Test
+  public void shouldBeReadyForDataAfterWritingSmallMessage() throws IOException {
+    sendHeadersIfServer();
+    // Make sure the writes don't complete so we "back up"
+    ChannelPromise uncompletedPromise = new DefaultChannelPromise(channel);
+    when(writeQueue.enqueue(any(QueuedCommand.class), anyBoolean())).thenReturn(uncompletedPromise);
+
+    assertTrue(stream.isReady());
+    byte[] msg = smallMessage();
+    stream.writeMessage(new ByteArrayInputStream(msg));
+    stream.flush();
+    assertTrue(stream.isReady());
+    verify(listener(), never()).onReady();
+  }
+
+  @Test
+  public void shouldNotBeReadyForDataAfterWritingLargeMessage() throws IOException {
+    sendHeadersIfServer();
+    // Make sure the writes don't complete so we "back up"
+    ChannelPromise uncompletedPromise = new DefaultChannelPromise(channel);
+    when(writeQueue.enqueue(any(QueuedCommand.class), anyBoolean())).thenReturn(uncompletedPromise);
+
+    assertTrue(stream.isReady());
+    byte[] msg = largeMessage();
+    stream.writeMessage(new ByteArrayInputStream(msg));
+    stream.flush();
+    assertFalse(stream.isReady());
+    verify(listener(), never()).onReady();
+  }
+
+  protected byte[] smallMessage() {
+    return MESSAGE.getBytes(US_ASCII);
+  }
+
+  protected byte[] largeMessage() {
+    byte[] smallMessage = smallMessage();
+    int size = smallMessage.length * 10 * 1024;
+    byte[] largeMessage = new byte[size];
+    for (int ix = 0; ix < size; ix += smallMessage.length) {
+      System.arraycopy(smallMessage, 0, largeMessage, ix, smallMessage.length);
+    }
+    return largeMessage;
+  }
+
+  protected abstract T createStream();
+
+  protected abstract void sendHeadersIfServer();
+
+  protected abstract StreamListener listener();
+
+  protected abstract Queue<InputStream> listenerMessageQueue();
+
+  protected abstract void closeStream();
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyTestUtil.java b/netty/src/test/java/io/grpc/netty/NettyTestUtil.java
new file mode 100644
index 0000000..d8cb482
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyTestUtil.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static io.netty.util.CharsetUtil.UTF_8;
+
+import com.google.common.io.ByteStreams;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.InputStream;
+
+/**
+ * Utility methods for supporting Netty tests.
+ */
+public class NettyTestUtil {
+
+  static String toString(InputStream in) throws Exception {
+    byte[] bytes = new byte[in.available()];
+    ByteStreams.readFully(in, bytes);
+    return new String(bytes, UTF_8);
+  }
+
+  static ByteBuf messageFrame(String message) throws Exception {
+    ByteArrayOutputStream os = new ByteArrayOutputStream();
+    DataOutputStream dos = new DataOutputStream(os);
+    dos.write(message.getBytes(UTF_8));
+    dos.close();
+
+    // Write the compression header followed by the context frame.
+    return compressionFrame(os.toByteArray());
+  }
+
+  static ByteBuf compressionFrame(byte[] data) {
+    ByteBuf buf = Unpooled.buffer();
+    buf.writeByte(0);
+    buf.writeInt(data.length);
+    buf.writeBytes(data);
+    return buf;
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyTransportTest.java b/netty/src/test/java/io/grpc/netty/NettyTransportTest.java
new file mode 100644
index 0000000..1aa4c12
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyTransportTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.ServerStreamTracer;
+import io.grpc.internal.ClientTransportFactory;
+import io.grpc.internal.FakeClock;
+import io.grpc.internal.InternalServer;
+import io.grpc.internal.ManagedClientTransport;
+import io.grpc.internal.testing.AbstractTransportTest;
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for Netty transport. */
+@RunWith(JUnit4.class)
+public class NettyTransportTest extends AbstractTransportTest {
+  private final FakeClock fakeClock = new FakeClock();
+  // Avoid LocalChannel for testing because LocalChannel can fail with
+  // io.netty.channel.ChannelException instead of java.net.ConnectException which breaks
+  // serverNotListening test.
+  private final ClientTransportFactory clientFactory = NettyChannelBuilder
+      // Although specified here, address is ignored because we never call build.
+      .forAddress("localhost", 0)
+      .flowControlWindow(65 * 1024)
+      .negotiationType(NegotiationType.PLAINTEXT)
+      .setTransportTracerFactory(fakeClockTransportTracer)
+      .buildTransportFactory();
+
+  @Override
+  protected boolean haveTransportTracer() {
+    return true;
+  }
+
+  @After
+  public void releaseClientFactory() {
+    clientFactory.close();
+  }
+
+  @Override
+  protected InternalServer newServer(List<ServerStreamTracer.Factory> streamTracerFactories) {
+    return NettyServerBuilder
+        .forPort(0)
+        .flowControlWindow(65 * 1024)
+        .setTransportTracerFactory(fakeClockTransportTracer)
+        .buildTransportServer(streamTracerFactories);
+  }
+
+  @Override
+  protected InternalServer newServer(
+      InternalServer server, List<ServerStreamTracer.Factory> streamTracerFactories) {
+    int port = server.getPort();
+    return NettyServerBuilder
+        .forPort(port)
+        .flowControlWindow(65 * 1024)
+        .setTransportTracerFactory(fakeClockTransportTracer)
+        .buildTransportServer(streamTracerFactories);
+  }
+
+  @Override
+  protected String testAuthority(InternalServer server) {
+    return "localhost:" + server.getPort();
+  }
+
+  @Override
+  protected void advanceClock(long offset, TimeUnit unit) {
+    fakeClock.forwardNanos(unit.toNanos(offset));
+  }
+
+  @Override
+  protected long fakeCurrentTimeNanos() {
+    return fakeClock.getTicker().read();
+  }
+
+  @Override
+  protected ManagedClientTransport newClientTransport(InternalServer server) {
+    int port = server.getPort();
+    return clientFactory.newClientTransport(
+        new InetSocketAddress("localhost", port),
+        new ClientTransportFactory.ClientTransportOptions()
+          .setAuthority(testAuthority(server)));
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyWritableBufferAllocatorTest.java b/netty/src/test/java/io/grpc/netty/NettyWritableBufferAllocatorTest.java
new file mode 100644
index 0000000..d577ec4
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyWritableBufferAllocatorTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static org.junit.Assert.assertEquals;
+
+import io.grpc.internal.WritableBuffer;
+import io.grpc.internal.WritableBufferAllocator;
+import io.grpc.internal.WritableBufferAllocatorTestBase;
+import io.netty.buffer.ByteBufAllocator;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link NettyWritableBufferAllocator}.
+ */
+@RunWith(JUnit4.class)
+public class NettyWritableBufferAllocatorTest extends WritableBufferAllocatorTestBase {
+
+  private final NettyWritableBufferAllocator allocator =
+          new NettyWritableBufferAllocator(ByteBufAllocator.DEFAULT);
+
+  @Override
+  protected WritableBufferAllocator allocator() {
+    return allocator;
+  }
+
+  @Test
+  public void testCapacityHasMinimum() {
+    WritableBuffer buffer = allocator().allocate(100);
+    assertEquals(0, buffer.readableBytes());
+    assertEquals(4096, buffer.writableBytes());
+  }
+
+  @Test
+  public void testCapacityIsExactAboveMinimum() {
+    WritableBuffer buffer = allocator().allocate(9000);
+    assertEquals(0, buffer.readableBytes());
+    assertEquals(9000, buffer.writableBytes());
+  }
+
+  @Test
+  public void testCapacityIsCappedAtMaximum() {
+    // Current max is 1MB
+    WritableBuffer buffer = allocator().allocate(1024 * 1025);
+    assertEquals(0, buffer.readableBytes());
+    assertEquals(1024 * 1024, buffer.writableBytes());
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/NettyWritableBufferTest.java b/netty/src/test/java/io/grpc/netty/NettyWritableBufferTest.java
new file mode 100644
index 0000000..b5f0f76
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/NettyWritableBufferTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import io.grpc.internal.WritableBuffer;
+import io.grpc.internal.WritableBufferTestBase;
+import io.netty.buffer.Unpooled;
+import java.util.Arrays;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link NettyWritableBuffer}.
+ */
+@RunWith(JUnit4.class)
+public class NettyWritableBufferTest extends WritableBufferTestBase {
+
+  private NettyWritableBuffer buffer;
+
+  @Before
+  public void setup() {
+    buffer = new NettyWritableBuffer(Unpooled.buffer(100));
+  }
+
+  @After
+  public void teardown() {
+    buffer.release();
+  }
+
+  @Override
+  protected WritableBuffer buffer() {
+    return buffer;
+  }
+
+  @Override
+  protected byte[] writtenBytes() {
+    byte[] b = buffer.bytebuf().array();
+    int fromIdx = buffer.bytebuf().arrayOffset();
+    return Arrays.copyOfRange(b, fromIdx, buffer.readableBytes());
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java
new file mode 100644
index 0000000..3b94bf2
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/ProtocolNegotiatorsTest.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+
+import io.grpc.internal.testing.TestUtils;
+import io.grpc.netty.ProtocolNegotiators.HostPort;
+import io.grpc.netty.ProtocolNegotiators.ServerTlsHandler;
+import io.grpc.netty.ProtocolNegotiators.TlsNegotiator;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandler;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.DefaultEventLoopGroup;
+import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.channel.local.LocalAddress;
+import io.netty.channel.local.LocalChannel;
+import io.netty.channel.local.LocalServerChannel;
+import io.netty.handler.proxy.ProxyConnectException;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslHandler;
+import io.netty.handler.ssl.SslHandshakeCompletionEvent;
+import io.netty.handler.ssl.SupportedCipherSuiteFilter;
+import java.io.File;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.logging.Filter;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLException;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+@RunWith(JUnit4.class)
+public class ProtocolNegotiatorsTest {
+  private static final Runnable NOOP_RUNNABLE = new Runnable() {
+    @Override public void run() {}
+  };
+
+  @Rule public final Timeout globalTimeout = Timeout.seconds(5);
+
+  @Rule
+  public final ExpectedException thrown = ExpectedException.none();
+
+  private GrpcHttp2ConnectionHandler grpcHandler = mock(GrpcHttp2ConnectionHandler.class);
+
+  private EmbeddedChannel channel = new EmbeddedChannel();
+  private ChannelPipeline pipeline = channel.pipeline();
+  private SslContext sslContext;
+  private SSLEngine engine;
+  private ChannelHandlerContext channelHandlerCtx;
+
+  @Before
+  public void setUp() throws Exception {
+    File serverCert = TestUtils.loadCert("server1.pem");
+    File key = TestUtils.loadCert("server1.key");
+    sslContext = GrpcSslContexts.forServer(serverCert, key)
+        .ciphers(TestUtils.preferredTestCiphers(), SupportedCipherSuiteFilter.INSTANCE).build();
+    engine = SSLContext.getDefault().createSSLEngine();
+  }
+
+  @Test
+  public void tlsHandler_failsOnNullEngine() throws Exception {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("ssl");
+
+    Object unused = ProtocolNegotiators.serverTls(null);
+  }
+
+  @Test
+  public void tlsAdapter_exceptionClosesChannel() throws Exception {
+    ChannelHandler handler = new ServerTlsHandler(sslContext, grpcHandler);
+
+    // Use addFirst due to the funny error handling in EmbeddedChannel.
+    pipeline.addFirst(handler);
+
+    pipeline.fireExceptionCaught(new Exception("bad"));
+
+    assertFalse(channel.isOpen());
+  }
+
+  @Test
+  public void tlsHandler_handlerAddedAddsSslHandler() throws Exception {
+    ChannelHandler handler = new ServerTlsHandler(sslContext, grpcHandler);
+
+    pipeline.addLast(handler);
+
+    assertTrue(pipeline.first() instanceof SslHandler);
+  }
+
+  @Test
+  public void tlsHandler_userEventTriggeredNonSslEvent() throws Exception {
+    ChannelHandler handler = new ServerTlsHandler(sslContext, grpcHandler);
+    pipeline.addLast(handler);
+    channelHandlerCtx = pipeline.context(handler);
+    Object nonSslEvent = new Object();
+
+    pipeline.fireUserEventTriggered(nonSslEvent);
+
+    // A non ssl event should not cause the grpcHandler to be in the pipeline yet.
+    ChannelHandlerContext grpcHandlerCtx = pipeline.context(grpcHandler);
+    assertNull(grpcHandlerCtx);
+  }
+
+  @Test
+  public void tlsHandler_userEventTriggeredSslEvent_unsupportedProtocol() throws Exception {
+    SslHandler badSslHandler = new SslHandler(engine, false) {
+      @Override
+      public String applicationProtocol() {
+        return "badprotocol";
+      }
+    };
+
+    ChannelHandler handler = new ServerTlsHandler(sslContext, grpcHandler);
+    pipeline.addLast(handler);
+
+    pipeline.replace(SslHandler.class, null, badSslHandler);
+    channelHandlerCtx = pipeline.context(handler);
+    Object sslEvent = SslHandshakeCompletionEvent.SUCCESS;
+
+    pipeline.fireUserEventTriggered(sslEvent);
+
+    // No h2 protocol was specified, so this should be closed.
+    assertFalse(channel.isOpen());
+    ChannelHandlerContext grpcHandlerCtx = pipeline.context(grpcHandler);
+    assertNull(grpcHandlerCtx);
+  }
+
+  @Test
+  public void tlsHandler_userEventTriggeredSslEvent_handshakeFailure() throws Exception {
+    ChannelHandler handler = new ServerTlsHandler(sslContext, grpcHandler);
+    pipeline.addLast(handler);
+    channelHandlerCtx = pipeline.context(handler);
+    Object sslEvent = new SslHandshakeCompletionEvent(new RuntimeException("bad"));
+
+    pipeline.fireUserEventTriggered(sslEvent);
+
+    // No h2 protocol was specified, so this should be closed.
+    assertFalse(channel.isOpen());
+    ChannelHandlerContext grpcHandlerCtx = pipeline.context(grpcHandler);
+    assertNull(grpcHandlerCtx);
+  }
+
+  @Test
+  public void tlsHandler_userEventTriggeredSslEvent_supportedProtocolH2() throws Exception {
+    SslHandler goodSslHandler = new SslHandler(engine, false) {
+      @Override
+      public String applicationProtocol() {
+        return "h2";
+      }
+    };
+
+    ChannelHandler handler = new ServerTlsHandler(sslContext, grpcHandler);
+    pipeline.addLast(handler);
+
+    pipeline.replace(SslHandler.class, null, goodSslHandler);
+    channelHandlerCtx = pipeline.context(handler);
+    Object sslEvent = SslHandshakeCompletionEvent.SUCCESS;
+
+    pipeline.fireUserEventTriggered(sslEvent);
+
+    assertTrue(channel.isOpen());
+    ChannelHandlerContext grpcHandlerCtx = pipeline.context(grpcHandler);
+    assertNotNull(grpcHandlerCtx);
+  }
+
+  @Test
+  public void tlsHandler_userEventTriggeredSslEvent_supportedProtocolGrpcExp() throws Exception {
+    SslHandler goodSslHandler = new SslHandler(engine, false) {
+      @Override
+      public String applicationProtocol() {
+        return "grpc-exp";
+      }
+    };
+
+    ChannelHandler handler = new ServerTlsHandler(sslContext, grpcHandler);
+    pipeline.addLast(handler);
+
+    pipeline.replace(SslHandler.class, null, goodSslHandler);
+    channelHandlerCtx = pipeline.context(handler);
+    Object sslEvent = SslHandshakeCompletionEvent.SUCCESS;
+
+    pipeline.fireUserEventTriggered(sslEvent);
+
+    assertTrue(channel.isOpen());
+    ChannelHandlerContext grpcHandlerCtx = pipeline.context(grpcHandler);
+    assertNotNull(grpcHandlerCtx);
+  }
+
+  @Test
+  public void engineLog() {
+    ChannelHandler handler = new ServerTlsHandler(sslContext, grpcHandler);
+    pipeline.addLast(handler);
+    channelHandlerCtx = pipeline.context(handler);
+
+    Logger logger = Logger.getLogger(ProtocolNegotiators.class.getName());
+    Filter oldFilter = logger.getFilter();
+    try {
+      logger.setFilter(new Filter() {
+        @Override
+        public boolean isLoggable(LogRecord record) {
+          // We still want to the log method to be exercised, just not printed to stderr.
+          return false;
+        }
+      });
+
+      ProtocolNegotiators.logSslEngineDetails(
+          Level.INFO, channelHandlerCtx, "message", new Exception("bad"));
+    } finally {
+      logger.setFilter(oldFilter);
+    }
+  }
+
+  @Test
+  public void tls_failsOnNullSslContext() {
+    thrown.expect(NullPointerException.class);
+
+    Object unused = ProtocolNegotiators.tls(null);
+  }
+
+  @Test
+  public void tls_hostAndPort() throws SSLException {
+    SslContext ctx = GrpcSslContexts.forClient().build();
+    TlsNegotiator negotiator = (TlsNegotiator) ProtocolNegotiators.tls(ctx);
+    HostPort hostPort = negotiator.parseAuthority("authority:1234");
+
+    assertEquals("authority", hostPort.host);
+    assertEquals(1234, hostPort.port);
+  }
+
+  @Test
+  public void tls_host() throws SSLException {
+    SslContext ctx = GrpcSslContexts.forClient().build();
+    TlsNegotiator negotiator = (TlsNegotiator) ProtocolNegotiators.tls(ctx);
+    HostPort hostPort = negotiator.parseAuthority("[::1]");
+
+    assertEquals("[::1]", hostPort.host);
+    assertEquals(-1, hostPort.port);
+  }
+
+  @Test
+  public void tls_invalidHost() throws SSLException {
+    SslContext ctx = GrpcSslContexts.forClient().build();
+    TlsNegotiator negotiator = (TlsNegotiator) ProtocolNegotiators.tls(ctx);
+    HostPort hostPort = negotiator.parseAuthority("bad_host:1234");
+
+    // Even though it looks like a port, we treat it as part of the authority, since the host is
+    // invalid.
+    assertEquals("bad_host:1234", hostPort.host);
+    assertEquals(-1, hostPort.port);
+  }
+
+  @Test
+  public void httpProxy_nullAddressNpe() throws Exception {
+    thrown.expect(NullPointerException.class);
+    Object unused =
+        ProtocolNegotiators.httpProxy(null, "user", "pass", ProtocolNegotiators.plaintext());
+  }
+
+  @Test
+  public void httpProxy_nullNegotiatorNpe() throws Exception {
+    thrown.expect(NullPointerException.class);
+    Object unused = ProtocolNegotiators.httpProxy(
+        InetSocketAddress.createUnresolved("localhost", 80), "user", "pass", null);
+  }
+
+  @Test
+  public void httpProxy_nullUserPassNoException() throws Exception {
+    assertNotNull(ProtocolNegotiators.httpProxy(
+        InetSocketAddress.createUnresolved("localhost", 80), null, null,
+        ProtocolNegotiators.plaintext()));
+  }
+
+  @Test
+  public void httpProxy_completes() throws Exception {
+    DefaultEventLoopGroup elg = new DefaultEventLoopGroup(1);
+    // ProxyHandler is incompatible with EmbeddedChannel because when channelRegistered() is called
+    // the channel is already active.
+    LocalAddress proxy = new LocalAddress("httpProxy_completes");
+    SocketAddress host = InetSocketAddress.createUnresolved("specialHost", 314);
+
+    ChannelInboundHandler mockHandler = mock(ChannelInboundHandler.class);
+    Channel serverChannel = new ServerBootstrap().group(elg).channel(LocalServerChannel.class)
+        .childHandler(mockHandler)
+        .bind(proxy).sync().channel();
+
+    ProtocolNegotiator nego =
+        ProtocolNegotiators.httpProxy(proxy, null, null, ProtocolNegotiators.plaintext());
+    ChannelHandler handler = nego.newHandler(grpcHandler);
+    Channel channel = new Bootstrap().group(elg).channel(LocalChannel.class).handler(handler)
+        .register().sync().channel();
+    pipeline = channel.pipeline();
+    // Wait for initialization to complete
+    channel.eventLoop().submit(NOOP_RUNNABLE).sync();
+    // The grpcHandler must be in the pipeline, but we don't actually want it during our test
+    // because it will consume all events since it is a mock. We only use it because it is required
+    // to construct the Handler.
+    pipeline.remove(grpcHandler);
+    channel.connect(host).sync();
+    serverChannel.close();
+    ArgumentCaptor<ChannelHandlerContext> contextCaptor =
+        ArgumentCaptor.forClass(ChannelHandlerContext.class);
+    Mockito.verify(mockHandler).channelActive(contextCaptor.capture());
+    ChannelHandlerContext serverContext = contextCaptor.getValue();
+
+    final String golden = "isThisThingOn?";
+    ChannelFuture negotiationFuture = channel.writeAndFlush(bb(golden, channel));
+
+    // Wait for sending initial request to complete
+    channel.eventLoop().submit(NOOP_RUNNABLE).sync();
+    ArgumentCaptor<Object> objectCaptor = ArgumentCaptor.forClass(Object.class);
+    Mockito.verify(mockHandler)
+        .channelRead(any(ChannelHandlerContext.class), objectCaptor.capture());
+    ByteBuf b = (ByteBuf) objectCaptor.getValue();
+    String request = b.toString(UTF_8);
+    b.release();
+    assertTrue("No trailing newline: " + request, request.endsWith("\r\n\r\n"));
+    assertTrue("No CONNECT: " + request, request.startsWith("CONNECT specialHost:314 "));
+    assertTrue("No host header: " + request, request.contains("host: specialHost:314"));
+
+    assertFalse(negotiationFuture.isDone());
+    serverContext.writeAndFlush(bb("HTTP/1.1 200 OK\r\n\r\n", serverContext.channel())).sync();
+    negotiationFuture.sync();
+
+    channel.eventLoop().submit(NOOP_RUNNABLE).sync();
+    objectCaptor.getAllValues().clear();
+    Mockito.verify(mockHandler, times(2))
+        .channelRead(any(ChannelHandlerContext.class), objectCaptor.capture());
+    b = (ByteBuf) objectCaptor.getAllValues().get(1);
+    // If we were using the real grpcHandler, this would have been the HTTP/2 preface
+    String preface = b.toString(UTF_8);
+    b.release();
+    assertEquals(golden, preface);
+
+    channel.close();
+  }
+
+  @Test
+  public void httpProxy_500() throws Exception {
+    DefaultEventLoopGroup elg = new DefaultEventLoopGroup(1);
+    // ProxyHandler is incompatible with EmbeddedChannel because when channelRegistered() is called
+    // the channel is already active.
+    LocalAddress proxy = new LocalAddress("httpProxy_500");
+    SocketAddress host = InetSocketAddress.createUnresolved("specialHost", 314);
+
+    ChannelInboundHandler mockHandler = mock(ChannelInboundHandler.class);
+    Channel serverChannel = new ServerBootstrap().group(elg).channel(LocalServerChannel.class)
+        .childHandler(mockHandler)
+        .bind(proxy).sync().channel();
+
+    ProtocolNegotiator nego =
+        ProtocolNegotiators.httpProxy(proxy, null, null, ProtocolNegotiators.plaintext());
+    ChannelHandler handler = nego.newHandler(grpcHandler);
+    Channel channel = new Bootstrap().group(elg).channel(LocalChannel.class).handler(handler)
+        .register().sync().channel();
+    pipeline = channel.pipeline();
+    // Wait for initialization to complete
+    channel.eventLoop().submit(NOOP_RUNNABLE).sync();
+    // The grpcHandler must be in the pipeline, but we don't actually want it during our test
+    // because it will consume all events since it is a mock. We only use it because it is required
+    // to construct the Handler.
+    pipeline.remove(grpcHandler);
+    channel.connect(host).sync();
+    serverChannel.close();
+    ArgumentCaptor<ChannelHandlerContext> contextCaptor =
+        ArgumentCaptor.forClass(ChannelHandlerContext.class);
+    Mockito.verify(mockHandler).channelActive(contextCaptor.capture());
+    ChannelHandlerContext serverContext = contextCaptor.getValue();
+
+    final String golden = "isThisThingOn?";
+    ChannelFuture negotiationFuture = channel.writeAndFlush(bb(golden, channel));
+
+    // Wait for sending initial request to complete
+    channel.eventLoop().submit(NOOP_RUNNABLE).sync();
+    ArgumentCaptor<Object> objectCaptor = ArgumentCaptor.forClass(Object.class);
+    Mockito.verify(mockHandler)
+        .channelRead(any(ChannelHandlerContext.class), objectCaptor.capture());
+    ByteBuf request = (ByteBuf) objectCaptor.getValue();
+    request.release();
+
+    assertFalse(negotiationFuture.isDone());
+    String response = "HTTP/1.1 500 OMG\r\nContent-Length: 4\r\n\r\noops";
+    serverContext.writeAndFlush(bb(response, serverContext.channel())).sync();
+    thrown.expect(ProxyConnectException.class);
+    try {
+      negotiationFuture.sync();
+    } finally {
+      channel.close();
+    }
+  }
+
+  private static ByteBuf bb(String s, Channel c) {
+    return ByteBufUtil.writeUtf8(c.alloc(), s);
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/TlsTest.java b/netty/src/test/java/io/grpc/netty/TlsTest.java
new file mode 100644
index 0000000..c2539c9
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/TlsTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Throwables;
+import com.google.common.util.concurrent.MoreExecutors;
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.ServerBuilder;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.internal.testing.TestUtils;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.protobuf.SimpleRequest;
+import io.grpc.testing.protobuf.SimpleResponse;
+import io.grpc.testing.protobuf.SimpleServiceGrpc;
+import io.netty.handler.ssl.ClientAuth;
+import io.netty.handler.ssl.OpenSsl;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.ssl.SslProvider;
+import java.io.File;
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLContext;
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+
+/**
+ * Integration tests for Netty's TLS support.
+ */
+@RunWith(Parameterized.class)
+public class TlsTest {
+
+  public static enum TlsImpl {
+    TCNATIVE, JDK, CONSCRYPT;
+  }
+
+  /**
+   * Iterable of various configurations to use for tests.
+   */
+  @Parameters(name = "{0}")
+  public static Iterable<Object[]> data() {
+    return Arrays.asList(new Object[][] {
+      {TlsImpl.TCNATIVE}, {TlsImpl.JDK}, {TlsImpl.CONSCRYPT},
+    });
+  }
+
+  @Parameter(value = 0)
+  public TlsImpl tlsImpl;
+
+  private ScheduledExecutorService executor;
+  private Server server;
+  private ManagedChannel channel;
+  private SslProvider sslProvider;
+  private Provider jdkProvider;
+  private SslContextBuilder clientContextBuilder;
+
+  @BeforeClass
+  public static void loadConscrypt() {
+    TestUtils.installConscryptIfAvailable();
+  }
+
+  @Before
+  public void setUp() throws NoSuchAlgorithmException {
+    executor = Executors.newSingleThreadScheduledExecutor();
+    switch (tlsImpl) {
+      case TCNATIVE:
+        Assume.assumeTrue(OpenSsl.isAvailable());
+        sslProvider = SslProvider.OPENSSL;
+        break;
+      case JDK:
+        Assume.assumeTrue(Arrays.asList(
+            SSLContext.getDefault().getSupportedSSLParameters().getCipherSuites())
+            .contains("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"));
+        sslProvider = SslProvider.JDK;
+        jdkProvider = Security.getProvider("SunJSSE");
+        Assume.assumeNotNull(jdkProvider);
+        try {
+          // Check for presence of an (ironic) class added in Java 9
+          Class.forName("java.lang.Runtime$Version");
+          // Java 9+
+        } catch (ClassNotFoundException ignored) {
+          // Before Java 9
+          try {
+            GrpcSslContexts.configure(SslContextBuilder.forClient(), jdkProvider);
+          } catch (IllegalArgumentException ex) {
+            Assume.assumeNoException("Not Java 9+ and Jetty ALPN does not seem available", ex);
+          }
+        }
+        break;
+      case CONSCRYPT:
+        sslProvider = SslProvider.JDK;
+        jdkProvider = Security.getProvider("Conscrypt");
+        Assume.assumeNotNull(jdkProvider);
+        break;
+      default:
+        throw new AssertionError();
+    }
+    clientContextBuilder = SslContextBuilder.forClient();
+    if (sslProvider == SslProvider.JDK) {
+      GrpcSslContexts.configure(clientContextBuilder, jdkProvider);
+    } else {
+      GrpcSslContexts.configure(clientContextBuilder, sslProvider);
+    }
+  }
+
+  @After
+  public void tearDown() {
+    if (server != null) {
+      server.shutdown();
+    }
+    if (channel != null) {
+      channel.shutdown();
+    }
+    MoreExecutors.shutdownAndAwaitTermination(executor, 5, TimeUnit.SECONDS);
+  }
+
+
+  /**
+   * Tests that a client and a server configured using GrpcSslContexts can successfully
+   * communicate with each other.
+   */
+  @Test
+  public void basicClientServerIntegrationTest() throws Exception {
+    // Create & start a server.
+    File serverCertFile = TestUtils.loadCert("server1.pem");
+    File serverPrivateKeyFile = TestUtils.loadCert("server1.key");
+    X509Certificate[] serverTrustedCaCerts = {
+      TestUtils.loadX509Cert("ca.pem")
+    };
+    server = serverBuilder(0, serverCertFile, serverPrivateKeyFile, serverTrustedCaCerts)
+        .addService(new SimpleServiceImpl())
+        .build()
+        .start();
+
+    // Create a client.
+    File clientCertChainFile = TestUtils.loadCert("client.pem");
+    File clientPrivateKeyFile = TestUtils.loadCert("client.key");
+    X509Certificate[] clientTrustedCaCerts = {
+      TestUtils.loadX509Cert("ca.pem")
+    };
+    channel = clientChannel(server.getPort(), clientContextBuilder
+        .keyManager(clientCertChainFile, clientPrivateKeyFile)
+        .trustManager(clientTrustedCaCerts)
+        .build());
+    SimpleServiceGrpc.SimpleServiceBlockingStub client = SimpleServiceGrpc.newBlockingStub(channel);
+
+    // Send an actual request, via the full GRPC & network stack, and check that a proper
+    // response comes back.
+    client.unaryRpc(SimpleRequest.getDefaultInstance());
+  }
+
+  /**
+   * Tests that a server configured to require client authentication refuses to accept connections
+   * from a client that has an untrusted certificate.
+   */
+  @Test
+  public void serverRejectsUntrustedClientCert() throws Exception {
+    // Create & start a server. It requires client authentication and trusts only the test CA.
+    File serverCertFile = TestUtils.loadCert("server1.pem");
+    File serverPrivateKeyFile = TestUtils.loadCert("server1.key");
+    X509Certificate[] serverTrustedCaCerts = {
+      TestUtils.loadX509Cert("ca.pem")
+    };
+    server = serverBuilder(0, serverCertFile, serverPrivateKeyFile, serverTrustedCaCerts)
+        .addService(new SimpleServiceImpl())
+        .build()
+        .start();
+
+    // Create a client. Its credentials come from a CA that the server does not trust. The client
+    // trusts both test CAs, so we can be sure that the handshake failure is due to the server
+    // rejecting the client's cert, not the client rejecting the server's cert.
+    File clientCertChainFile = TestUtils.loadCert("badclient.pem");
+    File clientPrivateKeyFile = TestUtils.loadCert("badclient.key");
+    X509Certificate[] clientTrustedCaCerts = {
+      TestUtils.loadX509Cert("ca.pem")
+    };
+    channel = clientChannel(server.getPort(), clientContextBuilder
+        .keyManager(clientCertChainFile, clientPrivateKeyFile)
+        .trustManager(clientTrustedCaCerts)
+        .build());
+    SimpleServiceGrpc.SimpleServiceBlockingStub client = SimpleServiceGrpc.newBlockingStub(channel);
+
+    // Check that the TLS handshake fails.
+    try {
+      client.unaryRpc(SimpleRequest.getDefaultInstance());
+      fail("TLS handshake should have failed, but didn't; received RPC response");
+    } catch (StatusRuntimeException e) {
+      // GRPC reports this situation by throwing a StatusRuntimeException that wraps either a
+      // javax.net.ssl.SSLHandshakeException or a java.nio.channels.ClosedChannelException.
+      // Thus, reliably detecting the underlying cause is not feasible.
+      assertEquals(
+          Throwables.getStackTraceAsString(e),
+          Status.Code.UNAVAILABLE, e.getStatus().getCode());
+    }
+  }
+
+
+  /**
+   * Tests that a server configured to require client authentication actually does require client
+   * authentication.
+   */
+  @Test
+  public void noClientAuthFailure() throws Exception {
+    // Create & start a server.
+    File serverCertFile = TestUtils.loadCert("server1.pem");
+    File serverPrivateKeyFile = TestUtils.loadCert("server1.key");
+    X509Certificate[] serverTrustedCaCerts = {
+      TestUtils.loadX509Cert("ca.pem")
+    };
+    server = serverBuilder(0, serverCertFile, serverPrivateKeyFile, serverTrustedCaCerts)
+        .addService(new SimpleServiceImpl())
+        .build()
+        .start();
+
+    // Create a client. It has no credentials.
+    X509Certificate[] clientTrustedCaCerts = {
+      TestUtils.loadX509Cert("ca.pem")
+    };
+    channel = clientChannel(server.getPort(), clientContextBuilder
+        .trustManager(clientTrustedCaCerts)
+        .build());
+    SimpleServiceGrpc.SimpleServiceBlockingStub client = SimpleServiceGrpc.newBlockingStub(channel);
+
+    // Check that the TLS handshake fails.
+    try {
+      client.unaryRpc(SimpleRequest.getDefaultInstance());
+      fail("TLS handshake should have failed, but didn't; received RPC response");
+    } catch (StatusRuntimeException e) {
+      // GRPC reports this situation by throwing a StatusRuntimeException that wraps either a
+      // javax.net.ssl.SSLHandshakeException or a java.nio.channels.ClosedChannelException.
+      // Thus, reliably detecting the underlying cause is not feasible.
+      assertEquals(
+          Throwables.getStackTraceAsString(e),
+          Status.Code.UNAVAILABLE, e.getStatus().getCode());
+    }
+  }
+
+
+  /**
+   * Tests that a client configured using GrpcSslContexts refuses to talk to a server that has an
+   * an untrusted certificate.
+   */
+  @Test
+  public void clientRejectsUntrustedServerCert() throws Exception {
+    // Create & start a server.
+    File serverCertFile = TestUtils.loadCert("badserver.pem");
+    File serverPrivateKeyFile = TestUtils.loadCert("badserver.key");
+    X509Certificate[] serverTrustedCaCerts = {
+      TestUtils.loadX509Cert("ca.pem")
+    };
+    server = serverBuilder(0, serverCertFile, serverPrivateKeyFile, serverTrustedCaCerts)
+        .addService(new SimpleServiceImpl())
+        .build()
+        .start();
+
+    // Create a client.
+    File clientCertChainFile = TestUtils.loadCert("client.pem");
+    File clientPrivateKeyFile = TestUtils.loadCert("client.key");
+    X509Certificate[] clientTrustedCaCerts = {
+      TestUtils.loadX509Cert("ca.pem")
+    };
+    channel = clientChannel(server.getPort(), clientContextBuilder
+        .keyManager(clientCertChainFile, clientPrivateKeyFile)
+        .trustManager(clientTrustedCaCerts)
+        .build());
+    SimpleServiceGrpc.SimpleServiceBlockingStub client = SimpleServiceGrpc.newBlockingStub(channel);
+
+    // Check that the TLS handshake fails.
+    try {
+      client.unaryRpc(SimpleRequest.getDefaultInstance());
+      fail("TLS handshake should have failed, but didn't; received RPC response");
+    } catch (StatusRuntimeException e) {
+      // GRPC reports this situation by throwing a StatusRuntimeException that wraps either a
+      // javax.net.ssl.SSLHandshakeException or a java.nio.channels.ClosedChannelException.
+      // Thus, reliably detecting the underlying cause is not feasible.
+      // TODO(carl-mastrangelo): eventually replace this with a hamcrest matcher.
+      assertEquals(
+          Throwables.getStackTraceAsString(e),
+          Status.Code.UNAVAILABLE, e.getStatus().getCode());
+    }
+  }
+
+
+  private ServerBuilder<?> serverBuilder(int port, File serverCertChainFile,
+      File serverPrivateKeyFile, X509Certificate[] serverTrustedCaCerts) throws IOException {
+    SslContextBuilder sslContextBuilder
+        = SslContextBuilder.forServer(serverCertChainFile, serverPrivateKeyFile);
+    if (sslProvider == SslProvider.JDK) {
+      GrpcSslContexts.configure(sslContextBuilder, jdkProvider);
+    } else {
+      GrpcSslContexts.configure(sslContextBuilder, sslProvider);
+    }
+    sslContextBuilder.trustManager(serverTrustedCaCerts)
+        .clientAuth(ClientAuth.REQUIRE);
+
+    return NettyServerBuilder.forPort(port)
+        .sslContext(sslContextBuilder.build());
+  }
+
+
+  private static ManagedChannel clientChannel(int port, SslContext sslContext) throws IOException {
+    return NettyChannelBuilder.forAddress("localhost", port)
+        .overrideAuthority(TestUtils.TEST_SERVER_HOST)
+        .negotiationType(NegotiationType.TLS)
+        .sslContext(sslContext)
+        .build();
+  }
+
+  private static class SimpleServiceImpl extends SimpleServiceGrpc.SimpleServiceImplBase {
+    @Override
+    public void unaryRpc(SimpleRequest req, StreamObserver<SimpleResponse> respOb) {
+      respOb.onNext(SimpleResponse.getDefaultInstance());
+      respOb.onCompleted();
+    }
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/UtilsTest.java b/netty/src/test/java/io/grpc/netty/UtilsTest.java
new file mode 100644
index 0000000..4c4bdb9
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/UtilsTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.truth.Truth;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.SocketOptions;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.internal.GrpcUtil;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ConnectTimeoutException;
+import io.netty.channel.WriteBufferWaterMark;
+import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.channel.socket.oio.OioSocketChannel;
+import io.netty.handler.codec.http2.DefaultHttp2Headers;
+import io.netty.handler.codec.http2.Http2Error;
+import io.netty.handler.codec.http2.Http2Exception;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.util.AsciiString;
+import java.util.Map;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link Utils}. */
+@RunWith(JUnit4.class)
+public class UtilsTest {
+  private final Metadata.Key<String> userKey =
+      Metadata.Key.of("user-key", Metadata.ASCII_STRING_MARSHALLER);
+  private final String userValue =  "user-value";
+
+  @Test
+  public void testStatusFromThrowable() {
+    Status s = Status.CANCELLED.withDescription("msg");
+    assertSame(s, Utils.statusFromThrowable(new Exception(s.asException())));
+    Throwable t;
+    t = new ConnectTimeoutException("msg");
+    assertStatusEquals(Status.UNAVAILABLE.withCause(t), Utils.statusFromThrowable(t));
+    t = new Http2Exception(Http2Error.INTERNAL_ERROR, "msg");
+    assertStatusEquals(Status.INTERNAL.withCause(t), Utils.statusFromThrowable(t));
+    t = new Exception("msg");
+    assertStatusEquals(Status.UNKNOWN.withCause(t), Utils.statusFromThrowable(t));
+  }
+
+  @Test
+  public void convertClientHeaders_sanitizes() {
+    Metadata metaData = new Metadata();
+
+    // Intentionally being explicit here rather than relying on any pre-defined lists of headers,
+    // since the goal of this test is to validate the correctness of such lists in the first place.
+    metaData.put(GrpcUtil.CONTENT_TYPE_KEY, "to-be-removed");
+    metaData.put(GrpcUtil.USER_AGENT_KEY, "to-be-removed");
+    metaData.put(GrpcUtil.TE_HEADER, "to-be-removed");
+    metaData.put(userKey, userValue);
+
+    String scheme = "https";
+    String userAgent = "user-agent";
+    String method = "POST";
+    String authority = "authority";
+    String path = "//testService/test";
+
+    Http2Headers output =
+        Utils.convertClientHeaders(
+            metaData,
+            new AsciiString(scheme),
+            new AsciiString(path),
+            new AsciiString(authority),
+            new AsciiString(method),
+            new AsciiString(userAgent));
+    DefaultHttp2Headers headers = new DefaultHttp2Headers();
+    for (Map.Entry<CharSequence, CharSequence> entry : output) {
+      headers.add(entry.getKey(), entry.getValue());
+    }
+
+    // 7 reserved headers, 1 user header
+    assertEquals(7 + 1, headers.size());
+    // Check the 3 reserved headers that are non pseudo
+    // Users can not create pseudo headers keys so no need to check for them here
+    assertEquals(GrpcUtil.CONTENT_TYPE_GRPC,
+        headers.get(GrpcUtil.CONTENT_TYPE_KEY.name()).toString());
+    assertEquals(userAgent, headers.get(GrpcUtil.USER_AGENT_KEY.name()).toString());
+    assertEquals(GrpcUtil.TE_TRAILERS, headers.get(GrpcUtil.TE_HEADER.name()).toString());
+    // Check the user header is in tact
+    assertEquals(userValue, headers.get(userKey.name()).toString());
+  }
+
+  @Test
+  public void convertServerHeaders_sanitizes() {
+    Metadata metaData = new Metadata();
+
+    // Intentionally being explicit here rather than relying on any pre-defined lists of headers,
+    // since the goal of this test is to validate the correctness of such lists in the first place.
+    metaData.put(GrpcUtil.CONTENT_TYPE_KEY, "to-be-removed");
+    metaData.put(GrpcUtil.TE_HEADER, "to-be-removed");
+    metaData.put(GrpcUtil.USER_AGENT_KEY, "to-be-removed");
+    metaData.put(userKey, userValue);
+
+    Http2Headers output = Utils.convertServerHeaders(metaData);
+    DefaultHttp2Headers headers = new DefaultHttp2Headers();
+    for (Map.Entry<CharSequence, CharSequence> entry : output) {
+      headers.add(entry.getKey(), entry.getValue());
+    }
+    // 2 reserved headers, 1 user header
+    assertEquals(2 + 1, headers.size());
+    assertEquals(Utils.CONTENT_TYPE_GRPC, headers.get(GrpcUtil.CONTENT_TYPE_KEY.name()));
+  }
+
+  @Test
+  public void channelOptionsTest_noLinger() {
+    Channel channel = new EmbeddedChannel();
+    assertNull(channel.config().getOption(ChannelOption.SO_LINGER));
+    InternalChannelz.SocketOptions socketOptions = Utils.getSocketOptions(channel);
+    assertNull(socketOptions.lingerSeconds);
+  }
+
+  @Test
+  public void channelOptionsTest_oio() {
+    Channel channel = new OioSocketChannel();
+    SocketOptions socketOptions = setAndValidateGeneric(channel);
+    assertEquals(250, (int) socketOptions.soTimeoutMillis);
+  }
+
+  @Test
+  public void channelOptionsTest_nio() {
+    Channel channel = new NioSocketChannel();
+    SocketOptions socketOptions = setAndValidateGeneric(channel);
+    assertNull(socketOptions.soTimeoutMillis);
+  }
+
+  private static InternalChannelz.SocketOptions setAndValidateGeneric(Channel channel) {
+    channel.config().setOption(ChannelOption.SO_LINGER, 3);
+    // only applicable for OIO channels:
+    channel.config().setOption(ChannelOption.SO_TIMEOUT, 250);
+    // Test some arbitrarily chosen options with a non numeric values
+    channel.config().setOption(ChannelOption.SO_KEEPALIVE, true);
+    WriteBufferWaterMark writeBufWaterMark = new WriteBufferWaterMark(10, 20);
+    channel.config().setOption(ChannelOption.WRITE_BUFFER_WATER_MARK, writeBufWaterMark);
+
+    InternalChannelz.SocketOptions socketOptions = Utils.getSocketOptions(channel);
+    assertEquals(3, (int) socketOptions.lingerSeconds);
+    assertEquals("true", socketOptions.others.get("SO_KEEPALIVE"));
+    assertEquals(
+        writeBufWaterMark.toString(),
+        socketOptions.others.get(ChannelOption.WRITE_BUFFER_WATER_MARK.toString()));
+    return socketOptions;
+  }
+
+  private static void assertStatusEquals(Status expected, Status actual) {
+    assertEquals(expected.getCode(), actual.getCode());
+    Truth.assertThat(MoreObjects.firstNonNull(actual.getDescription(), ""))
+        .contains(MoreObjects.firstNonNull(expected.getDescription(), ""));
+    assertEquals(expected.getCause(), actual.getCause());
+  }
+}
diff --git a/netty/src/test/java/io/grpc/netty/WriteQueueTest.java b/netty/src/test/java/io/grpc/netty/WriteQueueTest.java
new file mode 100644
index 0000000..e275f8b
--- /dev/null
+++ b/netty/src/test/java/io/grpc/netty/WriteQueueTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.netty;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import io.grpc.netty.WriteQueue.QueuedCommand;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.EventLoop;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+@RunWith(JUnit4.class)
+public class WriteQueueTest {
+
+  @Rule
+  public final Timeout globalTimeout = Timeout.seconds(10);
+
+  private final Object lock = new Object();
+
+  @Mock
+  public Channel channel;
+
+  @Mock
+  public ChannelPromise promise;
+
+  private long writeCalledNanos;
+  private long flushCalledNanos = writeCalledNanos;
+
+  /**
+   * Set up for test.
+   */
+  @Before
+  public void setUp() throws Exception {
+    MockitoAnnotations.initMocks(this);
+    when(channel.newPromise()).thenReturn(promise);
+
+    EventLoop eventLoop = Mockito.mock(EventLoop.class);
+    doAnswer(new Answer<Void>() {
+      @Override
+      public Void answer(InvocationOnMock invocation) throws Throwable {
+        Runnable r = (Runnable) invocation.getArguments()[0];
+        r.run();
+        return null;
+      }
+    }).when(eventLoop).execute(any(Runnable.class));
+    when(eventLoop.inEventLoop()).thenReturn(true);
+    when(channel.eventLoop()).thenReturn(eventLoop);
+
+    when(channel.flush()).thenAnswer(new Answer<Channel>() {
+      @Override
+      public Channel answer(InvocationOnMock invocation) throws Throwable {
+        synchronized (lock) {
+          flushCalledNanos = System.nanoTime();
+          if (flushCalledNanos == writeCalledNanos) {
+            flushCalledNanos += 1;
+          }
+        }
+        return channel;
+      }
+    });
+
+    when(channel.write(any(QueuedCommand.class), eq(promise))).thenAnswer(
+        new Answer<ChannelFuture>() {
+          @Override
+          public ChannelFuture answer(InvocationOnMock invocation) throws Throwable {
+            synchronized (lock) {
+              writeCalledNanos = System.nanoTime();
+              if (writeCalledNanos == flushCalledNanos) {
+                writeCalledNanos += 1;
+              }
+            }
+            return promise;
+          }
+        });
+  }
+
+  @Test
+  public void singleWriteShouldWork() {
+    WriteQueue queue = new WriteQueue(channel);
+    queue.enqueue(new CuteCommand(), true);
+
+    verify(channel).write(isA(QueuedCommand.class), eq(promise));
+    verify(channel).flush();
+  }
+
+  @Test
+  public void multipleWritesShouldBeBatched() {
+    WriteQueue queue = new WriteQueue(channel);
+    for (int i = 0; i < 5; i++) {
+      queue.enqueue(new CuteCommand(), false);
+    }
+    queue.scheduleFlush();
+
+    verify(channel, times(5)).write(isA(QueuedCommand.class), eq(promise));
+    verify(channel).flush();
+  }
+
+  @Test
+  public void maxWritesBeforeFlushShouldBeEnforced() {
+    WriteQueue queue = new WriteQueue(channel);
+    int writes = WriteQueue.DEQUE_CHUNK_SIZE + 10;
+    for (int i = 0; i < writes; i++) {
+      queue.enqueue(new CuteCommand(), false);
+    }
+    queue.scheduleFlush();
+
+    verify(channel, times(writes)).write(isA(QueuedCommand.class), eq(promise));
+    verify(channel, times(2)).flush();
+  }
+
+  @Test
+  public void concurrentWriteAndFlush() throws Throwable {
+    final WriteQueue queue = new WriteQueue(channel);
+    final CountDownLatch flusherStarted = new CountDownLatch(1);
+    final AtomicBoolean doneWriting = new AtomicBoolean();
+    Thread flusher = new Thread(new Runnable() {
+      @Override
+      public void run() {
+        flusherStarted.countDown();
+        while (!doneWriting.get()) {
+          queue.scheduleFlush();
+          assertFlushCalledAfterWrites();
+        }
+        // No more writes, so this flush should drain all writes from the queue
+        queue.scheduleFlush();
+        assertFlushCalledAfterWrites();
+      }
+
+      void assertFlushCalledAfterWrites() {
+        synchronized (lock) {
+          if (flushCalledNanos - writeCalledNanos <= 0) {
+            fail("flush must be called after all writes");
+          }
+        }
+      }
+    });
+
+    class ExceptionHandler implements Thread.UncaughtExceptionHandler {
+      private Throwable throwable;
+
+      @Override
+      public void uncaughtException(Thread t, Throwable e) {
+        throwable = e;
+      }
+
+      void checkException() throws Throwable {
+        if (throwable != null) {
+          throw throwable;
+        }
+      }
+    }
+
+    ExceptionHandler exHandler = new ExceptionHandler();
+    flusher.setUncaughtExceptionHandler(exHandler);
+
+    flusher.start();
+    flusherStarted.await();
+    int writes = 10 * WriteQueue.DEQUE_CHUNK_SIZE;
+    for (int i = 0; i < writes; i++) {
+      queue.enqueue(new CuteCommand(), false);
+    }
+    doneWriting.set(true);
+    flusher.join();
+
+    exHandler.checkException();
+    verify(channel, times(writes)).write(isA(CuteCommand.class), eq(promise));
+  }
+
+  static class CuteCommand extends WriteQueue.AbstractQueuedCommand {
+
+  }
+}
diff --git a/okhttp/BUILD.bazel b/okhttp/BUILD.bazel
new file mode 100644
index 0000000..62a7aff
--- /dev/null
+++ b/okhttp/BUILD.bazel
@@ -0,0 +1,19 @@
+java_library(
+    name = "okhttp",
+    srcs = glob([
+        "third_party/okhttp/main/java/**/*.java",
+        "src/main/java/**/*.java",
+    ]),
+    resources = glob([
+        "src/main/resources/**",
+    ]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//core",
+        "//core:internal",
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_guava_guava//jar",
+        "@com_squareup_okhttp_okhttp//jar",
+        "@com_squareup_okio_okio//jar",
+    ],
+)
diff --git a/okhttp/build.gradle b/okhttp/build.gradle
new file mode 100644
index 0000000..b35e10b
--- /dev/null
+++ b/okhttp/build.gradle
@@ -0,0 +1,24 @@
+description = "gRPC: OkHttp"
+
+dependencies {
+    compile project(':grpc-core'),
+            libraries.okhttp,
+            libraries.okio
+
+    // Tests depend on base class defined by core module.
+    testCompile project(':grpc-core').sourceSets.test.output,
+            project(':grpc-testing'),
+            project(':grpc-netty')
+    signature "org.codehaus.mojo.signature:java17:1.0@signature"
+    signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
+}
+
+project.sourceSets {
+    main { java { srcDir "${projectDir}/third_party/okhttp/main/java" } }
+    test { java { srcDir "${projectDir}/third_party/okhttp/test/java" } }
+}
+
+checkstyleMain.exclude '**/io/grpc/okhttp/internal/**'
+
+javadoc.exclude 'io/grpc/okhttp/internal/**'
+javadoc.options.links 'http://square.github.io/okhttp/2.x/okhttp/'
diff --git a/okhttp/src/main/java/io/grpc/okhttp/AsyncFrameWriter.java b/okhttp/src/main/java/io/grpc/okhttp/AsyncFrameWriter.java
new file mode 100644
index 0000000..3049c5b
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/AsyncFrameWriter.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import com.google.common.base.Preconditions;
+import io.grpc.internal.SerializingExecutor;
+import io.grpc.okhttp.internal.framed.ErrorCode;
+import io.grpc.okhttp.internal.framed.FrameWriter;
+import io.grpc.okhttp.internal.framed.Header;
+import io.grpc.okhttp.internal.framed.Settings;
+import java.io.IOException;
+import java.net.Socket;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import okio.Buffer;
+
+class AsyncFrameWriter implements FrameWriter {
+  private static final Logger log = Logger.getLogger(OkHttpClientTransport.class.getName());
+  private FrameWriter frameWriter;
+  private Socket socket;
+  // Although writes are thread-safe, we serialize them to prevent consuming many Threads that are
+  // just waiting on each other.
+  private final SerializingExecutor executor;
+  private final TransportExceptionHandler transportExceptionHandler;
+  private final AtomicLong flushCounter = new AtomicLong();
+
+  public AsyncFrameWriter(
+      TransportExceptionHandler transportExceptionHandler, SerializingExecutor executor) {
+    this.transportExceptionHandler = transportExceptionHandler;
+    this.executor = executor;
+  }
+
+  /**
+   * Set the real frameWriter and the corresponding underlying socket, the socket is needed for
+   * closing.
+   *
+   * <p>should only be called by thread of executor.
+   */
+  void becomeConnected(FrameWriter frameWriter, Socket socket) {
+    Preconditions.checkState(this.frameWriter == null,
+        "AsyncFrameWriter's setFrameWriter() should only be called once.");
+    this.frameWriter = Preconditions.checkNotNull(frameWriter, "frameWriter");
+    this.socket = Preconditions.checkNotNull(socket, "socket");
+  }
+
+  @Override
+  public void connectionPreface() {
+    executor.execute(new WriteRunnable() {
+      @Override
+      public void doRun() throws IOException {
+        frameWriter.connectionPreface();
+      }
+    });
+  }
+
+  @Override
+  public void ackSettings(final Settings peerSettings) {
+    executor.execute(new WriteRunnable() {
+      @Override
+      public void doRun() throws IOException {
+        frameWriter.ackSettings(peerSettings);
+      }
+    });
+  }
+
+  @Override
+  public void pushPromise(final int streamId, final int promisedStreamId,
+      final List<Header> requestHeaders) {
+    executor.execute(new WriteRunnable() {
+      @Override
+      public void doRun() throws IOException {
+        frameWriter.pushPromise(streamId, promisedStreamId, requestHeaders);
+      }
+    });
+  }
+
+  @Override
+  public void flush() {
+    // keep track of version of flushes to skip flush if another flush task is queued.
+    final long flushCount = flushCounter.incrementAndGet();
+
+    executor.execute(new WriteRunnable() {
+      @Override
+      public void doRun() throws IOException {
+        // There can be a flush starvation if there are continuous flood of flush is queued, this
+        // is not an issue with OkHttp since it flushes if the buffer is full.
+        if (flushCounter.get() == flushCount) {
+          frameWriter.flush();
+        }
+      }
+    });
+  }
+
+  @Override
+  public void synStream(final boolean outFinished, final boolean inFinished, final int streamId,
+      final int associatedStreamId, final List<Header> headerBlock) {
+    executor.execute(new WriteRunnable() {
+      @Override
+      public void doRun() throws IOException {
+        frameWriter.synStream(outFinished, inFinished, streamId, associatedStreamId, headerBlock);
+      }
+    });
+  }
+
+  @Override
+  public void synReply(final boolean outFinished, final int streamId,
+      final List<Header> headerBlock) {
+    executor.execute(new WriteRunnable() {
+      @Override
+      public void doRun() throws IOException {
+        frameWriter.synReply(outFinished, streamId, headerBlock);
+      }
+    });
+  }
+
+  @Override
+  public void headers(final int streamId, final List<Header> headerBlock) {
+    executor.execute(new WriteRunnable() {
+      @Override
+      public void doRun() throws IOException {
+        frameWriter.headers(streamId, headerBlock);
+      }
+    });
+  }
+
+  @Override
+  public void rstStream(final int streamId, final ErrorCode errorCode) {
+    executor.execute(new WriteRunnable() {
+      @Override
+      public void doRun() throws IOException {
+        frameWriter.rstStream(streamId, errorCode);
+      }
+    });
+  }
+
+  @Override
+  public void data(final boolean outFinished, final int streamId, final Buffer source,
+      final int byteCount) {
+    executor.execute(new WriteRunnable() {
+      @Override
+      public void doRun() throws IOException {
+        frameWriter.data(outFinished, streamId, source, byteCount);
+      }
+    });
+  }
+
+  @Override
+  public void settings(final Settings okHttpSettings) {
+    executor.execute(new WriteRunnable() {
+      @Override
+      public void doRun() throws IOException {
+        frameWriter.settings(okHttpSettings);
+      }
+    });
+  }
+
+  @Override
+  public void ping(final boolean ack, final int payload1, final int payload2) {
+    executor.execute(new WriteRunnable() {
+      @Override
+      public void doRun() throws IOException {
+        frameWriter.ping(ack, payload1, payload2);
+      }
+    });
+  }
+
+  @Override
+  public void goAway(final int lastGoodStreamId, final ErrorCode errorCode,
+      final byte[] debugData) {
+    executor.execute(new WriteRunnable() {
+      @Override
+      public void doRun() throws IOException {
+        frameWriter.goAway(lastGoodStreamId, errorCode, debugData);
+        // Flush it since after goAway, we are likely to close this writer.
+        frameWriter.flush();
+      }
+    });
+  }
+
+  @Override
+  public void windowUpdate(final int streamId, final long windowSizeIncrement) {
+    executor.execute(new WriteRunnable() {
+      @Override
+      public void doRun() throws IOException {
+        frameWriter.windowUpdate(streamId, windowSizeIncrement);
+      }
+    });
+  }
+
+  @Override
+  public void close() {
+    executor.execute(new Runnable() {
+      @Override
+      public void run() {
+        if (frameWriter != null) {
+          try {
+            frameWriter.close();
+            socket.close();
+          } catch (IOException e) {
+            log.log(Level.WARNING, "Failed closing connection", e);
+          }
+        }
+      }
+    });
+  }
+
+  private abstract class WriteRunnable implements Runnable {
+    @Override
+    public final void run() {
+      try {
+        if (frameWriter == null) {
+          throw new IOException("Unable to perform write due to unavailable frameWriter.");
+        }
+        doRun();
+      } catch (RuntimeException e) {
+        transportExceptionHandler.onException(e);
+      } catch (Exception e) {
+        transportExceptionHandler.onException(e);
+      }
+    }
+
+    public abstract void doRun() throws IOException;
+  }
+
+  @Override
+  public int maxDataLength() {
+    return frameWriter == null ? 0x4000 /* 16384, the minimum required by the HTTP/2 spec */
+        : frameWriter.maxDataLength();
+  }
+
+  /** A class that handles transport exception. */
+  interface TransportExceptionHandler {
+
+    /** Handles exception. */
+    void onException(Throwable throwable);
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/Headers.java b/okhttp/src/main/java/io/grpc/okhttp/Headers.java
new file mode 100644
index 0000000..cb71846
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/Headers.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static io.grpc.internal.GrpcUtil.CONTENT_TYPE_KEY;
+import static io.grpc.internal.GrpcUtil.USER_AGENT_KEY;
+
+import com.google.common.base.Preconditions;
+import io.grpc.InternalMetadata;
+import io.grpc.Metadata;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.TransportFrameUtil;
+import io.grpc.okhttp.internal.framed.Header;
+import java.util.ArrayList;
+import java.util.List;
+import okio.ByteString;
+
+/**
+ * Constants for request/response headers.
+ */
+class Headers {
+
+  public static final Header SCHEME_HEADER = new Header(Header.TARGET_SCHEME, "https");
+  public static final Header METHOD_HEADER = new Header(Header.TARGET_METHOD, GrpcUtil.HTTP_METHOD);
+  public static final Header METHOD_GET_HEADER = new Header(Header.TARGET_METHOD, "GET");
+  public static final Header CONTENT_TYPE_HEADER =
+      new Header(CONTENT_TYPE_KEY.name(), GrpcUtil.CONTENT_TYPE_GRPC);
+  public static final Header TE_HEADER = new Header("te", GrpcUtil.TE_TRAILERS);
+
+  /**
+   * Serializes the given headers and creates a list of OkHttp {@link Header}s to be used when
+   * creating a stream. Since this serializes the headers, this method should be called in the
+   * application thread context.
+   */
+  public static List<Header> createRequestHeaders(
+      Metadata headers, String defaultPath, String authority, String userAgent, boolean useGet) {
+    Preconditions.checkNotNull(headers, "headers");
+    Preconditions.checkNotNull(defaultPath, "defaultPath");
+    Preconditions.checkNotNull(authority, "authority");
+
+    // Discard any application supplied duplicates of the reserved headers
+    headers.discardAll(GrpcUtil.CONTENT_TYPE_KEY);
+    headers.discardAll(GrpcUtil.TE_HEADER);
+    headers.discardAll(GrpcUtil.USER_AGENT_KEY);
+
+    // 7 is the number of explicit add calls below.
+    List<Header> okhttpHeaders = new ArrayList<>(7 + InternalMetadata.headerCount(headers));
+
+    // Set GRPC-specific headers.
+    okhttpHeaders.add(SCHEME_HEADER);
+    if (useGet) {
+      okhttpHeaders.add(METHOD_GET_HEADER);
+    } else {
+      okhttpHeaders.add(METHOD_HEADER);
+    }
+
+    okhttpHeaders.add(new Header(Header.TARGET_AUTHORITY, authority));
+    String path = defaultPath;
+    okhttpHeaders.add(new Header(Header.TARGET_PATH, path));
+
+    okhttpHeaders.add(new Header(GrpcUtil.USER_AGENT_KEY.name(), userAgent));
+
+    // All non-pseudo headers must come after pseudo headers.
+    okhttpHeaders.add(CONTENT_TYPE_HEADER);
+    okhttpHeaders.add(TE_HEADER);
+
+    // Now add any application-provided headers.
+    byte[][] serializedHeaders = TransportFrameUtil.toHttp2Headers(headers);
+    for (int i = 0; i < serializedHeaders.length; i += 2) {
+      ByteString key = ByteString.of(serializedHeaders[i]);
+      String keyString = key.utf8();
+      if (isApplicationHeader(keyString)) {
+        ByteString value = ByteString.of(serializedHeaders[i + 1]);
+        okhttpHeaders.add(new Header(key, value));
+      }
+    }
+
+    return okhttpHeaders;
+  }
+
+  /**
+   * Returns {@code true} if the given header is an application-provided header. Otherwise, returns
+   * {@code false} if the header is reserved by GRPC.
+   */
+  private static boolean isApplicationHeader(String key) {
+    // Don't allow HTTP/2 pseudo headers or content-type to be added by the application.
+    return (!key.startsWith(":")
+            && !CONTENT_TYPE_KEY.name().equalsIgnoreCase(key))
+            && !USER_AGENT_KEY.name().equalsIgnoreCase(key);
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/NegotiationType.java b/okhttp/src/main/java/io/grpc/okhttp/NegotiationType.java
new file mode 100644
index 0000000..ebd8fd9
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/NegotiationType.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+/**
+ * Identifies the negotiation used for starting up HTTP/2.
+ *
+ * @deprecated use {@link OkHttpChannelBuilder#usePlaintext()} or {@link
+ *     OkHttpChannelBuilder#useTransportSecurity()} directly rather than {@link
+ *     OkHttpChannelBuilder#negotiationType(NegotiationType)}.
+ */
+@Deprecated
+public enum NegotiationType {
+  /**
+   * Uses TLS ALPN/NPN negotiation, assumes an SSL connection.
+   */
+  TLS,
+
+  /**
+   * Just assume the connection is plaintext (non-SSL) and the remote endpoint supports HTTP/2
+   * directly without an upgrade.
+   */
+  PLAINTEXT
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java
new file mode 100644
index 0000000..1a986f9
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelBuilder.java
@@ -0,0 +1,571 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
+import static io.grpc.internal.GrpcUtil.DEFAULT_KEEPALIVE_TIME_NANOS;
+import static io.grpc.internal.GrpcUtil.KEEPALIVE_TIME_NANOS_DISABLED;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import io.grpc.Attributes;
+import io.grpc.ExperimentalApi;
+import io.grpc.Internal;
+import io.grpc.NameResolver;
+import io.grpc.internal.AbstractManagedChannelImplBuilder;
+import io.grpc.internal.AtomicBackoff;
+import io.grpc.internal.ClientTransportFactory;
+import io.grpc.internal.ConnectionClientTransport;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.KeepAliveManager;
+import io.grpc.internal.SharedResourceHolder;
+import io.grpc.internal.SharedResourceHolder.Resource;
+import io.grpc.internal.TransportTracer;
+import io.grpc.okhttp.internal.CipherSuite;
+import io.grpc.okhttp.internal.ConnectionSpec;
+import io.grpc.okhttp.internal.Platform;
+import io.grpc.okhttp.internal.TlsVersion;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.SecureRandom;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+/** Convenience class for building channels with the OkHttp transport. */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1785")
+public class OkHttpChannelBuilder extends
+        AbstractManagedChannelImplBuilder<OkHttpChannelBuilder> {
+
+  /** Identifies the negotiation used for starting up HTTP/2. */
+  private enum NegotiationType {
+    /** Uses TLS ALPN/NPN negotiation, assumes an SSL connection. */
+    TLS,
+
+    /**
+     * Just assume the connection is plaintext (non-SSL) and the remote endpoint supports HTTP/2
+     * directly without an upgrade.
+     */
+    PLAINTEXT
+  }
+
+  /**
+   * ConnectionSpec closely matching the default configuration that could be used as a basis for
+   * modification.
+   *
+   * <p>Since this field is the only reference in gRPC to ConnectionSpec that may not be ProGuarded,
+   * we are removing the field to reduce method count. We've been unable to find any existing users
+   * of the field, and any such user would highly likely at least be changing the cipher suites,
+   * which is sort of the only part that's non-obvious. Any existing user should instead create
+   * their own spec from scratch or base it off ConnectionSpec.MODERN_TLS if believed to be
+   * necessary. If this was providing you with value and don't want to see it removed, open a GitHub
+   * issue to discuss keeping it.
+   *
+   * @deprecated Deemed of little benefit and users weren't using it. Just define one yourself
+   */
+  @Deprecated
+  public static final com.squareup.okhttp.ConnectionSpec DEFAULT_CONNECTION_SPEC =
+      new com.squareup.okhttp.ConnectionSpec.Builder(com.squareup.okhttp.ConnectionSpec.MODERN_TLS)
+          .cipherSuites(
+              // The following items should be sync with Netty's Http2SecurityUtil.CIPHERS.
+              com.squareup.okhttp.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+              com.squareup.okhttp.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+              com.squareup.okhttp.CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+              com.squareup.okhttp.CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+              com.squareup.okhttp.CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+              com.squareup.okhttp.CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
+              com.squareup.okhttp.CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
+              com.squareup.okhttp.CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384)
+          .tlsVersions(com.squareup.okhttp.TlsVersion.TLS_1_2)
+          .supportsTlsExtensions(true)
+          .build();
+
+  @VisibleForTesting
+  static final ConnectionSpec INTERNAL_DEFAULT_CONNECTION_SPEC =
+      new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
+          .cipherSuites(
+              // The following items should be sync with Netty's Http2SecurityUtil.CIPHERS.
+              CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+              CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+              CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+              CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+              CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+              CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,
+              CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
+              CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384)
+          .tlsVersions(TlsVersion.TLS_1_2)
+          .supportsTlsExtensions(true)
+          .build();
+
+  private static final long AS_LARGE_AS_INFINITE = TimeUnit.DAYS.toNanos(1000L);
+  private static final Resource<ExecutorService> SHARED_EXECUTOR =
+      new Resource<ExecutorService>() {
+        @Override
+        public ExecutorService create() {
+          return Executors.newCachedThreadPool(GrpcUtil.getThreadFactory("grpc-okhttp-%d", true));
+        }
+
+        @Override
+        public void close(ExecutorService executor) {
+          executor.shutdown();
+        }
+      };
+
+  /** Creates a new builder for the given server host and port. */
+  public static OkHttpChannelBuilder forAddress(String host, int port) {
+    return new OkHttpChannelBuilder(host, port);
+  }
+
+  /**
+   * Creates a new builder for the given target that will be resolved by
+   * {@link io.grpc.NameResolver}.
+   */
+  public static OkHttpChannelBuilder forTarget(String target) {
+    return new OkHttpChannelBuilder(target);
+  }
+
+  private Executor transportExecutor;
+  private ScheduledExecutorService scheduledExecutorService;
+
+  private SSLSocketFactory sslSocketFactory;
+  private HostnameVerifier hostnameVerifier;
+  private ConnectionSpec connectionSpec = INTERNAL_DEFAULT_CONNECTION_SPEC;
+  private NegotiationType negotiationType = NegotiationType.TLS;
+  private long keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
+  private long keepAliveTimeoutNanos = DEFAULT_KEEPALIVE_TIMEOUT_NANOS;
+  private boolean keepAliveWithoutCalls;
+
+  protected OkHttpChannelBuilder(String host, int port) {
+    this(GrpcUtil.authorityFromHostAndPort(host, port));
+  }
+
+  private OkHttpChannelBuilder(String target) {
+    super(target);
+  }
+
+  @VisibleForTesting
+  final OkHttpChannelBuilder setTransportTracerFactory(
+      TransportTracer.Factory transportTracerFactory) {
+    this.transportTracerFactory = transportTracerFactory;
+    return this;
+  }
+
+  /**
+   * Override the default executor necessary for internal transport use.
+   *
+   * <p>The channel does not take ownership of the given executor. It is the caller' responsibility
+   * to shutdown the executor when appropriate.
+   */
+  public final OkHttpChannelBuilder transportExecutor(@Nullable Executor transportExecutor) {
+    this.transportExecutor = transportExecutor;
+    return this;
+  }
+
+  /**
+   * Sets the negotiation type for the HTTP/2 connection.
+   *
+   * <p>If TLS is enabled a default {@link SSLSocketFactory} is created using the best
+   * {@link java.security.Provider} available and is NOT based on
+   * {@link SSLSocketFactory#getDefault}. To more precisely control the TLS configuration call
+   * {@link #sslSocketFactory} to override the socket factory used.
+   *
+   * <p>Default: <code>TLS</code>
+   *
+   * @deprecated use {@link #usePlaintext()} or {@link #useTransportSecurity()} instead.
+   */
+  @Deprecated
+  public final OkHttpChannelBuilder negotiationType(io.grpc.okhttp.NegotiationType type) {
+    Preconditions.checkNotNull(type, "type");
+    switch (type) {
+      case TLS:
+        negotiationType = NegotiationType.TLS;
+        break;
+      case PLAINTEXT:
+        negotiationType = NegotiationType.PLAINTEXT;
+        break;
+      default:
+        throw new AssertionError("Unknown negotiation type: " + type);
+    }
+    return this;
+  }
+
+  /**
+   * Enable keepalive with default delay and timeout.
+   *
+   * @deprecated Use {@link #keepAliveTime} instead
+   */
+  @Deprecated
+  public final OkHttpChannelBuilder enableKeepAlive(boolean enable) {
+    if (enable) {
+      return keepAliveTime(DEFAULT_KEEPALIVE_TIME_NANOS, TimeUnit.NANOSECONDS);
+    } else {
+      return keepAliveTime(KEEPALIVE_TIME_NANOS_DISABLED, TimeUnit.NANOSECONDS);
+    }
+  }
+
+  /**
+   * Enable keepalive with custom delay and timeout.
+   *
+   * @deprecated Use {@link #keepAliveTime} and {@link #keepAliveTimeout} instead
+   */
+  @Deprecated
+  public final OkHttpChannelBuilder enableKeepAlive(boolean enable, long keepAliveTime,
+      TimeUnit delayUnit, long keepAliveTimeout, TimeUnit timeoutUnit) {
+    if (enable) {
+      return keepAliveTime(keepAliveTime, delayUnit)
+          .keepAliveTimeout(keepAliveTimeout, timeoutUnit);
+    } else {
+      return keepAliveTime(KEEPALIVE_TIME_NANOS_DISABLED, TimeUnit.NANOSECONDS);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * @since 1.3.0
+   */
+  @Override
+  public OkHttpChannelBuilder keepAliveTime(long keepAliveTime, TimeUnit timeUnit) {
+    Preconditions.checkArgument(keepAliveTime > 0L, "keepalive time must be positive");
+    keepAliveTimeNanos = timeUnit.toNanos(keepAliveTime);
+    keepAliveTimeNanos = KeepAliveManager.clampKeepAliveTimeInNanos(keepAliveTimeNanos);
+    if (keepAliveTimeNanos >= AS_LARGE_AS_INFINITE) {
+      // Bump keepalive time to infinite. This disables keepalive.
+      keepAliveTimeNanos = KEEPALIVE_TIME_NANOS_DISABLED;
+    }
+    return this;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * @since 1.3.0
+   */
+  @Override
+  public OkHttpChannelBuilder keepAliveTimeout(long keepAliveTimeout, TimeUnit timeUnit) {
+    Preconditions.checkArgument(keepAliveTimeout > 0L, "keepalive timeout must be positive");
+    keepAliveTimeoutNanos = timeUnit.toNanos(keepAliveTimeout);
+    keepAliveTimeoutNanos = KeepAliveManager.clampKeepAliveTimeoutInNanos(keepAliveTimeoutNanos);
+    return this;
+  }
+
+  /**
+   * {@inheritDoc}
+   *
+   * @since 1.3.0
+   * @see #keepAliveTime(long, TimeUnit)
+   */
+  @Override
+  public OkHttpChannelBuilder keepAliveWithoutCalls(boolean enable) {
+    keepAliveWithoutCalls = enable;
+    return this;
+  }
+
+  /**
+   * Override the default {@link SSLSocketFactory} and enable TLS negotiation.
+   */
+  public final OkHttpChannelBuilder sslSocketFactory(SSLSocketFactory factory) {
+    this.sslSocketFactory = factory;
+    negotiationType = NegotiationType.TLS;
+    return this;
+  }
+
+  /**
+   * Set the hostname verifier to use when using TLS negotiation. The hostnameVerifier is only used
+   * if using TLS negotiation. If the hostname verifier is not set, a default hostname verifier is
+   * used.
+   *
+   * <p>Be careful when setting a custom hostname verifier! By setting a non-null value, you are
+   * replacing all default verification behavior. If the hostname verifier you supply does not
+   * effectively supply the same checks, you may be removing the security assurances that TLS aims
+   * to provide.</p>
+   *
+   * <p>This method should not be used to avoid hostname verification, even during testing, since
+   * {@link #overrideAuthority} is a safer alternative as it does not disable any security checks.
+   * </p>
+   *
+   * @see io.grpc.okhttp.internal.OkHostnameVerifier
+   *
+   * @since 1.6.0
+   * @return this
+   *
+   */
+  public final OkHttpChannelBuilder hostnameVerifier(@Nullable HostnameVerifier hostnameVerifier) {
+    this.hostnameVerifier = hostnameVerifier;
+    return this;
+  }
+
+  /**
+   * For secure connection, provides a ConnectionSpec to specify Cipher suite and
+   * TLS versions.
+   *
+   * <p>By default a modern, HTTP/2-compatible spec will be used.
+   *
+   * <p>This method is only used when building a secure connection. For plaintext
+   * connection, use {@link #usePlaintext()} instead.
+   *
+   * @throws IllegalArgumentException
+   *         If {@code connectionSpec} is not with TLS
+   */
+  public final OkHttpChannelBuilder connectionSpec(
+      com.squareup.okhttp.ConnectionSpec connectionSpec) {
+    Preconditions.checkArgument(connectionSpec.isTls(), "plaintext ConnectionSpec is not accepted");
+    this.connectionSpec = Utils.convertSpec(connectionSpec);
+    return this;
+  }
+
+  /**
+   * Equivalent to using {@link #negotiationType} with {@code PLAINTEXT}.
+   *
+   * @deprecated use {@link #usePlaintext()} instead.
+   */
+  @Override
+  @Deprecated
+  public final OkHttpChannelBuilder usePlaintext(boolean skipNegotiation) {
+    if (skipNegotiation) {
+      negotiationType(io.grpc.okhttp.NegotiationType.PLAINTEXT);
+    } else {
+      throw new IllegalArgumentException("Plaintext negotiation not currently supported");
+    }
+    return this;
+  }
+
+  /** Sets the negotiation type for the HTTP/2 connection to plaintext. */
+  @Override
+  public final OkHttpChannelBuilder usePlaintext() {
+    negotiationType = NegotiationType.PLAINTEXT;
+    return this;
+  }
+
+  /**
+   * Sets the negotiation type for the HTTP/2 connection to TLS (this is the default).
+   *
+   * <p>With TLS enabled, a default {@link SSLSocketFactory} is created using the best {@link
+   * java.security.Provider} available and is NOT based on {@link SSLSocketFactory#getDefault}. To
+   * more precisely control the TLS configuration call {@link #sslSocketFactory} to override the
+   * socket factory used.
+   */
+  @Override
+  public final OkHttpChannelBuilder useTransportSecurity() {
+    negotiationType = NegotiationType.TLS;
+    return this;
+  }
+
+  /**
+   * Provides a custom scheduled executor service.
+   *
+   * <p>It's an optional parameter. If the user has not provided a scheduled executor service when
+   * the channel is built, the builder will use a static cached thread pool.
+   *
+   * @return this
+   *
+   * @since 1.11.0
+   */
+  public final OkHttpChannelBuilder scheduledExecutorService(
+      ScheduledExecutorService scheduledExecutorService) {
+    this.scheduledExecutorService =
+        checkNotNull(scheduledExecutorService, "scheduledExecutorService");
+    return this;
+  }
+
+  @Override
+  @Internal
+  protected final ClientTransportFactory buildTransportFactory() {
+    boolean enableKeepAlive = keepAliveTimeNanos != KEEPALIVE_TIME_NANOS_DISABLED;
+    return new OkHttpTransportFactory(transportExecutor, scheduledExecutorService,
+        createSocketFactory(), hostnameVerifier, connectionSpec, maxInboundMessageSize(),
+        enableKeepAlive, keepAliveTimeNanos, keepAliveTimeoutNanos, keepAliveWithoutCalls,
+        transportTracerFactory);
+  }
+
+  @Override
+  protected Attributes getNameResolverParams() {
+    int defaultPort;
+    switch (negotiationType) {
+      case PLAINTEXT:
+        defaultPort = GrpcUtil.DEFAULT_PORT_PLAINTEXT;
+        break;
+      case TLS:
+        defaultPort = GrpcUtil.DEFAULT_PORT_SSL;
+        break;
+      default:
+        throw new AssertionError(negotiationType + " not handled");
+    }
+    return Attributes.newBuilder()
+        .set(NameResolver.Factory.PARAMS_DEFAULT_PORT, defaultPort).build();
+  }
+
+  @VisibleForTesting
+  @Nullable
+  SSLSocketFactory createSocketFactory() {
+    switch (negotiationType) {
+      case TLS:
+        try {
+          if (sslSocketFactory == null) {
+            SSLContext sslContext;
+            if (GrpcUtil.IS_RESTRICTED_APPENGINE) {
+              // The following auth code circumvents the following AccessControlException:
+              // access denied ("java.util.PropertyPermission" "javax.net.ssl.keyStore" "read")
+              // Conscrypt will attempt to load the default KeyStore if a trust manager is not
+              // provided, which is forbidden on AppEngine
+              sslContext = SSLContext.getInstance("TLS", Platform.get().getProvider());
+              TrustManagerFactory trustManagerFactory =
+                  TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+              trustManagerFactory.init((KeyStore) null);
+              sslContext.init(
+                  null,
+                  trustManagerFactory.getTrustManagers(),
+                  // Use an algorithm that doesn't need /dev/urandom
+                  SecureRandom.getInstance("SHA1PRNG", Platform.get().getProvider()));
+
+            } else {
+              sslContext = SSLContext.getInstance("Default", Platform.get().getProvider());
+            }
+            sslSocketFactory = sslContext.getSocketFactory();
+          }
+          return sslSocketFactory;
+        } catch (GeneralSecurityException gse) {
+          throw new RuntimeException("TLS Provider failure", gse);
+        }
+      case PLAINTEXT:
+        return null;
+      default:
+        throw new RuntimeException("Unknown negotiation type: " + negotiationType);
+    }
+  }
+
+  /**
+   * Creates OkHttp transports. Exposed for internal use, as it should be private.
+   */
+  @Internal
+  static final class OkHttpTransportFactory implements ClientTransportFactory {
+    private final Executor executor;
+    private final boolean usingSharedExecutor;
+    private final boolean usingSharedScheduler;
+    private final TransportTracer.Factory transportTracerFactory;
+    @Nullable
+    private final SSLSocketFactory socketFactory;
+    @Nullable
+    private final HostnameVerifier hostnameVerifier;
+    private final ConnectionSpec connectionSpec;
+    private final int maxMessageSize;
+    private final boolean enableKeepAlive;
+    private final AtomicBackoff keepAliveTimeNanos;
+    private final long keepAliveTimeoutNanos;
+    private final boolean keepAliveWithoutCalls;
+    private final ScheduledExecutorService timeoutService;
+    private boolean closed;
+
+    private OkHttpTransportFactory(Executor executor,
+        @Nullable ScheduledExecutorService timeoutService,
+        @Nullable SSLSocketFactory socketFactory,
+        @Nullable HostnameVerifier hostnameVerifier,
+        ConnectionSpec connectionSpec,
+        int maxMessageSize,
+        boolean enableKeepAlive,
+        long keepAliveTimeNanos,
+        long keepAliveTimeoutNanos,
+        boolean keepAliveWithoutCalls,
+        TransportTracer.Factory transportTracerFactory) {
+      usingSharedScheduler = timeoutService == null;
+      this.timeoutService = usingSharedScheduler
+          ? SharedResourceHolder.get(GrpcUtil.TIMER_SERVICE) : timeoutService;
+      this.socketFactory = socketFactory;
+      this.hostnameVerifier = hostnameVerifier;
+      this.connectionSpec = connectionSpec;
+      this.maxMessageSize = maxMessageSize;
+      this.enableKeepAlive = enableKeepAlive;
+      this.keepAliveTimeNanos = new AtomicBackoff("keepalive time nanos", keepAliveTimeNanos);
+      this.keepAliveTimeoutNanos = keepAliveTimeoutNanos;
+      this.keepAliveWithoutCalls = keepAliveWithoutCalls;
+
+      usingSharedExecutor = executor == null;
+      this.transportTracerFactory =
+          Preconditions.checkNotNull(transportTracerFactory, "transportTracerFactory");
+      if (usingSharedExecutor) {
+        // The executor was unspecified, using the shared executor.
+        this.executor = SharedResourceHolder.get(SHARED_EXECUTOR);
+      } else {
+        this.executor = executor;
+      }
+    }
+
+    @Override
+    public ConnectionClientTransport newClientTransport(
+        SocketAddress addr, ClientTransportOptions options) {
+      if (closed) {
+        throw new IllegalStateException("The transport factory is closed.");
+      }
+      final AtomicBackoff.State keepAliveTimeNanosState = keepAliveTimeNanos.getState();
+      Runnable tooManyPingsRunnable = new Runnable() {
+        @Override
+        public void run() {
+          keepAliveTimeNanosState.backoff();
+        }
+      };
+      InetSocketAddress inetSocketAddr = (InetSocketAddress) addr;
+      OkHttpClientTransport transport = new OkHttpClientTransport(
+          inetSocketAddr,
+          options.getAuthority(),
+          options.getUserAgent(),
+          executor,
+          socketFactory,
+          hostnameVerifier,
+          connectionSpec,
+          maxMessageSize,
+          options.getProxyParameters(),
+          tooManyPingsRunnable,
+          transportTracerFactory.create());
+      if (enableKeepAlive) {
+        transport.enableKeepAlive(
+            true, keepAliveTimeNanosState.get(), keepAliveTimeoutNanos, keepAliveWithoutCalls);
+      }
+      return transport;
+    }
+
+    @Override
+    public ScheduledExecutorService getScheduledExecutorService() {
+      return timeoutService;
+    }
+
+    @Override
+    public void close() {
+      if (closed) {
+        return;
+      }
+      closed = true;
+
+      if (usingSharedScheduler) {
+        SharedResourceHolder.release(GrpcUtil.TIMER_SERVICE, timeoutService);
+      }
+
+      if (usingSharedExecutor) {
+        SharedResourceHolder.release(SHARED_EXECUTOR, (ExecutorService) executor);
+      }
+    }
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelProvider.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelProvider.java
new file mode 100644
index 0000000..42169cd
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpChannelProvider.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import io.grpc.Internal;
+import io.grpc.InternalServiceProviders;
+import io.grpc.ManagedChannelProvider;
+import io.grpc.internal.GrpcUtil;
+
+/**
+ * Provider for {@link OkHttpChannelBuilder} instances.
+ */
+@Internal
+public final class OkHttpChannelProvider extends ManagedChannelProvider {
+
+  @Override
+  public boolean isAvailable() {
+    return true;
+  }
+
+  @Override
+  public int priority() {
+    return (GrpcUtil.IS_RESTRICTED_APPENGINE
+        || InternalServiceProviders.isAndroid(getClass().getClassLoader())) ? 8 : 3;
+  }
+
+  @Override
+  public OkHttpChannelBuilder builderForAddress(String name, int port) {
+    return OkHttpChannelBuilder.forAddress(name, port);
+  }
+
+  @Override
+  public OkHttpChannelBuilder builderForTarget(String target) {
+    return OkHttpChannelBuilder.forTarget(target);
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientStream.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientStream.java
new file mode 100644
index 0000000..c1585b4
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientStream.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED;
+
+import com.google.common.io.BaseEncoding;
+import io.grpc.Attributes;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.internal.AbstractClientStream;
+import io.grpc.internal.Http2ClientStreamTransportState;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.TransportTracer;
+import io.grpc.internal.WritableBuffer;
+import io.grpc.okhttp.internal.framed.ErrorCode;
+import io.grpc.okhttp.internal.framed.Header;
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.Queue;
+import javax.annotation.concurrent.GuardedBy;
+import okio.Buffer;
+
+/**
+ * Client stream for the okhttp transport.
+ */
+class OkHttpClientStream extends AbstractClientStream {
+
+  private static final int WINDOW_UPDATE_THRESHOLD = Utils.DEFAULT_WINDOW_SIZE / 2;
+
+  private static final Buffer EMPTY_BUFFER = new Buffer();
+
+  public static final int ABSENT_ID = -1;
+
+  private final MethodDescriptor<?, ?> method;
+
+  private final String userAgent;
+  private final StatsTraceContext statsTraceCtx;
+  private String authority;
+  private Object outboundFlowState;
+  private volatile int id = ABSENT_ID;
+  private final TransportState state;
+  private final Sink sink = new Sink();
+  private final Attributes attributes;
+
+  private boolean useGet = false;
+
+  OkHttpClientStream(
+      MethodDescriptor<?, ?> method,
+      Metadata headers,
+      AsyncFrameWriter frameWriter,
+      OkHttpClientTransport transport,
+      OutboundFlowController outboundFlow,
+      Object lock,
+      int maxMessageSize,
+      String authority,
+      String userAgent,
+      StatsTraceContext statsTraceCtx,
+      TransportTracer transportTracer) {
+    super(
+        new OkHttpWritableBufferAllocator(),
+        statsTraceCtx,
+        transportTracer,
+        headers,
+        method.isSafe());
+    this.statsTraceCtx = checkNotNull(statsTraceCtx, "statsTraceCtx");
+    this.method = method;
+    this.authority = authority;
+    this.userAgent = userAgent;
+    // OkHttpClientStream is only created after the transport has finished connecting,
+    // so it is safe to read the transport attributes.
+    // We make a copy here for convenience, even though we can ask the transport.
+    this.attributes = transport.getAttributes();
+    this.state = new TransportState(maxMessageSize, statsTraceCtx, lock, frameWriter, outboundFlow,
+        transport);
+  }
+
+  @Override
+  protected TransportState transportState() {
+    return state;
+  }
+
+  @Override
+  protected Sink abstractClientStreamSink() {
+    return sink;
+  }
+
+  /**
+   * Returns the type of this stream.
+   */
+  public MethodDescriptor.MethodType getType() {
+    return method.getType();
+  }
+
+  public int id() {
+    return id;
+  }
+
+  /**
+   * Returns whether the stream uses GET. This is not known until after {@link Sink#writeHeaders} is
+   * invoked.
+   */
+  boolean useGet() {
+    return useGet;
+  }
+
+  @Override
+  public void setAuthority(String authority) {
+    this.authority = checkNotNull(authority, "authority");
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    return attributes;
+  }
+
+  class Sink implements AbstractClientStream.Sink {
+    @SuppressWarnings("BetaApi") // BaseEncoding is stable in Guava 20.0
+    @Override
+    public void writeHeaders(Metadata metadata, byte[] payload) {
+      String defaultPath = "/" + method.getFullMethodName();
+      if (payload != null) {
+        useGet = true;
+        defaultPath += "?" + BaseEncoding.base64().encode(payload);
+      }
+      synchronized (state.lock) {
+        state.streamReady(metadata, defaultPath);
+      }
+    }
+
+    @Override
+    public void writeFrame(
+        WritableBuffer frame, boolean endOfStream, boolean flush, int numMessages) {
+      Buffer buffer;
+      if (frame == null) {
+        buffer = EMPTY_BUFFER;
+      } else {
+        buffer = ((OkHttpWritableBuffer) frame).buffer();
+        int size = (int) buffer.size();
+        if (size > 0) {
+          onSendingBytes(size);
+        }
+      }
+
+      synchronized (state.lock) {
+        state.sendBuffer(buffer, endOfStream, flush);
+        getTransportTracer().reportMessageSent(numMessages);
+      }
+    }
+
+    @Override
+    public void request(final int numMessages) {
+      synchronized (state.lock) {
+        state.requestMessagesFromDeframer(numMessages);
+      }
+    }
+
+    @Override
+    public void cancel(Status reason) {
+      synchronized (state.lock) {
+        state.cancel(reason, true, null);
+      }
+    }
+  }
+
+  class TransportState extends Http2ClientStreamTransportState {
+    private final Object lock;
+    @GuardedBy("lock")
+    private List<Header> requestHeaders;
+    /**
+     * Null iff {@link #requestHeaders} is null.  Non-null iff neither {@link #cancel} nor
+     * {@link #start(int)} have been called.
+     */
+    @GuardedBy("lock")
+    private Queue<PendingData> pendingData = new ArrayDeque<PendingData>();
+    @GuardedBy("lock")
+    private boolean cancelSent = false;
+    @GuardedBy("lock")
+    private int window = Utils.DEFAULT_WINDOW_SIZE;
+    @GuardedBy("lock")
+    private int processedWindow = Utils.DEFAULT_WINDOW_SIZE;
+    @GuardedBy("lock")
+    private final AsyncFrameWriter frameWriter;
+    @GuardedBy("lock")
+    private final OutboundFlowController outboundFlow;
+    @GuardedBy("lock")
+    private final OkHttpClientTransport transport;
+
+    public TransportState(
+        int maxMessageSize,
+        StatsTraceContext statsTraceCtx,
+        Object lock,
+        AsyncFrameWriter frameWriter,
+        OutboundFlowController outboundFlow,
+        OkHttpClientTransport transport) {
+      super(maxMessageSize, statsTraceCtx, OkHttpClientStream.this.getTransportTracer());
+      this.lock = checkNotNull(lock, "lock");
+      this.frameWriter = frameWriter;
+      this.outboundFlow = outboundFlow;
+      this.transport = transport;
+    }
+
+    @GuardedBy("lock")
+    public void start(int streamId) {
+      checkState(id == ABSENT_ID, "the stream has been started with id %s", streamId);
+      id = streamId;
+      state.onStreamAllocated();
+
+      if (pendingData != null) {
+        // Only happens when the stream has neither been started nor cancelled.
+        frameWriter.synStream(useGet, false, id, 0, requestHeaders);
+        statsTraceCtx.clientOutboundHeaders();
+        requestHeaders = null;
+
+        boolean flush = false;
+        while (!pendingData.isEmpty()) {
+          PendingData data = pendingData.poll();
+          outboundFlow.data(data.endOfStream, id, data.buffer, false);
+          if (data.flush) {
+            flush = true;
+          }
+        }
+        if (flush) {
+          outboundFlow.flush();
+        }
+        pendingData = null;
+      }
+    }
+
+    @GuardedBy("lock")
+    @Override
+    protected void onStreamAllocated() {
+      super.onStreamAllocated();
+      getTransportTracer().reportLocalStreamStarted();
+    }
+
+    @GuardedBy("lock")
+    @Override
+    protected void http2ProcessingFailed(Status status, boolean stopDelivery, Metadata trailers) {
+      cancel(status, stopDelivery, trailers);
+    }
+
+    @Override
+    @GuardedBy("lock")
+    public void deframeFailed(Throwable cause) {
+      http2ProcessingFailed(Status.fromThrowable(cause), true, new Metadata());
+    }
+
+    @Override
+    @GuardedBy("lock")
+    public void bytesRead(int processedBytes) {
+      processedWindow -= processedBytes;
+      if (processedWindow <= WINDOW_UPDATE_THRESHOLD) {
+        int delta = Utils.DEFAULT_WINDOW_SIZE - processedWindow;
+        window += delta;
+        processedWindow += delta;
+        frameWriter.windowUpdate(id(), delta);
+      }
+    }
+
+    @Override
+    @GuardedBy("lock")
+    public void deframerClosed(boolean hasPartialMessage) {
+      onEndOfStream();
+      super.deframerClosed(hasPartialMessage);
+    }
+
+    @Override
+    @GuardedBy("lock")
+    public void runOnTransportThread(final Runnable r) {
+      synchronized (lock) {
+        r.run();
+      }
+    }
+
+    /**
+     * Must be called with holding the transport lock.
+     */
+    @GuardedBy("lock")
+    public void transportHeadersReceived(List<Header> headers, boolean endOfStream) {
+      if (endOfStream) {
+        transportTrailersReceived(Utils.convertTrailers(headers));
+      } else {
+        transportHeadersReceived(Utils.convertHeaders(headers));
+      }
+    }
+
+    /**
+     * Must be called with holding the transport lock.
+     */
+    @GuardedBy("lock")
+    public void transportDataReceived(okio.Buffer frame, boolean endOfStream) {
+      // We only support 16 KiB frames, and the max permitted in HTTP/2 is 16 MiB. This is verified
+      // in OkHttp's Http2 deframer. In addition, this code is after the data has been read.
+      int length = (int) frame.size();
+      window -= length;
+      if (window < 0) {
+        frameWriter.rstStream(id(), ErrorCode.FLOW_CONTROL_ERROR);
+        transport.finishStream(
+            id(),
+            Status.INTERNAL.withDescription(
+                "Received data size exceeded our receiving window size"),
+            PROCESSED, false, null, null);
+        return;
+      }
+      super.transportDataReceived(new OkHttpReadableBuffer(frame), endOfStream);
+    }
+
+    @GuardedBy("lock")
+    private void onEndOfStream() {
+      if (!isOutboundClosed()) {
+        // If server's end-of-stream is received before client sends end-of-stream, we just send a
+        // reset to server to fully close the server side stream.
+        transport.finishStream(id(),null, PROCESSED, false, ErrorCode.CANCEL, null);
+      } else {
+        transport.finishStream(id(), null, PROCESSED, false, null, null);
+      }
+    }
+
+    @GuardedBy("lock")
+    private void cancel(Status reason, boolean stopDelivery, Metadata trailers) {
+      if (cancelSent) {
+        return;
+      }
+      cancelSent = true;
+      if (pendingData != null) {
+        // stream is pending.
+        transport.removePendingStream(OkHttpClientStream.this);
+        // release holding data, so they can be GCed or returned to pool earlier.
+        requestHeaders = null;
+        for (PendingData data : pendingData) {
+          data.buffer.clear();
+        }
+        pendingData = null;
+        transportReportStatus(reason, true, trailers != null ? trailers : new Metadata());
+      } else {
+        // If pendingData is null, start must have already been called, which means synStream has
+        // been called as well.
+        transport.finishStream(
+            id(), reason, PROCESSED, stopDelivery, ErrorCode.CANCEL, trailers);
+      }
+    }
+
+    @GuardedBy("lock")
+    private void sendBuffer(Buffer buffer, boolean endOfStream, boolean flush) {
+      if (cancelSent) {
+        return;
+      }
+      if (pendingData != null) {
+        // Stream is pending start, queue the data.
+        pendingData.add(new PendingData(buffer, endOfStream, flush));
+      } else {
+        checkState(id() != ABSENT_ID, "streamId should be set");
+        // If buffer > frameWriter.maxDataLength() the flow-controller will ensure that it is
+        // properly chunked.
+        outboundFlow.data(endOfStream, id(), buffer, flush);
+      }
+    }
+
+    @GuardedBy("lock")
+    private void streamReady(Metadata metadata, String path) {
+      requestHeaders = Headers.createRequestHeaders(metadata, path, authority, userAgent, useGet);
+      transport.streamReadyToStart(OkHttpClientStream.this);
+    }
+  }
+
+  void setOutboundFlowState(Object outboundFlowState) {
+    this.outboundFlowState = outboundFlowState;
+  }
+
+  Object getOutboundFlowState() {
+    return outboundFlowState;
+  }
+
+  private static class PendingData {
+    Buffer buffer;
+    boolean endOfStream;
+    boolean flush;
+
+    PendingData(Buffer buffer, boolean endOfStream, boolean flush) {
+      this.buffer = buffer;
+      this.endOfStream = endOfStream;
+      this.flush = flush;
+    }
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java
new file mode 100644
index 0000000..9bca807
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpClientTransport.java
@@ -0,0 +1,1208 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static com.google.common.base.Preconditions.checkState;
+import static io.grpc.internal.GrpcUtil.TIMER_SERVICE;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Supplier;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import com.squareup.okhttp.Credentials;
+import com.squareup.okhttp.HttpUrl;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.internal.http.StatusLine;
+import io.grpc.Attributes;
+import io.grpc.CallCredentials;
+import io.grpc.CallOptions;
+import io.grpc.Grpc;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalLogId;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.SecurityLevel;
+import io.grpc.Status;
+import io.grpc.Status.Code;
+import io.grpc.StatusException;
+import io.grpc.internal.ClientStreamListener.RpcProgress;
+import io.grpc.internal.ConnectionClientTransport;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.Http2Ping;
+import io.grpc.internal.KeepAliveManager;
+import io.grpc.internal.KeepAliveManager.ClientKeepAlivePinger;
+import io.grpc.internal.ProxyParameters;
+import io.grpc.internal.SerializingExecutor;
+import io.grpc.internal.SharedResourceHolder;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.TransportTracer;
+import io.grpc.okhttp.AsyncFrameWriter.TransportExceptionHandler;
+import io.grpc.okhttp.internal.ConnectionSpec;
+import io.grpc.okhttp.internal.framed.ErrorCode;
+import io.grpc.okhttp.internal.framed.FrameReader;
+import io.grpc.okhttp.internal.framed.FrameWriter;
+import io.grpc.okhttp.internal.framed.Header;
+import io.grpc.okhttp.internal.framed.HeadersMode;
+import io.grpc.okhttp.internal.framed.Http2;
+import io.grpc.okhttp.internal.framed.Settings;
+import io.grpc.okhttp.internal.framed.Variant;
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.URI;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import okio.Buffer;
+import okio.BufferedSink;
+import okio.BufferedSource;
+import okio.ByteString;
+import okio.Okio;
+import okio.Source;
+import okio.Timeout;
+
+/**
+ * A okhttp-based {@link ConnectionClientTransport} implementation.
+ */
+class OkHttpClientTransport implements ConnectionClientTransport, TransportExceptionHandler {
+  private static final Map<ErrorCode, Status> ERROR_CODE_TO_STATUS = buildErrorCodeToStatusMap();
+  private static final Logger log = Logger.getLogger(OkHttpClientTransport.class.getName());
+  private static final OkHttpClientStream[] EMPTY_STREAM_ARRAY = new OkHttpClientStream[0];
+
+  private static Map<ErrorCode, Status> buildErrorCodeToStatusMap() {
+    Map<ErrorCode, Status> errorToStatus = new EnumMap<ErrorCode, Status>(ErrorCode.class);
+    errorToStatus.put(ErrorCode.NO_ERROR,
+        Status.INTERNAL.withDescription("No error: A GRPC status of OK should have been sent"));
+    errorToStatus.put(ErrorCode.PROTOCOL_ERROR,
+        Status.INTERNAL.withDescription("Protocol error"));
+    errorToStatus.put(ErrorCode.INTERNAL_ERROR,
+        Status.INTERNAL.withDescription("Internal error"));
+    errorToStatus.put(ErrorCode.FLOW_CONTROL_ERROR,
+        Status.INTERNAL.withDescription("Flow control error"));
+    errorToStatus.put(ErrorCode.STREAM_CLOSED,
+        Status.INTERNAL.withDescription("Stream closed"));
+    errorToStatus.put(ErrorCode.FRAME_TOO_LARGE,
+        Status.INTERNAL.withDescription("Frame too large"));
+    errorToStatus.put(ErrorCode.REFUSED_STREAM,
+        Status.UNAVAILABLE.withDescription("Refused stream"));
+    errorToStatus.put(ErrorCode.CANCEL,
+        Status.CANCELLED.withDescription("Cancelled"));
+    errorToStatus.put(ErrorCode.COMPRESSION_ERROR,
+        Status.INTERNAL.withDescription("Compression error"));
+    errorToStatus.put(ErrorCode.CONNECT_ERROR,
+        Status.INTERNAL.withDescription("Connect error"));
+    errorToStatus.put(ErrorCode.ENHANCE_YOUR_CALM,
+        Status.RESOURCE_EXHAUSTED.withDescription("Enhance your calm"));
+    errorToStatus.put(ErrorCode.INADEQUATE_SECURITY,
+        Status.PERMISSION_DENIED.withDescription("Inadequate security"));
+    return Collections.unmodifiableMap(errorToStatus);
+  }
+
+  private final InetSocketAddress address;
+  private final String defaultAuthority;
+  private final String userAgent;
+  private final Random random = new Random();
+  // Returns new unstarted stopwatches
+  private final Supplier<Stopwatch> stopwatchFactory;
+  private Listener listener;
+  private FrameReader testFrameReader;
+  private AsyncFrameWriter frameWriter;
+  private OutboundFlowController outboundFlow;
+  private final Object lock = new Object();
+  private final InternalLogId logId = InternalLogId.allocate(getClass().getName());
+  @GuardedBy("lock")
+  private int nextStreamId;
+  @GuardedBy("lock")
+  private final Map<Integer, OkHttpClientStream> streams =
+      new HashMap<Integer, OkHttpClientStream>();
+  private final Executor executor;
+  // Wrap on executor, to guarantee some operations be executed serially.
+  private final SerializingExecutor serializingExecutor;
+  private final int maxMessageSize;
+  private int connectionUnacknowledgedBytesRead;
+  private ClientFrameHandler clientFrameHandler;
+  // Caution: Not synchronized, new value can only be safely read after the connection is complete.
+  private Attributes attributes = Attributes.EMPTY;
+  /**
+   * Indicates the transport is in go-away state: no new streams will be processed, but existing
+   * streams may continue.
+   */
+  @GuardedBy("lock")
+  private Status goAwayStatus;
+  @GuardedBy("lock")
+  private boolean goAwaySent;
+  @GuardedBy("lock")
+  private Http2Ping ping;
+  @GuardedBy("lock")
+  private boolean stopped;
+  @GuardedBy("lock")
+  private boolean inUse;
+  private SSLSocketFactory sslSocketFactory;
+  private HostnameVerifier hostnameVerifier;
+  private Socket socket;
+  @GuardedBy("lock")
+  private int maxConcurrentStreams = 0;
+  @SuppressWarnings("JdkObsolete") // Usage is bursty; want low memory usage when empty
+  @GuardedBy("lock")
+  private LinkedList<OkHttpClientStream> pendingStreams = new LinkedList<OkHttpClientStream>();
+  private final ConnectionSpec connectionSpec;
+  private FrameWriter testFrameWriter;
+  private ScheduledExecutorService scheduler;
+  private KeepAliveManager keepAliveManager;
+  private boolean enableKeepAlive;
+  private long keepAliveTimeNanos;
+  private long keepAliveTimeoutNanos;
+  private boolean keepAliveWithoutCalls;
+  private final Runnable tooManyPingsRunnable;
+  @GuardedBy("lock")
+  private final TransportTracer transportTracer;
+  @GuardedBy("lock")
+  private InternalChannelz.Security securityInfo;
+
+  @VisibleForTesting
+  @Nullable
+  final ProxyParameters proxy;
+
+  // The following fields should only be used for test.
+  Runnable connectingCallback;
+  SettableFuture<Void> connectedFuture;
+
+
+  OkHttpClientTransport(InetSocketAddress address, String authority, @Nullable String userAgent,
+      Executor executor, @Nullable SSLSocketFactory sslSocketFactory,
+      @Nullable HostnameVerifier hostnameVerifier, ConnectionSpec connectionSpec,
+      int maxMessageSize, @Nullable ProxyParameters proxy, Runnable tooManyPingsRunnable,
+      TransportTracer transportTracer) {
+    this.address = Preconditions.checkNotNull(address, "address");
+    this.defaultAuthority = authority;
+    this.maxMessageSize = maxMessageSize;
+    this.executor = Preconditions.checkNotNull(executor, "executor");
+    serializingExecutor = new SerializingExecutor(executor);
+    // Client initiated streams are odd, server initiated ones are even. Server should not need to
+    // use it. We start clients at 3 to avoid conflicting with HTTP negotiation.
+    nextStreamId = 3;
+    this.sslSocketFactory = sslSocketFactory;
+    this.hostnameVerifier = hostnameVerifier;
+    this.connectionSpec = Preconditions.checkNotNull(connectionSpec, "connectionSpec");
+    this.stopwatchFactory = GrpcUtil.STOPWATCH_SUPPLIER;
+    this.userAgent = GrpcUtil.getGrpcUserAgent("okhttp", userAgent);
+    this.proxy = proxy;
+    this.tooManyPingsRunnable =
+        Preconditions.checkNotNull(tooManyPingsRunnable, "tooManyPingsRunnable");
+    this.transportTracer = Preconditions.checkNotNull(transportTracer);
+    initTransportTracer();
+  }
+
+  /**
+   * Create a transport connected to a fake peer for test.
+   */
+  @VisibleForTesting
+  OkHttpClientTransport(
+      String userAgent,
+      Executor executor,
+      FrameReader frameReader,
+      FrameWriter testFrameWriter,
+      int nextStreamId,
+      Socket socket,
+      Supplier<Stopwatch> stopwatchFactory,
+      @Nullable Runnable connectingCallback,
+      SettableFuture<Void> connectedFuture,
+      int maxMessageSize,
+      Runnable tooManyPingsRunnable,
+      TransportTracer transportTracer) {
+    address = null;
+    this.maxMessageSize = maxMessageSize;
+    defaultAuthority = "notarealauthority:80";
+    this.userAgent = GrpcUtil.getGrpcUserAgent("okhttp", userAgent);
+    this.executor = Preconditions.checkNotNull(executor, "executor");
+    serializingExecutor = new SerializingExecutor(executor);
+    this.testFrameReader = Preconditions.checkNotNull(frameReader, "frameReader");
+    this.testFrameWriter = Preconditions.checkNotNull(testFrameWriter, "testFrameWriter");
+    this.socket = Preconditions.checkNotNull(socket, "socket");
+    this.nextStreamId = nextStreamId;
+    this.stopwatchFactory = stopwatchFactory;
+    this.connectionSpec = null;
+    this.connectingCallback = connectingCallback;
+    this.connectedFuture = Preconditions.checkNotNull(connectedFuture, "connectedFuture");
+    this.proxy = null;
+    this.tooManyPingsRunnable =
+        Preconditions.checkNotNull(tooManyPingsRunnable, "tooManyPingsRunnable");
+    this.transportTracer = Preconditions.checkNotNull(transportTracer, "transportTracer");
+    initTransportTracer();
+  }
+
+  private void initTransportTracer() {
+    synchronized (lock) { // to make @GuardedBy linter happy
+      transportTracer.setFlowControlWindowReader(new TransportTracer.FlowControlReader() {
+        @Override
+        public TransportTracer.FlowControlWindows read() {
+          synchronized (lock) {
+            long local = -1; // okhttp does not track the local window size
+            long remote = outboundFlow == null ? -1 : outboundFlow.windowUpdate(null, 0);
+            return new TransportTracer.FlowControlWindows(local, remote);
+          }
+        }
+      });
+    }
+  }
+
+  /**
+   * Enable keepalive with custom delay and timeout.
+   */
+  void enableKeepAlive(boolean enable, long keepAliveTimeNanos,
+      long keepAliveTimeoutNanos, boolean keepAliveWithoutCalls) {
+    enableKeepAlive = enable;
+    this.keepAliveTimeNanos = keepAliveTimeNanos;
+    this.keepAliveTimeoutNanos = keepAliveTimeoutNanos;
+    this.keepAliveWithoutCalls = keepAliveWithoutCalls;
+  }
+
+  private boolean isForTest() {
+    return address == null;
+  }
+
+  @Override
+  public void ping(final PingCallback callback, Executor executor) {
+    checkState(frameWriter != null);
+    long data = 0;
+    Http2Ping p;
+    boolean writePing;
+    synchronized (lock) {
+      if (stopped) {
+        Http2Ping.notifyFailed(callback, executor, getPingFailure());
+        return;
+      }
+      if (ping != null) {
+        // we only allow one outstanding ping at a time, so just add the callback to
+        // any outstanding operation
+        p = ping;
+        writePing = false;
+      } else {
+        // set outstanding operation and then write the ping after releasing lock
+        data = random.nextLong();
+        Stopwatch stopwatch = stopwatchFactory.get();
+        stopwatch.start();
+        p = ping = new Http2Ping(data, stopwatch);
+        writePing = true;
+        transportTracer.reportKeepAliveSent();
+      }
+    }
+    if (writePing) {
+      frameWriter.ping(false, (int) (data >>> 32), (int) data);
+    }
+    // If transport concurrently failed/stopped since we released the lock above, this could
+    // immediately invoke callback (which we shouldn't do while holding a lock)
+    p.addCallback(callback, executor);
+  }
+
+  @Override
+  public OkHttpClientStream newStream(final MethodDescriptor<?, ?> method,
+      final Metadata headers, CallOptions callOptions) {
+    Preconditions.checkNotNull(method, "method");
+    Preconditions.checkNotNull(headers, "headers");
+    StatsTraceContext statsTraceCtx = StatsTraceContext.newClientContext(callOptions, headers);
+    return new OkHttpClientStream(
+        method,
+        headers,
+        frameWriter,
+        OkHttpClientTransport.this,
+        outboundFlow,
+        lock,
+        maxMessageSize,
+        defaultAuthority,
+        userAgent,
+        statsTraceCtx,
+        transportTracer);
+  }
+
+  @GuardedBy("lock")
+  void streamReadyToStart(OkHttpClientStream clientStream) {
+    if (goAwayStatus != null) {
+      clientStream.transportState().transportReportStatus(
+          goAwayStatus, RpcProgress.REFUSED, true, new Metadata());
+    } else if (streams.size() >= maxConcurrentStreams) {
+      pendingStreams.add(clientStream);
+      setInUse();
+    } else {
+      startStream(clientStream);
+    }
+  }
+
+  @GuardedBy("lock")
+  private void startStream(OkHttpClientStream stream) {
+    Preconditions.checkState(
+        stream.id() == OkHttpClientStream.ABSENT_ID, "StreamId already assigned");
+    streams.put(nextStreamId, stream);
+    setInUse();
+    stream.transportState().start(nextStreamId);
+    // For unary and server streaming, there will be a data frame soon, no need to flush the header.
+    if ((stream.getType() != MethodType.UNARY && stream.getType() != MethodType.SERVER_STREAMING)
+        || stream.useGet()) {
+      frameWriter.flush();
+    }
+    if (nextStreamId >= Integer.MAX_VALUE - 2) {
+      // Make sure nextStreamId greater than all used id, so that mayHaveCreatedStream() performs
+      // correctly.
+      nextStreamId = Integer.MAX_VALUE;
+      startGoAway(Integer.MAX_VALUE, ErrorCode.NO_ERROR,
+          Status.UNAVAILABLE.withDescription("Stream ids exhausted"));
+    } else {
+      nextStreamId += 2;
+    }
+  }
+
+  /**
+   * Starts pending streams, returns true if at least one pending stream is started.
+   */
+  @GuardedBy("lock")
+  private boolean startPendingStreams() {
+    boolean hasStreamStarted = false;
+    while (!pendingStreams.isEmpty() && streams.size() < maxConcurrentStreams) {
+      OkHttpClientStream stream = pendingStreams.poll();
+      startStream(stream);
+      hasStreamStarted = true;
+    }
+    return hasStreamStarted;
+  }
+
+  /**
+   * Removes given pending stream, used when a pending stream is cancelled.
+   */
+  @GuardedBy("lock")
+  void removePendingStream(OkHttpClientStream pendingStream) {
+    pendingStreams.remove(pendingStream);
+    maybeClearInUse();
+  }
+
+  @Override
+  public Runnable start(Listener listener) {
+    this.listener = Preconditions.checkNotNull(listener, "listener");
+
+    if (enableKeepAlive) {
+      scheduler = SharedResourceHolder.get(TIMER_SERVICE);
+      keepAliveManager = new KeepAliveManager(
+          new ClientKeepAlivePinger(this), scheduler, keepAliveTimeNanos, keepAliveTimeoutNanos,
+          keepAliveWithoutCalls);
+      keepAliveManager.onTransportStarted();
+    }
+
+    frameWriter = new AsyncFrameWriter(this, serializingExecutor);
+    outboundFlow = new OutboundFlowController(this, frameWriter);
+    // Connecting in the serializingExecutor, so that some stream operations like synStream
+    // will be executed after connected.
+    serializingExecutor.execute(new Runnable() {
+      @Override
+      public void run() {
+        if (isForTest()) {
+          if (connectingCallback != null) {
+            connectingCallback.run();
+          }
+          clientFrameHandler = new ClientFrameHandler(testFrameReader);
+          executor.execute(clientFrameHandler);
+          synchronized (lock) {
+            maxConcurrentStreams = Integer.MAX_VALUE;
+            startPendingStreams();
+          }
+          frameWriter.becomeConnected(testFrameWriter, socket);
+          connectedFuture.set(null);
+          return;
+        }
+
+        // Use closed source on failure so that the reader immediately shuts down.
+        BufferedSource source = Okio.buffer(new Source() {
+          @Override
+          public long read(Buffer sink, long byteCount) {
+            return -1;
+          }
+
+          @Override
+          public Timeout timeout() {
+            return Timeout.NONE;
+          }
+
+          @Override
+          public void close() {}
+        });
+        Variant variant = new Http2();
+        BufferedSink sink;
+        Socket sock;
+        SSLSession sslSession = null;
+        try {
+          if (proxy == null) {
+            sock = new Socket(address.getAddress(), address.getPort());
+          } else {
+            sock = createHttpProxySocket(
+                address, proxy.proxyAddress, proxy.username, proxy.password);
+          }
+
+          if (sslSocketFactory != null) {
+            SSLSocket sslSocket = OkHttpTlsUpgrader.upgrade(
+                sslSocketFactory, hostnameVerifier, sock, getOverridenHost(), getOverridenPort(),
+                connectionSpec);
+            sslSession = sslSocket.getSession();
+            sock = sslSocket;
+          }
+          sock.setTcpNoDelay(true);
+          source = Okio.buffer(Okio.source(sock));
+          sink = Okio.buffer(Okio.sink(sock));
+          // The return value of OkHttpTlsUpgrader.upgrade is an SSLSocket that has this info
+          attributes = Attributes
+              .newBuilder()
+              .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, sock.getRemoteSocketAddress())
+              .set(Grpc.TRANSPORT_ATTR_SSL_SESSION, sslSession)
+              .set(CallCredentials.ATTR_SECURITY_LEVEL,
+                  sslSession == null ? SecurityLevel.NONE : SecurityLevel.PRIVACY_AND_INTEGRITY)
+              .build();
+        } catch (StatusException e) {
+          startGoAway(0, ErrorCode.INTERNAL_ERROR, e.getStatus());
+          return;
+        } catch (Exception e) {
+          onException(e);
+          return;
+        } finally {
+          clientFrameHandler = new ClientFrameHandler(variant.newReader(source, true));
+          executor.execute(clientFrameHandler);
+        }
+
+        FrameWriter rawFrameWriter;
+        synchronized (lock) {
+          socket = Preconditions.checkNotNull(sock, "socket");
+          maxConcurrentStreams = Integer.MAX_VALUE;
+          startPendingStreams();
+          if (sslSession != null) {
+            securityInfo = new InternalChannelz.Security(new InternalChannelz.Tls(sslSession));
+          }
+        }
+
+        rawFrameWriter = variant.newWriter(sink, true);
+        frameWriter.becomeConnected(rawFrameWriter, socket);
+
+        try {
+          // Do these with the raw FrameWriter, so that they will be done in this thread,
+          // and before any possible pending stream operations.
+          rawFrameWriter.connectionPreface();
+          Settings settings = new Settings();
+          rawFrameWriter.settings(settings);
+        } catch (Exception e) {
+          onException(e);
+          return;
+        }
+      }
+    });
+    return null;
+  }
+
+  private Socket createHttpProxySocket(InetSocketAddress address, InetSocketAddress proxyAddress,
+      String proxyUsername, String proxyPassword) throws IOException, StatusException {
+    try {
+      Socket sock;
+      // The proxy address may not be resolved
+      if (proxyAddress.getAddress() != null) {
+        sock = new Socket(proxyAddress.getAddress(), proxyAddress.getPort());
+      } else {
+        sock = new Socket(proxyAddress.getHostName(), proxyAddress.getPort());
+      }
+      sock.setTcpNoDelay(true);
+
+      Source source = Okio.source(sock);
+      BufferedSink sink = Okio.buffer(Okio.sink(sock));
+
+      // Prepare headers and request method line
+      Request proxyRequest = createHttpProxyRequest(address, proxyUsername, proxyPassword);
+      HttpUrl url = proxyRequest.httpUrl();
+      String requestLine = String.format("CONNECT %s:%d HTTP/1.1", url.host(), url.port());
+
+      // Write request to socket
+      sink.writeUtf8(requestLine).writeUtf8("\r\n");
+      for (int i = 0, size = proxyRequest.headers().size(); i < size; i++) {
+        sink.writeUtf8(proxyRequest.headers().name(i))
+            .writeUtf8(": ")
+            .writeUtf8(proxyRequest.headers().value(i))
+            .writeUtf8("\r\n");
+      }
+      sink.writeUtf8("\r\n");
+      // Flush buffer (flushes socket and sends request)
+      sink.flush();
+
+      // Read status line, check if 2xx was returned
+      StatusLine statusLine = StatusLine.parse(readUtf8LineStrictUnbuffered(source));
+      // Drain rest of headers
+      while (!readUtf8LineStrictUnbuffered(source).equals("")) {}
+      if (statusLine.code < 200 || statusLine.code >= 300) {
+        Buffer body = new Buffer();
+        try {
+          sock.shutdownOutput();
+          source.read(body, 1024);
+        } catch (IOException ex) {
+          body.writeUtf8("Unable to read body: " + ex.toString());
+        }
+        try {
+          sock.close();
+        } catch (IOException ignored) {
+          // ignored
+        }
+        String message = String.format(
+            "Response returned from proxy was not successful (expected 2xx, got %d %s). "
+              + "Response body:\n%s",
+            statusLine.code, statusLine.message, body.readUtf8());
+        throw Status.UNAVAILABLE.withDescription(message).asException();
+      }
+      return sock;
+    } catch (IOException e) {
+      throw Status.UNAVAILABLE.withDescription("Failed trying to connect with proxy").withCause(e)
+          .asException();
+    }
+  }
+
+  private Request createHttpProxyRequest(InetSocketAddress address, String proxyUsername,
+      String proxyPassword) {
+    HttpUrl tunnelUrl = new HttpUrl.Builder()
+        .scheme("https")
+        .host(address.getHostName())
+        .port(address.getPort())
+        .build();
+    Request.Builder request = new Request.Builder()
+        .url(tunnelUrl)
+        .header("Host", tunnelUrl.host() + ":" + tunnelUrl.port())
+        .header("User-Agent", userAgent);
+
+    // If we have proxy credentials, set them right away
+    if (proxyUsername != null && proxyPassword != null) {
+      request.header("Proxy-Authorization", Credentials.basic(proxyUsername, proxyPassword));
+    }
+    return request.build();
+  }
+
+  private static String readUtf8LineStrictUnbuffered(Source source) throws IOException {
+    Buffer buffer = new Buffer();
+    while (true) {
+      if (source.read(buffer, 1) == -1) {
+        throw new EOFException("\\n not found: " + buffer.readByteString().hex());
+      }
+      if (buffer.getByte(buffer.size() - 1) == '\n') {
+        return buffer.readUtf8LineStrict();
+      }
+    }
+  }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("logId", logId.getId())
+        .add("address", address)
+        .toString();
+  }
+
+  @Override
+  public InternalLogId getLogId() {
+    return logId;
+  }
+
+  /**
+   * Gets the overridden authority hostname.  If the authority is overridden to be an invalid
+   * authority, uri.getHost() will (rightly) return null, since the authority is no longer
+   * an actual service.  This method overrides the behavior for practical reasons.  For example,
+   * if an authority is in the form "invalid_authority" (note the "_"), rather than return null,
+   * we return the input.  This is because the return value, in conjunction with getOverridenPort,
+   * are used by the SSL library to reconstruct the actual authority.  It /already/ has a
+   * connection to the port, independent of this function.
+   *
+   * <p>Note: if the defaultAuthority has a port number in it and is also bad, this code will do
+   * the wrong thing.  An example wrong behavior would be "invalid_host:443".   Registry based
+   * authorities do not have ports, so this is even more wrong than before.  Sorry.
+   */
+  @VisibleForTesting
+  String getOverridenHost() {
+    URI uri = GrpcUtil.authorityToUri(defaultAuthority);
+    if (uri.getHost() != null) {
+      return uri.getHost();
+    }
+
+    return defaultAuthority;
+  }
+
+  @VisibleForTesting
+  int getOverridenPort() {
+    URI uri = GrpcUtil.authorityToUri(defaultAuthority);
+    if (uri.getPort() != -1) {
+      return uri.getPort();
+    }
+
+    return address.getPort();
+  }
+
+  @Override
+  public void shutdown(Status reason) {
+    synchronized (lock) {
+      if (goAwayStatus != null) {
+        return;
+      }
+
+      goAwayStatus = reason;
+      listener.transportShutdown(goAwayStatus);
+      stopIfNecessary();
+    }
+  }
+
+  @Override
+  public void shutdownNow(Status reason) {
+    shutdown(reason);
+    synchronized (lock) {
+      Iterator<Map.Entry<Integer, OkHttpClientStream>> it = streams.entrySet().iterator();
+      while (it.hasNext()) {
+        Map.Entry<Integer, OkHttpClientStream> entry = it.next();
+        it.remove();
+        entry.getValue().transportState().transportReportStatus(reason, false, new Metadata());
+      }
+
+      for (OkHttpClientStream stream : pendingStreams) {
+        stream.transportState().transportReportStatus(reason, true, new Metadata());
+      }
+      pendingStreams.clear();
+      maybeClearInUse();
+
+      stopIfNecessary();
+    }
+  }
+
+  @Override
+  public Attributes getAttributes() {
+    return attributes;
+  }
+
+  /**
+   * Gets all active streams as an array.
+   */
+  OkHttpClientStream[] getActiveStreams() {
+    synchronized (lock) {
+      return streams.values().toArray(EMPTY_STREAM_ARRAY);
+    }
+  }
+
+  @VisibleForTesting
+  ClientFrameHandler getHandler() {
+    return clientFrameHandler;
+  }
+
+  @VisibleForTesting
+  int getPendingStreamSize() {
+    synchronized (lock) {
+      return pendingStreams.size();
+    }
+  }
+
+  /**
+   * Finish all active streams due to an IOException, then close the transport.
+   */
+  @Override
+  public void onException(Throwable failureCause) {
+    Preconditions.checkNotNull(failureCause, "failureCause");
+    Status status = Status.UNAVAILABLE.withCause(failureCause);
+    startGoAway(0, ErrorCode.INTERNAL_ERROR, status);
+  }
+
+  /**
+   * Send GOAWAY to the server, then finish all active streams and close the transport.
+   */
+  private void onError(ErrorCode errorCode, String moreDetail) {
+    startGoAway(0, errorCode, toGrpcStatus(errorCode).augmentDescription(moreDetail));
+  }
+
+  private void startGoAway(int lastKnownStreamId, ErrorCode errorCode, Status status) {
+    synchronized (lock) {
+      if (goAwayStatus == null) {
+        goAwayStatus = status;
+        listener.transportShutdown(status);
+      }
+      if (errorCode != null && !goAwaySent) {
+        // Send GOAWAY with lastGoodStreamId of 0, since we don't expect any server-initiated
+        // streams. The GOAWAY is part of graceful shutdown.
+        goAwaySent = true;
+        frameWriter.goAway(0, errorCode, new byte[0]);
+      }
+
+      Iterator<Map.Entry<Integer, OkHttpClientStream>> it = streams.entrySet().iterator();
+      while (it.hasNext()) {
+        Map.Entry<Integer, OkHttpClientStream> entry = it.next();
+        if (entry.getKey() > lastKnownStreamId) {
+          it.remove();
+          entry.getValue().transportState().transportReportStatus(
+              status, RpcProgress.REFUSED, false, new Metadata());
+        }
+      }
+
+      for (OkHttpClientStream stream : pendingStreams) {
+        stream.transportState().transportReportStatus(
+            status, RpcProgress.REFUSED, true, new Metadata());
+      }
+      pendingStreams.clear();
+      maybeClearInUse();
+
+      stopIfNecessary();
+    }
+  }
+
+  /**
+   * Called when a stream is closed, we do things like:
+   * <ul>
+   * <li>Removing the stream from the map.
+   * <li>Optionally reporting the status.
+   * <li>Starting pending streams if we can.
+   * <li>Stopping the transport if this is the last live stream under a go-away status.
+   * </ul>
+   *
+   * @param streamId the Id of the stream.
+   * @param status the final status of this stream, null means no need to report.
+   * @param stopDelivery interrupt queued messages in the deframer
+   * @param errorCode reset the stream with this ErrorCode if not null.
+   * @param trailers the trailers received if not null
+   */
+  void finishStream(
+      int streamId,
+      @Nullable Status status,
+      RpcProgress rpcProgress,
+      boolean stopDelivery,
+      @Nullable ErrorCode errorCode,
+      @Nullable Metadata trailers) {
+    synchronized (lock) {
+      OkHttpClientStream stream = streams.remove(streamId);
+      if (stream != null) {
+        if (errorCode != null) {
+          frameWriter.rstStream(streamId, ErrorCode.CANCEL);
+        }
+        if (status != null) {
+          stream
+              .transportState()
+              .transportReportStatus(
+                  status,
+                  rpcProgress,
+                  stopDelivery,
+                  trailers != null ? trailers : new Metadata());
+        }
+        if (!startPendingStreams()) {
+          stopIfNecessary();
+          maybeClearInUse();
+        }
+      }
+    }
+  }
+
+  /**
+   * When the transport is in goAway state, we should stop it once all active streams finish.
+   */
+  @GuardedBy("lock")
+  private void stopIfNecessary() {
+    if (!(goAwayStatus != null && streams.isEmpty() && pendingStreams.isEmpty())) {
+      return;
+    }
+    if (stopped) {
+      return;
+    }
+    stopped = true;
+
+    if (keepAliveManager != null) {
+      keepAliveManager.onTransportTermination();
+      // KeepAliveManager should stop using the scheduler after onTransportTermination gets called.
+      scheduler = SharedResourceHolder.release(TIMER_SERVICE, scheduler);
+    }
+
+    if (ping != null) {
+      ping.failed(getPingFailure());
+      ping = null;
+    }
+
+    if (!goAwaySent) {
+      // Send GOAWAY with lastGoodStreamId of 0, since we don't expect any server-initiated
+      // streams. The GOAWAY is part of graceful shutdown.
+      goAwaySent = true;
+      frameWriter.goAway(0, ErrorCode.NO_ERROR, new byte[0]);
+    }
+
+    // We will close the underlying socket in the writing thread to break out the reader
+    // thread, which will close the frameReader and notify the listener.
+    frameWriter.close();
+  }
+
+  @GuardedBy("lock")
+  private void maybeClearInUse() {
+    if (inUse) {
+      if (pendingStreams.isEmpty() && streams.isEmpty()) {
+        inUse = false;
+        listener.transportInUse(false);
+        if (keepAliveManager != null) {
+          // We don't have any active streams. No need to do keepalives any more.
+          // Again, we have to call this inside the lock to avoid the race between onTransportIdle
+          // and onTransportActive.
+          keepAliveManager.onTransportIdle();
+        }
+      }
+    }
+  }
+
+  @GuardedBy("lock")
+  private void setInUse() {
+    if (!inUse) {
+      inUse = true;
+      listener.transportInUse(true);
+      if (keepAliveManager != null) {
+        // We have a new stream. We might need to do keepalives now.
+        // Note that we have to do this inside the lock to avoid calling
+        // KeepAliveManager.onTransportActive and KeepAliveManager.onTransportIdle in the wrong
+        // order.
+        keepAliveManager.onTransportActive();
+      }
+    }
+  }
+
+  private Throwable getPingFailure() {
+    synchronized (lock) {
+      if (goAwayStatus != null) {
+        return goAwayStatus.asException();
+      } else {
+        return Status.UNAVAILABLE.withDescription("Connection closed").asException();
+      }
+    }
+  }
+
+  boolean mayHaveCreatedStream(int streamId) {
+    synchronized (lock) {
+      return streamId < nextStreamId && (streamId & 1) == 1;
+    }
+  }
+
+  OkHttpClientStream getStream(int streamId) {
+    synchronized (lock) {
+      return streams.get(streamId);
+    }
+  }
+
+  /**
+   * Returns a Grpc status corresponding to the given ErrorCode.
+   */
+  @VisibleForTesting
+  static Status toGrpcStatus(ErrorCode code) {
+    Status status = ERROR_CODE_TO_STATUS.get(code);
+    return status != null ? status : Status.UNKNOWN.withDescription(
+        "Unknown http2 error code: " + code.httpCode);
+  }
+
+  @Override
+  public ListenableFuture<SocketStats> getStats() {
+    SettableFuture<SocketStats> ret = SettableFuture.create();
+    synchronized (lock) {
+      if (socket == null) {
+        ret.set(new SocketStats(
+            transportTracer.getStats(),
+            /*local=*/ null,
+            /*remote=*/ null,
+            new InternalChannelz.SocketOptions.Builder().build(),
+            /*security=*/ null));
+      } else {
+        ret.set(new SocketStats(
+            transportTracer.getStats(),
+            socket.getLocalSocketAddress(),
+            socket.getRemoteSocketAddress(),
+            Utils.getSocketOptions(socket),
+            securityInfo));
+      }
+      return ret;
+    }
+  }
+
+  /**
+   * Runnable which reads frames and dispatches them to in flight calls.
+   */
+  @VisibleForTesting
+  class ClientFrameHandler implements FrameReader.Handler, Runnable {
+    FrameReader frameReader;
+    boolean firstSettings = true;
+
+    ClientFrameHandler(FrameReader frameReader) {
+      this.frameReader = frameReader;
+    }
+
+    @Override
+    public void run() {
+      String threadName = Thread.currentThread().getName();
+      if (!GrpcUtil.IS_RESTRICTED_APPENGINE) {
+        Thread.currentThread().setName("OkHttpClientTransport");
+      }
+      try {
+        // Read until the underlying socket closes.
+        while (frameReader.nextFrame(this)) {
+          if (keepAliveManager != null) {
+            keepAliveManager.onDataReceived();
+          }
+        }
+        // frameReader.nextFrame() returns false when the underlying read encounters an IOException,
+        // it may be triggered by the socket closing, in such case, the startGoAway() will do
+        // nothing, otherwise, we finish all streams since it's a real IO issue.
+        startGoAway(0, ErrorCode.INTERNAL_ERROR,
+            Status.UNAVAILABLE.withDescription("End of stream or IOException"));
+      } catch (Throwable t) {
+        // TODO(madongfly): Send the exception message to the server.
+        startGoAway(
+            0, 
+            ErrorCode.PROTOCOL_ERROR, 
+            Status.UNAVAILABLE.withDescription("error in frame handler").withCause(t));
+      } finally {
+        try {
+          frameReader.close();
+        } catch (IOException ex) {
+          log.log(Level.INFO, "Exception closing frame reader", ex);
+        }
+        listener.transportTerminated();
+        if (!GrpcUtil.IS_RESTRICTED_APPENGINE) {
+          // Restore the original thread name.
+          Thread.currentThread().setName(threadName);
+        }
+      }
+    }
+
+    /**
+     * Handle a HTTP2 DATA frame.
+     */
+    @Override
+    public void data(boolean inFinished, int streamId, BufferedSource in, int length)
+        throws IOException {
+      OkHttpClientStream stream = getStream(streamId);
+      if (stream == null) {
+        if (mayHaveCreatedStream(streamId)) {
+          frameWriter.rstStream(streamId, ErrorCode.INVALID_STREAM);
+          in.skip(length);
+        } else {
+          onError(ErrorCode.PROTOCOL_ERROR, "Received data for unknown stream: " + streamId);
+          return;
+        }
+      } else {
+        // Wait until the frame is complete.
+        in.require(length);
+
+        Buffer buf = new Buffer();
+        buf.write(in.buffer(), length);
+        synchronized (lock) {
+          stream.transportState().transportDataReceived(buf, inFinished);
+        }
+      }
+
+      // connection window update
+      connectionUnacknowledgedBytesRead += length;
+      if (connectionUnacknowledgedBytesRead >= Utils.DEFAULT_WINDOW_SIZE / 2) {
+        frameWriter.windowUpdate(0, connectionUnacknowledgedBytesRead);
+        connectionUnacknowledgedBytesRead = 0;
+      }
+    }
+
+    /**
+     * Handle HTTP2 HEADER and CONTINUATION frames.
+     */
+    @Override
+    public void headers(boolean outFinished,
+        boolean inFinished,
+        int streamId,
+        int associatedStreamId,
+        List<Header> headerBlock,
+        HeadersMode headersMode) {
+      boolean unknownStream = false;
+      synchronized (lock) {
+        OkHttpClientStream stream = streams.get(streamId);
+        if (stream == null) {
+          if (mayHaveCreatedStream(streamId)) {
+            frameWriter.rstStream(streamId, ErrorCode.INVALID_STREAM);
+          } else {
+            unknownStream = true;
+          }
+        } else {
+          stream.transportState().transportHeadersReceived(headerBlock, inFinished);
+        }
+      }
+      if (unknownStream) {
+        // We don't expect any server-initiated streams.
+        onError(ErrorCode.PROTOCOL_ERROR, "Received header for unknown stream: " + streamId);
+      }
+    }
+
+    @Override
+    public void rstStream(int streamId, ErrorCode errorCode) {
+      Status status = toGrpcStatus(errorCode).augmentDescription("Rst Stream");
+      boolean stopDelivery =
+          (status.getCode() == Code.CANCELLED || status.getCode() == Code.DEADLINE_EXCEEDED);
+      finishStream(
+          streamId, status,
+          errorCode == ErrorCode.REFUSED_STREAM ? RpcProgress.REFUSED : RpcProgress.PROCESSED,
+          stopDelivery, null, null);
+    }
+
+    @Override
+    public void settings(boolean clearPrevious, Settings settings) {
+      boolean outboundWindowSizeIncreased = false;
+      synchronized (lock) {
+        if (OkHttpSettingsUtil.isSet(settings, OkHttpSettingsUtil.MAX_CONCURRENT_STREAMS)) {
+          int receivedMaxConcurrentStreams = OkHttpSettingsUtil.get(
+              settings, OkHttpSettingsUtil.MAX_CONCURRENT_STREAMS);
+          maxConcurrentStreams = receivedMaxConcurrentStreams;
+        }
+
+        if (OkHttpSettingsUtil.isSet(settings, OkHttpSettingsUtil.INITIAL_WINDOW_SIZE)) {
+          int initialWindowSize = OkHttpSettingsUtil.get(
+              settings, OkHttpSettingsUtil.INITIAL_WINDOW_SIZE);
+          outboundWindowSizeIncreased = outboundFlow.initialOutboundWindowSize(initialWindowSize);
+        }
+        if (firstSettings) {
+          listener.transportReady();
+          firstSettings = false;
+        }
+
+        // The changed settings are not finalized until SETTINGS acknowledgment frame is sent. Any
+        // writes due to update in settings must be sent after SETTINGS acknowledgment frame,
+        // otherwise it will cause a stream error (RST_STREAM).
+        frameWriter.ackSettings(settings);
+
+        // send any pending bytes / streams
+        if (outboundWindowSizeIncreased) {
+          outboundFlow.writeStreams();
+        }
+        startPendingStreams();
+      }
+    }
+
+    @Override
+    public void ping(boolean ack, int payload1, int payload2) {
+      if (!ack) {
+        frameWriter.ping(true, payload1, payload2);
+      } else {
+        Http2Ping p = null;
+        long ackPayload = (((long) payload1) << 32) | (payload2 & 0xffffffffL);
+        synchronized (lock) {
+          if (ping != null) {
+            if (ping.payload() == ackPayload) {
+              p = ping;
+              ping = null;
+            } else {
+              log.log(Level.WARNING, String.format("Received unexpected ping ack. "
+                  + "Expecting %d, got %d", ping.payload(), ackPayload));
+            }
+          } else {
+            log.warning("Received unexpected ping ack. No ping outstanding");
+          }
+        }
+        // don't complete it while holding lock since callbacks could run immediately
+        if (p != null) {
+          p.complete();
+        }
+      }
+    }
+
+    @Override
+    public void ackSettings() {
+      // Do nothing currently.
+    }
+
+    @Override
+    public void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {
+      if (errorCode == ErrorCode.ENHANCE_YOUR_CALM) {
+        String data = debugData.utf8();
+        log.log(Level.WARNING, String.format(
+            "%s: Received GOAWAY with ENHANCE_YOUR_CALM. Debug data: %s", this, data));
+        if ("too_many_pings".equals(data)) {
+          tooManyPingsRunnable.run();
+        }
+      }
+      Status status = GrpcUtil.Http2Error.statusForCode(errorCode.httpCode)
+          .augmentDescription("Received Goaway");
+      if (debugData.size() > 0) {
+        // If a debug message was provided, use it.
+        status = status.augmentDescription(debugData.utf8());
+      }
+      startGoAway(lastGoodStreamId, null, status);
+    }
+
+    @Override
+    public void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders)
+        throws IOException {
+      // We don't accept server initiated stream.
+      frameWriter.rstStream(streamId, ErrorCode.PROTOCOL_ERROR);
+    }
+
+    @Override
+    public void windowUpdate(int streamId, long delta) {
+      if (delta == 0) {
+        String errorMsg = "Received 0 flow control window increment.";
+        if (streamId == 0) {
+          onError(ErrorCode.PROTOCOL_ERROR, errorMsg);
+        } else {
+          finishStream(
+              streamId, Status.INTERNAL.withDescription(errorMsg), RpcProgress.PROCESSED, false,
+              ErrorCode.PROTOCOL_ERROR, null);
+        }
+        return;
+      }
+
+      boolean unknownStream = false;
+      synchronized (lock) {
+        if (streamId == Utils.CONNECTION_STREAM_ID) {
+          outboundFlow.windowUpdate(null, (int) delta);
+          return;
+        }
+
+        OkHttpClientStream stream = streams.get(streamId);
+        if (stream != null) {
+          outboundFlow.windowUpdate(stream, (int) delta);
+        } else if (!mayHaveCreatedStream(streamId)) {
+          unknownStream = true;
+        }
+      }
+      if (unknownStream) {
+        onError(ErrorCode.PROTOCOL_ERROR,
+            "Received window_update for unknown stream: " + streamId);
+      }
+    }
+
+    @Override
+    public void priority(int streamId, int streamDependency, int weight, boolean exclusive) {
+      // Ignore priority change.
+      // TODO(madongfly): log
+    }
+
+    @Override
+    public void alternateService(int streamId, String origin, ByteString protocol, String host,
+        int port, long maxAge) {
+      // TODO(madongfly): Deal with alternateService propagation
+    }
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpProtocolNegotiator.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpProtocolNegotiator.java
new file mode 100644
index 0000000..1858a93
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpProtocolNegotiator.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.okhttp.internal.OptionalMethod;
+import io.grpc.okhttp.internal.Platform;
+import io.grpc.okhttp.internal.Platform.TlsExtensionType;
+import io.grpc.okhttp.internal.Protocol;
+import io.grpc.okhttp.internal.Util;
+import java.io.IOException;
+import java.net.Socket;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * A helper class located in package com.squareup.okhttp.internal for TLS negotiation.
+ */
+class OkHttpProtocolNegotiator {
+  private static final Logger logger = Logger.getLogger(OkHttpProtocolNegotiator.class.getName());
+  private static final Platform DEFAULT_PLATFORM = Platform.get();
+  private static OkHttpProtocolNegotiator NEGOTIATOR =
+      createNegotiator(OkHttpProtocolNegotiator.class.getClassLoader());
+
+  protected final Platform platform;
+
+  @VisibleForTesting
+  OkHttpProtocolNegotiator(Platform platform) {
+    this.platform = checkNotNull(platform, "platform");
+  }
+
+  public static OkHttpProtocolNegotiator get() {
+    return NEGOTIATOR;
+  }
+
+  /**
+   * Creates corresponding negotiator according to whether on Android.
+   */
+  @VisibleForTesting
+  static OkHttpProtocolNegotiator createNegotiator(ClassLoader loader) {
+    boolean android = true;
+    try {
+      // Attempt to find Android 2.3+ APIs.
+      loader.loadClass("com.android.org.conscrypt.OpenSSLSocketImpl");
+    } catch (ClassNotFoundException e1) {
+      logger.log(Level.FINE, "Unable to find Conscrypt. Skipping", e1);
+      try {
+        // Older platform before being unbundled.
+        loader.loadClass("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
+      } catch (ClassNotFoundException e2) {
+        logger.log(Level.FINE, "Unable to find any OpenSSLSocketImpl. Skipping", e2);
+        android = false;
+      }
+    }
+    return android
+        ? new AndroidNegotiator(DEFAULT_PLATFORM)
+        : new OkHttpProtocolNegotiator(DEFAULT_PLATFORM);
+  }
+
+  /**
+   * Start and wait until the negotiation is done, returns the negotiated protocol.
+   *
+   * @throws IOException if an IO error was encountered during the handshake.
+   * @throws RuntimeException if the negotiation completed, but no protocol was selected.
+   */
+  public String negotiate(
+      SSLSocket sslSocket, String hostname, @Nullable List<Protocol> protocols) throws IOException {
+    if (protocols != null) {
+      configureTlsExtensions(sslSocket, hostname, protocols);
+    }
+    try {
+      // Force handshake.
+      sslSocket.startHandshake();
+
+      String negotiatedProtocol = getSelectedProtocol(sslSocket);
+      if (negotiatedProtocol == null) {
+        throw new RuntimeException("protocol negotiation failed");
+      }
+      return negotiatedProtocol;
+    } finally {
+      platform.afterHandshake(sslSocket);
+    }
+  }
+
+  /** Configure TLS extensions. */
+  protected void configureTlsExtensions(
+      SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
+    platform.configureTlsExtensions(sslSocket, hostname, protocols);
+  }
+
+  /** Returns the negotiated protocol, or null if no protocol was negotiated. */
+  public String getSelectedProtocol(SSLSocket socket) {
+    return platform.getSelectedProtocol(socket);
+  }
+
+  @VisibleForTesting
+  static final class AndroidNegotiator extends OkHttpProtocolNegotiator {
+    // setUseSessionTickets(boolean)
+    private static final OptionalMethod<Socket> SET_USE_SESSION_TICKETS =
+        new OptionalMethod<Socket>(null, "setUseSessionTickets", Boolean.TYPE);
+    // setHostname(String)
+    private static final OptionalMethod<Socket> SET_HOSTNAME =
+        new OptionalMethod<Socket>(null, "setHostname", String.class);
+    // byte[] getAlpnSelectedProtocol()
+    private static final OptionalMethod<Socket> GET_ALPN_SELECTED_PROTOCOL =
+        new OptionalMethod<Socket>(byte[].class, "getAlpnSelectedProtocol");
+    // setAlpnProtocol(byte[])
+    private static final OptionalMethod<Socket> SET_ALPN_PROTOCOLS =
+        new OptionalMethod<Socket>(null, "setAlpnProtocols", byte[].class);
+    // byte[] getNpnSelectedProtocol()
+    private static final OptionalMethod<Socket> GET_NPN_SELECTED_PROTOCOL =
+        new OptionalMethod<Socket>(byte[].class, "getNpnSelectedProtocol");
+    // setNpnProtocol(byte[])
+    private static final OptionalMethod<Socket> SET_NPN_PROTOCOLS =
+        new OptionalMethod<Socket>(null, "setNpnProtocols", byte[].class);
+
+    AndroidNegotiator(Platform platform) {
+      super(platform);
+    }
+
+    @Override
+    public String negotiate(SSLSocket sslSocket, String hostname, List<Protocol> protocols)
+        throws IOException {
+      // First check if a protocol has already been selected, since it's possible that the user
+      // provided SSLSocketFactory has already done the handshake when creates the SSLSocket.
+      String negotiatedProtocol = getSelectedProtocol(sslSocket);
+      if (negotiatedProtocol == null) {
+        negotiatedProtocol = super.negotiate(sslSocket, hostname, protocols);
+      }
+      return negotiatedProtocol;
+    }
+
+    /**
+     * Override {@link Platform}'s configureTlsExtensions for Android older than 5.0, since OkHttp
+     * (2.3+) only support such function for Android 5.0+.
+     */
+    @Override
+    protected void configureTlsExtensions(
+        SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
+      // Enable SNI and session tickets.
+      if (hostname != null) {
+        SET_USE_SESSION_TICKETS.invokeOptionalWithoutCheckedException(sslSocket, true);
+        SET_HOSTNAME.invokeOptionalWithoutCheckedException(sslSocket, hostname);
+      }
+
+      Object[] parameters = {Platform.concatLengthPrefixed(protocols)};
+      if (platform.getTlsExtensionType() == TlsExtensionType.ALPN_AND_NPN) {
+        SET_ALPN_PROTOCOLS.invokeWithoutCheckedException(sslSocket, parameters);
+      }
+
+      if (platform.getTlsExtensionType() != TlsExtensionType.NONE) {
+        SET_NPN_PROTOCOLS.invokeWithoutCheckedException(sslSocket, parameters);
+      } else {
+        throw new RuntimeException("We can not do TLS handshake on this Android version, please"
+            + " install the Google Play Services Dynamic Security Provider to use TLS");
+      }
+    }
+
+    @Override
+    public String getSelectedProtocol(SSLSocket socket) {
+      if (platform.getTlsExtensionType() == TlsExtensionType.ALPN_AND_NPN) {
+        try {
+          byte[] alpnResult =
+              (byte[]) GET_ALPN_SELECTED_PROTOCOL.invokeWithoutCheckedException(socket);
+          if (alpnResult != null) {
+            return new String(alpnResult, Util.UTF_8);
+          }
+        } catch (Exception e) {
+          // In some implementations, querying selected protocol before the handshake will fail with
+          // exception.
+        }
+      }
+
+      if (platform.getTlsExtensionType() != TlsExtensionType.NONE) {
+        try {
+          byte[] npnResult =
+              (byte[]) GET_NPN_SELECTED_PROTOCOL.invokeWithoutCheckedException(socket);
+          if (npnResult != null) {
+            return new String(npnResult, Util.UTF_8);
+          }
+        } catch (Exception e) {
+          // In some implementations, querying selected protocol before the handshake will fail with
+          // exception.
+        }
+      }
+      return null;
+    }
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpReadableBuffer.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpReadableBuffer.java
new file mode 100644
index 0000000..821f2e5
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpReadableBuffer.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import io.grpc.internal.AbstractReadableBuffer;
+import io.grpc.internal.ReadableBuffer;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * A {@link ReadableBuffer} implementation that is backed by an {@link okio.Buffer}.
+ */
+class OkHttpReadableBuffer extends AbstractReadableBuffer {
+  private final okio.Buffer buffer;
+
+  OkHttpReadableBuffer(okio.Buffer buffer) {
+    this.buffer = buffer;
+  }
+
+  @Override
+  public int readableBytes() {
+    return (int) buffer.size();
+  }
+
+  @Override
+  public int readUnsignedByte() {
+    return buffer.readByte() & 0x000000FF;
+  }
+
+  @Override
+  public void skipBytes(int length) {
+    try {
+      buffer.skip(length);
+    } catch (EOFException e) {
+      throw new IndexOutOfBoundsException(e.getMessage());
+    }
+  }
+
+  @Override
+  public void readBytes(byte[] dest, int destOffset, int length) {
+    while (length > 0) {
+      int bytesRead = buffer.read(dest, destOffset, length);
+      if (bytesRead == -1) {
+        throw new IndexOutOfBoundsException("EOF trying to read " + length + " bytes");
+      }
+      length -= bytesRead;
+      destOffset += bytesRead;
+    }
+  }
+
+  @Override
+  public void readBytes(ByteBuffer dest) {
+    // We are not using it.
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void readBytes(OutputStream dest, int length) throws IOException {
+    buffer.writeTo(dest, length);
+  }
+
+  @Override
+  public ReadableBuffer readBytes(int length) {
+    okio.Buffer buf = new okio.Buffer();
+    buf.write(buffer, length);
+    return new OkHttpReadableBuffer(buf);
+  }
+
+  @Override
+  public void close() {
+    buffer.clear();
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpSettingsUtil.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpSettingsUtil.java
new file mode 100644
index 0000000..5df8573
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpSettingsUtil.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import io.grpc.okhttp.internal.framed.Settings;
+
+/**
+ * A utility class help gRPC get/set the necessary fields of OkHttp's Settings.
+ */
+class OkHttpSettingsUtil {
+  public static final int MAX_CONCURRENT_STREAMS = Settings.MAX_CONCURRENT_STREAMS;
+  public static final int INITIAL_WINDOW_SIZE = Settings.INITIAL_WINDOW_SIZE;
+
+  public static boolean isSet(Settings settings, int id) {
+    return settings.isSet(id);
+  }
+
+  public static int get(Settings settings, int id) {
+    return settings.get(id);
+  }
+
+  public static void set(Settings settings, int id, int value) {
+    settings.set(id, 0, value);
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpTlsUpgrader.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpTlsUpgrader.java
new file mode 100644
index 0000000..0a8672c
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpTlsUpgrader.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import io.grpc.okhttp.internal.ConnectionSpec;
+import io.grpc.okhttp.internal.OkHostnameVerifier;
+import io.grpc.okhttp.internal.Protocol;
+import java.io.IOException;
+import java.net.Socket;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+/**
+ * A helper class that located in package com.squareup.okhttp, so that we can use OkHttp internals
+ * to do TLS upgrading.
+ */
+final class OkHttpTlsUpgrader {
+
+  /*
+   * List of ALPN/NPN protocols in order of preference. GRPC_EXP requires that
+   * HTTP_2 be present and that GRPC_EXP should be preferenced over HTTP_2.
+   */
+  @VisibleForTesting
+  static final List<Protocol> TLS_PROTOCOLS =
+      Collections.unmodifiableList(Arrays.<Protocol>asList(Protocol.GRPC_EXP, Protocol.HTTP_2));
+
+  /**
+   * Upgrades given Socket to be a SSLSocket.
+   *
+   * @throws IOException if an IO error was encountered during the upgrade handshake.
+   * @throws RuntimeException if the upgrade negotiation failed.
+   */
+  public static SSLSocket upgrade(SSLSocketFactory sslSocketFactory,
+      HostnameVerifier hostnameVerifier, Socket socket, String host, int port,
+      ConnectionSpec spec) throws IOException {
+    Preconditions.checkNotNull(sslSocketFactory, "sslSocketFactory");
+    Preconditions.checkNotNull(socket, "socket");
+    Preconditions.checkNotNull(spec, "spec");
+    SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(
+        socket, host, port, true /* auto close */);
+    spec.apply(sslSocket, false);
+    String negotiatedProtocol = OkHttpProtocolNegotiator.get().negotiate(
+        sslSocket, host, spec.supportsTlsExtensions() ? TLS_PROTOCOLS : null);
+    Preconditions.checkState(
+        TLS_PROTOCOLS.contains(Protocol.get(negotiatedProtocol)),
+        "Only " + TLS_PROTOCOLS + " are supported, but negotiated protocol is %s",
+        negotiatedProtocol);
+
+    if (hostnameVerifier == null) {
+      hostnameVerifier = OkHostnameVerifier.INSTANCE;
+    }
+    if (!hostnameVerifier.verify(canonicalizeHost(host), sslSocket.getSession())) {
+      throw new SSLPeerUnverifiedException("Cannot verify hostname: " + host);
+    }
+    return sslSocket;
+  }
+
+  /**
+   * Converts a host from URI to X509 format.
+   *
+   * <p>IPv6 host addresses derived from URIs are enclosed in square brackets per RFC2732, but
+   * omit these brackets in X509 certificate subjectAltName extensions per RFC5280.
+   *
+   * @see <a href="https://www.ietf.org/rfc/rfc2732.txt">RFC2732</a>
+   * @see <a href="https://tools.ietf.org/html/rfc5280#section-4.2.1.6">RFC5280</a>
+   *
+   * @return {@param host} in a form consistent with X509 certificates
+   */
+  @VisibleForTesting
+  static String canonicalizeHost(String host) {
+    if (host.startsWith("[") && host.endsWith("]")) {
+      return host.substring(1, host.length() - 1);
+    }
+    return host;
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpWritableBuffer.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpWritableBuffer.java
new file mode 100644
index 0000000..9cb553d
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpWritableBuffer.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import io.grpc.internal.WritableBuffer;
+import okio.Buffer;
+
+class OkHttpWritableBuffer implements WritableBuffer {
+
+  private final Buffer buffer;
+  private int writableBytes;
+  private int readableBytes;
+
+  OkHttpWritableBuffer(Buffer buffer, int capacity) {
+    this.buffer = buffer;
+    writableBytes = capacity;
+  }
+
+  @Override
+  public void write(byte[] src, int srcIndex, int length) {
+    buffer.write(src, srcIndex, length);
+    writableBytes -= length;
+    readableBytes += length;
+  }
+
+  @Override
+  public void write(byte b) {
+    buffer.writeByte(b);
+    writableBytes -= 1;
+    readableBytes += 1;
+  }
+
+  @Override
+  public int writableBytes() {
+    return writableBytes;
+  }
+
+  @Override
+  public int readableBytes() {
+    return readableBytes;
+  }
+
+  @Override
+  public void release() {
+  }
+
+  Buffer buffer() {
+    return buffer;
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OkHttpWritableBufferAllocator.java b/okhttp/src/main/java/io/grpc/okhttp/OkHttpWritableBufferAllocator.java
new file mode 100644
index 0000000..481ada6
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/OkHttpWritableBufferAllocator.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import io.grpc.internal.WritableBuffer;
+import io.grpc.internal.WritableBufferAllocator;
+import okio.Buffer;
+
+/**
+ * The default allocator for {@link OkHttpWritableBuffer}s used by the OkHttp transport. OkHttp
+ * cannot receive buffers larger than the max DATA frame size - 1 so we must set an upper bound on
+ * the allocated buffer size here.
+ */
+class OkHttpWritableBufferAllocator implements WritableBufferAllocator {
+
+  // Use 4k as our minimum buffer size.
+  private static final int MIN_BUFFER = 4096;
+
+  // Set the maximum buffer size to 1MB
+  private static final int MAX_BUFFER = 1024 * 1024;
+
+  /**
+   * Construct a new instance.
+   */
+  OkHttpWritableBufferAllocator() {
+  }
+
+  /**
+   * For OkHttp we will often return a buffer smaller than the requested capacity as this is the
+   * mechanism for chunking a large GRPC message over many DATA frames.
+   */
+  @Override
+  public WritableBuffer allocate(int capacityHint) {
+    capacityHint = Math.min(MAX_BUFFER, Math.max(MIN_BUFFER, capacityHint));
+    return new OkHttpWritableBuffer(new Buffer(), capacityHint);
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/OutboundFlowController.java b/okhttp/src/main/java/io/grpc/okhttp/OutboundFlowController.java
new file mode 100644
index 0000000..c81c323
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/OutboundFlowController.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static io.grpc.okhttp.Utils.CONNECTION_STREAM_ID;
+import static io.grpc.okhttp.Utils.DEFAULT_WINDOW_SIZE;
+import static java.lang.Math.ceil;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+import com.google.common.base.Preconditions;
+import io.grpc.okhttp.internal.framed.FrameWriter;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Queue;
+import javax.annotation.Nullable;
+import okio.Buffer;
+
+/**
+ * Simple outbound flow controller that evenly splits the connection window across all existing
+ * streams.
+ */
+class OutboundFlowController {
+  private final OkHttpClientTransport transport;
+  private final FrameWriter frameWriter;
+  private int initialWindowSize = DEFAULT_WINDOW_SIZE;
+  private final OutboundFlowState connectionState = new OutboundFlowState(CONNECTION_STREAM_ID);
+
+  OutboundFlowController(OkHttpClientTransport transport, FrameWriter frameWriter) {
+    this.transport = Preconditions.checkNotNull(transport, "transport");
+    this.frameWriter = Preconditions.checkNotNull(frameWriter, "frameWriter");
+  }
+
+  /**
+   * Adjusts outbound window size requested by peer. When window size is increased, it does not send
+   * any pending frames. If this method returns {@code true}, the caller should call {@link
+   * #writeStreams()} after settings ack.
+   *
+   * <p>Must be called with holding transport lock.
+   *
+   * @return true, if new window size is increased, false otherwise.
+   */
+  boolean initialOutboundWindowSize(int newWindowSize) {
+    if (newWindowSize < 0) {
+      throw new IllegalArgumentException("Invalid initial window size: " + newWindowSize);
+    }
+
+    int delta = newWindowSize - initialWindowSize;
+    initialWindowSize = newWindowSize;
+    for (OkHttpClientStream stream : transport.getActiveStreams()) {
+      OutboundFlowState state = (OutboundFlowState) stream.getOutboundFlowState();
+      if (state == null) {
+        // Create the OutboundFlowState with the new window size.
+        state = new OutboundFlowState(stream);
+        stream.setOutboundFlowState(state);
+      } else {
+        state.incrementStreamWindow(delta);
+      }
+    }
+
+    return delta > 0;
+  }
+
+  /**
+   * Update the outbound window for given stream, or for the connection if stream is null. Returns
+   * the new value of the window size.
+   *
+   * <p>Must be called with holding transport lock.
+   */
+  int windowUpdate(@Nullable OkHttpClientStream stream, int delta) {
+    final int updatedWindow;
+    if (stream == null) {
+      // Update the connection window and write any pending frames for all streams.
+      updatedWindow = connectionState.incrementStreamWindow(delta);
+      writeStreams();
+    } else {
+      // Update the stream window and write any pending frames for the stream.
+      OutboundFlowState state = state(stream);
+      updatedWindow = state.incrementStreamWindow(delta);
+
+      WriteStatus writeStatus = new WriteStatus();
+      state.writeBytes(state.writableWindow(), writeStatus);
+      if (writeStatus.hasWritten()) {
+        flush();
+      }
+    }
+    return updatedWindow;
+  }
+
+  /**
+   * Must be called with holding transport lock.
+   */
+  void data(boolean outFinished, int streamId, Buffer source, boolean flush) {
+    Preconditions.checkNotNull(source, "source");
+
+    OkHttpClientStream stream = transport.getStream(streamId);
+    if (stream == null) {
+      // This is possible for a stream that has received end-of-stream from server (but hasn't sent
+      // end-of-stream), and was removed from the transport stream map.
+      // In such case, we just throw away the data.
+      return;
+    }
+
+    OutboundFlowState state = state(stream);
+    int window = state.writableWindow();
+    boolean framesAlreadyQueued = state.hasFrame();
+
+    OutboundFlowState.Frame frame = state.newFrame(source, outFinished);
+    if (!framesAlreadyQueued && window >= frame.size()) {
+      // Window size is large enough to send entire data frame
+      frame.write();
+      if (flush) {
+        flush();
+      }
+      return;
+    }
+
+    // Enqueue the frame to be written when the window size permits.
+    frame.enqueue();
+
+    if (framesAlreadyQueued || window <= 0) {
+      // Stream already has frames pending or is stalled, don't send anything now.
+      if (flush) {
+        flush();
+      }
+      return;
+    }
+
+    // Create and send a partial frame up to the window size.
+    frame.split(window).write();
+    if (flush) {
+      flush();
+    }
+  }
+
+  void flush() {
+    try {
+      frameWriter.flush();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private OutboundFlowState state(OkHttpClientStream stream) {
+    OutboundFlowState state = (OutboundFlowState) stream.getOutboundFlowState();
+    if (state == null) {
+      state = new OutboundFlowState(stream);
+      stream.setOutboundFlowState(state);
+    }
+    return state;
+  }
+
+  /**
+   * Writes as much data for all the streams as possible given the current flow control windows.
+   *
+   * <p>Must be called with holding transport lock.
+   */
+  void writeStreams() {
+    OkHttpClientStream[] streams = transport.getActiveStreams();
+    int connectionWindow = connectionState.window();
+    for (int numStreams = streams.length; numStreams > 0 && connectionWindow > 0;) {
+      int nextNumStreams = 0;
+      int windowSlice = (int) ceil(connectionWindow / (float) numStreams);
+      for (int index = 0; index < numStreams && connectionWindow > 0; ++index) {
+        OkHttpClientStream stream = streams[index];
+        OutboundFlowState state = state(stream);
+
+        int bytesForStream = min(connectionWindow, min(state.unallocatedBytes(), windowSlice));
+        if (bytesForStream > 0) {
+          state.allocateBytes(bytesForStream);
+          connectionWindow -= bytesForStream;
+        }
+
+        if (state.unallocatedBytes() > 0) {
+          // There is more data to process for this stream. Add it to the next
+          // pass.
+          streams[nextNumStreams++] = stream;
+        }
+      }
+      numStreams = nextNumStreams;
+    }
+
+    // Now take one last pass through all of the streams and write any allocated bytes.
+    WriteStatus writeStatus = new WriteStatus();
+    for (OkHttpClientStream stream : transport.getActiveStreams()) {
+      OutboundFlowState state = state(stream);
+      state.writeBytes(state.allocatedBytes(), writeStatus);
+      state.clearAllocatedBytes();
+    }
+
+    if (writeStatus.hasWritten()) {
+      flush();
+    }
+  }
+
+  /**
+   * Simple status that keeps track of the number of writes performed.
+   */
+  private static final class WriteStatus {
+    int numWrites;
+
+    void incrementNumWrites() {
+      numWrites++;
+    }
+
+    boolean hasWritten() {
+      return numWrites > 0;
+    }
+  }
+
+  /**
+   * The outbound flow control state for a single stream.
+   */
+  private final class OutboundFlowState {
+    final Queue<Frame> pendingWriteQueue;
+    final int streamId;
+    int queuedBytes;
+    int window = initialWindowSize;
+    int allocatedBytes;
+    OkHttpClientStream stream;
+
+    OutboundFlowState(int streamId) {
+      this.streamId = streamId;
+      pendingWriteQueue = new ArrayDeque<Frame>(2);
+    }
+
+    OutboundFlowState(OkHttpClientStream stream) {
+      this(stream.id());
+      this.stream = stream;
+    }
+
+    int window() {
+      return window;
+    }
+
+    void allocateBytes(int bytes) {
+      allocatedBytes += bytes;
+    }
+
+    int allocatedBytes() {
+      return allocatedBytes;
+    }
+
+    int unallocatedBytes() {
+      return streamableBytes() - allocatedBytes;
+    }
+
+    void clearAllocatedBytes() {
+      allocatedBytes = 0;
+    }
+
+    /**
+     * Increments the flow control window for this stream by the given delta and returns the new
+     * value.
+     */
+    int incrementStreamWindow(int delta) {
+      if (delta > 0 && Integer.MAX_VALUE - delta < window) {
+        throw new IllegalArgumentException("Window size overflow for stream: " + streamId);
+      }
+      window += delta;
+
+      return window;
+    }
+
+    /**
+     * Returns the maximum writable window (minimum of the stream and connection windows).
+     */
+    int writableWindow() {
+      return min(window, connectionState.window());
+    }
+
+    int streamableBytes() {
+      return max(0, min(window, queuedBytes));
+    }
+
+    /**
+     * Creates a new frame with the given values but does not add it to the pending queue.
+     */
+    Frame newFrame(Buffer data, boolean endStream) {
+      return new Frame(data, endStream);
+    }
+
+    /**
+     * Indicates whether or not there are frames in the pending queue.
+     */
+    boolean hasFrame() {
+      return !pendingWriteQueue.isEmpty();
+    }
+
+    /**
+     * Returns the head of the pending queue, or {@code null} if empty.
+     */
+    private Frame peek() {
+      return pendingWriteQueue.peek();
+    }
+
+    /**
+     * Writes up to the number of bytes from the pending queue.
+     */
+    int writeBytes(int bytes, WriteStatus writeStatus) {
+      int bytesAttempted = 0;
+      int maxBytes = min(bytes, writableWindow());
+      while (hasFrame()) {
+        Frame pendingWrite = peek();
+        if (maxBytes >= pendingWrite.size()) {
+          // Window size is large enough to send entire data frame
+          writeStatus.incrementNumWrites();
+          bytesAttempted += pendingWrite.size();
+          pendingWrite.write();
+        } else if (maxBytes <= 0) {
+          // No data from the current frame can be written - we're done.
+          // We purposely check this after first testing the size of the
+          // pending frame to properly handle zero-length frame.
+          break;
+        } else {
+          // We can send a partial frame
+          Frame partialFrame = pendingWrite.split(maxBytes);
+          writeStatus.incrementNumWrites();
+          bytesAttempted += partialFrame.size();
+          partialFrame.write();
+        }
+
+        // Update the threshold.
+        maxBytes = min(bytes - bytesAttempted, writableWindow());
+      }
+      return bytesAttempted;
+    }
+
+    /**
+     * A wrapper class around the content of a data frame.
+     */
+    private final class Frame {
+      final Buffer data;
+      final boolean endStream;
+      boolean enqueued;
+
+      Frame(Buffer data, boolean endStream) {
+        this.data = data;
+        this.endStream = endStream;
+      }
+
+      /**
+       * Gets the total size (in bytes) of this frame including the data and padding.
+       */
+      int size() {
+        return (int) data.size();
+      }
+
+      void enqueue() {
+        if (!enqueued) {
+          enqueued = true;
+          pendingWriteQueue.offer(this);
+
+          // Increment the number of pending bytes for this stream.
+          queuedBytes += size();
+        }
+      }
+
+      /**
+       * Writes the frame and decrements the stream and connection window sizes. If the frame is in
+       * the pending queue, the written bytes are removed from this branch of the priority tree.
+       */
+      void write() {
+        // Using a do/while loop because if the buffer is empty we still need to call
+        // the writer once to send the empty frame.
+        do {
+          int bytesToWrite = size();
+          int frameBytes = min(bytesToWrite, frameWriter.maxDataLength());
+          if (frameBytes == bytesToWrite) {
+            // All the bytes fit into a single HTTP/2 frame, just send it all.
+            connectionState.incrementStreamWindow(-bytesToWrite);
+            incrementStreamWindow(-bytesToWrite);
+            try {
+              frameWriter.data(endStream, streamId, data, bytesToWrite);
+            } catch (IOException e) {
+              throw new RuntimeException(e);
+            }
+            stream.transportState().onSentBytes(bytesToWrite);
+
+            if (enqueued) {
+              // It's enqueued - remove it from the head of the pending write queue.
+              queuedBytes -= bytesToWrite;
+              pendingWriteQueue.remove(this);
+            }
+            return;
+          }
+
+          // Split a chunk that will fit into a single HTTP/2 frame and write it.
+          Frame frame = split(frameBytes);
+          frame.write();
+        } while (size() > 0);
+      }
+
+      /**
+       * Creates a new frame that is a view of this frame's data. The {@code maxBytes} are first
+       * split from the data buffer. If not all the requested bytes are available, the remaining
+       * bytes are then split from the padding (if available).
+       *
+       * @param maxBytes the maximum number of bytes that is allowed in the created frame.
+       * @return the partial frame.
+       */
+      Frame split(int maxBytes) {
+        // The requested maxBytes should always be less than the size of this frame.
+        assert maxBytes < size() : "Attempting to split a frame for the full size.";
+
+        // Get the portion of the data buffer to be split. Limit to the readable bytes.
+        int dataSplit = min(maxBytes, (int) data.size());
+
+        Buffer splitSlice = new Buffer();
+        splitSlice.write(data, dataSplit);
+
+        Frame frame = new Frame(splitSlice, false);
+
+        if (enqueued) {
+          queuedBytes -= dataSplit;
+        }
+        return frame;
+      }
+    }
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/Utils.java b/okhttp/src/main/java/io/grpc/okhttp/Utils.java
new file mode 100644
index 0000000..6a0dcd7
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/Utils.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import com.google.common.base.Preconditions;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalMetadata;
+import io.grpc.Metadata;
+import io.grpc.internal.TransportFrameUtil;
+import io.grpc.okhttp.internal.CipherSuite;
+import io.grpc.okhttp.internal.ConnectionSpec;
+import io.grpc.okhttp.internal.framed.Header;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Common utility methods for OkHttp transport.
+ */
+class Utils {
+  private static final Logger log = Logger.getLogger(Utils.class.getName());
+
+  static final int DEFAULT_WINDOW_SIZE = 65535;
+  static final int CONNECTION_STREAM_ID = 0;
+
+  public static Metadata convertHeaders(List<Header> http2Headers) {
+    return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers));
+  }
+
+  public static Metadata convertTrailers(List<Header> http2Headers) {
+    return InternalMetadata.newMetadata(convertHeadersToArray(http2Headers));
+  }
+
+  private static byte[][] convertHeadersToArray(List<Header> http2Headers) {
+    byte[][] headerValues = new byte[http2Headers.size() * 2][];
+    int i = 0;
+    for (Header header : http2Headers) {
+      headerValues[i++] = header.name.toByteArray();
+      headerValues[i++] = header.value.toByteArray();
+    }
+    return TransportFrameUtil.toRawSerializedHeaders(headerValues);
+  }
+
+  /**
+   * Converts an instance of {@link com.squareup.okhttp.ConnectionSpec} for a secure connection into
+   * that of {@link ConnectionSpec} in the current package.
+   *
+   * @throws IllegalArgumentException
+   *         If {@code spec} is not with TLS
+   */
+  static ConnectionSpec convertSpec(com.squareup.okhttp.ConnectionSpec spec) {
+    Preconditions.checkArgument(spec.isTls(), "plaintext ConnectionSpec is not accepted");
+
+    List<com.squareup.okhttp.TlsVersion> tlsVersionList = spec.tlsVersions();
+    String[] tlsVersions = new String[tlsVersionList.size()];
+    for (int i = 0; i < tlsVersions.length; i++) {
+      tlsVersions[i] = tlsVersionList.get(i).javaName();
+    }
+
+    List<com.squareup.okhttp.CipherSuite> cipherSuiteList = spec.cipherSuites();
+    CipherSuite[] cipherSuites = new CipherSuite[cipherSuiteList.size()];
+    for (int i = 0; i < cipherSuites.length; i++) {
+      cipherSuites[i] = CipherSuite.valueOf(cipherSuiteList.get(i).name());
+    }
+
+    return new ConnectionSpec.Builder(spec.isTls())
+        .supportsTlsExtensions(spec.supportsTlsExtensions())
+        .tlsVersions(tlsVersions)
+        .cipherSuites(cipherSuites)
+        .build();
+  }
+
+  /**
+   * Attempts to capture all known socket options and return the results as a
+   * {@link InternalChannelz.SocketOptions}. If getting a socket option threw an exception,
+   * log the error to the logger and report the value as an error in the response.
+   */
+  static InternalChannelz.SocketOptions getSocketOptions(Socket socket) {
+    InternalChannelz.SocketOptions.Builder builder = new InternalChannelz.SocketOptions.Builder();
+    try {
+      builder.setSocketOptionLingerSeconds(socket.getSoLinger());
+    } catch (SocketException e) {
+      log.log(Level.SEVERE, "Exception caught while reading socket option", e);
+      builder.addOption("SO_LINGER", "channelz_internal_error");
+    }
+
+    try {
+      builder.setSocketOptionTimeoutMillis(socket.getSoTimeout());
+    } catch (Exception e) {
+      log.log(Level.SEVERE, "Exception caught while reading socket option", e);
+      builder.addOption("SO_TIMEOUT", "channelz_internal_error");
+    }
+
+    try {
+      builder.addOption("TCP_NODELAY", socket.getTcpNoDelay());
+    } catch (SocketException e) {
+      log.log(Level.SEVERE, "Exception caught while reading socket option", e);
+      builder.addOption("TCP_NODELAY", "channelz_internal_error");
+    }
+
+    try {
+      builder.addOption("SO_REUSEADDR", socket.getReuseAddress());
+    } catch (SocketException e) {
+      log.log(Level.SEVERE, "Exception caught while reading socket option", e);
+      builder.addOption("SO_REUSEADDR", "channelz_internal_error");
+    }
+
+    try {
+      builder.addOption("SO_SNDBUF", socket.getSendBufferSize());
+    } catch (SocketException e) {
+      log.log(Level.SEVERE, "Exception caught while reading socket option", e);
+      builder.addOption("SO_SNDBUF", "channelz_internal_error");
+    }
+
+    try {
+      builder.addOption("SO_RECVBUF", socket.getReceiveBufferSize());
+    } catch (SocketException e) {
+      log.log(Level.SEVERE, "Exception caught while reading socket option", e);
+      builder.addOption("SO_RECVBUF", "channelz_internal_error");
+    }
+
+    try {
+      builder.addOption("SO_KEEPALIVE", socket.getKeepAlive());
+    } catch (SocketException e) {
+      log.log(Level.SEVERE, "Exception caught while reading socket option", e);
+      builder.addOption("SO_KEEPALIVE", "channelz_internal_error");
+    }
+
+    try {
+      builder.addOption("SO_OOBINLINE", socket.getOOBInline());
+    } catch (SocketException e) {
+      log.log(Level.SEVERE, "Exception caught while reading socket option", e);
+      builder.addOption("SO_OOBINLINE", "channelz_internal_error");
+    }
+
+    try {
+      builder.addOption("IP_TOS", socket.getTrafficClass());
+    } catch (SocketException e) {
+      log.log(Level.SEVERE, "Exception caught while reading socket option", e);
+      builder.addOption("IP_TOS", "channelz_internal_error");
+    }
+    return builder.build();
+  }
+
+  private Utils() {
+    // Prevents instantiation
+  }
+}
diff --git a/okhttp/src/main/java/io/grpc/okhttp/package-info.java b/okhttp/src/main/java/io/grpc/okhttp/package-info.java
new file mode 100644
index 0000000..126bd83
--- /dev/null
+++ b/okhttp/src/main/java/io/grpc/okhttp/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/**
+ * A lightweight transport based on
+ * <a target="_blank" href="http://square.github.io/okhttp/">OkHttp</a>, mainly for use on Android
+ * (client-only).
+ */
+package io.grpc.okhttp;
diff --git a/okhttp/src/main/resources/META-INF/services/io.grpc.ManagedChannelProvider b/okhttp/src/main/resources/META-INF/services/io.grpc.ManagedChannelProvider
new file mode 100644
index 0000000..8dea07f
--- /dev/null
+++ b/okhttp/src/main/resources/META-INF/services/io.grpc.ManagedChannelProvider
@@ -0,0 +1 @@
+io.grpc.okhttp.OkHttpChannelProvider
diff --git a/okhttp/src/test/java/io/grpc/internal/AccessProtectedHack.java b/okhttp/src/test/java/io/grpc/internal/AccessProtectedHack.java
new file mode 100644
index 0000000..2e84783
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/internal/AccessProtectedHack.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.ServerStreamTracer;
+import java.util.List;
+
+/** A hack to access protected methods from io.grpc.internal. */
+public final class AccessProtectedHack {
+  public static InternalServer serverBuilderBuildTransportServer(
+      AbstractServerImplBuilder<?> builder,
+      List<ServerStreamTracer.Factory> streamTracerFactories,
+      TransportTracer.Factory transportTracerFactory) {
+    builder.transportTracerFactory = transportTracerFactory;
+    return builder.buildTransportServer(streamTracerFactories);
+  }
+
+  private AccessProtectedHack() {}
+}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/AsyncFrameWriterTest.java b/okhttp/src/test/java/io/grpc/okhttp/AsyncFrameWriterTest.java
new file mode 100644
index 0000000..479a35a
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/AsyncFrameWriterTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.verify;
+import static org.mockito.internal.verification.VerificationModeFactory.times;
+
+import io.grpc.internal.SerializingExecutor;
+import io.grpc.okhttp.AsyncFrameWriter.TransportExceptionHandler;
+import io.grpc.okhttp.internal.framed.FrameWriter;
+import java.io.IOException;
+import java.net.Socket;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class AsyncFrameWriterTest {
+
+  @Mock private Socket socket;
+  @Mock private FrameWriter frameWriter;
+
+  private QueueingExecutor queueingExecutor = new QueueingExecutor();
+  private TransportExceptionHandler transportExceptionHandler =
+      new EscalatingTransportErrorHandler();
+  private AsyncFrameWriter asyncFrameWriter =
+      new AsyncFrameWriter(transportExceptionHandler, new SerializingExecutor(queueingExecutor));
+
+  @Before
+  public void setUp() throws Exception {
+    asyncFrameWriter.becomeConnected(frameWriter, socket);
+  }
+
+  @Test
+  public void noCoalesceRequired() throws IOException {
+    asyncFrameWriter.ping(true, 0, 1);
+    asyncFrameWriter.flush();
+    queueingExecutor.runAll();
+
+    verify(frameWriter, times(1)).ping(anyBoolean(), anyInt(), anyInt());
+    verify(frameWriter, times(1)).flush();
+  }
+
+  @Test
+  public void flushCoalescing_shouldNotMergeTwoDistinctFlushes() throws IOException {
+    asyncFrameWriter.ping(true, 0, 1);
+    asyncFrameWriter.flush();
+    queueingExecutor.runAll();
+
+    asyncFrameWriter.ping(true, 0, 2);
+    asyncFrameWriter.flush();
+    queueingExecutor.runAll();
+
+    verify(frameWriter, times(2)).ping(anyBoolean(), anyInt(), anyInt());
+    verify(frameWriter, times(2)).flush();
+  }
+
+  @Test
+  public void flushCoalescing_shouldMergeTwoQueuedFlushes() throws IOException {
+    asyncFrameWriter.ping(true, 0, 1);
+    asyncFrameWriter.flush();
+    asyncFrameWriter.ping(true, 0, 2);
+    asyncFrameWriter.flush();
+
+    queueingExecutor.runAll();
+
+    InOrder inOrder = inOrder(frameWriter);
+    inOrder.verify(frameWriter, times(2)).ping(anyBoolean(), anyInt(), anyInt());
+    inOrder.verify(frameWriter).flush();
+  }
+
+  /**
+   * Executor queues incoming runnables instead of running it. Runnables can be invoked via {@link
+   * QueueingExecutor#runAll} in serial order.
+   */
+  private static class QueueingExecutor implements Executor {
+
+    private final Queue<Runnable> runnables = new ConcurrentLinkedQueue<Runnable>();
+
+    @Override
+    public void execute(Runnable command) {
+      runnables.add(command);
+    }
+
+    public void runAll() {
+      Runnable r;
+      while ((r = runnables.poll()) != null) {
+        r.run();
+      }
+    }
+  }
+
+  /** Rethrows as Assertion error. */
+  private static class EscalatingTransportErrorHandler implements TransportExceptionHandler {
+
+    @Override
+    public void onException(Throwable throwable) {
+      throw new AssertionError(throwable);
+    }
+  }
+}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/HeadersTest.java b/okhttp/src/test/java/io/grpc/okhttp/HeadersTest.java
new file mode 100644
index 0000000..bb84a41
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/HeadersTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import io.grpc.Metadata;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.okhttp.internal.framed.Header;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class HeadersTest {
+  @Test
+  public void createRequestHeaders_sanitizes() {
+    Metadata metaData = new Metadata();
+
+    // Intentionally being explicit here rather than relying on any pre-defined lists of headers,
+    // since the goal of this test is to validate the correctness of such lists in the first place.
+    metaData.put(GrpcUtil.CONTENT_TYPE_KEY, "to-be-removed");
+    metaData.put(GrpcUtil.USER_AGENT_KEY, "to-be-removed");
+    metaData.put(GrpcUtil.TE_HEADER, "to-be-removed");
+
+
+    Metadata.Key<String> userKey = Metadata.Key.of("user-key", Metadata.ASCII_STRING_MARSHALLER);
+    String userValue = "user-value";
+    metaData.put(userKey, userValue);
+
+    String path = "//testServerice/test";
+    String authority = "localhost";
+    String userAgent = "useragent";
+
+    List<Header> headers = Headers.createRequestHeaders(
+        metaData,
+        path,
+        authority,
+        userAgent,
+        false);
+
+    // 7 reserved headers, 1 user header
+    assertEquals(7 + 1, headers.size());
+    // Check the 3 reserved headers that are non pseudo
+    // Users can not create pseudo headers keys so no need to check for them here
+    assertThat(headers).contains(Headers.CONTENT_TYPE_HEADER);
+    assertThat(headers).contains(new Header(GrpcUtil.USER_AGENT_KEY.name(), userAgent));
+    assertThat(headers).contains(new Header(GrpcUtil.TE_HEADER.name(), GrpcUtil.TE_TRAILERS));
+    // Check the user header is in tact
+    assertThat(headers).contains(new Header(userKey.name(), userValue));
+  }
+}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java
new file mode 100644
index 0000000..993d5fe
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelBuilderTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static io.grpc.internal.GrpcUtil.TIMER_SERVICE;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import com.squareup.okhttp.ConnectionSpec;
+import io.grpc.NameResolver;
+import io.grpc.internal.ClientTransportFactory;
+import io.grpc.internal.FakeClock;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.SharedResourceHolder;
+import java.net.InetSocketAddress;
+import java.util.concurrent.ScheduledExecutorService;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link OkHttpChannelBuilder}.
+ */
+@RunWith(JUnit4.class)
+public class OkHttpChannelBuilderTest {
+
+  @Rule public final ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void authorityIsReadable() {
+    OkHttpChannelBuilder builder = OkHttpChannelBuilder.forAddress("original", 1234);
+    assertEquals("original:1234", builder.build().authority());
+  }
+
+  @Test
+  public void overrideAuthorityIsReadableForAddress() {
+    OkHttpChannelBuilder builder = OkHttpChannelBuilder.forAddress("original", 1234);
+    overrideAuthorityIsReadableHelper(builder, "override:5678");
+  }
+
+  @Test
+  public void overrideAuthorityIsReadableForTarget() {
+    OkHttpChannelBuilder builder = OkHttpChannelBuilder.forTarget("original:1234");
+    overrideAuthorityIsReadableHelper(builder, "override:5678");
+  }
+
+  private void overrideAuthorityIsReadableHelper(OkHttpChannelBuilder builder,
+      String overrideAuthority) {
+    builder.overrideAuthority(overrideAuthority);
+    assertEquals(overrideAuthority, builder.build().authority());
+  }
+
+  @Test
+  public void overrideAllowsInvalidAuthority() {
+    OkHttpChannelBuilder builder = new OkHttpChannelBuilder("good", 1234) {
+      @Override
+      protected String checkAuthority(String authority) {
+        return authority;
+      }
+    };
+
+    builder.overrideAuthority("[invalidauthority").usePlaintext().buildTransportFactory();
+  }
+
+  @Test
+  public void failOverrideInvalidAuthority() {
+    OkHttpChannelBuilder builder = new OkHttpChannelBuilder("good", 1234);
+
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Invalid authority:");
+    builder.overrideAuthority("[invalidauthority");
+  }
+
+  @Test
+  public void failInvalidAuthority() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Invalid host or port");
+
+    OkHttpChannelBuilder.forAddress("invalid_authority", 1234);
+  }
+
+  @Test
+  public void failForUsingClearTextSpecDirectly() {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("plaintext ConnectionSpec is not accepted");
+
+    OkHttpChannelBuilder.forAddress("host", 1234).connectionSpec(ConnectionSpec.CLEARTEXT);
+  }
+
+  @Test
+  public void allowUsingTlsConnectionSpec() {
+    OkHttpChannelBuilder.forAddress("host", 1234).connectionSpec(ConnectionSpec.MODERN_TLS);
+  }
+
+  @Test
+  public void usePlaintext_newClientTransportAllowed() {
+    OkHttpChannelBuilder builder = OkHttpChannelBuilder.forAddress("host", 1234).usePlaintext();
+    builder.buildTransportFactory().newClientTransport(new InetSocketAddress(5678),
+        new ClientTransportFactory.ClientTransportOptions());
+  }
+
+  @Test
+  public void usePlaintextDefaultPort() {
+    OkHttpChannelBuilder builder = OkHttpChannelBuilder.forAddress("host", 1234).usePlaintext();
+    assertEquals(GrpcUtil.DEFAULT_PORT_PLAINTEXT,
+        builder.getNameResolverParams().get(NameResolver.Factory.PARAMS_DEFAULT_PORT).intValue());
+  }
+
+  @Test
+  public void usePlaintextCreatesNullSocketFactory() {
+    OkHttpChannelBuilder builder = OkHttpChannelBuilder.forAddress("host", 1234);
+    assertNotNull(builder.createSocketFactory());
+
+    builder.usePlaintext();
+    assertNull(builder.createSocketFactory());
+  }
+
+  @Test
+  public void scheduledExecutorService_default() {
+    OkHttpChannelBuilder builder = OkHttpChannelBuilder.forTarget("foo");
+    ClientTransportFactory clientTransportFactory = builder.buildTransportFactory();
+    assertSame(
+        SharedResourceHolder.get(TIMER_SERVICE),
+        clientTransportFactory.getScheduledExecutorService());
+
+    SharedResourceHolder.release(
+        TIMER_SERVICE, clientTransportFactory.getScheduledExecutorService());
+    clientTransportFactory.close();
+  }
+
+  @Test
+  public void scheduledExecutorService_custom() {
+    OkHttpChannelBuilder builder = OkHttpChannelBuilder.forTarget("foo");
+    ScheduledExecutorService scheduledExecutorService =
+        new FakeClock().getScheduledExecutorService();
+
+    OkHttpChannelBuilder builder1 = builder.scheduledExecutorService(scheduledExecutorService);
+    assertSame(builder, builder1);
+
+    ClientTransportFactory clientTransportFactory = builder1.buildTransportFactory();
+
+    assertSame(scheduledExecutorService, clientTransportFactory.getScheduledExecutorService());
+
+    clientTransportFactory.close();
+  }
+}
+
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelProviderTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelProviderTest.java
new file mode 100644
index 0000000..5300776
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpChannelProviderTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import io.grpc.InternalManagedChannelProvider;
+import io.grpc.InternalServiceProviders;
+import io.grpc.ManagedChannelProvider;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link OkHttpChannelProvider}. */
+@RunWith(JUnit4.class)
+public class OkHttpChannelProviderTest {
+  private OkHttpChannelProvider provider = new OkHttpChannelProvider();
+
+  @Test
+  public void provided() {
+    for (ManagedChannelProvider current
+        : InternalServiceProviders.getCandidatesViaServiceLoader(
+            ManagedChannelProvider.class, getClass().getClassLoader())) {
+      if (current instanceof OkHttpChannelProvider) {
+        return;
+      }
+    }
+    fail("ServiceLoader unable to load OkHttpChannelProvider");
+  }
+
+  @Test
+  public void providedHardCoded() {
+    for (ManagedChannelProvider current : InternalServiceProviders.getCandidatesViaHardCoded(
+        ManagedChannelProvider.class, InternalManagedChannelProvider.HARDCODED_CLASSES)) {
+      if (current instanceof OkHttpChannelProvider) {
+        return;
+      }
+    }
+    fail("Hard coded unable to load OkHttpChannelProvider");
+  }
+
+  @Test
+  public void isAvailable() {
+    assertTrue(provider.isAvailable());
+  }
+
+  @Test
+  public void builderIsAOkHttpBuilder() {
+    assertSame(OkHttpChannelBuilder.class, provider.builderForAddress("localhost", 443).getClass());
+  }
+}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientStreamTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientStreamTest.java
new file mode 100644
index 0000000..8d8347a
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientStreamTest.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import com.google.common.io.BaseEncoding;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.Status;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.NoopClientStreamListener;
+import io.grpc.internal.StatsTraceContext;
+import io.grpc.internal.TransportTracer;
+import io.grpc.okhttp.internal.framed.ErrorCode;
+import io.grpc.okhttp.internal.framed.Header;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+@RunWith(JUnit4.class)
+public class OkHttpClientStreamTest {
+  private static final int MAX_MESSAGE_SIZE = 100;
+
+  @Mock private MethodDescriptor.Marshaller<Void> marshaller;
+  @Mock private AsyncFrameWriter frameWriter;
+  @Mock private OkHttpClientTransport transport;
+  @Mock private OutboundFlowController flowController;
+  @Captor private ArgumentCaptor<List<Header>> headersCaptor;
+
+  private final Object lock = new Object();
+  private final TransportTracer transportTracer = new TransportTracer();
+
+  private MethodDescriptor<?, ?> methodDescriptor;
+  private OkHttpClientStream stream;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    methodDescriptor = MethodDescriptor.<Void, Void>newBuilder()
+        .setType(MethodDescriptor.MethodType.UNARY)
+        .setFullMethodName("testService/test")
+        .setRequestMarshaller(marshaller)
+        .setResponseMarshaller(marshaller)
+        .build();
+
+    stream = new OkHttpClientStream(
+        methodDescriptor,
+        new Metadata(),
+        frameWriter,
+        transport,
+        flowController,
+        lock,
+        MAX_MESSAGE_SIZE,
+        "localhost",
+        "userAgent",
+        StatsTraceContext.NOOP,
+        transportTracer);
+  }
+
+  @Test
+  public void getType() {
+    assertEquals(MethodType.UNARY, stream.getType());
+  }
+
+  @Test
+  public void cancel_notStarted() {
+    final AtomicReference<Status> statusRef = new AtomicReference<Status>();
+    stream.start(new BaseClientStreamListener() {
+      @Override
+      public void closed(
+          Status status, RpcProgress rpcProgress, Metadata trailers) {
+        statusRef.set(status);
+        assertTrue(Thread.holdsLock(lock));
+      }
+    });
+
+    stream.cancel(Status.CANCELLED);
+
+    assertEquals(Status.Code.CANCELLED, statusRef.get().getCode());
+  }
+
+  @Test
+  public void cancel_started() {
+    stream.start(new BaseClientStreamListener());
+    stream.transportState().start(1234);
+    Mockito.doAnswer(new Answer<Void>() {
+      @Override
+      public Void answer(InvocationOnMock invocation) throws Throwable {
+        assertTrue(Thread.holdsLock(lock));
+        return null;
+      }
+    }).when(transport).finishStream(
+        1234, Status.CANCELLED, PROCESSED, true, ErrorCode.CANCEL, null);
+
+    stream.cancel(Status.CANCELLED);
+
+    verify(transport).finishStream(1234, Status.CANCELLED, PROCESSED,true, ErrorCode.CANCEL, null);
+  }
+
+  @Test
+  public void start_alreadyCancelled() {
+    stream.start(new BaseClientStreamListener());
+    stream.cancel(Status.CANCELLED);
+
+    stream.transportState().start(1234);
+
+    verifyNoMoreInteractions(frameWriter);
+  }
+
+  @Test
+  public void start_userAgentRemoved() {
+    Metadata metaData = new Metadata();
+    metaData.put(GrpcUtil.USER_AGENT_KEY, "misbehaving-application");
+    stream = new OkHttpClientStream(methodDescriptor, metaData, frameWriter, transport,
+        flowController, lock, MAX_MESSAGE_SIZE, "localhost", "good-application",
+        StatsTraceContext.NOOP, transportTracer);
+    stream.start(new BaseClientStreamListener());
+    stream.transportState().start(3);
+
+    verify(frameWriter).synStream(eq(false), eq(false), eq(3), eq(0), headersCaptor.capture());
+    assertThat(headersCaptor.getValue())
+        .contains(new Header(GrpcUtil.USER_AGENT_KEY.name(), "good-application"));
+  }
+
+  @Test
+  public void start_headerFieldOrder() {
+    Metadata metaData = new Metadata();
+    metaData.put(GrpcUtil.USER_AGENT_KEY, "misbehaving-application");
+    stream = new OkHttpClientStream(methodDescriptor, metaData, frameWriter, transport,
+        flowController, lock, MAX_MESSAGE_SIZE, "localhost", "good-application",
+        StatsTraceContext.NOOP, transportTracer);
+    stream.start(new BaseClientStreamListener());
+    stream.transportState().start(3);
+
+    verify(frameWriter).synStream(eq(false), eq(false), eq(3), eq(0), headersCaptor.capture());
+    assertThat(headersCaptor.getValue()).containsExactly(
+        Headers.SCHEME_HEADER,
+        Headers.METHOD_HEADER,
+        new Header(Header.TARGET_AUTHORITY, "localhost"),
+        new Header(Header.TARGET_PATH, "/" + methodDescriptor.getFullMethodName()),
+        new Header(GrpcUtil.USER_AGENT_KEY.name(), "good-application"),
+        Headers.CONTENT_TYPE_HEADER,
+        Headers.TE_HEADER)
+            .inOrder();
+  }
+
+  @Test
+  public void getUnaryRequest() {
+    MethodDescriptor<?, ?> getMethod = MethodDescriptor.<Void, Void>newBuilder()
+        .setType(MethodDescriptor.MethodType.UNARY)
+        .setFullMethodName("service/method")
+        .setIdempotent(true)
+        .setSafe(true)
+        .setRequestMarshaller(marshaller)
+        .setResponseMarshaller(marshaller)
+        .build();
+    stream = new OkHttpClientStream(getMethod, new Metadata(), frameWriter, transport,
+        flowController, lock, MAX_MESSAGE_SIZE, "localhost", "good-application",
+        StatsTraceContext.NOOP, transportTracer);
+    stream.start(new BaseClientStreamListener());
+
+    // GET streams send headers after halfClose is called.
+    verify(frameWriter, times(0)).synStream(
+        eq(false), eq(false), eq(3), eq(0), headersCaptor.capture());
+    verify(transport, times(0)).streamReadyToStart(isA(OkHttpClientStream.class));
+
+    byte[] msg = "request".getBytes(Charset.forName("UTF-8"));
+    stream.writeMessage(new ByteArrayInputStream(msg));
+    stream.halfClose();
+    verify(transport).streamReadyToStart(eq(stream));
+    stream.transportState().start(3);
+
+    verify(frameWriter)
+        .synStream(eq(true), eq(false), eq(3), eq(0), headersCaptor.capture());
+    assertThat(headersCaptor.getValue()).contains(Headers.METHOD_GET_HEADER);
+    assertThat(headersCaptor.getValue()).contains(
+        new Header(Header.TARGET_PATH, "/" + getMethod.getFullMethodName() + "?"
+            + BaseEncoding.base64().encode(msg)));
+  }
+
+  // TODO(carl-mastrangelo): extract this out into a testing/ directory and remove other definitions
+  // of it.
+  private static class BaseClientStreamListener extends NoopClientStreamListener {
+
+    @Override
+    public void messagesAvailable(MessageProducer producer) {
+      while (producer.next() != null) {}
+    }
+  }
+}
+
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportFactoryTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportFactoryTest.java
new file mode 100644
index 0000000..dbd1023
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportFactoryTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import io.grpc.internal.ClientTransportFactory;
+import io.grpc.internal.testing.AbstractClientTransportFactoryTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class OkHttpClientTransportFactoryTest extends AbstractClientTransportFactoryTest {
+  @Override
+  protected ClientTransportFactory newClientTransportFactory() {
+    return OkHttpChannelBuilder.forAddress("localhost", 0)
+        .buildTransportFactory();
+  }
+}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java
new file mode 100644
index 0000000..116fc0b
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpClientTransportTest.java
@@ -0,0 +1,2080 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.internal.ClientStreamListener.RpcProgress.PROCESSED;
+import static io.grpc.internal.ClientStreamListener.RpcProgress.REFUSED;
+import static io.grpc.internal.GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE;
+import static io.grpc.okhttp.Headers.CONTENT_TYPE_HEADER;
+import static io.grpc.okhttp.Headers.METHOD_HEADER;
+import static io.grpc.okhttp.Headers.SCHEME_HEADER;
+import static io.grpc.okhttp.Headers.TE_HEADER;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Supplier;
+import com.google.common.base.Ticker;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.CallOptions;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalChannelz.TransportStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.InternalStatus;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.Status;
+import io.grpc.Status.Code;
+import io.grpc.StatusException;
+import io.grpc.internal.AbstractStream;
+import io.grpc.internal.ClientStreamListener;
+import io.grpc.internal.ClientTransport;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.ManagedClientTransport;
+import io.grpc.internal.ProxyParameters;
+import io.grpc.internal.TransportTracer;
+import io.grpc.okhttp.OkHttpClientTransport.ClientFrameHandler;
+import io.grpc.okhttp.internal.ConnectionSpec;
+import io.grpc.okhttp.internal.framed.ErrorCode;
+import io.grpc.okhttp.internal.framed.FrameReader;
+import io.grpc.okhttp.internal.framed.FrameWriter;
+import io.grpc.okhttp.internal.framed.Header;
+import io.grpc.okhttp.internal.framed.HeadersMode;
+import io.grpc.okhttp.internal.framed.Settings;
+import io.grpc.testing.TestMethodDescriptors;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.annotation.Nullable;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLSocketFactory;
+import okio.Buffer;
+import okio.ByteString;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Matchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link OkHttpClientTransport}.
+ */
+@RunWith(JUnit4.class)
+public class OkHttpClientTransportTest {
+  private static final int TIME_OUT_MS = 2000;
+  private static final String NETWORK_ISSUE_MESSAGE = "network issue";
+  private static final String ERROR_MESSAGE = "simulated error";
+  // The gRPC header length, which includes 1 byte compression flag and 4 bytes message length.
+  private static final int HEADER_LENGTH = 5;
+  private static final Status SHUTDOWN_REASON = Status.UNAVAILABLE.withDescription("for test");
+  private static final ProxyParameters NO_PROXY = null;
+  private static final String NO_USER = null;
+  private static final String NO_PW = null;
+  private static final int DEFAULT_START_STREAM_ID = 3;
+
+  @Rule public final Timeout globalTimeout = Timeout.seconds(10);
+
+  @Mock
+  private FrameWriter frameWriter;
+
+  private MethodDescriptor<Void, Void> method = TestMethodDescriptors.voidMethod();
+
+  @Mock
+  private ManagedClientTransport.Listener transportListener;
+
+  private final SSLSocketFactory sslSocketFactory = null;
+  private final HostnameVerifier hostnameVerifier = null;
+  private final TransportTracer transportTracer = new TransportTracer();
+  private OkHttpClientTransport clientTransport;
+  private MockFrameReader frameReader;
+  private ExecutorService executor = Executors.newCachedThreadPool();
+  private long nanoTime; // backs a ticker, for testing ping round-trip time measurement
+  private SettableFuture<Void> connectedFuture;
+  private DelayConnectedCallback delayConnectedCallback;
+  private Runnable tooManyPingsRunnable = new Runnable() {
+    @Override public void run() {
+      throw new AssertionError();
+    }
+  };
+
+  /** Set up for test. */
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+    when(frameWriter.maxDataLength()).thenReturn(Integer.MAX_VALUE);
+    frameReader = new MockFrameReader();
+  }
+
+  @After
+  public void tearDown() {
+    executor.shutdownNow();
+  }
+
+  private void initTransport() throws Exception {
+    startTransport(DEFAULT_START_STREAM_ID, null, true, DEFAULT_MAX_MESSAGE_SIZE, null);
+  }
+
+  private void initTransport(int startId) throws Exception {
+    startTransport(startId, null, true, DEFAULT_MAX_MESSAGE_SIZE, null);
+  }
+
+  private void initTransportAndDelayConnected() throws Exception {
+    delayConnectedCallback = new DelayConnectedCallback();
+    startTransport(
+        DEFAULT_START_STREAM_ID, delayConnectedCallback, false, DEFAULT_MAX_MESSAGE_SIZE, null);
+  }
+
+  private void startTransport(int startId, @Nullable Runnable connectingCallback,
+      boolean waitingForConnected, int maxMessageSize, String userAgent) throws Exception {
+    connectedFuture = SettableFuture.create();
+    final Ticker ticker = new Ticker() {
+      @Override
+      public long read() {
+        return nanoTime;
+      }
+    };
+    Supplier<Stopwatch> stopwatchSupplier = new Supplier<Stopwatch>() {
+      @Override
+      public Stopwatch get() {
+        return Stopwatch.createUnstarted(ticker);
+      }
+    };
+    clientTransport = new OkHttpClientTransport(
+        userAgent,
+        executor,
+        frameReader,
+        frameWriter,
+        startId,
+        new MockSocket(frameReader),
+        stopwatchSupplier,
+        connectingCallback,
+        connectedFuture,
+        maxMessageSize,
+        tooManyPingsRunnable,
+        new TransportTracer());
+    clientTransport.start(transportListener);
+    if (waitingForConnected) {
+      connectedFuture.get(TIME_OUT_MS, TimeUnit.MILLISECONDS);
+    }
+  }
+
+  @Test
+  public void testToString() throws Exception {
+    InetSocketAddress address = InetSocketAddress.createUnresolved("hostname", 31415);
+    clientTransport = new OkHttpClientTransport(
+        address,
+        "hostname",
+        /*agent=*/ null,
+        executor,
+        sslSocketFactory,
+        hostnameVerifier,
+        OkHttpChannelBuilder.INTERNAL_DEFAULT_CONNECTION_SPEC,
+        DEFAULT_MAX_MESSAGE_SIZE,
+        NO_PROXY,
+        tooManyPingsRunnable,
+        transportTracer);
+    String s = clientTransport.toString();
+    assertTrue("Unexpected: " + s, s.contains("OkHttpClientTransport"));
+    assertTrue("Unexpected: " + s, s.contains(address.toString()));
+  }
+
+  @Test
+  public void maxMessageSizeShouldBeEnforced() throws Exception {
+    // Allow the response payloads of up to 1 byte.
+    startTransport(3, null, true, 1, null);
+
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    stream.request(1);
+    assertContainStream(3);
+    frameHandler().headers(false, false, 3, 0, grpcResponseHeaders(), HeadersMode.HTTP_20_HEADERS);
+    assertNotNull(listener.headers);
+
+    // Receive the message.
+    final String message = "Hello Client";
+    Buffer buffer = createMessageFrame(message);
+    frameHandler().data(false, 3, buffer, (int) buffer.size());
+
+    listener.waitUntilStreamClosed();
+    assertEquals(Code.RESOURCE_EXHAUSTED, listener.status.getCode());
+    shutdownAndVerify();
+  }
+
+  /**
+   * When nextFrame throws IOException, the transport should be aborted.
+   */
+  @Test
+  public void nextFrameThrowIoException() throws Exception {
+    initTransport();
+    MockStreamListener listener1 = new MockStreamListener();
+    MockStreamListener listener2 = new MockStreamListener();
+    OkHttpClientStream stream1 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream1.start(listener1);
+    stream1.request(1);
+    OkHttpClientStream stream2 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream2.start(listener2);
+    stream2.request(1);
+    assertEquals(2, activeStreamCount());
+    assertContainStream(3);
+    assertContainStream(5);
+    frameReader.throwIoExceptionForNextFrame();
+    listener1.waitUntilStreamClosed();
+    listener2.waitUntilStreamClosed();
+
+    assertEquals(0, activeStreamCount());
+    assertEquals(Status.UNAVAILABLE.getCode(), listener1.status.getCode());
+    assertEquals(NETWORK_ISSUE_MESSAGE, listener1.status.getCause().getMessage());
+    assertEquals(Status.UNAVAILABLE.getCode(), listener2.status.getCode());
+    assertEquals(NETWORK_ISSUE_MESSAGE, listener2.status.getCause().getMessage());
+    verify(transportListener, timeout(TIME_OUT_MS)).transportShutdown(isA(Status.class));
+    verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated();
+    shutdownAndVerify();
+  }
+
+  /**
+   * Test that even if an Error is thrown from the reading loop of the transport,
+   * it can still clean up and call transportShutdown() and transportTerminated() as expected
+   * by the channel.
+   */
+  @Test
+  public void nextFrameThrowsError() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    stream.request(1);
+    assertEquals(1, activeStreamCount());
+    assertContainStream(3);
+    frameReader.throwErrorForNextFrame();
+    listener.waitUntilStreamClosed();
+
+    assertEquals(0, activeStreamCount());
+    assertEquals(Status.UNAVAILABLE.getCode(), listener.status.getCode());
+    assertEquals(ERROR_MESSAGE, listener.status.getCause().getMessage());
+    verify(transportListener, timeout(TIME_OUT_MS)).transportShutdown(isA(Status.class));
+    verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void nextFrameReturnFalse() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    stream.request(1);
+    frameReader.nextFrameAtEndOfStream();
+    listener.waitUntilStreamClosed();
+    assertEquals(Status.UNAVAILABLE.getCode(), listener.status.getCode());
+    verify(transportListener, timeout(TIME_OUT_MS)).transportShutdown(isA(Status.class));
+    verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void readMessages() throws Exception {
+    initTransport();
+    final int numMessages = 10;
+    final String message = "Hello Client";
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    stream.request(numMessages);
+    assertContainStream(3);
+    frameHandler().headers(false, false, 3, 0, grpcResponseHeaders(), HeadersMode.HTTP_20_HEADERS);
+    assertNotNull(listener.headers);
+    for (int i = 0; i < numMessages; i++) {
+      Buffer buffer = createMessageFrame(message + i);
+      frameHandler().data(false, 3, buffer, (int) buffer.size());
+    }
+    frameHandler().headers(true, true, 3, 0, grpcResponseTrailers(), HeadersMode.HTTP_20_HEADERS);
+    listener.waitUntilStreamClosed();
+    assertEquals(Status.OK, listener.status);
+    assertNotNull(listener.trailers);
+    assertEquals(numMessages, listener.messages.size());
+    for (int i = 0; i < numMessages; i++) {
+      assertEquals(message + i, listener.messages.get(i));
+    }
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void receivedHeadersForInvalidStreamShouldKillConnection() throws Exception {
+    initTransport();
+    // Empty headers block without correct content type or status
+    frameHandler().headers(false, false, 3, 0, new ArrayList<Header>(),
+        HeadersMode.HTTP_20_HEADERS);
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .goAway(eq(0), eq(ErrorCode.PROTOCOL_ERROR), any(byte[].class));
+    verify(transportListener).transportShutdown(isA(Status.class));
+    verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void receivedDataForInvalidStreamShouldKillConnection() throws Exception {
+    initTransport();
+    frameHandler().data(false, 3, createMessageFrame(new String(new char[1000])), 1000);
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .goAway(eq(0), eq(ErrorCode.PROTOCOL_ERROR), any(byte[].class));
+    verify(transportListener).transportShutdown(isA(Status.class));
+    verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void invalidInboundHeadersCancelStream() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    stream.request(1);
+    assertContainStream(3);
+    // Headers block without correct content type or status
+    frameHandler().headers(false, false, 3, 0, Arrays.asList(new Header("random", "4")),
+        HeadersMode.HTTP_20_HEADERS);
+    // Now wait to receive 1000 bytes of data so we can have a better error message before
+    // cancelling the streaam.
+    frameHandler().data(false, 3, createMessageFrame(new String(new char[1000])), 1000);
+    verify(frameWriter, timeout(TIME_OUT_MS)).rstStream(eq(3), eq(ErrorCode.CANCEL));
+    assertNull(listener.headers);
+    assertEquals(Status.INTERNAL.getCode(), listener.status.getCode());
+    assertNotNull(listener.trailers);
+    assertEquals("4", listener.trailers
+        .get(Metadata.Key.of("random", Metadata.ASCII_STRING_MARSHALLER)));
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void invalidInboundTrailersPropagateToMetadata() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    stream.request(1);
+    assertContainStream(3);
+    // Headers block with EOS without correct content type or status
+    frameHandler().headers(true, true, 3, 0, Arrays.asList(new Header("random", "4")),
+        HeadersMode.HTTP_20_HEADERS);
+    assertNull(listener.headers);
+    assertEquals(Status.INTERNAL.getCode(), listener.status.getCode());
+    assertNotNull(listener.trailers);
+    assertEquals("4", listener.trailers
+        .get(Metadata.Key.of("random", Metadata.ASCII_STRING_MARSHALLER)));
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void readStatus() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    assertContainStream(3);
+    frameHandler().headers(true, true, 3, 0, grpcResponseTrailers(), HeadersMode.HTTP_20_HEADERS);
+    listener.waitUntilStreamClosed();
+    assertEquals(Status.Code.OK, listener.status.getCode());
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void receiveReset() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    assertContainStream(3);
+    frameHandler().rstStream(3, ErrorCode.PROTOCOL_ERROR);
+    listener.waitUntilStreamClosed();
+
+    assertThat(listener.status.getDescription()).contains("Rst Stream");
+    assertThat(listener.status.getCode()).isEqualTo(Code.INTERNAL);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void cancelStream() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    getStream(3).cancel(Status.CANCELLED);
+    verify(frameWriter, timeout(TIME_OUT_MS)).rstStream(eq(3), eq(ErrorCode.CANCEL));
+    listener.waitUntilStreamClosed();
+    assertEquals(OkHttpClientTransport.toGrpcStatus(ErrorCode.CANCEL).getCode(),
+        listener.status.getCode());
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void addDefaultUserAgent() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    Header userAgentHeader = new Header(GrpcUtil.USER_AGENT_KEY.name(),
+            GrpcUtil.getGrpcUserAgent("okhttp", null));
+    List<Header> expectedHeaders = Arrays.asList(SCHEME_HEADER, METHOD_HEADER,
+            new Header(Header.TARGET_AUTHORITY, "notarealauthority:80"),
+            new Header(Header.TARGET_PATH, "/" + method.getFullMethodName()),
+            userAgentHeader, CONTENT_TYPE_HEADER, TE_HEADER);
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .synStream(eq(false), eq(false), eq(3), eq(0), eq(expectedHeaders));
+    getStream(3).cancel(Status.CANCELLED);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void overrideDefaultUserAgent() throws Exception {
+    startTransport(3, null, true, DEFAULT_MAX_MESSAGE_SIZE, "fakeUserAgent");
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    List<Header> expectedHeaders = Arrays.asList(SCHEME_HEADER, METHOD_HEADER,
+        new Header(Header.TARGET_AUTHORITY, "notarealauthority:80"),
+        new Header(Header.TARGET_PATH, "/" + method.getFullMethodName()),
+        new Header(GrpcUtil.USER_AGENT_KEY.name(),
+            GrpcUtil.getGrpcUserAgent("okhttp", "fakeUserAgent")),
+        CONTENT_TYPE_HEADER, TE_HEADER);
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .synStream(eq(false), eq(false), eq(3), eq(0), eq(expectedHeaders));
+    getStream(3).cancel(Status.CANCELLED);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void cancelStreamForDeadlineExceeded() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    getStream(3).cancel(Status.DEADLINE_EXCEEDED);
+    verify(frameWriter, timeout(TIME_OUT_MS)).rstStream(eq(3), eq(ErrorCode.CANCEL));
+    listener.waitUntilStreamClosed();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void writeMessage() throws Exception {
+    initTransport();
+    final String message = "Hello Server";
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    InputStream input = new ByteArrayInputStream(message.getBytes(UTF_8));
+    assertEquals(12, input.available());
+    stream.writeMessage(input);
+    stream.flush();
+    ArgumentCaptor<Buffer> captor = ArgumentCaptor.forClass(Buffer.class);
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .data(eq(false), eq(3), captor.capture(), eq(12 + HEADER_LENGTH));
+    Buffer sentFrame = captor.getValue();
+    assertEquals(createMessageFrame(message), sentFrame);
+    stream.cancel(Status.CANCELLED);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void transportTracer_windowSizeDefault() throws Exception {
+    initTransport();
+    TransportStats stats = getTransportStats(clientTransport);
+    assertEquals(Utils.DEFAULT_WINDOW_SIZE, stats.remoteFlowControlWindow);
+    // okhttp does not track local window sizes
+    assertEquals(-1, stats.localFlowControlWindow);
+  }
+
+  @Test
+  public void transportTracer_windowSize_remote() throws Exception {
+    initTransport();
+    TransportStats before = getTransportStats(clientTransport);
+    assertEquals(Utils.DEFAULT_WINDOW_SIZE, before.remoteFlowControlWindow);
+    // okhttp does not track local window sizes
+    assertEquals(-1, before.localFlowControlWindow);
+
+    frameHandler().windowUpdate(0, 1000);
+    TransportStats after = getTransportStats(clientTransport);
+    assertEquals(Utils.DEFAULT_WINDOW_SIZE + 1000, after.remoteFlowControlWindow);
+    // okhttp does not track local window sizes
+    assertEquals(-1, after.localFlowControlWindow);
+  }
+
+  @Test
+  public void windowUpdate() throws Exception {
+    initTransport();
+    MockStreamListener listener1 = new MockStreamListener();
+    MockStreamListener listener2 = new MockStreamListener();
+    OkHttpClientStream stream1 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream1.start(listener1);
+    stream1.request(2);
+
+    OkHttpClientStream stream2 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream2.start(listener2);
+    stream2.request(2);
+    assertEquals(2, activeStreamCount());
+    stream1 = getStream(3);
+    stream2 = getStream(5);
+
+    frameHandler().headers(false, false, 3, 0, grpcResponseHeaders(), HeadersMode.HTTP_20_HEADERS);
+    frameHandler().headers(false, false, 5, 0, grpcResponseHeaders(), HeadersMode.HTTP_20_HEADERS);
+
+    int messageLength = Utils.DEFAULT_WINDOW_SIZE / 4;
+    byte[] fakeMessage = new byte[messageLength];
+
+    // Stream 1 receives a message
+    Buffer buffer = createMessageFrame(fakeMessage);
+    int messageFrameLength = (int) buffer.size();
+    frameHandler().data(false, 3, buffer, messageFrameLength);
+
+    // Stream 2 receives a message
+    buffer = createMessageFrame(fakeMessage);
+    frameHandler().data(false, 5, buffer, messageFrameLength);
+
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .windowUpdate(eq(0), eq((long) 2 * messageFrameLength));
+    reset(frameWriter);
+
+    // Stream 1 receives another message
+    buffer = createMessageFrame(fakeMessage);
+    frameHandler().data(false, 3, buffer, messageFrameLength);
+
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .windowUpdate(eq(3), eq((long) 2 * messageFrameLength));
+
+    // Stream 2 receives another message
+    buffer = createMessageFrame(fakeMessage);
+    frameHandler().data(false, 5, buffer, messageFrameLength);
+
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .windowUpdate(eq(5), eq((long) 2 * messageFrameLength));
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .windowUpdate(eq(0), eq((long) 2 * messageFrameLength));
+
+    stream1.cancel(Status.CANCELLED);
+    verify(frameWriter, timeout(TIME_OUT_MS)).rstStream(eq(3), eq(ErrorCode.CANCEL));
+    listener1.waitUntilStreamClosed();
+    assertEquals(OkHttpClientTransport.toGrpcStatus(ErrorCode.CANCEL).getCode(),
+        listener1.status.getCode());
+
+    stream2.cancel(Status.CANCELLED);
+    verify(frameWriter, timeout(TIME_OUT_MS)).rstStream(eq(5), eq(ErrorCode.CANCEL));
+    listener2.waitUntilStreamClosed();
+    assertEquals(OkHttpClientTransport.toGrpcStatus(ErrorCode.CANCEL).getCode(),
+        listener2.status.getCode());
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void windowUpdateWithInboundFlowControl() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    int messageLength = Utils.DEFAULT_WINDOW_SIZE / 2 + 1;
+    byte[] fakeMessage = new byte[messageLength];
+
+    frameHandler().headers(false, false, 3, 0, grpcResponseHeaders(), HeadersMode.HTTP_20_HEADERS);
+    Buffer buffer = createMessageFrame(fakeMessage);
+    long messageFrameLength = buffer.size();
+    frameHandler().data(false, 3, buffer, (int) messageFrameLength);
+    ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
+    verify(frameWriter, timeout(TIME_OUT_MS)).windowUpdate(
+        idCaptor.capture(), eq(messageFrameLength));
+    // Should only send window update for the connection.
+    assertEquals(1, idCaptor.getAllValues().size());
+    assertEquals(0, (int)idCaptor.getValue());
+
+    stream.request(1);
+    // We return the bytes for the stream window as we read the message.
+    verify(frameWriter, timeout(TIME_OUT_MS)).windowUpdate(eq(3), eq(messageFrameLength));
+
+    getStream(3).cancel(Status.CANCELLED);
+    verify(frameWriter, timeout(TIME_OUT_MS)).rstStream(eq(3), eq(ErrorCode.CANCEL));
+    listener.waitUntilStreamClosed();
+    assertEquals(OkHttpClientTransport.toGrpcStatus(ErrorCode.CANCEL).getCode(),
+        listener.status.getCode());
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void outboundFlowControl() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    // The first message should be sent out.
+    int messageLength = Utils.DEFAULT_WINDOW_SIZE / 2 + 1;
+    InputStream input = new ByteArrayInputStream(new byte[messageLength]);
+    stream.writeMessage(input);
+    stream.flush();
+    verify(frameWriter, timeout(TIME_OUT_MS)).data(
+        eq(false), eq(3), any(Buffer.class), eq(messageLength + HEADER_LENGTH));
+
+
+    // The second message should be partially sent out.
+    input = new ByteArrayInputStream(new byte[messageLength]);
+    stream.writeMessage(input);
+    stream.flush();
+    int partiallySentSize =
+        Utils.DEFAULT_WINDOW_SIZE - messageLength - HEADER_LENGTH;
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .data(eq(false), eq(3), any(Buffer.class), eq(partiallySentSize));
+
+    // Get more credit, the rest data should be sent out.
+    frameHandler().windowUpdate(3, Utils.DEFAULT_WINDOW_SIZE);
+    frameHandler().windowUpdate(0, Utils.DEFAULT_WINDOW_SIZE);
+    verify(frameWriter, timeout(TIME_OUT_MS)).data(
+        eq(false), eq(3), any(Buffer.class),
+        eq(messageLength + HEADER_LENGTH - partiallySentSize));
+
+    stream.cancel(Status.CANCELLED);
+    listener.waitUntilStreamClosed();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void outboundFlowControlWithInitialWindowSizeChange() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    int messageLength = 20;
+    setInitialWindowSize(HEADER_LENGTH + 10);
+    InputStream input = new ByteArrayInputStream(new byte[messageLength]);
+    stream.writeMessage(input);
+    stream.flush();
+    // part of the message can be sent.
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .data(eq(false), eq(3), any(Buffer.class), eq(HEADER_LENGTH + 10));
+    // Avoid connection flow control.
+    frameHandler().windowUpdate(0, HEADER_LENGTH + 10);
+
+    // Increase initial window size
+    setInitialWindowSize(HEADER_LENGTH + 20);
+    // The rest data should be sent.
+    verify(frameWriter, timeout(TIME_OUT_MS)).data(eq(false), eq(3), any(Buffer.class), eq(10));
+    frameHandler().windowUpdate(0, 10);
+
+    // Decrease initial window size to HEADER_LENGTH, since we've already sent
+    // out HEADER_LENGTH + 20 bytes data, the window size should be -20 now.
+    setInitialWindowSize(HEADER_LENGTH);
+    // Get 20 tokens back, still can't send any data.
+    frameHandler().windowUpdate(3, 20);
+    input = new ByteArrayInputStream(new byte[messageLength]);
+    stream.writeMessage(input);
+    stream.flush();
+    // Only the previous two write operations happened.
+    verify(frameWriter, timeout(TIME_OUT_MS).times(2))
+        .data(anyBoolean(), anyInt(), any(Buffer.class), anyInt());
+
+    // Get enough tokens to send the pending message.
+    frameHandler().windowUpdate(3, HEADER_LENGTH + 20);
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .data(eq(false), eq(3), any(Buffer.class), eq(HEADER_LENGTH + 20));
+
+    stream.cancel(Status.CANCELLED);
+    listener.waitUntilStreamClosed();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void outboundFlowControlWithInitialWindowSizeChangeInMiddleOfStream() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    int messageLength = 20;
+    setInitialWindowSize(HEADER_LENGTH + 10);
+    InputStream input = new ByteArrayInputStream(new byte[messageLength]);
+    stream.writeMessage(input);
+    stream.flush();
+    // part of the message can be sent.
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .data(eq(false), eq(3), any(Buffer.class), eq(HEADER_LENGTH + 10));
+    // Avoid connection flow control.
+    frameHandler().windowUpdate(0, HEADER_LENGTH + 20);
+
+    // Increase initial window size
+    setInitialWindowSize(HEADER_LENGTH + 20);
+
+    // wait until pending frames sent (inOrder doesn't support timeout)
+    verify(frameWriter, timeout(TIME_OUT_MS).atLeastOnce())
+        .data(eq(false), eq(3), any(Buffer.class), eq(10));
+    // It should ack the settings, then send remaining message.
+    InOrder inOrder = inOrder(frameWriter);
+    inOrder.verify(frameWriter).ackSettings(any(Settings.class));
+    inOrder.verify(frameWriter).data(eq(false), eq(3), any(Buffer.class), eq(10));
+
+    stream.cancel(Status.CANCELLED);
+    listener.waitUntilStreamClosed();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void stopNormally() throws Exception {
+    initTransport();
+    MockStreamListener listener1 = new MockStreamListener();
+    MockStreamListener listener2 = new MockStreamListener();
+    OkHttpClientStream stream1 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream1.start(listener1);
+    OkHttpClientStream stream2 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream2.start(listener2);
+    assertEquals(2, activeStreamCount());
+    clientTransport.shutdown(SHUTDOWN_REASON);
+
+    assertEquals(2, activeStreamCount());
+    verify(transportListener).transportShutdown(same(SHUTDOWN_REASON));
+
+    stream1.cancel(Status.CANCELLED);
+    stream2.cancel(Status.CANCELLED);
+    listener1.waitUntilStreamClosed();
+    listener2.waitUntilStreamClosed();
+    assertEquals(0, activeStreamCount());
+    assertEquals(Status.CANCELLED.getCode(), listener1.status.getCode());
+    assertEquals(Status.CANCELLED.getCode(), listener2.status.getCode());
+    verify(frameWriter, timeout(TIME_OUT_MS)).goAway(eq(0), eq(ErrorCode.NO_ERROR), (byte[]) any());
+    verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void receiveGoAway() throws Exception {
+    initTransport();
+    // start 2 streams.
+    MockStreamListener listener1 = new MockStreamListener();
+    MockStreamListener listener2 = new MockStreamListener();
+    OkHttpClientStream stream1 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream1.start(listener1);
+    stream1.request(1);
+    OkHttpClientStream stream2 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream2.start(listener2);
+    stream2.request(1);
+    assertEquals(2, activeStreamCount());
+
+    // Receive goAway, max good id is 3.
+    frameHandler().goAway(3, ErrorCode.CANCEL, ByteString.EMPTY);
+
+    // Transport should be in STOPPING state.
+    verify(transportListener).transportShutdown(isA(Status.class));
+    verify(transportListener, never()).transportTerminated();
+
+    // Stream 2 should be closed.
+    listener2.waitUntilStreamClosed();
+    assertEquals(1, activeStreamCount());
+    assertEquals(Status.CANCELLED.getCode(), listener2.status.getCode());
+
+    // New stream should be failed.
+    assertNewStreamFail();
+
+    // But stream 1 should be able to send.
+    final String sentMessage = "Should I also go away?";
+    OkHttpClientStream stream = getStream(3);
+    InputStream input = new ByteArrayInputStream(sentMessage.getBytes(UTF_8));
+    assertEquals(22, input.available());
+    stream.writeMessage(input);
+    stream.flush();
+    ArgumentCaptor<Buffer> captor =
+        ArgumentCaptor.forClass(Buffer.class);
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .data(eq(false), eq(3), captor.capture(), eq(22 + HEADER_LENGTH));
+    Buffer sentFrame = captor.getValue();
+    assertEquals(createMessageFrame(sentMessage), sentFrame);
+
+    // And read.
+    frameHandler().headers(false, false, 3, 0, grpcResponseHeaders(), HeadersMode.HTTP_20_HEADERS);
+    final String receivedMessage = "No, you are fine.";
+    Buffer buffer = createMessageFrame(receivedMessage);
+    frameHandler().data(false, 3, buffer, (int) buffer.size());
+    frameHandler().headers(true, true, 3, 0, grpcResponseTrailers(), HeadersMode.HTTP_20_HEADERS);
+    listener1.waitUntilStreamClosed();
+    assertEquals(1, listener1.messages.size());
+    assertEquals(receivedMessage, listener1.messages.get(0));
+
+    // The transport should be stopped after all active streams finished.
+    verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void streamIdExhausted() throws Exception {
+    int startId = Integer.MAX_VALUE - 2;
+    initTransport(startId);
+
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    stream.request(1);
+
+    // New stream should be failed.
+    assertNewStreamFail();
+
+    // The alive stream should be functional, receives a message.
+    frameHandler().headers(
+        false, false, startId, 0, grpcResponseHeaders(), HeadersMode.HTTP_20_HEADERS);
+    assertNotNull(listener.headers);
+    String message = "hello";
+    Buffer buffer = createMessageFrame(message);
+    frameHandler().data(false, startId, buffer, (int) buffer.size());
+
+    getStream(startId).cancel(Status.CANCELLED);
+    // Receives the second message after be cancelled.
+    buffer = createMessageFrame(message);
+    frameHandler().data(false, startId, buffer, (int) buffer.size());
+
+    listener.waitUntilStreamClosed();
+    // Should only have the first message delivered.
+    assertEquals(message, listener.messages.get(0));
+    verify(frameWriter, timeout(TIME_OUT_MS)).rstStream(eq(startId), eq(ErrorCode.CANCEL));
+    verify(transportListener).transportShutdown(isA(Status.class));
+    verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void pendingStreamSucceed() throws Exception {
+    initTransport();
+    setMaxConcurrentStreams(1);
+    final MockStreamListener listener1 = new MockStreamListener();
+    final MockStreamListener listener2 = new MockStreamListener();
+    OkHttpClientStream stream1 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream1.start(listener1);
+    // The second stream should be pending.
+    OkHttpClientStream stream2 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream2.start(listener2);
+    String sentMessage = "hello";
+    InputStream input = new ByteArrayInputStream(sentMessage.getBytes(UTF_8));
+    assertEquals(5, input.available());
+    stream2.writeMessage(input);
+    stream2.flush();
+    stream2.halfClose();
+
+    waitForStreamPending(1);
+    assertEquals(1, activeStreamCount());
+
+    // Finish the first stream
+    stream1.cancel(Status.CANCELLED);
+    listener1.waitUntilStreamClosed();
+
+    // The second stream should be active now, and the pending data should be sent out.
+    assertEquals(1, activeStreamCount());
+    assertEquals(0, clientTransport.getPendingStreamSize());
+    ArgumentCaptor<Buffer> captor = ArgumentCaptor.forClass(Buffer.class);
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .data(eq(false), eq(5), captor.capture(), eq(5 + HEADER_LENGTH));
+    Buffer sentFrame = captor.getValue();
+    assertEquals(createMessageFrame(sentMessage), sentFrame);
+    verify(frameWriter, timeout(TIME_OUT_MS)).data(eq(true), eq(5), any(Buffer.class), eq(0));
+    stream2.cancel(Status.CANCELLED);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void pendingStreamCancelled() throws Exception {
+    initTransport();
+    setMaxConcurrentStreams(0);
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    waitForStreamPending(1);
+    stream.cancel(Status.CANCELLED);
+    // The second cancel should be an no-op.
+    stream.cancel(Status.UNKNOWN);
+    listener.waitUntilStreamClosed();
+    assertEquals(0, clientTransport.getPendingStreamSize());
+    assertEquals(Status.CANCELLED.getCode(), listener.status.getCode());
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void pendingStreamFailedByGoAway() throws Exception {
+    initTransport();
+    setMaxConcurrentStreams(1);
+    final MockStreamListener listener1 = new MockStreamListener();
+    final MockStreamListener listener2 = new MockStreamListener();
+    OkHttpClientStream stream1 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream1.start(listener1);
+    // The second stream should be pending.
+    OkHttpClientStream stream2 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream2.start(listener2);
+
+    waitForStreamPending(1);
+    assertEquals(1, activeStreamCount());
+
+    // Receives GO_AWAY.
+    frameHandler().goAway(99, ErrorCode.CANCEL, ByteString.EMPTY);
+
+    listener2.waitUntilStreamClosed();
+    assertEquals(Status.CANCELLED.getCode(), listener2.status.getCode());
+    assertEquals(0, clientTransport.getPendingStreamSize());
+
+    // active stream should not be affected.
+    assertEquals(1, activeStreamCount());
+    getStream(3).cancel(Status.CANCELLED);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void pendingStreamSucceedAfterShutdown() throws Exception {
+    initTransport();
+    setMaxConcurrentStreams(0);
+    final MockStreamListener listener = new MockStreamListener();
+    // The second stream should be pending.
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    waitForStreamPending(1);
+
+    clientTransport.shutdown(SHUTDOWN_REASON);
+    setMaxConcurrentStreams(1);
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .synStream(anyBoolean(), anyBoolean(), eq(3), anyInt(), anyListHeader());
+    assertEquals(1, activeStreamCount());
+    stream.cancel(Status.CANCELLED);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void pendingStreamFailedByIdExhausted() throws Exception {
+    int startId = Integer.MAX_VALUE - 4;
+    initTransport(startId);
+    setMaxConcurrentStreams(1);
+
+    final MockStreamListener listener1 = new MockStreamListener();
+    final MockStreamListener listener2 = new MockStreamListener();
+    final MockStreamListener listener3 = new MockStreamListener();
+
+    OkHttpClientStream stream1 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream1.start(listener1);
+
+    // The second and third stream should be pending.
+    OkHttpClientStream stream2 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream2.start(listener2);
+    OkHttpClientStream stream3 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream3.start(listener3);
+
+    waitForStreamPending(2);
+    assertEquals(1, activeStreamCount());
+
+    // Now finish stream1, stream2 should be started and exhaust the id,
+    // so stream3 should be failed.
+    stream1.cancel(Status.CANCELLED);
+    listener1.waitUntilStreamClosed();
+    listener3.waitUntilStreamClosed();
+    assertEquals(Status.UNAVAILABLE.getCode(), listener3.status.getCode());
+    assertEquals(0, clientTransport.getPendingStreamSize());
+    assertEquals(1, activeStreamCount());
+    stream2 = getStream(startId + 2);
+    stream2.cancel(Status.CANCELLED);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void receivingWindowExceeded() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    stream.request(1);
+
+    frameHandler().headers(false, false, 3, 0, grpcResponseHeaders(), HeadersMode.HTTP_20_HEADERS);
+
+    int messageLength = Utils.DEFAULT_WINDOW_SIZE + 1;
+    byte[] fakeMessage = new byte[messageLength];
+    Buffer buffer = createMessageFrame(fakeMessage);
+    int messageFrameLength = (int) buffer.size();
+    frameHandler().data(false, 3, buffer, messageFrameLength);
+
+    listener.waitUntilStreamClosed();
+    assertEquals(Status.INTERNAL.getCode(), listener.status.getCode());
+    assertEquals("Received data size exceeded our receiving window size",
+        listener.status.getDescription());
+    verify(frameWriter, timeout(TIME_OUT_MS)).rstStream(eq(3), eq(ErrorCode.FLOW_CONTROL_ERROR));
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void unaryHeadersShouldNotBeFlushed() throws Exception {
+    // By default the method is a Unary call
+    shouldHeadersBeFlushed(false);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void serverStreamingHeadersShouldNotBeFlushed() throws Exception {
+    method = method.toBuilder().setType(MethodType.SERVER_STREAMING).build();
+    shouldHeadersBeFlushed(false);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void clientStreamingHeadersShouldBeFlushed() throws Exception {
+    method = method.toBuilder().setType(MethodType.CLIENT_STREAMING).build();
+    shouldHeadersBeFlushed(true);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void duplexStreamingHeadersShouldNotBeFlushed() throws Exception {
+    method = method.toBuilder().setType(MethodType.BIDI_STREAMING).build();
+    shouldHeadersBeFlushed(true);
+    shutdownAndVerify();
+  }
+
+  private void shouldHeadersBeFlushed(boolean shouldBeFlushed) throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    verify(frameWriter, timeout(TIME_OUT_MS)).synStream(
+        eq(false), eq(false), eq(3), eq(0), Matchers.anyListOf(Header.class));
+    if (shouldBeFlushed) {
+      verify(frameWriter, timeout(TIME_OUT_MS)).flush();
+    } else {
+      verify(frameWriter, timeout(TIME_OUT_MS).times(0)).flush();
+    }
+    stream.cancel(Status.CANCELLED);
+  }
+
+  @Test
+  public void receiveDataWithoutHeader() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    stream.request(1);
+    Buffer buffer = createMessageFrame(new byte[1]);
+    frameHandler().data(false, 3, buffer, (int) buffer.size());
+
+    // Trigger the failure by a trailer.
+    frameHandler().headers(
+        true, true, 3, 0, grpcResponseHeaders(), HeadersMode.HTTP_20_HEADERS);
+
+    listener.waitUntilStreamClosed();
+    assertEquals(Status.INTERNAL.getCode(), listener.status.getCode());
+    assertTrue(listener.status.getDescription().startsWith("headers not received before payload"));
+    assertEquals(0, listener.messages.size());
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void receiveDataWithoutHeaderAndTrailer() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    stream.request(1);
+    Buffer buffer = createMessageFrame(new byte[1]);
+    frameHandler().data(false, 3, buffer, (int) buffer.size());
+
+    // Trigger the failure by a data frame.
+    buffer = createMessageFrame(new byte[1]);
+    frameHandler().data(true, 3, buffer, (int) buffer.size());
+
+    listener.waitUntilStreamClosed();
+    assertEquals(Status.INTERNAL.getCode(), listener.status.getCode());
+    assertTrue(listener.status.getDescription().startsWith("headers not received before payload"));
+    assertEquals(0, listener.messages.size());
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void receiveLongEnoughDataWithoutHeaderAndTrailer() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    stream.request(1);
+    Buffer buffer = createMessageFrame(new byte[1000]);
+    frameHandler().data(false, 3, buffer, (int) buffer.size());
+
+    // Once we receive enough detail, we cancel the stream. so we should have sent cancel.
+    verify(frameWriter, timeout(TIME_OUT_MS)).rstStream(eq(3), eq(ErrorCode.CANCEL));
+
+    listener.waitUntilStreamClosed();
+    assertEquals(Status.INTERNAL.getCode(), listener.status.getCode());
+    assertTrue(listener.status.getDescription().startsWith("headers not received before payload"));
+    assertEquals(0, listener.messages.size());
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void receiveDataForUnknownStreamUpdateConnectionWindow() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    stream.cancel(Status.CANCELLED);
+
+    Buffer buffer = createMessageFrame(
+        new byte[Utils.DEFAULT_WINDOW_SIZE / 2 + 1]);
+    frameHandler().data(false, 3, buffer, (int) buffer.size());
+    // Should still update the connection window even stream 3 is gone.
+    verify(frameWriter, timeout(TIME_OUT_MS)).windowUpdate(0,
+        HEADER_LENGTH + Utils.DEFAULT_WINDOW_SIZE / 2 + 1);
+    buffer = createMessageFrame(
+        new byte[Utils.DEFAULT_WINDOW_SIZE / 2 + 1]);
+
+    // This should kill the connection, since we never created stream 5.
+    frameHandler().data(false, 5, buffer, (int) buffer.size());
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .goAway(eq(0), eq(ErrorCode.PROTOCOL_ERROR), any(byte[].class));
+    verify(transportListener).transportShutdown(isA(Status.class));
+    verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void receiveWindowUpdateForUnknownStream() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    stream.cancel(Status.CANCELLED);
+    // This should be ignored.
+    frameHandler().windowUpdate(3, 73);
+    listener.waitUntilStreamClosed();
+    // This should kill the connection, since we never created stream 5.
+    frameHandler().windowUpdate(5, 73);
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .goAway(eq(0), eq(ErrorCode.PROTOCOL_ERROR), any(byte[].class));
+    verify(transportListener).transportShutdown(isA(Status.class));
+    verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void shouldBeInitiallyReady() throws Exception {
+    initTransport();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    assertTrue(stream.isReady());
+    assertTrue(listener.isOnReadyCalled());
+    stream.cancel(Status.CANCELLED);
+    assertFalse(stream.isReady());
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void notifyOnReady() throws Exception {
+    initTransport();
+    // exactly one byte below the threshold
+    int messageLength =
+        AbstractStream.TransportState.DEFAULT_ONREADY_THRESHOLD - HEADER_LENGTH - 1;
+    setInitialWindowSize(0);
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    assertTrue(stream.isReady());
+    // Be notified at the beginning.
+    assertTrue(listener.isOnReadyCalled());
+
+    // Write a message that will not exceed the notification threshold and queue it.
+    InputStream input = new ByteArrayInputStream(new byte[messageLength]);
+    stream.writeMessage(input);
+    stream.flush();
+    assertTrue(stream.isReady());
+
+    // Write another two messages, still be queued.
+    input = new ByteArrayInputStream(new byte[messageLength]);
+    stream.writeMessage(input);
+    stream.flush();
+    assertFalse(stream.isReady());
+    input = new ByteArrayInputStream(new byte[messageLength]);
+    stream.writeMessage(input);
+    stream.flush();
+    assertFalse(stream.isReady());
+
+    // Let the first message out.
+    frameHandler().windowUpdate(0, HEADER_LENGTH + messageLength);
+    frameHandler().windowUpdate(3, HEADER_LENGTH + messageLength);
+    assertFalse(stream.isReady());
+    assertFalse(listener.isOnReadyCalled());
+
+    // Let the second message out.
+    frameHandler().windowUpdate(0, HEADER_LENGTH + messageLength);
+    frameHandler().windowUpdate(3, HEADER_LENGTH + messageLength);
+    assertTrue(stream.isReady());
+    assertTrue(listener.isOnReadyCalled());
+
+    stream.cancel(Status.CANCELLED);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void transportReady() throws Exception {
+    initTransport();
+    verifyZeroInteractions(transportListener);
+    frameHandler().settings(false, new Settings());
+    verify(transportListener).transportReady();
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void ping() throws Exception {
+    initTransport();
+    PingCallbackImpl callback1 = new PingCallbackImpl();
+    clientTransport.ping(callback1, MoreExecutors.directExecutor());
+    assertEquals(1, getTransportStats(clientTransport).keepAlivesSent);
+    // add'l ping will be added as listener to outstanding operation
+    PingCallbackImpl callback2 = new PingCallbackImpl();
+    clientTransport.ping(callback2, MoreExecutors.directExecutor());
+    assertEquals(1, getTransportStats(clientTransport).keepAlivesSent);
+
+    ArgumentCaptor<Integer> captor1 = ArgumentCaptor.forClass(int.class);
+    ArgumentCaptor<Integer> captor2 = ArgumentCaptor.forClass(int.class);
+    verify(frameWriter, timeout(TIME_OUT_MS)).ping(eq(false), captor1.capture(), captor2.capture());
+    // callback not invoked until we see acknowledgement
+    assertEquals(0, callback1.invocationCount);
+    assertEquals(0, callback2.invocationCount);
+
+    int payload1 = captor1.getValue();
+    int payload2 = captor2.getValue();
+    // getting a bad ack won't complete the future
+    // to make the ack "bad", we modify the payload so it doesn't match
+    frameHandler().ping(true, payload1, payload2 - 1);
+    // operation not complete because ack was wrong
+    assertEquals(0, callback1.invocationCount);
+    assertEquals(0, callback2.invocationCount);
+
+    nanoTime += 10101;
+
+    // reading the proper response should complete the future
+    frameHandler().ping(true, payload1, payload2);
+    assertEquals(1, callback1.invocationCount);
+    assertEquals(10101, callback1.roundTripTime);
+    assertNull(callback1.failureCause);
+    // callback2 piggy-backed on same operation
+    assertEquals(1, callback2.invocationCount);
+    assertEquals(10101, callback2.roundTripTime);
+    assertNull(callback2.failureCause);
+
+    // now that previous ping is done, next request returns a different future
+    callback1 = new PingCallbackImpl();
+    clientTransport.ping(callback1, MoreExecutors.directExecutor());
+    assertEquals(2, getTransportStats(clientTransport).keepAlivesSent);
+    assertEquals(0, callback1.invocationCount);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void ping_failsWhenTransportShutdown() throws Exception {
+    initTransport();
+    PingCallbackImpl callback = new PingCallbackImpl();
+    clientTransport.ping(callback, MoreExecutors.directExecutor());
+    assertEquals(1, getTransportStats(clientTransport).keepAlivesSent);
+    assertEquals(0, callback.invocationCount);
+
+    clientTransport.shutdown(SHUTDOWN_REASON);
+    // ping failed on channel shutdown
+    assertEquals(1, callback.invocationCount);
+    assertTrue(callback.failureCause instanceof StatusException);
+    assertSame(SHUTDOWN_REASON, ((StatusException) callback.failureCause).getStatus());
+
+    // now that handler is in terminal state, all future pings fail immediately
+    callback = new PingCallbackImpl();
+    clientTransport.ping(callback, MoreExecutors.directExecutor());
+    assertEquals(1, getTransportStats(clientTransport).keepAlivesSent);
+    assertEquals(1, callback.invocationCount);
+    assertTrue(callback.failureCause instanceof StatusException);
+    assertSame(SHUTDOWN_REASON, ((StatusException) callback.failureCause).getStatus());
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void ping_failsIfTransportFails() throws Exception {
+    initTransport();
+    PingCallbackImpl callback = new PingCallbackImpl();
+    clientTransport.ping(callback, MoreExecutors.directExecutor());
+    assertEquals(1, getTransportStats(clientTransport).keepAlivesSent);
+    assertEquals(0, callback.invocationCount);
+
+    clientTransport.onException(new IOException());
+    // ping failed on error
+    assertEquals(1, callback.invocationCount);
+    assertTrue(callback.failureCause instanceof StatusException);
+    assertEquals(Status.Code.UNAVAILABLE,
+        ((StatusException) callback.failureCause).getStatus().getCode());
+
+    // now that handler is in terminal state, all future pings fail immediately
+    callback = new PingCallbackImpl();
+    clientTransport.ping(callback, MoreExecutors.directExecutor());
+    assertEquals(1, getTransportStats(clientTransport).keepAlivesSent);
+    assertEquals(1, callback.invocationCount);
+    assertTrue(callback.failureCause instanceof StatusException);
+    assertEquals(Status.Code.UNAVAILABLE,
+        ((StatusException) callback.failureCause).getStatus().getCode());
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void writeBeforeConnected() throws Exception {
+    initTransportAndDelayConnected();
+    final String message = "Hello Server";
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    InputStream input = new ByteArrayInputStream(message.getBytes(UTF_8));
+    stream.writeMessage(input);
+    stream.flush();
+    // The message should be queued.
+    verifyNoMoreInteractions(frameWriter);
+
+    allowTransportConnected();
+
+    // The queued message should be sent out.
+    ArgumentCaptor<Buffer> captor = ArgumentCaptor.forClass(Buffer.class);
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .data(eq(false), eq(3), captor.capture(), eq(12 + HEADER_LENGTH));
+    Buffer sentFrame = captor.getValue();
+    assertEquals(createMessageFrame(message), sentFrame);
+    stream.cancel(Status.CANCELLED);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void cancelBeforeConnected() throws Exception {
+    initTransportAndDelayConnected();
+    final String message = "Hello Server";
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    InputStream input = new ByteArrayInputStream(message.getBytes(UTF_8));
+    stream.writeMessage(input);
+    stream.flush();
+    stream.cancel(Status.CANCELLED);
+    verifyNoMoreInteractions(frameWriter);
+
+    allowTransportConnected();
+    verifyNoMoreInteractions(frameWriter);
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void shutdownDuringConnecting() throws Exception {
+    initTransportAndDelayConnected();
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    clientTransport.shutdown(SHUTDOWN_REASON);
+    allowTransportConnected();
+
+    // The new stream should be failed, but not the pending stream.
+    assertNewStreamFail();
+    verify(frameWriter, timeout(TIME_OUT_MS))
+        .synStream(anyBoolean(), anyBoolean(), eq(3), anyInt(), anyListHeader());
+    assertEquals(1, activeStreamCount());
+    stream.cancel(Status.CANCELLED);
+    listener.waitUntilStreamClosed();
+    assertEquals(Status.CANCELLED.getCode(), listener.status.getCode());
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void invalidAuthorityPropagates() {
+    clientTransport = new OkHttpClientTransport(
+        new InetSocketAddress("host", 1234),
+        "invalid_authority",
+        "userAgent",
+        executor,
+        sslSocketFactory,
+        hostnameVerifier,
+        ConnectionSpec.CLEARTEXT,
+        DEFAULT_MAX_MESSAGE_SIZE,
+        NO_PROXY,
+        tooManyPingsRunnable,
+        transportTracer);
+
+    String host = clientTransport.getOverridenHost();
+    int port = clientTransport.getOverridenPort();
+
+    assertEquals("invalid_authority", host);
+    assertEquals(1234, port);
+  }
+
+  @Test
+  public void unreachableServer() throws Exception {
+    clientTransport = new OkHttpClientTransport(
+        new InetSocketAddress("localhost", 0),
+        "authority",
+        "userAgent",
+        executor,
+        sslSocketFactory,
+        hostnameVerifier,
+        ConnectionSpec.CLEARTEXT,
+        DEFAULT_MAX_MESSAGE_SIZE,
+        NO_PROXY,
+        tooManyPingsRunnable,
+        new TransportTracer());
+
+    ManagedClientTransport.Listener listener = mock(ManagedClientTransport.Listener.class);
+    clientTransport.start(listener);
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
+    verify(listener, timeout(TIME_OUT_MS)).transportShutdown(captor.capture());
+    Status status = captor.getValue();
+    assertEquals(Status.UNAVAILABLE.getCode(), status.getCode());
+    assertTrue(status.getCause().toString(), status.getCause() instanceof IOException);
+
+    MockStreamListener streamListener = new MockStreamListener();
+    clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT).start(streamListener);
+    streamListener.waitUntilStreamClosed();
+    assertEquals(Status.UNAVAILABLE.getCode(), streamListener.status.getCode());
+  }
+
+  @Test
+  public void proxy_200() throws Exception {
+    ServerSocket serverSocket = new ServerSocket(0);
+    clientTransport = new OkHttpClientTransport(
+        InetSocketAddress.createUnresolved("theservice", 80),
+        "authority",
+        "userAgent",
+        executor,
+        sslSocketFactory,
+        hostnameVerifier,
+        ConnectionSpec.CLEARTEXT,
+        DEFAULT_MAX_MESSAGE_SIZE,
+        new ProxyParameters(
+            (InetSocketAddress) serverSocket.getLocalSocketAddress(), NO_USER, NO_PW),
+        tooManyPingsRunnable,
+        transportTracer);
+    clientTransport.start(transportListener);
+
+    Socket sock = serverSocket.accept();
+    serverSocket.close();
+
+    BufferedReader reader = new BufferedReader(new InputStreamReader(sock.getInputStream(), UTF_8));
+    assertEquals("CONNECT theservice:80 HTTP/1.1", reader.readLine());
+    assertEquals("Host: theservice:80", reader.readLine());
+    while (!"".equals(reader.readLine())) {}
+
+    sock.getOutputStream().write("HTTP/1.1 200 OK\r\nServer: test\r\n\r\n".getBytes(UTF_8));
+    sock.getOutputStream().flush();
+
+    assertEquals("PRI * HTTP/2.0", reader.readLine());
+    assertEquals("", reader.readLine());
+    assertEquals("SM", reader.readLine());
+    assertEquals("", reader.readLine());
+
+    // Empty SETTINGS
+    sock.getOutputStream().write(new byte[] {0, 0, 0, 0, 0x4, 0});
+    // GOAWAY
+    sock.getOutputStream().write(new byte[] {
+        0, 0, 0, 8, 0x7, 0,
+        0, 0, 0, 0, // last stream id
+        0, 0, 0, 0, // error code
+    });
+    sock.getOutputStream().flush();
+
+    verify(transportListener, timeout(TIME_OUT_MS)).transportShutdown(isA(Status.class));
+    while (sock.getInputStream().read() != -1) {}
+    verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated();
+    sock.close();
+  }
+
+  @Test
+  public void proxy_500() throws Exception {
+    ServerSocket serverSocket = new ServerSocket(0);
+    clientTransport = new OkHttpClientTransport(
+        InetSocketAddress.createUnresolved("theservice", 80),
+        "authority",
+        "userAgent",
+        executor,
+        sslSocketFactory,
+        hostnameVerifier,
+        ConnectionSpec.CLEARTEXT,
+        DEFAULT_MAX_MESSAGE_SIZE,
+        new ProxyParameters(
+            (InetSocketAddress) serverSocket.getLocalSocketAddress(), NO_USER, NO_PW),
+        tooManyPingsRunnable,
+        transportTracer);
+    clientTransport.start(transportListener);
+
+    Socket sock = serverSocket.accept();
+    serverSocket.close();
+
+    BufferedReader reader = new BufferedReader(new InputStreamReader(sock.getInputStream(), UTF_8));
+    assertEquals("CONNECT theservice:80 HTTP/1.1", reader.readLine());
+    assertEquals("Host: theservice:80", reader.readLine());
+    while (!"".equals(reader.readLine())) {}
+
+    final String errorText = "text describing error";
+    sock.getOutputStream().write("HTTP/1.1 500 OH NO\r\n\r\n".getBytes(UTF_8));
+    sock.getOutputStream().write(errorText.getBytes(UTF_8));
+    sock.getOutputStream().flush();
+    sock.shutdownOutput();
+
+    assertEquals(-1, sock.getInputStream().read());
+
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
+    verify(transportListener, timeout(TIME_OUT_MS)).transportShutdown(captor.capture());
+    Status error = captor.getValue();
+    assertTrue("Status didn't contain error code: " + captor.getValue(),
+        error.getDescription().contains("500"));
+    assertTrue("Status didn't contain error description: " + captor.getValue(),
+        error.getDescription().contains("OH NO"));
+    assertTrue("Status didn't contain error text: " + captor.getValue(),
+        error.getDescription().contains(errorText));
+    assertEquals("Not UNAVAILABLE: " + captor.getValue(),
+        Status.UNAVAILABLE.getCode(), error.getCode());
+    sock.close();
+    verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated();
+  }
+
+  @Test
+  public void proxy_immediateServerClose() throws Exception {
+    ServerSocket serverSocket = new ServerSocket(0);
+    clientTransport = new OkHttpClientTransport(
+        InetSocketAddress.createUnresolved("theservice", 80),
+        "authority",
+        "userAgent",
+        executor,
+        sslSocketFactory,
+        hostnameVerifier,
+        ConnectionSpec.CLEARTEXT,
+        DEFAULT_MAX_MESSAGE_SIZE,
+        new ProxyParameters(
+            (InetSocketAddress) serverSocket.getLocalSocketAddress(), NO_USER, NO_PW),
+        tooManyPingsRunnable,
+        transportTracer);
+    clientTransport.start(transportListener);
+
+    Socket sock = serverSocket.accept();
+    serverSocket.close();
+    sock.close();
+
+    ArgumentCaptor<Status> captor = ArgumentCaptor.forClass(Status.class);
+    verify(transportListener, timeout(TIME_OUT_MS)).transportShutdown(captor.capture());
+    Status error = captor.getValue();
+    assertTrue("Status didn't contain proxy: " + captor.getValue(),
+        error.getDescription().contains("proxy"));
+    assertEquals("Not UNAVAILABLE: " + captor.getValue(),
+        Status.UNAVAILABLE.getCode(), error.getCode());
+    verify(transportListener, timeout(TIME_OUT_MS)).transportTerminated();
+  }
+
+  @Test
+  public void goAway_notUtf8() throws Exception {
+    initTransport();
+    // 0xFF is never permitted in UTF-8. 0xF0 should have 3 continuations following, and 0x0a isn't
+    // a continuation.
+    frameHandler().goAway(
+        0, ErrorCode.ENHANCE_YOUR_CALM, ByteString.of((byte) 0xFF, (byte) 0xF0, (byte) 0x0a));
+
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void goAway_notTooManyPings() throws Exception {
+    final AtomicBoolean run = new AtomicBoolean();
+    tooManyPingsRunnable = new Runnable() {
+      @Override
+      public void run() {
+        run.set(true);
+      }
+    };
+    initTransport();
+    frameHandler().goAway(0, ErrorCode.ENHANCE_YOUR_CALM, ByteString.encodeUtf8("not_many_pings"));
+    assertFalse(run.get());
+
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void goAway_tooManyPings() throws Exception {
+    final AtomicBoolean run = new AtomicBoolean();
+    tooManyPingsRunnable = new Runnable() {
+      @Override
+      public void run() {
+        run.set(true);
+      }
+    };
+    initTransport();
+    frameHandler().goAway(0, ErrorCode.ENHANCE_YOUR_CALM, ByteString.encodeUtf8("too_many_pings"));
+    assertTrue(run.get());
+
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void goAway_streamListenerRpcProgress() throws Exception {
+    initTransport();
+    setMaxConcurrentStreams(2);
+    MockStreamListener listener1 = new MockStreamListener();
+    MockStreamListener listener2 = new MockStreamListener();
+    MockStreamListener listener3 = new MockStreamListener();
+    OkHttpClientStream stream1 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream1.start(listener1);
+    OkHttpClientStream stream2 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream2.start(listener2);
+    OkHttpClientStream stream3 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream3.start(listener3);
+    waitForStreamPending(1);
+
+    assertEquals(2, activeStreamCount());
+    assertContainStream(DEFAULT_START_STREAM_ID);
+    assertContainStream(DEFAULT_START_STREAM_ID + 2);
+
+    frameHandler()
+        .goAway(DEFAULT_START_STREAM_ID, ErrorCode.CANCEL, ByteString.encodeUtf8("blablabla"));
+
+    listener2.waitUntilStreamClosed();
+    listener3.waitUntilStreamClosed();
+    assertNull(listener1.rpcProgress);
+    assertEquals(REFUSED, listener2.rpcProgress);
+    assertEquals(REFUSED, listener3.rpcProgress);
+    assertEquals(1, activeStreamCount());
+    assertContainStream(DEFAULT_START_STREAM_ID);
+
+    getStream(DEFAULT_START_STREAM_ID).cancel(Status.CANCELLED);
+
+    listener1.waitUntilStreamClosed();
+    assertEquals(PROCESSED, listener1.rpcProgress);
+
+    shutdownAndVerify();
+  }
+
+  @Test
+  public void reset_streamListenerRpcProgress() throws Exception {
+    initTransport();
+    MockStreamListener listener1 = new MockStreamListener();
+    MockStreamListener listener2 = new MockStreamListener();
+    MockStreamListener listener3 = new MockStreamListener();
+    OkHttpClientStream stream1 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream1.start(listener1);
+    OkHttpClientStream stream2 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream2.start(listener2);
+    OkHttpClientStream stream3 =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream3.start(listener3);
+
+    assertEquals(3, activeStreamCount());
+    assertContainStream(DEFAULT_START_STREAM_ID);
+    assertContainStream(DEFAULT_START_STREAM_ID + 2);
+    assertContainStream(DEFAULT_START_STREAM_ID + 4);
+
+    frameHandler().rstStream(DEFAULT_START_STREAM_ID + 2, ErrorCode.REFUSED_STREAM);
+
+    listener2.waitUntilStreamClosed();
+    assertNull(listener1.rpcProgress);
+    assertEquals(REFUSED, listener2.rpcProgress);
+    assertNull(listener3.rpcProgress);
+
+    frameHandler().rstStream(DEFAULT_START_STREAM_ID, ErrorCode.CANCEL);
+    listener1.waitUntilStreamClosed();
+    assertEquals(PROCESSED, listener1.rpcProgress);
+    assertNull(listener3.rpcProgress);
+
+    getStream(DEFAULT_START_STREAM_ID + 4).cancel(Status.CANCELLED);
+
+    listener3.waitUntilStreamClosed();
+    assertEquals(PROCESSED, listener3.rpcProgress);
+
+    shutdownAndVerify();
+  }
+
+  private int activeStreamCount() {
+    return clientTransport.getActiveStreams().length;
+  }
+
+  private OkHttpClientStream getStream(int streamId) {
+    return clientTransport.getStream(streamId);
+  }
+
+  void assertContainStream(int streamId) {
+    assertNotNull(clientTransport.getStream(streamId));
+  }
+
+  private ClientFrameHandler frameHandler() throws Exception {
+    return clientTransport.getHandler();
+  }
+
+  private void waitForStreamPending(int expected) throws Exception {
+    int duration = TIME_OUT_MS / 10;
+    for (int i = 0; i < 10; i++) {
+      if (clientTransport.getPendingStreamSize() == expected) {
+        return;
+      }
+      Thread.sleep(duration);
+    }
+    assertEquals(expected, clientTransport.getPendingStreamSize());
+  }
+
+  private void assertNewStreamFail() throws Exception {
+    MockStreamListener listener = new MockStreamListener();
+    OkHttpClientStream stream =
+        clientTransport.newStream(method, new Metadata(), CallOptions.DEFAULT);
+    stream.start(listener);
+    listener.waitUntilStreamClosed();
+    assertFalse(listener.status.isOk());
+  }
+
+  private void setMaxConcurrentStreams(int num) throws Exception {
+    Settings settings = new Settings();
+    OkHttpSettingsUtil.set(settings, OkHttpSettingsUtil.MAX_CONCURRENT_STREAMS, num);
+    frameHandler().settings(false, settings);
+  }
+
+  private void setInitialWindowSize(int size) throws Exception {
+    Settings settings = new Settings();
+    OkHttpSettingsUtil.set(settings, OkHttpSettingsUtil.INITIAL_WINDOW_SIZE, size);
+    frameHandler().settings(false, settings);
+  }
+
+  private static Buffer createMessageFrame(String message) {
+    return createMessageFrame(message.getBytes(UTF_8));
+  }
+
+  private static Buffer createMessageFrame(byte[] message) {
+    Buffer buffer = new Buffer();
+    buffer.writeByte(0 /* UNCOMPRESSED */);
+    buffer.writeInt(message.length);
+    buffer.write(message);
+    return buffer;
+  }
+
+  private List<Header> grpcResponseHeaders() {
+    return ImmutableList.of(
+        new Header(":status", "200"),
+        CONTENT_TYPE_HEADER);
+  }
+
+  private List<Header> grpcResponseTrailers() {
+    return ImmutableList.of(
+        new Header(InternalStatus.CODE_KEY.name(), "0"),
+        // Adding Content-Type and :status for testing responses with only a single HEADERS frame.
+        new Header(":status", "200"),
+        CONTENT_TYPE_HEADER);
+  }
+
+  private static List<Header> anyListHeader() {
+    return any();
+  }
+
+  private static class MockFrameReader implements FrameReader {
+    final CountDownLatch closed = new CountDownLatch(1);
+
+    enum Result {
+      THROW_EXCEPTION,
+      RETURN_FALSE,
+      THROW_ERROR
+    }
+
+    final LinkedBlockingQueue<Result> nextResults = new LinkedBlockingQueue<Result>();
+
+    @Override
+    public void close() throws IOException {
+      closed.countDown();
+    }
+
+    void assertClosed() {
+      try {
+        if (!closed.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)) {
+          fail("Failed waiting frame reader to be closed.");
+        }
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        fail("Interrupted while waiting for frame reader to be closed.");
+      }
+    }
+
+    // The wait is safe; nextFrame is called in a loop and can have spurious wakeups
+    @SuppressWarnings("WaitNotInLoop")
+    @Override
+    public boolean nextFrame(Handler handler) throws IOException {
+      Result result;
+      try {
+        result = nextResults.take();
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        throw new IOException(e);
+      }
+      switch (result) {
+        case THROW_EXCEPTION:
+          throw new IOException(NETWORK_ISSUE_MESSAGE);
+        case RETURN_FALSE:
+          return false;
+        case THROW_ERROR:
+          throw new Error(ERROR_MESSAGE);
+        default:
+          throw new UnsupportedOperationException("unimplemented: " + result);
+      }
+    }
+
+    void throwIoExceptionForNextFrame() {
+      nextResults.add(Result.THROW_EXCEPTION);
+    }
+
+    void throwErrorForNextFrame() {
+      nextResults.add(Result.THROW_ERROR);
+    }
+
+    void nextFrameAtEndOfStream() {
+      nextResults.add(Result.RETURN_FALSE);
+    }
+
+    @Override
+    public void readConnectionPreface() throws IOException {
+      // not used.
+    }
+  }
+
+  private static class MockStreamListener implements ClientStreamListener {
+    Status status;
+    Metadata headers;
+    Metadata trailers;
+    RpcProgress rpcProgress;
+    CountDownLatch closed = new CountDownLatch(1);
+    ArrayList<String> messages = new ArrayList<>();
+    boolean onReadyCalled;
+
+    MockStreamListener() {
+    }
+
+    @Override
+    public void headersRead(Metadata headers) {
+      this.headers = headers;
+    }
+
+    @Override
+    public void messagesAvailable(MessageProducer producer) {
+      InputStream inputStream;
+      while ((inputStream = producer.next()) != null) {
+        String msg = getContent(inputStream);
+        if (msg != null) {
+          messages.add(msg);
+        }
+      }
+    }
+
+    @Override
+    public void closed(Status status, Metadata trailers) {
+      closed(status, PROCESSED, trailers);
+    }
+
+    @Override
+    public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) {
+      this.status = status;
+      this.trailers = trailers;
+      this.rpcProgress = rpcProgress;
+      closed.countDown();
+    }
+
+    @Override
+    public void onReady() {
+      onReadyCalled = true;
+    }
+
+    boolean isOnReadyCalled() {
+      boolean value = onReadyCalled;
+      onReadyCalled = false;
+      return value;
+    }
+
+    void waitUntilStreamClosed() throws InterruptedException {
+      if (!closed.await(TIME_OUT_MS, TimeUnit.MILLISECONDS)) {
+        fail("Failed waiting stream to be closed.");
+      }
+    }
+
+    @SuppressWarnings("Finally") // We don't care about suppressed exceptions in the test
+    static String getContent(InputStream message) {
+      BufferedReader br = new BufferedReader(new InputStreamReader(message, UTF_8));
+      try {
+        // Only one line message is used in this test.
+        return br.readLine();
+      } catch (IOException e) {
+        return null;
+      } finally {
+        try {
+          message.close();
+        } catch (IOException e) {
+          // Ignore
+        }
+      }
+    }
+  }
+
+  private static class MockSocket extends Socket {
+    MockFrameReader frameReader;
+
+    MockSocket(MockFrameReader frameReader) {
+      this.frameReader = frameReader;
+    }
+
+    @Override
+    public synchronized void close() {
+      frameReader.nextFrameAtEndOfStream();
+    }
+
+    @Override
+    public SocketAddress getLocalSocketAddress() {
+      return InetSocketAddress.createUnresolved("localhost", 4000);
+    }
+  }
+
+  static class PingCallbackImpl implements ClientTransport.PingCallback {
+    int invocationCount;
+    long roundTripTime;
+    Throwable failureCause;
+
+    @Override
+    public void onSuccess(long roundTripTimeNanos) {
+      invocationCount++;
+      this.roundTripTime = roundTripTimeNanos;
+    }
+
+    @Override
+    public void onFailure(Throwable cause) {
+      invocationCount++;
+      this.failureCause = cause;
+    }
+  }
+
+  private void allowTransportConnected() {
+    delayConnectedCallback.allowConnected();
+  }
+
+  private void shutdownAndVerify() {
+    clientTransport.shutdown(SHUTDOWN_REASON);
+    assertEquals(0, activeStreamCount());
+    try {
+      verify(frameWriter, timeout(TIME_OUT_MS)).close();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+
+    }
+    frameReader.assertClosed();
+  }
+
+  private static class DelayConnectedCallback implements Runnable {
+    SettableFuture<Void> delayed = SettableFuture.create();
+
+    @Override
+    public void run() {
+      Futures.getUnchecked(delayed);
+    }
+
+    void allowConnected() {
+      delayed.set(null);
+    }
+  }
+
+  private static TransportStats getTransportStats(InternalInstrumented<SocketStats> obj)
+      throws ExecutionException, InterruptedException {
+    return obj.getStats().get().data;
+  }
+}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpProtocolNegotiatorTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpProtocolNegotiatorTest.java
new file mode 100644
index 0000000..5f183dc
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpProtocolNegotiatorTest.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import io.grpc.okhttp.OkHttpProtocolNegotiator.AndroidNegotiator;
+import io.grpc.okhttp.internal.Platform;
+import io.grpc.okhttp.internal.Platform.TlsExtensionType;
+import io.grpc.okhttp.internal.Protocol;
+import java.io.IOException;
+import javax.net.ssl.HandshakeCompletedListener;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+/**
+ * Tests for {@link OkHttpProtocolNegotiator}.
+ */
+@RunWith(JUnit4.class)
+public class OkHttpProtocolNegotiatorTest {
+  @Rule public final ExpectedException thrown = ExpectedException.none();
+
+  private final SSLSocket sock = mock(SSLSocket.class);
+  private final Platform platform = mock(Platform.class);
+
+  @Test
+  public void createNegotiator_isAndroid() {
+    ClassLoader cl = new ClassLoader(this.getClass().getClassLoader()) {
+      @Override
+      protected Class<?> findClass(String name) throws ClassNotFoundException {
+        // Just don't throw.
+        if ("com.android.org.conscrypt.OpenSSLSocketImpl".equals(name)) {
+          return null;
+        }
+        return super.findClass(name);
+      }
+    };
+
+    OkHttpProtocolNegotiator negotiator = OkHttpProtocolNegotiator.createNegotiator(cl);
+    assertEquals(AndroidNegotiator.class, negotiator.getClass());
+  }
+
+  @Test
+  public void createNegotiator_isAndroidLegacy() {
+    ClassLoader cl = new ClassLoader(this.getClass().getClassLoader()) {
+      @Override
+      protected Class<?> findClass(String name) throws ClassNotFoundException {
+        // Just don't throw.
+        if ("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl".equals(name)) {
+          return null;
+        }
+        return super.findClass(name);
+      }
+    };
+
+    OkHttpProtocolNegotiator negotiator = OkHttpProtocolNegotiator.createNegotiator(cl);
+
+    assertEquals(AndroidNegotiator.class, negotiator.getClass());
+  }
+
+  @Test
+  public void createNegotiator_notAndroid() {
+    ClassLoader cl = new ClassLoader(this.getClass().getClassLoader()) {
+      @Override
+      protected Class<?> findClass(String name) throws ClassNotFoundException {
+        if ("com.android.org.conscrypt.OpenSSLSocketImpl".equals(name)) {
+          throw new ClassNotFoundException();
+        }
+        return super.findClass(name);
+      }
+    };
+
+    OkHttpProtocolNegotiator negotiator = OkHttpProtocolNegotiator.createNegotiator(cl);
+
+    assertEquals(OkHttpProtocolNegotiator.class, negotiator.getClass());
+  }
+
+  @Test
+  public void negotiatorNotNull() {
+    assertNotNull(OkHttpProtocolNegotiator.get());
+  }
+
+  @Test
+  public void negotiate_handshakeFails() throws IOException {
+    SSLParameters parameters = new SSLParameters();
+    OkHttpProtocolNegotiator negotiator = OkHttpProtocolNegotiator.get();
+    doReturn(parameters).when(sock).getSSLParameters();
+    doThrow(new IOException()).when(sock).startHandshake();
+    thrown.expect(IOException.class);
+
+    negotiator.negotiate(sock, "hostname", ImmutableList.of(Protocol.HTTP_2));
+  }
+
+  @Test
+  public void negotiate_noSelectedProtocol() throws Exception {
+    Platform platform = mock(Platform.class);
+
+    OkHttpProtocolNegotiator negotiator = new OkHttpProtocolNegotiator(platform);
+
+    thrown.expect(RuntimeException.class);
+    thrown.expectMessage("protocol negotiation failed");
+
+    negotiator.negotiate(sock, "hostname", ImmutableList.of(Protocol.HTTP_2));
+  }
+
+  @Test
+  public void negotiate_success() throws Exception {
+    when(platform.getSelectedProtocol(Mockito.<SSLSocket>any())).thenReturn("h2");
+    OkHttpProtocolNegotiator negotiator = new OkHttpProtocolNegotiator(platform);
+
+    String actual = negotiator.negotiate(sock, "hostname", ImmutableList.of(Protocol.HTTP_2));
+
+    assertEquals("h2", actual);
+    verify(sock).startHandshake();
+    verify(platform).getSelectedProtocol(sock);
+    verify(platform).afterHandshake(sock);
+  }
+
+  @Test
+  public void negotiate_preferGrpcExp() throws Exception {
+    // This test doesn't actually verify that grpc-exp is preferred, since the
+    // mocking of getSelectedProtocol() causes the protocol list to be ignored.
+    // The main usefulness of the test is for future changes to
+    // OkHttpProtocolNegotiator, where we can catch any change that would affect
+    // grpc-exp preference.
+    when(platform.getSelectedProtocol(Mockito.<SSLSocket>any())).thenReturn("grpc-exp");
+    OkHttpProtocolNegotiator negotiator = new OkHttpProtocolNegotiator(platform);
+
+    String actual =
+        negotiator.negotiate(sock, "hostname",
+                ImmutableList.of(Protocol.GRPC_EXP, Protocol.HTTP_2));
+
+    assertEquals("grpc-exp", actual);
+    verify(sock).startHandshake();
+    verify(platform).getSelectedProtocol(sock);
+    verify(platform).afterHandshake(sock);
+  }
+
+  // Checks that the super class is properly invoked.
+  @Test
+  public void negotiate_android_handshakeFails() throws Exception {
+    when(platform.getTlsExtensionType()).thenReturn(TlsExtensionType.ALPN_AND_NPN);
+    AndroidNegotiator negotiator = new AndroidNegotiator(platform);
+
+    FakeAndroidSslSocket androidSock = new FakeAndroidSslSocket() {
+      @Override
+      public void startHandshake() throws IOException {
+        throw new IOException("expected");
+      }
+    };
+
+    thrown.expect(IOException.class);
+    thrown.expectMessage("expected");
+
+    negotiator.negotiate(androidSock, "hostname", ImmutableList.of(Protocol.HTTP_2));
+  }
+
+  @VisibleForTesting
+  public static class FakeAndroidSslSocketAlpn extends FakeAndroidSslSocket {
+    @Override
+    public byte[] getAlpnSelectedProtocol() {
+      return "h2".getBytes(UTF_8);
+    }
+  }
+
+  @Test
+  public void getSelectedProtocol_alpn() throws Exception {
+    when(platform.getTlsExtensionType()).thenReturn(TlsExtensionType.ALPN_AND_NPN);
+    AndroidNegotiator negotiator = new AndroidNegotiator(platform);
+    FakeAndroidSslSocket androidSock = new FakeAndroidSslSocketAlpn();
+
+    String actual = negotiator.getSelectedProtocol(androidSock);
+
+    assertEquals("h2", actual);
+  }
+
+  @VisibleForTesting
+  public static class FakeAndroidSslSocketNpn extends FakeAndroidSslSocket {
+    @Override
+    public byte[] getNpnSelectedProtocol() {
+      return "h2".getBytes(UTF_8);
+    }
+  }
+
+  @Test
+  public void getSelectedProtocol_npn() throws Exception {
+    when(platform.getTlsExtensionType()).thenReturn(TlsExtensionType.NPN);
+    AndroidNegotiator negotiator = new AndroidNegotiator(platform);
+    FakeAndroidSslSocket androidSock = new FakeAndroidSslSocketNpn();
+
+    String actual = negotiator.getSelectedProtocol(androidSock);
+
+    assertEquals("h2", actual);
+  }
+
+  // A fake of org.conscrypt.OpenSSLSocketImpl
+  @VisibleForTesting // Must be public for reflection to work
+  public static class FakeAndroidSslSocket extends SSLSocket {
+
+    public void setUseSessionTickets(boolean arg) {}
+
+    public void setHostname(String arg) {}
+
+    public byte[] getAlpnSelectedProtocol() {
+      return null;
+    }
+
+    public void setAlpnProtocols(byte[] arg) {}
+
+    public byte[] getNpnSelectedProtocol() {
+      return null;
+    }
+
+    public void setNpnProtocols(byte[] arg) {}
+
+    @Override
+    public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {}
+
+    @Override
+    public boolean getEnableSessionCreation() {
+      return false;
+    }
+
+    @Override
+    public String[] getEnabledCipherSuites() {
+      return null;
+    }
+
+    @Override
+    public String[] getEnabledProtocols() {
+      return null;
+    }
+
+    @Override
+    public boolean getNeedClientAuth() {
+      return false;
+    }
+
+    @Override
+    public SSLSession getSession() {
+      return null;
+    }
+
+    @Override
+    public String[] getSupportedCipherSuites() {
+      return null;
+    }
+
+    @Override
+    public String[] getSupportedProtocols() {
+      return null;
+    }
+
+    @Override
+    public boolean getUseClientMode() {
+      return false;
+    }
+
+    @Override
+    public boolean getWantClientAuth() {
+      return false;
+    }
+
+    @Override
+    public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {}
+
+    @Override
+    public void setEnableSessionCreation(boolean flag) {}
+
+    @Override
+    public void setEnabledCipherSuites(String[] suites) {}
+
+    @Override
+    public void setEnabledProtocols(String[] protocols) {}
+
+    @Override
+    public void setNeedClientAuth(boolean need) {}
+
+    @Override
+    public void setUseClientMode(boolean mode) {}
+
+    @Override
+    public void setWantClientAuth(boolean want) {}
+
+    @Override
+    public void startHandshake() throws IOException {}
+  }
+}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpReadableBufferTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpReadableBufferTest.java
new file mode 100644
index 0000000..2ece98f
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpReadableBufferTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import io.grpc.internal.ReadableBuffer;
+import io.grpc.internal.ReadableBufferTestBase;
+import okio.Buffer;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link OkHttpReadableBuffer}.
+ */
+@RunWith(JUnit4.class)
+public class OkHttpReadableBufferTest extends ReadableBufferTestBase {
+
+  private OkHttpReadableBuffer buffer;
+
+  /** Initialize buffer. */
+  @SuppressWarnings("resource")
+  @Before
+  public void setup() {
+    Buffer empty = new Buffer();
+    try {
+      buffer = new OkHttpReadableBuffer(empty.writeUtf8(msg));
+    } finally {
+      empty.close();
+    }
+  }
+
+  @Override
+  @Test
+  public void readToByteBufferShouldSucceed() {
+    // Not supported.
+  }
+
+  @Override
+  @Test
+  public void partialReadToByteBufferShouldSucceed() {
+    // Not supported.
+  }
+
+  @Override
+  protected ReadableBuffer buffer() {
+    return buffer;
+  }
+}
+
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpTlsUpgraderTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpTlsUpgraderTest.java
new file mode 100644
index 0000000..05c649a
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpTlsUpgraderTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static io.grpc.okhttp.OkHttpTlsUpgrader.canonicalizeHost;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.okhttp.internal.Protocol;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link io.grpc.okhttp.OkHttpTlsUpgrader}. */
+@RunWith(JUnit4.class)
+public class OkHttpTlsUpgraderTest {
+  @Test public void upgrade_grpcExp() {
+    assertTrue(
+        OkHttpTlsUpgrader.TLS_PROTOCOLS.indexOf(Protocol.GRPC_EXP) == -1
+            || OkHttpTlsUpgrader.TLS_PROTOCOLS.indexOf(Protocol.GRPC_EXP)
+                < OkHttpTlsUpgrader.TLS_PROTOCOLS.indexOf(Protocol.HTTP_2));
+  }
+
+  @Test public void canonicalizeHosts() {
+    assertEquals("::1", canonicalizeHost("::1"));
+    assertEquals("::1", canonicalizeHost("[::1]"));
+    assertEquals("127.0.0.1", canonicalizeHost("127.0.0.1"));
+    assertEquals("some.long.url.com", canonicalizeHost("some.long.url.com"));
+
+    // Extra square brackets in a malformed URI are retained
+    assertEquals("[::1]", canonicalizeHost("[[::1]]"));
+  }
+}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpTransportTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpTransportTest.java
new file mode 100644
index 0000000..97a686d
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpTransportTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import io.grpc.ServerStreamTracer;
+import io.grpc.internal.AccessProtectedHack;
+import io.grpc.internal.ClientTransportFactory;
+import io.grpc.internal.FakeClock;
+import io.grpc.internal.InternalServer;
+import io.grpc.internal.ManagedClientTransport;
+import io.grpc.internal.testing.AbstractTransportTest;
+import io.grpc.netty.NettyServerBuilder;
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for OkHttp transport. */
+@RunWith(JUnit4.class)
+public class OkHttpTransportTest extends AbstractTransportTest {
+  private final FakeClock fakeClock = new FakeClock();
+  private ClientTransportFactory clientFactory =
+      OkHttpChannelBuilder
+          // Although specified here, address is ignored because we never call build.
+          .forAddress("localhost", 0)
+          .usePlaintext()
+          .setTransportTracerFactory(fakeClockTransportTracer)
+          .buildTransportFactory();
+
+  @After
+  public void releaseClientFactory() {
+    clientFactory.close();
+  }
+
+  @Override
+  protected InternalServer newServer(List<ServerStreamTracer.Factory> streamTracerFactories) {
+    return AccessProtectedHack.serverBuilderBuildTransportServer(
+        NettyServerBuilder
+          .forPort(0)
+          .flowControlWindow(65 * 1024),
+        streamTracerFactories,
+        fakeClockTransportTracer);
+  }
+
+  @Override
+  protected InternalServer newServer(
+      InternalServer server, List<ServerStreamTracer.Factory> streamTracerFactories) {
+    int port = server.getPort();
+    return AccessProtectedHack.serverBuilderBuildTransportServer(
+        NettyServerBuilder
+            .forPort(port)
+            .flowControlWindow(65 * 1024),
+        streamTracerFactories,
+        fakeClockTransportTracer);
+  }
+
+  @Override
+  protected String testAuthority(InternalServer server) {
+    return "thebestauthority:" + server.getPort();
+  }
+
+  @Override
+  protected ManagedClientTransport newClientTransport(InternalServer server) {
+    int port = server.getPort();
+    return clientFactory.newClientTransport(
+        new InetSocketAddress("localhost", port),
+        new ClientTransportFactory.ClientTransportOptions()
+          .setAuthority(testAuthority(server)));
+  }
+
+  @Override
+  protected void advanceClock(long offset, TimeUnit unit) {
+    fakeClock.forwardNanos(unit.toNanos(offset));
+  }
+
+  @Override
+  protected long fakeCurrentTimeNanos() {
+    return fakeClock.getTicker().read();
+  }
+
+  @Override
+  protected boolean haveTransportTracer() {
+    return true;
+  }
+}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpWritableBufferAllocatorTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpWritableBufferAllocatorTest.java
new file mode 100644
index 0000000..e606b6b
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpWritableBufferAllocatorTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static org.junit.Assert.assertEquals;
+
+import io.grpc.internal.WritableBuffer;
+import io.grpc.internal.WritableBufferAllocator;
+import io.grpc.internal.WritableBufferAllocatorTestBase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link OkHttpWritableBufferAllocator}.
+ */
+@RunWith(JUnit4.class)
+public class OkHttpWritableBufferAllocatorTest extends WritableBufferAllocatorTestBase {
+
+  private final OkHttpWritableBufferAllocator allocator = new OkHttpWritableBufferAllocator();
+
+  @Override
+  protected WritableBufferAllocator allocator() {
+    return allocator;
+  }
+
+  @Test
+  public void testCapacity() {
+    WritableBuffer buffer = allocator().allocate(4096);
+    assertEquals(0, buffer.readableBytes());
+    assertEquals(4096, buffer.writableBytes());
+  }
+
+  @Test
+  public void testInitialCapacityHasMaximum() {
+    WritableBuffer buffer = allocator().allocate(1024 * 1025);
+    assertEquals(0, buffer.readableBytes());
+    assertEquals(1024 * 1024, buffer.writableBytes());
+  }
+
+  @Test
+  public void testIsExactBelowMaxCapacity() {
+    WritableBuffer buffer = allocator().allocate(4097);
+    assertEquals(0, buffer.readableBytes());
+    assertEquals(4097, buffer.writableBytes());
+  }
+}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OkHttpWritableBufferTest.java b/okhttp/src/test/java/io/grpc/okhttp/OkHttpWritableBufferTest.java
new file mode 100644
index 0000000..98b7703
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/OkHttpWritableBufferTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import io.grpc.internal.WritableBuffer;
+import io.grpc.internal.WritableBufferTestBase;
+import okio.Buffer;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link OkHttpWritableBuffer}.
+ */
+@RunWith(JUnit4.class)
+public class OkHttpWritableBufferTest extends WritableBufferTestBase {
+
+  private OkHttpWritableBuffer buffer;
+
+  @Before
+  public void setup() {
+    buffer = new OkHttpWritableBuffer(new Buffer(), 100);
+  }
+
+  @Override
+  protected WritableBuffer buffer() {
+    return buffer;
+  }
+
+  @Override
+  protected byte[] writtenBytes() {
+    return buffer.buffer().readByteArray();
+  }
+}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/OptionalMethodTest.java b/okhttp/src/test/java/io/grpc/okhttp/OptionalMethodTest.java
new file mode 100644
index 0000000..c2515d2
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/OptionalMethodTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.okhttp.internal.OptionalMethod;
+import java.lang.reflect.InvocationTargetException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for OptionalMethod.
+ */
+@RunWith(JUnit4.class)
+public class OptionalMethodTest {
+
+  public static class DefaultClass {
+    public String testMethod(String arg) {
+      return arg;
+    }
+  }
+
+  public abstract static class PublicParent {
+    public abstract String testMethod(String arg);
+  }
+
+  private static class PrivateImpl extends PublicParent {
+    @Override
+    public String testMethod(String arg) {
+      return arg;
+    }
+  }
+
+  private static class PrivateClass {
+    public String testMethod(String arg) {
+      return arg;
+    }
+  }
+
+  @Test
+  public void isSupported() {
+    OptionalMethod<DefaultClass> defaultClassMethod = new OptionalMethod<DefaultClass>(
+        String.class, "testMethod", String.class);
+    assertTrue(defaultClassMethod.isSupported(new DefaultClass()));
+
+    OptionalMethod<PublicParent> privateImpl = new OptionalMethod<PublicParent>(
+        String.class, "testMethod", String.class);
+    assertTrue(privateImpl.isSupported(new PrivateImpl()));
+
+    OptionalMethod<PrivateClass> privateClass = new OptionalMethod<PrivateClass>(
+        String.class, "testMethod", String.class);
+    assertFalse(privateClass.isSupported(new PrivateClass()));
+  }
+
+  @Test
+  public void invokeOptional() throws InvocationTargetException {
+    OptionalMethod<DefaultClass> defaultClassMethod = new OptionalMethod<DefaultClass>(
+        String.class, "testMethod", String.class);
+    assertEquals("testArg", defaultClassMethod.invokeOptional(new DefaultClass(), "testArg"));
+
+    OptionalMethod<PublicParent> privateImpl = new OptionalMethod<PublicParent>(
+        String.class, "testMethod", String.class);
+    assertEquals("testArg", privateImpl.invokeOptional(new PrivateImpl(), "testArg"));
+
+    OptionalMethod<PrivateClass> privateClass = new OptionalMethod<PrivateClass>(
+        String.class, "testMethod", String.class);
+    assertEquals(null, privateClass.invokeOptional(new PrivateClass(), "testArg"));
+  }
+}
diff --git a/okhttp/src/test/java/io/grpc/okhttp/UtilsTest.java b/okhttp/src/test/java/io/grpc/okhttp/UtilsTest.java
new file mode 100644
index 0000000..cdaf98c
--- /dev/null
+++ b/okhttp/src/test/java/io/grpc/okhttp/UtilsTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.InternalChannelz.SocketOptions;
+import io.grpc.okhttp.internal.CipherSuite;
+import io.grpc.okhttp.internal.ConnectionSpec;
+import io.grpc.okhttp.internal.TlsVersion;
+import java.net.Socket;
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link Utils}.
+ */
+@RunWith(JUnit4.class)
+public class UtilsTest {
+
+  @Rule
+  public final ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void convertSpecRejectsPlaintext() {
+    com.squareup.okhttp.ConnectionSpec plaintext = com.squareup.okhttp.ConnectionSpec.CLEARTEXT;
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("plaintext ConnectionSpec is not accepted");
+    Utils.convertSpec(plaintext);
+  }
+
+  @Test
+  public void convertSpecKeepsAllData() {
+    com.squareup.okhttp.ConnectionSpec squareSpec = com.squareup.okhttp.ConnectionSpec.MODERN_TLS;
+    ConnectionSpec spec = Utils.convertSpec(squareSpec);
+
+    List<com.squareup.okhttp.TlsVersion> squareTlsVersions = squareSpec.tlsVersions();
+    List<TlsVersion> tlsVersions = spec.tlsVersions();
+    int versionsSize = squareTlsVersions.size();
+    List<com.squareup.okhttp.CipherSuite> squareCipherSuites = squareSpec.cipherSuites();
+    List<CipherSuite> cipherSuites = spec.cipherSuites();
+    int cipherSuitesSize = squareCipherSuites.size();
+
+    assertTrue(spec.isTls());
+    assertTrue(spec.supportsTlsExtensions());
+    assertEquals(versionsSize, tlsVersions.size());
+    for (int i = 0; i < versionsSize; i++) {
+      assertEquals(TlsVersion.forJavaName(squareTlsVersions.get(i).javaName()), tlsVersions.get(i));
+    }
+    assertEquals(cipherSuitesSize, cipherSuites.size());
+    for (int i = 0; i < cipherSuitesSize; i++) {
+      assertEquals(CipherSuite.forJavaName(squareCipherSuites.get(i).name()), cipherSuites.get(i));
+    }
+  }
+
+  @Test
+  public void getSocketOptions() throws Exception {
+    Socket socket = new Socket();
+    socket.setSoLinger(true, 2);
+    socket.setSoTimeout(3);
+    socket.setTcpNoDelay(true);
+    socket.setReuseAddress(true);
+    socket.setReceiveBufferSize(4000);
+    socket.setSendBufferSize(5000);
+    socket.setKeepAlive(true);
+    socket.setOOBInline(true);
+    socket.setTrafficClass(8); // note: see javadoc for valid input values
+
+    SocketOptions socketOptions = Utils.getSocketOptions(socket);
+    assertEquals(2, (int) socketOptions.lingerSeconds);
+    assertEquals(3, (int) socketOptions.soTimeoutMillis);
+    assertEquals("true", socketOptions.others.get("TCP_NODELAY"));
+    assertEquals("true", socketOptions.others.get("SO_REUSEADDR"));
+    assertEquals("4000", socketOptions.others.get("SO_RECVBUF"));
+    assertEquals("5000", socketOptions.others.get("SO_SNDBUF"));
+    assertEquals("true", socketOptions.others.get("SO_KEEPALIVE"));
+    assertEquals("true", socketOptions.others.get("SO_OOBINLINE"));
+    assertEquals("8", socketOptions.others.get("IP_TOS"));
+  }
+}
diff --git a/okhttp/third_party/okhttp/LICENSE b/okhttp/third_party/okhttp/LICENSE
new file mode 100644
index 0000000..7a4a3ea
--- /dev/null
+++ b/okhttp/third_party/okhttp/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
\ No newline at end of file
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/CipherSuite.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/CipherSuite.java
new file mode 100644
index 0000000..bc2a9b7
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/CipherSuite.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal;
+
+import static java.lang.Integer.MAX_VALUE;
+
+/**
+ * <a href="https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml">TLS cipher
+ * suites</a>.
+ *
+ * <p><strong>Not all cipher suites are supported on all platforms.</strong> As newer cipher suites
+ * are created (for stronger privacy, better performance, etc.) they will be adopted by the platform
+ * and then exposed here. Cipher suites that are not available on either Android (through API level
+ * 20) or Java (through JDK 8) are omitted for brevity.
+ *
+ * <p>See also <a href="https://android.googlesource.com/platform/external/conscrypt/+/master/src/main/java/org/conscrypt/NativeCrypto.java">NativeCrypto.java</a>
+ * from conscrypt, which lists the cipher suites supported by Android.
+ */
+public enum CipherSuite {
+  // Last updated 2014-11-11 using cipher suites from Android 21 and Java 8.
+
+  // TLS_NULL_WITH_NULL_NULL("TLS_NULL_WITH_NULL_NULL", 0x0000, 5246, MAX_VALUE, MAX_VALUE),
+  TLS_RSA_WITH_NULL_MD5("SSL_RSA_WITH_NULL_MD5", 0x0001, 5246, 6, 10),
+  TLS_RSA_WITH_NULL_SHA("SSL_RSA_WITH_NULL_SHA", 0x0002, 5246, 6, 10),
+  TLS_RSA_EXPORT_WITH_RC4_40_MD5("SSL_RSA_EXPORT_WITH_RC4_40_MD5", 0x0003, 4346, 6, 10),
+  TLS_RSA_WITH_RC4_128_MD5("SSL_RSA_WITH_RC4_128_MD5", 0x0004, 5246, 6, 10),
+  TLS_RSA_WITH_RC4_128_SHA("SSL_RSA_WITH_RC4_128_SHA", 0x0005, 5246, 6, 10),
+  // TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5("SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5", 0x0006, 4346, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_IDEA_CBC_SHA("TLS_RSA_WITH_IDEA_CBC_SHA", 0x0007, 5469, MAX_VALUE, MAX_VALUE),
+  TLS_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0008, 4346, 6, 10),
+  TLS_RSA_WITH_DES_CBC_SHA("SSL_RSA_WITH_DES_CBC_SHA", 0x0009, 5469, 6, 10),
+  TLS_RSA_WITH_3DES_EDE_CBC_SHA("SSL_RSA_WITH_3DES_EDE_CBC_SHA", 0x000a, 5246, 6, 10),
+  // TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x000b, 4346, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_DES_CBC_SHA("TLS_DH_DSS_WITH_DES_CBC_SHA", 0x000c, 5469, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA("TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA", 0x000d, 5246, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x000e, 4346, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_DES_CBC_SHA("TLS_DH_RSA_WITH_DES_CBC_SHA", 0x000f, 5469, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA("TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA", 0x0010, 5246, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA("SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", 0x0011, 4346, 6, 10),
+  TLS_DHE_DSS_WITH_DES_CBC_SHA("SSL_DHE_DSS_WITH_DES_CBC_SHA", 0x0012, 5469, 6, 10),
+  TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA("SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", 0x0013, 5246, 6, 10),
+  TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA("SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", 0x0014, 4346, 6, 10),
+  TLS_DHE_RSA_WITH_DES_CBC_SHA("SSL_DHE_RSA_WITH_DES_CBC_SHA", 0x0015, 5469, 6, 10),
+  TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA("SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", 0x0016, 5246, 6, 10),
+  TLS_DH_anon_EXPORT_WITH_RC4_40_MD5("SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", 0x0017, 4346, 6, 10),
+  TLS_DH_anon_WITH_RC4_128_MD5("SSL_DH_anon_WITH_RC4_128_MD5", 0x0018, 5246, 6, 10),
+  TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA("SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA", 0x0019, 4346, 6, 10),
+  TLS_DH_anon_WITH_DES_CBC_SHA("SSL_DH_anon_WITH_DES_CBC_SHA", 0x001a, 5469, 6, 10),
+  TLS_DH_anon_WITH_3DES_EDE_CBC_SHA("SSL_DH_anon_WITH_3DES_EDE_CBC_SHA", 0x001b, 5246, 6, 10),
+  TLS_KRB5_WITH_DES_CBC_SHA("TLS_KRB5_WITH_DES_CBC_SHA", 0x001e, 2712, 6, MAX_VALUE),
+  TLS_KRB5_WITH_3DES_EDE_CBC_SHA("TLS_KRB5_WITH_3DES_EDE_CBC_SHA", 0x001f, 2712, 6, MAX_VALUE),
+  TLS_KRB5_WITH_RC4_128_SHA("TLS_KRB5_WITH_RC4_128_SHA", 0x0020, 2712, 6, MAX_VALUE),
+  // TLS_KRB5_WITH_IDEA_CBC_SHA("TLS_KRB5_WITH_IDEA_CBC_SHA", 0x0021, 2712, MAX_VALUE, MAX_VALUE),
+  TLS_KRB5_WITH_DES_CBC_MD5("TLS_KRB5_WITH_DES_CBC_MD5", 0x0022, 2712, 6, MAX_VALUE),
+  TLS_KRB5_WITH_3DES_EDE_CBC_MD5("TLS_KRB5_WITH_3DES_EDE_CBC_MD5", 0x0023, 2712, 6, MAX_VALUE),
+  TLS_KRB5_WITH_RC4_128_MD5("TLS_KRB5_WITH_RC4_128_MD5", 0x0024, 2712, 6, MAX_VALUE),
+  // TLS_KRB5_WITH_IDEA_CBC_MD5("TLS_KRB5_WITH_IDEA_CBC_MD5", 0x0025, 2712, MAX_VALUE, MAX_VALUE),
+  TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA("TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", 0x0026, 2712, 6, MAX_VALUE),
+  // TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", 0x0027, 2712, MAX_VALUE, MAX_VALUE),
+  TLS_KRB5_EXPORT_WITH_RC4_40_SHA("TLS_KRB5_EXPORT_WITH_RC4_40_SHA", 0x0028, 2712, 6, MAX_VALUE),
+  TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5("TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", 0x0029, 2712, 6, MAX_VALUE),
+  // TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5("TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", 0x002a, 2712, MAX_VALUE, MAX_VALUE),
+  TLS_KRB5_EXPORT_WITH_RC4_40_MD5("TLS_KRB5_EXPORT_WITH_RC4_40_MD5", 0x002b, 2712, 6, MAX_VALUE),
+  // TLS_PSK_WITH_NULL_SHA("TLS_PSK_WITH_NULL_SHA", 0x002c, 4785, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_NULL_SHA("TLS_DHE_PSK_WITH_NULL_SHA", 0x002d, 4785, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_NULL_SHA("TLS_RSA_PSK_WITH_NULL_SHA", 0x002e, 4785, MAX_VALUE, MAX_VALUE),
+  TLS_RSA_WITH_AES_128_CBC_SHA("TLS_RSA_WITH_AES_128_CBC_SHA", 0x002f, 5246, 6, 10),
+  // TLS_DH_DSS_WITH_AES_128_CBC_SHA("TLS_DH_DSS_WITH_AES_128_CBC_SHA", 0x0030, 5246, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_AES_128_CBC_SHA("TLS_DH_RSA_WITH_AES_128_CBC_SHA", 0x0031, 5246, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_DSS_WITH_AES_128_CBC_SHA("TLS_DHE_DSS_WITH_AES_128_CBC_SHA", 0x0032, 5246, 6, 10),
+  TLS_DHE_RSA_WITH_AES_128_CBC_SHA("TLS_DHE_RSA_WITH_AES_128_CBC_SHA", 0x0033, 5246, 6, 10),
+  TLS_DH_anon_WITH_AES_128_CBC_SHA("TLS_DH_anon_WITH_AES_128_CBC_SHA", 0x0034, 5246, 6, 10),
+  TLS_RSA_WITH_AES_256_CBC_SHA("TLS_RSA_WITH_AES_256_CBC_SHA", 0x0035, 5246, 6, 10),
+  // TLS_DH_DSS_WITH_AES_256_CBC_SHA("TLS_DH_DSS_WITH_AES_256_CBC_SHA", 0x0036, 5246, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_AES_256_CBC_SHA("TLS_DH_RSA_WITH_AES_256_CBC_SHA", 0x0037, 5246, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_DSS_WITH_AES_256_CBC_SHA("TLS_DHE_DSS_WITH_AES_256_CBC_SHA", 0x0038, 5246, 6, 10),
+  TLS_DHE_RSA_WITH_AES_256_CBC_SHA("TLS_DHE_RSA_WITH_AES_256_CBC_SHA", 0x0039, 5246, 6, 10),
+  TLS_DH_anon_WITH_AES_256_CBC_SHA("TLS_DH_anon_WITH_AES_256_CBC_SHA", 0x003a, 5246, 6, 10),
+  TLS_RSA_WITH_NULL_SHA256("TLS_RSA_WITH_NULL_SHA256", 0x003b, 5246, 7, 21),
+  TLS_RSA_WITH_AES_128_CBC_SHA256("TLS_RSA_WITH_AES_128_CBC_SHA256", 0x003c, 5246, 7, 21),
+  TLS_RSA_WITH_AES_256_CBC_SHA256("TLS_RSA_WITH_AES_256_CBC_SHA256", 0x003d, 5246, 7, 21),
+  // TLS_DH_DSS_WITH_AES_128_CBC_SHA256("TLS_DH_DSS_WITH_AES_128_CBC_SHA256", 0x003e, 5246, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_AES_128_CBC_SHA256("TLS_DH_RSA_WITH_AES_128_CBC_SHA256", 0x003f, 5246, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_DSS_WITH_AES_128_CBC_SHA256("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", 0x0040, 5246, 7, 21),
+  // TLS_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0041, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA", 0x0042, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0043, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA", 0x0044, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", 0x0045, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", 0x0046, 5932, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_RSA_WITH_AES_128_CBC_SHA256("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", 0x0067, 5246, 7, 21),
+  // TLS_DH_DSS_WITH_AES_256_CBC_SHA256("TLS_DH_DSS_WITH_AES_256_CBC_SHA256", 0x0068, 5246, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_AES_256_CBC_SHA256("TLS_DH_RSA_WITH_AES_256_CBC_SHA256", 0x0069, 5246, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_DSS_WITH_AES_256_CBC_SHA256("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", 0x006a, 5246, 7, 21),
+  TLS_DHE_RSA_WITH_AES_256_CBC_SHA256("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", 0x006b, 5246, 7, 21),
+  TLS_DH_anon_WITH_AES_128_CBC_SHA256("TLS_DH_anon_WITH_AES_128_CBC_SHA256", 0x006c, 5246, 7, 21),
+  TLS_DH_anon_WITH_AES_256_CBC_SHA256("TLS_DH_anon_WITH_AES_256_CBC_SHA256", 0x006d, 5246, 7, 21),
+  // TLS_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0084, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA", 0x0085, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0086, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA", 0x0087, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", 0x0088, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", 0x0089, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_RC4_128_SHA("TLS_PSK_WITH_RC4_128_SHA", 0x008a, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_3DES_EDE_CBC_SHA("TLS_PSK_WITH_3DES_EDE_CBC_SHA", 0x008b, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_128_CBC_SHA("TLS_PSK_WITH_AES_128_CBC_SHA", 0x008c, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_256_CBC_SHA("TLS_PSK_WITH_AES_256_CBC_SHA", 0x008d, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_RC4_128_SHA("TLS_DHE_PSK_WITH_RC4_128_SHA", 0x008e, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA("TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA", 0x008f, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_128_CBC_SHA("TLS_DHE_PSK_WITH_AES_128_CBC_SHA", 0x0090, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_256_CBC_SHA("TLS_DHE_PSK_WITH_AES_256_CBC_SHA", 0x0091, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_RC4_128_SHA("TLS_RSA_PSK_WITH_RC4_128_SHA", 0x0092, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA("TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA", 0x0093, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_AES_128_CBC_SHA("TLS_RSA_PSK_WITH_AES_128_CBC_SHA", 0x0094, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_AES_256_CBC_SHA("TLS_RSA_PSK_WITH_AES_256_CBC_SHA", 0x0095, 4279, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_SEED_CBC_SHA("TLS_RSA_WITH_SEED_CBC_SHA", 0x0096, 4162, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_SEED_CBC_SHA("TLS_DH_DSS_WITH_SEED_CBC_SHA", 0x0097, 4162, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_SEED_CBC_SHA("TLS_DH_RSA_WITH_SEED_CBC_SHA", 0x0098, 4162, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_SEED_CBC_SHA("TLS_DHE_DSS_WITH_SEED_CBC_SHA", 0x0099, 4162, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_SEED_CBC_SHA("TLS_DHE_RSA_WITH_SEED_CBC_SHA", 0x009a, 4162, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_SEED_CBC_SHA("TLS_DH_anon_WITH_SEED_CBC_SHA", 0x009b, 4162, MAX_VALUE, MAX_VALUE),
+  TLS_RSA_WITH_AES_128_GCM_SHA256("TLS_RSA_WITH_AES_128_GCM_SHA256", 0x009c, 5288, 8, 21),
+  TLS_RSA_WITH_AES_256_GCM_SHA384("TLS_RSA_WITH_AES_256_GCM_SHA384", 0x009d, 5288, 8, 21),
+  TLS_DHE_RSA_WITH_AES_128_GCM_SHA256("TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", 0x009e, 5288, 8, 21),
+  TLS_DHE_RSA_WITH_AES_256_GCM_SHA384("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", 0x009f, 5288, 8, 21),
+  // TLS_DH_RSA_WITH_AES_128_GCM_SHA256("TLS_DH_RSA_WITH_AES_128_GCM_SHA256", 0x00a0, 5288, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_AES_256_GCM_SHA384("TLS_DH_RSA_WITH_AES_256_GCM_SHA384", 0x00a1, 5288, MAX_VALUE, MAX_VALUE),
+  TLS_DHE_DSS_WITH_AES_128_GCM_SHA256("TLS_DHE_DSS_WITH_AES_128_GCM_SHA256", 0x00a2, 5288, 8, 21),
+  TLS_DHE_DSS_WITH_AES_256_GCM_SHA384("TLS_DHE_DSS_WITH_AES_256_GCM_SHA384", 0x00a3, 5288, 8, 21),
+  // TLS_DH_DSS_WITH_AES_128_GCM_SHA256("TLS_DH_DSS_WITH_AES_128_GCM_SHA256", 0x00a4, 5288, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_AES_256_GCM_SHA384("TLS_DH_DSS_WITH_AES_256_GCM_SHA384", 0x00a5, 5288, MAX_VALUE, MAX_VALUE),
+  TLS_DH_anon_WITH_AES_128_GCM_SHA256("TLS_DH_anon_WITH_AES_128_GCM_SHA256", 0x00a6, 5288, 8, 21),
+  TLS_DH_anon_WITH_AES_256_GCM_SHA384("TLS_DH_anon_WITH_AES_256_GCM_SHA384", 0x00a7, 5288, 8, 21),
+  // TLS_PSK_WITH_AES_128_GCM_SHA256("TLS_PSK_WITH_AES_128_GCM_SHA256", 0x00a8, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_256_GCM_SHA384("TLS_PSK_WITH_AES_256_GCM_SHA384", 0x00a9, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_128_GCM_SHA256("TLS_DHE_PSK_WITH_AES_128_GCM_SHA256", 0x00aa, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_256_GCM_SHA384("TLS_DHE_PSK_WITH_AES_256_GCM_SHA384", 0x00ab, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_AES_128_GCM_SHA256("TLS_RSA_PSK_WITH_AES_128_GCM_SHA256", 0x00ac, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_AES_256_GCM_SHA384("TLS_RSA_PSK_WITH_AES_256_GCM_SHA384", 0x00ad, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_128_CBC_SHA256("TLS_PSK_WITH_AES_128_CBC_SHA256", 0x00ae, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_256_CBC_SHA384("TLS_PSK_WITH_AES_256_CBC_SHA384", 0x00af, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_NULL_SHA256("TLS_PSK_WITH_NULL_SHA256", 0x00b0, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_NULL_SHA384("TLS_PSK_WITH_NULL_SHA384", 0x00b1, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_128_CBC_SHA256("TLS_DHE_PSK_WITH_AES_128_CBC_SHA256", 0x00b2, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_256_CBC_SHA384("TLS_DHE_PSK_WITH_AES_256_CBC_SHA384", 0x00b3, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_NULL_SHA256("TLS_DHE_PSK_WITH_NULL_SHA256", 0x00b4, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_NULL_SHA384("TLS_DHE_PSK_WITH_NULL_SHA384", 0x00b5, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_AES_128_CBC_SHA256("TLS_RSA_PSK_WITH_AES_128_CBC_SHA256", 0x00b6, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_AES_256_CBC_SHA384("TLS_RSA_PSK_WITH_AES_256_CBC_SHA384", 0x00b7, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_NULL_SHA256("TLS_RSA_PSK_WITH_NULL_SHA256", 0x00b8, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_NULL_SHA384("TLS_RSA_PSK_WITH_NULL_SHA384", 0x00b9, 5487, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00ba, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256", 0x00bb, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00bc, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256", 0x00bd, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0x00be, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256("TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", 0x00bf, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c0, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256", 0x00c1, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c2, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256", 0x00c3, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", 0x00c4, 5932, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256("TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", 0x00c5, 5932, MAX_VALUE, MAX_VALUE),
+  TLS_EMPTY_RENEGOTIATION_INFO_SCSV("TLS_EMPTY_RENEGOTIATION_INFO_SCSV", 0x00ff, 5746, 6, 14),
+  TLS_ECDH_ECDSA_WITH_NULL_SHA("TLS_ECDH_ECDSA_WITH_NULL_SHA", 0xc001, 4492, 7, 14),
+  TLS_ECDH_ECDSA_WITH_RC4_128_SHA("TLS_ECDH_ECDSA_WITH_RC4_128_SHA", 0xc002, 4492, 7, 14),
+  TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xc003, 4492, 7, 14),
+  TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", 0xc004, 4492, 7, 14),
+  TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", 0xc005, 4492, 7, 14),
+  TLS_ECDHE_ECDSA_WITH_NULL_SHA("TLS_ECDHE_ECDSA_WITH_NULL_SHA", 0xc006, 4492, 7, 14),
+  TLS_ECDHE_ECDSA_WITH_RC4_128_SHA("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", 0xc007, 4492, 7, 14),
+  TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", 0xc008, 4492, 7, 14),
+  TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", 0xc009, 4492, 7, 14),
+  TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", 0xc00a, 4492, 7, 14),
+  TLS_ECDH_RSA_WITH_NULL_SHA("TLS_ECDH_RSA_WITH_NULL_SHA", 0xc00b, 4492, 7, 14),
+  TLS_ECDH_RSA_WITH_RC4_128_SHA("TLS_ECDH_RSA_WITH_RC4_128_SHA", 0xc00c, 4492, 7, 14),
+  TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", 0xc00d, 4492, 7, 14),
+  TLS_ECDH_RSA_WITH_AES_128_CBC_SHA("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", 0xc00e, 4492, 7, 14),
+  TLS_ECDH_RSA_WITH_AES_256_CBC_SHA("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", 0xc00f, 4492, 7, 14),
+  TLS_ECDHE_RSA_WITH_NULL_SHA("TLS_ECDHE_RSA_WITH_NULL_SHA", 0xc010, 4492, 7, 14),
+  TLS_ECDHE_RSA_WITH_RC4_128_SHA("TLS_ECDHE_RSA_WITH_RC4_128_SHA", 0xc011, 4492, 7, 14),
+  TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", 0xc012, 4492, 7, 14),
+  TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", 0xc013, 4492, 7, 14),
+  TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", 0xc014, 4492, 7, 14),
+  TLS_ECDH_anon_WITH_NULL_SHA("TLS_ECDH_anon_WITH_NULL_SHA", 0xc015, 4492, 7, 14),
+  TLS_ECDH_anon_WITH_RC4_128_SHA("TLS_ECDH_anon_WITH_RC4_128_SHA", 0xc016, 4492, 7, 14),
+  TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA("TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA", 0xc017, 4492, 7, 14),
+  TLS_ECDH_anon_WITH_AES_128_CBC_SHA("TLS_ECDH_anon_WITH_AES_128_CBC_SHA", 0xc018, 4492, 7, 14),
+  TLS_ECDH_anon_WITH_AES_256_CBC_SHA("TLS_ECDH_anon_WITH_AES_256_CBC_SHA", 0xc019, 4492, 7, 14),
+  // TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA", 0xc01a, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA", 0xc01b, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA", 0xc01c, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_WITH_AES_128_CBC_SHA", 0xc01d, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA", 0xc01e, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA("TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA", 0xc01f, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_WITH_AES_256_CBC_SHA", 0xc020, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA", 0xc021, 5054, MAX_VALUE, MAX_VALUE),
+  // TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA("TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA", 0xc022, 5054, MAX_VALUE, MAX_VALUE),
+  TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", 0xc023, 5289, 7, 21),
+  TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", 0xc024, 5289, 7, 21),
+  TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256", 0xc025, 5289, 7, 21),
+  TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384", 0xc026, 5289, 7, 21),
+  TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", 0xc027, 5289, 7, 21),
+  TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", 0xc028, 5289, 7, 21),
+  TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256", 0xc029, 5289, 7, 21),
+  TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384", 0xc02a, 5289, 7, 21),
+  TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 0xc02b, 5289, 8, 21),
+  TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", 0xc02c, 5289, 8, 21),
+  TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", 0xc02d, 5289, 8, 21),
+  TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384", 0xc02e, 5289, 8, 21),
+  TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 0xc02f, 5289, 8, 21),
+  TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 0xc030, 5289, 8, 21),
+  TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256("TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", 0xc031, 5289, 8, 21),
+  TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384("TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384", 0xc032, 5289, 8, 21),
+  // TLS_ECDHE_PSK_WITH_RC4_128_SHA("TLS_ECDHE_PSK_WITH_RC4_128_SHA", 0xc033, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA("TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA", 0xc034, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA", 0xc035, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA", 0xc036, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256", 0xc037, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384", 0xc038, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_NULL_SHA("TLS_ECDHE_PSK_WITH_NULL_SHA", 0xc039, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_NULL_SHA256("TLS_ECDHE_PSK_WITH_NULL_SHA256", 0xc03a, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_NULL_SHA384("TLS_ECDHE_PSK_WITH_NULL_SHA384", 0xc03b, 5489, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_ARIA_128_CBC_SHA256("TLS_RSA_WITH_ARIA_128_CBC_SHA256", 0xc03c, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_ARIA_256_CBC_SHA384("TLS_RSA_WITH_ARIA_256_CBC_SHA384", 0xc03d, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256("TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256", 0xc03e, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384("TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384", 0xc03f, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256("TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256", 0xc040, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384("TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384", 0xc041, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256("TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256", 0xc042, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384("TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384", 0xc043, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256("TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256", 0xc044, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384("TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384", 0xc045, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_ARIA_128_CBC_SHA256("TLS_DH_anon_WITH_ARIA_128_CBC_SHA256", 0xc046, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_ARIA_256_CBC_SHA384("TLS_DH_anon_WITH_ARIA_256_CBC_SHA384", 0xc047, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256", 0xc048, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384", 0xc049, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256", 0xc04a, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384", 0xc04b, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256", 0xc04c, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384", 0xc04d, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256("TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256", 0xc04e, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384("TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384", 0xc04f, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_ARIA_128_GCM_SHA256("TLS_RSA_WITH_ARIA_128_GCM_SHA256", 0xc050, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_ARIA_256_GCM_SHA384("TLS_RSA_WITH_ARIA_256_GCM_SHA384", 0xc051, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256("TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256", 0xc052, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384("TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384", 0xc053, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256("TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256", 0xc054, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384("TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384", 0xc055, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256("TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256", 0xc056, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384("TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384", 0xc057, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256("TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256", 0xc058, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384("TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384", 0xc059, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_ARIA_128_GCM_SHA256("TLS_DH_anon_WITH_ARIA_128_GCM_SHA256", 0xc05a, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_ARIA_256_GCM_SHA384("TLS_DH_anon_WITH_ARIA_256_GCM_SHA384", 0xc05b, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256", 0xc05c, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384", 0xc05d, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256", 0xc05e, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384", 0xc05f, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256", 0xc060, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384", 0xc061, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256("TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256", 0xc062, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384("TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384", 0xc063, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_ARIA_128_CBC_SHA256("TLS_PSK_WITH_ARIA_128_CBC_SHA256", 0xc064, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_ARIA_256_CBC_SHA384("TLS_PSK_WITH_ARIA_256_CBC_SHA384", 0xc065, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256("TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256", 0xc066, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384("TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384", 0xc067, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256("TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256", 0xc068, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384("TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384", 0xc069, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_ARIA_128_GCM_SHA256("TLS_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06a, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_ARIA_256_GCM_SHA384("TLS_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06b, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256("TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06c, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384("TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06d, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256("TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256", 0xc06e, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384("TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384", 0xc06f, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256", 0xc070, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384", 0xc071, 6209, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc072, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc073, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc074, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc075, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc076, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc077, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256", 0xc078, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384", 0xc079, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07a, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07b, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07c, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07d, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc07e, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc07f, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256", 0xc080, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384", 0xc081, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256", 0xc082, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384", 0xc083, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256("TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256", 0xc084, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384("TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384", 0xc085, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc086, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc087, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc088, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc089, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc08a, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc08b, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256("TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256", 0xc08c, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384("TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384", 0xc08d, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc08e, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc08f, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc090, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc091, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256("TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256", 0xc092, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384("TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384", 0xc093, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc094, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc095, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc096, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc097, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc098, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc099, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256("TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256", 0xc09a, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384("TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384", 0xc09b, 6367, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_AES_128_CCM("TLS_RSA_WITH_AES_128_CCM", 0xc09c, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_AES_256_CCM("TLS_RSA_WITH_AES_256_CCM", 0xc09d, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_AES_128_CCM("TLS_DHE_RSA_WITH_AES_128_CCM", 0xc09e, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_AES_256_CCM("TLS_DHE_RSA_WITH_AES_256_CCM", 0xc09f, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_AES_128_CCM_8("TLS_RSA_WITH_AES_128_CCM_8", 0xc0a0, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_RSA_WITH_AES_256_CCM_8("TLS_RSA_WITH_AES_256_CCM_8", 0xc0a1, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_AES_128_CCM_8("TLS_DHE_RSA_WITH_AES_128_CCM_8", 0xc0a2, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_RSA_WITH_AES_256_CCM_8("TLS_DHE_RSA_WITH_AES_256_CCM_8", 0xc0a3, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_128_CCM("TLS_PSK_WITH_AES_128_CCM", 0xc0a4, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_256_CCM("TLS_PSK_WITH_AES_256_CCM", 0xc0a5, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_128_CCM("TLS_DHE_PSK_WITH_AES_128_CCM", 0xc0a6, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_DHE_PSK_WITH_AES_256_CCM("TLS_DHE_PSK_WITH_AES_256_CCM", 0xc0a7, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_128_CCM_8("TLS_PSK_WITH_AES_128_CCM_8", 0xc0a8, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_WITH_AES_256_CCM_8("TLS_PSK_WITH_AES_256_CCM_8", 0xc0a9, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_DHE_WITH_AES_128_CCM_8("TLS_PSK_DHE_WITH_AES_128_CCM_8", 0xc0aa, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_PSK_DHE_WITH_AES_256_CCM_8("TLS_PSK_DHE_WITH_AES_256_CCM_8", 0xc0ab, 6655, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_AES_128_CCM("TLS_ECDHE_ECDSA_WITH_AES_128_CCM", 0xc0ac, 7251, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_AES_256_CCM("TLS_ECDHE_ECDSA_WITH_AES_256_CCM", 0xc0ad, 7251, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8("TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8", 0xc0ae, 7251, MAX_VALUE, MAX_VALUE),
+  // TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8("TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8", 0xc0af, 7251, MAX_VALUE, MAX_VALUE),
+  ;
+
+  final String javaName;
+
+  /**
+   * @param javaName the name used by Java APIs for this cipher suite. Different than the IANA name
+   *     for older cipher suites because the prefix is {@code SSL_} instead of {@code TLS_}.
+   * @param value the integer identifier for this cipher suite. (Documentation only.)
+   * @param rfc the RFC describing this cipher suite. (Documentation only.)
+   * @param sinceJavaVersion the first major Java release supporting this cipher suite.
+   * @param sinceAndroidVersion the first Android SDK version supporting this cipher suite.
+   */
+  private CipherSuite(
+      String javaName, int value, int rfc, int sinceJavaVersion, int sinceAndroidVersion) {
+    this.javaName = javaName;
+  }
+
+  public static CipherSuite forJavaName(String javaName) {
+    return javaName.startsWith("SSL_")
+        ? valueOf("TLS_" + javaName.substring(4))
+        : valueOf(javaName);
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/ConnectionSpec.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/ConnectionSpec.java
new file mode 100644
index 0000000..457e9c3
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/ConnectionSpec.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal;
+
+import java.util.Arrays;
+import java.util.List;
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Specifies configuration for the socket connection that HTTP traffic travels through. For {@code
+ * https:} URLs, this includes the TLS version and cipher suites to use when negotiating a secure
+ * connection.
+ */
+public final class ConnectionSpec {
+
+  // This is a subset of the cipher suites supported in Chrome 37, current as of 2014-10-5.
+  // All of these suites are available on Android 5.0; earlier releases support a subset of
+  // these suites. https://github.com/square/okhttp/issues/330
+  private static final CipherSuite[] APPROVED_CIPHER_SUITES = new CipherSuite[] {
+      CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+      CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+      CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+
+      // Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll
+      // continue to include them until better suites are commonly available. For example, none
+      // of the better cipher suites listed above shipped with Android 4.4 or Java 7.
+      CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+      CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+      CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+      CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+      CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+      CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
+      CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
+      CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
+      CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
+      CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
+      CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+  };
+
+  /** A modern TLS connection with extensions like SNI and ALPN available. */
+  public static final ConnectionSpec MODERN_TLS = new Builder(true)
+      .cipherSuites(APPROVED_CIPHER_SUITES)
+      .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
+      .supportsTlsExtensions(true)
+      .build();
+
+  /** A backwards-compatible fallback connection for interop with obsolete servers. */
+  public static final ConnectionSpec COMPATIBLE_TLS = new Builder(MODERN_TLS)
+      .tlsVersions(TlsVersion.TLS_1_0)
+      .supportsTlsExtensions(true)
+      .build();
+
+  /** Unencrypted, unauthenticated connections for {@code http:} URLs. */
+  public static final ConnectionSpec CLEARTEXT = new Builder(false).build();
+
+  final boolean tls;
+
+  /**
+   * Used if tls == true. The cipher suites to set on the SSLSocket. {@code null} means "use
+   * default set".
+   */
+  private final String[] cipherSuites;
+
+  /** Used if tls == true. The TLS protocol versions to use. */
+  private final String[] tlsVersions;
+
+  final boolean supportsTlsExtensions;
+
+  private ConnectionSpec(Builder builder) {
+    this.tls = builder.tls;
+    this.cipherSuites = builder.cipherSuites;
+    this.tlsVersions = builder.tlsVersions;
+    this.supportsTlsExtensions = builder.supportsTlsExtensions;
+  }
+
+  public boolean isTls() {
+    return tls;
+  }
+
+  /**
+   * Returns the cipher suites to use for a connection. This method can return {@code null} if the
+   * cipher suites enabled by default should be used.
+   */
+  public List<CipherSuite> cipherSuites() {
+    if (cipherSuites == null) {
+      return null;
+    }
+    CipherSuite[] result = new CipherSuite[cipherSuites.length];
+    for (int i = 0; i < cipherSuites.length; i++) {
+      result[i] = CipherSuite.forJavaName(cipherSuites[i]);
+    }
+    return Util.immutableList(result);
+  }
+
+  public List<TlsVersion> tlsVersions() {
+    TlsVersion[] result = new TlsVersion[tlsVersions.length];
+    for (int i = 0; i < tlsVersions.length; i++) {
+      result[i] = TlsVersion.forJavaName(tlsVersions[i]);
+    }
+    return Util.immutableList(result);
+  }
+
+  public boolean supportsTlsExtensions() {
+    return supportsTlsExtensions;
+  }
+
+  /** Applies this spec to {@code sslSocket}. */
+  public void apply(SSLSocket sslSocket, boolean isFallback) {
+    ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);
+
+    sslSocket.setEnabledProtocols(specToApply.tlsVersions);
+
+    String[] cipherSuitesToEnable = specToApply.cipherSuites;
+    // null means "use default set".
+    if (cipherSuitesToEnable != null) {
+      sslSocket.setEnabledCipherSuites(cipherSuitesToEnable);
+    }
+  }
+
+  /**
+   * Returns a copy of this that omits cipher suites and TLS versions not enabled by
+   * {@code sslSocket}.
+   */
+  private ConnectionSpec supportedSpec(SSLSocket sslSocket, boolean isFallback) {
+    String[] cipherSuitesToEnable = null;
+    if (cipherSuites != null) {
+      String[] cipherSuitesToSelectFrom = sslSocket.getEnabledCipherSuites();
+      cipherSuitesToEnable =
+          Util.intersect(String.class, cipherSuites, cipherSuitesToSelectFrom);
+    }
+
+    if (isFallback) {
+      // In accordance with https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00
+      // the SCSV cipher is added to signal that a protocol fallback has taken place.
+      final String fallbackScsv = "TLS_FALLBACK_SCSV";
+      boolean socketSupportsFallbackScsv =
+          Arrays.asList(sslSocket.getSupportedCipherSuites()).contains(fallbackScsv);
+
+      if (socketSupportsFallbackScsv) {
+        // Add the SCSV cipher to the set of enabled cipher suites iff it is supported.
+        String[] oldEnabledCipherSuites = cipherSuitesToEnable != null
+            ? cipherSuitesToEnable
+            : sslSocket.getEnabledCipherSuites();
+        String[] newEnabledCipherSuites = new String[oldEnabledCipherSuites.length + 1];
+        System.arraycopy(oldEnabledCipherSuites, 0,
+            newEnabledCipherSuites, 0, oldEnabledCipherSuites.length);
+        newEnabledCipherSuites[newEnabledCipherSuites.length - 1] = fallbackScsv;
+        cipherSuitesToEnable = newEnabledCipherSuites;
+      }
+    }
+
+    String[] protocolsToSelectFrom = sslSocket.getEnabledProtocols();
+    String[] protocolsToEnable = Util.intersect(String.class, tlsVersions, protocolsToSelectFrom);
+    return new Builder(this)
+        .cipherSuites(cipherSuitesToEnable)
+        .tlsVersions(protocolsToEnable)
+        .build();
+  }
+
+  /**
+   * Returns {@code true} if the socket, as currently configured, supports this ConnectionSpec.
+   * In order for a socket to be compatible the enabled cipher suites and protocols must intersect.
+   *
+   * <p>For cipher suites, at least one of the {@link #cipherSuites() required cipher suites} must
+   * match the socket's enabled cipher suites. If there are no required cipher suites the socket
+   * must have at least one cipher suite enabled.
+   *
+   * <p>For protocols, at least one of the {@link #tlsVersions() required protocols} must match the
+   * socket's enabled protocols.
+   */
+  public boolean isCompatible(SSLSocket socket) {
+    if (!tls) {
+      return false;
+    }
+
+    String[] enabledProtocols = socket.getEnabledProtocols();
+    boolean requiredProtocolsEnabled = nonEmptyIntersection(tlsVersions, enabledProtocols);
+    if (!requiredProtocolsEnabled) {
+      return false;
+    }
+
+    boolean requiredCiphersEnabled;
+    if (cipherSuites == null) {
+      requiredCiphersEnabled = socket.getEnabledCipherSuites().length > 0;
+    } else {
+      String[] enabledCipherSuites = socket.getEnabledCipherSuites();
+      requiredCiphersEnabled = nonEmptyIntersection(cipherSuites, enabledCipherSuites);
+    }
+    return requiredCiphersEnabled;
+  }
+
+  /**
+   * An N*M intersection that terminates if any intersection is found. The sizes of both
+   * arguments are assumed to be so small, and the likelihood of an intersection so great, that it
+   * is not worth the CPU cost of sorting or the memory cost of hashing.
+   */
+  private static boolean nonEmptyIntersection(String[] a, String[] b) {
+    if (a == null || b == null || a.length == 0 || b.length == 0) {
+      return false;
+    }
+    for (String toFind : a) {
+      if (contains(b, toFind)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private static <T> boolean contains(T[] array, T value) {
+    for (T arrayValue : array) {
+      if (Util.equal(value, arrayValue)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override public boolean equals(Object other) {
+    if (!(other instanceof ConnectionSpec)) return false;
+    if (other == this) return true;
+
+    ConnectionSpec that = (ConnectionSpec) other;
+    if (this.tls != that.tls) return false;
+
+    if (tls) {
+      if (!Arrays.equals(this.cipherSuites, that.cipherSuites)) return false;
+      if (!Arrays.equals(this.tlsVersions, that.tlsVersions)) return false;
+      if (this.supportsTlsExtensions != that.supportsTlsExtensions) return false;
+    }
+
+    return true;
+  }
+
+  @Override public int hashCode() {
+    int result = 17;
+    if (tls) {
+      result = 31 * result + Arrays.hashCode(cipherSuites);
+      result = 31 * result + Arrays.hashCode(tlsVersions);
+      result = 31 * result + (supportsTlsExtensions ? 0 : 1);
+    }
+    return result;
+  }
+
+  @Override public String toString() {
+    if (tls) {
+      List<CipherSuite> cipherSuites = cipherSuites();
+      String cipherSuitesString = cipherSuites == null ? "[use default]" : cipherSuites.toString();
+      return "ConnectionSpec(cipherSuites=" + cipherSuitesString
+          + ", tlsVersions=" + tlsVersions()
+          + ", supportsTlsExtensions=" + supportsTlsExtensions
+          + ")";
+    } else {
+      return "ConnectionSpec()";
+    }
+  }
+
+  public static final class Builder {
+    private boolean tls;
+    private String[] cipherSuites;
+    private String[] tlsVersions;
+    private boolean supportsTlsExtensions;
+
+    public Builder(boolean tls) {
+      this.tls = tls;
+    }
+
+    public Builder(ConnectionSpec connectionSpec) {
+      this.tls = connectionSpec.tls;
+      this.cipherSuites = connectionSpec.cipherSuites;
+      this.tlsVersions = connectionSpec.tlsVersions;
+      this.supportsTlsExtensions = connectionSpec.supportsTlsExtensions;
+    }
+
+    public Builder cipherSuites(CipherSuite... cipherSuites) {
+      if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
+
+      // Convert enums to the string names Java wants. This makes a defensive copy!
+      String[] strings = new String[cipherSuites.length];
+      for (int i = 0; i < cipherSuites.length; i++) {
+        strings[i] = cipherSuites[i].javaName;
+      }
+      this.cipherSuites = strings;
+      return this;
+    }
+
+    public Builder cipherSuites(String... cipherSuites) {
+      if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
+
+      if (cipherSuites == null) {
+        this.cipherSuites = null;
+      } else {
+        // This makes a defensive copy!
+        this.cipherSuites = cipherSuites.clone();
+      }
+
+      return this;
+    }
+
+    public Builder tlsVersions(TlsVersion... tlsVersions) {
+      if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
+      if (tlsVersions.length == 0) {
+        throw new IllegalArgumentException("At least one TlsVersion is required");
+      }
+
+      // Convert enums to the string names Java wants. This makes a defensive copy!
+      String[] strings = new String[tlsVersions.length];
+      for (int i = 0; i < tlsVersions.length; i++) {
+        strings[i] = tlsVersions[i].javaName;
+      }
+      this.tlsVersions = strings;
+      return this;
+    }
+
+    public Builder tlsVersions(String... tlsVersions) {
+      if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
+
+      if (tlsVersions == null) {
+        this.tlsVersions = null;
+      } else {
+        // This makes a defensive copy!
+        this.tlsVersions = tlsVersions.clone();
+      }
+
+      return this;
+    }
+
+    public Builder supportsTlsExtensions(boolean supportsTlsExtensions) {
+      if (!tls) throw new IllegalStateException("no TLS extensions for cleartext connections");
+      this.supportsTlsExtensions = supportsTlsExtensions;
+      return this;
+    }
+
+    public ConnectionSpec build() {
+      return new ConnectionSpec(this);
+    }
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/DistinguishedNameParser.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/DistinguishedNameParser.java
new file mode 100644
index 0000000..166b097
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/DistinguishedNameParser.java
@@ -0,0 +1,411 @@
+/*
+ *  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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal;
+
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * A distinguished name (DN) parser. This parser only supports extracting a
+ * string value from a DN. It doesn't support values in the hex-string style.
+ */
+final class DistinguishedNameParser {
+  private final String dn;
+  private final int length;
+  private int pos;
+  private int beg;
+  private int end;
+
+  /** Temporary variable to store positions of the currently parsed item. */
+  private int cur;
+
+  /** Distinguished name characters. */
+  private char[] chars;
+
+  public DistinguishedNameParser(X500Principal principal) {
+    // RFC2253 is used to ensure we get attributes in the reverse
+    // order of the underlying ASN.1 encoding, so that the most
+    // significant values of repeated attributes occur first.
+    this.dn = principal.getName(X500Principal.RFC2253);
+    this.length = this.dn.length();
+  }
+
+  // gets next attribute type: (ALPHA 1*keychar) / oid
+  private String nextAT() {
+    // skip preceding space chars, they can present after
+    // comma or semicolon (compatibility with RFC 1779)
+    for (; pos < length && chars[pos] == ' '; pos++) {
+    }
+    if (pos == length) {
+      return null; // reached the end of DN
+    }
+
+    // mark the beginning of attribute type
+    beg = pos;
+
+    // attribute type chars
+    pos++;
+    for (; pos < length && chars[pos] != '=' && chars[pos] != ' '; pos++) {
+      // we don't follow exact BNF syntax here:
+      // accept any char except space and '='
+    }
+    if (pos >= length) {
+      throw new IllegalStateException("Unexpected end of DN: " + dn);
+    }
+
+    // mark the end of attribute type
+    end = pos;
+
+    // skip trailing space chars between attribute type and '='
+    // (compatibility with RFC 1779)
+    if (chars[pos] == ' ') {
+      for (; pos < length && chars[pos] != '=' && chars[pos] == ' '; pos++) {
+      }
+
+      if (chars[pos] != '=' || pos == length) {
+        throw new IllegalStateException("Unexpected end of DN: " + dn);
+      }
+    }
+
+    pos++; //skip '=' char
+
+    // skip space chars between '=' and attribute value
+    // (compatibility with RFC 1779)
+    for (; pos < length && chars[pos] == ' '; pos++) {
+    }
+
+    // in case of oid attribute type skip its prefix: "oid." or "OID."
+    // (compatibility with RFC 1779)
+    if ((end - beg > 4) && (chars[beg + 3] == '.')
+        && (chars[beg] == 'O' || chars[beg] == 'o')
+        && (chars[beg + 1] == 'I' || chars[beg + 1] == 'i')
+        && (chars[beg + 2] == 'D' || chars[beg + 2] == 'd')) {
+      beg += 4;
+    }
+
+    return new String(chars, beg, end - beg);
+  }
+
+  // gets quoted attribute value: QUOTATION *( quotechar / pair ) QUOTATION
+  private String quotedAV() {
+    pos++;
+    beg = pos;
+    end = beg;
+    while (true) {
+
+      if (pos == length) {
+        throw new IllegalStateException("Unexpected end of DN: " + dn);
+      }
+
+      if (chars[pos] == '"') {
+        // enclosing quotation was found
+        pos++;
+        break;
+      } else if (chars[pos] == '\\') {
+        chars[end] = getEscaped();
+      } else {
+        // shift char: required for string with escaped chars
+        chars[end] = chars[pos];
+      }
+      pos++;
+      end++;
+    }
+
+    // skip trailing space chars before comma or semicolon.
+    // (compatibility with RFC 1779)
+    for (; pos < length && chars[pos] == ' '; pos++) {
+    }
+
+    return new String(chars, beg, end - beg);
+  }
+
+  // gets hex string attribute value: "#" hexstring
+  @SuppressWarnings("NarrowingCompoundAssignment")
+  private String hexAV() {
+    if (pos + 4 >= length) {
+      // encoded byte array  must be not less then 4 c
+      throw new IllegalStateException("Unexpected end of DN: " + dn);
+    }
+
+    beg = pos; // store '#' position
+    pos++;
+    while (true) {
+
+      // check for end of attribute value
+      // looks for space and component separators
+      if (pos == length || chars[pos] == '+' || chars[pos] == ','
+          || chars[pos] == ';') {
+        end = pos;
+        break;
+      }
+
+      if (chars[pos] == ' ') {
+        end = pos;
+        pos++;
+        // skip trailing space chars before comma or semicolon.
+        // (compatibility with RFC 1779)
+        for (; pos < length && chars[pos] == ' '; pos++) {
+        }
+        break;
+      } else if (chars[pos] >= 'A' && chars[pos] <= 'F') {
+        chars[pos] += 32; //to low case
+      }
+
+      pos++;
+    }
+
+    // verify length of hex string
+    // encoded byte array  must be not less then 4 and must be even number
+    int hexLen = end - beg; // skip first '#' char
+    if (hexLen < 5 || (hexLen & 1) == 0) {
+      throw new IllegalStateException("Unexpected end of DN: " + dn);
+    }
+
+    // get byte encoding from string representation
+    byte[] encoded = new byte[hexLen / 2];
+    for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) {
+      encoded[i] = (byte) getByte(p);
+    }
+
+    return new String(chars, beg, hexLen);
+  }
+
+  // gets string attribute value: *( stringchar / pair )
+  private String escapedAV() {
+    beg = pos;
+    end = pos;
+    while (true) {
+      if (pos >= length) {
+        // the end of DN has been found
+        return new String(chars, beg, end - beg);
+      }
+
+      switch (chars[pos]) {
+        case '+':
+        case ',':
+        case ';':
+          // separator char has been found
+          return new String(chars, beg, end - beg);
+        case '\\':
+          // escaped char
+          chars[end++] = getEscaped();
+          pos++;
+          break;
+        case ' ':
+          // need to figure out whether space defines
+          // the end of attribute value or not
+          cur = end;
+
+          pos++;
+          chars[end++] = ' ';
+
+          for (; pos < length && chars[pos] == ' '; pos++) {
+            chars[end++] = ' ';
+          }
+          if (pos == length || chars[pos] == ',' || chars[pos] == '+'
+              || chars[pos] == ';') {
+            // separator char or the end of DN has been found
+            return new String(chars, beg, cur - beg);
+          }
+          break;
+        default:
+          chars[end++] = chars[pos];
+          pos++;
+      }
+    }
+  }
+
+  // returns escaped char
+  private char getEscaped() {
+    pos++;
+    if (pos == length) {
+      throw new IllegalStateException("Unexpected end of DN: " + dn);
+    }
+
+    switch (chars[pos]) {
+      case '"':
+      case '\\':
+      case ',':
+      case '=':
+      case '+':
+      case '<':
+      case '>':
+      case '#':
+      case ';':
+      case ' ':
+      case '*':
+      case '%':
+      case '_':
+        //FIXME: escaping is allowed only for leading or trailing space char
+        return chars[pos];
+      default:
+        // RFC doesn't explicitly say that escaped hex pair is
+        // interpreted as UTF-8 char. It only contains an example of such DN.
+        return getUTF8();
+    }
+  }
+
+  // decodes UTF-8 char
+  // see http://www.unicode.org for UTF-8 bit distribution table
+  private char getUTF8() {
+    int res = getByte(pos);
+    pos++; //FIXME tmp
+
+    if (res < 128) { // one byte: 0-7F
+      return (char) res;
+    } else if (res >= 192 && res <= 247) {
+
+      int count;
+      if (res <= 223) { // two bytes: C0-DF
+        count = 1;
+        res = res & 0x1F;
+      } else if (res <= 239) { // three bytes: E0-EF
+        count = 2;
+        res = res & 0x0F;
+      } else { // four bytes: F0-F7
+        count = 3;
+        res = res & 0x07;
+      }
+
+      int b;
+      for (int i = 0; i < count; i++) {
+        pos++;
+        if (pos == length || chars[pos] != '\\') {
+          return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+        }
+        pos++;
+
+        b = getByte(pos);
+        pos++; //FIXME tmp
+        if ((b & 0xC0) != 0x80) {
+          return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+        }
+
+        res = (res << 6) + (b & 0x3F);
+      }
+      return (char) res;
+    } else {
+      return 0x3F; //FIXME failed to decode UTF-8 char - return '?'
+    }
+  }
+
+  // Returns byte representation of a char pair
+  // The char pair is composed of DN char in
+  // specified 'position' and the next char
+  // According to BNF syntax:
+  // hexchar    = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
+  //                    / "a" / "b" / "c" / "d" / "e" / "f"
+  private int getByte(int position) {
+    if (position + 1 >= length) {
+      throw new IllegalStateException("Malformed DN: " + dn);
+    }
+
+    int b1, b2;
+
+    b1 = chars[position];
+    if (b1 >= '0' && b1 <= '9') {
+      b1 = b1 - '0';
+    } else if (b1 >= 'a' && b1 <= 'f') {
+      b1 = b1 - 87; // 87 = 'a' - 10
+    } else if (b1 >= 'A' && b1 <= 'F') {
+      b1 = b1 - 55; // 55 = 'A' - 10
+    } else {
+      throw new IllegalStateException("Malformed DN: " + dn);
+    }
+
+    b2 = chars[position + 1];
+    if (b2 >= '0' && b2 <= '9') {
+      b2 = b2 - '0';
+    } else if (b2 >= 'a' && b2 <= 'f') {
+      b2 = b2 - 87; // 87 = 'a' - 10
+    } else if (b2 >= 'A' && b2 <= 'F') {
+      b2 = b2 - 55; // 55 = 'A' - 10
+    } else {
+      throw new IllegalStateException("Malformed DN: " + dn);
+    }
+
+    return (b1 << 4) + b2;
+  }
+
+  /**
+   * Parses the DN and returns the most significant attribute value
+   * for an attribute type, or null if none found.
+   *
+   * @param attributeType attribute type to look for (e.g. "ca")
+   */
+  public String findMostSpecific(String attributeType) {
+    // Initialize internal state.
+    pos = 0;
+    beg = 0;
+    end = 0;
+    cur = 0;
+    chars = dn.toCharArray();
+
+    String attType = nextAT();
+    if (attType == null) {
+      return null;
+    }
+    while (true) {
+      String attValue = "";
+
+      if (pos == length) {
+        return null;
+      }
+
+      switch (chars[pos]) {
+        case '"':
+          attValue = quotedAV();
+          break;
+        case '#':
+          attValue = hexAV();
+          break;
+        case '+':
+        case ',':
+        case ';': // compatibility with RFC 1779: semicolon can separate RDNs
+          //empty attribute value
+          break;
+        default:
+          attValue = escapedAV();
+      }
+
+      // Values are ordered from most specific to least specific
+      // due to the RFC2253 formatting. So take the first match
+      // we see.
+      if (attributeType.equalsIgnoreCase(attType)) {
+        return attValue;
+      }
+
+      if (pos >= length) {
+        return null;
+      }
+
+      if (chars[pos] == ',' || chars[pos] == ';') {
+      } else if (chars[pos] != '+') {
+        throw new IllegalStateException("Malformed DN: " + dn);
+      }
+
+      pos++;
+      attType = nextAT();
+      if (attType == null) {
+        throw new IllegalStateException("Malformed DN: " + dn);
+      }
+    }
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/OkHostnameVerifier.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/OkHostnameVerifier.java
new file mode 100644
index 0000000..8b108c7
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/OkHostnameVerifier.java
@@ -0,0 +1,255 @@
+/*
+ *  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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal;
+
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSession;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * A HostnameVerifier consistent with <a
+ * href="http://www.ietf.org/rfc/rfc2818.txt">RFC 2818</a>.
+ */
+public final class OkHostnameVerifier implements HostnameVerifier {
+  public static final OkHostnameVerifier INSTANCE = new OkHostnameVerifier();
+
+  /**
+   * Quick and dirty pattern to differentiate IP addresses from hostnames. This
+   * is an approximation of Android's private InetAddress#isNumeric API.
+   *
+   * <p>This matches IPv6 addresses as a hex string containing at least one
+   * colon, and possibly including dots after the first colon. It matches IPv4
+   * addresses as strings containing only decimal digits and dots. This pattern
+   * matches strings like "a:.23" and "54" that are neither IP addresses nor
+   * hostnames; they will be verified as IP addresses (which is a more strict
+   * verification).
+   */
+  private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile(
+      "([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)");
+
+  private static final int ALT_DNS_NAME = 2;
+  private static final int ALT_IPA_NAME = 7;
+
+  private OkHostnameVerifier() {
+  }
+
+  @Override
+  public boolean verify(String host, SSLSession session) {
+    try {
+      Certificate[] certificates = session.getPeerCertificates();
+      return verify(host, (X509Certificate) certificates[0]);
+    } catch (SSLException e) {
+      return false;
+    }
+  }
+
+  public boolean verify(String host, X509Certificate certificate) {
+    return verifyAsIpAddress(host)
+        ? verifyIpAddress(host, certificate)
+        : verifyHostName(host, certificate);
+  }
+
+  static boolean verifyAsIpAddress(String host) {
+    return VERIFY_AS_IP_ADDRESS.matcher(host).matches();
+  }
+
+  /**
+   * Returns true if {@code certificate} matches {@code ipAddress}.
+   */
+  private boolean verifyIpAddress(String ipAddress, X509Certificate certificate) {
+    List<String> altNames = getSubjectAltNames(certificate, ALT_IPA_NAME);
+    for (int i = 0, size = altNames.size(); i < size; i++) {
+      if (ipAddress.equalsIgnoreCase(altNames.get(i))) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Returns true if {@code certificate} matches {@code hostName}.
+   */
+  private boolean verifyHostName(String hostName, X509Certificate certificate) {
+    hostName = hostName.toLowerCase(Locale.US);
+    boolean hasDns = false;
+    List<String> altNames = getSubjectAltNames(certificate, ALT_DNS_NAME);
+    for (int i = 0, size = altNames.size(); i < size; i++) {
+      hasDns = true;
+      if (verifyHostName(hostName, altNames.get(i))) {
+        return true;
+      }
+    }
+
+    if (!hasDns) {
+      X500Principal principal = certificate.getSubjectX500Principal();
+      // RFC 2818 advises using the most specific name for matching.
+      String cn = new DistinguishedNameParser(principal).findMostSpecific("cn");
+      if (cn != null) {
+        return verifyHostName(hostName, cn);
+      }
+    }
+
+    return false;
+  }
+
+  public static List<String> allSubjectAltNames(X509Certificate certificate) {
+    List<String> altIpaNames = getSubjectAltNames(certificate, ALT_IPA_NAME);
+    List<String> altDnsNames = getSubjectAltNames(certificate, ALT_DNS_NAME);
+    List<String> result = new ArrayList<>(altIpaNames.size() + altDnsNames.size());
+    result.addAll(altIpaNames);
+    result.addAll(altDnsNames);
+    return result;
+  }
+
+  private static List<String> getSubjectAltNames(X509Certificate certificate, int type) {
+    List<String> result = new ArrayList<>();
+    try {
+      Collection<?> subjectAltNames = certificate.getSubjectAlternativeNames();
+      if (subjectAltNames == null) {
+        return Collections.emptyList();
+      }
+      for (Object subjectAltName : subjectAltNames) {
+        List<?> entry = (List<?>) subjectAltName;
+        if (entry == null || entry.size() < 2) {
+          continue;
+        }
+        Integer altNameType = (Integer) entry.get(0);
+        if (altNameType == null) {
+          continue;
+        }
+        if (altNameType == type) {
+          String altName = (String) entry.get(1);
+          if (altName != null) {
+            result.add(altName);
+          }
+        }
+      }
+      return result;
+    } catch (CertificateParsingException e) {
+      return Collections.emptyList();
+    }
+  }
+
+  /**
+   * Returns {@code true} iff {@code hostName} matches the domain name {@code pattern}.
+   *
+   * @param hostName lower-case host name.
+   * @param pattern domain name pattern from certificate. May be a wildcard pattern such as
+   *        {@code *.android.com}.
+   */
+  private boolean verifyHostName(String hostName, String pattern) {
+    // Basic sanity checks
+    // Check length == 0 instead of .isEmpty() to support Java 5.
+    if ((hostName == null) || (hostName.length() == 0) || (hostName.startsWith("."))
+        || (hostName.endsWith(".."))) {
+      // Invalid domain name
+      return false;
+    }
+    if ((pattern == null) || (pattern.length() == 0) || (pattern.startsWith("."))
+        || (pattern.endsWith(".."))) {
+      // Invalid pattern/domain name
+      return false;
+    }
+
+    // Normalize hostName and pattern by turning them into absolute domain names if they are not
+    // yet absolute. This is needed because server certificates do not normally contain absolute
+    // names or patterns, but they should be treated as absolute. At the same time, any hostName
+    // presented to this method should also be treated as absolute for the purposes of matching
+    // to the server certificate.
+    //   www.android.com  matches www.android.com
+    //   www.android.com  matches www.android.com.
+    //   www.android.com. matches www.android.com.
+    //   www.android.com. matches www.android.com
+    if (!hostName.endsWith(".")) {
+      hostName += '.';
+    }
+    if (!pattern.endsWith(".")) {
+      pattern += '.';
+    }
+    // hostName and pattern are now absolute domain names.
+
+    pattern = pattern.toLowerCase(Locale.US);
+    // hostName and pattern are now in lower case -- domain names are case-insensitive.
+
+    if (!pattern.contains("*")) {
+      // Not a wildcard pattern -- hostName and pattern must match exactly.
+      return hostName.equals(pattern);
+    }
+    // Wildcard pattern
+
+    // WILDCARD PATTERN RULES:
+    // 1. Asterisk (*) is only permitted in the left-most domain name label and must be the
+    //    only character in that label (i.e., must match the whole left-most label).
+    //    For example, *.example.com is permitted, while *a.example.com, a*.example.com,
+    //    a*b.example.com, a.*.example.com are not permitted.
+    // 2. Asterisk (*) cannot match across domain name labels.
+    //    For example, *.example.com matches test.example.com but does not match
+    //    sub.test.example.com.
+    // 3. Wildcard patterns for single-label domain names are not permitted.
+
+    if ((!pattern.startsWith("*.")) || (pattern.indexOf('*', 1) != -1)) {
+      // Asterisk (*) is only permitted in the left-most domain name label and must be the only
+      // character in that label
+      return false;
+    }
+
+    // Optimization: check whether hostName is too short to match the pattern. hostName must be at
+    // least as long as the pattern because asterisk must match the whole left-most label and
+    // hostName starts with a non-empty label. Thus, asterisk has to match one or more characters.
+    if (hostName.length() < pattern.length()) {
+      // hostName too short to match the pattern.
+      return false;
+    }
+
+    if ("*.".equals(pattern)) {
+      // Wildcard pattern for single-label domain name -- not permitted.
+      return false;
+    }
+
+    // hostName must end with the region of pattern following the asterisk.
+    String suffix = pattern.substring(1);
+    if (!hostName.endsWith(suffix)) {
+      // hostName does not end with the suffix
+      return false;
+    }
+
+    // Check that asterisk did not match across domain name labels.
+    int suffixStartIndexInHostName = hostName.length() - suffix.length();
+    if ((suffixStartIndexInHostName > 0)
+        && (hostName.lastIndexOf('.', suffixStartIndexInHostName - 1) != -1)) {
+      // Asterisk is matching across domain name labels -- not permitted.
+      return false;
+    }
+
+    // hostName matches pattern
+    return true;
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/OptionalMethod.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/OptionalMethod.java
new file mode 100644
index 0000000..a93c838
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/OptionalMethod.java
@@ -0,0 +1,188 @@
+/*
+ *  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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Duck-typing for methods: Represents a method that may or may not be present on an object.
+ *
+ * @param <T> the type of the object the method might be on, typically an interface or base class
+ */
+public class OptionalMethod<T> {
+
+  /** The return type of the method. null means "don't care". */
+  private final Class<?> returnType;
+
+  private final String methodName;
+
+  @SuppressWarnings("rawtypes")
+  private final Class[] methodParams;
+
+  /**
+   * Creates an optional method.
+   *
+   * @param returnType the return type to required, null if it does not matter
+   * @param methodName the name of the method
+   * @param methodParams the method parameter types
+   */
+  @SuppressWarnings("rawtypes")
+  public OptionalMethod(Class<?> returnType, String methodName, Class... methodParams) {
+    this.returnType = returnType;
+    this.methodName = methodName;
+    this.methodParams = methodParams;
+  }
+
+  /**
+   * Returns true if the method exists on the supplied {@code target}.
+   */
+  public boolean isSupported(T target) {
+    return getMethod(target.getClass()) != null;
+  }
+
+  /**
+   * Invokes the method on {@code target} with {@code args}. If the method does not exist or is not
+   * public then {@code null} is returned. See also
+   * {@link #invokeOptionalWithoutCheckedException(Object, Object...)}.
+   *
+   * @throws IllegalArgumentException if the arguments are invalid
+   * @throws InvocationTargetException if the invocation throws an exception
+   */
+  public Object invokeOptional(T target, Object... args) throws InvocationTargetException {
+    Method m = getMethod(target.getClass());
+    if (m == null) {
+      return null;
+    }
+    try {
+      return m.invoke(target, args);
+    } catch (IllegalAccessException e) {
+      return null;
+    }
+  }
+
+  /**
+   * Invokes the method on {@code target}.  If the method does not exist or is not
+   * public then {@code null} is returned. Any RuntimeException thrown by the method is thrown,
+   * checked exceptions are wrapped in an {@link AssertionError}.
+   *
+   * @throws IllegalArgumentException if the arguments are invalid
+   */
+  public Object invokeOptionalWithoutCheckedException(T target, Object... args) {
+    try {
+      return invokeOptional(target, args);
+    } catch (InvocationTargetException e) {
+      Throwable targetException = e.getTargetException();
+      if (targetException instanceof RuntimeException) {
+        throw (RuntimeException) targetException;
+      }
+      AssertionError error = new AssertionError("Unexpected exception");
+      error.initCause(targetException);
+      throw error;
+    }
+  }
+
+  /**
+   * Invokes the method on {@code target} with {@code args}. Throws an error if the method is not
+   * supported. See also {@link #invokeWithoutCheckedException(Object, Object...)}.
+   *
+   * @throws IllegalArgumentException if the arguments are invalid
+   * @throws InvocationTargetException if the invocation throws an exception
+   */
+  public Object invoke(T target, Object... args) throws InvocationTargetException {
+    Method m = getMethod(target.getClass());
+    if (m == null) {
+      throw new AssertionError("Method " + methodName + " not supported for object " + target);
+    }
+    try {
+      return m.invoke(target, args);
+    } catch (IllegalAccessException e) {
+      // Method should be public: we checked.
+      AssertionError error = new AssertionError("Unexpectedly could not call: " + m);
+      error.initCause(e);
+      throw error;
+    }
+  }
+
+  /**
+   * Invokes the method on {@code target}. Throws an error if the method is not supported. Any
+   * RuntimeException thrown by the method is thrown, checked exceptions are wrapped in
+   * an {@link AssertionError}.
+   *
+   * @throws IllegalArgumentException if the arguments are invalid
+   */
+  public Object invokeWithoutCheckedException(T target, Object... args) {
+    try {
+      return invoke(target, args);
+    } catch (InvocationTargetException e) {
+      Throwable targetException = e.getTargetException();
+      if (targetException instanceof RuntimeException) {
+        throw (RuntimeException) targetException;
+      }
+      AssertionError error = new AssertionError("Unexpected exception");
+      error.initCause(targetException);
+      throw error;
+    }
+  }
+
+  /**
+   * Perform a lookup for the method. No caching.
+   * In order to return a method the method name and arguments must match those specified when
+   * the {@link OptionalMethod} was created. If the return type is specified (i.e. non-null) it
+   * must also be compatible. The method must also be public.
+   */
+  private Method getMethod(Class<?> clazz) {
+    Method method = null;
+    if (methodName != null) {
+      method = getPublicMethod(clazz, methodName, methodParams);
+      if (method != null
+          && returnType != null
+          && !returnType.isAssignableFrom(method.getReturnType())) {
+
+        // If the return type is non-null it must be compatible.
+        method = null;
+      }
+    }
+    return method;
+  }
+
+  @SuppressWarnings("rawtypes")
+  private static Method getPublicMethod(Class<?> clazz, String methodName, Class[] parameterTypes) {
+    Method method = null;
+    try {
+      if (clazz == null) {
+        return null;
+      }
+      if ((clazz.getModifiers() & Modifier.PUBLIC) == 0) {
+        return getPublicMethod(clazz.getSuperclass(), methodName, parameterTypes);
+      }
+      method = clazz.getMethod(methodName, parameterTypes);
+      if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
+        method = null;
+      }
+    } catch (NoSuchMethodException e) {
+      // None.
+    }
+    return method;
+  }
+}
+
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java
new file mode 100644
index 0000000..51244c9
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Platform.java
@@ -0,0 +1,616 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal;
+
+import io.grpc.internal.GrpcUtil;
+import java.io.IOException;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketException;
+import java.security.AccessController;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.Provider;
+import java.security.Security;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import javax.net.ssl.SSLSocket;
+import okio.Buffer;
+
+/**
+ * Access to platform-specific features.
+ *
+ * <h3>Server name indication (SNI)</h3>
+ *
+ * Supported on Android 2.3+.
+ *
+ * <h3>Session Tickets</h3>
+ *
+ * Supported on Android 2.3+.
+ *
+ * <h3>Android Traffic Stats (Socket Tagging)</h3>
+ *
+ * Supported on Android 4.0+.
+ *
+ * <h3>ALPN (Application Layer Protocol Negotiation)</h3>
+ *
+ * Supported on Android 5.0+. The APIs were present in Android 4.4, but that implementation was
+ * unstable.
+ *
+ * <p>Supported on OpenJDK 9+.
+ *
+ * <p>Supported on OpenJDK 7 and 8 (via the JettyALPN-boot library).
+ */
+public class Platform {
+  public static final Logger logger = Logger.getLogger(Platform.class.getName());
+
+  public enum TlsExtensionType {
+    ALPN_AND_NPN,
+    NPN,
+    NONE,
+  }
+
+  /**
+   * List of recognized security providers. The first recognized security provider according to the
+   * preference order returned by {@link Security#getProviders} will be selected.
+   */
+  private static final String[] ANDROID_SECURITY_PROVIDERS =
+      new String[] {
+        // See https://developer.android.com/training/articles/security-gms-provider.html
+        "com.google.android.gms.org.conscrypt.OpenSSLProvider",
+        "org.conscrypt.OpenSSLProvider",
+        "com.android.org.conscrypt.OpenSSLProvider",
+        "org.apache.harmony.xnet.provider.jsse.OpenSSLProvider"
+      };
+
+  private static final Platform PLATFORM = findPlatform();
+
+  public static Platform get() {
+    return PLATFORM;
+  }
+
+  private final Provider sslProvider;
+
+  public Platform(Provider sslProvider) {
+    this.sslProvider = sslProvider;
+  }
+
+  /** Prefix used on custom headers. */
+  public String getPrefix() {
+    return "OkHttp";
+  }
+
+  public void logW(String warning) {
+    System.out.println(warning);
+  }
+
+  public void tagSocket(Socket socket) throws SocketException {
+  }
+
+  public void untagSocket(Socket socket) throws SocketException {
+  }
+
+  public Provider getProvider() {
+    return sslProvider;
+  }
+
+  /** Returns the TLS extension type available (ALPN and NPN, NPN, or None). */
+  public TlsExtensionType getTlsExtensionType() {
+    return TlsExtensionType.NONE;
+  }
+
+  /**
+   * Configure TLS extensions on {@code sslSocket} for {@code route}.
+   *
+   * @param hostname non-null for client-side handshakes; null for
+   *     server-side handshakes.
+   */
+  public void configureTlsExtensions(SSLSocket sslSocket, String hostname,
+      List<Protocol> protocols) {
+  }
+
+  /**
+   * Called after the TLS handshake to release resources allocated by {@link
+   * #configureTlsExtensions}.
+   */
+  public void afterHandshake(SSLSocket sslSocket) {
+  }
+
+  /** Returns the negotiated protocol, or null if no protocol was negotiated. */
+  public String getSelectedProtocol(SSLSocket socket) {
+    return null;
+  }
+
+  public void connectSocket(Socket socket, InetSocketAddress address,
+      int connectTimeout) throws IOException {
+    socket.connect(address, connectTimeout);
+  }
+
+  /** Attempt to match the host runtime to a capable Platform implementation. */
+  private static Platform findPlatform() {
+    Provider androidOrAppEngineProvider =
+        GrpcUtil.IS_RESTRICTED_APPENGINE ? getAppEngineProvider() : getAndroidSecurityProvider();
+    if (androidOrAppEngineProvider != null) {
+      // Attempt to find Android 2.3+ APIs.
+      OptionalMethod<Socket> setUseSessionTickets
+          = new OptionalMethod<Socket>(null, "setUseSessionTickets", boolean.class);
+      OptionalMethod<Socket> setHostname
+          = new OptionalMethod<Socket>(null, "setHostname", String.class);
+      Method trafficStatsTagSocket = null;
+      Method trafficStatsUntagSocket = null;
+      OptionalMethod<Socket> getAlpnSelectedProtocol =
+          new OptionalMethod<Socket>(byte[].class, "getAlpnSelectedProtocol");
+      OptionalMethod<Socket> setAlpnProtocols =
+          new OptionalMethod<Socket>(null, "setAlpnProtocols", byte[].class);
+
+      // Attempt to find Android 4.0+ APIs.
+      try {
+        Class<?> trafficStats = Class.forName("android.net.TrafficStats");
+        trafficStatsTagSocket = trafficStats.getMethod("tagSocket", Socket.class);
+        trafficStatsUntagSocket = trafficStats.getMethod("untagSocket", Socket.class);
+      } catch (ClassNotFoundException ignored) {
+      } catch (NoSuchMethodException ignored) {
+      }
+
+      TlsExtensionType tlsExtensionType;
+      if (GrpcUtil.IS_RESTRICTED_APPENGINE) {
+        tlsExtensionType = TlsExtensionType.ALPN_AND_NPN;
+      } else if (androidOrAppEngineProvider.getName().equals("GmsCore_OpenSSL")
+          || androidOrAppEngineProvider.getName().equals("Conscrypt")) {
+        tlsExtensionType = TlsExtensionType.ALPN_AND_NPN;
+      } else if (isAtLeastAndroid5()) {
+        tlsExtensionType = TlsExtensionType.ALPN_AND_NPN;
+      } else if (isAtLeastAndroid41()) {
+        tlsExtensionType = TlsExtensionType.NPN;
+      } else {
+        tlsExtensionType = TlsExtensionType.NONE;
+      }
+      return new Android(
+          setUseSessionTickets,
+          setHostname,
+          trafficStatsTagSocket,
+          trafficStatsUntagSocket,
+          getAlpnSelectedProtocol,
+          setAlpnProtocols,
+          androidOrAppEngineProvider,
+          tlsExtensionType);
+    }
+    Provider sslProvider;
+    try {
+      sslProvider = SSLContext.getDefault().getProvider();
+    } catch (NoSuchAlgorithmException nsae) {
+      throw new RuntimeException(nsae);
+    }
+
+    // Find JDK9+ ALPN support
+    try {
+      // getApplicationProtocol() may throw UnsupportedOperationException, so first construct a
+      // dummy SSLEngine and verify the method does not throw.
+      SSLContext context = SSLContext.getInstance("TLS", sslProvider);
+      context.init(null, null, null);
+      SSLEngine engine = context.createSSLEngine();
+      Method getEngineApplicationProtocol =
+          AccessController.doPrivileged(
+              new PrivilegedExceptionAction<Method>() {
+                @Override
+                public Method run() throws Exception {
+                  return SSLEngine.class.getMethod("getApplicationProtocol");
+                }
+              });
+      getEngineApplicationProtocol.invoke(engine);
+
+      Method setApplicationProtocols =
+          AccessController.doPrivileged(
+              new PrivilegedExceptionAction<Method>() {
+                @Override
+                public Method run() throws Exception {
+                  return SSLParameters.class.getMethod("setApplicationProtocols", String[].class);
+                }
+              });
+      Method getApplicationProtocol =
+          AccessController.doPrivileged(
+              new PrivilegedExceptionAction<Method>() {
+                @Override
+                public Method run() throws Exception {
+                  return SSLSocket.class.getMethod("getApplicationProtocol");
+                }
+              });
+      return new JdkAlpnPlatform(sslProvider, setApplicationProtocols, getApplicationProtocol);
+    } catch (NoSuchAlgorithmException ignored) {
+    } catch (KeyManagementException ignored) {
+    } catch (PrivilegedActionException ignored) {
+    } catch (IllegalAccessException ignored) {
+    } catch (InvocationTargetException ignored) {
+    }
+
+    // Find Jetty's ALPN extension for OpenJDK.
+    try {
+      String negoClassName = "org.eclipse.jetty.alpn.ALPN";
+      Class<?> negoClass = Class.forName(negoClassName);
+      Class<?> providerClass = Class.forName(negoClassName + "$Provider");
+      Class<?> clientProviderClass = Class.forName(negoClassName + "$ClientProvider");
+      Class<?> serverProviderClass = Class.forName(negoClassName + "$ServerProvider");
+      Method putMethod = negoClass.getMethod("put", SSLSocket.class, providerClass);
+      Method getMethod = negoClass.getMethod("get", SSLSocket.class);
+      Method removeMethod = negoClass.getMethod("remove", SSLSocket.class);
+      return new JdkWithJettyBootPlatform(
+          putMethod, getMethod, removeMethod, clientProviderClass, serverProviderClass,
+          sslProvider);
+    } catch (ClassNotFoundException ignored) {
+    } catch (NoSuchMethodException ignored) {
+    }
+
+    // TODO(ericgribkoff) Return null here
+    return new Platform(sslProvider);
+  }
+
+  private static boolean isAtLeastAndroid5() {
+    try {
+      Platform.class
+          .getClassLoader()
+          .loadClass("android.net.Network"); // Arbitrary class added in Android 5.0.
+      return true;
+    } catch (ClassNotFoundException e) {
+      logger.log(Level.FINE, "Can't find class", e);
+    }
+    return false;
+  }
+
+  private static boolean isAtLeastAndroid41() {
+    try {
+      Platform.class
+          .getClassLoader()
+          .loadClass("android.app.ActivityOptions"); // Arbitrary class added in Android 4.1.
+      return true;
+    } catch (ClassNotFoundException e) {
+      logger.log(Level.FINE, "Can't find class", e);
+    }
+    return false;
+  }
+
+  /**
+   * Forcibly load the conscrypt security provider on AppEngine if it's available. If not fail.
+   */
+  private static Provider getAppEngineProvider() {
+    try {
+      // Forcibly load conscrypt as it is unlikely to be an installed provider on AppEngine
+      return (Provider) Class.forName("org.conscrypt.OpenSSLProvider")
+          .getConstructor().newInstance();
+    } catch (Throwable t) {
+      throw new RuntimeException("Unable to load conscrypt security provider", t);
+    }
+  }
+
+  /**
+   * Select the first recognized security provider according to the preference order returned by
+   * {@link Security#getProviders}. If a recognized provider is not found then warn but continue.
+   */
+  private static Provider getAndroidSecurityProvider() {
+    Provider[] providers = Security.getProviders();
+    for (Provider availableProvider : providers) {
+      for (String providerClassName : ANDROID_SECURITY_PROVIDERS) {
+        if (providerClassName.equals(availableProvider.getClass().getName())) {
+          logger.log(Level.FINE, "Found registered provider {0}", providerClassName);
+          return availableProvider;
+        }
+      }
+    }
+    logger.log(Level.WARNING, "Unable to find Conscrypt");
+    return null;
+  }
+
+  /** Android 2.3 or better, or AppEngine with Conscrypt. */
+  private static class Android extends Platform {
+
+    private final OptionalMethod<Socket> setUseSessionTickets;
+    private final OptionalMethod<Socket> setHostname;
+
+    // Non-null on Android 4.0+.
+    private final Method trafficStatsTagSocket;
+    private final Method trafficStatsUntagSocket;
+
+    // Non-null on Android 5.0+.
+    private final OptionalMethod<Socket> getAlpnSelectedProtocol;
+    private final OptionalMethod<Socket> setAlpnProtocols;
+
+    private final TlsExtensionType tlsExtensionType;
+
+    public Android(
+        OptionalMethod<Socket> setUseSessionTickets,
+        OptionalMethod<Socket> setHostname,
+        Method trafficStatsTagSocket,
+        Method trafficStatsUntagSocket,
+        OptionalMethod<Socket> getAlpnSelectedProtocol,
+        OptionalMethod<Socket> setAlpnProtocols,
+        Provider provider,
+        TlsExtensionType tlsExtensionType) {
+      super(provider);
+      this.setUseSessionTickets = setUseSessionTickets;
+      this.setHostname = setHostname;
+      this.trafficStatsTagSocket = trafficStatsTagSocket;
+      this.trafficStatsUntagSocket = trafficStatsUntagSocket;
+      this.getAlpnSelectedProtocol = getAlpnSelectedProtocol;
+      this.setAlpnProtocols = setAlpnProtocols;
+      this.tlsExtensionType = tlsExtensionType;
+    }
+
+    @Override
+    public TlsExtensionType getTlsExtensionType() {
+      return tlsExtensionType;
+    }
+
+    @Override public void connectSocket(Socket socket, InetSocketAddress address,
+        int connectTimeout) throws IOException {
+      try {
+        socket.connect(address, connectTimeout);
+      } catch (SecurityException se) {
+        // Before android 4.3, socket.connect could throw a SecurityException
+        // if opening a socket resulted in an EACCES error.
+        IOException ioException = new IOException("Exception in connect");
+        ioException.initCause(se);
+        throw ioException;
+      }
+    }
+
+    @Override public void configureTlsExtensions(
+        SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
+      // Enable SNI and session tickets.
+      if (hostname != null) {
+        setUseSessionTickets.invokeOptionalWithoutCheckedException(sslSocket, true);
+        setHostname.invokeOptionalWithoutCheckedException(sslSocket, hostname);
+      }
+
+      // Enable ALPN.
+      if (setAlpnProtocols.isSupported(sslSocket)) {
+        Object[] parameters = { concatLengthPrefixed(protocols) };
+        setAlpnProtocols.invokeWithoutCheckedException(sslSocket, parameters);
+      }
+    }
+
+    @Override public String getSelectedProtocol(SSLSocket socket) {
+      if (!getAlpnSelectedProtocol.isSupported(socket)) return null;
+
+      byte[] alpnResult = (byte[]) getAlpnSelectedProtocol.invokeWithoutCheckedException(socket);
+      return alpnResult != null ? new String(alpnResult, Util.UTF_8) : null;
+    }
+
+    @Override public void tagSocket(Socket socket) throws SocketException {
+      if (trafficStatsTagSocket == null) return;
+
+      try {
+        trafficStatsTagSocket.invoke(null, socket);
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(e);
+      } catch (InvocationTargetException e) {
+        throw new RuntimeException(e.getCause());
+      }
+    }
+
+    @Override public void untagSocket(Socket socket) throws SocketException {
+      if (trafficStatsUntagSocket == null) return;
+
+      try {
+        trafficStatsUntagSocket.invoke(null, socket);
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(e);
+      } catch (InvocationTargetException e) {
+        throw new RuntimeException(e.getCause());
+      }
+    }
+  }
+
+  /** OpenJDK 9+. */
+  private static class JdkAlpnPlatform extends Platform {
+    private final Method setApplicationProtocols;
+    private final Method getApplicationProtocol;
+
+    private JdkAlpnPlatform(
+        Provider provider, Method setApplicationProtocols, Method getApplicationProtocol) {
+      super(provider);
+      this.setApplicationProtocols = setApplicationProtocols;
+      this.getApplicationProtocol = getApplicationProtocol;
+    }
+
+    @Override
+    public TlsExtensionType getTlsExtensionType() {
+      return TlsExtensionType.ALPN_AND_NPN;
+    }
+
+    @Override
+    public void configureTlsExtensions(
+        SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
+      SSLParameters parameters = sslSocket.getSSLParameters();
+      List<String> names = new ArrayList<>(protocols.size());
+      for (Protocol protocol : protocols) {
+        if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN.
+        names.add(protocol.toString());
+      }
+      try {
+        setApplicationProtocols.invoke(
+            parameters, new Object[] {names.toArray(new String[names.size()])});
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(e);
+      } catch (InvocationTargetException e) {
+        throw new RuntimeException(e);
+      }
+      sslSocket.setSSLParameters(parameters);
+    }
+
+    /** Returns the negotiated protocol, or null if no protocol was negotiated. */
+    @Override
+    public String getSelectedProtocol(SSLSocket socket) {
+      try {
+        return (String) getApplicationProtocol.invoke(socket);
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(e);
+      } catch (InvocationTargetException e) {
+        throw new RuntimeException(e);
+      }
+    }
+  }
+
+  /**
+   * OpenJDK 7+ with {@code org.mortbay.jetty.alpn/alpn-boot} in the boot class path.
+   */
+  private static class JdkWithJettyBootPlatform extends Platform {
+    private final Method putMethod;
+    private final Method getMethod;
+    private final Method removeMethod;
+    private final Class<?> clientProviderClass;
+    private final Class<?> serverProviderClass;
+
+    public JdkWithJettyBootPlatform(Method putMethod, Method getMethod, Method removeMethod,
+        Class<?> clientProviderClass, Class<?> serverProviderClass, Provider provider) {
+      super(provider);
+      this.putMethod = putMethod;
+      this.getMethod = getMethod;
+      this.removeMethod = removeMethod;
+      this.clientProviderClass = clientProviderClass;
+      this.serverProviderClass = serverProviderClass;
+    }
+
+    @Override
+    public TlsExtensionType getTlsExtensionType() {
+      return TlsExtensionType.ALPN_AND_NPN;
+    }
+
+    @Override public void configureTlsExtensions(
+        SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
+      List<String> names = new ArrayList<>(protocols.size());
+      for (int i = 0, size = protocols.size(); i < size; i++) {
+        Protocol protocol = protocols.get(i);
+        if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN.
+        names.add(protocol.toString());
+      }
+      try {
+        Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(),
+            new Class<?>[] { clientProviderClass, serverProviderClass }, new JettyNegoProvider(names));
+        putMethod.invoke(null, sslSocket, provider);
+      } catch (InvocationTargetException e) {
+        throw new AssertionError(e);
+      } catch (IllegalAccessException e) {
+        throw new AssertionError(e);
+      }
+    }
+
+    @Override public void afterHandshake(SSLSocket sslSocket) {
+      try {
+        removeMethod.invoke(null, sslSocket);
+      } catch (IllegalAccessException ignored) {
+        throw new AssertionError();
+      } catch (InvocationTargetException ignored) {
+      }
+    }
+
+    @Override public String getSelectedProtocol(SSLSocket socket) {
+      try {
+        JettyNegoProvider provider =
+            (JettyNegoProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket));
+        if (!provider.unsupported && provider.selected == null) {
+          logger.log(Level.INFO, "ALPN callback dropped: SPDY and HTTP/2 are disabled. "
+              + "Is alpn-boot on the boot class path?");
+          return null;
+        }
+        return provider.unsupported ? null : provider.selected;
+      } catch (InvocationTargetException e) {
+        throw new AssertionError();
+      } catch (IllegalAccessException e) {
+        throw new AssertionError();
+      }
+    }
+  }
+
+  /**
+   * Handle the methods of ALPN's ClientProvider and ServerProvider
+   * without a compile-time dependency on those interfaces.
+   */
+  private static class JettyNegoProvider implements InvocationHandler {
+    /** This peer's supported protocols. */
+    private final List<String> protocols;
+    /** Set when remote peer notifies ALPN is unsupported. */
+    private boolean unsupported;
+    /** The protocol the server selected. */
+    private String selected;
+
+    public JettyNegoProvider(List<String> protocols) {
+      this.protocols = protocols;
+    }
+
+    @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+      String methodName = method.getName();
+      Class<?> returnType = method.getReturnType();
+      if (args == null) {
+        args = Util.EMPTY_STRING_ARRAY;
+      }
+      if (methodName.equals("supports") && boolean.class == returnType) {
+        return true; // ALPN is supported.
+      } else if (methodName.equals("unsupported") && void.class == returnType) {
+        this.unsupported = true; // Peer doesn't support ALPN.
+        return null;
+      } else if (methodName.equals("protocols") && args.length == 0) {
+        return protocols; // Client advertises these protocols.
+      } else if ((methodName.equals("selectProtocol") || methodName.equals("select"))
+          && String.class == returnType && args.length == 1 && args[0] instanceof List) {
+        @SuppressWarnings("unchecked")
+        List<String> peerProtocols = (List) args[0];
+        // Pick the first known protocol the peer advertises.
+        for (int i = 0, size = peerProtocols.size(); i < size; i++) {
+          if (protocols.contains(peerProtocols.get(i))) {
+            return selected = peerProtocols.get(i);
+          }
+        }
+        return selected = protocols.get(0); // On no intersection, try peer's first protocol.
+      } else if ((methodName.equals("protocolSelected") || methodName.equals("selected"))
+          && args.length == 1) {
+        this.selected = (String) args[0]; // Server selected this protocol.
+        return null;
+      } else {
+        return method.invoke(this, args);
+      }
+    }
+  }
+
+  /**
+   * Returns the concatenation of 8-bit, length prefixed protocol names.
+   * http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
+   */
+  public static byte[] concatLengthPrefixed(List<Protocol> protocols) {
+    Buffer result = new Buffer();
+    for (int i = 0, size = protocols.size(); i < size; i++) {
+      Protocol protocol = protocols.get(i);
+      if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for ALPN.
+      result.writeByte(protocol.toString().length());
+      result.writeUtf8(protocol.toString());
+    }
+    return result.readByteArray();
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Protocol.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Protocol.java
new file mode 100644
index 0000000..7b5a3ae
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Protocol.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal;
+
+import java.io.IOException;
+
+/**
+ * Protocols that OkHttp implements for <a
+ * href="http://tools.ietf.org/html/draft-ietf-tls-applayerprotoneg">ALPN</a>
+ * selection.
+ *
+ * <h3>Protocol vs Scheme</h3>
+ * Despite its name, {@link java.net.URL#getProtocol()} returns the
+ * {@linkplain java.net.URI#getScheme() scheme} (http, https, etc.) of the URL, not
+ * the protocol (http/1.1, spdy/3.1, etc.). OkHttp uses the word <i>protocol</i>
+ * to identify how HTTP messages are framed.
+ */
+public enum Protocol {
+  /**
+   * An obsolete plaintext framing that does not use persistent sockets by
+   * default.
+   */
+  HTTP_1_0("http/1.0"),
+
+  /**
+   * A plaintext framing that includes persistent connections.
+   *
+   * <p>This version of OkHttp implements <a
+   * href="http://www.ietf.org/rfc/rfc2616.txt">RFC 2616</a>, and tracks
+   * revisions to that spec.
+   */
+  HTTP_1_1("http/1.1"),
+
+  /**
+   * Chromium's binary-framed protocol that includes header compression,
+   * multiplexing multiple requests on the same socket, and server-push.
+   * HTTP/1.1 semantics are layered on SPDY/3.
+   *
+   * <p>This version of OkHttp implements SPDY 3 <a
+   * href="http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1">draft
+   * 3.1</a>. Future releases of OkHttp may use this identifier for a newer draft
+   * of the SPDY spec.
+   */
+  SPDY_3("spdy/3.1"),
+
+  /**
+   * The IETF's binary-framed protocol that includes header compression,
+   * multiplexing multiple requests on the same socket, and server-push.
+   * HTTP/1.1 semantics are layered on HTTP/2.
+   *
+   * <p>HTTP/2 requires deployments of HTTP/2 that use TLS 1.2 support
+   * {@linkplain CipherSuite#TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}
+   * , present in Java 8+ and Android 5+. Servers that enforce this may send an
+   * exception message including the string {@code INADEQUATE_SECURITY}.
+   */
+  HTTP_2("h2"),
+
+  /**
+   * The experimental "grpc-exp" string identifies gRPC (and by implication
+   * HTTP/2) when used over TLS. This indicates to the server that the client
+   * will only send gRPC traffic on the h2 connection and is negotiated in
+   * preference to h2 when the client and server support it, but is not
+   * standardized. Support for this may be removed at any time.
+   */
+  GRPC_EXP("grpc-exp");
+
+  private final String protocol;
+
+  Protocol(String protocol) {
+    this.protocol = protocol;
+  }
+
+  /**
+   * Returns the protocol identified by {@code protocol}.
+   * @throws IOException if {@code protocol} is unknown.
+   */
+  public static Protocol get(String protocol) throws IOException {
+    // Unroll the loop over values() to save an allocation.
+    if (protocol.equals(HTTP_1_0.protocol)) return HTTP_1_0;
+    if (protocol.equals(HTTP_1_1.protocol)) return HTTP_1_1;
+    if (protocol.equals(HTTP_2.protocol)) return HTTP_2;
+    if (protocol.equals(GRPC_EXP.protocol)) return GRPC_EXP;
+    if (protocol.equals(SPDY_3.protocol)) return SPDY_3;
+    throw new IOException("Unexpected protocol: " + protocol);
+  }
+
+  /**
+   * Returns the string used to identify this protocol for ALPN, like
+   * "http/1.1", "spdy/3.1" or "h2".
+   */
+  @Override public String toString() {
+    return protocol;
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/TlsVersion.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/TlsVersion.java
new file mode 100644
index 0000000..548f4ac
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/TlsVersion.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 Square, Inc.
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal;
+
+import javax.net.ssl.SSLSocket;
+
+/**
+ * Versions of TLS that can be offered when negotiating a secure socket. See
+ * {@link SSLSocket#setEnabledProtocols}.
+ */
+public enum TlsVersion {
+  TLS_1_2("TLSv1.2"), // 2008.
+  TLS_1_1("TLSv1.1"), // 2006.
+  TLS_1_0("TLSv1"),   // 1999.
+  SSL_3_0("SSLv3"),   // 1996.
+  ;
+
+  final String javaName;
+
+  private TlsVersion(String javaName) {
+    this.javaName = javaName;
+  }
+
+  public static TlsVersion forJavaName(String javaName) {
+    if ("TLSv1.2".equals(javaName)) {
+      return TLS_1_2;
+    } else if ("TLSv1.1".equals(javaName)) {
+      return TLS_1_1;
+    } else if ("TLSv1".equals(javaName)) {
+      return TLS_1_0;
+    } else  if ("SSLv3".equals(javaName)) {
+      return SSL_3_0;
+    }
+    throw new IllegalArgumentException("Unexpected TLS version: " + javaName);
+  }
+
+  public String javaName() {
+    return javaName;
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java
new file mode 100644
index 0000000..a0e668f
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/Util.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Array;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import okio.Buffer;
+import okio.ByteString;
+import okio.Source;
+
+/** Junk drawer of utility methods. */
+public final class Util {
+  public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+  public static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+  /** A cheap and type-safe constant for the UTF-8 Charset. */
+  public static final Charset UTF_8 = Charset.forName("UTF-8");
+
+  private Util() {
+  }
+
+  public static void checkOffsetAndCount(long arrayLength, long offset, long count) {
+    if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
+      throw new ArrayIndexOutOfBoundsException();
+    }
+  }
+
+  /** Returns true if two possibly-null objects are equal. */
+  public static boolean equal(Object a, Object b) {
+    return a == b || (a != null && a.equals(b));
+  }
+
+  /**
+   * Closes {@code closeable}, ignoring any checked exceptions. Does nothing
+   * if {@code closeable} is null.
+   */
+  public static void closeQuietly(Closeable closeable) {
+    if (closeable != null) {
+      try {
+        closeable.close();
+      } catch (RuntimeException rethrown) {
+        throw rethrown;
+      } catch (Exception ignored) {
+      }
+    }
+  }
+
+  /**
+   * Closes {@code socket}, ignoring any checked exceptions. Does nothing if
+   * {@code socket} is null.
+   */
+  public static void closeQuietly(Socket socket) {
+    if (socket != null) {
+      try {
+        socket.close();
+      } catch (AssertionError e) {
+        if (!isAndroidGetsocknameError(e)) throw e;
+      } catch (RuntimeException rethrown) {
+        throw rethrown;
+      } catch (Exception ignored) {
+      }
+    }
+  }
+
+  /**
+   * Closes {@code serverSocket}, ignoring any checked exceptions. Does nothing if
+   * {@code serverSocket} is null.
+   */
+  public static void closeQuietly(ServerSocket serverSocket) {
+    if (serverSocket != null) {
+      try {
+        serverSocket.close();
+      } catch (RuntimeException rethrown) {
+        throw rethrown;
+      } catch (Exception ignored) {
+      }
+    }
+  }
+
+  /**
+   * Closes {@code a} and {@code b}. If either close fails, this completes
+   * the other close and rethrows the first encountered exception.
+   */
+  public static void closeAll(Closeable a, Closeable b) throws IOException {
+    Throwable thrown = null;
+    try {
+      a.close();
+    } catch (Throwable e) {
+      thrown = e;
+    }
+    try {
+      b.close();
+    } catch (Throwable e) {
+      if (thrown == null) thrown = e;
+    }
+    if (thrown == null) return;
+    if (thrown instanceof IOException) throw (IOException) thrown;
+    if (thrown instanceof RuntimeException) throw (RuntimeException) thrown;
+    if (thrown instanceof Error) throw (Error) thrown;
+    throw new AssertionError(thrown);
+  }
+
+  /**
+   * Attempts to exhaust {@code source}, returning true if successful. This is useful when reading
+   * a complete source is helpful, such as when doing so completes a cache body or frees a socket
+   * connection for reuse.
+   */
+  public static boolean discard(Source source, int timeout, TimeUnit timeUnit) {
+    try {
+      return skipAll(source, timeout, timeUnit);
+    } catch (IOException e) {
+      return false;
+    }
+  }
+
+  /**
+   * Reads until {@code in} is exhausted or the deadline has been reached. This is careful to not
+   * extend the deadline if one exists already.
+   */
+  public static boolean skipAll(Source source, int duration, TimeUnit timeUnit) throws IOException {
+    long now = System.nanoTime();
+    long originalDuration = source.timeout().hasDeadline()
+        ? source.timeout().deadlineNanoTime() - now
+        : Long.MAX_VALUE;
+    source.timeout().deadlineNanoTime(now + Math.min(originalDuration, timeUnit.toNanos(duration)));
+    try {
+      Buffer skipBuffer = new Buffer();
+      while (source.read(skipBuffer, 2048) != -1) {
+        skipBuffer.clear();
+      }
+      return true; // Success! The source has been exhausted.
+    } catch (InterruptedIOException e) {
+      return false; // We ran out of time before exhausting the source.
+    } finally {
+      if (originalDuration == Long.MAX_VALUE) {
+        source.timeout().clearDeadline();
+      } else {
+        source.timeout().deadlineNanoTime(now + originalDuration);
+      }
+    }
+  }
+
+  /** Returns a 32 character string containing an MD5 hash of {@code s}. */
+  public static String md5Hex(String s) {
+    try {
+      MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+      byte[] md5bytes = messageDigest.digest(s.getBytes("UTF-8"));
+      return ByteString.of(md5bytes).hex();
+    } catch (NoSuchAlgorithmException e) {
+      throw new AssertionError(e);
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  /** Returns a Base 64-encoded string containing a SHA-1 hash of {@code s}. */
+  public static String shaBase64(String s) {
+    try {
+      MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
+      byte[] sha1Bytes = messageDigest.digest(s.getBytes("UTF-8"));
+      return ByteString.of(sha1Bytes).base64();
+    } catch (NoSuchAlgorithmException e) {
+      throw new AssertionError(e);
+    } catch (UnsupportedEncodingException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  /** Returns a SHA-1 hash of {@code s}. */
+  public static ByteString sha1(ByteString s) {
+    try {
+      MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
+      byte[] sha1Bytes = messageDigest.digest(s.toByteArray());
+      return ByteString.of(sha1Bytes);
+    } catch (NoSuchAlgorithmException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  /** Returns an immutable copy of {@code list}. */
+  public static <T> List<T> immutableList(List<T> list) {
+    return Collections.unmodifiableList(new ArrayList<>(list));
+  }
+
+  /** Returns an immutable list containing {@code elements}. */
+  public static <T> List<T> immutableList(T[] elements) {
+    return Collections.unmodifiableList(Arrays.asList(elements.clone()));
+  }
+
+  /** Returns an immutable copy of {@code map}. */
+  public static <K, V> Map<K, V> immutableMap(Map<K, V> map) {
+    return Collections.unmodifiableMap(new LinkedHashMap<K, V>(map));
+  }
+
+  public static ThreadFactory threadFactory(final String name, final boolean daemon) {
+    return new ThreadFactory() {
+      @Override public Thread newThread(Runnable runnable) {
+        Thread result = new Thread(runnable, name);
+        result.setDaemon(daemon);
+        return result;
+      }
+    };
+  }
+
+  /**
+   * Returns an array containing containing only elements found in {@code first}  and also in
+   * {@code second}. The returned elements are in the same order as in {@code first}.
+   */
+  @SuppressWarnings("unchecked")
+  public static <T> T[] intersect(Class<T> arrayType, T[] first, T[] second) {
+    List<T> result = intersect(first, second);
+    return result.toArray((T[]) Array.newInstance(arrayType, result.size()));
+  }
+
+  /**
+   * Returns a list containing containing only elements found in {@code first}  and also in
+   * {@code second}. The returned elements are in the same order as in {@code first}.
+   */
+  private static <T> List<T> intersect(T[] first, T[] second) {
+    List<T> result = new ArrayList<>();
+    for (T a : first) {
+      for (T b : second) {
+        if (a.equals(b)) {
+          result.add(b);
+          break;
+        }
+      }
+    }
+    return result;
+  }
+
+  /** Returns {@code s} with control characters and non-ASCII characters replaced with '?'. */
+  public static String toHumanReadableAscii(String s) {
+    for (int i = 0, length = s.length(), c; i < length; i += Character.charCount(c)) {
+      c = s.codePointAt(i);
+      if (c > '\u001f' && c < '\u007f') continue;
+
+      Buffer buffer = new Buffer();
+      buffer.writeUtf8(s, 0, i);
+      for (int j = i; j < length; j += Character.charCount(c)) {
+        c = s.codePointAt(j);
+        buffer.writeUtf8CodePoint(c > '\u001f' && c < '\u007f' ? c : '?');
+      }
+      return buffer.readUtf8();
+    }
+    return s;
+  }
+
+  /**
+   * Returns true if {@code e} is due to a firmware bug fixed after Android 4.2.2.
+   * https://code.google.com/p/android/issues/detail?id=54072
+   */
+  public static boolean isAndroidGetsocknameError(AssertionError e) {
+    return e.getCause() != null && e.getMessage() != null
+        && e.getMessage().contains("getsockname failed");
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/ErrorCode.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/ErrorCode.java
new file mode 100644
index 0000000..38a5459
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/ErrorCode.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal.framed;
+
+// http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-7
+public enum ErrorCode {
+  /** Not an error! For SPDY stream resets, prefer null over NO_ERROR. */
+  NO_ERROR(0, -1, 0),
+
+  PROTOCOL_ERROR(1, 1, 1),
+
+  /** A subtype of PROTOCOL_ERROR used by SPDY. */
+  INVALID_STREAM(1, 2, -1),
+
+  /** A subtype of PROTOCOL_ERROR used by SPDY. */
+  UNSUPPORTED_VERSION(1, 4, -1),
+
+  /** A subtype of PROTOCOL_ERROR used by SPDY. */
+  STREAM_IN_USE(1, 8, -1),
+
+  /** A subtype of PROTOCOL_ERROR used by SPDY. */
+  STREAM_ALREADY_CLOSED(1, 9, -1),
+
+  INTERNAL_ERROR(2, 6, 2),
+
+  FLOW_CONTROL_ERROR(3, 7, -1),
+
+  STREAM_CLOSED(5, -1, -1),
+
+  FRAME_TOO_LARGE(6, 11, -1),
+
+  REFUSED_STREAM(7, 3, -1),
+
+  CANCEL(8, 5, -1),
+
+  COMPRESSION_ERROR(9, -1, -1),
+
+  CONNECT_ERROR(10, -1, -1),
+
+  ENHANCE_YOUR_CALM(11, -1, -1),
+
+  INADEQUATE_SECURITY(12, -1, -1),
+
+  HTTP_1_1_REQUIRED(13, -1, -1),
+
+  INVALID_CREDENTIALS(-1, 10, -1);
+
+  public final int httpCode;
+  public final int spdyRstCode;
+  public final int spdyGoAwayCode;
+
+  private ErrorCode(int httpCode, int spdyRstCode, int spdyGoAwayCode) {
+    this.httpCode = httpCode;
+    this.spdyRstCode = spdyRstCode;
+    this.spdyGoAwayCode = spdyGoAwayCode;
+  }
+
+  public static ErrorCode fromSpdy3Rst(int code) {
+    for (ErrorCode errorCode : ErrorCode.values()) {
+      if (errorCode.spdyRstCode == code) return errorCode;
+    }
+    return null;
+  }
+
+  public static ErrorCode fromHttp2(int code) {
+    for (ErrorCode errorCode : ErrorCode.values()) {
+      if (errorCode.httpCode == code) return errorCode;
+    }
+    return null;
+  }
+
+  public static ErrorCode fromSpdyGoAway(int code) {
+    for (ErrorCode errorCode : ErrorCode.values()) {
+      if (errorCode.spdyGoAwayCode == code) return errorCode;
+    }
+    return null;
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java
new file mode 100644
index 0000000..20c50f7
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameReader.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal.framed;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+
+import okio.BufferedSource;
+import okio.ByteString;
+
+/** Reads transport frames for SPDY/3 or HTTP/2. */
+public interface FrameReader extends Closeable {
+  void readConnectionPreface() throws IOException;
+  boolean nextFrame(Handler handler) throws IOException;
+
+  interface Handler {
+    void data(boolean inFinished, int streamId, BufferedSource source, int length)
+        throws IOException;
+
+    /**
+     * Create or update incoming headers, creating the corresponding streams
+     * if necessary. Frames that trigger this are SPDY SYN_STREAM, HEADERS, and
+     * SYN_REPLY, and HTTP/2 HEADERS and PUSH_PROMISE.
+     *
+     * @param outFinished true if the receiver should not send further frames.
+     * @param inFinished true if the sender will not send further frames.
+     * @param streamId the stream owning these headers.
+     * @param associatedStreamId the stream that triggered the sender to create
+     *     this stream.
+     */
+    void headers(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
+        List<io.grpc.okhttp.internal.framed.Header> headerBlock, HeadersMode headersMode);
+    void rstStream(int streamId, io.grpc.okhttp.internal.framed.ErrorCode errorCode);
+    void settings(boolean clearPrevious, io.grpc.okhttp.internal.framed.Settings settings);
+
+    /** HTTP/2 only. */
+    void ackSettings();
+
+    /**
+     *  Read a connection-level ping from the peer.  {@code ack} indicates this
+     *  is a reply.  Payload parameters are different between SPDY/3 and HTTP/2.
+     *  <p>
+     *  In SPDY/3, only the first {@code payload1} parameter is set.  If the
+     *  reader is a client, it is an unsigned even number.  Likewise, a server
+     *  will receive an odd number.
+     *  <p>
+     *  In HTTP/2, both {@code payload1} and {@code payload2} parameters are
+     *  set. The data is opaque binary, and there are no rules on the content.
+     */
+    void ping(boolean ack, int payload1, int payload2);
+
+    /**
+     * The peer tells us to stop creating streams.  It is safe to replay
+     * streams with {@code ID > lastGoodStreamId} on a new connection.  In-
+     * flight streams with {@code ID <= lastGoodStreamId} can only be replayed
+     * on a new connection if they are idempotent.
+     *
+     * @param lastGoodStreamId the last stream ID the peer processed before
+     *     sending this message. If {@code lastGoodStreamId} is zero, the peer
+     *     processed no frames.
+     * @param errorCode reason for closing the connection.
+     * @param debugData only valid for HTTP/2; opaque debug data to send.
+     */
+    void goAway(int lastGoodStreamId, io.grpc.okhttp.internal.framed.ErrorCode errorCode, ByteString debugData);
+
+    /**
+     * Notifies that an additional {@code windowSizeIncrement} bytes can be
+     * sent on {@code streamId}, or the connection if {@code streamId} is zero.
+     */
+    void windowUpdate(int streamId, long windowSizeIncrement);
+
+    /**
+     * Called when reading a headers or priority frame. This may be used to
+     * change the stream's weight from the default (16) to a new value.
+     *
+     * @param streamId stream which has a priority change.
+     * @param streamDependency the stream ID this stream is dependent on.
+     * @param weight relative proportion of priority in [1..256].
+     * @param exclusive inserts this stream ID as the sole child of
+     *     {@code streamDependency}.
+     */
+    void priority(int streamId, int streamDependency, int weight, boolean exclusive);
+
+    /**
+     * HTTP/2 only. Receive a push promise header block.
+     * <p>
+     * A push promise contains all the headers that pertain to a server-initiated
+     * request, and a {@code promisedStreamId} to which response frames will be
+     * delivered. Push promise frames are sent as a part of the response to
+     * {@code streamId}.
+     *
+     * @param streamId client-initiated stream ID.  Must be an odd number.
+     * @param promisedStreamId server-initiated stream ID.  Must be an even
+     * number.
+     * @param requestHeaders minimally includes {@code :method}, {@code :scheme},
+     * {@code :authority}, and (@code :path}.
+     */
+    void pushPromise(int streamId, int promisedStreamId, List<io.grpc.okhttp.internal.framed.Header> requestHeaders)
+        throws IOException;
+
+    /**
+     * HTTP/2 only. Expresses that resources for the connection or a client-
+     * initiated stream are available from a different network location or
+     * protocol configuration.
+     *
+     * <p>See <a href="http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-01">alt-svc</a>
+     *
+     * @param streamId when a client-initiated stream ID (odd number), the
+     *     origin of this alternate service is the origin of the stream. When
+     *     zero, the origin is specified in the {@code origin} parameter.
+     * @param origin when present, the
+     *     <a href="http://tools.ietf.org/html/rfc6454">origin</a> is typically
+     *     represented as a combination of scheme, host and port. When empty,
+     *     the origin is that of the {@code streamId}.
+     * @param protocol an ALPN protocol, such as {@code h2}.
+     * @param host an IP address or hostname.
+     * @param port the IP port associated with the service.
+     * @param maxAge time in seconds that this alternative is considered fresh.
+     */
+    void alternateService(int streamId, String origin, ByteString protocol, String host, int port,
+        long maxAge);
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameWriter.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameWriter.java
new file mode 100644
index 0000000..333e06c
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/FrameWriter.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal.framed;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+
+import okio.Buffer;
+
+/** Writes transport frames for SPDY/3 or HTTP/2. */
+public interface FrameWriter extends Closeable {
+  /** HTTP/2 only. */
+  void connectionPreface() throws IOException;
+  /** Informs the peer that we've applied its latest settings. */
+  void ackSettings(Settings peerSettings) throws IOException;
+
+  /**
+   * HTTP/2 only. Send a push promise header block.
+   * <p>
+   * A push promise contains all the headers that pertain to a server-initiated
+   * request, and a {@code promisedStreamId} to which response frames will be
+   * delivered. Push promise frames are sent as a part of the response to
+   * {@code streamId}.  The {@code promisedStreamId} has a priority of one
+   * greater than {@code streamId}.
+   *
+   * @param streamId client-initiated stream ID.  Must be an odd number.
+   * @param promisedStreamId server-initiated stream ID.  Must be an even
+   * number.
+   * @param requestHeaders minimally includes {@code :method}, {@code :scheme},
+   * {@code :authority}, and (@code :path}.
+   */
+  void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders)
+      throws IOException;
+
+  /** SPDY/3 only. */
+  void flush() throws IOException;
+  void synStream(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
+      List<Header> headerBlock) throws IOException;
+  void synReply(boolean outFinished, int streamId, List<Header> headerBlock)
+      throws IOException;
+  void headers(int streamId, List<Header> headerBlock) throws IOException;
+  void rstStream(int streamId, ErrorCode errorCode) throws IOException;
+
+  /** The maximum size of bytes that may be sent in a single call to {@link #data}. */
+  int maxDataLength();
+
+  /**
+   * {@code source.length} may be longer than the max length of the variant's data frame.
+   * Implementations must send multiple frames as necessary.
+   *
+   * @param source the buffer to draw bytes from. May be null if byteCount is 0.
+   * @param byteCount must be between 0 and the minimum of {code source.length}
+   * and {@link #maxDataLength}.
+   */
+  void data(boolean outFinished, int streamId, Buffer source, int byteCount) throws IOException;
+
+  /** Write okhttp's settings to the peer. */
+  void settings(Settings okHttpSettings) throws IOException;
+
+  /**
+   *  Send a connection-level ping to the peer.  {@code ack} indicates this is
+   *  a reply.  Payload parameters are different between SPDY/3 and HTTP/2.
+   *  <p>
+   *  In SPDY/3, only the first {@code payload1} parameter is sent.  If the
+   *  sender is a client, it is an unsigned odd number.  Likewise, a server
+   *  will send an even number.
+   *  <p>
+   *  In HTTP/2, both {@code payload1} and {@code payload2} parameters are
+   *  sent.  The data is opaque binary, and there are no rules on the content.
+   */
+  void ping(boolean ack, int payload1, int payload2) throws IOException;
+
+  /**
+   * Tell the peer to stop creating streams and that we last processed
+   * {@code lastGoodStreamId}, or zero if no streams were processed.
+   *
+   * @param lastGoodStreamId the last stream ID processed, or zero if no
+   * streams were processed.
+   * @param errorCode reason for closing the connection.
+   * @param debugData only valid for HTTP/2; opaque debug data to send.
+   */
+  void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) throws IOException;
+
+  /**
+   * Inform peer that an additional {@code windowSizeIncrement} bytes can be
+   * sent on {@code streamId}, or the connection if {@code streamId} is zero.
+   */
+  void windowUpdate(int streamId, long windowSizeIncrement) throws IOException;
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Header.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Header.java
new file mode 100644
index 0000000..00081d4
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Header.java
@@ -0,0 +1,60 @@
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal.framed;
+
+import okio.ByteString;
+
+/** HTTP header: the name is an ASCII string, but the value can be UTF-8. */
+public final class Header {
+  // Special header names defined in the SPDY and HTTP/2 specs.
+  public static final ByteString RESPONSE_STATUS = ByteString.encodeUtf8(":status");
+  public static final ByteString TARGET_METHOD = ByteString.encodeUtf8(":method");
+  public static final ByteString TARGET_PATH = ByteString.encodeUtf8(":path");
+  public static final ByteString TARGET_SCHEME = ByteString.encodeUtf8(":scheme");
+  public static final ByteString TARGET_AUTHORITY = ByteString.encodeUtf8(":authority"); // HTTP/2
+  public static final ByteString TARGET_HOST = ByteString.encodeUtf8(":host"); // spdy/3
+  public static final ByteString VERSION = ByteString.encodeUtf8(":version"); // spdy/3
+
+  /** Name in case-insensitive ASCII encoding. */
+  public final ByteString name;
+  /** Value in UTF-8 encoding. */
+  public final ByteString value;
+  final int hpackSize;
+
+  // TODO: search for toLowerCase and consider moving logic here.
+  public Header(String name, String value) {
+    this(ByteString.encodeUtf8(name), ByteString.encodeUtf8(value));
+  }
+
+  public Header(ByteString name, String value) {
+    this(name, ByteString.encodeUtf8(value));
+  }
+
+  public Header(ByteString name, ByteString value) {
+    this.name = name;
+    this.value = value;
+    this.hpackSize = 32 + name.size() + value.size();
+  }
+
+  @Override public boolean equals(Object other) {
+    if (other instanceof Header) {
+      Header that = (Header) other;
+      return this.name.equals(that.name)
+          && this.value.equals(that.value);
+    }
+    return false;
+  }
+
+  @Override public int hashCode() {
+    int result = 17;
+    result = 31 * result + name.hashCode();
+    result = 31 * result + value.hashCode();
+    return result;
+  }
+
+  @Override public String toString() {
+    return String.format("%s: %s", name.utf8(), value.utf8());
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/HeadersMode.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/HeadersMode.java
new file mode 100644
index 0000000..3826252
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/HeadersMode.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal.framed;
+
+public enum HeadersMode {
+  SPDY_SYN_STREAM,
+  SPDY_REPLY,
+  SPDY_HEADERS,
+  HTTP_20_HEADERS;
+
+  /** Returns true if it is an error these headers to create a new stream. */
+  public boolean failIfStreamAbsent() {
+    return this == SPDY_REPLY || this == SPDY_HEADERS;
+  }
+
+  /** Returns true if it is an error these headers to update an existing stream. */
+  public boolean failIfStreamPresent() {
+    return this == SPDY_SYN_STREAM;
+  }
+
+  /**
+   * Returns true if it is an error these headers to be the initial headers of a
+   * response.
+   */
+  public boolean failIfHeadersAbsent() {
+    return this == SPDY_HEADERS;
+  }
+
+  /**
+   * Returns true if it is an error these headers to be update existing headers
+   * of a response.
+   */
+  public boolean failIfHeadersPresent() {
+    return this == SPDY_REPLY;
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Hpack.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Hpack.java
new file mode 100644
index 0000000..5db4f09
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Hpack.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal.framed;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import okio.Buffer;
+import okio.BufferedSource;
+import okio.ByteString;
+import okio.Okio;
+import okio.Source;
+
+/**
+ * Read and write HPACK v10.
+ *
+ * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12
+ *
+ * This implementation uses an array for the dynamic table and a list for
+ * indexed entries.  Dynamic entries are added to the array, starting in the
+ * last position moving forward.  When the array fills, it is doubled.
+ */
+final class Hpack {
+  private static final int PREFIX_4_BITS = 0x0f;
+  private static final int PREFIX_5_BITS = 0x1f;
+  private static final int PREFIX_6_BITS = 0x3f;
+  private static final int PREFIX_7_BITS = 0x7f;
+
+  private static final io.grpc.okhttp.internal.framed.Header[] STATIC_HEADER_TABLE = new io.grpc.okhttp.internal.framed.Header[] {
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_AUTHORITY, ""),
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_METHOD, "GET"),
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_METHOD, "POST"),
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_PATH, "/"),
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_PATH, "/index.html"),
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_SCHEME, "http"),
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_SCHEME, "https"),
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "200"),
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "204"),
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "206"),
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "304"),
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "400"),
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "404"),
+      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "500"),
+      new io.grpc.okhttp.internal.framed.Header("accept-charset", ""),
+      new io.grpc.okhttp.internal.framed.Header("accept-encoding", "gzip, deflate"),
+      new io.grpc.okhttp.internal.framed.Header("accept-language", ""),
+      new io.grpc.okhttp.internal.framed.Header("accept-ranges", ""),
+      new io.grpc.okhttp.internal.framed.Header("accept", ""),
+      new io.grpc.okhttp.internal.framed.Header("access-control-allow-origin", ""),
+      new io.grpc.okhttp.internal.framed.Header("age", ""),
+      new io.grpc.okhttp.internal.framed.Header("allow", ""),
+      new io.grpc.okhttp.internal.framed.Header("authorization", ""),
+      new io.grpc.okhttp.internal.framed.Header("cache-control", ""),
+      new io.grpc.okhttp.internal.framed.Header("content-disposition", ""),
+      new io.grpc.okhttp.internal.framed.Header("content-encoding", ""),
+      new io.grpc.okhttp.internal.framed.Header("content-language", ""),
+      new io.grpc.okhttp.internal.framed.Header("content-length", ""),
+      new io.grpc.okhttp.internal.framed.Header("content-location", ""),
+      new io.grpc.okhttp.internal.framed.Header("content-range", ""),
+      new io.grpc.okhttp.internal.framed.Header("content-type", ""),
+      new io.grpc.okhttp.internal.framed.Header("cookie", ""),
+      new io.grpc.okhttp.internal.framed.Header("date", ""),
+      new io.grpc.okhttp.internal.framed.Header("etag", ""),
+      new io.grpc.okhttp.internal.framed.Header("expect", ""),
+      new io.grpc.okhttp.internal.framed.Header("expires", ""),
+      new io.grpc.okhttp.internal.framed.Header("from", ""),
+      new io.grpc.okhttp.internal.framed.Header("host", ""),
+      new io.grpc.okhttp.internal.framed.Header("if-match", ""),
+      new io.grpc.okhttp.internal.framed.Header("if-modified-since", ""),
+      new io.grpc.okhttp.internal.framed.Header("if-none-match", ""),
+      new io.grpc.okhttp.internal.framed.Header("if-range", ""),
+      new io.grpc.okhttp.internal.framed.Header("if-unmodified-since", ""),
+      new io.grpc.okhttp.internal.framed.Header("last-modified", ""),
+      new io.grpc.okhttp.internal.framed.Header("link", ""),
+      new io.grpc.okhttp.internal.framed.Header("location", ""),
+      new io.grpc.okhttp.internal.framed.Header("max-forwards", ""),
+      new io.grpc.okhttp.internal.framed.Header("proxy-authenticate", ""),
+      new io.grpc.okhttp.internal.framed.Header("proxy-authorization", ""),
+      new io.grpc.okhttp.internal.framed.Header("range", ""),
+      new io.grpc.okhttp.internal.framed.Header("referer", ""),
+      new io.grpc.okhttp.internal.framed.Header("refresh", ""),
+      new io.grpc.okhttp.internal.framed.Header("retry-after", ""),
+      new io.grpc.okhttp.internal.framed.Header("server", ""),
+      new io.grpc.okhttp.internal.framed.Header("set-cookie", ""),
+      new io.grpc.okhttp.internal.framed.Header("strict-transport-security", ""),
+      new io.grpc.okhttp.internal.framed.Header("transfer-encoding", ""),
+      new io.grpc.okhttp.internal.framed.Header("user-agent", ""),
+      new io.grpc.okhttp.internal.framed.Header("vary", ""),
+      new io.grpc.okhttp.internal.framed.Header("via", ""),
+      new io.grpc.okhttp.internal.framed.Header("www-authenticate", "")
+  };
+
+  private Hpack() {
+  }
+
+  // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-3.1
+  static final class Reader {
+
+    private final List<io.grpc.okhttp.internal.framed.Header> headerList = new ArrayList<io.grpc.okhttp.internal.framed.Header>();
+    private final BufferedSource source;
+
+    private int headerTableSizeSetting;
+    private int maxDynamicTableByteCount;
+    // Visible for testing.
+    io.grpc.okhttp.internal.framed.Header[] dynamicTable = new io.grpc.okhttp.internal.framed.Header[8];
+    // Array is populated back to front, so new entries always have lowest index.
+    int nextDynamicTableIndex = dynamicTable.length - 1;
+    int dynamicTableHeaderCount = 0;
+    int dynamicTableByteCount = 0;
+
+    Reader(int headerTableSizeSetting, Source source) {
+      this.headerTableSizeSetting = headerTableSizeSetting;
+      this.maxDynamicTableByteCount = headerTableSizeSetting;
+      this.source = Okio.buffer(source);
+    }
+
+    int maxDynamicTableByteCount() {
+      return maxDynamicTableByteCount;
+    }
+
+    /**
+     * Called by the reader when the peer sent {@link Settings#HEADER_TABLE_SIZE}.
+     * While this establishes the maximum dynamic table size, the
+     * {@link #maxDynamicTableByteCount} set during processing may limit the
+     * table size to a smaller amount.
+     * <p> Evicts entries or clears the table as needed.
+     */
+    void headerTableSizeSetting(int headerTableSizeSetting) {
+      this.headerTableSizeSetting = headerTableSizeSetting;
+      this.maxDynamicTableByteCount = headerTableSizeSetting;
+      adjustDynamicTableByteCount();
+    }
+
+    private void adjustDynamicTableByteCount() {
+      if (maxDynamicTableByteCount < dynamicTableByteCount) {
+        if (maxDynamicTableByteCount == 0) {
+          clearDynamicTable();
+        } else {
+          evictToRecoverBytes(dynamicTableByteCount - maxDynamicTableByteCount);
+        }
+      }
+    }
+
+    private void clearDynamicTable() {
+      Arrays.fill(dynamicTable, null);
+      nextDynamicTableIndex = dynamicTable.length - 1;
+      dynamicTableHeaderCount = 0;
+      dynamicTableByteCount = 0;
+    }
+
+    /** Returns the count of entries evicted. */
+    private int evictToRecoverBytes(int bytesToRecover) {
+      int entriesToEvict = 0;
+      if (bytesToRecover > 0) {
+        // determine how many headers need to be evicted.
+        for (int j = dynamicTable.length - 1; j >= nextDynamicTableIndex && bytesToRecover > 0; j--) {
+          bytesToRecover -= dynamicTable[j].hpackSize;
+          dynamicTableByteCount -= dynamicTable[j].hpackSize;
+          dynamicTableHeaderCount--;
+          entriesToEvict++;
+        }
+        System.arraycopy(dynamicTable, nextDynamicTableIndex + 1, dynamicTable,
+            nextDynamicTableIndex + 1 + entriesToEvict, dynamicTableHeaderCount);
+        nextDynamicTableIndex += entriesToEvict;
+      }
+      return entriesToEvict;
+    }
+
+    /**
+     * Read {@code byteCount} bytes of headers from the source stream. This
+     * implementation does not propagate the never indexed flag of a header.
+     */
+    void readHeaders() throws IOException {
+      while (!source.exhausted()) {
+        int b = source.readByte() & 0xff;
+        if (b == 0x80) { // 10000000
+          throw new IOException("index == 0");
+        } else if ((b & 0x80) == 0x80) { // 1NNNNNNN
+          int index = readInt(b, PREFIX_7_BITS);
+          readIndexedHeader(index - 1);
+        } else if (b == 0x40) { // 01000000
+          readLiteralHeaderWithIncrementalIndexingNewName();
+        } else if ((b & 0x40) == 0x40) {  // 01NNNNNN
+          int index = readInt(b, PREFIX_6_BITS);
+          readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1);
+        } else if ((b & 0x20) == 0x20) {  // 001NNNNN
+          maxDynamicTableByteCount = readInt(b, PREFIX_5_BITS);
+          if (maxDynamicTableByteCount < 0
+              || maxDynamicTableByteCount > headerTableSizeSetting) {
+            throw new IOException("Invalid dynamic table size update " + maxDynamicTableByteCount);
+          }
+          adjustDynamicTableByteCount();
+        } else if (b == 0x10 || b == 0) { // 000?0000 - Ignore never indexed bit.
+          readLiteralHeaderWithoutIndexingNewName();
+        } else { // 000?NNNN - Ignore never indexed bit.
+          int index = readInt(b, PREFIX_4_BITS);
+          readLiteralHeaderWithoutIndexingIndexedName(index - 1);
+        }
+      }
+    }
+
+    public List<io.grpc.okhttp.internal.framed.Header> getAndResetHeaderList() {
+      List<io.grpc.okhttp.internal.framed.Header> result = new ArrayList<io.grpc.okhttp.internal.framed.Header>(headerList);
+      headerList.clear();
+      return result;
+    }
+
+    private void readIndexedHeader(int index) throws IOException {
+      if (isStaticHeader(index)) {
+        io.grpc.okhttp.internal.framed.Header staticEntry = STATIC_HEADER_TABLE[index];
+        headerList.add(staticEntry);
+      } else {
+        int dynamicTableIndex = dynamicTableIndex(index - STATIC_HEADER_TABLE.length);
+        if (dynamicTableIndex < 0 || dynamicTableIndex > dynamicTable.length - 1) {
+          throw new IOException("Header index too large " + (index + 1));
+        }
+        headerList.add(dynamicTable[dynamicTableIndex]);
+      }
+    }
+
+    // referencedHeaders is relative to nextDynamicTableIndex + 1.
+    private int dynamicTableIndex(int index) {
+      return nextDynamicTableIndex + 1 + index;
+    }
+
+    private void readLiteralHeaderWithoutIndexingIndexedName(int index) throws IOException {
+      ByteString name = getName(index);
+      ByteString value = readByteString();
+      headerList.add(new io.grpc.okhttp.internal.framed.Header(name, value));
+    }
+
+    private void readLiteralHeaderWithoutIndexingNewName() throws IOException {
+      ByteString name = checkLowercase(readByteString());
+      ByteString value = readByteString();
+      headerList.add(new io.grpc.okhttp.internal.framed.Header(name, value));
+    }
+
+    private void readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex)
+        throws IOException {
+      ByteString name = getName(nameIndex);
+      ByteString value = readByteString();
+      insertIntoDynamicTable(-1, new io.grpc.okhttp.internal.framed.Header(name, value));
+    }
+
+    private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException {
+      ByteString name = checkLowercase(readByteString());
+      ByteString value = readByteString();
+      insertIntoDynamicTable(-1, new io.grpc.okhttp.internal.framed.Header(name, value));
+    }
+
+    private ByteString getName(int index) {
+      if (isStaticHeader(index)) {
+        return STATIC_HEADER_TABLE[index].name;
+      } else {
+        return dynamicTable[dynamicTableIndex(index - STATIC_HEADER_TABLE.length)].name;
+      }
+    }
+
+    private boolean isStaticHeader(int index) {
+      return index >= 0 && index <= STATIC_HEADER_TABLE.length - 1;
+    }
+
+    /** index == -1 when new. */
+    private void insertIntoDynamicTable(int index, io.grpc.okhttp.internal.framed.Header entry) {
+      headerList.add(entry);
+
+      int delta = entry.hpackSize;
+      if (index != -1) { // Index -1 == new header.
+        delta -= dynamicTable[dynamicTableIndex(index)].hpackSize;
+      }
+
+      // if the new or replacement header is too big, drop all entries.
+      if (delta > maxDynamicTableByteCount) {
+        clearDynamicTable();
+        return;
+      }
+
+      // Evict headers to the required length.
+      int bytesToRecover = (dynamicTableByteCount + delta) - maxDynamicTableByteCount;
+      int entriesEvicted = evictToRecoverBytes(bytesToRecover);
+
+      if (index == -1) { // Adding a value to the dynamic table.
+        if (dynamicTableHeaderCount + 1 > dynamicTable.length) { // Need to grow the dynamic table.
+          io.grpc.okhttp.internal.framed.Header[] doubled = new io.grpc.okhttp.internal.framed.Header[dynamicTable.length * 2];
+          System.arraycopy(dynamicTable, 0, doubled, dynamicTable.length, dynamicTable.length);
+          nextDynamicTableIndex = dynamicTable.length - 1;
+          dynamicTable = doubled;
+        }
+        index = nextDynamicTableIndex--;
+        dynamicTable[index] = entry;
+        dynamicTableHeaderCount++;
+      } else { // Replace value at same position.
+        index += dynamicTableIndex(index) + entriesEvicted;
+        dynamicTable[index] = entry;
+      }
+      dynamicTableByteCount += delta;
+    }
+
+    private int readByte() throws IOException {
+      return source.readByte() & 0xff;
+    }
+
+    int readInt(int firstByte, int prefixMask) throws IOException {
+      int prefix = firstByte & prefixMask;
+      if (prefix < prefixMask) {
+        return prefix; // This was a single byte value.
+      }
+
+      // This is a multibyte value. Read 7 bits at a time.
+      int result = prefixMask;
+      int shift = 0;
+      while (true) {
+        int b = readByte();
+        if ((b & 0x80) != 0) { // Equivalent to (b >= 128) since b is in [0..255].
+          result += (b & 0x7f) << shift;
+          shift += 7;
+        } else {
+          result += b << shift; // Last byte.
+          break;
+        }
+      }
+      return result;
+    }
+
+    /** Reads a potentially Huffman encoded byte string. */
+    ByteString readByteString() throws IOException {
+      int firstByte = readByte();
+      boolean huffmanDecode = (firstByte & 0x80) == 0x80; // 1NNNNNNN
+      int length = readInt(firstByte, PREFIX_7_BITS);
+
+      if (huffmanDecode) {
+        return ByteString.of(io.grpc.okhttp.internal.framed.Huffman.get().decode(source.readByteArray(length)));
+      } else {
+        return source.readByteString(length);
+      }
+    }
+  }
+
+  private static final Map<ByteString, Integer> NAME_TO_FIRST_INDEX = nameToFirstIndex();
+
+  private static Map<ByteString, Integer> nameToFirstIndex() {
+    Map<ByteString, Integer> result =
+        new LinkedHashMap<ByteString, Integer>(STATIC_HEADER_TABLE.length);
+    for (int i = 0; i < STATIC_HEADER_TABLE.length; i++) {
+      if (!result.containsKey(STATIC_HEADER_TABLE[i].name)) {
+        result.put(STATIC_HEADER_TABLE[i].name, i);
+      }
+    }
+    return Collections.unmodifiableMap(result);
+  }
+
+  static final class Writer {
+    private final Buffer out;
+
+    Writer(Buffer out) {
+      this.out = out;
+    }
+
+    /** This does not use "never indexed" semantics for sensitive headers. */
+    // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-6.2.3
+    void writeHeaders(List<io.grpc.okhttp.internal.framed.Header> headerBlock) throws IOException {
+      // TODO: implement index tracking
+      for (int i = 0, size = headerBlock.size(); i < size; i++) {
+        ByteString name = headerBlock.get(i).name.toAsciiLowercase();
+        Integer staticIndex = NAME_TO_FIRST_INDEX.get(name);
+        if (staticIndex != null) {
+          // Literal Header Field without Indexing - Indexed Name.
+          writeInt(staticIndex + 1, PREFIX_4_BITS, 0);
+          writeByteString(headerBlock.get(i).value);
+        } else {
+          out.writeByte(0x00); // Literal Header without Indexing - New Name.
+          writeByteString(name);
+          writeByteString(headerBlock.get(i).value);
+        }
+      }
+    }
+
+    // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-4.1.1
+    void writeInt(int value, int prefixMask, int bits) throws IOException {
+      // Write the raw value for a single byte value.
+      if (value < prefixMask) {
+        out.writeByte(bits | value);
+        return;
+      }
+
+      // Write the mask to start a multibyte value.
+      out.writeByte(bits | prefixMask);
+      value -= prefixMask;
+
+      // Write 7 bits at a time 'til we're done.
+      while (value >= 0x80) {
+        int b = value & 0x7f;
+        out.writeByte(b | 0x80);
+        value >>>= 7;
+      }
+      out.writeByte(value);
+    }
+
+    void writeByteString(ByteString data) throws IOException {
+      writeInt(data.size(), PREFIX_7_BITS, 0);
+      out.write(data);
+    }
+  }
+
+  /**
+   * An HTTP/2 response cannot contain uppercase header characters and must
+   * be treated as malformed.
+   */
+  private static ByteString checkLowercase(ByteString name) throws IOException {
+    for (int i = 0, length = name.size(); i < length; i++) {
+      byte c = name.getByte(i);
+      if (c >= 'A' && c <= 'Z') {
+        throw new IOException("PROTOCOL_ERROR response malformed: mixed case name: " + name.utf8());
+      }
+    }
+    return name;
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Http2.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Http2.java
new file mode 100644
index 0000000..20eed3c
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Http2.java
@@ -0,0 +1,777 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal.framed;
+
+import io.grpc.okhttp.internal.Protocol;
+import java.io.IOException;
+import java.util.List;
+import java.util.logging.Logger;
+
+import okio.Buffer;
+import okio.BufferedSink;
+import okio.BufferedSource;
+import okio.ByteString;
+import okio.Source;
+import okio.Timeout;
+
+import static io.grpc.okhttp.internal.framed.Http2.FrameLogger.formatHeader;
+import static java.lang.String.format;
+import static java.util.logging.Level.FINE;
+import static okio.ByteString.EMPTY;
+
+/**
+ * Read and write HTTP/2 frames.
+ * <p>
+ * This implementation assumes we do not send an increased
+ * {@link io.grpc.okhttp.internal.framed.Settings#getMaxFrameSize frame size setting} to the peer. Hence, we
+ * expect all frames to have a max length of {@link #INITIAL_MAX_FRAME_SIZE}.
+ * <p>http://tools.ietf.org/html/draft-ietf-httpbis-http2-17
+ */
+public final class Http2 implements Variant {
+  private static final Logger logger = Logger.getLogger(FrameLogger.class.getName());
+
+  @Override public Protocol getProtocol() {
+    return Protocol.HTTP_2;
+  }
+
+  private static final ByteString CONNECTION_PREFACE
+      = ByteString.encodeUtf8("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
+
+  /** The initial max frame size, applied independently writing to, or reading from the peer. */
+  static final int INITIAL_MAX_FRAME_SIZE = 0x4000; // 16384
+
+  static final byte TYPE_DATA = 0x0;
+  static final byte TYPE_HEADERS = 0x1;
+  static final byte TYPE_PRIORITY = 0x2;
+  static final byte TYPE_RST_STREAM = 0x3;
+  static final byte TYPE_SETTINGS = 0x4;
+  static final byte TYPE_PUSH_PROMISE = 0x5;
+  static final byte TYPE_PING = 0x6;
+  static final byte TYPE_GOAWAY = 0x7;
+  static final byte TYPE_WINDOW_UPDATE = 0x8;
+  static final byte TYPE_CONTINUATION = 0x9;
+
+  static final byte FLAG_NONE = 0x0;
+  static final byte FLAG_ACK = 0x1; // Used for settings and ping.
+  static final byte FLAG_END_STREAM = 0x1; // Used for headers and data.
+  static final byte FLAG_END_HEADERS = 0x4; // Used for headers and continuation.
+  static final byte FLAG_END_PUSH_PROMISE = 0x4;
+  static final byte FLAG_PADDED = 0x8; // Used for headers and data.
+  static final byte FLAG_PRIORITY = 0x20; // Used for headers.
+  static final byte FLAG_COMPRESSED = 0x20; // Used for data.
+
+  /**
+   * Creates a frame reader with max header table size of 4096 and data frame
+   * compression disabled.
+   */
+  @Override public FrameReader newReader(BufferedSource source, boolean client) {
+    return new Reader(source, 4096, client);
+  }
+
+  @Override public io.grpc.okhttp.internal.framed.FrameWriter newWriter(BufferedSink sink, boolean client) {
+    return new Writer(sink, client);
+  }
+
+  static final class Reader implements FrameReader {
+    private final BufferedSource source;
+    private final ContinuationSource continuation;
+    private final boolean client;
+
+    // Visible for testing.
+    final Hpack.Reader hpackReader;
+
+    Reader(BufferedSource source, int headerTableSize, boolean client) {
+      this.source = source;
+      this.client = client;
+      this.continuation = new ContinuationSource(this.source);
+      this.hpackReader = new Hpack.Reader(headerTableSize, continuation);
+    }
+
+    @Override public void readConnectionPreface() throws IOException {
+      if (client) return; // Nothing to read; servers doesn't send a connection preface!
+      ByteString connectionPreface = source.readByteString(CONNECTION_PREFACE.size());
+      if (logger.isLoggable(FINE)) logger.fine(format("<< CONNECTION %s", connectionPreface.hex()));
+      if (!CONNECTION_PREFACE.equals(connectionPreface)) {
+        throw ioException("Expected a connection header but was %s", connectionPreface.utf8());
+      }
+    }
+
+    @Override public boolean nextFrame(Handler handler) throws IOException {
+      try {
+        source.require(9); // Frame header size
+      } catch (IOException e) {
+        return false; // This might be a normal socket close.
+      }
+
+      /*  0                   1                   2                   3
+       *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+       * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+       * |                 Length (24)                   |
+       * +---------------+---------------+---------------+
+       * |   Type (8)    |   Flags (8)   |
+       * +-+-+-----------+---------------+-------------------------------+
+       * |R|                 Stream Identifier (31)                      |
+       * +=+=============================================================+
+       * |                   Frame Payload (0...)                      ...
+       * +---------------------------------------------------------------+
+       */
+      int length = readMedium(source);
+      if (length < 0 || length > INITIAL_MAX_FRAME_SIZE) {
+        throw ioException("FRAME_SIZE_ERROR: %s", length);
+      }
+      byte type = (byte) (source.readByte() & 0xff);
+      byte flags = (byte) (source.readByte() & 0xff);
+      int streamId = (source.readInt() & 0x7fffffff); // Ignore reserved bit.
+      if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags));
+
+      switch (type) {
+        case TYPE_DATA:
+          readData(handler, length, flags, streamId);
+          break;
+
+        case TYPE_HEADERS:
+          readHeaders(handler, length, flags, streamId);
+          break;
+
+        case TYPE_PRIORITY:
+          readPriority(handler, length, flags, streamId);
+          break;
+
+        case TYPE_RST_STREAM:
+          readRstStream(handler, length, flags, streamId);
+          break;
+
+        case TYPE_SETTINGS:
+          readSettings(handler, length, flags, streamId);
+          break;
+
+        case TYPE_PUSH_PROMISE:
+          readPushPromise(handler, length, flags, streamId);
+          break;
+
+        case TYPE_PING:
+          readPing(handler, length, flags, streamId);
+          break;
+
+        case TYPE_GOAWAY:
+          readGoAway(handler, length, flags, streamId);
+          break;
+
+        case TYPE_WINDOW_UPDATE:
+          readWindowUpdate(handler, length, flags, streamId);
+          break;
+
+        default:
+          // Implementations MUST discard frames that have unknown or unsupported types.
+          source.skip(length);
+      }
+      return true;
+    }
+
+    private void readHeaders(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (streamId == 0) throw ioException("PROTOCOL_ERROR: TYPE_HEADERS streamId == 0");
+
+      boolean endStream = (flags & FLAG_END_STREAM) != 0;
+
+      short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
+
+      if ((flags & FLAG_PRIORITY) != 0) {
+        readPriority(handler, streamId);
+        length -= 5; // account for above read.
+      }
+
+      length = lengthWithoutPadding(length, flags, padding);
+
+      List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId);
+
+      handler.headers(false, endStream, streamId, -1, headerBlock, HeadersMode.HTTP_20_HEADERS);
+    }
+
+    private List<Header> readHeaderBlock(int length, short padding, byte flags, int streamId)
+        throws IOException {
+      continuation.length = continuation.left = length;
+      continuation.padding = padding;
+      continuation.flags = flags;
+      continuation.streamId = streamId;
+
+      // TODO: Concat multi-value headers with 0x0, except COOKIE, which uses 0x3B, 0x20.
+      // http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-8.1.2.5
+      hpackReader.readHeaders();
+      return hpackReader.getAndResetHeaderList();
+    }
+
+    private void readData(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      // TODO: checkState open or half-closed (local) or raise STREAM_CLOSED
+      boolean inFinished = (flags & FLAG_END_STREAM) != 0;
+      boolean gzipped = (flags & FLAG_COMPRESSED) != 0;
+      if (gzipped) {
+        throw ioException("PROTOCOL_ERROR: FLAG_COMPRESSED without SETTINGS_COMPRESS_DATA");
+      }
+
+      short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
+      length = lengthWithoutPadding(length, flags, padding);
+
+      handler.data(inFinished, streamId, source, length);
+      source.skip(padding);
+    }
+
+    private void readPriority(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (length != 5) throw ioException("TYPE_PRIORITY length: %d != 5", length);
+      if (streamId == 0) throw ioException("TYPE_PRIORITY streamId == 0");
+      readPriority(handler, streamId);
+    }
+
+    private void readPriority(Handler handler, int streamId) throws IOException {
+      int w1 = source.readInt();
+      boolean exclusive = (w1 & 0x80000000) != 0;
+      int streamDependency = (w1 & 0x7fffffff);
+      int weight = (source.readByte() & 0xff) + 1;
+      handler.priority(streamId, streamDependency, weight, exclusive);
+    }
+
+    private void readRstStream(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (length != 4) throw ioException("TYPE_RST_STREAM length: %d != 4", length);
+      if (streamId == 0) throw ioException("TYPE_RST_STREAM streamId == 0");
+      int errorCodeInt = source.readInt();
+      io.grpc.okhttp.internal.framed.ErrorCode errorCode = io.grpc.okhttp.internal.framed.ErrorCode.fromHttp2(errorCodeInt);
+      if (errorCode == null) {
+        throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
+      }
+      handler.rstStream(streamId, errorCode);
+    }
+
+    private void readSettings(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0");
+      if ((flags & FLAG_ACK) != 0) {
+        if (length != 0) throw ioException("FRAME_SIZE_ERROR ack frame should be empty!");
+        handler.ackSettings();
+        return;
+      }
+
+      if (length % 6 != 0) throw ioException("TYPE_SETTINGS length %% 6 != 0: %s", length);
+      io.grpc.okhttp.internal.framed.Settings settings = new io.grpc.okhttp.internal.framed.Settings();
+      for (int i = 0; i < length; i += 6) {
+        short id = source.readShort();
+        int value = source.readInt();
+
+        switch (id) {
+          case 1: // SETTINGS_HEADER_TABLE_SIZE
+            break;
+          case 2: // SETTINGS_ENABLE_PUSH
+            if (value != 0 && value != 1) {
+              throw ioException("PROTOCOL_ERROR SETTINGS_ENABLE_PUSH != 0 or 1");
+            }
+            break;
+          case 3: // SETTINGS_MAX_CONCURRENT_STREAMS
+            id = 4; // Renumbered in draft 10.
+            break;
+          case 4: // SETTINGS_INITIAL_WINDOW_SIZE
+            id = 7; // Renumbered in draft 10.
+            if (value < 0) {
+              throw ioException("PROTOCOL_ERROR SETTINGS_INITIAL_WINDOW_SIZE > 2^31 - 1");
+            }
+            break;
+          case 5: // SETTINGS_MAX_FRAME_SIZE
+            if (value < INITIAL_MAX_FRAME_SIZE || value > 16777215) {
+              throw ioException("PROTOCOL_ERROR SETTINGS_MAX_FRAME_SIZE: %s", value);
+            }
+            break;
+          case 6: // SETTINGS_MAX_HEADER_LIST_SIZE
+            break; // Advisory only, so ignored.
+          default:
+            // Implementations MUST ignore any unknown or unsupported identifier.
+            continue;
+        }
+        settings.set(id, 0, value);
+      }
+      handler.settings(false, settings);
+      if (settings.getHeaderTableSize() >= 0) {
+        hpackReader.headerTableSizeSetting(settings.getHeaderTableSize());
+      }
+    }
+
+    private void readPushPromise(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (streamId == 0) {
+        throw ioException("PROTOCOL_ERROR: TYPE_PUSH_PROMISE streamId == 0");
+      }
+      short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;
+      int promisedStreamId = source.readInt() & 0x7fffffff;
+      length -= 4; // account for above read.
+      length = lengthWithoutPadding(length, flags, padding);
+      List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId);
+      handler.pushPromise(streamId, promisedStreamId, headerBlock);
+    }
+
+    private void readPing(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (length != 8) throw ioException("TYPE_PING length != 8: %s", length);
+      if (streamId != 0) throw ioException("TYPE_PING streamId != 0");
+      int payload1 = source.readInt();
+      int payload2 = source.readInt();
+      boolean ack = (flags & FLAG_ACK) != 0;
+      handler.ping(ack, payload1, payload2);
+    }
+
+    private void readGoAway(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length);
+      if (streamId != 0) throw ioException("TYPE_GOAWAY streamId != 0");
+      int lastStreamId = source.readInt();
+      int errorCodeInt = source.readInt();
+      int opaqueDataLength = length - 8;
+      io.grpc.okhttp.internal.framed.ErrorCode errorCode = io.grpc.okhttp.internal.framed.ErrorCode.fromHttp2(errorCodeInt);
+      if (errorCode == null) {
+        throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
+      }
+      ByteString debugData = EMPTY;
+      if (opaqueDataLength > 0) { // Must read debug data in order to not corrupt the connection.
+        debugData = source.readByteString(opaqueDataLength);
+      }
+      handler.goAway(lastStreamId, errorCode, debugData);
+    }
+
+    private void readWindowUpdate(Handler handler, int length, byte flags, int streamId)
+        throws IOException {
+      if (length != 4) throw ioException("TYPE_WINDOW_UPDATE length !=4: %s", length);
+      long increment = (source.readInt() & 0x7fffffffL);
+      if (increment == 0) throw ioException("windowSizeIncrement was 0", increment);
+      handler.windowUpdate(streamId, increment);
+    }
+
+    @Override public void close() throws IOException {
+      source.close();
+    }
+  }
+
+  static final class Writer implements io.grpc.okhttp.internal.framed.FrameWriter {
+    private final BufferedSink sink;
+    private final boolean client;
+    private final Buffer hpackBuffer;
+    private final Hpack.Writer hpackWriter;
+    private int maxFrameSize;
+    private boolean closed;
+
+    Writer(BufferedSink sink, boolean client) {
+      this.sink = sink;
+      this.client = client;
+      this.hpackBuffer = new Buffer();
+      this.hpackWriter = new Hpack.Writer(hpackBuffer);
+      this.maxFrameSize = INITIAL_MAX_FRAME_SIZE;
+    }
+
+    @Override public synchronized void flush() throws IOException {
+      if (closed) throw new IOException("closed");
+      sink.flush();
+    }
+
+    @Override public synchronized void ackSettings(io.grpc.okhttp.internal.framed.Settings peerSettings) throws IOException {
+      if (closed) throw new IOException("closed");
+      this.maxFrameSize = peerSettings.getMaxFrameSize(maxFrameSize);
+      int length = 0;
+      byte type = TYPE_SETTINGS;
+      byte flags = FLAG_ACK;
+      int streamId = 0;
+      frameHeader(streamId, length, type, flags);
+      sink.flush();
+    }
+
+    @Override public synchronized void connectionPreface() throws IOException {
+      if (closed) throw new IOException("closed");
+      if (!client) return; // Nothing to write; servers don't send connection headers!
+      if (logger.isLoggable(FINE)) {
+        logger.fine(format(">> CONNECTION %s", CONNECTION_PREFACE.hex()));
+      }
+      sink.write(CONNECTION_PREFACE.toByteArray());
+      sink.flush();
+    }
+
+    @Override public synchronized void synStream(boolean outFinished, boolean inFinished,
+        int streamId, int associatedStreamId, List<Header> headerBlock)
+        throws IOException {
+      if (inFinished) throw new UnsupportedOperationException();
+      if (closed) throw new IOException("closed");
+      headers(outFinished, streamId, headerBlock);
+    }
+
+    @Override public synchronized void synReply(boolean outFinished, int streamId,
+        List<Header> headerBlock) throws IOException {
+      if (closed) throw new IOException("closed");
+      headers(outFinished, streamId, headerBlock);
+    }
+
+    @Override public synchronized void headers(int streamId, List<Header> headerBlock)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      headers(false, streamId, headerBlock);
+    }
+
+    @Override public synchronized void pushPromise(int streamId, int promisedStreamId,
+        List<Header> requestHeaders) throws IOException {
+      if (closed) throw new IOException("closed");
+      hpackWriter.writeHeaders(requestHeaders);
+
+      long byteCount = hpackBuffer.size();
+      int length = (int) Math.min(maxFrameSize - 4, byteCount);
+      byte type = TYPE_PUSH_PROMISE;
+      byte flags = byteCount == length ? FLAG_END_HEADERS : 0;
+      frameHeader(streamId, length + 4, type, flags);
+      sink.writeInt(promisedStreamId & 0x7fffffff);
+      sink.write(hpackBuffer, length);
+
+      if (byteCount > length) writeContinuationFrames(streamId, byteCount - length);
+    }
+
+    void headers(boolean outFinished, int streamId, List<Header> headerBlock) throws IOException {
+      if (closed) throw new IOException("closed");
+      hpackWriter.writeHeaders(headerBlock);
+
+      long byteCount = hpackBuffer.size();
+      int length = (int) Math.min(maxFrameSize, byteCount);
+      byte type = TYPE_HEADERS;
+      byte flags = byteCount == length ? FLAG_END_HEADERS : 0;
+      if (outFinished) flags |= FLAG_END_STREAM;
+      frameHeader(streamId, length, type, flags);
+      sink.write(hpackBuffer, length);
+
+      if (byteCount > length) writeContinuationFrames(streamId, byteCount - length);
+    }
+
+    private void writeContinuationFrames(int streamId, long byteCount) throws IOException {
+      while (byteCount > 0) {
+        int length = (int) Math.min(maxFrameSize, byteCount);
+        byteCount -= length;
+        frameHeader(streamId, length, TYPE_CONTINUATION, byteCount == 0 ? FLAG_END_HEADERS : 0);
+        sink.write(hpackBuffer, length);
+      }
+    }
+
+    @Override public synchronized void rstStream(int streamId, io.grpc.okhttp.internal.framed.ErrorCode errorCode)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      if (errorCode.httpCode == -1) throw new IllegalArgumentException();
+
+      int length = 4;
+      byte type = TYPE_RST_STREAM;
+      byte flags = FLAG_NONE;
+      frameHeader(streamId, length, type, flags);
+      sink.writeInt(errorCode.httpCode);
+      sink.flush();
+    }
+
+    @Override public int maxDataLength() {
+      return maxFrameSize;
+    }
+
+    @Override public synchronized void data(boolean outFinished, int streamId, Buffer source,
+        int byteCount) throws IOException {
+      if (closed) throw new IOException("closed");
+      byte flags = FLAG_NONE;
+      if (outFinished) flags |= FLAG_END_STREAM;
+      dataFrame(streamId, flags, source, byteCount);
+    }
+
+    void dataFrame(int streamId, byte flags, Buffer buffer, int byteCount) throws IOException {
+      byte type = TYPE_DATA;
+      frameHeader(streamId, byteCount, type, flags);
+      if (byteCount > 0) {
+        sink.write(buffer, byteCount);
+      }
+    }
+
+    @Override public synchronized void settings(io.grpc.okhttp.internal.framed.Settings settings) throws IOException {
+      if (closed) throw new IOException("closed");
+      int length = settings.size() * 6;
+      byte type = TYPE_SETTINGS;
+      byte flags = FLAG_NONE;
+      int streamId = 0;
+      frameHeader(streamId, length, type, flags);
+      for (int i = 0; i < io.grpc.okhttp.internal.framed.Settings.COUNT; i++) {
+        if (!settings.isSet(i)) continue;
+        int id = i;
+        if (id == 4) id = 3; // SETTINGS_MAX_CONCURRENT_STREAMS renumbered.
+        else if (id == 7) id = 4; // SETTINGS_INITIAL_WINDOW_SIZE renumbered.
+        sink.writeShort(id);
+        sink.writeInt(settings.get(i));
+      }
+      sink.flush();
+    }
+
+    @Override public synchronized void ping(boolean ack, int payload1, int payload2)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      int length = 8;
+      byte type = TYPE_PING;
+      byte flags = ack ? FLAG_ACK : FLAG_NONE;
+      int streamId = 0;
+      frameHeader(streamId, length, type, flags);
+      sink.writeInt(payload1);
+      sink.writeInt(payload2);
+      sink.flush();
+    }
+
+    @Override public synchronized void goAway(int lastGoodStreamId, io.grpc.okhttp.internal.framed.ErrorCode errorCode,
+        byte[] debugData) throws IOException {
+      if (closed) throw new IOException("closed");
+      if (errorCode.httpCode == -1) throw illegalArgument("errorCode.httpCode == -1");
+      int length = 8 + debugData.length;
+      byte type = TYPE_GOAWAY;
+      byte flags = FLAG_NONE;
+      int streamId = 0;
+      frameHeader(streamId, length, type, flags);
+      sink.writeInt(lastGoodStreamId);
+      sink.writeInt(errorCode.httpCode);
+      if (debugData.length > 0) {
+        sink.write(debugData);
+      }
+      sink.flush();
+    }
+
+    @Override public synchronized void windowUpdate(int streamId, long windowSizeIncrement)
+        throws IOException {
+      if (closed) throw new IOException("closed");
+      if (windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL) {
+        throw illegalArgument("windowSizeIncrement == 0 || windowSizeIncrement > 0x7fffffffL: %s",
+            windowSizeIncrement);
+      }
+      int length = 4;
+      byte type = TYPE_WINDOW_UPDATE;
+      byte flags = FLAG_NONE;
+      frameHeader(streamId, length, type, flags);
+      sink.writeInt((int) windowSizeIncrement);
+      sink.flush();
+    }
+
+    @Override public synchronized void close() throws IOException {
+      closed = true;
+      sink.close();
+    }
+
+    void frameHeader(int streamId, int length, byte type, byte flags) throws IOException {
+      if (logger.isLoggable(FINE)) logger.fine(formatHeader(false, streamId, length, type, flags));
+      if (length > maxFrameSize) {
+        throw illegalArgument("FRAME_SIZE_ERROR length > %d: %d", maxFrameSize, length);
+      }
+      if ((streamId & 0x80000000) != 0) throw illegalArgument("reserved bit set: %s", streamId);
+      writeMedium(sink, length);
+      sink.writeByte(type & 0xff);
+      sink.writeByte(flags & 0xff);
+      sink.writeInt(streamId & 0x7fffffff);
+    }
+  }
+
+  private static IllegalArgumentException illegalArgument(String message, Object... args) {
+    throw new IllegalArgumentException(format(message, args));
+  }
+
+  private static IOException ioException(String message, Object... args) throws IOException {
+    throw new IOException(format(message, args));
+  }
+
+  /**
+   * Decompression of the header block occurs above the framing layer. This
+   * class lazily reads continuation frames as they are needed by {@link
+   * Hpack.Reader#readHeaders()}.
+   */
+  static final class ContinuationSource implements Source {
+    private final BufferedSource source;
+
+    int length;
+    byte flags;
+    int streamId;
+
+    int left;
+    short padding;
+
+    public ContinuationSource(BufferedSource source) {
+      this.source = source;
+    }
+
+    @Override public long read(Buffer sink, long byteCount) throws IOException {
+      while (left == 0) {
+        source.skip(padding);
+        padding = 0;
+        if ((flags & FLAG_END_HEADERS) != 0) return -1;
+        readContinuationHeader();
+        // TODO: test case for empty continuation header?
+      }
+
+      long read = source.read(sink, Math.min(byteCount, left));
+      if (read == -1) return -1;
+      left -= (int) read;
+      return read;
+    }
+
+    @Override public Timeout timeout() {
+      return source.timeout();
+    }
+
+    @Override public void close() throws IOException {
+    }
+
+    private void readContinuationHeader() throws IOException {
+      int previousStreamId = streamId;
+
+      length = left = readMedium(source);
+      byte type = (byte) (source.readByte() & 0xff);
+      flags = (byte) (source.readByte() & 0xff);
+      if (logger.isLoggable(FINE)) logger.fine(formatHeader(true, streamId, length, type, flags));
+      streamId = (source.readInt() & 0x7fffffff);
+      if (type != TYPE_CONTINUATION) throw ioException("%s != TYPE_CONTINUATION", type);
+      if (streamId != previousStreamId) throw ioException("TYPE_CONTINUATION streamId changed");
+    }
+  }
+
+  private static int lengthWithoutPadding(int length, byte flags, short padding)
+      throws IOException {
+    if ((flags & FLAG_PADDED) != 0) length--; // Account for reading the padding length.
+    if (padding > length) {
+      throw ioException("PROTOCOL_ERROR padding %s > remaining length %s", padding, length);
+    }
+    return (short) (length - padding);
+  }
+
+  /**
+   * Logs a human-readable representation of HTTP/2 frame headers.
+   *
+   * <p>The format is:
+   *
+   * <pre>
+   *   direction streamID length type flags
+   * </pre>
+   * Where direction is {@code <<} for inbound and {@code >>} for outbound.
+   *
+   * <p> For example, the following would indicate a HEAD request sent from
+   * the client.
+   * <pre>
+   * {@code
+   *   << 0x0000000f    12 HEADERS       END_HEADERS|END_STREAM
+   * }
+   * </pre>
+   */
+  static final class FrameLogger {
+
+    static String formatHeader(boolean inbound, int streamId, int length, byte type, byte flags) {
+      String formattedType = type < TYPES.length ? TYPES[type] : format("0x%02x", type);
+      String formattedFlags = formatFlags(type, flags);
+      return format("%s 0x%08x %5d %-13s %s", inbound ? "<<" : ">>", streamId, length,
+          formattedType, formattedFlags);
+    }
+
+    /**
+     * Looks up valid string representing flags from the table. Invalid
+     * combinations are represented in binary.
+     */
+    // Visible for testing.
+    static String formatFlags(byte type, byte flags) {
+      if (flags == 0) return "";
+      switch (type) { // Special case types that have 0 or 1 flag.
+        case TYPE_SETTINGS:
+        case TYPE_PING:
+          return flags == FLAG_ACK ? "ACK" : BINARY[flags];
+        case TYPE_PRIORITY:
+        case TYPE_RST_STREAM:
+        case TYPE_GOAWAY:
+        case TYPE_WINDOW_UPDATE:
+          return BINARY[flags];
+      }
+      String result = flags < FLAGS.length ? FLAGS[flags] : BINARY[flags];
+      // Special case types that have overlap flag values.
+      if (type == TYPE_PUSH_PROMISE && (flags & FLAG_END_PUSH_PROMISE) != 0) {
+        return result.replace("HEADERS", "PUSH_PROMISE"); // TODO: Avoid allocation.
+      } else if (type == TYPE_DATA && (flags & FLAG_COMPRESSED) != 0) {
+        return result.replace("PRIORITY", "COMPRESSED"); // TODO: Avoid allocation.
+      }
+      return result;
+    }
+
+    /** Lookup table for valid frame types. */
+    private static final String[] TYPES = new String[] {
+        "DATA",
+        "HEADERS",
+        "PRIORITY",
+        "RST_STREAM",
+        "SETTINGS",
+        "PUSH_PROMISE",
+        "PING",
+        "GOAWAY",
+        "WINDOW_UPDATE",
+        "CONTINUATION"
+    };
+
+    /**
+     * Lookup table for valid flags for DATA, HEADERS, CONTINUATION. Invalid
+     * combinations are represented in binary.
+     */
+    private static final String[] FLAGS = new String[0x40]; // Highest bit flag is 0x20.
+    private static final String[] BINARY = new String[256];
+
+    static {
+      for (int i = 0; i < BINARY.length; i++) {
+        BINARY[i] = format("%8s", Integer.toBinaryString(i)).replace(' ', '0');
+      }
+
+      FLAGS[FLAG_NONE] = "";
+      FLAGS[FLAG_END_STREAM] = "END_STREAM";
+
+      int[] prefixFlags = new int[] {FLAG_END_STREAM};
+
+      FLAGS[FLAG_PADDED] = "PADDED";
+      for (int prefixFlag : prefixFlags) {
+         FLAGS[prefixFlag | FLAG_PADDED] = FLAGS[prefixFlag] + "|PADDED";
+      }
+
+      FLAGS[FLAG_END_HEADERS] = "END_HEADERS"; // Same as END_PUSH_PROMISE.
+      FLAGS[FLAG_PRIORITY] = "PRIORITY"; // Same as FLAG_COMPRESSED.
+      FLAGS[FLAG_END_HEADERS | FLAG_PRIORITY] = "END_HEADERS|PRIORITY"; // Only valid on HEADERS.
+      int[] frameFlags =
+          new int[] {FLAG_END_HEADERS, FLAG_PRIORITY, FLAG_END_HEADERS | FLAG_PRIORITY};
+
+      for (int frameFlag : frameFlags) {
+        for (int prefixFlag : prefixFlags) {
+          FLAGS[prefixFlag | frameFlag] = FLAGS[prefixFlag] + '|' + FLAGS[frameFlag];
+          FLAGS[prefixFlag | frameFlag | FLAG_PADDED] =
+              FLAGS[prefixFlag] + '|' + FLAGS[frameFlag] + "|PADDED";
+        }
+      }
+
+      for (int i = 0; i < FLAGS.length; i++) { // Fill in holes with binary representation.
+        if (FLAGS[i] == null) FLAGS[i] = BINARY[i];
+      }
+    }
+  }
+
+  private static int readMedium(BufferedSource source) throws IOException {
+    return (source.readByte() & 0xff) << 16
+        |  (source.readByte() & 0xff) <<  8
+        |  (source.readByte() & 0xff);
+  }
+
+  private static void writeMedium(BufferedSink sink, int i) throws IOException {
+    sink.writeByte((i >>> 16) & 0xff);
+    sink.writeByte((i >>>  8) & 0xff);
+    sink.writeByte(i          & 0xff);
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Huffman.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Huffman.java
new file mode 100644
index 0000000..5eb4a88
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Huffman.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2013 Twitter, Inc.
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal.framed;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This class was originally composed from the following classes in
+ * <a href="https://github.com/twitter/hpack">Twitter Hpack</a>.
+ * <ul>
+ * <li>{@code com.twitter.hpack.HuffmanEncoder}</li>
+ * <li>{@code com.twitter.hpack.HuffmanDecoder}</li>
+ * <li>{@code com.twitter.hpack.HpackUtil}</li>
+ * </ul>
+ */
+class Huffman {
+
+  // Appendix C: Huffman Codes
+  // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-B
+  private static final int[] CODES = {
+      0x1ff8, 0x7fffd8, 0xfffffe2, 0xfffffe3, 0xfffffe4, 0xfffffe5, 0xfffffe6, 0xfffffe7, 0xfffffe8,
+      0xffffea, 0x3ffffffc, 0xfffffe9, 0xfffffea, 0x3ffffffd, 0xfffffeb, 0xfffffec, 0xfffffed,
+      0xfffffee, 0xfffffef, 0xffffff0, 0xffffff1, 0xffffff2, 0x3ffffffe, 0xffffff3, 0xffffff4,
+      0xffffff5, 0xffffff6, 0xffffff7, 0xffffff8, 0xffffff9, 0xffffffa, 0xffffffb, 0x14, 0x3f8,
+      0x3f9, 0xffa, 0x1ff9, 0x15, 0xf8, 0x7fa, 0x3fa, 0x3fb, 0xf9, 0x7fb, 0xfa, 0x16, 0x17, 0x18,
+      0x0, 0x1, 0x2, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x5c, 0xfb, 0x7ffc, 0x20, 0xffb,
+      0x3fc, 0x1ffa, 0x21, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
+      0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0xfc, 0x73, 0xfd, 0x1ffb, 0x7fff0,
+      0x1ffc, 0x3ffc, 0x22, 0x7ffd, 0x3, 0x23, 0x4, 0x24, 0x5, 0x25, 0x26, 0x27, 0x6, 0x74, 0x75,
+      0x28, 0x29, 0x2a, 0x7, 0x2b, 0x76, 0x2c, 0x8, 0x9, 0x2d, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7ffe,
+      0x7fc, 0x3ffd, 0x1ffd, 0xffffffc, 0xfffe6, 0x3fffd2, 0xfffe7, 0xfffe8, 0x3fffd3, 0x3fffd4,
+      0x3fffd5, 0x7fffd9, 0x3fffd6, 0x7fffda, 0x7fffdb, 0x7fffdc, 0x7fffdd, 0x7fffde, 0xffffeb,
+      0x7fffdf, 0xffffec, 0xffffed, 0x3fffd7, 0x7fffe0, 0xffffee, 0x7fffe1, 0x7fffe2, 0x7fffe3,
+      0x7fffe4, 0x1fffdc, 0x3fffd8, 0x7fffe5, 0x3fffd9, 0x7fffe6, 0x7fffe7, 0xffffef, 0x3fffda,
+      0x1fffdd, 0xfffe9, 0x3fffdb, 0x3fffdc, 0x7fffe8, 0x7fffe9, 0x1fffde, 0x7fffea, 0x3fffdd,
+      0x3fffde, 0xfffff0, 0x1fffdf, 0x3fffdf, 0x7fffeb, 0x7fffec, 0x1fffe0, 0x1fffe1, 0x3fffe0,
+      0x1fffe2, 0x7fffed, 0x3fffe1, 0x7fffee, 0x7fffef, 0xfffea, 0x3fffe2, 0x3fffe3, 0x3fffe4,
+      0x7ffff0, 0x3fffe5, 0x3fffe6, 0x7ffff1, 0x3ffffe0, 0x3ffffe1, 0xfffeb, 0x7fff1, 0x3fffe7,
+      0x7ffff2, 0x3fffe8, 0x1ffffec, 0x3ffffe2, 0x3ffffe3, 0x3ffffe4, 0x7ffffde, 0x7ffffdf,
+      0x3ffffe5, 0xfffff1, 0x1ffffed, 0x7fff2, 0x1fffe3, 0x3ffffe6, 0x7ffffe0, 0x7ffffe1, 0x3ffffe7,
+      0x7ffffe2, 0xfffff2, 0x1fffe4, 0x1fffe5, 0x3ffffe8, 0x3ffffe9, 0xffffffd, 0x7ffffe3,
+      0x7ffffe4, 0x7ffffe5, 0xfffec, 0xfffff3, 0xfffed, 0x1fffe6, 0x3fffe9, 0x1fffe7, 0x1fffe8,
+      0x7ffff3, 0x3fffea, 0x3fffeb, 0x1ffffee, 0x1ffffef, 0xfffff4, 0xfffff5, 0x3ffffea, 0x7ffff4,
+      0x3ffffeb, 0x7ffffe6, 0x3ffffec, 0x3ffffed, 0x7ffffe7, 0x7ffffe8, 0x7ffffe9, 0x7ffffea,
+      0x7ffffeb, 0xffffffe, 0x7ffffec, 0x7ffffed, 0x7ffffee, 0x7ffffef, 0x7fffff0, 0x3ffffee
+  };
+
+  private static final byte[] CODE_LENGTHS = {
+      13, 23, 28, 28, 28, 28, 28, 28, 28, 24, 30, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 30,
+      28, 28, 28, 28, 28, 28, 28, 28, 28, 6, 10, 10, 12, 13, 6, 8, 11, 10, 10, 8, 11, 8, 6, 6, 6, 5,
+      5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 15, 6, 12, 10, 13, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+      7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 8, 13, 19, 13, 14, 6, 15, 5, 6, 5, 6, 5, 6, 6, 6, 5, 7, 7, 6,
+      6, 6, 5, 6, 7, 6, 5, 5, 6, 7, 7, 7, 7, 7, 15, 11, 14, 13, 28, 20, 22, 20, 20, 22, 22, 22, 23,
+      22, 23, 23, 23, 23, 23, 24, 23, 24, 24, 22, 23, 24, 23, 23, 23, 23, 21, 22, 23, 22, 23, 23,
+      24, 22, 21, 20, 22, 22, 23, 23, 21, 23, 22, 22, 24, 21, 22, 23, 23, 21, 21, 22, 21, 23, 22,
+      23, 23, 20, 22, 22, 22, 23, 22, 22, 23, 26, 26, 20, 19, 22, 23, 22, 25, 26, 26, 26, 27, 27,
+      26, 24, 25, 19, 21, 26, 27, 27, 26, 27, 24, 21, 21, 26, 26, 28, 27, 27, 27, 20, 24, 20, 21,
+      22, 21, 21, 23, 22, 22, 25, 25, 24, 24, 26, 23, 26, 27, 26, 26, 27, 27, 27, 27, 27, 28, 27,
+      27, 27, 27, 27, 26
+  };
+
+  private static final Huffman INSTANCE = new Huffman();
+
+  public static Huffman get() {
+    return INSTANCE;
+  }
+
+  private final Node root = new Node();
+
+  private Huffman() {
+    buildTree();
+  }
+
+  void encode(byte[] data, OutputStream out) throws IOException {
+    long current = 0;
+    int n = 0;
+
+    for (int i = 0; i < data.length; i++) {
+      int b = data[i] & 0xFF;
+      int code = CODES[b];
+      int nbits = CODE_LENGTHS[b];
+
+      current <<= nbits;
+      current |= code;
+      n += nbits;
+
+      while (n >= 8) {
+        n -= 8;
+        out.write(((int) (current >> n)));
+      }
+    }
+
+    if (n > 0) {
+      current <<= (8 - n);
+      current |= (0xFF >>> n);
+      out.write((int) current);
+    }
+  }
+
+  int encodedLength(byte[] bytes) {
+    long len = 0;
+
+    for (int i = 0; i < bytes.length; i++) {
+      int b = bytes[i] & 0xFF;
+      len += CODE_LENGTHS[b];
+    }
+
+    return (int) ((len + 7) >> 3);
+  }
+
+  byte[] decode(byte[] buf) throws IOException {
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    Node node = root;
+    int current = 0;
+    int nbits = 0;
+    for (int i = 0; i < buf.length; i++) {
+      int b = buf[i] & 0xFF;
+      current = (current << 8) | b;
+      nbits += 8;
+      while (nbits >= 8) {
+        int c = (current >>> (nbits - 8)) & 0xFF;
+        node = node.children[c];
+        if (node.children == null) {
+          // terminal node
+          baos.write(node.symbol);
+          nbits -= node.terminalBits;
+          node = root;
+        } else {
+          // non-terminal node
+          nbits -= 8;
+        }
+      }
+    }
+
+    while (nbits > 0) {
+      int c = (current << (8 - nbits)) & 0xFF;
+      node = node.children[c];
+      if (node.children != null || node.terminalBits > nbits) {
+        break;
+      }
+      baos.write(node.symbol);
+      nbits -= node.terminalBits;
+      node = root;
+    }
+
+    return baos.toByteArray();
+  }
+
+  private void buildTree() {
+    for (int i = 0; i < CODE_LENGTHS.length; i++) {
+      addCode(i, CODES[i], CODE_LENGTHS[i]);
+    }
+  }
+
+  @SuppressWarnings("NarrowingCompoundAssignment")
+  private void addCode(int sym, int code, byte len) {
+    Node terminal = new Node(sym, len);
+
+    Node current = root;
+    while (len > 8) {
+      len -= 8;
+      int i = ((code >>> len) & 0xFF);
+      if (current.children == null) {
+        throw new IllegalStateException("invalid dictionary: prefix not unique");
+      }
+      if (current.children[i] == null) {
+        current.children[i] = new Node();
+      }
+      current = current.children[i];
+    }
+
+    int shift = 8 - len;
+    int start = (code << shift) & 0xFF;
+    int end = 1 << shift;
+    for (int i = start; i < start + end; i++) {
+      current.children[i] = terminal;
+    }
+  }
+
+  private static final class Node {
+
+    // Null if terminal.
+    private final Node[] children;
+
+    // Terminal nodes have a symbol.
+    private final int symbol;
+
+    // Number of bits represented in the terminal node.
+    private final int terminalBits;
+
+    /** Construct an internal node. */
+    Node() {
+      this.children = new Node[256];
+      this.symbol = 0; // Not read.
+      this.terminalBits = 0; // Not read.
+    }
+
+    /**
+     * Construct a terminal node.
+     *
+     * @param symbol symbol the node represents
+     * @param bits length of Huffman code in bits
+     */
+    Node(int symbol, int bits) {
+      this.children = null;
+      this.symbol = symbol;
+      int b = bits & 0x07;
+      this.terminalBits = b == 0 ? 8 : b;
+    }
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Settings.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Settings.java
new file mode 100644
index 0000000..0d0ecce
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Settings.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2012 Square, Inc.
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal.framed;
+
+import java.util.Arrays;
+
+/**
+ * Settings describe characteristics of the sending peer, which are used by the receiving peer.
+ */
+public final class Settings {
+  /**
+   * From the SPDY/3 and HTTP/2 specs, the default initial window size for all
+   * streams is 64 KiB. (Chrome 25 uses 10 MiB).
+   */
+  static final int DEFAULT_INITIAL_WINDOW_SIZE = 64 * 1024;
+
+  /** Peer request to clear durable settings. */
+  static final int FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x1;
+
+  /** Sent by servers only. The peer requests this setting persisted for future connections. */
+  static final int PERSIST_VALUE = 0x1;
+  /** Sent by clients only. The client is reminding the server of a persisted value. */
+  static final int PERSISTED = 0x2;
+
+  /** spdy/3: Sender's estimate of max incoming kbps. */
+  static final int UPLOAD_BANDWIDTH = 1;
+  /** HTTP/2: Size in bytes of the table used to decode the sender's header blocks. */
+  static final int HEADER_TABLE_SIZE = 1;
+  /** spdy/3: Sender's estimate of max outgoing kbps. */
+  static final int DOWNLOAD_BANDWIDTH = 2;
+  /** HTTP/2: The peer must not send a PUSH_PROMISE frame when this is 0. */
+  static final int ENABLE_PUSH = 2;
+  /** spdy/3: Sender's estimate of millis between sending a request and receiving a response. */
+  static final int ROUND_TRIP_TIME = 3;
+  /** Sender's maximum number of concurrent streams. */
+  public static final int MAX_CONCURRENT_STREAMS = 4;
+  /** spdy/3: Current CWND in Packets. */
+  static final int CURRENT_CWND = 5;
+  /** HTTP/2: Size in bytes of the largest frame payload the sender will accept. */
+  static final int MAX_FRAME_SIZE = 5;
+  /** spdy/3: Retransmission rate. Percentage */
+  static final int DOWNLOAD_RETRANS_RATE = 6;
+  /** HTTP/2: Advisory only. Size in bytes of the largest header list the sender will accept. */
+  static final int MAX_HEADER_LIST_SIZE = 6;
+  /** Window size in bytes. */
+  public static final int INITIAL_WINDOW_SIZE = 7;
+  /** spdy/3: Size of the client certificate vector. Unsupported. */
+  static final int CLIENT_CERTIFICATE_VECTOR_SIZE = 8;
+  /** Flow control options. */
+  static final int FLOW_CONTROL_OPTIONS = 10;
+
+  /** Total number of settings. */
+  static final int COUNT = 10;
+
+  /** If set, flow control is disabled for streams directed to the sender of these settings. */
+  static final int FLOW_CONTROL_OPTIONS_DISABLED = 0x1;
+
+  /** Bitfield of which flags that values. */
+  private int set;
+
+  /** Bitfield of flags that have {@link #PERSIST_VALUE}. */
+  private int persistValue;
+
+  /** Bitfield of flags that have {@link #PERSISTED}. */
+  private int persisted;
+
+  /** Flag values. */
+  private final int[] values = new int[COUNT];
+
+  void clear() {
+    set = persistValue = persisted = 0;
+    Arrays.fill(values, 0);
+  }
+
+  public Settings set(int id, int idFlags, int value) {
+    if (id >= values.length) {
+      return this; // Discard unknown settings.
+    }
+
+    int bit = 1 << id;
+    set |= bit;
+    if ((idFlags & PERSIST_VALUE) != 0) {
+      persistValue |= bit;
+    } else {
+      persistValue &= ~bit;
+    }
+    if ((idFlags & PERSISTED) != 0) {
+      persisted |= bit;
+    } else {
+      persisted &= ~bit;
+    }
+
+    values[id] = value;
+    return this;
+  }
+
+  /** Returns true if a value has been assigned for the setting {@code id}. */
+  public boolean isSet(int id) {
+    int bit = 1 << id;
+    return (set & bit) != 0;
+  }
+
+  /** Returns the value for the setting {@code id}, or 0 if unset. */
+  public int get(int id) {
+    return values[id];
+  }
+
+  /** Returns the flags for the setting {@code id}, or 0 if unset. */
+  int flags(int id) {
+    int result = 0;
+    if (isPersisted(id)) result |= Settings.PERSISTED;
+    if (persistValue(id)) result |= Settings.PERSIST_VALUE;
+    return result;
+  }
+
+  /** Returns the number of settings that have values assigned. */
+  int size() {
+    return Integer.bitCount(set);
+  }
+
+  /** spdy/3 only. */
+  int getUploadBandwidth(int defaultValue) {
+    int bit = 1 << UPLOAD_BANDWIDTH;
+    return (bit & set) != 0 ? values[UPLOAD_BANDWIDTH] : defaultValue;
+  }
+
+  /** HTTP/2 only. Returns -1 if unset. */
+  int getHeaderTableSize() {
+    int bit = 1 << HEADER_TABLE_SIZE;
+    return (bit & set) != 0 ? values[HEADER_TABLE_SIZE] : -1;
+  }
+
+  /** spdy/3 only. */
+  int getDownloadBandwidth(int defaultValue) {
+    int bit = 1 << DOWNLOAD_BANDWIDTH;
+    return (bit & set) != 0 ? values[DOWNLOAD_BANDWIDTH] : defaultValue;
+  }
+
+  /** HTTP/2 only. */
+  // TODO: honor this setting in HTTP/2.
+  boolean getEnablePush(boolean defaultValue) {
+    int bit = 1 << ENABLE_PUSH;
+    return ((bit & set) != 0 ? values[ENABLE_PUSH] : defaultValue ? 1 : 0) == 1;
+  }
+
+  /** spdy/3 only. */
+  int getRoundTripTime(int defaultValue) {
+    int bit = 1 << ROUND_TRIP_TIME;
+    return (bit & set) != 0 ? values[ROUND_TRIP_TIME] : defaultValue;
+  }
+
+  // TODO: honor this setting in spdy/3 and HTTP/2.
+  int getMaxConcurrentStreams(int defaultValue) {
+    int bit = 1 << MAX_CONCURRENT_STREAMS;
+    return (bit & set) != 0 ? values[MAX_CONCURRENT_STREAMS] : defaultValue;
+  }
+
+  /** spdy/3 only. */
+  int getCurrentCwnd(int defaultValue) {
+    int bit = 1 << CURRENT_CWND;
+    return (bit & set) != 0 ? values[CURRENT_CWND] : defaultValue;
+  }
+
+  /** HTTP/2 only. */
+  int getMaxFrameSize(int defaultValue) {
+    int bit = 1 << MAX_FRAME_SIZE;
+    return (bit & set) != 0 ? values[MAX_FRAME_SIZE] : defaultValue;
+  }
+
+  /** spdy/3 only. */
+  int getDownloadRetransRate(int defaultValue) {
+    int bit = 1 << DOWNLOAD_RETRANS_RATE;
+    return (bit & set) != 0 ? values[DOWNLOAD_RETRANS_RATE] : defaultValue;
+  }
+
+  /** HTTP/2 only. */
+  int getMaxHeaderListSize(int defaultValue) {
+    int bit = 1 << MAX_HEADER_LIST_SIZE;
+    return (bit & set) != 0 ? values[MAX_HEADER_LIST_SIZE] : defaultValue;
+  }
+
+  int getInitialWindowSize(int defaultValue) {
+    int bit = 1 << INITIAL_WINDOW_SIZE;
+    return (bit & set) != 0 ? values[INITIAL_WINDOW_SIZE] : defaultValue;
+  }
+
+  /** spdy/3 only. */
+  int getClientCertificateVectorSize(int defaultValue) {
+    int bit = 1 << CLIENT_CERTIFICATE_VECTOR_SIZE;
+    return (bit & set) != 0 ? values[CLIENT_CERTIFICATE_VECTOR_SIZE] : defaultValue;
+  }
+
+  // TODO: honor this setting in spdy/3 and HTTP/2.
+  boolean isFlowControlDisabled() {
+    int bit = 1 << FLOW_CONTROL_OPTIONS;
+    int value = (bit & set) != 0 ? values[FLOW_CONTROL_OPTIONS] : 0;
+    return (value & FLOW_CONTROL_OPTIONS_DISABLED) != 0;
+  }
+
+  /**
+   * Returns true if this user agent should use this setting in future spdy/3
+   * connections to the same host.
+   */
+  boolean persistValue(int id) {
+    int bit = 1 << id;
+    return (persistValue & bit) != 0;
+  }
+
+  /** Returns true if this setting was persisted. */
+  boolean isPersisted(int id) {
+    int bit = 1 << id;
+    return (persisted & bit) != 0;
+  }
+
+  /**
+   * Writes {@code other} into this. If any setting is populated by this and
+   * {@code other}, the value and flags from {@code other} will be kept.
+   */
+  void merge(Settings other) {
+    for (int i = 0; i < COUNT; i++) {
+      if (!other.isSet(i)) continue;
+      set(i, other.flags(i), other.get(i));
+    }
+  }
+}
diff --git a/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Variant.java b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Variant.java
new file mode 100644
index 0000000..2b70708
--- /dev/null
+++ b/okhttp/third_party/okhttp/main/java/io/grpc/okhttp/internal/framed/Variant.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed 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.
+ */
+/*
+ * Forked from OkHttp 2.5.0
+ */
+
+package io.grpc.okhttp.internal.framed;
+
+import io.grpc.okhttp.internal.Protocol;
+import okio.BufferedSink;
+import okio.BufferedSource;
+
+/** A version and dialect of the framed socket protocol. */
+public interface Variant {
+
+  /** The protocol as selected using ALPN. */
+  Protocol getProtocol();
+
+  /**
+   * @param client true if this is the HTTP client's reader, reading frames from a server.
+   */
+  FrameReader newReader(BufferedSource source, boolean client);
+
+  /**
+   * @param client true if this is the HTTP client's writer, writing frames to a server.
+   */
+  FrameWriter newWriter(BufferedSink sink, boolean client);
+}
diff --git a/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java b/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java
new file mode 100644
index 0000000..9c919e9
--- /dev/null
+++ b/okhttp/third_party/okhttp/test/java/io/grpc/okhttp/internal/framed/HpackTest.java
@@ -0,0 +1,754 @@
+/*
+ * Copyright (C) 2013 Square, Inc.
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.okhttp.internal.framed;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+import okio.Buffer;
+import okio.ByteString;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static okio.ByteString.decodeHex;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+@RunWith(JUnit4.class)
+public class HpackTest {
+
+  private static List<Header> headerEntries(String... elements) {
+    List<Header> result = new ArrayList<>(elements.length / 2);
+    for (int i = 0; i < elements.length; i += 2) {
+      result.add(new Header(elements[i], elements[i + 1]));
+    }
+    return result;
+  }
+
+  private final Buffer bytesIn = new Buffer();
+  private Hpack.Reader hpackReader;
+  private Buffer bytesOut = new Buffer();
+  private Hpack.Writer hpackWriter;
+
+  @Before public void reset() {
+    hpackReader = newReader(bytesIn);
+    hpackWriter = new Hpack.Writer(bytesOut);
+  }
+
+  /**
+   * Variable-length quantity special cases strings which are longer than 127
+   * bytes.  Values such as cookies can be 4KiB, and should be possible to send.
+   *
+   * <p> http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-5.2
+   */
+  @Test public void largeHeaderValue() throws IOException {
+    char[] value = new char[4096];
+    Arrays.fill(value, '!');
+    List<Header> headerBlock = headerEntries("cookie", new String(value));
+
+    hpackWriter.writeHeaders(headerBlock);
+    bytesIn.writeAll(bytesOut);
+    hpackReader.readHeaders();
+
+    assertEquals(0, hpackReader.dynamicTableHeaderCount);
+
+    assertEquals(headerBlock, hpackReader.getAndResetHeaderList());
+  }
+
+  /**
+   * HPACK has a max header table size, which can be smaller than the max header message.
+   * Ensure the larger header content is not lost.
+   */
+  @Test public void tooLargeToHPackIsStillEmitted() throws IOException {
+    bytesIn.writeByte(0x00); // Literal indexed
+    bytesIn.writeByte(0x0a); // Literal name (len = 10)
+    bytesIn.writeUtf8("custom-key");
+
+    bytesIn.writeByte(0x0d); // Literal value (len = 13)
+    bytesIn.writeUtf8("custom-header");
+
+    hpackReader.headerTableSizeSetting(1);
+    hpackReader.readHeaders();
+
+    assertEquals(0, hpackReader.dynamicTableHeaderCount);
+
+    assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndResetHeaderList());
+  }
+
+  @Test public void setMaxDynamicTableToZeroDoesNotClearHeaderList() throws IOException {
+    bytesIn.writeByte(0x40); // Literal indexed
+    bytesIn.writeByte(0x0a); // Literal name (len = 10)
+    bytesIn.writeUtf8("custom-key");
+
+    bytesIn.writeByte(0x0d); // Literal value (len = 13)
+    bytesIn.writeUtf8("custom-header");
+
+    bytesIn.writeByte(0x20); // Set max table size to 0
+
+    hpackReader.readHeaders();
+
+    assertEquals(0, hpackReader.maxDynamicTableByteCount());
+    assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndResetHeaderList());
+  }
+
+  /** Oldest entries are evicted to support newer ones. */
+  @Test public void testEviction() throws IOException {
+    bytesIn.writeByte(0x40); // Literal indexed
+    bytesIn.writeByte(0x0a); // Literal name (len = 10)
+    bytesIn.writeUtf8("custom-foo");
+
+    bytesIn.writeByte(0x0d); // Literal value (len = 13)
+    bytesIn.writeUtf8("custom-header");
+
+    bytesIn.writeByte(0x40); // Literal indexed
+    bytesIn.writeByte(0x0a); // Literal name (len = 10)
+    bytesIn.writeUtf8("custom-bar");
+
+    bytesIn.writeByte(0x0d); // Literal value (len = 13)
+    bytesIn.writeUtf8("custom-header");
+
+    bytesIn.writeByte(0x40); // Literal indexed
+    bytesIn.writeByte(0x0a); // Literal name (len = 10)
+    bytesIn.writeUtf8("custom-baz");
+
+    bytesIn.writeByte(0x0d); // Literal value (len = 13)
+    bytesIn.writeUtf8("custom-header");
+
+    // Set to only support 110 bytes (enough for 2 headers).
+    hpackReader.headerTableSizeSetting(110);
+    hpackReader.readHeaders();
+
+    assertEquals(2, hpackReader.dynamicTableHeaderCount);
+
+    Header entry = hpackReader.dynamicTable[headerTableLength() - 1];
+    checkEntry(entry, "custom-bar", "custom-header", 55);
+
+    entry = hpackReader.dynamicTable[headerTableLength() - 2];
+    checkEntry(entry, "custom-baz", "custom-header", 55);
+
+    // Once a header field is decoded and added to the reconstructed header
+    // list, it cannot be removed from it. Hence, foo is here.
+    assertEquals(
+        headerEntries(
+            "custom-foo", "custom-header",
+            "custom-bar", "custom-header",
+            "custom-baz", "custom-header"),
+        hpackReader.getAndResetHeaderList());
+
+    // Simulate receiving a small settings frame, that implies eviction.
+    hpackReader.headerTableSizeSetting(55);
+    assertEquals(1, hpackReader.dynamicTableHeaderCount);
+  }
+
+  /** Header table backing array is initially 8 long, let's ensure it grows. */
+  @Test public void dynamicallyGrowsBeyond64Entries() throws IOException {
+    for (int i = 0; i < 256; i++) {
+      bytesIn.writeByte(0x40); // Literal indexed
+      bytesIn.writeByte(0x0a); // Literal name (len = 10)
+      bytesIn.writeUtf8("custom-foo");
+
+      bytesIn.writeByte(0x0d); // Literal value (len = 13)
+      bytesIn.writeUtf8("custom-header");
+    }
+
+    hpackReader.headerTableSizeSetting(16384); // Lots of headers need more room!
+    hpackReader.readHeaders();
+
+    assertEquals(256, hpackReader.dynamicTableHeaderCount);
+  }
+
+  @Test public void huffmanDecodingSupported() throws IOException {
+    bytesIn.writeByte(0x44); // == Literal indexed ==
+    // Indexed name (idx = 4) -> :path
+    bytesIn.writeByte(0x8c); // Literal value Huffman encoded 12 bytes
+    // decodes to www.example.com which is length 15
+    bytesIn.write(decodeHex("f1e3c2e5f23a6ba0ab90f4ff"));
+
+    hpackReader.readHeaders();
+
+    assertEquals(1, hpackReader.dynamicTableHeaderCount);
+    assertEquals(52, hpackReader.dynamicTableByteCount);
+
+    Header entry = hpackReader.dynamicTable[headerTableLength() - 1];
+    checkEntry(entry, ":path", "www.example.com", 52);
+  }
+
+  /**
+   * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.2.1
+   */
+  @Test public void readLiteralHeaderFieldWithIndexing() throws IOException {
+    bytesIn.writeByte(0x40); // Literal indexed
+    bytesIn.writeByte(0x0a); // Literal name (len = 10)
+    bytesIn.writeUtf8("custom-key");
+
+    bytesIn.writeByte(0x0d); // Literal value (len = 13)
+    bytesIn.writeUtf8("custom-header");
+
+    hpackReader.readHeaders();
+
+    assertEquals(1, hpackReader.dynamicTableHeaderCount);
+    assertEquals(55, hpackReader.dynamicTableByteCount);
+
+    Header entry = hpackReader.dynamicTable[headerTableLength() - 1];
+    checkEntry(entry, "custom-key", "custom-header", 55);
+
+    assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndResetHeaderList());
+  }
+
+  /**
+   * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.2.2
+   */
+  @Test public void literalHeaderFieldWithoutIndexingIndexedName() throws IOException {
+    List<Header> headerBlock = headerEntries(":path", "/sample/path");
+
+    bytesIn.writeByte(0x04); // == Literal not indexed ==
+    // Indexed name (idx = 4) -> :path
+    bytesIn.writeByte(0x0c); // Literal value (len = 12)
+    bytesIn.writeUtf8("/sample/path");
+
+    hpackWriter.writeHeaders(headerBlock);
+    assertEquals(bytesIn, bytesOut);
+
+    hpackReader.readHeaders();
+
+    assertEquals(0, hpackReader.dynamicTableHeaderCount);
+
+    assertEquals(headerBlock, hpackReader.getAndResetHeaderList());
+  }
+
+  @Test public void literalHeaderFieldWithoutIndexingNewName() throws IOException {
+    List<Header> headerBlock = headerEntries("custom-key", "custom-header");
+
+    bytesIn.writeByte(0x00); // Not indexed
+    bytesIn.writeByte(0x0a); // Literal name (len = 10)
+    bytesIn.writeUtf8("custom-key");
+
+    bytesIn.writeByte(0x0d); // Literal value (len = 13)
+    bytesIn.writeUtf8("custom-header");
+
+    hpackWriter.writeHeaders(headerBlock);
+    assertEquals(bytesIn, bytesOut);
+
+    hpackReader.readHeaders();
+
+    assertEquals(0, hpackReader.dynamicTableHeaderCount);
+
+    assertEquals(headerBlock, hpackReader.getAndResetHeaderList());
+  }
+
+  @Test public void literalHeaderFieldNeverIndexedIndexedName() throws IOException {
+    bytesIn.writeByte(0x14); // == Literal never indexed ==
+    // Indexed name (idx = 4) -> :path
+    bytesIn.writeByte(0x0c); // Literal value (len = 12)
+    bytesIn.writeUtf8("/sample/path");
+
+    hpackReader.readHeaders();
+
+    assertEquals(0, hpackReader.dynamicTableHeaderCount);
+
+    assertEquals(headerEntries(":path", "/sample/path"), hpackReader.getAndResetHeaderList());
+  }
+
+  @Test public void literalHeaderFieldNeverIndexedNewName() throws IOException {
+    bytesIn.writeByte(0x10); // Never indexed
+    bytesIn.writeByte(0x0a); // Literal name (len = 10)
+    bytesIn.writeUtf8("custom-key");
+
+    bytesIn.writeByte(0x0d); // Literal value (len = 13)
+    bytesIn.writeUtf8("custom-header");
+
+    hpackReader.readHeaders();
+
+    assertEquals(0, hpackReader.dynamicTableHeaderCount);
+
+    assertEquals(headerEntries("custom-key", "custom-header"), hpackReader.getAndResetHeaderList());
+  }
+
+  @Test public void staticHeaderIsNotCopiedIntoTheIndexedTable() throws IOException {
+    bytesIn.writeByte(0x82); // == Indexed - Add ==
+    // idx = 2 -> :method: GET
+
+    hpackReader.readHeaders();
+
+    assertEquals(0, hpackReader.dynamicTableHeaderCount);
+    assertEquals(0, hpackReader.dynamicTableByteCount);
+
+    assertEquals(null, hpackReader.dynamicTable[headerTableLength() - 1]);
+
+    assertEquals(headerEntries(":method", "GET"), hpackReader.getAndResetHeaderList());
+  }
+
+  // Example taken from twitter/hpack DecoderTest.testUnusedIndex
+  @Test public void readIndexedHeaderFieldIndex0() throws IOException {
+    bytesIn.writeByte(0x80); // == Indexed - Add idx = 0
+
+    try {
+      hpackReader.readHeaders();
+      fail();
+    } catch (IOException e) {
+      assertEquals("index == 0", e.getMessage());
+    }
+  }
+
+  // Example taken from twitter/hpack DecoderTest.testIllegalIndex
+  @Test public void readIndexedHeaderFieldTooLargeIndex() throws IOException {
+    bytesIn.writeShort(0xff00); // == Indexed - Add idx = 127
+
+    try {
+      hpackReader.readHeaders();
+      fail();
+    } catch (IOException e) {
+      assertEquals("Header index too large 127", e.getMessage());
+    }
+  }
+
+  // Example taken from twitter/hpack DecoderTest.testInsidiousIndex
+  @Test public void readIndexedHeaderFieldInsidiousIndex() throws IOException {
+    bytesIn.writeByte(0xff); // == Indexed - Add ==
+    bytesIn.write(decodeHex("8080808008")); // idx = -2147483521
+
+    try {
+      hpackReader.readHeaders();
+      fail();
+    } catch (IOException e) {
+      assertEquals("Header index too large -2147483521", e.getMessage());
+    }
+  }
+
+  // Example taken from twitter/hpack DecoderTest.testHeaderTableSizeUpdate
+  @Test public void minMaxHeaderTableSize() throws IOException {
+    bytesIn.writeByte(0x20);
+    hpackReader.readHeaders();
+
+    assertEquals(0, hpackReader.maxDynamicTableByteCount());
+
+    bytesIn.writeByte(0x3f); // encode size 4096
+    bytesIn.writeByte(0xe1);
+    bytesIn.writeByte(0x1f);
+    hpackReader.readHeaders();
+
+    assertEquals(4096, hpackReader.maxDynamicTableByteCount());
+  }
+
+  // Example taken from twitter/hpack DecoderTest.testIllegalHeaderTableSizeUpdate
+  @Test public void cannotSetTableSizeLargerThanSettingsValue() throws IOException {
+    bytesIn.writeByte(0x3f); // encode size 4097
+    bytesIn.writeByte(0xe2);
+    bytesIn.writeByte(0x1f);
+
+    try {
+      hpackReader.readHeaders();
+      fail();
+    } catch (IOException e) {
+      assertEquals("Invalid dynamic table size update 4097", e.getMessage());
+    }
+  }
+
+  // Example taken from twitter/hpack DecoderTest.testInsidiousMaxHeaderSize
+  @Test public void readHeaderTableStateChangeInsidiousMaxHeaderByteCount() throws IOException {
+    bytesIn.writeByte(0x3f);
+    bytesIn.write(decodeHex("e1ffffff07")); // count = -2147483648
+
+    try {
+      hpackReader.readHeaders();
+      fail();
+    } catch (IOException e) {
+      assertEquals("Invalid dynamic table size update -2147483648", e.getMessage());
+    }
+  }
+
+  /**
+   * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.2.4
+   */
+  @Test public void readIndexedHeaderFieldFromStaticTableWithoutBuffering() throws IOException {
+    bytesIn.writeByte(0x82); // == Indexed - Add ==
+    // idx = 2 -> :method: GET
+
+    hpackReader.headerTableSizeSetting(0); // SETTINGS_HEADER_TABLE_SIZE == 0
+    hpackReader.readHeaders();
+
+    // Not buffered in header table.
+    assertEquals(0, hpackReader.dynamicTableHeaderCount);
+
+    assertEquals(headerEntries(":method", "GET"), hpackReader.getAndResetHeaderList());
+  }
+
+  /**
+   * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.2
+   */
+  @Test public void readRequestExamplesWithoutHuffman() throws IOException {
+    firstRequestWithoutHuffman();
+    hpackReader.readHeaders();
+    checkReadFirstRequestWithoutHuffman();
+
+    secondRequestWithoutHuffman();
+    hpackReader.readHeaders();
+    checkReadSecondRequestWithoutHuffman();
+
+    thirdRequestWithoutHuffman();
+    hpackReader.readHeaders();
+    checkReadThirdRequestWithoutHuffman();
+  }
+
+  private void firstRequestWithoutHuffman() {
+    bytesIn.writeByte(0x82); // == Indexed - Add ==
+    // idx = 2 -> :method: GET
+    bytesIn.writeByte(0x86); // == Indexed - Add ==
+    // idx = 7 -> :scheme: http
+    bytesIn.writeByte(0x84); // == Indexed - Add ==
+    // idx = 6 -> :path: /
+    bytesIn.writeByte(0x41); // == Literal indexed ==
+    // Indexed name (idx = 4) -> :authority
+    bytesIn.writeByte(0x0f); // Literal value (len = 15)
+    bytesIn.writeUtf8("www.example.com");
+  }
+
+  private void checkReadFirstRequestWithoutHuffman() {
+    assertEquals(1, hpackReader.dynamicTableHeaderCount);
+
+    // [  1] (s =  57) :authority: www.example.com
+    Header entry = hpackReader.dynamicTable[headerTableLength() - 1];
+    checkEntry(entry, ":authority", "www.example.com", 57);
+
+    // Table size: 57
+    assertEquals(57, hpackReader.dynamicTableByteCount);
+
+    // Decoded header list:
+    assertEquals(headerEntries(
+        ":method", "GET",
+        ":scheme", "http",
+        ":path", "/",
+        ":authority", "www.example.com"), hpackReader.getAndResetHeaderList());
+  }
+
+  private void secondRequestWithoutHuffman() {
+    bytesIn.writeByte(0x82); // == Indexed - Add ==
+    // idx = 2 -> :method: GET
+    bytesIn.writeByte(0x86); // == Indexed - Add ==
+    // idx = 7 -> :scheme: http
+    bytesIn.writeByte(0x84); // == Indexed - Add ==
+    // idx = 6 -> :path: /
+    bytesIn.writeByte(0xbe); // == Indexed - Add ==
+    // Indexed name (idx = 62) -> :authority: www.example.com
+    bytesIn.writeByte(0x58); // == Literal indexed ==
+    // Indexed name (idx = 24) -> cache-control
+    bytesIn.writeByte(0x08); // Literal value (len = 8)
+    bytesIn.writeUtf8("no-cache");
+  }
+
+  private void checkReadSecondRequestWithoutHuffman() {
+    assertEquals(2, hpackReader.dynamicTableHeaderCount);
+
+    // [  1] (s =  53) cache-control: no-cache
+    Header entry = hpackReader.dynamicTable[headerTableLength() - 2];
+    checkEntry(entry, "cache-control", "no-cache", 53);
+
+    // [  2] (s =  57) :authority: www.example.com
+    entry = hpackReader.dynamicTable[headerTableLength() - 1];
+    checkEntry(entry, ":authority", "www.example.com", 57);
+
+    // Table size: 110
+    assertEquals(110, hpackReader.dynamicTableByteCount);
+
+    // Decoded header list:
+    assertEquals(headerEntries(
+        ":method", "GET",
+        ":scheme", "http",
+        ":path", "/",
+        ":authority", "www.example.com",
+        "cache-control", "no-cache"), hpackReader.getAndResetHeaderList());
+  }
+
+  private void thirdRequestWithoutHuffman() {
+    bytesIn.writeByte(0x82); // == Indexed - Add ==
+    // idx = 2 -> :method: GET
+    bytesIn.writeByte(0x87); // == Indexed - Add ==
+    // idx = 7 -> :scheme: http
+    bytesIn.writeByte(0x85); // == Indexed - Add ==
+    // idx = 5 -> :path: /index.html
+    bytesIn.writeByte(0xbf); // == Indexed - Add ==
+    // Indexed name (idx = 63) -> :authority: www.example.com
+    bytesIn.writeByte(0x40); // Literal indexed
+    bytesIn.writeByte(0x0a); // Literal name (len = 10)
+    bytesIn.writeUtf8("custom-key");
+    bytesIn.writeByte(0x0c); // Literal value (len = 12)
+    bytesIn.writeUtf8("custom-value");
+  }
+
+  private void checkReadThirdRequestWithoutHuffman() {
+    assertEquals(3, hpackReader.dynamicTableHeaderCount);
+
+    // [  1] (s =  54) custom-key: custom-value
+    Header entry = hpackReader.dynamicTable[headerTableLength() - 3];
+    checkEntry(entry, "custom-key", "custom-value", 54);
+
+    // [  2] (s =  53) cache-control: no-cache
+    entry = hpackReader.dynamicTable[headerTableLength() - 2];
+    checkEntry(entry, "cache-control", "no-cache", 53);
+
+    // [  3] (s =  57) :authority: www.example.com
+    entry = hpackReader.dynamicTable[headerTableLength() - 1];
+    checkEntry(entry, ":authority", "www.example.com", 57);
+
+    // Table size: 164
+    assertEquals(164, hpackReader.dynamicTableByteCount);
+
+    // Decoded header list:
+    assertEquals(headerEntries(
+        ":method", "GET",
+        ":scheme", "https",
+        ":path", "/index.html",
+        ":authority", "www.example.com",
+        "custom-key", "custom-value"), hpackReader.getAndResetHeaderList());
+  }
+
+  /**
+   * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#appendix-C.4
+   */
+  @Test public void readRequestExamplesWithHuffman() throws IOException {
+    firstRequestWithHuffman();
+    hpackReader.readHeaders();
+    checkReadFirstRequestWithHuffman();
+
+    secondRequestWithHuffman();
+    hpackReader.readHeaders();
+    checkReadSecondRequestWithHuffman();
+
+    thirdRequestWithHuffman();
+    hpackReader.readHeaders();
+    checkReadThirdRequestWithHuffman();
+  }
+
+  private void firstRequestWithHuffman() {
+    bytesIn.writeByte(0x82); // == Indexed - Add ==
+    // idx = 2 -> :method: GET
+    bytesIn.writeByte(0x86); // == Indexed - Add ==
+    // idx = 6 -> :scheme: http
+    bytesIn.writeByte(0x84); // == Indexed - Add ==
+    // idx = 4 -> :path: /
+    bytesIn.writeByte(0x41); // == Literal indexed ==
+    // Indexed name (idx = 1) -> :authority
+    bytesIn.writeByte(0x8c); // Literal value Huffman encoded 12 bytes
+    // decodes to www.example.com which is length 15
+    bytesIn.write(decodeHex("f1e3c2e5f23a6ba0ab90f4ff"));
+  }
+
+  private void checkReadFirstRequestWithHuffman() {
+    assertEquals(1, hpackReader.dynamicTableHeaderCount);
+
+    // [  1] (s =  57) :authority: www.example.com
+    Header entry = hpackReader.dynamicTable[headerTableLength() - 1];
+    checkEntry(entry, ":authority", "www.example.com", 57);
+
+    // Table size: 57
+    assertEquals(57, hpackReader.dynamicTableByteCount);
+
+    // Decoded header list:
+    assertEquals(headerEntries(
+        ":method", "GET",
+        ":scheme", "http",
+        ":path", "/",
+        ":authority", "www.example.com"), hpackReader.getAndResetHeaderList());
+  }
+
+  private void secondRequestWithHuffman() {
+    bytesIn.writeByte(0x82); // == Indexed - Add ==
+    // idx = 2 -> :method: GET
+    bytesIn.writeByte(0x86); // == Indexed - Add ==
+    // idx = 6 -> :scheme: http
+    bytesIn.writeByte(0x84); // == Indexed - Add ==
+    // idx = 4 -> :path: /
+    bytesIn.writeByte(0xbe); // == Indexed - Add ==
+    // idx = 62 -> :authority: www.example.com
+    bytesIn.writeByte(0x58); // == Literal indexed ==
+    // Indexed name (idx = 24) -> cache-control
+    bytesIn.writeByte(0x86); // Literal value Huffman encoded 6 bytes
+    // decodes to no-cache which is length 8
+    bytesIn.write(decodeHex("a8eb10649cbf"));
+  }
+
+  private void checkReadSecondRequestWithHuffman() {
+    assertEquals(2, hpackReader.dynamicTableHeaderCount);
+
+    // [  1] (s =  53) cache-control: no-cache
+    Header entry = hpackReader.dynamicTable[headerTableLength() - 2];
+    checkEntry(entry, "cache-control", "no-cache", 53);
+
+    // [  2] (s =  57) :authority: www.example.com
+    entry = hpackReader.dynamicTable[headerTableLength() - 1];
+    checkEntry(entry, ":authority", "www.example.com", 57);
+
+    // Table size: 110
+    assertEquals(110, hpackReader.dynamicTableByteCount);
+
+    // Decoded header list:
+    assertEquals(headerEntries(
+        ":method", "GET",
+        ":scheme", "http",
+        ":path", "/",
+        ":authority", "www.example.com",
+        "cache-control", "no-cache"), hpackReader.getAndResetHeaderList());
+  }
+
+  private void thirdRequestWithHuffman() {
+    bytesIn.writeByte(0x82); // == Indexed - Add ==
+    // idx = 2 -> :method: GET
+    bytesIn.writeByte(0x87); // == Indexed - Add ==
+    // idx = 7 -> :scheme: https
+    bytesIn.writeByte(0x85); // == Indexed - Add ==
+    // idx = 5 -> :path: /index.html
+    bytesIn.writeByte(0xbf); // == Indexed - Add ==
+    // idx = 63 -> :authority: www.example.com
+    bytesIn.writeByte(0x40); // Literal indexed
+    bytesIn.writeByte(0x88); // Literal name Huffman encoded 8 bytes
+    // decodes to custom-key which is length 10
+    bytesIn.write(decodeHex("25a849e95ba97d7f"));
+    bytesIn.writeByte(0x89); // Literal value Huffman encoded 9 bytes
+    // decodes to custom-value which is length 12
+    bytesIn.write(decodeHex("25a849e95bb8e8b4bf"));
+  }
+
+  private void checkReadThirdRequestWithHuffman() {
+    assertEquals(3, hpackReader.dynamicTableHeaderCount);
+
+    // [  1] (s =  54) custom-key: custom-value
+    Header entry = hpackReader.dynamicTable[headerTableLength() - 3];
+    checkEntry(entry, "custom-key", "custom-value", 54);
+
+    // [  2] (s =  53) cache-control: no-cache
+    entry = hpackReader.dynamicTable[headerTableLength() - 2];
+    checkEntry(entry, "cache-control", "no-cache", 53);
+
+    // [  3] (s =  57) :authority: www.example.com
+    entry = hpackReader.dynamicTable[headerTableLength() - 1];
+    checkEntry(entry, ":authority", "www.example.com", 57);
+
+    // Table size: 164
+    assertEquals(164, hpackReader.dynamicTableByteCount);
+
+    // Decoded header list:
+    assertEquals(headerEntries(
+        ":method", "GET",
+        ":scheme", "https",
+        ":path", "/index.html",
+        ":authority", "www.example.com",
+        "custom-key", "custom-value"), hpackReader.getAndResetHeaderList());
+  }
+
+  @Test public void readSingleByteInt() throws IOException {
+    assertEquals(10, newReader(byteStream()).readInt(10, 31));
+    assertEquals(10, newReader(byteStream()).readInt(0xe0 | 10, 31));
+  }
+
+  @Test public void readMultibyteInt() throws IOException {
+    assertEquals(1337, newReader(byteStream(154, 10)).readInt(31, 31));
+  }
+
+  @Test public void writeSingleByteInt() throws IOException {
+    hpackWriter.writeInt(10, 31, 0);
+    assertBytes(10);
+    hpackWriter.writeInt(10, 31, 0xe0);
+    assertBytes(0xe0 | 10);
+  }
+
+  @Test public void writeMultibyteInt() throws IOException {
+    hpackWriter.writeInt(1337, 31, 0);
+    assertBytes(31, 154, 10);
+    hpackWriter.writeInt(1337, 31, 0xe0);
+    assertBytes(0xe0 | 31, 154, 10);
+  }
+
+  @Test public void max31BitValue() throws IOException {
+    hpackWriter.writeInt(0x7fffffff, 31, 0);
+    assertBytes(31, 224, 255, 255, 255, 7);
+    assertEquals(0x7fffffff,
+        newReader(byteStream(224, 255, 255, 255, 7)).readInt(31, 31));
+  }
+
+  @Test public void prefixMask() throws IOException {
+    hpackWriter.writeInt(31, 31, 0);
+    assertBytes(31, 0);
+    assertEquals(31, newReader(byteStream(0)).readInt(31, 31));
+  }
+
+  @Test public void prefixMaskMinusOne() throws IOException {
+    hpackWriter.writeInt(30, 31, 0);
+    assertBytes(30);
+    assertEquals(31, newReader(byteStream(0)).readInt(31, 31));
+  }
+
+  @Test public void zero() throws IOException {
+    hpackWriter.writeInt(0, 31, 0);
+    assertBytes(0);
+    assertEquals(0, newReader(byteStream()).readInt(0, 31));
+  }
+
+  @Test public void lowercaseHeaderNameBeforeEmit() throws IOException {
+    hpackWriter.writeHeaders(Arrays.asList(new Header("FoO", "BaR")));
+    assertBytes(0, 3, 'f', 'o', 'o', 3, 'B', 'a', 'R');
+  }
+
+  @Test public void mixedCaseHeaderNameIsMalformed() throws IOException {
+    try {
+      newReader(byteStream(0, 3, 'F', 'o', 'o', 3, 'B', 'a', 'R')).readHeaders();
+      fail();
+    } catch (IOException e) {
+      assertEquals("PROTOCOL_ERROR response malformed: mixed case name: Foo", e.getMessage());
+    }
+  }
+
+  @Test public void emptyHeaderName() throws IOException {
+    hpackWriter.writeByteString(ByteString.encodeUtf8(""));
+    assertBytes(0);
+    assertEquals(ByteString.EMPTY, newReader(byteStream(0)).readByteString());
+  }
+
+  private Hpack.Reader newReader(Buffer source) {
+    return new Hpack.Reader(4096, source);
+  }
+
+  private Buffer byteStream(int... bytes) {
+    return new Buffer().write(intArrayToByteArray(bytes));
+  }
+
+  private void checkEntry(Header entry, String name, String value, int size) {
+    assertEquals(name, entry.name.utf8());
+    assertEquals(value, entry.value.utf8());
+    assertEquals(size, entry.hpackSize);
+  }
+
+  private void assertBytes(int... bytes) throws IOException {
+    ByteString expected = intArrayToByteArray(bytes);
+    ByteString actual = bytesOut.readByteString();
+    assertEquals(expected, actual);
+  }
+
+  private ByteString intArrayToByteArray(int[] bytes) {
+    byte[] data = new byte[bytes.length];
+    for (int i = 0; i < bytes.length; i++) {
+      data[i] = (byte) bytes[i];
+    }
+    return ByteString.of(data);
+  }
+
+  private int headerTableLength() {
+    return hpackReader.dynamicTable.length;
+  }
+}
diff --git a/protobuf-lite/BUILD.bazel b/protobuf-lite/BUILD.bazel
new file mode 100644
index 0000000..cd97b07
--- /dev/null
+++ b/protobuf-lite/BUILD.bazel
@@ -0,0 +1,26 @@
+java_library(
+    name = "protobuf-lite",
+    srcs = glob([
+        "src/main/java/**/*.java",
+    ]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//core",
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_guava_guava//jar",
+    ] + select({
+        ":android": ["@com_google_protobuf_javalite//:protobuf_java_lite"],
+        "//conditions:default": ["@com_google_protobuf//:protobuf_java"],
+    }),
+)
+
+# This config is not fully-reliable. If it breaks, it is probably because you
+# are changing --android_crosstool_top. Instead of doing that, you can bind
+# your own toolchain on top of the default android/crosstool, as mentioned at
+# https://github.com/bazelbuild/bazel/issues/3924#issuecomment-338704582
+config_setting(
+    name = "android",
+    values = {
+        "crosstool_top": "//external:android/crosstool",
+    },
+)
diff --git a/protobuf-lite/build.gradle b/protobuf-lite/build.gradle
new file mode 100644
index 0000000..ead5180
--- /dev/null
+++ b/protobuf-lite/build.gradle
@@ -0,0 +1,58 @@
+buildscript {
+    repositories {
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+        mavenLocal()
+    }
+    dependencies { classpath libraries.protobuf_plugin }
+}
+
+apply plugin: 'com.google.protobuf'
+
+description = 'gRPC: Protobuf Lite'
+
+dependencies {
+    compile project(':grpc-core'),
+            libraries.protobuf_lite,
+            libraries.guava
+
+    testProtobuf libraries.protobuf
+
+    signature "org.codehaus.mojo.signature:java17:1.0@signature"
+    signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
+}
+
+compileTestJava {
+    // Protobuf-generated Lite produces quite a few warnings.
+    options.compilerArgs += [
+        "-Xlint:-rawtypes",
+        "-Xlint:-unchecked",
+        "-Xlint:-fallthrough",
+        "-XepExcludedPaths:.*/build/generated/source/proto/.*"
+    ]
+}
+
+protobuf {
+    protoc {
+        if (project.hasProperty('protoc')) {
+            path = project.protoc
+        } else {
+            artifact = "com.google.protobuf:protoc:${protocVersion}"
+        }
+    }
+    plugins {
+        javalite {
+            if (project.hasProperty('protoc-gen-javalite')) {
+                path = project['protoc-gen-javalite']
+            } else {
+                artifact = libraries.protoc_lite
+            }
+        }
+    }
+    generateProtoTasks {
+        ofSourceSet('test')*.each { task ->
+            task.builtins { remove java }
+            task.plugins { javalite {} }
+        }
+    }
+}
diff --git a/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoInputStream.java b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoInputStream.java
new file mode 100644
index 0000000..27ec32a
--- /dev/null
+++ b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoInputStream.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf.lite;
+
+import com.google.protobuf.CodedOutputStream;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+import io.grpc.Drainable;
+import io.grpc.KnownLength;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import javax.annotation.Nullable;
+
+/**
+ * An {@link InputStream} backed by a protobuf.
+ */
+final class ProtoInputStream extends InputStream implements Drainable, KnownLength {
+
+  // ProtoInputStream is first initialized with a *message*. *partial* is initially null.
+  // Once there has been a read operation on this stream, *message* is serialized to *partial* and
+  // set to null.
+  @Nullable private MessageLite message;
+  private final Parser<?> parser;
+  @Nullable private ByteArrayInputStream partial;
+
+  ProtoInputStream(MessageLite message, Parser<?> parser) {
+    this.message = message;
+    this.parser = parser;
+  }
+
+  @Override
+  public int drainTo(OutputStream target) throws IOException {
+    int written;
+    if (message != null) {
+      written = message.getSerializedSize();
+      message.writeTo(target);
+      message = null;
+    } else if (partial != null) {
+      written = (int) ProtoLiteUtils.copy(partial, target);
+      partial = null;
+    } else {
+      written = 0;
+    }
+    return written;
+  }
+
+  @Override
+  public int read() throws IOException {
+    if (message != null) {
+      partial = new ByteArrayInputStream(message.toByteArray());
+      message = null;
+    }
+    if (partial != null) {
+      return partial.read();
+    }
+    return -1;
+  }
+
+  @Override
+  public int read(byte[] b, int off, int len) throws IOException {
+    if (message != null) {
+      int size = message.getSerializedSize();
+      if (size == 0) {
+        message = null;
+        partial = null;
+        return -1;
+      }
+      if (len >= size) {
+        // This is the only case that is zero-copy.
+        CodedOutputStream stream = CodedOutputStream.newInstance(b, off, size);
+        message.writeTo(stream);
+        stream.flush();
+        stream.checkNoSpaceLeft();
+
+        message = null;
+        partial = null;
+        return size;
+      }
+
+      partial = new ByteArrayInputStream(message.toByteArray());
+      message = null;
+    }
+    if (partial != null) {
+      return partial.read(b, off, len);
+    }
+    return -1;
+  }
+
+  @Override
+  public int available() {
+    if (message != null) {
+      return message.getSerializedSize();
+    } else if (partial != null) {
+      return partial.available();
+    }
+    return 0;
+  }
+
+  MessageLite message() {
+    if (message == null) {
+      throw new IllegalStateException("message not available");
+    }
+    return message;
+  }
+
+  Parser<?> parser() {
+    return parser;
+  }
+}
diff --git a/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java
new file mode 100644
index 0000000..402105f
--- /dev/null
+++ b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/ProtoLiteUtils.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf.lite;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.ExtensionRegistryLite;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+import io.grpc.ExperimentalApi;
+import io.grpc.KnownLength;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.MethodDescriptor.PrototypeMarshaller;
+import io.grpc.Status;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+
+/**
+ * Utility methods for using protobuf with grpc.
+ */
+@ExperimentalApi("Experimental until Lite is stable in protobuf")
+public final class ProtoLiteUtils {
+
+  // default visibility to avoid synthetic accessors
+  static volatile ExtensionRegistryLite globalRegistry =
+      ExtensionRegistryLite.getEmptyRegistry();
+
+  private static final int BUF_SIZE = 8192;
+
+  /**
+   * The same value as {@link io.grpc.internal.GrpcUtil#DEFAULT_MAX_MESSAGE_SIZE}.
+   */
+  @VisibleForTesting
+  static final int DEFAULT_MAX_MESSAGE_SIZE = 4 * 1024 * 1024;
+
+  /**
+   * Sets the global registry for proto marshalling shared across all servers and clients.
+   *
+   * <p>Warning:  This API will likely change over time.  It is not possible to have separate
+   * registries per Process, Server, Channel, Service, or Method.  This is intentional until there
+   * is a more appropriate API to set them.
+   *
+   * <p>Warning:  Do NOT modify the extension registry after setting it.  It is thread safe to call
+   * {@link #setExtensionRegistry}, but not to modify the underlying object.
+   *
+   * <p>If you need custom parsing behavior for protos, you will need to make your own
+   * {@code MethodDescriptor.Marshaller} for the time being.
+   *
+   * @since 1.0.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1787")
+  public static void setExtensionRegistry(ExtensionRegistryLite newRegistry) {
+    globalRegistry = checkNotNull(newRegistry, "newRegistry");
+  }
+
+  /**
+   * Creates a {@link Marshaller} for protos of the same type as {@code defaultInstance}.
+   *
+   * @since 1.0.0
+   */
+  public static <T extends MessageLite> Marshaller<T> marshaller(T defaultInstance) {
+    // TODO(ejona): consider changing return type to PrototypeMarshaller (assuming ABI safe)
+    return new MessageMarshaller<T>(defaultInstance);
+  }
+
+  /**
+   * Produce a metadata marshaller for a protobuf type.
+   *
+   * @since 1.0.0
+   */
+  public static <T extends MessageLite> Metadata.BinaryMarshaller<T> metadataMarshaller(
+      T defaultInstance) {
+    return new MetadataMarshaller<T>(defaultInstance);
+  }
+
+  /** Copies the data from input stream to output stream. */
+  static long copy(InputStream from, OutputStream to) throws IOException {
+    // Copied from guava com.google.common.io.ByteStreams because its API is unstable (beta)
+    checkNotNull(from);
+    checkNotNull(to);
+    byte[] buf = new byte[BUF_SIZE];
+    long total = 0;
+    while (true) {
+      int r = from.read(buf);
+      if (r == -1) {
+        break;
+      }
+      to.write(buf, 0, r);
+      total += r;
+    }
+    return total;
+  }
+
+  private ProtoLiteUtils() {
+  }
+
+  private static final class MessageMarshaller<T extends MessageLite>
+      implements PrototypeMarshaller<T> {
+    private static final ThreadLocal<Reference<byte[]>> bufs = new ThreadLocal<Reference<byte[]>>();
+
+    private final Parser<T> parser;
+    private final T defaultInstance;
+
+    @SuppressWarnings("unchecked")
+    MessageMarshaller(T defaultInstance) {
+      this.defaultInstance = defaultInstance;
+      parser = (Parser<T>) defaultInstance.getParserForType();
+    }
+
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Class<T> getMessageClass() {
+      // Precisely T since protobuf doesn't let messages extend other messages.
+      return (Class<T>) defaultInstance.getClass();
+    }
+
+    @Override
+    public T getMessagePrototype() {
+      return defaultInstance;
+    }
+
+    @Override
+    public InputStream stream(T value) {
+      return new ProtoInputStream(value, parser);
+    }
+
+    @Override
+    public T parse(InputStream stream) {
+      if (stream instanceof ProtoInputStream) {
+        ProtoInputStream protoStream = (ProtoInputStream) stream;
+        // Optimization for in-memory transport. Returning provided object is safe since protobufs
+        // are immutable.
+        //
+        // However, we can't assume the types match, so we have to verify the parser matches.
+        // Today the parser is always the same for a given proto, but that isn't guaranteed. Even
+        // if not, using the same MethodDescriptor would ensure the parser matches and permit us
+        // to enable this optimization.
+        if (protoStream.parser() == parser) {
+          try {
+            @SuppressWarnings("unchecked")
+            T message = (T) ((ProtoInputStream) stream).message();
+            return message;
+          } catch (IllegalStateException ex) {
+            // Stream must have been read from, which is a strange state. Since the point of this
+            // optimization is to be transparent, instead of throwing an error we'll continue,
+            // even though it seems likely there's a bug.
+          }
+        }
+      }
+      CodedInputStream cis = null;
+      try {
+        if (stream instanceof KnownLength) {
+          int size = stream.available();
+          if (size > 0 && size <= DEFAULT_MAX_MESSAGE_SIZE) {
+            Reference<byte[]> ref;
+            // buf should not be used after this method has returned.
+            byte[] buf;
+            if ((ref = bufs.get()) == null || (buf = ref.get()) == null || buf.length < size) {
+              buf = new byte[size];
+              bufs.set(new WeakReference<byte[]>(buf));
+            }
+
+            int remaining = size;
+            while (remaining > 0) {
+              int position = size - remaining;
+              int count = stream.read(buf, position, remaining);
+              if (count == -1) {
+                break;
+              }
+              remaining -= count;
+            }
+
+            if (remaining != 0) {
+              int position = size - remaining;
+              throw new RuntimeException("size inaccurate: " + size + " != " + position);
+            }
+            cis = CodedInputStream.newInstance(buf, 0, size);
+          } else if (size == 0) {
+            return defaultInstance;
+          }
+        }
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+      if (cis == null) {
+        cis = CodedInputStream.newInstance(stream);
+      }
+      // Pre-create the CodedInputStream so that we can remove the size limit restriction
+      // when parsing.
+      cis.setSizeLimit(Integer.MAX_VALUE);
+
+      try {
+        return parseFrom(cis);
+      } catch (InvalidProtocolBufferException ipbe) {
+        throw Status.INTERNAL.withDescription("Invalid protobuf byte sequence")
+            .withCause(ipbe).asRuntimeException();
+      }
+    }
+
+    private T parseFrom(CodedInputStream stream) throws InvalidProtocolBufferException {
+      T message = parser.parseFrom(stream, globalRegistry);
+      try {
+        stream.checkLastTagWas(0);
+        return message;
+      } catch (InvalidProtocolBufferException e) {
+        e.setUnfinishedMessage(message);
+        throw e;
+      }
+    }
+  }
+
+  private static final class MetadataMarshaller<T extends MessageLite>
+      implements Metadata.BinaryMarshaller<T> {
+
+    private final T defaultInstance;
+
+    MetadataMarshaller(T defaultInstance) {
+      this.defaultInstance = defaultInstance;
+    }
+
+    @Override
+    public byte[] toBytes(T value) {
+      return value.toByteArray();
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public T parseBytes(byte[] serialized) {
+      try {
+        return (T) defaultInstance.getParserForType().parseFrom(serialized, globalRegistry);
+      } catch (InvalidProtocolBufferException ipbe) {
+        throw new IllegalArgumentException(ipbe);
+      }
+    }
+  }
+}
diff --git a/protobuf-lite/src/main/java/io/grpc/protobuf/lite/package-info.java b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/package-info.java
new file mode 100644
index 0000000..ceee669
--- /dev/null
+++ b/protobuf-lite/src/main/java/io/grpc/protobuf/lite/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/**
+ * API for gRPC over Protocol Buffers with proto message classes generated by the Lite Runtime
+ * library.
+ */
+
+package io.grpc.protobuf.lite;
diff --git a/protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java b/protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java
new file mode 100644
index 0000000..d05e884
--- /dev/null
+++ b/protobuf-lite/src/test/java/io/grpc/protobuf/lite/ProtoLiteUtilsTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf.lite;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import com.google.common.io.ByteStreams;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.Empty;
+import com.google.protobuf.Enum;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.Type;
+import io.grpc.Drainable;
+import io.grpc.KnownLength;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.MethodDescriptor.PrototypeMarshaller;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.internal.GrpcUtil;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ProtoLiteUtils}. */
+@RunWith(JUnit4.class)
+public class ProtoLiteUtilsTest {
+
+  @Rule public final ExpectedException thrown = ExpectedException.none();
+
+  private Marshaller<Type> marshaller = ProtoLiteUtils.marshaller(Type.getDefaultInstance());
+  private Type proto = Type.newBuilder().setName("name").build();
+
+  @Test
+  public void testPassthrough() {
+    assertSame(proto, marshaller.parse(marshaller.stream(proto)));
+  }
+
+  @Test
+  public void testRoundtrip() throws Exception {
+    InputStream is = marshaller.stream(proto);
+    is = new ByteArrayInputStream(ByteStreams.toByteArray(is));
+    assertEquals(proto, marshaller.parse(is));
+  }
+
+  @Test
+  public void testInvalidatedMessage() throws Exception {
+    InputStream is = marshaller.stream(proto);
+    // Invalidates message, and drains all bytes
+    byte[] unused = ByteStreams.toByteArray(is);
+    try {
+      ((ProtoInputStream) is).message();
+      fail("Expected exception");
+    } catch (IllegalStateException ex) {
+      // expected
+    }
+    // Zero bytes is the default message
+    assertEquals(Type.getDefaultInstance(), marshaller.parse(is));
+  }
+
+  @Test
+  public void parseInvalid() throws Exception {
+    InputStream is = new ByteArrayInputStream(new byte[] {-127});
+    try {
+      marshaller.parse(is);
+      fail("Expected exception");
+    } catch (StatusRuntimeException ex) {
+      assertEquals(Status.Code.INTERNAL, ex.getStatus().getCode());
+      assertNotNull(((InvalidProtocolBufferException) ex.getCause()).getUnfinishedMessage());
+    }
+  }
+
+  @Test
+  public void testMismatch() throws Exception {
+    Marshaller<Enum> enumMarshaller = ProtoLiteUtils.marshaller(Enum.getDefaultInstance());
+    // Enum's name and Type's name are both strings with tag 1.
+    Enum altProto = Enum.newBuilder().setName(proto.getName()).build();
+    assertEquals(proto, marshaller.parse(enumMarshaller.stream(altProto)));
+  }
+
+  @Test
+  public void introspection() throws Exception {
+    Marshaller<Enum> enumMarshaller = ProtoLiteUtils.marshaller(Enum.getDefaultInstance());
+    PrototypeMarshaller<Enum> prototypeMarshaller = (PrototypeMarshaller<Enum>) enumMarshaller;
+    assertSame(Enum.getDefaultInstance(), prototypeMarshaller.getMessagePrototype());
+    assertSame(Enum.class, prototypeMarshaller.getMessageClass());
+  }
+
+  @Test
+  public void marshallerShouldNotLimitProtoSize() throws Exception {
+    // The default limit is 64MB. Using a larger proto to verify that the limit is not enforced.
+    byte[] bigName = new byte[70 * 1024 * 1024];
+    Arrays.fill(bigName, (byte) 32);
+
+    proto = Type.newBuilder().setNameBytes(ByteString.copyFrom(bigName)).build();
+
+    // Just perform a round trip to verify that it works.
+    testRoundtrip();
+  }
+
+  @Test
+  public void testAvailable() throws Exception {
+    InputStream is = marshaller.stream(proto);
+    assertEquals(proto.getSerializedSize(), is.available());
+    is.read();
+    assertEquals(proto.getSerializedSize() - 1, is.available());
+    while (is.read() != -1) {}
+    assertEquals(-1, is.read());
+    assertEquals(0, is.available());
+  }
+
+  @Test
+  public void testEmpty() throws IOException {
+    Marshaller<Empty> marshaller = ProtoLiteUtils.marshaller(Empty.getDefaultInstance());
+    InputStream is = marshaller.stream(Empty.getDefaultInstance());
+    assertEquals(0, is.available());
+    byte[] b = new byte[10];
+    assertEquals(-1, is.read(b));
+    assertArrayEquals(new byte[10], b);
+    // Do the same thing again, because the internal state may be different
+    assertEquals(-1, is.read(b));
+    assertArrayEquals(new byte[10], b);
+    assertEquals(-1, is.read());
+    assertEquals(0, is.available());
+  }
+
+  @Test
+  public void testDrainTo_all() throws Exception {
+    byte[] golden = ByteStreams.toByteArray(marshaller.stream(proto));
+    InputStream is = marshaller.stream(proto);
+    Drainable d = (Drainable) is;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    int drained = d.drainTo(baos);
+    assertEquals(baos.size(), drained);
+    assertArrayEquals(golden, baos.toByteArray());
+    assertEquals(0, is.available());
+  }
+
+  @Test
+  public void testDrainTo_partial() throws Exception {
+    final byte[] golden;
+    {
+      InputStream is = marshaller.stream(proto);
+      is.read();
+      golden = ByteStreams.toByteArray(is);
+    }
+    InputStream is = marshaller.stream(proto);
+    is.read();
+    Drainable d = (Drainable) is;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    int drained = d.drainTo(baos);
+    assertEquals(baos.size(), drained);
+    assertArrayEquals(golden, baos.toByteArray());
+    assertEquals(0, is.available());
+  }
+
+  @Test
+  public void testDrainTo_none() throws Exception {
+    InputStream is = marshaller.stream(proto);
+    byte[] unused = ByteStreams.toByteArray(is);
+    Drainable d = (Drainable) is;
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    assertEquals(0, d.drainTo(baos));
+    assertArrayEquals(new byte[0], baos.toByteArray());
+    assertEquals(0, is.available());
+  }
+
+  @Test
+  public void metadataMarshaller_roundtrip() {
+    Metadata.BinaryMarshaller<Type> metadataMarshaller =
+        ProtoLiteUtils.metadataMarshaller(Type.getDefaultInstance());
+    assertEquals(proto, metadataMarshaller.parseBytes(metadataMarshaller.toBytes(proto)));
+  }
+
+  @Test
+  public void metadataMarshaller_invalid() {
+    Metadata.BinaryMarshaller<Type> metadataMarshaller =
+        ProtoLiteUtils.metadataMarshaller(Type.getDefaultInstance());
+    try {
+      metadataMarshaller.parseBytes(new byte[] {-127});
+      fail("Expected exception");
+    } catch (IllegalArgumentException ex) {
+      assertNotNull(((InvalidProtocolBufferException) ex.getCause()).getUnfinishedMessage());
+    }
+  }
+
+  @Test
+  public void extensionRegistry_notNull() {
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("newRegistry");
+
+    ProtoLiteUtils.setExtensionRegistry(null);
+  }
+
+  @Test
+  public void parseFromKnowLengthInputStream() throws Exception {
+    Marshaller<Type> marshaller = ProtoLiteUtils.marshaller(Type.getDefaultInstance());
+    Type expect = Type.newBuilder().setName("expected name").build();
+
+    Type result = marshaller.parse(new CustomKnownLengthInputStream(expect.toByteArray()));
+    assertEquals(expect, result);
+  }
+
+  @Test
+  public void defaultMaxMessageSize() {
+    assertEquals(GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE, ProtoLiteUtils.DEFAULT_MAX_MESSAGE_SIZE);
+  }
+
+  private static class CustomKnownLengthInputStream extends InputStream implements KnownLength {
+    private int position = 0;
+    private byte[] source;
+
+    private CustomKnownLengthInputStream(byte[] source) {
+      this.source = source;
+    }
+
+    @Override
+    public int available() throws IOException {
+      return source.length - position;
+    }
+
+    @Override
+    public int read() throws IOException {
+      if (position == source.length) {
+        return -1;
+      }
+
+      return source[position++];
+    }
+  }
+}
diff --git a/protobuf-nano/BUILD.bazel b/protobuf-nano/BUILD.bazel
new file mode 100644
index 0000000..5854245
--- /dev/null
+++ b/protobuf-nano/BUILD.bazel
@@ -0,0 +1,13 @@
+java_library(
+    name = "protobuf_nano",
+    srcs = glob([
+        "src/main/java/**/*.java",
+    ]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//core",
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_guava_guava//jar",
+        "@com_google_protobuf_nano_protobuf_javanano//jar",
+    ],
+)
diff --git a/protobuf-nano/build.gradle b/protobuf-nano/build.gradle
new file mode 100644
index 0000000..dd26db4
--- /dev/null
+++ b/protobuf-nano/build.gradle
@@ -0,0 +1,33 @@
+// Add dependency on the protobuf plugin
+buildscript {
+    repositories {
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    }
+    dependencies { classpath libraries.protobuf_plugin }
+}
+
+description = 'gRPC: Protobuf Nano'
+
+dependencies {
+    compile project(':grpc-core'),
+            libraries.protobuf_nano,
+            libraries.guava
+    signature "org.codehaus.mojo.signature:java17:1.0@signature"
+    signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
+}
+
+configureProtoCompilation()
+
+if (project.hasProperty('protobuf')) {
+    protobuf {
+        generateProtoTasks {
+            all().each { task ->
+                task.builtins {
+                    remove java
+                    javanano { option 'ignore_services=true' }
+                }
+            }
+        }
+    }
+}
diff --git a/protobuf-nano/src/generated/test/javanano/io/grpc/protobuf/nano/Messages.java b/protobuf-nano/src/generated/test/javanano/io/grpc/protobuf/nano/Messages.java
new file mode 100644
index 0000000..91915c9
--- /dev/null
+++ b/protobuf-nano/src/generated/test/javanano/io/grpc/protobuf/nano/Messages.java
@@ -0,0 +1,136 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+
+package io.grpc.protobuf.nano;
+
+@SuppressWarnings("hiding")
+public interface Messages {
+
+  public static final class Message extends
+      com.google.protobuf.nano.MessageNano {
+
+    private static volatile Message[] _emptyArray;
+    public static Message[] emptyArray() {
+      // Lazily initializes the empty array
+      if (_emptyArray == null) {
+        synchronized (
+            com.google.protobuf.nano.InternalNano.LAZY_INIT_LOCK) {
+          if (_emptyArray == null) {
+            _emptyArray = new Message[0];
+          }
+        }
+      }
+      return _emptyArray;
+    }
+
+    // int32 i = 1;
+    public int i;
+
+    // bool b = 2;
+    public boolean b;
+
+    // string s = 3;
+    public java.lang.String s;
+
+    // bytes bs = 4;
+    public byte[] bs;
+
+    public Message() {
+      clear();
+    }
+
+    public Message clear() {
+      i = 0;
+      b = false;
+      s = "";
+      bs = com.google.protobuf.nano.WireFormatNano.EMPTY_BYTES;
+      cachedSize = -1;
+      return this;
+    }
+
+    @Override
+    public void writeTo(com.google.protobuf.nano.CodedOutputByteBufferNano output)
+        throws java.io.IOException {
+      if (this.i != 0) {
+        output.writeInt32(1, this.i);
+      }
+      if (this.b != false) {
+        output.writeBool(2, this.b);
+      }
+      if (!this.s.equals("")) {
+        output.writeString(3, this.s);
+      }
+      if (!java.util.Arrays.equals(this.bs, com.google.protobuf.nano.WireFormatNano.EMPTY_BYTES)) {
+        output.writeBytes(4, this.bs);
+      }
+      super.writeTo(output);
+    }
+
+    @Override
+    protected int computeSerializedSize() {
+      int size = super.computeSerializedSize();
+      if (this.i != 0) {
+        size += com.google.protobuf.nano.CodedOutputByteBufferNano
+            .computeInt32Size(1, this.i);
+      }
+      if (this.b != false) {
+        size += com.google.protobuf.nano.CodedOutputByteBufferNano
+            .computeBoolSize(2, this.b);
+      }
+      if (!this.s.equals("")) {
+        size += com.google.protobuf.nano.CodedOutputByteBufferNano
+            .computeStringSize(3, this.s);
+      }
+      if (!java.util.Arrays.equals(this.bs, com.google.protobuf.nano.WireFormatNano.EMPTY_BYTES)) {
+        size += com.google.protobuf.nano.CodedOutputByteBufferNano
+            .computeBytesSize(4, this.bs);
+      }
+      return size;
+    }
+
+    @Override
+    public Message mergeFrom(
+            com.google.protobuf.nano.CodedInputByteBufferNano input)
+        throws java.io.IOException {
+      while (true) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            return this;
+          default: {
+            if (!com.google.protobuf.nano.WireFormatNano.parseUnknownField(input, tag)) {
+              return this;
+            }
+            break;
+          }
+          case 8: {
+            this.i = input.readInt32();
+            break;
+          }
+          case 16: {
+            this.b = input.readBool();
+            break;
+          }
+          case 26: {
+            this.s = input.readString();
+            break;
+          }
+          case 34: {
+            this.bs = input.readBytes();
+            break;
+          }
+        }
+      }
+    }
+
+    public static Message parseFrom(byte[] data)
+        throws com.google.protobuf.nano.InvalidProtocolBufferNanoException {
+      return com.google.protobuf.nano.MessageNano.mergeFrom(new Message(), data);
+    }
+
+    public static Message parseFrom(
+            com.google.protobuf.nano.CodedInputByteBufferNano input)
+        throws java.io.IOException {
+      return new Message().mergeFrom(input);
+    }
+  }
+}
diff --git a/protobuf-nano/src/main/java/io/grpc/protobuf/nano/MessageNanoFactory.java b/protobuf-nano/src/main/java/io/grpc/protobuf/nano/MessageNanoFactory.java
new file mode 100644
index 0000000..ce2a9b5
--- /dev/null
+++ b/protobuf-nano/src/main/java/io/grpc/protobuf/nano/MessageNanoFactory.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf.nano;
+
+import com.google.protobuf.nano.MessageNano;
+
+/**
+ * Produce new message instances. Used by a marshaller to deserialize incoming messages.
+ *
+ * <p>Should be implemented by generated code.
+ *
+ * @since 1.0.0
+ */
+public interface MessageNanoFactory<T extends MessageNano> {
+  T newInstance();
+}
diff --git a/protobuf-nano/src/main/java/io/grpc/protobuf/nano/NanoProtoInputStream.java b/protobuf-nano/src/main/java/io/grpc/protobuf/nano/NanoProtoInputStream.java
new file mode 100644
index 0000000..a684649
--- /dev/null
+++ b/protobuf-nano/src/main/java/io/grpc/protobuf/nano/NanoProtoInputStream.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf.nano;
+
+import com.google.protobuf.nano.CodedOutputByteBufferNano;
+import com.google.protobuf.nano.MessageNano;
+import io.grpc.KnownLength;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import javax.annotation.Nullable;
+
+/**
+ * An {@link InputStream} backed by a nano proto.
+ */
+final class NanoProtoInputStream extends InputStream implements KnownLength {
+
+  // NanoProtoInputStream is first initialized with a *message*. *partial* is initially null.
+  // Once there has been a read operation on this stream, *message* is serialized to *partial* and
+  // set to null.
+  @Nullable private MessageNano message;
+  @Nullable private ByteArrayInputStream partial;
+
+  NanoProtoInputStream(MessageNano message) {
+    this.message = message;
+  }
+
+  private void toPartial() {
+    if (message != null) {
+      partial = new ByteArrayInputStream(MessageNano.toByteArray(message));
+      message = null;
+    }
+  }
+
+  @Override
+  public int read() throws IOException {
+    toPartial();
+    if (partial != null) {
+      return partial.read();
+    }
+    return -1;
+  }
+
+  @Override
+  public int read(byte[] b, int off, int len) throws IOException {
+    if (message != null) {
+      int size = message.getSerializedSize();
+      if (size == 0) {
+        message = null;
+        partial = null;
+        return -1;
+      }
+      if (len >= size) {
+        // This is the only case that is zero-copy.
+        CodedOutputByteBufferNano output = CodedOutputByteBufferNano.newInstance(b, off, size);
+        message.writeTo(output);
+        output.checkNoSpaceLeft();
+
+        message = null;
+        partial = null;
+        return size;
+      }
+
+      toPartial();
+    }
+    if (partial != null) {
+      return partial.read(b, off, len);
+    }
+    return -1;
+  }
+
+  @Override
+  public int available() {
+    if (message != null) {
+      return message.getSerializedSize();
+    } else if (partial != null) {
+      return partial.available();
+    }
+    return 0;
+  }
+}
diff --git a/protobuf-nano/src/main/java/io/grpc/protobuf/nano/NanoUtils.java b/protobuf-nano/src/main/java/io/grpc/protobuf/nano/NanoUtils.java
new file mode 100644
index 0000000..2195707
--- /dev/null
+++ b/protobuf-nano/src/main/java/io/grpc/protobuf/nano/NanoUtils.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf.nano;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.protobuf.nano.CodedInputByteBufferNano;
+import com.google.protobuf.nano.MessageNano;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.Status;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Utility methods for using nano proto with grpc.
+ */
+public final class NanoUtils {
+
+  private NanoUtils() {}
+
+  /**
+   * Adapt {@code parser} to a {@link Marshaller}.
+   *
+   * @since 1.0.0
+   */
+  public static <T extends MessageNano> Marshaller<T> marshaller(MessageNanoFactory<T> factory) {
+    return new MessageMarshaller<T>(factory);
+  }
+
+  private static final class MessageMarshaller<T extends MessageNano> implements Marshaller<T> {
+    private static final int BUF_SIZE = 8192;
+
+    private final MessageNanoFactory<T> factory;
+
+    MessageMarshaller(MessageNanoFactory<T> factory) {
+      this.factory = factory;
+    }
+
+    @Override
+    public InputStream stream(T value) {
+      return new NanoProtoInputStream(value);
+    }
+
+    @Override
+    public T parse(InputStream stream) {
+      try {
+        // TODO(simonma): Investigate whether we can do 0-copy here.
+        CodedInputByteBufferNano input =
+            CodedInputByteBufferNano.newInstance(toByteArray(stream));
+        input.setSizeLimit(Integer.MAX_VALUE);
+        T message = factory.newInstance();
+        message.mergeFrom(input);
+        return message;
+      } catch (IOException ipbe) {
+        throw Status.INTERNAL.withDescription("Failed parsing nano proto message").withCause(ipbe)
+            .asRuntimeException();
+      }
+    }
+
+    // Copied from guava com.google.common.io.ByteStreams because its API is unstable (beta)
+    private static byte[] toByteArray(InputStream in) throws IOException {
+      ByteArrayOutputStream out = new ByteArrayOutputStream();
+      copy(in, out);
+      return out.toByteArray();
+    }
+
+    // Copied from guava com.google.common.io.ByteStreams because its API is unstable (beta)
+    private static long copy(InputStream from, OutputStream to) throws IOException {
+      checkNotNull(from);
+      checkNotNull(to);
+      byte[] buf = new byte[BUF_SIZE];
+      long total = 0;
+      while (true) {
+        int r = from.read(buf);
+        if (r == -1) {
+          break;
+        }
+        to.write(buf, 0, r);
+        total += r;
+      }
+      return total;
+    }
+  }
+}
diff --git a/protobuf-nano/src/main/java/io/grpc/protobuf/nano/package-info.java b/protobuf-nano/src/main/java/io/grpc/protobuf/nano/package-info.java
new file mode 100644
index 0000000..08993d8
--- /dev/null
+++ b/protobuf-nano/src/main/java/io/grpc/protobuf/nano/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/**
+ * API for gRPC over Protocol Buffers with proto message classes generated by the JavaNano compiler.
+ */
+
+package io.grpc.protobuf.nano;
diff --git a/protobuf-nano/src/test/java/io/grpc/protobuf/nano/NanoUtilsTest.java b/protobuf-nano/src/test/java/io/grpc/protobuf/nano/NanoUtilsTest.java
new file mode 100644
index 0000000..344a5fb
--- /dev/null
+++ b/protobuf-nano/src/test/java/io/grpc/protobuf/nano/NanoUtilsTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf.nano;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import com.google.protobuf.nano.MessageNano;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.protobuf.nano.Messages.Message;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link NanoUtils}. */
+@RunWith(JUnit4.class)
+public class NanoUtilsTest {
+  private Marshaller<Message> marshaller = NanoUtils.marshaller(new MessageNanoFactory<Message>() {
+    @Override
+    public Message newInstance() {
+      return new Message();
+    }
+  });
+
+  @Test
+  public void testRoundTrip() {
+    Message m = new Message();
+    m.i = 2;
+    m.b = true;
+    m.s = "string";
+    Message m2 = marshaller.parse(marshaller.stream(m));
+    assertNotSame(m, m2);
+    assertEquals(2, m2.i);
+    assertEquals(true, m2.b);
+    assertEquals("string", m2.s);
+    assertTrue(MessageNano.messageNanoEquals(m, m2));
+  }
+
+  @Test
+  public void parseInvalid() throws Exception {
+    InputStream is = new ByteArrayInputStream(new byte[] {-127});
+    try {
+      marshaller.parse(is);
+      fail("Expected exception");
+    } catch (StatusRuntimeException ex) {
+      assertEquals(Status.Code.INTERNAL, ex.getStatus().getCode());
+      assertTrue(ex.getCause() instanceof InvalidProtocolBufferNanoException);
+    }
+  }
+
+  @Test
+  public void testLarge() {
+    Message m = new Message();
+    // The default limit is 64MB. Using a larger proto to verify that the limit is not enforced.
+    m.bs = new byte[70 * 1024 * 1024];
+    Message m2 = marshaller.parse(marshaller.stream(m));
+    assertNotSame(m, m2);
+    // TODO(carl-mastrangelo): assertArrayEquals is REALLY slow, and been fixed in junit4.12.
+    // Eventually switch back to it once we are using 4.12 everywhere.
+    // assertArrayEquals(m.bs, m2.bs);
+    assertEquals(m.bs.length, m2.bs.length);
+    for (int i = 0; i < m.bs.length; i++) {
+      assertEquals(m.bs[i], m2.bs[i]);
+    }
+  }
+
+  @Test
+  public void testAvailable() throws Exception {
+    Message m = new Message();
+    m.s = "string";
+    InputStream is = marshaller.stream(m);
+    assertEquals(m.getSerializedSize(), is.available());
+    is.read();
+    assertEquals(m.getSerializedSize() - 1, is.available());
+    while (is.read() != -1) {}
+    assertEquals(-1, is.read());
+    assertEquals(0, is.available());
+  }
+
+  @Test
+  public void testEmpty() throws IOException {
+    InputStream is = marshaller.stream(new Message());
+    assertEquals(0, is.available());
+    byte[] b = new byte[10];
+    assertEquals(-1, is.read(b));
+    assertArrayEquals(new byte[10], b);
+    // Do the same thing again, because the internal state may be different
+    assertEquals(-1, is.read(b));
+    assertArrayEquals(new byte[10], b);
+    assertEquals(-1, is.read());
+    assertEquals(0, is.available());
+  }
+}
diff --git a/protobuf-nano/src/test/proto/grpc/testing/nano/messages.proto b/protobuf-nano/src/test/proto/grpc/testing/nano/messages.proto
new file mode 100644
index 0000000..ca7a708
--- /dev/null
+++ b/protobuf-nano/src/test/proto/grpc/testing/nano/messages.proto
@@ -0,0 +1,26 @@
+
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto3";
+
+package grpc.testing.nano;
+
+option java_package = "io.grpc.protobuf";
+
+message Message {
+  int32 i = 1;
+  bool b = 2;
+  string s = 3;
+  bytes bs = 4;
+}
diff --git a/protobuf/BUILD.bazel b/protobuf/BUILD.bazel
new file mode 100644
index 0000000..1c583ef
--- /dev/null
+++ b/protobuf/BUILD.bazel
@@ -0,0 +1,16 @@
+java_library(
+    name = "protobuf",
+    srcs = glob([
+        "src/main/java/**/*.java",
+    ]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//core",
+        "//protobuf-lite",
+        "@com_google_api_grpc_proto_google_common_protos//jar",
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_guava_guava//jar",
+        "@com_google_protobuf//:protobuf_java",
+        "@com_google_protobuf//:protobuf_java_util",
+    ],
+)
diff --git a/protobuf/build.gradle b/protobuf/build.gradle
new file mode 100644
index 0000000..abb2bc7
--- /dev/null
+++ b/protobuf/build.gradle
@@ -0,0 +1,31 @@
+description = 'gRPC: Protobuf'
+
+buildscript {
+    repositories {
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    }
+    dependencies { classpath libraries.protobuf_plugin }
+}
+
+dependencies {
+    compile project(':grpc-core'),
+            libraries.protobuf,
+            libraries.guava
+
+    compile (libraries.google_api_protos) {
+        // 'com.google.api:api-common' transitively depends on auto-value, which breaks our
+        // annotations.
+        exclude group: 'com.google.api', module: 'api-common'
+        // Prefer our more up-to-date protobuf over 3.2.0
+        exclude group: 'com.google.protobuf', module: 'protobuf-java'
+    }
+
+    compile (project(':grpc-protobuf-lite')) {
+        exclude group: 'com.google.protobuf', module: 'protobuf-lite'
+    }
+
+    signature "org.codehaus.mojo.signature:java17:1.0@signature"
+}
+
+javadoc.options.links 'https://developers.google.com/protocol-buffers/docs/reference/java/'
diff --git a/protobuf/src/main/java/io/grpc/protobuf/ProtoFileDescriptorSupplier.java b/protobuf/src/main/java/io/grpc/protobuf/ProtoFileDescriptorSupplier.java
new file mode 100644
index 0000000..b87cf33
--- /dev/null
+++ b/protobuf/src/main/java/io/grpc/protobuf/ProtoFileDescriptorSupplier.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf;
+
+import com.google.protobuf.Descriptors.FileDescriptor;
+
+/**
+ * Provides access to the underlying proto file descriptor.
+ *
+ * @since 1.1.0
+ */
+public interface ProtoFileDescriptorSupplier {
+  /**
+   * Returns file descriptor to the underlying proto file.
+   */
+  FileDescriptor getFileDescriptor();
+}
diff --git a/protobuf/src/main/java/io/grpc/protobuf/ProtoMethodDescriptorSupplier.java b/protobuf/src/main/java/io/grpc/protobuf/ProtoMethodDescriptorSupplier.java
new file mode 100644
index 0000000..e5b2f38
--- /dev/null
+++ b/protobuf/src/main/java/io/grpc/protobuf/ProtoMethodDescriptorSupplier.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf;
+
+import com.google.protobuf.Descriptors.MethodDescriptor;
+import javax.annotation.CheckReturnValue;
+
+/**
+ * Provides access to the underlying proto service method descriptor.
+ *
+ * @since 1.7.0
+ */
+public interface ProtoMethodDescriptorSupplier extends ProtoServiceDescriptorSupplier {
+  /**
+   * Returns method descriptor to the proto service method.
+   */
+  @CheckReturnValue
+  MethodDescriptor getMethodDescriptor();
+}
diff --git a/protobuf/src/main/java/io/grpc/protobuf/ProtoServiceDescriptorSupplier.java b/protobuf/src/main/java/io/grpc/protobuf/ProtoServiceDescriptorSupplier.java
new file mode 100644
index 0000000..b37150e
--- /dev/null
+++ b/protobuf/src/main/java/io/grpc/protobuf/ProtoServiceDescriptorSupplier.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf;
+
+import com.google.protobuf.Descriptors.ServiceDescriptor;
+
+/**
+ * Provides access to the underlying proto service descriptor.
+ *
+ * @since 1.7.0
+ */
+public interface ProtoServiceDescriptorSupplier extends ProtoFileDescriptorSupplier {
+  /**
+   * Returns service descriptor to the proto service.
+   */
+  ServiceDescriptor getServiceDescriptor();
+}
diff --git a/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java b/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java
new file mode 100644
index 0000000..a230d50
--- /dev/null
+++ b/protobuf/src/main/java/io/grpc/protobuf/ProtoUtils.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf;
+
+import com.google.protobuf.Message;
+import io.grpc.ExperimentalApi;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.protobuf.lite.ProtoLiteUtils;
+
+/**
+ * Utility methods for using protobuf with grpc.
+ */
+public final class ProtoUtils {
+
+  /**
+   * Create a {@link Marshaller} for protos of the same type as {@code defaultInstance}.
+   *
+   * @since 1.0.0
+   */
+  public static <T extends Message> Marshaller<T> marshaller(final T defaultInstance) {
+    return ProtoLiteUtils.marshaller(defaultInstance);
+  }
+
+  /**
+   * Produce a metadata key for a generated protobuf type.
+   *
+   * @since 1.0.0
+   */
+  public static <T extends Message> Metadata.Key<T> keyForProto(T instance) {
+    return Metadata.Key.of(
+        instance.getDescriptorForType().getFullName() + Metadata.BINARY_HEADER_SUFFIX,
+        metadataMarshaller(instance));
+  }
+
+  /**
+   * Produce a metadata marshaller for a protobuf type.
+   * 
+   * @since 1.13.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/4477")
+  public static <T extends Message> Metadata.BinaryMarshaller<T> metadataMarshaller(T instance) {
+    return ProtoLiteUtils.metadataMarshaller(instance);
+  }
+
+  private ProtoUtils() {
+  }
+}
diff --git a/protobuf/src/main/java/io/grpc/protobuf/StatusProto.java b/protobuf/src/main/java/io/grpc/protobuf/StatusProto.java
new file mode 100644
index 0000000..2648138
--- /dev/null
+++ b/protobuf/src/main/java/io/grpc/protobuf/StatusProto.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.grpc.ExperimentalApi;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.StatusException;
+import io.grpc.StatusRuntimeException;
+import io.grpc.protobuf.lite.ProtoLiteUtils;
+import javax.annotation.Nullable;
+
+/** Utility methods for working with {@link com.google.rpc.Status}. */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4695")
+public final class StatusProto {
+  private StatusProto() {}
+
+  private static final Metadata.Key<com.google.rpc.Status> STATUS_DETAILS_KEY =
+      Metadata.Key.of(
+          "grpc-status-details-bin",
+          ProtoLiteUtils.metadataMarshaller(com.google.rpc.Status.getDefaultInstance()));
+
+  /**
+   * Convert a {@link com.google.rpc.Status} instance to a {@link StatusRuntimeException}.
+   *
+   * <p>The returned {@link StatusRuntimeException} will wrap a {@link Status} whose code and
+   * description are set from the code and message in {@code statusProto}. {@code statusProto} will
+   * be serialized and placed into the metadata of the returned {@link StatusRuntimeException}.
+   *
+   * @throws IllegalArgumentException if the value of {@code statusProto.getCode()} is not a valid
+   *     gRPC status code.
+   * @since 1.3.0
+   */
+  public static StatusRuntimeException toStatusRuntimeException(com.google.rpc.Status statusProto) {
+    return toStatus(statusProto).asRuntimeException(toMetadata(statusProto));
+  }
+
+  /**
+   * Convert a {@link com.google.rpc.Status} instance to a {@link StatusRuntimeException} with
+   * additional metadata.
+   *
+   * <p>The returned {@link StatusRuntimeException} will wrap a {@link Status} whose code and
+   * description are set from the code and message in {@code statusProto}. {@code statusProto} will
+   * be serialized and added to {@code metadata}. {@code metadata} will be set as the metadata of
+   * the returned {@link StatusRuntimeException}.
+   *
+   * @throws IllegalArgumentException if the value of {@code statusProto.getCode()} is not a valid
+   *     gRPC status code.
+   * @since 1.3.0
+   */
+  public static StatusRuntimeException toStatusRuntimeException(
+      com.google.rpc.Status statusProto, Metadata metadata) {
+    return toStatus(statusProto).asRuntimeException(toMetadata(statusProto, metadata));
+  }
+
+  /**
+   * Convert a {@link com.google.rpc.Status} instance to a {@link StatusException}.
+   *
+   * <p>The returned {@link StatusException} will wrap a {@link Status} whose code and description
+   * are set from the code and message in {@code statusProto}. {@code statusProto} will be
+   * serialized and placed into the metadata of the returned {@link StatusException}.
+   *
+   * @throws IllegalArgumentException if the value of {@code statusProto.getCode()} is not a valid
+   *     gRPC status code.
+   * @since 1.3.0
+   */
+  public static StatusException toStatusException(com.google.rpc.Status statusProto) {
+    return toStatus(statusProto).asException(toMetadata(statusProto));
+  }
+
+  /**
+   * Convert a {@link com.google.rpc.Status} instance to a {@link StatusException} with additional
+   * metadata.
+   *
+   * <p>The returned {@link StatusException} will wrap a {@link Status} whose code and description
+   * are set from the code and message in {@code statusProto}. {@code statusProto} will be
+   * serialized and added to {@code metadata}. {@code metadata} will be set as the metadata of the
+   * returned {@link StatusException}.
+   *
+   * @throws IllegalArgumentException if the value of {@code statusProto.getCode()} is not a valid
+   *     gRPC status code.
+   * @since 1.3.0
+   */
+  public static StatusException toStatusException(
+      com.google.rpc.Status statusProto, Metadata metadata) {
+    return toStatus(statusProto).asException(toMetadata(statusProto, metadata));
+  }
+
+  private static Status toStatus(com.google.rpc.Status statusProto) {
+    Status status = Status.fromCodeValue(statusProto.getCode());
+    checkArgument(status.getCode().value() == statusProto.getCode(), "invalid status code");
+    return status.withDescription(statusProto.getMessage());
+  }
+
+  private static Metadata toMetadata(com.google.rpc.Status statusProto) {
+    Metadata metadata = new Metadata();
+    metadata.put(STATUS_DETAILS_KEY, statusProto);
+    return metadata;
+  }
+
+  private static Metadata toMetadata(com.google.rpc.Status statusProto, Metadata metadata) {
+    checkNotNull(metadata, "metadata must not be null");
+    metadata.discardAll(STATUS_DETAILS_KEY);
+    metadata.put(STATUS_DETAILS_KEY, statusProto);
+    return metadata;
+  }
+
+  /**
+   * Extract a {@link com.google.rpc.Status} instance from the causal chain of a {@link Throwable}.
+   *
+   * @return the extracted {@link com.google.rpc.Status} instance, or {@code null} if none exists.
+   * @throws IllegalArgumentException if an embedded {@link com.google.rpc.Status} is found and its
+   *     code does not match the gRPC {@link Status} code.
+   * @since 1.3.0
+   */
+  @Nullable
+  public static com.google.rpc.Status fromThrowable(Throwable t) {
+    Throwable cause = checkNotNull(t, "t");
+    while (cause != null) {
+      if (cause instanceof StatusException) {
+        StatusException e = (StatusException) cause;
+        return fromStatusAndTrailers(e.getStatus(), e.getTrailers());
+      } else if (cause instanceof StatusRuntimeException) {
+        StatusRuntimeException e = (StatusRuntimeException) cause;
+        return fromStatusAndTrailers(e.getStatus(), e.getTrailers());
+      }
+      cause = cause.getCause();
+    }
+    return null;
+  }
+
+  /**
+   * Extracts the google.rpc.Status from trailers, and makes sure they match the gRPC
+   * {@code status}.
+   *
+   * @return the embedded google.rpc.Status or {@code null} if it is not present.
+   * @since 1.11.0
+   */
+  @Nullable
+  public static com.google.rpc.Status fromStatusAndTrailers(Status status, Metadata trailers) {
+    if (trailers != null) {
+      com.google.rpc.Status statusProto = trailers.get(STATUS_DETAILS_KEY);
+      if (statusProto != null) {
+        checkArgument(
+            status.getCode().value() == statusProto.getCode(),
+            "com.google.rpc.Status code must match gRPC status code");
+        return statusProto;
+      }
+    }
+    return null;
+  }
+}
diff --git a/protobuf/src/main/java/io/grpc/protobuf/package-info.java b/protobuf/src/main/java/io/grpc/protobuf/package-info.java
new file mode 100644
index 0000000..c73d24f
--- /dev/null
+++ b/protobuf/src/main/java/io/grpc/protobuf/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/**
+ * API for gRPC over Protocol Buffers, including tools for serializing and de-serializing protobuf
+ * messages.
+ */
+package io.grpc.protobuf;
diff --git a/protobuf/src/test/java/io/grpc/protobuf/ProtoUtilsTest.java b/protobuf/src/test/java/io/grpc/protobuf/ProtoUtilsTest.java
new file mode 100644
index 0000000..74707c9
--- /dev/null
+++ b/protobuf/src/test/java/io/grpc/protobuf/ProtoUtilsTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.common.io.ByteStreams;
+import com.google.protobuf.Type;
+import io.grpc.MethodDescriptor.Marshaller;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ProtoUtils}. */
+@RunWith(JUnit4.class)
+public class ProtoUtilsTest {
+  private Type proto = Type.newBuilder().setName("value").build();
+
+  @Test
+  public void testRoundtrip() throws Exception {
+    Marshaller<Type> marshaller = ProtoUtils.marshaller(Type.getDefaultInstance());
+    InputStream is = marshaller.stream(proto);
+    is = new ByteArrayInputStream(ByteStreams.toByteArray(is));
+    assertEquals(proto, marshaller.parse(is));
+  }
+
+  @Test
+  public void keyForProto() {
+    assertEquals("google.protobuf.Type-bin",
+        ProtoUtils.keyForProto(Type.getDefaultInstance()).originalName());
+  }
+}
diff --git a/protobuf/src/test/java/io/grpc/protobuf/StatusProtoTest.java b/protobuf/src/test/java/io/grpc/protobuf/StatusProtoTest.java
new file mode 100644
index 0000000..f981236
--- /dev/null
+++ b/protobuf/src/test/java/io/grpc/protobuf/StatusProtoTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.StatusException;
+import io.grpc.StatusRuntimeException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link StatusProto}. */
+@RunWith(JUnit4.class)
+public class StatusProtoTest {
+  private Metadata metadata;
+
+  @Before
+  public void setup() {
+    metadata = new Metadata();
+    metadata.put(METADATA_KEY, METADATA_VALUE);
+  }
+
+  @Test
+  public void toStatusRuntimeException() throws Exception {
+    StatusRuntimeException sre = StatusProto.toStatusRuntimeException(STATUS_PROTO);
+    com.google.rpc.Status extractedStatusProto = StatusProto.fromThrowable(sre);
+
+    assertEquals(STATUS_PROTO.getCode(), sre.getStatus().getCode().value());
+    assertEquals(STATUS_PROTO.getMessage(), sre.getStatus().getDescription());
+    assertEquals(STATUS_PROTO, extractedStatusProto);
+  }
+
+  @Test
+  public void toStatusRuntimeExceptionWithMetadata_shouldIncludeMetadata() throws Exception {
+    StatusRuntimeException sre = StatusProto.toStatusRuntimeException(STATUS_PROTO, metadata);
+    com.google.rpc.Status extractedStatusProto = StatusProto.fromThrowable(sre);
+
+    assertEquals(STATUS_PROTO.getCode(), sre.getStatus().getCode().value());
+    assertEquals(STATUS_PROTO.getMessage(), sre.getStatus().getDescription());
+    assertEquals(STATUS_PROTO, extractedStatusProto);
+    assertNotNull(sre.getTrailers());
+    assertEquals(METADATA_VALUE, sre.getTrailers().get(METADATA_KEY));
+  }
+
+  @Test
+  public void toStatusRuntimeExceptionWithMetadata_shouldThrowIfMetadataIsNull() throws Exception {
+    try {
+      StatusProto.toStatusRuntimeException(STATUS_PROTO, null);
+      fail("NullPointerException expected");
+    } catch (NullPointerException npe) {
+      assertEquals("metadata must not be null", npe.getMessage());
+    }
+  }
+
+  @Test
+  public void toStatusRuntimeException_shouldThrowIfStatusCodeInvalid() throws Exception {
+    try {
+      StatusProto.toStatusRuntimeException(INVALID_STATUS_PROTO);
+      fail("IllegalArgumentException expected");
+    } catch (IllegalArgumentException expectedException) {
+      assertEquals("invalid status code", expectedException.getMessage());
+    }
+  }
+
+  @Test
+  public void toStatusException() throws Exception {
+    StatusException se = StatusProto.toStatusException(STATUS_PROTO);
+    com.google.rpc.Status extractedStatusProto = StatusProto.fromThrowable(se);
+
+    assertEquals(STATUS_PROTO.getCode(), se.getStatus().getCode().value());
+    assertEquals(STATUS_PROTO.getMessage(), se.getStatus().getDescription());
+    assertEquals(STATUS_PROTO, extractedStatusProto);
+  }
+
+  @Test
+  public void toStatusExceptionWithMetadata_shouldIncludeMetadata() throws Exception {
+    StatusException se = StatusProto.toStatusException(STATUS_PROTO, metadata);
+    com.google.rpc.Status extractedStatusProto = StatusProto.fromThrowable(se);
+
+    assertEquals(STATUS_PROTO.getCode(), se.getStatus().getCode().value());
+    assertEquals(STATUS_PROTO.getMessage(), se.getStatus().getDescription());
+    assertEquals(STATUS_PROTO, extractedStatusProto);
+    assertNotNull(se.getTrailers());
+    assertEquals(METADATA_VALUE, se.getTrailers().get(METADATA_KEY));
+  }
+
+  @Test
+  public void toStatusExceptionWithMetadata_shouldThrowIfMetadataIsNull() throws Exception {
+    try {
+      StatusProto.toStatusException(STATUS_PROTO, null);
+      fail("NullPointerException expected");
+    } catch (NullPointerException npe) {
+      assertEquals("metadata must not be null", npe.getMessage());
+    }
+  }
+
+  @Test
+  public void toStatusException_shouldThrowIfStatusCodeInvalid() throws Exception {
+    try {
+      StatusProto.toStatusException(INVALID_STATUS_PROTO);
+      fail("IllegalArgumentException expected");
+    } catch (IllegalArgumentException expectedException) {
+      assertEquals("invalid status code", expectedException.getMessage());
+    }
+  }
+
+  @Test
+  public void fromThrowable_shouldReturnNullIfTrailersAreNull() {
+    Status status = Status.fromCodeValue(0);
+
+    assertNull(StatusProto.fromThrowable(status.asRuntimeException()));
+    assertNull(StatusProto.fromThrowable(status.asException()));
+  }
+
+  @Test
+  public void fromThrowable_shouldReturnNullIfStatusDetailsKeyIsMissing() {
+    Status status = Status.fromCodeValue(0);
+    Metadata emptyMetadata = new Metadata();
+
+    assertNull(StatusProto.fromThrowable(status.asRuntimeException(emptyMetadata)));
+    assertNull(StatusProto.fromThrowable(status.asException(emptyMetadata)));
+  }
+
+  @Test
+  public void fromThrowableWithNestedStatusRuntimeException() {
+    StatusRuntimeException sre = StatusProto.toStatusRuntimeException(STATUS_PROTO);
+    Throwable nestedSre = new Throwable(sre);
+
+    com.google.rpc.Status extractedStatusProto = StatusProto.fromThrowable(sre);
+    com.google.rpc.Status extractedStatusProtoFromNestedSre = StatusProto.fromThrowable(nestedSre);
+
+    assertEquals(extractedStatusProto, extractedStatusProtoFromNestedSre);
+  }
+
+  @Test
+  public void fromThrowableWithNestedStatusException() {
+    StatusException se = StatusProto.toStatusException(STATUS_PROTO);
+    Throwable nestedSe = new Throwable(se);
+
+    com.google.rpc.Status extractedStatusProto = StatusProto.fromThrowable(se);
+    com.google.rpc.Status extractedStatusProtoFromNestedSe = StatusProto.fromThrowable(nestedSe);
+
+    assertEquals(extractedStatusProto, extractedStatusProtoFromNestedSe);
+  }
+
+  @Test
+  public void fromThrowable_shouldReturnNullIfNoEmbeddedStatus() {
+    Throwable nestedSe = new Throwable(new Throwable("no status found"));
+
+    assertNull(StatusProto.fromThrowable(nestedSe));
+  }
+
+  private static final Metadata.Key<String> METADATA_KEY =
+      Metadata.Key.of("test-metadata", Metadata.ASCII_STRING_MARSHALLER);
+  private static final String METADATA_VALUE = "test metadata value";
+  private static final com.google.rpc.Status STATUS_PROTO =
+      com.google.rpc.Status.newBuilder()
+          .setCode(2)
+          .setMessage("status message")
+          .addDetails(
+              com.google.protobuf.Any.pack(
+                  com.google.rpc.Status.newBuilder()
+                      .setCode(13)
+                      .setMessage("nested message")
+                      .build()))
+          .build();
+  private static final com.google.rpc.Status INVALID_STATUS_PROTO =
+      com.google.rpc.Status.newBuilder().setCode(-1).build();
+}
diff --git a/repositories.bzl b/repositories.bzl
new file mode 100644
index 0000000..c3bbdfd
--- /dev/null
+++ b/repositories.bzl
@@ -0,0 +1,333 @@
+"""External dependencies for grpc-java."""
+
+def grpc_java_repositories(
+        omit_com_google_api_grpc_google_common_protos = False,
+        omit_com_google_auth_google_auth_library_credentials = False,
+        omit_com_google_auth_google_auth_library_oauth2_http = False,
+        omit_com_google_code_findbugs_jsr305 = False,
+        omit_com_google_code_gson = False,
+        omit_com_google_errorprone_error_prone_annotations = False,
+        omit_com_google_guava = False,
+        omit_com_google_protobuf = False,
+        omit_com_google_protobuf_java = False,
+        omit_com_google_protobuf_javalite = False,
+        omit_com_google_protobuf_nano_protobuf_javanano = False,
+        omit_com_google_re2j = False,
+        omit_com_google_truth_truth = False,
+        omit_com_squareup_okhttp = False,
+        omit_com_squareup_okio = False,
+        omit_io_netty_buffer = False,
+        omit_io_netty_common = False,
+        omit_io_netty_transport = False,
+        omit_io_netty_codec = False,
+        omit_io_netty_codec_socks = False,
+        omit_io_netty_codec_http = False,
+        omit_io_netty_codec_http2 = False,
+        omit_io_netty_handler = False,
+        omit_io_netty_handler_proxy = False,
+        omit_io_netty_resolver = False,
+        omit_io_netty_tcnative_boringssl_static = False,
+        omit_io_opencensus_api = False,
+        omit_io_opencensus_grpc_metrics = False,
+        omit_javax_annotation = False,
+        omit_junit_junit = False,
+        omit_org_apache_commons_lang3 = False,
+        omit_org_codehaus_mojo_animal_sniffer_annotations = False):
+    """Imports dependencies for grpc-java."""
+    if not omit_com_google_api_grpc_google_common_protos:
+        com_google_api_grpc_google_common_protos()
+    if not omit_com_google_auth_google_auth_library_credentials:
+        com_google_auth_google_auth_library_credentials()
+    if not omit_com_google_auth_google_auth_library_oauth2_http:
+        com_google_auth_google_auth_library_oauth2_http()
+    if not omit_com_google_code_findbugs_jsr305:
+        com_google_code_findbugs_jsr305()
+    if not omit_com_google_code_gson:
+        com_google_code_gson()
+    if not omit_com_google_errorprone_error_prone_annotations:
+        com_google_errorprone_error_prone_annotations()
+    if not omit_com_google_guava:
+        com_google_guava()
+    if not omit_com_google_protobuf:
+        com_google_protobuf()
+    if omit_com_google_protobuf_java:
+        fail("omit_com_google_protobuf_java is no longer supported and must be not be passed to grpc_java_repositories()")
+    if not omit_com_google_protobuf_javalite:
+        com_google_protobuf_javalite()
+    if not omit_com_google_protobuf_nano_protobuf_javanano:
+        com_google_protobuf_nano_protobuf_javanano()
+    if not omit_com_google_re2j:
+        com_google_re2j()
+    if not omit_com_google_truth_truth:
+        com_google_truth_truth()
+    if not omit_com_squareup_okhttp:
+        com_squareup_okhttp()
+    if not omit_com_squareup_okio:
+        com_squareup_okio()
+    if not omit_io_netty_buffer:
+        io_netty_buffer()
+    if not omit_io_netty_common:
+        io_netty_common()
+    if not omit_io_netty_transport:
+        io_netty_transport()
+    if not omit_io_netty_codec:
+        io_netty_codec()
+    if not omit_io_netty_codec_socks:
+        io_netty_codec_socks()
+    if not omit_io_netty_codec_http:
+        io_netty_codec_http()
+    if not omit_io_netty_codec_http2:
+        io_netty_codec_http2()
+    if not omit_io_netty_handler:
+        io_netty_handler()
+    if not omit_io_netty_handler_proxy:
+        io_netty_handler_proxy()
+    if not omit_io_netty_resolver:
+        io_netty_resolver()
+    if not omit_io_netty_tcnative_boringssl_static:
+        io_netty_tcnative_boringssl_static()
+    if not omit_io_opencensus_api:
+        io_opencensus_api()
+    if not omit_io_opencensus_grpc_metrics:
+        io_opencensus_grpc_metrics()
+    if not omit_javax_annotation:
+        javax_annotation()
+    if not omit_junit_junit:
+        junit_junit()
+    if not omit_org_apache_commons_lang3:
+        org_apache_commons_lang3()
+    if not omit_org_codehaus_mojo_animal_sniffer_annotations:
+        org_codehaus_mojo_animal_sniffer_annotations()
+
+    native.bind(
+        name = "guava",
+        actual = "@com_google_guava_guava//jar",
+    )
+    native.bind(
+        name = "gson",
+        actual = "@com_google_code_gson_gson//jar",
+    )
+
+def com_google_api_grpc_google_common_protos():
+    native.maven_jar(
+        name = "com_google_api_grpc_proto_google_common_protos",
+        artifact = "com.google.api.grpc:proto-google-common-protos:1.0.0",
+        sha1 = "86f070507e28b930e50d218ee5b6788ef0dd05e6",
+    )
+
+def com_google_auth_google_auth_library_credentials():
+    native.maven_jar(
+        name = "com_google_auth_google_auth_library_credentials",
+        artifact = "com.google.auth:google-auth-library-credentials:0.9.0",
+        sha1 = "8e2b181feff6005c9cbc6f5c1c1e2d3ec9138d46",
+    )
+
+def com_google_auth_google_auth_library_oauth2_http():
+    native.maven_jar(
+        name = "com_google_auth_google_auth_library_oauth2_http",
+        artifact = "com.google.auth:google-auth-library-oauth2-http:0.9.0",
+        sha1 = "04e6152c3aead24148627e84f5651e79698c00d9",
+    )
+
+def com_google_code_findbugs_jsr305():
+    native.maven_jar(
+        name = "com_google_code_findbugs_jsr305",
+        artifact = "com.google.code.findbugs:jsr305:3.0.0",
+        sha1 = "5871fb60dc68d67da54a663c3fd636a10a532948",
+    )
+
+def com_google_code_gson():
+    native.maven_jar(
+        name = "com_google_code_gson_gson",
+        artifact = "com.google.code.gson:gson:jar:2.7",
+        sha1 = "751f548c85fa49f330cecbb1875893f971b33c4e",
+    )
+
+def com_google_errorprone_error_prone_annotations():
+    native.maven_jar(
+        name = "com_google_errorprone_error_prone_annotations",
+        artifact = "com.google.errorprone:error_prone_annotations:2.2.0",
+        sha1 = "88e3c593e9b3586e1c6177f89267da6fc6986f0c",
+    )
+
+def com_google_guava():
+    native.maven_jar(
+        name = "com_google_guava_guava",
+        artifact = "com.google.guava:guava:20.0",
+        sha1 = "89507701249388e1ed5ddcf8c41f4ce1be7831ef",
+    )
+
+def com_google_protobuf():
+    # proto_library rules implicitly depend on @com_google_protobuf//:protoc,
+    # which is the proto-compiler.
+    # This statement defines the @com_google_protobuf repo.
+    native.http_archive(
+        name = "com_google_protobuf",
+        sha256 = "1f8b9b202e9a4e467ff0b0f25facb1642727cdf5e69092038f15b37c75b99e45",
+        strip_prefix = "protobuf-3.5.1",
+        urls = ["https://github.com/google/protobuf/archive/v3.5.1.zip"],
+    )
+
+def com_google_protobuf_javalite():
+    # java_lite_proto_library rules implicitly depend on @com_google_protobuf_javalite
+    native.http_archive(
+        name = "com_google_protobuf_javalite",
+        sha256 = "d8a2fed3708781196f92e1e7e7e713cf66804bd2944894401057214aff4f468e",
+        strip_prefix = "protobuf-5e8916e881c573c5d83980197a6f783c132d4276",
+        urls = ["https://github.com/google/protobuf/archive/5e8916e881c573c5d83980197a6f783c132d4276.zip"],
+    )
+
+def com_google_protobuf_nano_protobuf_javanano():
+    native.maven_jar(
+        name = "com_google_protobuf_nano_protobuf_javanano",
+        artifact = "com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-5",
+        sha1 = "357e60f95cebb87c72151e49ba1f570d899734f8",
+    )
+
+def com_google_re2j():
+    native.maven_jar(
+        name = "com_google_re2j",
+        artifact = "com.google.re2j:re2j:1.2",
+        sha1 = "499d5e041f962fefd0f245a9325e8125608ebb54",
+    )
+
+def com_google_truth_truth():
+    native.maven_jar(
+        name = "com_google_truth_truth",
+        artifact = "com.google.truth:truth:0.42",
+        sha1 = "b5768f644b114e6cf5c3962c2ebcb072f788dcbb",
+    )
+
+def com_squareup_okhttp():
+    native.maven_jar(
+        name = "com_squareup_okhttp_okhttp",
+        artifact = "com.squareup.okhttp:okhttp:2.5.0",
+        sha1 = "4de2b4ed3445c37ec1720a7d214712e845a24636",
+    )
+
+def com_squareup_okio():
+    native.maven_jar(
+        name = "com_squareup_okio_okio",
+        artifact = "com.squareup.okio:okio:1.13.0",
+        sha1 = "a9283170b7305c8d92d25aff02a6ab7e45d06cbe",
+    )
+
+def io_netty_codec_http2():
+    native.maven_jar(
+        name = "io_netty_netty_codec_http2",
+        artifact = "io.netty:netty-codec-http2:4.1.27.Final",
+        sha1 = "3769790a2033667d663f9a526d5b63cfecdbdf4e",
+    )
+
+def io_netty_buffer():
+    native.maven_jar(
+        name = "io_netty_netty_buffer",
+        artifact = "io.netty:netty-buffer:4.1.27.Final",
+        sha1 = "aafe2b9fb0d8f3b200cf10b9fd6486c6a722d7a1",
+    )
+
+def io_netty_common():
+    native.maven_jar(
+        name = "io_netty_netty_common",
+        artifact = "io.netty:netty-common:4.1.27.Final",
+        sha1 = "6a12a969c27fb37b230c4bde5a67bd822fa6b7a4",
+    )
+
+def io_netty_transport():
+    native.maven_jar(
+        name = "io_netty_netty_transport",
+        artifact = "io.netty:netty-transport:4.1.27.Final",
+        sha1 = "b5c2da3ea89dd67320925f1504c9eb3615241b7c",
+    )
+
+def io_netty_codec():
+    native.maven_jar(
+        name = "io_netty_netty_codec",
+        artifact = "io.netty:netty-codec:4.1.27.Final",
+        sha1 = "d2653d78ebaa650064768fb26b10051f5c8efb2c",
+    )
+
+def io_netty_codec_socks():
+    native.maven_jar(
+        name = "io_netty_netty_codec_socks",
+        artifact = "io.netty:netty-codec-socks:4.1.27.Final",
+        sha1 = "285b09af98764cf02e4b77b3d95af188469a7133",
+    )
+
+def io_netty_codec_http():
+    native.maven_jar(
+        name = "io_netty_netty_codec_http",
+        artifact = "io.netty:netty-codec-http:4.1.27.Final",
+        sha1 = "a1722d6bcbbef1c4c7877e8bf38b07a3db5ed07f",
+    )
+
+def io_netty_handler():
+    native.maven_jar(
+        name = "io_netty_netty_handler",
+        artifact = "io.netty:netty-handler:4.1.27.Final",
+        sha1 = "21bd9cf565390a8d72579b8664303e3c175dfc6a",
+    )
+
+def io_netty_handler_proxy():
+    native.maven_jar(
+        name = "io_netty_netty_handler_proxy",
+        artifact = "io.netty:netty-handler-proxy:4.1.27.Final",
+        sha1 = "1a822ce7760bc6eb4937b7e448c9e081fedcc807",
+    )
+
+def io_netty_resolver():
+    native.maven_jar(
+        name = "io_netty_netty_resolver",
+        artifact = "io.netty:netty-resolver:4.1.27.Final",
+        sha1 = "2536447ef9605ccb2b5203aa22392c6514484ea9",
+    )
+
+def io_netty_tcnative_boringssl_static():
+    native.maven_jar(
+        name = "io_netty_netty_tcnative_boringssl_static",
+        artifact = "io.netty:netty-tcnative-boringssl-static:2.0.12.Final",
+        sha1 = "b884be1450a7fd0854b98743836b8ccb0dfd75a4",
+    )
+
+def io_opencensus_api():
+    native.maven_jar(
+        name = "io_opencensus_opencensus_api",
+        artifact = "io.opencensus:opencensus-api:0.12.3",
+        sha1 = "743f074095f29aa985517299545e72cc99c87de0",
+    )
+
+def io_opencensus_grpc_metrics():
+    native.maven_jar(
+        name = "io_opencensus_opencensus_contrib_grpc_metrics",
+        artifact = "io.opencensus:opencensus-contrib-grpc-metrics:0.12.3",
+        sha1 = "a4c7ff238a91b901c8b459889b6d0d7a9d889b4d",
+    )
+
+def javax_annotation():
+    # Use //stub:javax_annotation for neverlink=1 support.
+    native.maven_jar(
+        name = "javax_annotation_javax_annotation_api",
+        artifact = "javax.annotation:javax.annotation-api:1.2",
+        sha1 = "479c1e06db31c432330183f5cae684163f186146",
+    )
+
+def junit_junit():
+    native.maven_jar(
+        name = "junit_junit",
+        artifact = "junit:junit:4.12",
+        sha1 = "2973d150c0dc1fefe998f834810d68f278ea58ec",
+    )
+
+def org_apache_commons_lang3():
+    native.maven_jar(
+        name = "org_apache_commons_commons_lang3",
+        artifact = "org.apache.commons:commons-lang3:3.5",
+        sha1 = "6c6c702c89bfff3cd9e80b04d668c5e190d588c6",
+    )
+
+def org_codehaus_mojo_animal_sniffer_annotations():
+    native.maven_jar(
+        name = "org_codehaus_mojo_animal_sniffer_annotations",
+        artifact = "org.codehaus.mojo:animal-sniffer-annotations:1.17",
+        sha1 = "f97ce6decaea32b36101e37979f8b647f00681fb",
+    )
diff --git a/run-test-client.sh b/run-test-client.sh
new file mode 100755
index 0000000..ddcf236
--- /dev/null
+++ b/run-test-client.sh
@@ -0,0 +1,9 @@
+#!/bin/bash -e
+cd "$(dirname "$0")"
+cat >&2 <<EOF
+Gradle is no longer run automatically. Make sure to run
+'./gradlew installDist -PskipCodegen=true' or
+'./gradlew :grpc-interop-testing:installDist -PskipCodegen=true' after any
+changes. -PskipCodegen=true is optional, but requires less setup.
+EOF
+exec ./interop-testing/build/install/grpc-interop-testing/bin/test-client "$@"
diff --git a/run-test-server.sh b/run-test-server.sh
new file mode 100755
index 0000000..487328a
--- /dev/null
+++ b/run-test-server.sh
@@ -0,0 +1,9 @@
+#!/bin/bash -e
+cd "$(dirname "$0")"
+cat >&2 <<EOF
+Gradle is no longer run automatically. Make sure to run
+'./gradlew installDist -PskipCodegen=true' or
+'./gradlew :grpc-interop-testing:installDist -PskipCodegen=true' after any
+changes. -PskipCodegen=true is optional, but requires less setup.
+EOF
+exec ./interop-testing/build/install/grpc-interop-testing/bin/test-server "$@"
diff --git a/services/build.gradle b/services/build.gradle
new file mode 100644
index 0000000..108127d
--- /dev/null
+++ b/services/build.gradle
@@ -0,0 +1,27 @@
+// Add dependency on the protobuf plugin
+buildscript {
+    repositories {
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    }
+    dependencies { classpath libraries.protobuf_plugin }
+}
+
+description = "gRPC: Services"
+
+dependencies {
+    compile project(':grpc-protobuf'),
+            project(':grpc-stub')
+    compile (libraries.protobuf_util) {
+        // prefer 20.0 from libraries instead of 19.0
+        exclude group: 'com.google.guava', module: 'guava'
+    }
+    compile libraries.re2j
+
+    compileOnly libraries.javax_annotation
+    testCompile project(':grpc-testing'),
+            libraries.netty_epoll // for DomainSocketAddress
+    signature "org.codehaus.mojo.signature:java17:1.0@signature"
+}
+
+configureProtoCompilation()
diff --git a/services/src/generated/main/grpc/io/grpc/channelz/v1/ChannelzGrpc.java b/services/src/generated/main/grpc/io/grpc/channelz/v1/ChannelzGrpc.java
new file mode 100644
index 0000000..7150f28
--- /dev/null
+++ b/services/src/generated/main/grpc/io/grpc/channelz/v1/ChannelzGrpc.java
@@ -0,0 +1,751 @@
+package io.grpc.channelz.v1;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ * <pre>
+ * Channelz is a service exposed by gRPC servers that provides detailed debug
+ * information.
+ * </pre>
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: grpc/channelz/v1/channelz.proto")
+public final class ChannelzGrpc {
+
+  private ChannelzGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.channelz.v1.Channelz";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetTopChannelsRequest,
+      io.grpc.channelz.v1.GetTopChannelsResponse> getGetTopChannelsMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "GetTopChannels",
+      requestType = io.grpc.channelz.v1.GetTopChannelsRequest.class,
+      responseType = io.grpc.channelz.v1.GetTopChannelsResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetTopChannelsRequest,
+      io.grpc.channelz.v1.GetTopChannelsResponse> getGetTopChannelsMethod() {
+    io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetTopChannelsRequest, io.grpc.channelz.v1.GetTopChannelsResponse> getGetTopChannelsMethod;
+    if ((getGetTopChannelsMethod = ChannelzGrpc.getGetTopChannelsMethod) == null) {
+      synchronized (ChannelzGrpc.class) {
+        if ((getGetTopChannelsMethod = ChannelzGrpc.getGetTopChannelsMethod) == null) {
+          ChannelzGrpc.getGetTopChannelsMethod = getGetTopChannelsMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.channelz.v1.GetTopChannelsRequest, io.grpc.channelz.v1.GetTopChannelsResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.channelz.v1.Channelz", "GetTopChannels"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.channelz.v1.GetTopChannelsRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.channelz.v1.GetTopChannelsResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new ChannelzMethodDescriptorSupplier("GetTopChannels"))
+                  .build();
+          }
+        }
+     }
+     return getGetTopChannelsMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetServersRequest,
+      io.grpc.channelz.v1.GetServersResponse> getGetServersMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "GetServers",
+      requestType = io.grpc.channelz.v1.GetServersRequest.class,
+      responseType = io.grpc.channelz.v1.GetServersResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetServersRequest,
+      io.grpc.channelz.v1.GetServersResponse> getGetServersMethod() {
+    io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetServersRequest, io.grpc.channelz.v1.GetServersResponse> getGetServersMethod;
+    if ((getGetServersMethod = ChannelzGrpc.getGetServersMethod) == null) {
+      synchronized (ChannelzGrpc.class) {
+        if ((getGetServersMethod = ChannelzGrpc.getGetServersMethod) == null) {
+          ChannelzGrpc.getGetServersMethod = getGetServersMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.channelz.v1.GetServersRequest, io.grpc.channelz.v1.GetServersResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.channelz.v1.Channelz", "GetServers"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.channelz.v1.GetServersRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.channelz.v1.GetServersResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new ChannelzMethodDescriptorSupplier("GetServers"))
+                  .build();
+          }
+        }
+     }
+     return getGetServersMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetServerSocketsRequest,
+      io.grpc.channelz.v1.GetServerSocketsResponse> getGetServerSocketsMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "GetServerSockets",
+      requestType = io.grpc.channelz.v1.GetServerSocketsRequest.class,
+      responseType = io.grpc.channelz.v1.GetServerSocketsResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetServerSocketsRequest,
+      io.grpc.channelz.v1.GetServerSocketsResponse> getGetServerSocketsMethod() {
+    io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetServerSocketsRequest, io.grpc.channelz.v1.GetServerSocketsResponse> getGetServerSocketsMethod;
+    if ((getGetServerSocketsMethod = ChannelzGrpc.getGetServerSocketsMethod) == null) {
+      synchronized (ChannelzGrpc.class) {
+        if ((getGetServerSocketsMethod = ChannelzGrpc.getGetServerSocketsMethod) == null) {
+          ChannelzGrpc.getGetServerSocketsMethod = getGetServerSocketsMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.channelz.v1.GetServerSocketsRequest, io.grpc.channelz.v1.GetServerSocketsResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.channelz.v1.Channelz", "GetServerSockets"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.channelz.v1.GetServerSocketsRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.channelz.v1.GetServerSocketsResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new ChannelzMethodDescriptorSupplier("GetServerSockets"))
+                  .build();
+          }
+        }
+     }
+     return getGetServerSocketsMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetChannelRequest,
+      io.grpc.channelz.v1.GetChannelResponse> getGetChannelMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "GetChannel",
+      requestType = io.grpc.channelz.v1.GetChannelRequest.class,
+      responseType = io.grpc.channelz.v1.GetChannelResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetChannelRequest,
+      io.grpc.channelz.v1.GetChannelResponse> getGetChannelMethod() {
+    io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetChannelRequest, io.grpc.channelz.v1.GetChannelResponse> getGetChannelMethod;
+    if ((getGetChannelMethod = ChannelzGrpc.getGetChannelMethod) == null) {
+      synchronized (ChannelzGrpc.class) {
+        if ((getGetChannelMethod = ChannelzGrpc.getGetChannelMethod) == null) {
+          ChannelzGrpc.getGetChannelMethod = getGetChannelMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.channelz.v1.GetChannelRequest, io.grpc.channelz.v1.GetChannelResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.channelz.v1.Channelz", "GetChannel"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.channelz.v1.GetChannelRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.channelz.v1.GetChannelResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new ChannelzMethodDescriptorSupplier("GetChannel"))
+                  .build();
+          }
+        }
+     }
+     return getGetChannelMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetSubchannelRequest,
+      io.grpc.channelz.v1.GetSubchannelResponse> getGetSubchannelMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "GetSubchannel",
+      requestType = io.grpc.channelz.v1.GetSubchannelRequest.class,
+      responseType = io.grpc.channelz.v1.GetSubchannelResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetSubchannelRequest,
+      io.grpc.channelz.v1.GetSubchannelResponse> getGetSubchannelMethod() {
+    io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetSubchannelRequest, io.grpc.channelz.v1.GetSubchannelResponse> getGetSubchannelMethod;
+    if ((getGetSubchannelMethod = ChannelzGrpc.getGetSubchannelMethod) == null) {
+      synchronized (ChannelzGrpc.class) {
+        if ((getGetSubchannelMethod = ChannelzGrpc.getGetSubchannelMethod) == null) {
+          ChannelzGrpc.getGetSubchannelMethod = getGetSubchannelMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.channelz.v1.GetSubchannelRequest, io.grpc.channelz.v1.GetSubchannelResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.channelz.v1.Channelz", "GetSubchannel"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.channelz.v1.GetSubchannelRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.channelz.v1.GetSubchannelResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new ChannelzMethodDescriptorSupplier("GetSubchannel"))
+                  .build();
+          }
+        }
+     }
+     return getGetSubchannelMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetSocketRequest,
+      io.grpc.channelz.v1.GetSocketResponse> getGetSocketMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "GetSocket",
+      requestType = io.grpc.channelz.v1.GetSocketRequest.class,
+      responseType = io.grpc.channelz.v1.GetSocketResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetSocketRequest,
+      io.grpc.channelz.v1.GetSocketResponse> getGetSocketMethod() {
+    io.grpc.MethodDescriptor<io.grpc.channelz.v1.GetSocketRequest, io.grpc.channelz.v1.GetSocketResponse> getGetSocketMethod;
+    if ((getGetSocketMethod = ChannelzGrpc.getGetSocketMethod) == null) {
+      synchronized (ChannelzGrpc.class) {
+        if ((getGetSocketMethod = ChannelzGrpc.getGetSocketMethod) == null) {
+          ChannelzGrpc.getGetSocketMethod = getGetSocketMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.channelz.v1.GetSocketRequest, io.grpc.channelz.v1.GetSocketResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.channelz.v1.Channelz", "GetSocket"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.channelz.v1.GetSocketRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.channelz.v1.GetSocketResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new ChannelzMethodDescriptorSupplier("GetSocket"))
+                  .build();
+          }
+        }
+     }
+     return getGetSocketMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static ChannelzStub newStub(io.grpc.Channel channel) {
+    return new ChannelzStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static ChannelzBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new ChannelzBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static ChannelzFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new ChannelzFutureStub(channel);
+  }
+
+  /**
+   * <pre>
+   * Channelz is a service exposed by gRPC servers that provides detailed debug
+   * information.
+   * </pre>
+   */
+  public static abstract class ChannelzImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * Gets all root channels (i.e. channels the application has directly
+     * created). This does not include subchannels nor non-top level channels.
+     * </pre>
+     */
+    public void getTopChannels(io.grpc.channelz.v1.GetTopChannelsRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetTopChannelsResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getGetTopChannelsMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Gets all servers that exist in the process.
+     * </pre>
+     */
+    public void getServers(io.grpc.channelz.v1.GetServersRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetServersResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getGetServersMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Gets all server sockets that exist in the process.
+     * </pre>
+     */
+    public void getServerSockets(io.grpc.channelz.v1.GetServerSocketsRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetServerSocketsResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getGetServerSocketsMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Returns a single Channel, or else a NOT_FOUND code.
+     * </pre>
+     */
+    public void getChannel(io.grpc.channelz.v1.GetChannelRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetChannelResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getGetChannelMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Returns a single Subchannel, or else a NOT_FOUND code.
+     * </pre>
+     */
+    public void getSubchannel(io.grpc.channelz.v1.GetSubchannelRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetSubchannelResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getGetSubchannelMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Returns a single Socket or else a NOT_FOUND code.
+     * </pre>
+     */
+    public void getSocket(io.grpc.channelz.v1.GetSocketRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetSocketResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getGetSocketMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getGetTopChannelsMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.channelz.v1.GetTopChannelsRequest,
+                io.grpc.channelz.v1.GetTopChannelsResponse>(
+                  this, METHODID_GET_TOP_CHANNELS)))
+          .addMethod(
+            getGetServersMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.channelz.v1.GetServersRequest,
+                io.grpc.channelz.v1.GetServersResponse>(
+                  this, METHODID_GET_SERVERS)))
+          .addMethod(
+            getGetServerSocketsMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.channelz.v1.GetServerSocketsRequest,
+                io.grpc.channelz.v1.GetServerSocketsResponse>(
+                  this, METHODID_GET_SERVER_SOCKETS)))
+          .addMethod(
+            getGetChannelMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.channelz.v1.GetChannelRequest,
+                io.grpc.channelz.v1.GetChannelResponse>(
+                  this, METHODID_GET_CHANNEL)))
+          .addMethod(
+            getGetSubchannelMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.channelz.v1.GetSubchannelRequest,
+                io.grpc.channelz.v1.GetSubchannelResponse>(
+                  this, METHODID_GET_SUBCHANNEL)))
+          .addMethod(
+            getGetSocketMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.channelz.v1.GetSocketRequest,
+                io.grpc.channelz.v1.GetSocketResponse>(
+                  this, METHODID_GET_SOCKET)))
+          .build();
+    }
+  }
+
+  /**
+   * <pre>
+   * Channelz is a service exposed by gRPC servers that provides detailed debug
+   * information.
+   * </pre>
+   */
+  public static final class ChannelzStub extends io.grpc.stub.AbstractStub<ChannelzStub> {
+    private ChannelzStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ChannelzStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ChannelzStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ChannelzStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Gets all root channels (i.e. channels the application has directly
+     * created). This does not include subchannels nor non-top level channels.
+     * </pre>
+     */
+    public void getTopChannels(io.grpc.channelz.v1.GetTopChannelsRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetTopChannelsResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getGetTopChannelsMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Gets all servers that exist in the process.
+     * </pre>
+     */
+    public void getServers(io.grpc.channelz.v1.GetServersRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetServersResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getGetServersMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Gets all server sockets that exist in the process.
+     * </pre>
+     */
+    public void getServerSockets(io.grpc.channelz.v1.GetServerSocketsRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetServerSocketsResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getGetServerSocketsMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Returns a single Channel, or else a NOT_FOUND code.
+     * </pre>
+     */
+    public void getChannel(io.grpc.channelz.v1.GetChannelRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetChannelResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getGetChannelMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Returns a single Subchannel, or else a NOT_FOUND code.
+     * </pre>
+     */
+    public void getSubchannel(io.grpc.channelz.v1.GetSubchannelRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetSubchannelResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getGetSubchannelMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Returns a single Socket or else a NOT_FOUND code.
+     * </pre>
+     */
+    public void getSocket(io.grpc.channelz.v1.GetSocketRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetSocketResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getGetSocketMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   * <pre>
+   * Channelz is a service exposed by gRPC servers that provides detailed debug
+   * information.
+   * </pre>
+   */
+  public static final class ChannelzBlockingStub extends io.grpc.stub.AbstractStub<ChannelzBlockingStub> {
+    private ChannelzBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ChannelzBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ChannelzBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ChannelzBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Gets all root channels (i.e. channels the application has directly
+     * created). This does not include subchannels nor non-top level channels.
+     * </pre>
+     */
+    public io.grpc.channelz.v1.GetTopChannelsResponse getTopChannels(io.grpc.channelz.v1.GetTopChannelsRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getGetTopChannelsMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * Gets all servers that exist in the process.
+     * </pre>
+     */
+    public io.grpc.channelz.v1.GetServersResponse getServers(io.grpc.channelz.v1.GetServersRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getGetServersMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * Gets all server sockets that exist in the process.
+     * </pre>
+     */
+    public io.grpc.channelz.v1.GetServerSocketsResponse getServerSockets(io.grpc.channelz.v1.GetServerSocketsRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getGetServerSocketsMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * Returns a single Channel, or else a NOT_FOUND code.
+     * </pre>
+     */
+    public io.grpc.channelz.v1.GetChannelResponse getChannel(io.grpc.channelz.v1.GetChannelRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getGetChannelMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * Returns a single Subchannel, or else a NOT_FOUND code.
+     * </pre>
+     */
+    public io.grpc.channelz.v1.GetSubchannelResponse getSubchannel(io.grpc.channelz.v1.GetSubchannelRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getGetSubchannelMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * Returns a single Socket or else a NOT_FOUND code.
+     * </pre>
+     */
+    public io.grpc.channelz.v1.GetSocketResponse getSocket(io.grpc.channelz.v1.GetSocketRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getGetSocketMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   * <pre>
+   * Channelz is a service exposed by gRPC servers that provides detailed debug
+   * information.
+   * </pre>
+   */
+  public static final class ChannelzFutureStub extends io.grpc.stub.AbstractStub<ChannelzFutureStub> {
+    private ChannelzFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ChannelzFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ChannelzFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ChannelzFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Gets all root channels (i.e. channels the application has directly
+     * created). This does not include subchannels nor non-top level channels.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.channelz.v1.GetTopChannelsResponse> getTopChannels(
+        io.grpc.channelz.v1.GetTopChannelsRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getGetTopChannelsMethod(), getCallOptions()), request);
+    }
+
+    /**
+     * <pre>
+     * Gets all servers that exist in the process.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.channelz.v1.GetServersResponse> getServers(
+        io.grpc.channelz.v1.GetServersRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getGetServersMethod(), getCallOptions()), request);
+    }
+
+    /**
+     * <pre>
+     * Gets all server sockets that exist in the process.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.channelz.v1.GetServerSocketsResponse> getServerSockets(
+        io.grpc.channelz.v1.GetServerSocketsRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getGetServerSocketsMethod(), getCallOptions()), request);
+    }
+
+    /**
+     * <pre>
+     * Returns a single Channel, or else a NOT_FOUND code.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.channelz.v1.GetChannelResponse> getChannel(
+        io.grpc.channelz.v1.GetChannelRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getGetChannelMethod(), getCallOptions()), request);
+    }
+
+    /**
+     * <pre>
+     * Returns a single Subchannel, or else a NOT_FOUND code.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.channelz.v1.GetSubchannelResponse> getSubchannel(
+        io.grpc.channelz.v1.GetSubchannelRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getGetSubchannelMethod(), getCallOptions()), request);
+    }
+
+    /**
+     * <pre>
+     * Returns a single Socket or else a NOT_FOUND code.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.channelz.v1.GetSocketResponse> getSocket(
+        io.grpc.channelz.v1.GetSocketRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getGetSocketMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_GET_TOP_CHANNELS = 0;
+  private static final int METHODID_GET_SERVERS = 1;
+  private static final int METHODID_GET_SERVER_SOCKETS = 2;
+  private static final int METHODID_GET_CHANNEL = 3;
+  private static final int METHODID_GET_SUBCHANNEL = 4;
+  private static final int METHODID_GET_SOCKET = 5;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final ChannelzImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(ChannelzImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_GET_TOP_CHANNELS:
+          serviceImpl.getTopChannels((io.grpc.channelz.v1.GetTopChannelsRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetTopChannelsResponse>) responseObserver);
+          break;
+        case METHODID_GET_SERVERS:
+          serviceImpl.getServers((io.grpc.channelz.v1.GetServersRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetServersResponse>) responseObserver);
+          break;
+        case METHODID_GET_SERVER_SOCKETS:
+          serviceImpl.getServerSockets((io.grpc.channelz.v1.GetServerSocketsRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetServerSocketsResponse>) responseObserver);
+          break;
+        case METHODID_GET_CHANNEL:
+          serviceImpl.getChannel((io.grpc.channelz.v1.GetChannelRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetChannelResponse>) responseObserver);
+          break;
+        case METHODID_GET_SUBCHANNEL:
+          serviceImpl.getSubchannel((io.grpc.channelz.v1.GetSubchannelRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetSubchannelResponse>) responseObserver);
+          break;
+        case METHODID_GET_SOCKET:
+          serviceImpl.getSocket((io.grpc.channelz.v1.GetSocketRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.channelz.v1.GetSocketResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class ChannelzBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    ChannelzBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("Channelz");
+    }
+  }
+
+  private static final class ChannelzFileDescriptorSupplier
+      extends ChannelzBaseDescriptorSupplier {
+    ChannelzFileDescriptorSupplier() {}
+  }
+
+  private static final class ChannelzMethodDescriptorSupplier
+      extends ChannelzBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    ChannelzMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (ChannelzGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new ChannelzFileDescriptorSupplier())
+              .addMethod(getGetTopChannelsMethod())
+              .addMethod(getGetServersMethod())
+              .addMethod(getGetServerSocketsMethod())
+              .addMethod(getGetChannelMethod())
+              .addMethod(getGetSubchannelMethod())
+              .addMethod(getGetSocketMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/services/src/generated/main/grpc/io/grpc/health/v1/HealthGrpc.java b/services/src/generated/main/grpc/io/grpc/health/v1/HealthGrpc.java
new file mode 100644
index 0000000..295a747
--- /dev/null
+++ b/services/src/generated/main/grpc/io/grpc/health/v1/HealthGrpc.java
@@ -0,0 +1,280 @@
+package io.grpc.health.v1;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: grpc/health/v1/health.proto")
+public final class HealthGrpc {
+
+  private HealthGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.health.v1.Health";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.health.v1.HealthCheckRequest,
+      io.grpc.health.v1.HealthCheckResponse> getCheckMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "Check",
+      requestType = io.grpc.health.v1.HealthCheckRequest.class,
+      responseType = io.grpc.health.v1.HealthCheckResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.health.v1.HealthCheckRequest,
+      io.grpc.health.v1.HealthCheckResponse> getCheckMethod() {
+    io.grpc.MethodDescriptor<io.grpc.health.v1.HealthCheckRequest, io.grpc.health.v1.HealthCheckResponse> getCheckMethod;
+    if ((getCheckMethod = HealthGrpc.getCheckMethod) == null) {
+      synchronized (HealthGrpc.class) {
+        if ((getCheckMethod = HealthGrpc.getCheckMethod) == null) {
+          HealthGrpc.getCheckMethod = getCheckMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.health.v1.HealthCheckRequest, io.grpc.health.v1.HealthCheckResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.health.v1.Health", "Check"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.health.v1.HealthCheckRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.health.v1.HealthCheckResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new HealthMethodDescriptorSupplier("Check"))
+                  .build();
+          }
+        }
+     }
+     return getCheckMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static HealthStub newStub(io.grpc.Channel channel) {
+    return new HealthStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static HealthBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new HealthBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static HealthFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new HealthFutureStub(channel);
+  }
+
+  /**
+   */
+  public static abstract class HealthImplBase implements io.grpc.BindableService {
+
+    /**
+     */
+    public void check(io.grpc.health.v1.HealthCheckRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.health.v1.HealthCheckResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getCheckMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getCheckMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.health.v1.HealthCheckRequest,
+                io.grpc.health.v1.HealthCheckResponse>(
+                  this, METHODID_CHECK)))
+          .build();
+    }
+  }
+
+  /**
+   */
+  public static final class HealthStub extends io.grpc.stub.AbstractStub<HealthStub> {
+    private HealthStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private HealthStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected HealthStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new HealthStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public void check(io.grpc.health.v1.HealthCheckRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.health.v1.HealthCheckResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getCheckMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   */
+  public static final class HealthBlockingStub extends io.grpc.stub.AbstractStub<HealthBlockingStub> {
+    private HealthBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private HealthBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected HealthBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new HealthBlockingStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public io.grpc.health.v1.HealthCheckResponse check(io.grpc.health.v1.HealthCheckRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getCheckMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   */
+  public static final class HealthFutureStub extends io.grpc.stub.AbstractStub<HealthFutureStub> {
+    private HealthFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private HealthFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected HealthFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new HealthFutureStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.health.v1.HealthCheckResponse> check(
+        io.grpc.health.v1.HealthCheckRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getCheckMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_CHECK = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final HealthImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(HealthImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_CHECK:
+          serviceImpl.check((io.grpc.health.v1.HealthCheckRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.health.v1.HealthCheckResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class HealthBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    HealthBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.health.v1.HealthProto.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("Health");
+    }
+  }
+
+  private static final class HealthFileDescriptorSupplier
+      extends HealthBaseDescriptorSupplier {
+    HealthFileDescriptorSupplier() {}
+  }
+
+  private static final class HealthMethodDescriptorSupplier
+      extends HealthBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    HealthMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (HealthGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new HealthFileDescriptorSupplier())
+              .addMethod(getCheckMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/services/src/generated/main/grpc/io/grpc/reflection/v1alpha/ServerReflectionGrpc.java b/services/src/generated/main/grpc/io/grpc/reflection/v1alpha/ServerReflectionGrpc.java
new file mode 100644
index 0000000..78f192c
--- /dev/null
+++ b/services/src/generated/main/grpc/io/grpc/reflection/v1alpha/ServerReflectionGrpc.java
@@ -0,0 +1,272 @@
+package io.grpc.reflection.v1alpha;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: io/grpc/reflection/v1alpha/reflection.proto")
+public final class ServerReflectionGrpc {
+
+  private ServerReflectionGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.reflection.v1alpha.ServerReflection";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.reflection.v1alpha.ServerReflectionRequest,
+      io.grpc.reflection.v1alpha.ServerReflectionResponse> getServerReflectionInfoMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "ServerReflectionInfo",
+      requestType = io.grpc.reflection.v1alpha.ServerReflectionRequest.class,
+      responseType = io.grpc.reflection.v1alpha.ServerReflectionResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.reflection.v1alpha.ServerReflectionRequest,
+      io.grpc.reflection.v1alpha.ServerReflectionResponse> getServerReflectionInfoMethod() {
+    io.grpc.MethodDescriptor<io.grpc.reflection.v1alpha.ServerReflectionRequest, io.grpc.reflection.v1alpha.ServerReflectionResponse> getServerReflectionInfoMethod;
+    if ((getServerReflectionInfoMethod = ServerReflectionGrpc.getServerReflectionInfoMethod) == null) {
+      synchronized (ServerReflectionGrpc.class) {
+        if ((getServerReflectionInfoMethod = ServerReflectionGrpc.getServerReflectionInfoMethod) == null) {
+          ServerReflectionGrpc.getServerReflectionInfoMethod = getServerReflectionInfoMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.reflection.v1alpha.ServerReflectionRequest, io.grpc.reflection.v1alpha.ServerReflectionResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.reflection.v1alpha.ServerReflection", "ServerReflectionInfo"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.reflection.v1alpha.ServerReflectionRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.reflection.v1alpha.ServerReflectionResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new ServerReflectionMethodDescriptorSupplier("ServerReflectionInfo"))
+                  .build();
+          }
+        }
+     }
+     return getServerReflectionInfoMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static ServerReflectionStub newStub(io.grpc.Channel channel) {
+    return new ServerReflectionStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static ServerReflectionBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new ServerReflectionBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static ServerReflectionFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new ServerReflectionFutureStub(channel);
+  }
+
+  /**
+   */
+  public static abstract class ServerReflectionImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * The reflection service is structured as a bidirectional stream, ensuring
+     * all related requests go to a single server.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.reflection.v1alpha.ServerReflectionRequest> serverReflectionInfo(
+        io.grpc.stub.StreamObserver<io.grpc.reflection.v1alpha.ServerReflectionResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getServerReflectionInfoMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getServerReflectionInfoMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.reflection.v1alpha.ServerReflectionRequest,
+                io.grpc.reflection.v1alpha.ServerReflectionResponse>(
+                  this, METHODID_SERVER_REFLECTION_INFO)))
+          .build();
+    }
+  }
+
+  /**
+   */
+  public static final class ServerReflectionStub extends io.grpc.stub.AbstractStub<ServerReflectionStub> {
+    private ServerReflectionStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ServerReflectionStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ServerReflectionStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ServerReflectionStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * The reflection service is structured as a bidirectional stream, ensuring
+     * all related requests go to a single server.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.reflection.v1alpha.ServerReflectionRequest> serverReflectionInfo(
+        io.grpc.stub.StreamObserver<io.grpc.reflection.v1alpha.ServerReflectionResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getServerReflectionInfoMethod(), getCallOptions()), responseObserver);
+    }
+  }
+
+  /**
+   */
+  public static final class ServerReflectionBlockingStub extends io.grpc.stub.AbstractStub<ServerReflectionBlockingStub> {
+    private ServerReflectionBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ServerReflectionBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ServerReflectionBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ServerReflectionBlockingStub(channel, callOptions);
+    }
+  }
+
+  /**
+   */
+  public static final class ServerReflectionFutureStub extends io.grpc.stub.AbstractStub<ServerReflectionFutureStub> {
+    private ServerReflectionFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ServerReflectionFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ServerReflectionFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ServerReflectionFutureStub(channel, callOptions);
+    }
+  }
+
+  private static final int METHODID_SERVER_REFLECTION_INFO = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final ServerReflectionImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(ServerReflectionImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_SERVER_REFLECTION_INFO:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.serverReflectionInfo(
+              (io.grpc.stub.StreamObserver<io.grpc.reflection.v1alpha.ServerReflectionResponse>) responseObserver);
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class ServerReflectionBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    ServerReflectionBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("ServerReflection");
+    }
+  }
+
+  private static final class ServerReflectionFileDescriptorSupplier
+      extends ServerReflectionBaseDescriptorSupplier {
+    ServerReflectionFileDescriptorSupplier() {}
+  }
+
+  private static final class ServerReflectionMethodDescriptorSupplier
+      extends ServerReflectionBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    ServerReflectionMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (ServerReflectionGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new ServerReflectionFileDescriptorSupplier())
+              .addMethod(getServerReflectionInfoMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1alpha/BinaryLogProto.java b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/BinaryLogProto.java
new file mode 100644
index 0000000..5b6a5e6
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/BinaryLogProto.java
@@ -0,0 +1,143 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1alpha/binarylog.proto
+
+package io.grpc.binarylog.v1alpha;
+
+public final class BinaryLogProto {
+  private BinaryLogProto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_binarylog_v1alpha_GrpcLogEntry_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1alpha_GrpcLogEntry_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_binarylog_v1alpha_Message_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1alpha_Message_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_binarylog_v1alpha_Metadata_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1alpha_Metadata_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_binarylog_v1alpha_MetadataEntry_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1alpha_MetadataEntry_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_binarylog_v1alpha_Peer_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1alpha_Peer_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_binarylog_v1alpha_Uint128_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_binarylog_v1alpha_Uint128_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n#grpc/binlog/v1alpha/binarylog.proto\022\026g" +
+      "rpc.binarylog.v1alpha\032\036google/protobuf/d" +
+      "uration.proto\"\364\005\n\014GrpcLogEntry\0227\n\004type\030\001" +
+      " \001(\0162).grpc.binarylog.v1alpha.GrpcLogEnt" +
+      "ry.Type\022;\n\006logger\030\002 \001(\0162+.grpc.binarylog" +
+      ".v1alpha.GrpcLogEntry.Logger\0220\n\007call_id\030" +
+      "\003 \001(\0132\037.grpc.binarylog.v1alpha.Uint128\0224" +
+      "\n\010metadata\030\004 \001(\0132 .grpc.binarylog.v1alph" +
+      "a.MetadataH\000\0222\n\007message\030\005 \001(\0132\037.grpc.bin" +
+      "arylog.v1alpha.MessageH\000\022*\n\004peer\030\006 \001(\0132\034" +
+      ".grpc.binarylog.v1alpha.Peer\022\021\n\ttruncate" +
+      "d\030\007 \001(\010\022\023\n\013method_name\030\010 \001(\t\022\023\n\013status_c" +
+      "ode\030\t \001(\r\022\026\n\016status_message\030\n \001(\t\022\026\n\016sta" +
+      "tus_details\030\013 \001(\014\022*\n\007timeout\030\014 \001(\0132\031.goo" +
+      "gle.protobuf.Duration\022\037\n\027sequence_id_wit" +
+      "hin_call\030\r \001(\r\"\252\001\n\004Type\022\020\n\014UNKNOWN_TYPE\020" +
+      "\000\022\031\n\025SEND_INITIAL_METADATA\020\001\022\032\n\026SEND_TRA" +
+      "ILING_METADATA\020\002\022\020\n\014SEND_MESSAGE\020\003\022\031\n\025RE" +
+      "CV_INITIAL_METADATA\020\004\022\032\n\026RECV_TRAILING_M" +
+      "ETADATA\020\005\022\020\n\014RECV_MESSAGE\020\006\"4\n\006Logger\022\022\n" +
+      "\016UNKNOWN_LOGGER\020\000\022\n\n\006CLIENT\020\001\022\n\n\006SERVER\020" +
+      "\002B\t\n\007payload\"6\n\007Message\022\r\n\005flags\030\001 \001(\r\022\016" +
+      "\n\006length\030\002 \001(\r\022\014\n\004data\030\003 \001(\014\"@\n\010Metadata" +
+      "\0224\n\005entry\030\001 \003(\0132%.grpc.binarylog.v1alpha" +
+      ".MetadataEntry\"+\n\rMetadataEntry\022\013\n\003key\030\001" +
+      " \001(\014\022\r\n\005value\030\002 \001(\014\"\277\001\n\004Peer\0228\n\tpeer_typ" +
+      "e\030\001 \001(\0162%.grpc.binarylog.v1alpha.Peer.Pe" +
+      "erType\022\014\n\004peer\030\002 \001(\014\022\017\n\007address\030\003 \001(\t\022\017\n" +
+      "\007ip_port\030\004 \001(\r\"M\n\010PeerType\022\024\n\020UNKNOWN_PE" +
+      "ERTYPE\020\000\022\r\n\tPEER_IPV4\020\001\022\r\n\tPEER_IPV6\020\002\022\r" +
+      "\n\tPEER_UNIX\020\003\"$\n\007Uint128\022\014\n\004high\030\001 \001(\006\022\013" +
+      "\n\003low\030\002 \001(\006B-\n\031io.grpc.binarylog.v1alpha" +
+      "B\016BinaryLogProtoP\001b\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          com.google.protobuf.DurationProto.getDescriptor(),
+        }, assigner);
+    internal_static_grpc_binarylog_v1alpha_GrpcLogEntry_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_binarylog_v1alpha_GrpcLogEntry_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1alpha_GrpcLogEntry_descriptor,
+        new java.lang.String[] { "Type", "Logger", "CallId", "Metadata", "Message", "Peer", "Truncated", "MethodName", "StatusCode", "StatusMessage", "StatusDetails", "Timeout", "SequenceIdWithinCall", "Payload", });
+    internal_static_grpc_binarylog_v1alpha_Message_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_binarylog_v1alpha_Message_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1alpha_Message_descriptor,
+        new java.lang.String[] { "Flags", "Length", "Data", });
+    internal_static_grpc_binarylog_v1alpha_Metadata_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_binarylog_v1alpha_Metadata_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1alpha_Metadata_descriptor,
+        new java.lang.String[] { "Entry", });
+    internal_static_grpc_binarylog_v1alpha_MetadataEntry_descriptor =
+      getDescriptor().getMessageTypes().get(3);
+    internal_static_grpc_binarylog_v1alpha_MetadataEntry_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1alpha_MetadataEntry_descriptor,
+        new java.lang.String[] { "Key", "Value", });
+    internal_static_grpc_binarylog_v1alpha_Peer_descriptor =
+      getDescriptor().getMessageTypes().get(4);
+    internal_static_grpc_binarylog_v1alpha_Peer_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1alpha_Peer_descriptor,
+        new java.lang.String[] { "PeerType", "Peer", "Address", "IpPort", });
+    internal_static_grpc_binarylog_v1alpha_Uint128_descriptor =
+      getDescriptor().getMessageTypes().get(5);
+    internal_static_grpc_binarylog_v1alpha_Uint128_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_binarylog_v1alpha_Uint128_descriptor,
+        new java.lang.String[] { "High", "Low", });
+    com.google.protobuf.DurationProto.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1alpha/GrpcLogEntry.java b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/GrpcLogEntry.java
new file mode 100644
index 0000000..be928ad
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/GrpcLogEntry.java
@@ -0,0 +1,2839 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1alpha/binarylog.proto
+
+package io.grpc.binarylog.v1alpha;
+
+/**
+ * <pre>
+ * Log entry we store in binary logs
+ * </pre>
+ *
+ * Protobuf type {@code grpc.binarylog.v1alpha.GrpcLogEntry}
+ */
+public  final class GrpcLogEntry extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.v1alpha.GrpcLogEntry)
+    GrpcLogEntryOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GrpcLogEntry.newBuilder() to construct.
+  private GrpcLogEntry(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GrpcLogEntry() {
+    type_ = 0;
+    logger_ = 0;
+    truncated_ = false;
+    methodName_ = "";
+    statusCode_ = 0;
+    statusMessage_ = "";
+    statusDetails_ = com.google.protobuf.ByteString.EMPTY;
+    sequenceIdWithinCall_ = 0;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GrpcLogEntry(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+            int rawValue = input.readEnum();
+
+            type_ = rawValue;
+            break;
+          }
+          case 16: {
+            int rawValue = input.readEnum();
+
+            logger_ = rawValue;
+            break;
+          }
+          case 26: {
+            io.grpc.binarylog.v1alpha.Uint128.Builder subBuilder = null;
+            if (callId_ != null) {
+              subBuilder = callId_.toBuilder();
+            }
+            callId_ = input.readMessage(io.grpc.binarylog.v1alpha.Uint128.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(callId_);
+              callId_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 34: {
+            io.grpc.binarylog.v1alpha.Metadata.Builder subBuilder = null;
+            if (payloadCase_ == 4) {
+              subBuilder = ((io.grpc.binarylog.v1alpha.Metadata) payload_).toBuilder();
+            }
+            payload_ =
+                input.readMessage(io.grpc.binarylog.v1alpha.Metadata.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.binarylog.v1alpha.Metadata) payload_);
+              payload_ = subBuilder.buildPartial();
+            }
+            payloadCase_ = 4;
+            break;
+          }
+          case 42: {
+            io.grpc.binarylog.v1alpha.Message.Builder subBuilder = null;
+            if (payloadCase_ == 5) {
+              subBuilder = ((io.grpc.binarylog.v1alpha.Message) payload_).toBuilder();
+            }
+            payload_ =
+                input.readMessage(io.grpc.binarylog.v1alpha.Message.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.binarylog.v1alpha.Message) payload_);
+              payload_ = subBuilder.buildPartial();
+            }
+            payloadCase_ = 5;
+            break;
+          }
+          case 50: {
+            io.grpc.binarylog.v1alpha.Peer.Builder subBuilder = null;
+            if (peer_ != null) {
+              subBuilder = peer_.toBuilder();
+            }
+            peer_ = input.readMessage(io.grpc.binarylog.v1alpha.Peer.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(peer_);
+              peer_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 56: {
+
+            truncated_ = input.readBool();
+            break;
+          }
+          case 66: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            methodName_ = s;
+            break;
+          }
+          case 72: {
+
+            statusCode_ = input.readUInt32();
+            break;
+          }
+          case 82: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            statusMessage_ = s;
+            break;
+          }
+          case 90: {
+
+            statusDetails_ = input.readBytes();
+            break;
+          }
+          case 98: {
+            com.google.protobuf.Duration.Builder subBuilder = null;
+            if (timeout_ != null) {
+              subBuilder = timeout_.toBuilder();
+            }
+            timeout_ = input.readMessage(com.google.protobuf.Duration.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(timeout_);
+              timeout_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 104: {
+
+            sequenceIdWithinCall_ = input.readUInt32();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_GrpcLogEntry_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_GrpcLogEntry_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1alpha.GrpcLogEntry.class, io.grpc.binarylog.v1alpha.GrpcLogEntry.Builder.class);
+  }
+
+  /**
+   * <pre>
+   * Enumerates the type of logs
+   * </pre>
+   *
+   * Protobuf enum {@code grpc.binarylog.v1alpha.GrpcLogEntry.Type}
+   */
+  public enum Type
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>UNKNOWN_TYPE = 0;</code>
+     */
+    UNKNOWN_TYPE(0),
+    /**
+     * <code>SEND_INITIAL_METADATA = 1;</code>
+     */
+    SEND_INITIAL_METADATA(1),
+    /**
+     * <code>SEND_TRAILING_METADATA = 2;</code>
+     */
+    SEND_TRAILING_METADATA(2),
+    /**
+     * <code>SEND_MESSAGE = 3;</code>
+     */
+    SEND_MESSAGE(3),
+    /**
+     * <code>RECV_INITIAL_METADATA = 4;</code>
+     */
+    RECV_INITIAL_METADATA(4),
+    /**
+     * <code>RECV_TRAILING_METADATA = 5;</code>
+     */
+    RECV_TRAILING_METADATA(5),
+    /**
+     * <code>RECV_MESSAGE = 6;</code>
+     */
+    RECV_MESSAGE(6),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <code>UNKNOWN_TYPE = 0;</code>
+     */
+    public static final int UNKNOWN_TYPE_VALUE = 0;
+    /**
+     * <code>SEND_INITIAL_METADATA = 1;</code>
+     */
+    public static final int SEND_INITIAL_METADATA_VALUE = 1;
+    /**
+     * <code>SEND_TRAILING_METADATA = 2;</code>
+     */
+    public static final int SEND_TRAILING_METADATA_VALUE = 2;
+    /**
+     * <code>SEND_MESSAGE = 3;</code>
+     */
+    public static final int SEND_MESSAGE_VALUE = 3;
+    /**
+     * <code>RECV_INITIAL_METADATA = 4;</code>
+     */
+    public static final int RECV_INITIAL_METADATA_VALUE = 4;
+    /**
+     * <code>RECV_TRAILING_METADATA = 5;</code>
+     */
+    public static final int RECV_TRAILING_METADATA_VALUE = 5;
+    /**
+     * <code>RECV_MESSAGE = 6;</code>
+     */
+    public static final int RECV_MESSAGE_VALUE = 6;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static Type valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static Type forNumber(int value) {
+      switch (value) {
+        case 0: return UNKNOWN_TYPE;
+        case 1: return SEND_INITIAL_METADATA;
+        case 2: return SEND_TRAILING_METADATA;
+        case 3: return SEND_MESSAGE;
+        case 4: return RECV_INITIAL_METADATA;
+        case 5: return RECV_TRAILING_METADATA;
+        case 6: return RECV_MESSAGE;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<Type>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        Type> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<Type>() {
+            public Type findValueByNumber(int number) {
+              return Type.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1alpha.GrpcLogEntry.getDescriptor().getEnumTypes().get(0);
+    }
+
+    private static final Type[] VALUES = values();
+
+    public static Type valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private Type(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.binarylog.v1alpha.GrpcLogEntry.Type)
+  }
+
+  /**
+   * <pre>
+   * Enumerates the entity that generates the log entry
+   * </pre>
+   *
+   * Protobuf enum {@code grpc.binarylog.v1alpha.GrpcLogEntry.Logger}
+   */
+  public enum Logger
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>UNKNOWN_LOGGER = 0;</code>
+     */
+    UNKNOWN_LOGGER(0),
+    /**
+     * <code>CLIENT = 1;</code>
+     */
+    CLIENT(1),
+    /**
+     * <code>SERVER = 2;</code>
+     */
+    SERVER(2),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <code>UNKNOWN_LOGGER = 0;</code>
+     */
+    public static final int UNKNOWN_LOGGER_VALUE = 0;
+    /**
+     * <code>CLIENT = 1;</code>
+     */
+    public static final int CLIENT_VALUE = 1;
+    /**
+     * <code>SERVER = 2;</code>
+     */
+    public static final int SERVER_VALUE = 2;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static Logger valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static Logger forNumber(int value) {
+      switch (value) {
+        case 0: return UNKNOWN_LOGGER;
+        case 1: return CLIENT;
+        case 2: return SERVER;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<Logger>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        Logger> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<Logger>() {
+            public Logger findValueByNumber(int number) {
+              return Logger.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1alpha.GrpcLogEntry.getDescriptor().getEnumTypes().get(1);
+    }
+
+    private static final Logger[] VALUES = values();
+
+    public static Logger valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private Logger(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.binarylog.v1alpha.GrpcLogEntry.Logger)
+  }
+
+  private int payloadCase_ = 0;
+  private java.lang.Object payload_;
+  public enum PayloadCase
+      implements com.google.protobuf.Internal.EnumLite {
+    METADATA(4),
+    MESSAGE(5),
+    PAYLOAD_NOT_SET(0);
+    private final int value;
+    private PayloadCase(int value) {
+      this.value = value;
+    }
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static PayloadCase valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static PayloadCase forNumber(int value) {
+      switch (value) {
+        case 4: return METADATA;
+        case 5: return MESSAGE;
+        case 0: return PAYLOAD_NOT_SET;
+        default: return null;
+      }
+    }
+    public int getNumber() {
+      return this.value;
+    }
+  };
+
+  public PayloadCase
+  getPayloadCase() {
+    return PayloadCase.forNumber(
+        payloadCase_);
+  }
+
+  public static final int TYPE_FIELD_NUMBER = 1;
+  private int type_;
+  /**
+   * <pre>
+   * One of the above Type enum
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Type type = 1;</code>
+   */
+  public int getTypeValue() {
+    return type_;
+  }
+  /**
+   * <pre>
+   * One of the above Type enum
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Type type = 1;</code>
+   */
+  public io.grpc.binarylog.v1alpha.GrpcLogEntry.Type getType() {
+    io.grpc.binarylog.v1alpha.GrpcLogEntry.Type result = io.grpc.binarylog.v1alpha.GrpcLogEntry.Type.valueOf(type_);
+    return result == null ? io.grpc.binarylog.v1alpha.GrpcLogEntry.Type.UNRECOGNIZED : result;
+  }
+
+  public static final int LOGGER_FIELD_NUMBER = 2;
+  private int logger_;
+  /**
+   * <pre>
+   * One of the above Logger enum
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Logger logger = 2;</code>
+   */
+  public int getLoggerValue() {
+    return logger_;
+  }
+  /**
+   * <pre>
+   * One of the above Logger enum
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Logger logger = 2;</code>
+   */
+  public io.grpc.binarylog.v1alpha.GrpcLogEntry.Logger getLogger() {
+    io.grpc.binarylog.v1alpha.GrpcLogEntry.Logger result = io.grpc.binarylog.v1alpha.GrpcLogEntry.Logger.valueOf(logger_);
+    return result == null ? io.grpc.binarylog.v1alpha.GrpcLogEntry.Logger.UNRECOGNIZED : result;
+  }
+
+  public static final int CALL_ID_FIELD_NUMBER = 3;
+  private io.grpc.binarylog.v1alpha.Uint128 callId_;
+  /**
+   * <pre>
+   * Uniquely identifies a call. Each call may have several log entries, they
+   * will share the same call_id. 128 bits split into 2 64-bit parts.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+   */
+  public boolean hasCallId() {
+    return callId_ != null;
+  }
+  /**
+   * <pre>
+   * Uniquely identifies a call. Each call may have several log entries, they
+   * will share the same call_id. 128 bits split into 2 64-bit parts.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+   */
+  public io.grpc.binarylog.v1alpha.Uint128 getCallId() {
+    return callId_ == null ? io.grpc.binarylog.v1alpha.Uint128.getDefaultInstance() : callId_;
+  }
+  /**
+   * <pre>
+   * Uniquely identifies a call. Each call may have several log entries, they
+   * will share the same call_id. 128 bits split into 2 64-bit parts.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+   */
+  public io.grpc.binarylog.v1alpha.Uint128OrBuilder getCallIdOrBuilder() {
+    return getCallId();
+  }
+
+  public static final int METADATA_FIELD_NUMBER = 4;
+  /**
+   * <pre>
+   * Used by {SEND,RECV}_INITIAL_METADATA and
+   * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+   * from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+   */
+  public boolean hasMetadata() {
+    return payloadCase_ == 4;
+  }
+  /**
+   * <pre>
+   * Used by {SEND,RECV}_INITIAL_METADATA and
+   * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+   * from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+   */
+  public io.grpc.binarylog.v1alpha.Metadata getMetadata() {
+    if (payloadCase_ == 4) {
+       return (io.grpc.binarylog.v1alpha.Metadata) payload_;
+    }
+    return io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance();
+  }
+  /**
+   * <pre>
+   * Used by {SEND,RECV}_INITIAL_METADATA and
+   * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+   * from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+   */
+  public io.grpc.binarylog.v1alpha.MetadataOrBuilder getMetadataOrBuilder() {
+    if (payloadCase_ == 4) {
+       return (io.grpc.binarylog.v1alpha.Metadata) payload_;
+    }
+    return io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance();
+  }
+
+  public static final int MESSAGE_FIELD_NUMBER = 5;
+  /**
+   * <pre>
+   * Used by {SEND,RECV}_MESSAGE
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+   */
+  public boolean hasMessage() {
+    return payloadCase_ == 5;
+  }
+  /**
+   * <pre>
+   * Used by {SEND,RECV}_MESSAGE
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+   */
+  public io.grpc.binarylog.v1alpha.Message getMessage() {
+    if (payloadCase_ == 5) {
+       return (io.grpc.binarylog.v1alpha.Message) payload_;
+    }
+    return io.grpc.binarylog.v1alpha.Message.getDefaultInstance();
+  }
+  /**
+   * <pre>
+   * Used by {SEND,RECV}_MESSAGE
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+   */
+  public io.grpc.binarylog.v1alpha.MessageOrBuilder getMessageOrBuilder() {
+    if (payloadCase_ == 5) {
+       return (io.grpc.binarylog.v1alpha.Message) payload_;
+    }
+    return io.grpc.binarylog.v1alpha.Message.getDefaultInstance();
+  }
+
+  public static final int PEER_FIELD_NUMBER = 6;
+  private io.grpc.binarylog.v1alpha.Peer peer_;
+  /**
+   * <pre>
+   * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+   * and RECV_INITIAL_METADATA entries.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+   */
+  public boolean hasPeer() {
+    return peer_ != null;
+  }
+  /**
+   * <pre>
+   * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+   * and RECV_INITIAL_METADATA entries.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+   */
+  public io.grpc.binarylog.v1alpha.Peer getPeer() {
+    return peer_ == null ? io.grpc.binarylog.v1alpha.Peer.getDefaultInstance() : peer_;
+  }
+  /**
+   * <pre>
+   * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+   * and RECV_INITIAL_METADATA entries.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+   */
+  public io.grpc.binarylog.v1alpha.PeerOrBuilder getPeerOrBuilder() {
+    return getPeer();
+  }
+
+  public static final int TRUNCATED_FIELD_NUMBER = 7;
+  private boolean truncated_;
+  /**
+   * <pre>
+   * true if payload does not represent the full message or metadata.
+   * </pre>
+   *
+   * <code>bool truncated = 7;</code>
+   */
+  public boolean getTruncated() {
+    return truncated_;
+  }
+
+  public static final int METHOD_NAME_FIELD_NUMBER = 8;
+  private volatile java.lang.Object methodName_;
+  /**
+   * <pre>
+   * The method name. Logged for the first entry:
+   * RECV_INITIAL_METADATA for server side or
+   * SEND_INITIAL_METADATA for client side.
+   * </pre>
+   *
+   * <code>string method_name = 8;</code>
+   */
+  public java.lang.String getMethodName() {
+    java.lang.Object ref = methodName_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      methodName_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * The method name. Logged for the first entry:
+   * RECV_INITIAL_METADATA for server side or
+   * SEND_INITIAL_METADATA for client side.
+   * </pre>
+   *
+   * <code>string method_name = 8;</code>
+   */
+  public com.google.protobuf.ByteString
+      getMethodNameBytes() {
+    java.lang.Object ref = methodName_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      methodName_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int STATUS_CODE_FIELD_NUMBER = 9;
+  private int statusCode_;
+  /**
+   * <pre>
+   * status_code and status_message:
+   * Only present for SEND_TRAILING_METADATA on server side or
+   * RECV_TRAILING_METADATA on client side.
+   * </pre>
+   *
+   * <code>uint32 status_code = 9;</code>
+   */
+  public int getStatusCode() {
+    return statusCode_;
+  }
+
+  public static final int STATUS_MESSAGE_FIELD_NUMBER = 10;
+  private volatile java.lang.Object statusMessage_;
+  /**
+   * <pre>
+   * An original status message before any transport specific
+   * encoding.
+   * </pre>
+   *
+   * <code>string status_message = 10;</code>
+   */
+  public java.lang.String getStatusMessage() {
+    java.lang.Object ref = statusMessage_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      statusMessage_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * An original status message before any transport specific
+   * encoding.
+   * </pre>
+   *
+   * <code>string status_message = 10;</code>
+   */
+  public com.google.protobuf.ByteString
+      getStatusMessageBytes() {
+    java.lang.Object ref = statusMessage_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      statusMessage_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int STATUS_DETAILS_FIELD_NUMBER = 11;
+  private com.google.protobuf.ByteString statusDetails_;
+  /**
+   * <pre>
+   * The value of the 'grpc-status-details-bin' metadata key. If
+   * present, this is always an encoded 'google.rpc.Status' message.
+   * </pre>
+   *
+   * <code>bytes status_details = 11;</code>
+   */
+  public com.google.protobuf.ByteString getStatusDetails() {
+    return statusDetails_;
+  }
+
+  public static final int TIMEOUT_FIELD_NUMBER = 12;
+  private com.google.protobuf.Duration timeout_;
+  /**
+   * <pre>
+   * the RPC timeout
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration timeout = 12;</code>
+   */
+  public boolean hasTimeout() {
+    return timeout_ != null;
+  }
+  /**
+   * <pre>
+   * the RPC timeout
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration timeout = 12;</code>
+   */
+  public com.google.protobuf.Duration getTimeout() {
+    return timeout_ == null ? com.google.protobuf.Duration.getDefaultInstance() : timeout_;
+  }
+  /**
+   * <pre>
+   * the RPC timeout
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration timeout = 12;</code>
+   */
+  public com.google.protobuf.DurationOrBuilder getTimeoutOrBuilder() {
+    return getTimeout();
+  }
+
+  public static final int SEQUENCE_ID_WITHIN_CALL_FIELD_NUMBER = 13;
+  private int sequenceIdWithinCall_;
+  /**
+   * <pre>
+   * The entry sequence id for this call. The first GrpcLogEntry has a
+   * value of 1, to disambiguate from an unset value. The purpose of
+   * this field is to detect missing entries in environments where
+   * durability or ordering is not guaranteed.
+   * </pre>
+   *
+   * <code>uint32 sequence_id_within_call = 13;</code>
+   */
+  public int getSequenceIdWithinCall() {
+    return sequenceIdWithinCall_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (type_ != io.grpc.binarylog.v1alpha.GrpcLogEntry.Type.UNKNOWN_TYPE.getNumber()) {
+      output.writeEnum(1, type_);
+    }
+    if (logger_ != io.grpc.binarylog.v1alpha.GrpcLogEntry.Logger.UNKNOWN_LOGGER.getNumber()) {
+      output.writeEnum(2, logger_);
+    }
+    if (callId_ != null) {
+      output.writeMessage(3, getCallId());
+    }
+    if (payloadCase_ == 4) {
+      output.writeMessage(4, (io.grpc.binarylog.v1alpha.Metadata) payload_);
+    }
+    if (payloadCase_ == 5) {
+      output.writeMessage(5, (io.grpc.binarylog.v1alpha.Message) payload_);
+    }
+    if (peer_ != null) {
+      output.writeMessage(6, getPeer());
+    }
+    if (truncated_ != false) {
+      output.writeBool(7, truncated_);
+    }
+    if (!getMethodNameBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 8, methodName_);
+    }
+    if (statusCode_ != 0) {
+      output.writeUInt32(9, statusCode_);
+    }
+    if (!getStatusMessageBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 10, statusMessage_);
+    }
+    if (!statusDetails_.isEmpty()) {
+      output.writeBytes(11, statusDetails_);
+    }
+    if (timeout_ != null) {
+      output.writeMessage(12, getTimeout());
+    }
+    if (sequenceIdWithinCall_ != 0) {
+      output.writeUInt32(13, sequenceIdWithinCall_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (type_ != io.grpc.binarylog.v1alpha.GrpcLogEntry.Type.UNKNOWN_TYPE.getNumber()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeEnumSize(1, type_);
+    }
+    if (logger_ != io.grpc.binarylog.v1alpha.GrpcLogEntry.Logger.UNKNOWN_LOGGER.getNumber()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeEnumSize(2, logger_);
+    }
+    if (callId_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(3, getCallId());
+    }
+    if (payloadCase_ == 4) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(4, (io.grpc.binarylog.v1alpha.Metadata) payload_);
+    }
+    if (payloadCase_ == 5) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(5, (io.grpc.binarylog.v1alpha.Message) payload_);
+    }
+    if (peer_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(6, getPeer());
+    }
+    if (truncated_ != false) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBoolSize(7, truncated_);
+    }
+    if (!getMethodNameBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(8, methodName_);
+    }
+    if (statusCode_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(9, statusCode_);
+    }
+    if (!getStatusMessageBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(10, statusMessage_);
+    }
+    if (!statusDetails_.isEmpty()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBytesSize(11, statusDetails_);
+    }
+    if (timeout_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(12, getTimeout());
+    }
+    if (sequenceIdWithinCall_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(13, sequenceIdWithinCall_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.binarylog.v1alpha.GrpcLogEntry)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1alpha.GrpcLogEntry other = (io.grpc.binarylog.v1alpha.GrpcLogEntry) obj;
+
+    boolean result = true;
+    result = result && type_ == other.type_;
+    result = result && logger_ == other.logger_;
+    result = result && (hasCallId() == other.hasCallId());
+    if (hasCallId()) {
+      result = result && getCallId()
+          .equals(other.getCallId());
+    }
+    result = result && (hasPeer() == other.hasPeer());
+    if (hasPeer()) {
+      result = result && getPeer()
+          .equals(other.getPeer());
+    }
+    result = result && (getTruncated()
+        == other.getTruncated());
+    result = result && getMethodName()
+        .equals(other.getMethodName());
+    result = result && (getStatusCode()
+        == other.getStatusCode());
+    result = result && getStatusMessage()
+        .equals(other.getStatusMessage());
+    result = result && getStatusDetails()
+        .equals(other.getStatusDetails());
+    result = result && (hasTimeout() == other.hasTimeout());
+    if (hasTimeout()) {
+      result = result && getTimeout()
+          .equals(other.getTimeout());
+    }
+    result = result && (getSequenceIdWithinCall()
+        == other.getSequenceIdWithinCall());
+    result = result && getPayloadCase().equals(
+        other.getPayloadCase());
+    if (!result) return false;
+    switch (payloadCase_) {
+      case 4:
+        result = result && getMetadata()
+            .equals(other.getMetadata());
+        break;
+      case 5:
+        result = result && getMessage()
+            .equals(other.getMessage());
+        break;
+      case 0:
+      default:
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + TYPE_FIELD_NUMBER;
+    hash = (53 * hash) + type_;
+    hash = (37 * hash) + LOGGER_FIELD_NUMBER;
+    hash = (53 * hash) + logger_;
+    if (hasCallId()) {
+      hash = (37 * hash) + CALL_ID_FIELD_NUMBER;
+      hash = (53 * hash) + getCallId().hashCode();
+    }
+    if (hasPeer()) {
+      hash = (37 * hash) + PEER_FIELD_NUMBER;
+      hash = (53 * hash) + getPeer().hashCode();
+    }
+    hash = (37 * hash) + TRUNCATED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+        getTruncated());
+    hash = (37 * hash) + METHOD_NAME_FIELD_NUMBER;
+    hash = (53 * hash) + getMethodName().hashCode();
+    hash = (37 * hash) + STATUS_CODE_FIELD_NUMBER;
+    hash = (53 * hash) + getStatusCode();
+    hash = (37 * hash) + STATUS_MESSAGE_FIELD_NUMBER;
+    hash = (53 * hash) + getStatusMessage().hashCode();
+    hash = (37 * hash) + STATUS_DETAILS_FIELD_NUMBER;
+    hash = (53 * hash) + getStatusDetails().hashCode();
+    if (hasTimeout()) {
+      hash = (37 * hash) + TIMEOUT_FIELD_NUMBER;
+      hash = (53 * hash) + getTimeout().hashCode();
+    }
+    hash = (37 * hash) + SEQUENCE_ID_WITHIN_CALL_FIELD_NUMBER;
+    hash = (53 * hash) + getSequenceIdWithinCall();
+    switch (payloadCase_) {
+      case 4:
+        hash = (37 * hash) + METADATA_FIELD_NUMBER;
+        hash = (53 * hash) + getMetadata().hashCode();
+        break;
+      case 5:
+        hash = (37 * hash) + MESSAGE_FIELD_NUMBER;
+        hash = (53 * hash) + getMessage().hashCode();
+        break;
+      case 0:
+      default:
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.binarylog.v1alpha.GrpcLogEntry parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.GrpcLogEntry parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.GrpcLogEntry parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.GrpcLogEntry parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.GrpcLogEntry parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.GrpcLogEntry parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.GrpcLogEntry parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.GrpcLogEntry parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.GrpcLogEntry parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.GrpcLogEntry parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.GrpcLogEntry parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.GrpcLogEntry parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.binarylog.v1alpha.GrpcLogEntry prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Log entry we store in binary logs
+   * </pre>
+   *
+   * Protobuf type {@code grpc.binarylog.v1alpha.GrpcLogEntry}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1alpha.GrpcLogEntry)
+      io.grpc.binarylog.v1alpha.GrpcLogEntryOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_GrpcLogEntry_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_GrpcLogEntry_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1alpha.GrpcLogEntry.class, io.grpc.binarylog.v1alpha.GrpcLogEntry.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.v1alpha.GrpcLogEntry.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      type_ = 0;
+
+      logger_ = 0;
+
+      if (callIdBuilder_ == null) {
+        callId_ = null;
+      } else {
+        callId_ = null;
+        callIdBuilder_ = null;
+      }
+      if (peerBuilder_ == null) {
+        peer_ = null;
+      } else {
+        peer_ = null;
+        peerBuilder_ = null;
+      }
+      truncated_ = false;
+
+      methodName_ = "";
+
+      statusCode_ = 0;
+
+      statusMessage_ = "";
+
+      statusDetails_ = com.google.protobuf.ByteString.EMPTY;
+
+      if (timeoutBuilder_ == null) {
+        timeout_ = null;
+      } else {
+        timeout_ = null;
+        timeoutBuilder_ = null;
+      }
+      sequenceIdWithinCall_ = 0;
+
+      payloadCase_ = 0;
+      payload_ = null;
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_GrpcLogEntry_descriptor;
+    }
+
+    public io.grpc.binarylog.v1alpha.GrpcLogEntry getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1alpha.GrpcLogEntry.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1alpha.GrpcLogEntry build() {
+      io.grpc.binarylog.v1alpha.GrpcLogEntry result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1alpha.GrpcLogEntry buildPartial() {
+      io.grpc.binarylog.v1alpha.GrpcLogEntry result = new io.grpc.binarylog.v1alpha.GrpcLogEntry(this);
+      result.type_ = type_;
+      result.logger_ = logger_;
+      if (callIdBuilder_ == null) {
+        result.callId_ = callId_;
+      } else {
+        result.callId_ = callIdBuilder_.build();
+      }
+      if (payloadCase_ == 4) {
+        if (metadataBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = metadataBuilder_.build();
+        }
+      }
+      if (payloadCase_ == 5) {
+        if (messageBuilder_ == null) {
+          result.payload_ = payload_;
+        } else {
+          result.payload_ = messageBuilder_.build();
+        }
+      }
+      if (peerBuilder_ == null) {
+        result.peer_ = peer_;
+      } else {
+        result.peer_ = peerBuilder_.build();
+      }
+      result.truncated_ = truncated_;
+      result.methodName_ = methodName_;
+      result.statusCode_ = statusCode_;
+      result.statusMessage_ = statusMessage_;
+      result.statusDetails_ = statusDetails_;
+      if (timeoutBuilder_ == null) {
+        result.timeout_ = timeout_;
+      } else {
+        result.timeout_ = timeoutBuilder_.build();
+      }
+      result.sequenceIdWithinCall_ = sequenceIdWithinCall_;
+      result.payloadCase_ = payloadCase_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.binarylog.v1alpha.GrpcLogEntry) {
+        return mergeFrom((io.grpc.binarylog.v1alpha.GrpcLogEntry)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1alpha.GrpcLogEntry other) {
+      if (other == io.grpc.binarylog.v1alpha.GrpcLogEntry.getDefaultInstance()) return this;
+      if (other.type_ != 0) {
+        setTypeValue(other.getTypeValue());
+      }
+      if (other.logger_ != 0) {
+        setLoggerValue(other.getLoggerValue());
+      }
+      if (other.hasCallId()) {
+        mergeCallId(other.getCallId());
+      }
+      if (other.hasPeer()) {
+        mergePeer(other.getPeer());
+      }
+      if (other.getTruncated() != false) {
+        setTruncated(other.getTruncated());
+      }
+      if (!other.getMethodName().isEmpty()) {
+        methodName_ = other.methodName_;
+        onChanged();
+      }
+      if (other.getStatusCode() != 0) {
+        setStatusCode(other.getStatusCode());
+      }
+      if (!other.getStatusMessage().isEmpty()) {
+        statusMessage_ = other.statusMessage_;
+        onChanged();
+      }
+      if (other.getStatusDetails() != com.google.protobuf.ByteString.EMPTY) {
+        setStatusDetails(other.getStatusDetails());
+      }
+      if (other.hasTimeout()) {
+        mergeTimeout(other.getTimeout());
+      }
+      if (other.getSequenceIdWithinCall() != 0) {
+        setSequenceIdWithinCall(other.getSequenceIdWithinCall());
+      }
+      switch (other.getPayloadCase()) {
+        case METADATA: {
+          mergeMetadata(other.getMetadata());
+          break;
+        }
+        case MESSAGE: {
+          mergeMessage(other.getMessage());
+          break;
+        }
+        case PAYLOAD_NOT_SET: {
+          break;
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.binarylog.v1alpha.GrpcLogEntry parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1alpha.GrpcLogEntry) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int payloadCase_ = 0;
+    private java.lang.Object payload_;
+    public PayloadCase
+        getPayloadCase() {
+      return PayloadCase.forNumber(
+          payloadCase_);
+    }
+
+    public Builder clearPayload() {
+      payloadCase_ = 0;
+      payload_ = null;
+      onChanged();
+      return this;
+    }
+
+
+    private int type_ = 0;
+    /**
+     * <pre>
+     * One of the above Type enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Type type = 1;</code>
+     */
+    public int getTypeValue() {
+      return type_;
+    }
+    /**
+     * <pre>
+     * One of the above Type enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Type type = 1;</code>
+     */
+    public Builder setTypeValue(int value) {
+      type_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * One of the above Type enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Type type = 1;</code>
+     */
+    public io.grpc.binarylog.v1alpha.GrpcLogEntry.Type getType() {
+      io.grpc.binarylog.v1alpha.GrpcLogEntry.Type result = io.grpc.binarylog.v1alpha.GrpcLogEntry.Type.valueOf(type_);
+      return result == null ? io.grpc.binarylog.v1alpha.GrpcLogEntry.Type.UNRECOGNIZED : result;
+    }
+    /**
+     * <pre>
+     * One of the above Type enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Type type = 1;</code>
+     */
+    public Builder setType(io.grpc.binarylog.v1alpha.GrpcLogEntry.Type value) {
+      if (value == null) {
+        throw new NullPointerException();
+      }
+      
+      type_ = value.getNumber();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * One of the above Type enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Type type = 1;</code>
+     */
+    public Builder clearType() {
+      
+      type_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int logger_ = 0;
+    /**
+     * <pre>
+     * One of the above Logger enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Logger logger = 2;</code>
+     */
+    public int getLoggerValue() {
+      return logger_;
+    }
+    /**
+     * <pre>
+     * One of the above Logger enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Logger logger = 2;</code>
+     */
+    public Builder setLoggerValue(int value) {
+      logger_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * One of the above Logger enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Logger logger = 2;</code>
+     */
+    public io.grpc.binarylog.v1alpha.GrpcLogEntry.Logger getLogger() {
+      io.grpc.binarylog.v1alpha.GrpcLogEntry.Logger result = io.grpc.binarylog.v1alpha.GrpcLogEntry.Logger.valueOf(logger_);
+      return result == null ? io.grpc.binarylog.v1alpha.GrpcLogEntry.Logger.UNRECOGNIZED : result;
+    }
+    /**
+     * <pre>
+     * One of the above Logger enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Logger logger = 2;</code>
+     */
+    public Builder setLogger(io.grpc.binarylog.v1alpha.GrpcLogEntry.Logger value) {
+      if (value == null) {
+        throw new NullPointerException();
+      }
+      
+      logger_ = value.getNumber();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * One of the above Logger enum
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Logger logger = 2;</code>
+     */
+    public Builder clearLogger() {
+      
+      logger_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private io.grpc.binarylog.v1alpha.Uint128 callId_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1alpha.Uint128, io.grpc.binarylog.v1alpha.Uint128.Builder, io.grpc.binarylog.v1alpha.Uint128OrBuilder> callIdBuilder_;
+    /**
+     * <pre>
+     * Uniquely identifies a call. Each call may have several log entries, they
+     * will share the same call_id. 128 bits split into 2 64-bit parts.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+     */
+    public boolean hasCallId() {
+      return callIdBuilder_ != null || callId_ != null;
+    }
+    /**
+     * <pre>
+     * Uniquely identifies a call. Each call may have several log entries, they
+     * will share the same call_id. 128 bits split into 2 64-bit parts.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+     */
+    public io.grpc.binarylog.v1alpha.Uint128 getCallId() {
+      if (callIdBuilder_ == null) {
+        return callId_ == null ? io.grpc.binarylog.v1alpha.Uint128.getDefaultInstance() : callId_;
+      } else {
+        return callIdBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * Uniquely identifies a call. Each call may have several log entries, they
+     * will share the same call_id. 128 bits split into 2 64-bit parts.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+     */
+    public Builder setCallId(io.grpc.binarylog.v1alpha.Uint128 value) {
+      if (callIdBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        callId_ = value;
+        onChanged();
+      } else {
+        callIdBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Uniquely identifies a call. Each call may have several log entries, they
+     * will share the same call_id. 128 bits split into 2 64-bit parts.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+     */
+    public Builder setCallId(
+        io.grpc.binarylog.v1alpha.Uint128.Builder builderForValue) {
+      if (callIdBuilder_ == null) {
+        callId_ = builderForValue.build();
+        onChanged();
+      } else {
+        callIdBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Uniquely identifies a call. Each call may have several log entries, they
+     * will share the same call_id. 128 bits split into 2 64-bit parts.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+     */
+    public Builder mergeCallId(io.grpc.binarylog.v1alpha.Uint128 value) {
+      if (callIdBuilder_ == null) {
+        if (callId_ != null) {
+          callId_ =
+            io.grpc.binarylog.v1alpha.Uint128.newBuilder(callId_).mergeFrom(value).buildPartial();
+        } else {
+          callId_ = value;
+        }
+        onChanged();
+      } else {
+        callIdBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Uniquely identifies a call. Each call may have several log entries, they
+     * will share the same call_id. 128 bits split into 2 64-bit parts.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+     */
+    public Builder clearCallId() {
+      if (callIdBuilder_ == null) {
+        callId_ = null;
+        onChanged();
+      } else {
+        callId_ = null;
+        callIdBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Uniquely identifies a call. Each call may have several log entries, they
+     * will share the same call_id. 128 bits split into 2 64-bit parts.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+     */
+    public io.grpc.binarylog.v1alpha.Uint128.Builder getCallIdBuilder() {
+      
+      onChanged();
+      return getCallIdFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Uniquely identifies a call. Each call may have several log entries, they
+     * will share the same call_id. 128 bits split into 2 64-bit parts.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+     */
+    public io.grpc.binarylog.v1alpha.Uint128OrBuilder getCallIdOrBuilder() {
+      if (callIdBuilder_ != null) {
+        return callIdBuilder_.getMessageOrBuilder();
+      } else {
+        return callId_ == null ?
+            io.grpc.binarylog.v1alpha.Uint128.getDefaultInstance() : callId_;
+      }
+    }
+    /**
+     * <pre>
+     * Uniquely identifies a call. Each call may have several log entries, they
+     * will share the same call_id. 128 bits split into 2 64-bit parts.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1alpha.Uint128, io.grpc.binarylog.v1alpha.Uint128.Builder, io.grpc.binarylog.v1alpha.Uint128OrBuilder> 
+        getCallIdFieldBuilder() {
+      if (callIdBuilder_ == null) {
+        callIdBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.binarylog.v1alpha.Uint128, io.grpc.binarylog.v1alpha.Uint128.Builder, io.grpc.binarylog.v1alpha.Uint128OrBuilder>(
+                getCallId(),
+                getParentForChildren(),
+                isClean());
+        callId_ = null;
+      }
+      return callIdBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1alpha.Metadata, io.grpc.binarylog.v1alpha.Metadata.Builder, io.grpc.binarylog.v1alpha.MetadataOrBuilder> metadataBuilder_;
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_INITIAL_METADATA and
+     * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+     * from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+     */
+    public boolean hasMetadata() {
+      return payloadCase_ == 4;
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_INITIAL_METADATA and
+     * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+     * from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+     */
+    public io.grpc.binarylog.v1alpha.Metadata getMetadata() {
+      if (metadataBuilder_ == null) {
+        if (payloadCase_ == 4) {
+          return (io.grpc.binarylog.v1alpha.Metadata) payload_;
+        }
+        return io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance();
+      } else {
+        if (payloadCase_ == 4) {
+          return metadataBuilder_.getMessage();
+        }
+        return io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_INITIAL_METADATA and
+     * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+     * from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+     */
+    public Builder setMetadata(io.grpc.binarylog.v1alpha.Metadata value) {
+      if (metadataBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        payload_ = value;
+        onChanged();
+      } else {
+        metadataBuilder_.setMessage(value);
+      }
+      payloadCase_ = 4;
+      return this;
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_INITIAL_METADATA and
+     * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+     * from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+     */
+    public Builder setMetadata(
+        io.grpc.binarylog.v1alpha.Metadata.Builder builderForValue) {
+      if (metadataBuilder_ == null) {
+        payload_ = builderForValue.build();
+        onChanged();
+      } else {
+        metadataBuilder_.setMessage(builderForValue.build());
+      }
+      payloadCase_ = 4;
+      return this;
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_INITIAL_METADATA and
+     * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+     * from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+     */
+    public Builder mergeMetadata(io.grpc.binarylog.v1alpha.Metadata value) {
+      if (metadataBuilder_ == null) {
+        if (payloadCase_ == 4 &&
+            payload_ != io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance()) {
+          payload_ = io.grpc.binarylog.v1alpha.Metadata.newBuilder((io.grpc.binarylog.v1alpha.Metadata) payload_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          payload_ = value;
+        }
+        onChanged();
+      } else {
+        if (payloadCase_ == 4) {
+          metadataBuilder_.mergeFrom(value);
+        }
+        metadataBuilder_.setMessage(value);
+      }
+      payloadCase_ = 4;
+      return this;
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_INITIAL_METADATA and
+     * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+     * from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+     */
+    public Builder clearMetadata() {
+      if (metadataBuilder_ == null) {
+        if (payloadCase_ == 4) {
+          payloadCase_ = 0;
+          payload_ = null;
+          onChanged();
+        }
+      } else {
+        if (payloadCase_ == 4) {
+          payloadCase_ = 0;
+          payload_ = null;
+        }
+        metadataBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_INITIAL_METADATA and
+     * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+     * from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+     */
+    public io.grpc.binarylog.v1alpha.Metadata.Builder getMetadataBuilder() {
+      return getMetadataFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_INITIAL_METADATA and
+     * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+     * from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+     */
+    public io.grpc.binarylog.v1alpha.MetadataOrBuilder getMetadataOrBuilder() {
+      if ((payloadCase_ == 4) && (metadataBuilder_ != null)) {
+        return metadataBuilder_.getMessageOrBuilder();
+      } else {
+        if (payloadCase_ == 4) {
+          return (io.grpc.binarylog.v1alpha.Metadata) payload_;
+        }
+        return io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_INITIAL_METADATA and
+     * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+     * from the application.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1alpha.Metadata, io.grpc.binarylog.v1alpha.Metadata.Builder, io.grpc.binarylog.v1alpha.MetadataOrBuilder> 
+        getMetadataFieldBuilder() {
+      if (metadataBuilder_ == null) {
+        if (!(payloadCase_ == 4)) {
+          payload_ = io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance();
+        }
+        metadataBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.binarylog.v1alpha.Metadata, io.grpc.binarylog.v1alpha.Metadata.Builder, io.grpc.binarylog.v1alpha.MetadataOrBuilder>(
+                (io.grpc.binarylog.v1alpha.Metadata) payload_,
+                getParentForChildren(),
+                isClean());
+        payload_ = null;
+      }
+      payloadCase_ = 4;
+      onChanged();;
+      return metadataBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1alpha.Message, io.grpc.binarylog.v1alpha.Message.Builder, io.grpc.binarylog.v1alpha.MessageOrBuilder> messageBuilder_;
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+     */
+    public boolean hasMessage() {
+      return payloadCase_ == 5;
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+     */
+    public io.grpc.binarylog.v1alpha.Message getMessage() {
+      if (messageBuilder_ == null) {
+        if (payloadCase_ == 5) {
+          return (io.grpc.binarylog.v1alpha.Message) payload_;
+        }
+        return io.grpc.binarylog.v1alpha.Message.getDefaultInstance();
+      } else {
+        if (payloadCase_ == 5) {
+          return messageBuilder_.getMessage();
+        }
+        return io.grpc.binarylog.v1alpha.Message.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+     */
+    public Builder setMessage(io.grpc.binarylog.v1alpha.Message value) {
+      if (messageBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        payload_ = value;
+        onChanged();
+      } else {
+        messageBuilder_.setMessage(value);
+      }
+      payloadCase_ = 5;
+      return this;
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+     */
+    public Builder setMessage(
+        io.grpc.binarylog.v1alpha.Message.Builder builderForValue) {
+      if (messageBuilder_ == null) {
+        payload_ = builderForValue.build();
+        onChanged();
+      } else {
+        messageBuilder_.setMessage(builderForValue.build());
+      }
+      payloadCase_ = 5;
+      return this;
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+     */
+    public Builder mergeMessage(io.grpc.binarylog.v1alpha.Message value) {
+      if (messageBuilder_ == null) {
+        if (payloadCase_ == 5 &&
+            payload_ != io.grpc.binarylog.v1alpha.Message.getDefaultInstance()) {
+          payload_ = io.grpc.binarylog.v1alpha.Message.newBuilder((io.grpc.binarylog.v1alpha.Message) payload_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          payload_ = value;
+        }
+        onChanged();
+      } else {
+        if (payloadCase_ == 5) {
+          messageBuilder_.mergeFrom(value);
+        }
+        messageBuilder_.setMessage(value);
+      }
+      payloadCase_ = 5;
+      return this;
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+     */
+    public Builder clearMessage() {
+      if (messageBuilder_ == null) {
+        if (payloadCase_ == 5) {
+          payloadCase_ = 0;
+          payload_ = null;
+          onChanged();
+        }
+      } else {
+        if (payloadCase_ == 5) {
+          payloadCase_ = 0;
+          payload_ = null;
+        }
+        messageBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+     */
+    public io.grpc.binarylog.v1alpha.Message.Builder getMessageBuilder() {
+      return getMessageFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+     */
+    public io.grpc.binarylog.v1alpha.MessageOrBuilder getMessageOrBuilder() {
+      if ((payloadCase_ == 5) && (messageBuilder_ != null)) {
+        return messageBuilder_.getMessageOrBuilder();
+      } else {
+        if (payloadCase_ == 5) {
+          return (io.grpc.binarylog.v1alpha.Message) payload_;
+        }
+        return io.grpc.binarylog.v1alpha.Message.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * Used by {SEND,RECV}_MESSAGE
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1alpha.Message, io.grpc.binarylog.v1alpha.Message.Builder, io.grpc.binarylog.v1alpha.MessageOrBuilder> 
+        getMessageFieldBuilder() {
+      if (messageBuilder_ == null) {
+        if (!(payloadCase_ == 5)) {
+          payload_ = io.grpc.binarylog.v1alpha.Message.getDefaultInstance();
+        }
+        messageBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.binarylog.v1alpha.Message, io.grpc.binarylog.v1alpha.Message.Builder, io.grpc.binarylog.v1alpha.MessageOrBuilder>(
+                (io.grpc.binarylog.v1alpha.Message) payload_,
+                getParentForChildren(),
+                isClean());
+        payload_ = null;
+      }
+      payloadCase_ = 5;
+      onChanged();;
+      return messageBuilder_;
+    }
+
+    private io.grpc.binarylog.v1alpha.Peer peer_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1alpha.Peer, io.grpc.binarylog.v1alpha.Peer.Builder, io.grpc.binarylog.v1alpha.PeerOrBuilder> peerBuilder_;
+    /**
+     * <pre>
+     * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+     * and RECV_INITIAL_METADATA entries.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+     */
+    public boolean hasPeer() {
+      return peerBuilder_ != null || peer_ != null;
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+     * and RECV_INITIAL_METADATA entries.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+     */
+    public io.grpc.binarylog.v1alpha.Peer getPeer() {
+      if (peerBuilder_ == null) {
+        return peer_ == null ? io.grpc.binarylog.v1alpha.Peer.getDefaultInstance() : peer_;
+      } else {
+        return peerBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+     * and RECV_INITIAL_METADATA entries.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+     */
+    public Builder setPeer(io.grpc.binarylog.v1alpha.Peer value) {
+      if (peerBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        peer_ = value;
+        onChanged();
+      } else {
+        peerBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+     * and RECV_INITIAL_METADATA entries.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+     */
+    public Builder setPeer(
+        io.grpc.binarylog.v1alpha.Peer.Builder builderForValue) {
+      if (peerBuilder_ == null) {
+        peer_ = builderForValue.build();
+        onChanged();
+      } else {
+        peerBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+     * and RECV_INITIAL_METADATA entries.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+     */
+    public Builder mergePeer(io.grpc.binarylog.v1alpha.Peer value) {
+      if (peerBuilder_ == null) {
+        if (peer_ != null) {
+          peer_ =
+            io.grpc.binarylog.v1alpha.Peer.newBuilder(peer_).mergeFrom(value).buildPartial();
+        } else {
+          peer_ = value;
+        }
+        onChanged();
+      } else {
+        peerBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+     * and RECV_INITIAL_METADATA entries.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+     */
+    public Builder clearPeer() {
+      if (peerBuilder_ == null) {
+        peer_ = null;
+        onChanged();
+      } else {
+        peer_ = null;
+        peerBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+     * and RECV_INITIAL_METADATA entries.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+     */
+    public io.grpc.binarylog.v1alpha.Peer.Builder getPeerBuilder() {
+      
+      onChanged();
+      return getPeerFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+     * and RECV_INITIAL_METADATA entries.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+     */
+    public io.grpc.binarylog.v1alpha.PeerOrBuilder getPeerOrBuilder() {
+      if (peerBuilder_ != null) {
+        return peerBuilder_.getMessageOrBuilder();
+      } else {
+        return peer_ == null ?
+            io.grpc.binarylog.v1alpha.Peer.getDefaultInstance() : peer_;
+      }
+    }
+    /**
+     * <pre>
+     * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+     * and RECV_INITIAL_METADATA entries.
+     * </pre>
+     *
+     * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.binarylog.v1alpha.Peer, io.grpc.binarylog.v1alpha.Peer.Builder, io.grpc.binarylog.v1alpha.PeerOrBuilder> 
+        getPeerFieldBuilder() {
+      if (peerBuilder_ == null) {
+        peerBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.binarylog.v1alpha.Peer, io.grpc.binarylog.v1alpha.Peer.Builder, io.grpc.binarylog.v1alpha.PeerOrBuilder>(
+                getPeer(),
+                getParentForChildren(),
+                isClean());
+        peer_ = null;
+      }
+      return peerBuilder_;
+    }
+
+    private boolean truncated_ ;
+    /**
+     * <pre>
+     * true if payload does not represent the full message or metadata.
+     * </pre>
+     *
+     * <code>bool truncated = 7;</code>
+     */
+    public boolean getTruncated() {
+      return truncated_;
+    }
+    /**
+     * <pre>
+     * true if payload does not represent the full message or metadata.
+     * </pre>
+     *
+     * <code>bool truncated = 7;</code>
+     */
+    public Builder setTruncated(boolean value) {
+      
+      truncated_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * true if payload does not represent the full message or metadata.
+     * </pre>
+     *
+     * <code>bool truncated = 7;</code>
+     */
+    public Builder clearTruncated() {
+      
+      truncated_ = false;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object methodName_ = "";
+    /**
+     * <pre>
+     * The method name. Logged for the first entry:
+     * RECV_INITIAL_METADATA for server side or
+     * SEND_INITIAL_METADATA for client side.
+     * </pre>
+     *
+     * <code>string method_name = 8;</code>
+     */
+    public java.lang.String getMethodName() {
+      java.lang.Object ref = methodName_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        methodName_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * The method name. Logged for the first entry:
+     * RECV_INITIAL_METADATA for server side or
+     * SEND_INITIAL_METADATA for client side.
+     * </pre>
+     *
+     * <code>string method_name = 8;</code>
+     */
+    public com.google.protobuf.ByteString
+        getMethodNameBytes() {
+      java.lang.Object ref = methodName_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        methodName_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * The method name. Logged for the first entry:
+     * RECV_INITIAL_METADATA for server side or
+     * SEND_INITIAL_METADATA for client side.
+     * </pre>
+     *
+     * <code>string method_name = 8;</code>
+     */
+    public Builder setMethodName(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      methodName_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The method name. Logged for the first entry:
+     * RECV_INITIAL_METADATA for server side or
+     * SEND_INITIAL_METADATA for client side.
+     * </pre>
+     *
+     * <code>string method_name = 8;</code>
+     */
+    public Builder clearMethodName() {
+      
+      methodName_ = getDefaultInstance().getMethodName();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The method name. Logged for the first entry:
+     * RECV_INITIAL_METADATA for server side or
+     * SEND_INITIAL_METADATA for client side.
+     * </pre>
+     *
+     * <code>string method_name = 8;</code>
+     */
+    public Builder setMethodNameBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      methodName_ = value;
+      onChanged();
+      return this;
+    }
+
+    private int statusCode_ ;
+    /**
+     * <pre>
+     * status_code and status_message:
+     * Only present for SEND_TRAILING_METADATA on server side or
+     * RECV_TRAILING_METADATA on client side.
+     * </pre>
+     *
+     * <code>uint32 status_code = 9;</code>
+     */
+    public int getStatusCode() {
+      return statusCode_;
+    }
+    /**
+     * <pre>
+     * status_code and status_message:
+     * Only present for SEND_TRAILING_METADATA on server side or
+     * RECV_TRAILING_METADATA on client side.
+     * </pre>
+     *
+     * <code>uint32 status_code = 9;</code>
+     */
+    public Builder setStatusCode(int value) {
+      
+      statusCode_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * status_code and status_message:
+     * Only present for SEND_TRAILING_METADATA on server side or
+     * RECV_TRAILING_METADATA on client side.
+     * </pre>
+     *
+     * <code>uint32 status_code = 9;</code>
+     */
+    public Builder clearStatusCode() {
+      
+      statusCode_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object statusMessage_ = "";
+    /**
+     * <pre>
+     * An original status message before any transport specific
+     * encoding.
+     * </pre>
+     *
+     * <code>string status_message = 10;</code>
+     */
+    public java.lang.String getStatusMessage() {
+      java.lang.Object ref = statusMessage_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        statusMessage_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An original status message before any transport specific
+     * encoding.
+     * </pre>
+     *
+     * <code>string status_message = 10;</code>
+     */
+    public com.google.protobuf.ByteString
+        getStatusMessageBytes() {
+      java.lang.Object ref = statusMessage_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        statusMessage_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An original status message before any transport specific
+     * encoding.
+     * </pre>
+     *
+     * <code>string status_message = 10;</code>
+     */
+    public Builder setStatusMessage(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      statusMessage_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An original status message before any transport specific
+     * encoding.
+     * </pre>
+     *
+     * <code>string status_message = 10;</code>
+     */
+    public Builder clearStatusMessage() {
+      
+      statusMessage_ = getDefaultInstance().getStatusMessage();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An original status message before any transport specific
+     * encoding.
+     * </pre>
+     *
+     * <code>string status_message = 10;</code>
+     */
+    public Builder setStatusMessageBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      statusMessage_ = value;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.ByteString statusDetails_ = com.google.protobuf.ByteString.EMPTY;
+    /**
+     * <pre>
+     * The value of the 'grpc-status-details-bin' metadata key. If
+     * present, this is always an encoded 'google.rpc.Status' message.
+     * </pre>
+     *
+     * <code>bytes status_details = 11;</code>
+     */
+    public com.google.protobuf.ByteString getStatusDetails() {
+      return statusDetails_;
+    }
+    /**
+     * <pre>
+     * The value of the 'grpc-status-details-bin' metadata key. If
+     * present, this is always an encoded 'google.rpc.Status' message.
+     * </pre>
+     *
+     * <code>bytes status_details = 11;</code>
+     */
+    public Builder setStatusDetails(com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      statusDetails_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The value of the 'grpc-status-details-bin' metadata key. If
+     * present, this is always an encoded 'google.rpc.Status' message.
+     * </pre>
+     *
+     * <code>bytes status_details = 11;</code>
+     */
+    public Builder clearStatusDetails() {
+      
+      statusDetails_ = getDefaultInstance().getStatusDetails();
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.Duration timeout_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Duration, com.google.protobuf.Duration.Builder, com.google.protobuf.DurationOrBuilder> timeoutBuilder_;
+    /**
+     * <pre>
+     * the RPC timeout
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration timeout = 12;</code>
+     */
+    public boolean hasTimeout() {
+      return timeoutBuilder_ != null || timeout_ != null;
+    }
+    /**
+     * <pre>
+     * the RPC timeout
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration timeout = 12;</code>
+     */
+    public com.google.protobuf.Duration getTimeout() {
+      if (timeoutBuilder_ == null) {
+        return timeout_ == null ? com.google.protobuf.Duration.getDefaultInstance() : timeout_;
+      } else {
+        return timeoutBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * the RPC timeout
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration timeout = 12;</code>
+     */
+    public Builder setTimeout(com.google.protobuf.Duration value) {
+      if (timeoutBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        timeout_ = value;
+        onChanged();
+      } else {
+        timeoutBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * the RPC timeout
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration timeout = 12;</code>
+     */
+    public Builder setTimeout(
+        com.google.protobuf.Duration.Builder builderForValue) {
+      if (timeoutBuilder_ == null) {
+        timeout_ = builderForValue.build();
+        onChanged();
+      } else {
+        timeoutBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * the RPC timeout
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration timeout = 12;</code>
+     */
+    public Builder mergeTimeout(com.google.protobuf.Duration value) {
+      if (timeoutBuilder_ == null) {
+        if (timeout_ != null) {
+          timeout_ =
+            com.google.protobuf.Duration.newBuilder(timeout_).mergeFrom(value).buildPartial();
+        } else {
+          timeout_ = value;
+        }
+        onChanged();
+      } else {
+        timeoutBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * the RPC timeout
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration timeout = 12;</code>
+     */
+    public Builder clearTimeout() {
+      if (timeoutBuilder_ == null) {
+        timeout_ = null;
+        onChanged();
+      } else {
+        timeout_ = null;
+        timeoutBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * the RPC timeout
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration timeout = 12;</code>
+     */
+    public com.google.protobuf.Duration.Builder getTimeoutBuilder() {
+      
+      onChanged();
+      return getTimeoutFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * the RPC timeout
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration timeout = 12;</code>
+     */
+    public com.google.protobuf.DurationOrBuilder getTimeoutOrBuilder() {
+      if (timeoutBuilder_ != null) {
+        return timeoutBuilder_.getMessageOrBuilder();
+      } else {
+        return timeout_ == null ?
+            com.google.protobuf.Duration.getDefaultInstance() : timeout_;
+      }
+    }
+    /**
+     * <pre>
+     * the RPC timeout
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration timeout = 12;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Duration, com.google.protobuf.Duration.Builder, com.google.protobuf.DurationOrBuilder> 
+        getTimeoutFieldBuilder() {
+      if (timeoutBuilder_ == null) {
+        timeoutBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Duration, com.google.protobuf.Duration.Builder, com.google.protobuf.DurationOrBuilder>(
+                getTimeout(),
+                getParentForChildren(),
+                isClean());
+        timeout_ = null;
+      }
+      return timeoutBuilder_;
+    }
+
+    private int sequenceIdWithinCall_ ;
+    /**
+     * <pre>
+     * The entry sequence id for this call. The first GrpcLogEntry has a
+     * value of 1, to disambiguate from an unset value. The purpose of
+     * this field is to detect missing entries in environments where
+     * durability or ordering is not guaranteed.
+     * </pre>
+     *
+     * <code>uint32 sequence_id_within_call = 13;</code>
+     */
+    public int getSequenceIdWithinCall() {
+      return sequenceIdWithinCall_;
+    }
+    /**
+     * <pre>
+     * The entry sequence id for this call. The first GrpcLogEntry has a
+     * value of 1, to disambiguate from an unset value. The purpose of
+     * this field is to detect missing entries in environments where
+     * durability or ordering is not guaranteed.
+     * </pre>
+     *
+     * <code>uint32 sequence_id_within_call = 13;</code>
+     */
+    public Builder setSequenceIdWithinCall(int value) {
+      
+      sequenceIdWithinCall_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The entry sequence id for this call. The first GrpcLogEntry has a
+     * value of 1, to disambiguate from an unset value. The purpose of
+     * this field is to detect missing entries in environments where
+     * durability or ordering is not guaranteed.
+     * </pre>
+     *
+     * <code>uint32 sequence_id_within_call = 13;</code>
+     */
+    public Builder clearSequenceIdWithinCall() {
+      
+      sequenceIdWithinCall_ = 0;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.binarylog.v1alpha.GrpcLogEntry)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1alpha.GrpcLogEntry)
+  private static final io.grpc.binarylog.v1alpha.GrpcLogEntry DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1alpha.GrpcLogEntry();
+  }
+
+  public static io.grpc.binarylog.v1alpha.GrpcLogEntry getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GrpcLogEntry>
+      PARSER = new com.google.protobuf.AbstractParser<GrpcLogEntry>() {
+    public GrpcLogEntry parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GrpcLogEntry(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GrpcLogEntry> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GrpcLogEntry> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.binarylog.v1alpha.GrpcLogEntry getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1alpha/GrpcLogEntryOrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/GrpcLogEntryOrBuilder.java
new file mode 100644
index 0000000..e4c7c6e
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/GrpcLogEntryOrBuilder.java
@@ -0,0 +1,266 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1alpha/binarylog.proto
+
+package io.grpc.binarylog.v1alpha;
+
+public interface GrpcLogEntryOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1alpha.GrpcLogEntry)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * One of the above Type enum
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Type type = 1;</code>
+   */
+  int getTypeValue();
+  /**
+   * <pre>
+   * One of the above Type enum
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Type type = 1;</code>
+   */
+  io.grpc.binarylog.v1alpha.GrpcLogEntry.Type getType();
+
+  /**
+   * <pre>
+   * One of the above Logger enum
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Logger logger = 2;</code>
+   */
+  int getLoggerValue();
+  /**
+   * <pre>
+   * One of the above Logger enum
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.GrpcLogEntry.Logger logger = 2;</code>
+   */
+  io.grpc.binarylog.v1alpha.GrpcLogEntry.Logger getLogger();
+
+  /**
+   * <pre>
+   * Uniquely identifies a call. Each call may have several log entries, they
+   * will share the same call_id. 128 bits split into 2 64-bit parts.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+   */
+  boolean hasCallId();
+  /**
+   * <pre>
+   * Uniquely identifies a call. Each call may have several log entries, they
+   * will share the same call_id. 128 bits split into 2 64-bit parts.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+   */
+  io.grpc.binarylog.v1alpha.Uint128 getCallId();
+  /**
+   * <pre>
+   * Uniquely identifies a call. Each call may have several log entries, they
+   * will share the same call_id. 128 bits split into 2 64-bit parts.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Uint128 call_id = 3;</code>
+   */
+  io.grpc.binarylog.v1alpha.Uint128OrBuilder getCallIdOrBuilder();
+
+  /**
+   * <pre>
+   * Used by {SEND,RECV}_INITIAL_METADATA and
+   * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+   * from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+   */
+  boolean hasMetadata();
+  /**
+   * <pre>
+   * Used by {SEND,RECV}_INITIAL_METADATA and
+   * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+   * from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+   */
+  io.grpc.binarylog.v1alpha.Metadata getMetadata();
+  /**
+   * <pre>
+   * Used by {SEND,RECV}_INITIAL_METADATA and
+   * {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+   * from the application.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Metadata metadata = 4;</code>
+   */
+  io.grpc.binarylog.v1alpha.MetadataOrBuilder getMetadataOrBuilder();
+
+  /**
+   * <pre>
+   * Used by {SEND,RECV}_MESSAGE
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+   */
+  boolean hasMessage();
+  /**
+   * <pre>
+   * Used by {SEND,RECV}_MESSAGE
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+   */
+  io.grpc.binarylog.v1alpha.Message getMessage();
+  /**
+   * <pre>
+   * Used by {SEND,RECV}_MESSAGE
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Message message = 5;</code>
+   */
+  io.grpc.binarylog.v1alpha.MessageOrBuilder getMessageOrBuilder();
+
+  /**
+   * <pre>
+   * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+   * and RECV_INITIAL_METADATA entries.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+   */
+  boolean hasPeer();
+  /**
+   * <pre>
+   * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+   * and RECV_INITIAL_METADATA entries.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+   */
+  io.grpc.binarylog.v1alpha.Peer getPeer();
+  /**
+   * <pre>
+   * Peer address information, will only be recorded in SEND_INITIAL_METADATA
+   * and RECV_INITIAL_METADATA entries.
+   * </pre>
+   *
+   * <code>.grpc.binarylog.v1alpha.Peer peer = 6;</code>
+   */
+  io.grpc.binarylog.v1alpha.PeerOrBuilder getPeerOrBuilder();
+
+  /**
+   * <pre>
+   * true if payload does not represent the full message or metadata.
+   * </pre>
+   *
+   * <code>bool truncated = 7;</code>
+   */
+  boolean getTruncated();
+
+  /**
+   * <pre>
+   * The method name. Logged for the first entry:
+   * RECV_INITIAL_METADATA for server side or
+   * SEND_INITIAL_METADATA for client side.
+   * </pre>
+   *
+   * <code>string method_name = 8;</code>
+   */
+  java.lang.String getMethodName();
+  /**
+   * <pre>
+   * The method name. Logged for the first entry:
+   * RECV_INITIAL_METADATA for server side or
+   * SEND_INITIAL_METADATA for client side.
+   * </pre>
+   *
+   * <code>string method_name = 8;</code>
+   */
+  com.google.protobuf.ByteString
+      getMethodNameBytes();
+
+  /**
+   * <pre>
+   * status_code and status_message:
+   * Only present for SEND_TRAILING_METADATA on server side or
+   * RECV_TRAILING_METADATA on client side.
+   * </pre>
+   *
+   * <code>uint32 status_code = 9;</code>
+   */
+  int getStatusCode();
+
+  /**
+   * <pre>
+   * An original status message before any transport specific
+   * encoding.
+   * </pre>
+   *
+   * <code>string status_message = 10;</code>
+   */
+  java.lang.String getStatusMessage();
+  /**
+   * <pre>
+   * An original status message before any transport specific
+   * encoding.
+   * </pre>
+   *
+   * <code>string status_message = 10;</code>
+   */
+  com.google.protobuf.ByteString
+      getStatusMessageBytes();
+
+  /**
+   * <pre>
+   * The value of the 'grpc-status-details-bin' metadata key. If
+   * present, this is always an encoded 'google.rpc.Status' message.
+   * </pre>
+   *
+   * <code>bytes status_details = 11;</code>
+   */
+  com.google.protobuf.ByteString getStatusDetails();
+
+  /**
+   * <pre>
+   * the RPC timeout
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration timeout = 12;</code>
+   */
+  boolean hasTimeout();
+  /**
+   * <pre>
+   * the RPC timeout
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration timeout = 12;</code>
+   */
+  com.google.protobuf.Duration getTimeout();
+  /**
+   * <pre>
+   * the RPC timeout
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration timeout = 12;</code>
+   */
+  com.google.protobuf.DurationOrBuilder getTimeoutOrBuilder();
+
+  /**
+   * <pre>
+   * The entry sequence id for this call. The first GrpcLogEntry has a
+   * value of 1, to disambiguate from an unset value. The purpose of
+   * this field is to detect missing entries in environments where
+   * durability or ordering is not guaranteed.
+   * </pre>
+   *
+   * <code>uint32 sequence_id_within_call = 13;</code>
+   */
+  int getSequenceIdWithinCall();
+
+  public io.grpc.binarylog.v1alpha.GrpcLogEntry.PayloadCase getPayloadCase();
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Message.java b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Message.java
new file mode 100644
index 0000000..590b176
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Message.java
@@ -0,0 +1,642 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1alpha/binarylog.proto
+
+package io.grpc.binarylog.v1alpha;
+
+/**
+ * <pre>
+ * Message payload, used by REQUEST and RESPONSE
+ * </pre>
+ *
+ * Protobuf type {@code grpc.binarylog.v1alpha.Message}
+ */
+public  final class Message extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.v1alpha.Message)
+    MessageOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Message.newBuilder() to construct.
+  private Message(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Message() {
+    flags_ = 0;
+    length_ = 0;
+    data_ = com.google.protobuf.ByteString.EMPTY;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Message(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+
+            flags_ = input.readUInt32();
+            break;
+          }
+          case 16: {
+
+            length_ = input.readUInt32();
+            break;
+          }
+          case 26: {
+
+            data_ = input.readBytes();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Message_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Message_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1alpha.Message.class, io.grpc.binarylog.v1alpha.Message.Builder.class);
+  }
+
+  public static final int FLAGS_FIELD_NUMBER = 1;
+  private int flags_;
+  /**
+   * <pre>
+   * This flag is currently used to indicate whether the payload is compressed,
+   * it may contain other semantics in the future. Value of 1 indicates that the
+   * binary octet sequence of Message is compressed using the mechanism declared
+   * by the Message-Encoding header. A value of 0 indicates that no encoding of
+   * Message bytes has occurred.
+   * </pre>
+   *
+   * <code>uint32 flags = 1;</code>
+   */
+  public int getFlags() {
+    return flags_;
+  }
+
+  public static final int LENGTH_FIELD_NUMBER = 2;
+  private int length_;
+  /**
+   * <pre>
+   * Length of the message. It may not be the same as the length of the
+   * data field, as the logging payload can be truncated or omitted.
+   * </pre>
+   *
+   * <code>uint32 length = 2;</code>
+   */
+  public int getLength() {
+    return length_;
+  }
+
+  public static final int DATA_FIELD_NUMBER = 3;
+  private com.google.protobuf.ByteString data_;
+  /**
+   * <pre>
+   * May be truncated or omitted.
+   * </pre>
+   *
+   * <code>bytes data = 3;</code>
+   */
+  public com.google.protobuf.ByteString getData() {
+    return data_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (flags_ != 0) {
+      output.writeUInt32(1, flags_);
+    }
+    if (length_ != 0) {
+      output.writeUInt32(2, length_);
+    }
+    if (!data_.isEmpty()) {
+      output.writeBytes(3, data_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (flags_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(1, flags_);
+    }
+    if (length_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(2, length_);
+    }
+    if (!data_.isEmpty()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBytesSize(3, data_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.binarylog.v1alpha.Message)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1alpha.Message other = (io.grpc.binarylog.v1alpha.Message) obj;
+
+    boolean result = true;
+    result = result && (getFlags()
+        == other.getFlags());
+    result = result && (getLength()
+        == other.getLength());
+    result = result && getData()
+        .equals(other.getData());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + FLAGS_FIELD_NUMBER;
+    hash = (53 * hash) + getFlags();
+    hash = (37 * hash) + LENGTH_FIELD_NUMBER;
+    hash = (53 * hash) + getLength();
+    hash = (37 * hash) + DATA_FIELD_NUMBER;
+    hash = (53 * hash) + getData().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.binarylog.v1alpha.Message parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.Message parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Message parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.Message parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Message parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.Message parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Message parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.Message parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Message parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.Message parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Message parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.Message parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.binarylog.v1alpha.Message prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Message payload, used by REQUEST and RESPONSE
+   * </pre>
+   *
+   * Protobuf type {@code grpc.binarylog.v1alpha.Message}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1alpha.Message)
+      io.grpc.binarylog.v1alpha.MessageOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Message_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Message_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1alpha.Message.class, io.grpc.binarylog.v1alpha.Message.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.v1alpha.Message.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      flags_ = 0;
+
+      length_ = 0;
+
+      data_ = com.google.protobuf.ByteString.EMPTY;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Message_descriptor;
+    }
+
+    public io.grpc.binarylog.v1alpha.Message getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1alpha.Message.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1alpha.Message build() {
+      io.grpc.binarylog.v1alpha.Message result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1alpha.Message buildPartial() {
+      io.grpc.binarylog.v1alpha.Message result = new io.grpc.binarylog.v1alpha.Message(this);
+      result.flags_ = flags_;
+      result.length_ = length_;
+      result.data_ = data_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.binarylog.v1alpha.Message) {
+        return mergeFrom((io.grpc.binarylog.v1alpha.Message)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1alpha.Message other) {
+      if (other == io.grpc.binarylog.v1alpha.Message.getDefaultInstance()) return this;
+      if (other.getFlags() != 0) {
+        setFlags(other.getFlags());
+      }
+      if (other.getLength() != 0) {
+        setLength(other.getLength());
+      }
+      if (other.getData() != com.google.protobuf.ByteString.EMPTY) {
+        setData(other.getData());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.binarylog.v1alpha.Message parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1alpha.Message) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private int flags_ ;
+    /**
+     * <pre>
+     * This flag is currently used to indicate whether the payload is compressed,
+     * it may contain other semantics in the future. Value of 1 indicates that the
+     * binary octet sequence of Message is compressed using the mechanism declared
+     * by the Message-Encoding header. A value of 0 indicates that no encoding of
+     * Message bytes has occurred.
+     * </pre>
+     *
+     * <code>uint32 flags = 1;</code>
+     */
+    public int getFlags() {
+      return flags_;
+    }
+    /**
+     * <pre>
+     * This flag is currently used to indicate whether the payload is compressed,
+     * it may contain other semantics in the future. Value of 1 indicates that the
+     * binary octet sequence of Message is compressed using the mechanism declared
+     * by the Message-Encoding header. A value of 0 indicates that no encoding of
+     * Message bytes has occurred.
+     * </pre>
+     *
+     * <code>uint32 flags = 1;</code>
+     */
+    public Builder setFlags(int value) {
+      
+      flags_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * This flag is currently used to indicate whether the payload is compressed,
+     * it may contain other semantics in the future. Value of 1 indicates that the
+     * binary octet sequence of Message is compressed using the mechanism declared
+     * by the Message-Encoding header. A value of 0 indicates that no encoding of
+     * Message bytes has occurred.
+     * </pre>
+     *
+     * <code>uint32 flags = 1;</code>
+     */
+    public Builder clearFlags() {
+      
+      flags_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int length_ ;
+    /**
+     * <pre>
+     * Length of the message. It may not be the same as the length of the
+     * data field, as the logging payload can be truncated or omitted.
+     * </pre>
+     *
+     * <code>uint32 length = 2;</code>
+     */
+    public int getLength() {
+      return length_;
+    }
+    /**
+     * <pre>
+     * Length of the message. It may not be the same as the length of the
+     * data field, as the logging payload can be truncated or omitted.
+     * </pre>
+     *
+     * <code>uint32 length = 2;</code>
+     */
+    public Builder setLength(int value) {
+      
+      length_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Length of the message. It may not be the same as the length of the
+     * data field, as the logging payload can be truncated or omitted.
+     * </pre>
+     *
+     * <code>uint32 length = 2;</code>
+     */
+    public Builder clearLength() {
+      
+      length_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.ByteString data_ = com.google.protobuf.ByteString.EMPTY;
+    /**
+     * <pre>
+     * May be truncated or omitted.
+     * </pre>
+     *
+     * <code>bytes data = 3;</code>
+     */
+    public com.google.protobuf.ByteString getData() {
+      return data_;
+    }
+    /**
+     * <pre>
+     * May be truncated or omitted.
+     * </pre>
+     *
+     * <code>bytes data = 3;</code>
+     */
+    public Builder setData(com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      data_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * May be truncated or omitted.
+     * </pre>
+     *
+     * <code>bytes data = 3;</code>
+     */
+    public Builder clearData() {
+      
+      data_ = getDefaultInstance().getData();
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.binarylog.v1alpha.Message)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1alpha.Message)
+  private static final io.grpc.binarylog.v1alpha.Message DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1alpha.Message();
+  }
+
+  public static io.grpc.binarylog.v1alpha.Message getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<Message>
+      PARSER = new com.google.protobuf.AbstractParser<Message>() {
+    public Message parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Message(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Message> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Message> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.binarylog.v1alpha.Message getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1alpha/MessageOrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/MessageOrBuilder.java
new file mode 100644
index 0000000..6ac17a7
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/MessageOrBuilder.java
@@ -0,0 +1,41 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1alpha/binarylog.proto
+
+package io.grpc.binarylog.v1alpha;
+
+public interface MessageOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1alpha.Message)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * This flag is currently used to indicate whether the payload is compressed,
+   * it may contain other semantics in the future. Value of 1 indicates that the
+   * binary octet sequence of Message is compressed using the mechanism declared
+   * by the Message-Encoding header. A value of 0 indicates that no encoding of
+   * Message bytes has occurred.
+   * </pre>
+   *
+   * <code>uint32 flags = 1;</code>
+   */
+  int getFlags();
+
+  /**
+   * <pre>
+   * Length of the message. It may not be the same as the length of the
+   * data field, as the logging payload can be truncated or omitted.
+   * </pre>
+   *
+   * <code>uint32 length = 2;</code>
+   */
+  int getLength();
+
+  /**
+   * <pre>
+   * May be truncated or omitted.
+   * </pre>
+   *
+   * <code>bytes data = 3;</code>
+   */
+  com.google.protobuf.ByteString getData();
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Metadata.java b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Metadata.java
new file mode 100644
index 0000000..25ecca1
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Metadata.java
@@ -0,0 +1,764 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1alpha/binarylog.proto
+
+package io.grpc.binarylog.v1alpha;
+
+/**
+ * <pre>
+ * A list of metadata pairs, used in the payload of CLIENT_INIT_METADATA,
+ * SERVER_INIT_METADATA and TRAILING_METADATA
+ * Implementations may omit some entries to honor the header limits
+ * of GRPC_BINARY_LOG_CONFIG.
+ * 
+ * Implementations will not log the following entries, and this is
+ * not to be treated as a truncation:
+ * - entries handled by grpc that are not user visible, such as those
+ *   that begin with 'grpc-' or keys like 'lb-token'
+ * - transport specific entries, including but not limited to:
+ *   ':path', ':authority', 'content-encoding', 'user-agent', 'te', etc
+ * - entries added for call credentials
+ * </pre>
+ *
+ * Protobuf type {@code grpc.binarylog.v1alpha.Metadata}
+ */
+public  final class Metadata extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.v1alpha.Metadata)
+    MetadataOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Metadata.newBuilder() to construct.
+  private Metadata(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Metadata() {
+    entry_ = java.util.Collections.emptyList();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Metadata(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+              entry_ = new java.util.ArrayList<io.grpc.binarylog.v1alpha.MetadataEntry>();
+              mutable_bitField0_ |= 0x00000001;
+            }
+            entry_.add(
+                input.readMessage(io.grpc.binarylog.v1alpha.MetadataEntry.parser(), extensionRegistry));
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+        entry_ = java.util.Collections.unmodifiableList(entry_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Metadata_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Metadata_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1alpha.Metadata.class, io.grpc.binarylog.v1alpha.Metadata.Builder.class);
+  }
+
+  public static final int ENTRY_FIELD_NUMBER = 1;
+  private java.util.List<io.grpc.binarylog.v1alpha.MetadataEntry> entry_;
+  /**
+   * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+   */
+  public java.util.List<io.grpc.binarylog.v1alpha.MetadataEntry> getEntryList() {
+    return entry_;
+  }
+  /**
+   * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+   */
+  public java.util.List<? extends io.grpc.binarylog.v1alpha.MetadataEntryOrBuilder> 
+      getEntryOrBuilderList() {
+    return entry_;
+  }
+  /**
+   * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+   */
+  public int getEntryCount() {
+    return entry_.size();
+  }
+  /**
+   * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+   */
+  public io.grpc.binarylog.v1alpha.MetadataEntry getEntry(int index) {
+    return entry_.get(index);
+  }
+  /**
+   * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+   */
+  public io.grpc.binarylog.v1alpha.MetadataEntryOrBuilder getEntryOrBuilder(
+      int index) {
+    return entry_.get(index);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    for (int i = 0; i < entry_.size(); i++) {
+      output.writeMessage(1, entry_.get(i));
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    for (int i = 0; i < entry_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, entry_.get(i));
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.binarylog.v1alpha.Metadata)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1alpha.Metadata other = (io.grpc.binarylog.v1alpha.Metadata) obj;
+
+    boolean result = true;
+    result = result && getEntryList()
+        .equals(other.getEntryList());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (getEntryCount() > 0) {
+      hash = (37 * hash) + ENTRY_FIELD_NUMBER;
+      hash = (53 * hash) + getEntryList().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.binarylog.v1alpha.Metadata parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.Metadata parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Metadata parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.Metadata parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Metadata parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.Metadata parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Metadata parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.Metadata parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Metadata parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.Metadata parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Metadata parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.Metadata parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.binarylog.v1alpha.Metadata prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * A list of metadata pairs, used in the payload of CLIENT_INIT_METADATA,
+   * SERVER_INIT_METADATA and TRAILING_METADATA
+   * Implementations may omit some entries to honor the header limits
+   * of GRPC_BINARY_LOG_CONFIG.
+   * 
+   * Implementations will not log the following entries, and this is
+   * not to be treated as a truncation:
+   * - entries handled by grpc that are not user visible, such as those
+   *   that begin with 'grpc-' or keys like 'lb-token'
+   * - transport specific entries, including but not limited to:
+   *   ':path', ':authority', 'content-encoding', 'user-agent', 'te', etc
+   * - entries added for call credentials
+   * </pre>
+   *
+   * Protobuf type {@code grpc.binarylog.v1alpha.Metadata}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1alpha.Metadata)
+      io.grpc.binarylog.v1alpha.MetadataOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Metadata_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Metadata_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1alpha.Metadata.class, io.grpc.binarylog.v1alpha.Metadata.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.v1alpha.Metadata.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+        getEntryFieldBuilder();
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (entryBuilder_ == null) {
+        entry_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+      } else {
+        entryBuilder_.clear();
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Metadata_descriptor;
+    }
+
+    public io.grpc.binarylog.v1alpha.Metadata getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1alpha.Metadata build() {
+      io.grpc.binarylog.v1alpha.Metadata result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1alpha.Metadata buildPartial() {
+      io.grpc.binarylog.v1alpha.Metadata result = new io.grpc.binarylog.v1alpha.Metadata(this);
+      int from_bitField0_ = bitField0_;
+      if (entryBuilder_ == null) {
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          entry_ = java.util.Collections.unmodifiableList(entry_);
+          bitField0_ = (bitField0_ & ~0x00000001);
+        }
+        result.entry_ = entry_;
+      } else {
+        result.entry_ = entryBuilder_.build();
+      }
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.binarylog.v1alpha.Metadata) {
+        return mergeFrom((io.grpc.binarylog.v1alpha.Metadata)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1alpha.Metadata other) {
+      if (other == io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance()) return this;
+      if (entryBuilder_ == null) {
+        if (!other.entry_.isEmpty()) {
+          if (entry_.isEmpty()) {
+            entry_ = other.entry_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+          } else {
+            ensureEntryIsMutable();
+            entry_.addAll(other.entry_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.entry_.isEmpty()) {
+          if (entryBuilder_.isEmpty()) {
+            entryBuilder_.dispose();
+            entryBuilder_ = null;
+            entry_ = other.entry_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+            entryBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getEntryFieldBuilder() : null;
+          } else {
+            entryBuilder_.addAllMessages(other.entry_);
+          }
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.binarylog.v1alpha.Metadata parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1alpha.Metadata) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.util.List<io.grpc.binarylog.v1alpha.MetadataEntry> entry_ =
+      java.util.Collections.emptyList();
+    private void ensureEntryIsMutable() {
+      if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+        entry_ = new java.util.ArrayList<io.grpc.binarylog.v1alpha.MetadataEntry>(entry_);
+        bitField0_ |= 0x00000001;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.binarylog.v1alpha.MetadataEntry, io.grpc.binarylog.v1alpha.MetadataEntry.Builder, io.grpc.binarylog.v1alpha.MetadataEntryOrBuilder> entryBuilder_;
+
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public java.util.List<io.grpc.binarylog.v1alpha.MetadataEntry> getEntryList() {
+      if (entryBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(entry_);
+      } else {
+        return entryBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public int getEntryCount() {
+      if (entryBuilder_ == null) {
+        return entry_.size();
+      } else {
+        return entryBuilder_.getCount();
+      }
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public io.grpc.binarylog.v1alpha.MetadataEntry getEntry(int index) {
+      if (entryBuilder_ == null) {
+        return entry_.get(index);
+      } else {
+        return entryBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public Builder setEntry(
+        int index, io.grpc.binarylog.v1alpha.MetadataEntry value) {
+      if (entryBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureEntryIsMutable();
+        entry_.set(index, value);
+        onChanged();
+      } else {
+        entryBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public Builder setEntry(
+        int index, io.grpc.binarylog.v1alpha.MetadataEntry.Builder builderForValue) {
+      if (entryBuilder_ == null) {
+        ensureEntryIsMutable();
+        entry_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        entryBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public Builder addEntry(io.grpc.binarylog.v1alpha.MetadataEntry value) {
+      if (entryBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureEntryIsMutable();
+        entry_.add(value);
+        onChanged();
+      } else {
+        entryBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public Builder addEntry(
+        int index, io.grpc.binarylog.v1alpha.MetadataEntry value) {
+      if (entryBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureEntryIsMutable();
+        entry_.add(index, value);
+        onChanged();
+      } else {
+        entryBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public Builder addEntry(
+        io.grpc.binarylog.v1alpha.MetadataEntry.Builder builderForValue) {
+      if (entryBuilder_ == null) {
+        ensureEntryIsMutable();
+        entry_.add(builderForValue.build());
+        onChanged();
+      } else {
+        entryBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public Builder addEntry(
+        int index, io.grpc.binarylog.v1alpha.MetadataEntry.Builder builderForValue) {
+      if (entryBuilder_ == null) {
+        ensureEntryIsMutable();
+        entry_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        entryBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public Builder addAllEntry(
+        java.lang.Iterable<? extends io.grpc.binarylog.v1alpha.MetadataEntry> values) {
+      if (entryBuilder_ == null) {
+        ensureEntryIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, entry_);
+        onChanged();
+      } else {
+        entryBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public Builder clearEntry() {
+      if (entryBuilder_ == null) {
+        entry_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+        onChanged();
+      } else {
+        entryBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public Builder removeEntry(int index) {
+      if (entryBuilder_ == null) {
+        ensureEntryIsMutable();
+        entry_.remove(index);
+        onChanged();
+      } else {
+        entryBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public io.grpc.binarylog.v1alpha.MetadataEntry.Builder getEntryBuilder(
+        int index) {
+      return getEntryFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public io.grpc.binarylog.v1alpha.MetadataEntryOrBuilder getEntryOrBuilder(
+        int index) {
+      if (entryBuilder_ == null) {
+        return entry_.get(index);  } else {
+        return entryBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public java.util.List<? extends io.grpc.binarylog.v1alpha.MetadataEntryOrBuilder> 
+         getEntryOrBuilderList() {
+      if (entryBuilder_ != null) {
+        return entryBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(entry_);
+      }
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public io.grpc.binarylog.v1alpha.MetadataEntry.Builder addEntryBuilder() {
+      return getEntryFieldBuilder().addBuilder(
+          io.grpc.binarylog.v1alpha.MetadataEntry.getDefaultInstance());
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public io.grpc.binarylog.v1alpha.MetadataEntry.Builder addEntryBuilder(
+        int index) {
+      return getEntryFieldBuilder().addBuilder(
+          index, io.grpc.binarylog.v1alpha.MetadataEntry.getDefaultInstance());
+    }
+    /**
+     * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+     */
+    public java.util.List<io.grpc.binarylog.v1alpha.MetadataEntry.Builder> 
+         getEntryBuilderList() {
+      return getEntryFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.binarylog.v1alpha.MetadataEntry, io.grpc.binarylog.v1alpha.MetadataEntry.Builder, io.grpc.binarylog.v1alpha.MetadataEntryOrBuilder> 
+        getEntryFieldBuilder() {
+      if (entryBuilder_ == null) {
+        entryBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.binarylog.v1alpha.MetadataEntry, io.grpc.binarylog.v1alpha.MetadataEntry.Builder, io.grpc.binarylog.v1alpha.MetadataEntryOrBuilder>(
+                entry_,
+                ((bitField0_ & 0x00000001) == 0x00000001),
+                getParentForChildren(),
+                isClean());
+        entry_ = null;
+      }
+      return entryBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.binarylog.v1alpha.Metadata)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1alpha.Metadata)
+  private static final io.grpc.binarylog.v1alpha.Metadata DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1alpha.Metadata();
+  }
+
+  public static io.grpc.binarylog.v1alpha.Metadata getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<Metadata>
+      PARSER = new com.google.protobuf.AbstractParser<Metadata>() {
+    public Metadata parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Metadata(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Metadata> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Metadata> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.binarylog.v1alpha.Metadata getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1alpha/MetadataEntry.java b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/MetadataEntry.java
new file mode 100644
index 0000000..f93f003
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/MetadataEntry.java
@@ -0,0 +1,519 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1alpha/binarylog.proto
+
+package io.grpc.binarylog.v1alpha;
+
+/**
+ * <pre>
+ * A metadata key value pair
+ * </pre>
+ *
+ * Protobuf type {@code grpc.binarylog.v1alpha.MetadataEntry}
+ */
+public  final class MetadataEntry extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.v1alpha.MetadataEntry)
+    MetadataEntryOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use MetadataEntry.newBuilder() to construct.
+  private MetadataEntry(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private MetadataEntry() {
+    key_ = com.google.protobuf.ByteString.EMPTY;
+    value_ = com.google.protobuf.ByteString.EMPTY;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private MetadataEntry(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+
+            key_ = input.readBytes();
+            break;
+          }
+          case 18: {
+
+            value_ = input.readBytes();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_MetadataEntry_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_MetadataEntry_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1alpha.MetadataEntry.class, io.grpc.binarylog.v1alpha.MetadataEntry.Builder.class);
+  }
+
+  public static final int KEY_FIELD_NUMBER = 1;
+  private com.google.protobuf.ByteString key_;
+  /**
+   * <code>bytes key = 1;</code>
+   */
+  public com.google.protobuf.ByteString getKey() {
+    return key_;
+  }
+
+  public static final int VALUE_FIELD_NUMBER = 2;
+  private com.google.protobuf.ByteString value_;
+  /**
+   * <code>bytes value = 2;</code>
+   */
+  public com.google.protobuf.ByteString getValue() {
+    return value_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!key_.isEmpty()) {
+      output.writeBytes(1, key_);
+    }
+    if (!value_.isEmpty()) {
+      output.writeBytes(2, value_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!key_.isEmpty()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBytesSize(1, key_);
+    }
+    if (!value_.isEmpty()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBytesSize(2, value_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.binarylog.v1alpha.MetadataEntry)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1alpha.MetadataEntry other = (io.grpc.binarylog.v1alpha.MetadataEntry) obj;
+
+    boolean result = true;
+    result = result && getKey()
+        .equals(other.getKey());
+    result = result && getValue()
+        .equals(other.getValue());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + KEY_FIELD_NUMBER;
+    hash = (53 * hash) + getKey().hashCode();
+    hash = (37 * hash) + VALUE_FIELD_NUMBER;
+    hash = (53 * hash) + getValue().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.binarylog.v1alpha.MetadataEntry parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.MetadataEntry parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.MetadataEntry parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.MetadataEntry parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.MetadataEntry parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.MetadataEntry parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.MetadataEntry parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.MetadataEntry parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.MetadataEntry parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.MetadataEntry parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.MetadataEntry parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.MetadataEntry parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.binarylog.v1alpha.MetadataEntry prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * A metadata key value pair
+   * </pre>
+   *
+   * Protobuf type {@code grpc.binarylog.v1alpha.MetadataEntry}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1alpha.MetadataEntry)
+      io.grpc.binarylog.v1alpha.MetadataEntryOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_MetadataEntry_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_MetadataEntry_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1alpha.MetadataEntry.class, io.grpc.binarylog.v1alpha.MetadataEntry.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.v1alpha.MetadataEntry.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      key_ = com.google.protobuf.ByteString.EMPTY;
+
+      value_ = com.google.protobuf.ByteString.EMPTY;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_MetadataEntry_descriptor;
+    }
+
+    public io.grpc.binarylog.v1alpha.MetadataEntry getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1alpha.MetadataEntry.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1alpha.MetadataEntry build() {
+      io.grpc.binarylog.v1alpha.MetadataEntry result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1alpha.MetadataEntry buildPartial() {
+      io.grpc.binarylog.v1alpha.MetadataEntry result = new io.grpc.binarylog.v1alpha.MetadataEntry(this);
+      result.key_ = key_;
+      result.value_ = value_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.binarylog.v1alpha.MetadataEntry) {
+        return mergeFrom((io.grpc.binarylog.v1alpha.MetadataEntry)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1alpha.MetadataEntry other) {
+      if (other == io.grpc.binarylog.v1alpha.MetadataEntry.getDefaultInstance()) return this;
+      if (other.getKey() != com.google.protobuf.ByteString.EMPTY) {
+        setKey(other.getKey());
+      }
+      if (other.getValue() != com.google.protobuf.ByteString.EMPTY) {
+        setValue(other.getValue());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.binarylog.v1alpha.MetadataEntry parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1alpha.MetadataEntry) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private com.google.protobuf.ByteString key_ = com.google.protobuf.ByteString.EMPTY;
+    /**
+     * <code>bytes key = 1;</code>
+     */
+    public com.google.protobuf.ByteString getKey() {
+      return key_;
+    }
+    /**
+     * <code>bytes key = 1;</code>
+     */
+    public Builder setKey(com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      key_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>bytes key = 1;</code>
+     */
+    public Builder clearKey() {
+      
+      key_ = getDefaultInstance().getKey();
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.ByteString value_ = com.google.protobuf.ByteString.EMPTY;
+    /**
+     * <code>bytes value = 2;</code>
+     */
+    public com.google.protobuf.ByteString getValue() {
+      return value_;
+    }
+    /**
+     * <code>bytes value = 2;</code>
+     */
+    public Builder setValue(com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      value_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>bytes value = 2;</code>
+     */
+    public Builder clearValue() {
+      
+      value_ = getDefaultInstance().getValue();
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.binarylog.v1alpha.MetadataEntry)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1alpha.MetadataEntry)
+  private static final io.grpc.binarylog.v1alpha.MetadataEntry DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1alpha.MetadataEntry();
+  }
+
+  public static io.grpc.binarylog.v1alpha.MetadataEntry getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<MetadataEntry>
+      PARSER = new com.google.protobuf.AbstractParser<MetadataEntry>() {
+    public MetadataEntry parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new MetadataEntry(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<MetadataEntry> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<MetadataEntry> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.binarylog.v1alpha.MetadataEntry getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1alpha/MetadataEntryOrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/MetadataEntryOrBuilder.java
new file mode 100644
index 0000000..78f32a0
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/MetadataEntryOrBuilder.java
@@ -0,0 +1,19 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1alpha/binarylog.proto
+
+package io.grpc.binarylog.v1alpha;
+
+public interface MetadataEntryOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1alpha.MetadataEntry)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>bytes key = 1;</code>
+   */
+  com.google.protobuf.ByteString getKey();
+
+  /**
+   * <code>bytes value = 2;</code>
+   */
+  com.google.protobuf.ByteString getValue();
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1alpha/MetadataOrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/MetadataOrBuilder.java
new file mode 100644
index 0000000..75cacfc
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/MetadataOrBuilder.java
@@ -0,0 +1,33 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1alpha/binarylog.proto
+
+package io.grpc.binarylog.v1alpha;
+
+public interface MetadataOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1alpha.Metadata)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+   */
+  java.util.List<io.grpc.binarylog.v1alpha.MetadataEntry> 
+      getEntryList();
+  /**
+   * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+   */
+  io.grpc.binarylog.v1alpha.MetadataEntry getEntry(int index);
+  /**
+   * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+   */
+  int getEntryCount();
+  /**
+   * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+   */
+  java.util.List<? extends io.grpc.binarylog.v1alpha.MetadataEntryOrBuilder> 
+      getEntryOrBuilderList();
+  /**
+   * <code>repeated .grpc.binarylog.v1alpha.MetadataEntry entry = 1;</code>
+   */
+  io.grpc.binarylog.v1alpha.MetadataEntryOrBuilder getEntryOrBuilder(
+      int index);
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Peer.java b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Peer.java
new file mode 100644
index 0000000..3a29df0
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Peer.java
@@ -0,0 +1,900 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1alpha/binarylog.proto
+
+package io.grpc.binarylog.v1alpha;
+
+/**
+ * <pre>
+ * Peer information
+ * </pre>
+ *
+ * Protobuf type {@code grpc.binarylog.v1alpha.Peer}
+ */
+public  final class Peer extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.v1alpha.Peer)
+    PeerOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Peer.newBuilder() to construct.
+  private Peer(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Peer() {
+    peerType_ = 0;
+    peer_ = com.google.protobuf.ByteString.EMPTY;
+    address_ = "";
+    ipPort_ = 0;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Peer(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+            int rawValue = input.readEnum();
+
+            peerType_ = rawValue;
+            break;
+          }
+          case 18: {
+
+            peer_ = input.readBytes();
+            break;
+          }
+          case 26: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            address_ = s;
+            break;
+          }
+          case 32: {
+
+            ipPort_ = input.readUInt32();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Peer_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Peer_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1alpha.Peer.class, io.grpc.binarylog.v1alpha.Peer.Builder.class);
+  }
+
+  /**
+   * Protobuf enum {@code grpc.binarylog.v1alpha.Peer.PeerType}
+   */
+  public enum PeerType
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>UNKNOWN_PEERTYPE = 0;</code>
+     */
+    UNKNOWN_PEERTYPE(0),
+    /**
+     * <pre>
+     * address is the address in 1.2.3.4 form
+     * </pre>
+     *
+     * <code>PEER_IPV4 = 1;</code>
+     */
+    PEER_IPV4(1),
+    /**
+     * <pre>
+     * address the address in canonical form (RFC5952 section 4)
+     * The scope is NOT included in the peer string.
+     * </pre>
+     *
+     * <code>PEER_IPV6 = 2;</code>
+     */
+    PEER_IPV6(2),
+    /**
+     * <pre>
+     * address is UDS string
+     * </pre>
+     *
+     * <code>PEER_UNIX = 3;</code>
+     */
+    PEER_UNIX(3),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <code>UNKNOWN_PEERTYPE = 0;</code>
+     */
+    public static final int UNKNOWN_PEERTYPE_VALUE = 0;
+    /**
+     * <pre>
+     * address is the address in 1.2.3.4 form
+     * </pre>
+     *
+     * <code>PEER_IPV4 = 1;</code>
+     */
+    public static final int PEER_IPV4_VALUE = 1;
+    /**
+     * <pre>
+     * address the address in canonical form (RFC5952 section 4)
+     * The scope is NOT included in the peer string.
+     * </pre>
+     *
+     * <code>PEER_IPV6 = 2;</code>
+     */
+    public static final int PEER_IPV6_VALUE = 2;
+    /**
+     * <pre>
+     * address is UDS string
+     * </pre>
+     *
+     * <code>PEER_UNIX = 3;</code>
+     */
+    public static final int PEER_UNIX_VALUE = 3;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static PeerType valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static PeerType forNumber(int value) {
+      switch (value) {
+        case 0: return UNKNOWN_PEERTYPE;
+        case 1: return PEER_IPV4;
+        case 2: return PEER_IPV6;
+        case 3: return PEER_UNIX;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<PeerType>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        PeerType> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<PeerType>() {
+            public PeerType findValueByNumber(int number) {
+              return PeerType.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1alpha.Peer.getDescriptor().getEnumTypes().get(0);
+    }
+
+    private static final PeerType[] VALUES = values();
+
+    public static PeerType valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private PeerType(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.binarylog.v1alpha.Peer.PeerType)
+  }
+
+  public static final int PEER_TYPE_FIELD_NUMBER = 1;
+  private int peerType_;
+  /**
+   * <code>.grpc.binarylog.v1alpha.Peer.PeerType peer_type = 1;</code>
+   */
+  public int getPeerTypeValue() {
+    return peerType_;
+  }
+  /**
+   * <code>.grpc.binarylog.v1alpha.Peer.PeerType peer_type = 1;</code>
+   */
+  public io.grpc.binarylog.v1alpha.Peer.PeerType getPeerType() {
+    io.grpc.binarylog.v1alpha.Peer.PeerType result = io.grpc.binarylog.v1alpha.Peer.PeerType.valueOf(peerType_);
+    return result == null ? io.grpc.binarylog.v1alpha.Peer.PeerType.UNRECOGNIZED : result;
+  }
+
+  public static final int PEER_FIELD_NUMBER = 2;
+  private com.google.protobuf.ByteString peer_;
+  /**
+   * <pre>
+   * will be removed: do not use
+   * </pre>
+   *
+   * <code>bytes peer = 2;</code>
+   */
+  public com.google.protobuf.ByteString getPeer() {
+    return peer_;
+  }
+
+  public static final int ADDRESS_FIELD_NUMBER = 3;
+  private volatile java.lang.Object address_;
+  /**
+   * <code>string address = 3;</code>
+   */
+  public java.lang.String getAddress() {
+    java.lang.Object ref = address_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      address_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string address = 3;</code>
+   */
+  public com.google.protobuf.ByteString
+      getAddressBytes() {
+    java.lang.Object ref = address_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      address_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int IP_PORT_FIELD_NUMBER = 4;
+  private int ipPort_;
+  /**
+   * <pre>
+   * only for PEER_IPV4 and PEER_IPV6
+   * </pre>
+   *
+   * <code>uint32 ip_port = 4;</code>
+   */
+  public int getIpPort() {
+    return ipPort_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (peerType_ != io.grpc.binarylog.v1alpha.Peer.PeerType.UNKNOWN_PEERTYPE.getNumber()) {
+      output.writeEnum(1, peerType_);
+    }
+    if (!peer_.isEmpty()) {
+      output.writeBytes(2, peer_);
+    }
+    if (!getAddressBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 3, address_);
+    }
+    if (ipPort_ != 0) {
+      output.writeUInt32(4, ipPort_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (peerType_ != io.grpc.binarylog.v1alpha.Peer.PeerType.UNKNOWN_PEERTYPE.getNumber()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeEnumSize(1, peerType_);
+    }
+    if (!peer_.isEmpty()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBytesSize(2, peer_);
+    }
+    if (!getAddressBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, address_);
+    }
+    if (ipPort_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(4, ipPort_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.binarylog.v1alpha.Peer)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1alpha.Peer other = (io.grpc.binarylog.v1alpha.Peer) obj;
+
+    boolean result = true;
+    result = result && peerType_ == other.peerType_;
+    result = result && getPeer()
+        .equals(other.getPeer());
+    result = result && getAddress()
+        .equals(other.getAddress());
+    result = result && (getIpPort()
+        == other.getIpPort());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + PEER_TYPE_FIELD_NUMBER;
+    hash = (53 * hash) + peerType_;
+    hash = (37 * hash) + PEER_FIELD_NUMBER;
+    hash = (53 * hash) + getPeer().hashCode();
+    hash = (37 * hash) + ADDRESS_FIELD_NUMBER;
+    hash = (53 * hash) + getAddress().hashCode();
+    hash = (37 * hash) + IP_PORT_FIELD_NUMBER;
+    hash = (53 * hash) + getIpPort();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.binarylog.v1alpha.Peer parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.Peer parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Peer parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.Peer parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Peer parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.Peer parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Peer parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.Peer parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Peer parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.Peer parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Peer parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.Peer parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.binarylog.v1alpha.Peer prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Peer information
+   * </pre>
+   *
+   * Protobuf type {@code grpc.binarylog.v1alpha.Peer}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1alpha.Peer)
+      io.grpc.binarylog.v1alpha.PeerOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Peer_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Peer_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1alpha.Peer.class, io.grpc.binarylog.v1alpha.Peer.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.v1alpha.Peer.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      peerType_ = 0;
+
+      peer_ = com.google.protobuf.ByteString.EMPTY;
+
+      address_ = "";
+
+      ipPort_ = 0;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Peer_descriptor;
+    }
+
+    public io.grpc.binarylog.v1alpha.Peer getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1alpha.Peer.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1alpha.Peer build() {
+      io.grpc.binarylog.v1alpha.Peer result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1alpha.Peer buildPartial() {
+      io.grpc.binarylog.v1alpha.Peer result = new io.grpc.binarylog.v1alpha.Peer(this);
+      result.peerType_ = peerType_;
+      result.peer_ = peer_;
+      result.address_ = address_;
+      result.ipPort_ = ipPort_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.binarylog.v1alpha.Peer) {
+        return mergeFrom((io.grpc.binarylog.v1alpha.Peer)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1alpha.Peer other) {
+      if (other == io.grpc.binarylog.v1alpha.Peer.getDefaultInstance()) return this;
+      if (other.peerType_ != 0) {
+        setPeerTypeValue(other.getPeerTypeValue());
+      }
+      if (other.getPeer() != com.google.protobuf.ByteString.EMPTY) {
+        setPeer(other.getPeer());
+      }
+      if (!other.getAddress().isEmpty()) {
+        address_ = other.address_;
+        onChanged();
+      }
+      if (other.getIpPort() != 0) {
+        setIpPort(other.getIpPort());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.binarylog.v1alpha.Peer parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1alpha.Peer) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private int peerType_ = 0;
+    /**
+     * <code>.grpc.binarylog.v1alpha.Peer.PeerType peer_type = 1;</code>
+     */
+    public int getPeerTypeValue() {
+      return peerType_;
+    }
+    /**
+     * <code>.grpc.binarylog.v1alpha.Peer.PeerType peer_type = 1;</code>
+     */
+    public Builder setPeerTypeValue(int value) {
+      peerType_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1alpha.Peer.PeerType peer_type = 1;</code>
+     */
+    public io.grpc.binarylog.v1alpha.Peer.PeerType getPeerType() {
+      io.grpc.binarylog.v1alpha.Peer.PeerType result = io.grpc.binarylog.v1alpha.Peer.PeerType.valueOf(peerType_);
+      return result == null ? io.grpc.binarylog.v1alpha.Peer.PeerType.UNRECOGNIZED : result;
+    }
+    /**
+     * <code>.grpc.binarylog.v1alpha.Peer.PeerType peer_type = 1;</code>
+     */
+    public Builder setPeerType(io.grpc.binarylog.v1alpha.Peer.PeerType value) {
+      if (value == null) {
+        throw new NullPointerException();
+      }
+      
+      peerType_ = value.getNumber();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.grpc.binarylog.v1alpha.Peer.PeerType peer_type = 1;</code>
+     */
+    public Builder clearPeerType() {
+      
+      peerType_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.ByteString peer_ = com.google.protobuf.ByteString.EMPTY;
+    /**
+     * <pre>
+     * will be removed: do not use
+     * </pre>
+     *
+     * <code>bytes peer = 2;</code>
+     */
+    public com.google.protobuf.ByteString getPeer() {
+      return peer_;
+    }
+    /**
+     * <pre>
+     * will be removed: do not use
+     * </pre>
+     *
+     * <code>bytes peer = 2;</code>
+     */
+    public Builder setPeer(com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      peer_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * will be removed: do not use
+     * </pre>
+     *
+     * <code>bytes peer = 2;</code>
+     */
+    public Builder clearPeer() {
+      
+      peer_ = getDefaultInstance().getPeer();
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object address_ = "";
+    /**
+     * <code>string address = 3;</code>
+     */
+    public java.lang.String getAddress() {
+      java.lang.Object ref = address_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        address_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string address = 3;</code>
+     */
+    public com.google.protobuf.ByteString
+        getAddressBytes() {
+      java.lang.Object ref = address_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        address_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string address = 3;</code>
+     */
+    public Builder setAddress(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      address_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string address = 3;</code>
+     */
+    public Builder clearAddress() {
+      
+      address_ = getDefaultInstance().getAddress();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string address = 3;</code>
+     */
+    public Builder setAddressBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      address_ = value;
+      onChanged();
+      return this;
+    }
+
+    private int ipPort_ ;
+    /**
+     * <pre>
+     * only for PEER_IPV4 and PEER_IPV6
+     * </pre>
+     *
+     * <code>uint32 ip_port = 4;</code>
+     */
+    public int getIpPort() {
+      return ipPort_;
+    }
+    /**
+     * <pre>
+     * only for PEER_IPV4 and PEER_IPV6
+     * </pre>
+     *
+     * <code>uint32 ip_port = 4;</code>
+     */
+    public Builder setIpPort(int value) {
+      
+      ipPort_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * only for PEER_IPV4 and PEER_IPV6
+     * </pre>
+     *
+     * <code>uint32 ip_port = 4;</code>
+     */
+    public Builder clearIpPort() {
+      
+      ipPort_ = 0;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.binarylog.v1alpha.Peer)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1alpha.Peer)
+  private static final io.grpc.binarylog.v1alpha.Peer DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1alpha.Peer();
+  }
+
+  public static io.grpc.binarylog.v1alpha.Peer getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<Peer>
+      PARSER = new com.google.protobuf.AbstractParser<Peer>() {
+    public Peer parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Peer(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Peer> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Peer> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.binarylog.v1alpha.Peer getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1alpha/PeerOrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/PeerOrBuilder.java
new file mode 100644
index 0000000..15ec92c
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/PeerOrBuilder.java
@@ -0,0 +1,46 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1alpha/binarylog.proto
+
+package io.grpc.binarylog.v1alpha;
+
+public interface PeerOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1alpha.Peer)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>.grpc.binarylog.v1alpha.Peer.PeerType peer_type = 1;</code>
+   */
+  int getPeerTypeValue();
+  /**
+   * <code>.grpc.binarylog.v1alpha.Peer.PeerType peer_type = 1;</code>
+   */
+  io.grpc.binarylog.v1alpha.Peer.PeerType getPeerType();
+
+  /**
+   * <pre>
+   * will be removed: do not use
+   * </pre>
+   *
+   * <code>bytes peer = 2;</code>
+   */
+  com.google.protobuf.ByteString getPeer();
+
+  /**
+   * <code>string address = 3;</code>
+   */
+  java.lang.String getAddress();
+  /**
+   * <code>string address = 3;</code>
+   */
+  com.google.protobuf.ByteString
+      getAddressBytes();
+
+  /**
+   * <pre>
+   * only for PEER_IPV4 and PEER_IPV6
+   * </pre>
+   *
+   * <code>uint32 ip_port = 4;</code>
+   */
+  int getIpPort();
+}
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Uint128.java b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Uint128.java
new file mode 100644
index 0000000..db5949b
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Uint128.java
@@ -0,0 +1,515 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1alpha/binarylog.proto
+
+package io.grpc.binarylog.v1alpha;
+
+/**
+ * <pre>
+ * Used to record call_id.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.binarylog.v1alpha.Uint128}
+ */
+public  final class Uint128 extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.binarylog.v1alpha.Uint128)
+    Uint128OrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Uint128.newBuilder() to construct.
+  private Uint128(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Uint128() {
+    high_ = 0L;
+    low_ = 0L;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Uint128(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 9: {
+
+            high_ = input.readFixed64();
+            break;
+          }
+          case 17: {
+
+            low_ = input.readFixed64();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Uint128_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Uint128_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.binarylog.v1alpha.Uint128.class, io.grpc.binarylog.v1alpha.Uint128.Builder.class);
+  }
+
+  public static final int HIGH_FIELD_NUMBER = 1;
+  private long high_;
+  /**
+   * <code>fixed64 high = 1;</code>
+   */
+  public long getHigh() {
+    return high_;
+  }
+
+  public static final int LOW_FIELD_NUMBER = 2;
+  private long low_;
+  /**
+   * <code>fixed64 low = 2;</code>
+   */
+  public long getLow() {
+    return low_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (high_ != 0L) {
+      output.writeFixed64(1, high_);
+    }
+    if (low_ != 0L) {
+      output.writeFixed64(2, low_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (high_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeFixed64Size(1, high_);
+    }
+    if (low_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeFixed64Size(2, low_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.binarylog.v1alpha.Uint128)) {
+      return super.equals(obj);
+    }
+    io.grpc.binarylog.v1alpha.Uint128 other = (io.grpc.binarylog.v1alpha.Uint128) obj;
+
+    boolean result = true;
+    result = result && (getHigh()
+        == other.getHigh());
+    result = result && (getLow()
+        == other.getLow());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + HIGH_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getHigh());
+    hash = (37 * hash) + LOW_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getLow());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.binarylog.v1alpha.Uint128 parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.Uint128 parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Uint128 parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.Uint128 parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Uint128 parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.binarylog.v1alpha.Uint128 parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Uint128 parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.Uint128 parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Uint128 parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.Uint128 parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.binarylog.v1alpha.Uint128 parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.binarylog.v1alpha.Uint128 parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.binarylog.v1alpha.Uint128 prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Used to record call_id.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.binarylog.v1alpha.Uint128}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.binarylog.v1alpha.Uint128)
+      io.grpc.binarylog.v1alpha.Uint128OrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Uint128_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Uint128_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.binarylog.v1alpha.Uint128.class, io.grpc.binarylog.v1alpha.Uint128.Builder.class);
+    }
+
+    // Construct using io.grpc.binarylog.v1alpha.Uint128.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      high_ = 0L;
+
+      low_ = 0L;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.binarylog.v1alpha.BinaryLogProto.internal_static_grpc_binarylog_v1alpha_Uint128_descriptor;
+    }
+
+    public io.grpc.binarylog.v1alpha.Uint128 getDefaultInstanceForType() {
+      return io.grpc.binarylog.v1alpha.Uint128.getDefaultInstance();
+    }
+
+    public io.grpc.binarylog.v1alpha.Uint128 build() {
+      io.grpc.binarylog.v1alpha.Uint128 result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.binarylog.v1alpha.Uint128 buildPartial() {
+      io.grpc.binarylog.v1alpha.Uint128 result = new io.grpc.binarylog.v1alpha.Uint128(this);
+      result.high_ = high_;
+      result.low_ = low_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.binarylog.v1alpha.Uint128) {
+        return mergeFrom((io.grpc.binarylog.v1alpha.Uint128)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.binarylog.v1alpha.Uint128 other) {
+      if (other == io.grpc.binarylog.v1alpha.Uint128.getDefaultInstance()) return this;
+      if (other.getHigh() != 0L) {
+        setHigh(other.getHigh());
+      }
+      if (other.getLow() != 0L) {
+        setLow(other.getLow());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.binarylog.v1alpha.Uint128 parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.binarylog.v1alpha.Uint128) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private long high_ ;
+    /**
+     * <code>fixed64 high = 1;</code>
+     */
+    public long getHigh() {
+      return high_;
+    }
+    /**
+     * <code>fixed64 high = 1;</code>
+     */
+    public Builder setHigh(long value) {
+      
+      high_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>fixed64 high = 1;</code>
+     */
+    public Builder clearHigh() {
+      
+      high_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long low_ ;
+    /**
+     * <code>fixed64 low = 2;</code>
+     */
+    public long getLow() {
+      return low_;
+    }
+    /**
+     * <code>fixed64 low = 2;</code>
+     */
+    public Builder setLow(long value) {
+      
+      low_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>fixed64 low = 2;</code>
+     */
+    public Builder clearLow() {
+      
+      low_ = 0L;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.binarylog.v1alpha.Uint128)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.binarylog.v1alpha.Uint128)
+  private static final io.grpc.binarylog.v1alpha.Uint128 DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.binarylog.v1alpha.Uint128();
+  }
+
+  public static io.grpc.binarylog.v1alpha.Uint128 getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<Uint128>
+      PARSER = new com.google.protobuf.AbstractParser<Uint128>() {
+    public Uint128 parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Uint128(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Uint128> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Uint128> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.binarylog.v1alpha.Uint128 getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Uint128OrBuilder.java b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Uint128OrBuilder.java
new file mode 100644
index 0000000..fbd37d0
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/binarylog/v1alpha/Uint128OrBuilder.java
@@ -0,0 +1,19 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/binlog/v1alpha/binarylog.proto
+
+package io.grpc.binarylog.v1alpha;
+
+public interface Uint128OrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.binarylog.v1alpha.Uint128)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>fixed64 high = 1;</code>
+   */
+  long getHigh();
+
+  /**
+   * <code>fixed64 low = 2;</code>
+   */
+  long getLow();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/Address.java b/services/src/generated/main/java/io/grpc/channelz/v1/Address.java
new file mode 100644
index 0000000..06e66d5
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/Address.java
@@ -0,0 +1,2995 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * Address represents the address used to create the socket.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.Address}
+ */
+public  final class Address extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.Address)
+    AddressOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Address.newBuilder() to construct.
+  private Address(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Address() {
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Address(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            io.grpc.channelz.v1.Address.TcpIpAddress.Builder subBuilder = null;
+            if (addressCase_ == 1) {
+              subBuilder = ((io.grpc.channelz.v1.Address.TcpIpAddress) address_).toBuilder();
+            }
+            address_ =
+                input.readMessage(io.grpc.channelz.v1.Address.TcpIpAddress.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.channelz.v1.Address.TcpIpAddress) address_);
+              address_ = subBuilder.buildPartial();
+            }
+            addressCase_ = 1;
+            break;
+          }
+          case 18: {
+            io.grpc.channelz.v1.Address.UdsAddress.Builder subBuilder = null;
+            if (addressCase_ == 2) {
+              subBuilder = ((io.grpc.channelz.v1.Address.UdsAddress) address_).toBuilder();
+            }
+            address_ =
+                input.readMessage(io.grpc.channelz.v1.Address.UdsAddress.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.channelz.v1.Address.UdsAddress) address_);
+              address_ = subBuilder.buildPartial();
+            }
+            addressCase_ = 2;
+            break;
+          }
+          case 26: {
+            io.grpc.channelz.v1.Address.OtherAddress.Builder subBuilder = null;
+            if (addressCase_ == 3) {
+              subBuilder = ((io.grpc.channelz.v1.Address.OtherAddress) address_).toBuilder();
+            }
+            address_ =
+                input.readMessage(io.grpc.channelz.v1.Address.OtherAddress.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.channelz.v1.Address.OtherAddress) address_);
+              address_ = subBuilder.buildPartial();
+            }
+            addressCase_ = 3;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.Address.class, io.grpc.channelz.v1.Address.Builder.class);
+  }
+
+  public interface TcpIpAddressOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.Address.TcpIpAddress)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * Either the IPv4 or IPv6 address in bytes.  Will be either 4 bytes or 16
+     * bytes in length.
+     * </pre>
+     *
+     * <code>bytes ip_address = 1;</code>
+     */
+    com.google.protobuf.ByteString getIpAddress();
+
+    /**
+     * <pre>
+     * 0-64k, or -1 if not appropriate.
+     * </pre>
+     *
+     * <code>int32 port = 2;</code>
+     */
+    int getPort();
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.Address.TcpIpAddress}
+   */
+  public  static final class TcpIpAddress extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.channelz.v1.Address.TcpIpAddress)
+      TcpIpAddressOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use TcpIpAddress.newBuilder() to construct.
+    private TcpIpAddress(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private TcpIpAddress() {
+      ipAddress_ = com.google.protobuf.ByteString.EMPTY;
+      port_ = 0;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private TcpIpAddress(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+
+              ipAddress_ = input.readBytes();
+              break;
+            }
+            case 16: {
+
+              port_ = input.readInt32();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_TcpIpAddress_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_TcpIpAddress_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.Address.TcpIpAddress.class, io.grpc.channelz.v1.Address.TcpIpAddress.Builder.class);
+    }
+
+    public static final int IP_ADDRESS_FIELD_NUMBER = 1;
+    private com.google.protobuf.ByteString ipAddress_;
+    /**
+     * <pre>
+     * Either the IPv4 or IPv6 address in bytes.  Will be either 4 bytes or 16
+     * bytes in length.
+     * </pre>
+     *
+     * <code>bytes ip_address = 1;</code>
+     */
+    public com.google.protobuf.ByteString getIpAddress() {
+      return ipAddress_;
+    }
+
+    public static final int PORT_FIELD_NUMBER = 2;
+    private int port_;
+    /**
+     * <pre>
+     * 0-64k, or -1 if not appropriate.
+     * </pre>
+     *
+     * <code>int32 port = 2;</code>
+     */
+    public int getPort() {
+      return port_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!ipAddress_.isEmpty()) {
+        output.writeBytes(1, ipAddress_);
+      }
+      if (port_ != 0) {
+        output.writeInt32(2, port_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!ipAddress_.isEmpty()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBytesSize(1, ipAddress_);
+      }
+      if (port_ != 0) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(2, port_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.channelz.v1.Address.TcpIpAddress)) {
+        return super.equals(obj);
+      }
+      io.grpc.channelz.v1.Address.TcpIpAddress other = (io.grpc.channelz.v1.Address.TcpIpAddress) obj;
+
+      boolean result = true;
+      result = result && getIpAddress()
+          .equals(other.getIpAddress());
+      result = result && (getPort()
+          == other.getPort());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + IP_ADDRESS_FIELD_NUMBER;
+      hash = (53 * hash) + getIpAddress().hashCode();
+      hash = (37 * hash) + PORT_FIELD_NUMBER;
+      hash = (53 * hash) + getPort();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.channelz.v1.Address.TcpIpAddress parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Address.TcpIpAddress parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.TcpIpAddress parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Address.TcpIpAddress parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.TcpIpAddress parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Address.TcpIpAddress parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.TcpIpAddress parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Address.TcpIpAddress parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.TcpIpAddress parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Address.TcpIpAddress parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.TcpIpAddress parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Address.TcpIpAddress parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.channelz.v1.Address.TcpIpAddress prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.channelz.v1.Address.TcpIpAddress}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.Address.TcpIpAddress)
+        io.grpc.channelz.v1.Address.TcpIpAddressOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_TcpIpAddress_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_TcpIpAddress_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.channelz.v1.Address.TcpIpAddress.class, io.grpc.channelz.v1.Address.TcpIpAddress.Builder.class);
+      }
+
+      // Construct using io.grpc.channelz.v1.Address.TcpIpAddress.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        ipAddress_ = com.google.protobuf.ByteString.EMPTY;
+
+        port_ = 0;
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_TcpIpAddress_descriptor;
+      }
+
+      public io.grpc.channelz.v1.Address.TcpIpAddress getDefaultInstanceForType() {
+        return io.grpc.channelz.v1.Address.TcpIpAddress.getDefaultInstance();
+      }
+
+      public io.grpc.channelz.v1.Address.TcpIpAddress build() {
+        io.grpc.channelz.v1.Address.TcpIpAddress result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.channelz.v1.Address.TcpIpAddress buildPartial() {
+        io.grpc.channelz.v1.Address.TcpIpAddress result = new io.grpc.channelz.v1.Address.TcpIpAddress(this);
+        result.ipAddress_ = ipAddress_;
+        result.port_ = port_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.channelz.v1.Address.TcpIpAddress) {
+          return mergeFrom((io.grpc.channelz.v1.Address.TcpIpAddress)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.channelz.v1.Address.TcpIpAddress other) {
+        if (other == io.grpc.channelz.v1.Address.TcpIpAddress.getDefaultInstance()) return this;
+        if (other.getIpAddress() != com.google.protobuf.ByteString.EMPTY) {
+          setIpAddress(other.getIpAddress());
+        }
+        if (other.getPort() != 0) {
+          setPort(other.getPort());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.channelz.v1.Address.TcpIpAddress parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.channelz.v1.Address.TcpIpAddress) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private com.google.protobuf.ByteString ipAddress_ = com.google.protobuf.ByteString.EMPTY;
+      /**
+       * <pre>
+       * Either the IPv4 or IPv6 address in bytes.  Will be either 4 bytes or 16
+       * bytes in length.
+       * </pre>
+       *
+       * <code>bytes ip_address = 1;</code>
+       */
+      public com.google.protobuf.ByteString getIpAddress() {
+        return ipAddress_;
+      }
+      /**
+       * <pre>
+       * Either the IPv4 or IPv6 address in bytes.  Will be either 4 bytes or 16
+       * bytes in length.
+       * </pre>
+       *
+       * <code>bytes ip_address = 1;</code>
+       */
+      public Builder setIpAddress(com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        ipAddress_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Either the IPv4 or IPv6 address in bytes.  Will be either 4 bytes or 16
+       * bytes in length.
+       * </pre>
+       *
+       * <code>bytes ip_address = 1;</code>
+       */
+      public Builder clearIpAddress() {
+        
+        ipAddress_ = getDefaultInstance().getIpAddress();
+        onChanged();
+        return this;
+      }
+
+      private int port_ ;
+      /**
+       * <pre>
+       * 0-64k, or -1 if not appropriate.
+       * </pre>
+       *
+       * <code>int32 port = 2;</code>
+       */
+      public int getPort() {
+        return port_;
+      }
+      /**
+       * <pre>
+       * 0-64k, or -1 if not appropriate.
+       * </pre>
+       *
+       * <code>int32 port = 2;</code>
+       */
+      public Builder setPort(int value) {
+        
+        port_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * 0-64k, or -1 if not appropriate.
+       * </pre>
+       *
+       * <code>int32 port = 2;</code>
+       */
+      public Builder clearPort() {
+        
+        port_ = 0;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.Address.TcpIpAddress)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.channelz.v1.Address.TcpIpAddress)
+    private static final io.grpc.channelz.v1.Address.TcpIpAddress DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.channelz.v1.Address.TcpIpAddress();
+    }
+
+    public static io.grpc.channelz.v1.Address.TcpIpAddress getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<TcpIpAddress>
+        PARSER = new com.google.protobuf.AbstractParser<TcpIpAddress>() {
+      public TcpIpAddress parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new TcpIpAddress(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<TcpIpAddress> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<TcpIpAddress> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.channelz.v1.Address.TcpIpAddress getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface UdsAddressOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.Address.UdsAddress)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <code>string filename = 1;</code>
+     */
+    java.lang.String getFilename();
+    /**
+     * <code>string filename = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getFilenameBytes();
+  }
+  /**
+   * <pre>
+   * A Unix Domain Socket address.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.Address.UdsAddress}
+   */
+  public  static final class UdsAddress extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.channelz.v1.Address.UdsAddress)
+      UdsAddressOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use UdsAddress.newBuilder() to construct.
+    private UdsAddress(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private UdsAddress() {
+      filename_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private UdsAddress(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              filename_ = s;
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_UdsAddress_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_UdsAddress_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.Address.UdsAddress.class, io.grpc.channelz.v1.Address.UdsAddress.Builder.class);
+    }
+
+    public static final int FILENAME_FIELD_NUMBER = 1;
+    private volatile java.lang.Object filename_;
+    /**
+     * <code>string filename = 1;</code>
+     */
+    public java.lang.String getFilename() {
+      java.lang.Object ref = filename_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        filename_ = s;
+        return s;
+      }
+    }
+    /**
+     * <code>string filename = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getFilenameBytes() {
+      java.lang.Object ref = filename_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        filename_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!getFilenameBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, filename_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!getFilenameBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, filename_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.channelz.v1.Address.UdsAddress)) {
+        return super.equals(obj);
+      }
+      io.grpc.channelz.v1.Address.UdsAddress other = (io.grpc.channelz.v1.Address.UdsAddress) obj;
+
+      boolean result = true;
+      result = result && getFilename()
+          .equals(other.getFilename());
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + FILENAME_FIELD_NUMBER;
+      hash = (53 * hash) + getFilename().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.channelz.v1.Address.UdsAddress parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Address.UdsAddress parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.UdsAddress parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Address.UdsAddress parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.UdsAddress parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Address.UdsAddress parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.UdsAddress parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Address.UdsAddress parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.UdsAddress parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Address.UdsAddress parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.UdsAddress parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Address.UdsAddress parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.channelz.v1.Address.UdsAddress prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * A Unix Domain Socket address.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.channelz.v1.Address.UdsAddress}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.Address.UdsAddress)
+        io.grpc.channelz.v1.Address.UdsAddressOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_UdsAddress_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_UdsAddress_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.channelz.v1.Address.UdsAddress.class, io.grpc.channelz.v1.Address.UdsAddress.Builder.class);
+      }
+
+      // Construct using io.grpc.channelz.v1.Address.UdsAddress.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        filename_ = "";
+
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_UdsAddress_descriptor;
+      }
+
+      public io.grpc.channelz.v1.Address.UdsAddress getDefaultInstanceForType() {
+        return io.grpc.channelz.v1.Address.UdsAddress.getDefaultInstance();
+      }
+
+      public io.grpc.channelz.v1.Address.UdsAddress build() {
+        io.grpc.channelz.v1.Address.UdsAddress result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.channelz.v1.Address.UdsAddress buildPartial() {
+        io.grpc.channelz.v1.Address.UdsAddress result = new io.grpc.channelz.v1.Address.UdsAddress(this);
+        result.filename_ = filename_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.channelz.v1.Address.UdsAddress) {
+          return mergeFrom((io.grpc.channelz.v1.Address.UdsAddress)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.channelz.v1.Address.UdsAddress other) {
+        if (other == io.grpc.channelz.v1.Address.UdsAddress.getDefaultInstance()) return this;
+        if (!other.getFilename().isEmpty()) {
+          filename_ = other.filename_;
+          onChanged();
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.channelz.v1.Address.UdsAddress parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.channelz.v1.Address.UdsAddress) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private java.lang.Object filename_ = "";
+      /**
+       * <code>string filename = 1;</code>
+       */
+      public java.lang.String getFilename() {
+        java.lang.Object ref = filename_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          filename_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <code>string filename = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getFilenameBytes() {
+        java.lang.Object ref = filename_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          filename_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <code>string filename = 1;</code>
+       */
+      public Builder setFilename(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        filename_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string filename = 1;</code>
+       */
+      public Builder clearFilename() {
+        
+        filename_ = getDefaultInstance().getFilename();
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>string filename = 1;</code>
+       */
+      public Builder setFilenameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        filename_ = value;
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.Address.UdsAddress)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.channelz.v1.Address.UdsAddress)
+    private static final io.grpc.channelz.v1.Address.UdsAddress DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.channelz.v1.Address.UdsAddress();
+    }
+
+    public static io.grpc.channelz.v1.Address.UdsAddress getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<UdsAddress>
+        PARSER = new com.google.protobuf.AbstractParser<UdsAddress>() {
+      public UdsAddress parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new UdsAddress(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<UdsAddress> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<UdsAddress> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.channelz.v1.Address.UdsAddress getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface OtherAddressOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.Address.OtherAddress)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * The human readable version of the value.  This value should be set.
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    java.lang.String getName();
+    /**
+     * <pre>
+     * The human readable version of the value.  This value should be set.
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getNameBytes();
+
+    /**
+     * <pre>
+     * The actual address message.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any value = 2;</code>
+     */
+    boolean hasValue();
+    /**
+     * <pre>
+     * The actual address message.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any value = 2;</code>
+     */
+    com.google.protobuf.Any getValue();
+    /**
+     * <pre>
+     * The actual address message.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any value = 2;</code>
+     */
+    com.google.protobuf.AnyOrBuilder getValueOrBuilder();
+  }
+  /**
+   * <pre>
+   * An address type not included above.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.Address.OtherAddress}
+   */
+  public  static final class OtherAddress extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.channelz.v1.Address.OtherAddress)
+      OtherAddressOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use OtherAddress.newBuilder() to construct.
+    private OtherAddress(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private OtherAddress() {
+      name_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private OtherAddress(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              name_ = s;
+              break;
+            }
+            case 18: {
+              com.google.protobuf.Any.Builder subBuilder = null;
+              if (value_ != null) {
+                subBuilder = value_.toBuilder();
+              }
+              value_ = input.readMessage(com.google.protobuf.Any.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(value_);
+                value_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_OtherAddress_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_OtherAddress_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.Address.OtherAddress.class, io.grpc.channelz.v1.Address.OtherAddress.Builder.class);
+    }
+
+    public static final int NAME_FIELD_NUMBER = 1;
+    private volatile java.lang.Object name_;
+    /**
+     * <pre>
+     * The human readable version of the value.  This value should be set.
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        name_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * The human readable version of the value.  This value should be set.
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int VALUE_FIELD_NUMBER = 2;
+    private com.google.protobuf.Any value_;
+    /**
+     * <pre>
+     * The actual address message.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any value = 2;</code>
+     */
+    public boolean hasValue() {
+      return value_ != null;
+    }
+    /**
+     * <pre>
+     * The actual address message.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any value = 2;</code>
+     */
+    public com.google.protobuf.Any getValue() {
+      return value_ == null ? com.google.protobuf.Any.getDefaultInstance() : value_;
+    }
+    /**
+     * <pre>
+     * The actual address message.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any value = 2;</code>
+     */
+    public com.google.protobuf.AnyOrBuilder getValueOrBuilder() {
+      return getValue();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!getNameBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_);
+      }
+      if (value_ != null) {
+        output.writeMessage(2, getValue());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!getNameBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_);
+      }
+      if (value_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, getValue());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.channelz.v1.Address.OtherAddress)) {
+        return super.equals(obj);
+      }
+      io.grpc.channelz.v1.Address.OtherAddress other = (io.grpc.channelz.v1.Address.OtherAddress) obj;
+
+      boolean result = true;
+      result = result && getName()
+          .equals(other.getName());
+      result = result && (hasValue() == other.hasValue());
+      if (hasValue()) {
+        result = result && getValue()
+            .equals(other.getValue());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + NAME_FIELD_NUMBER;
+      hash = (53 * hash) + getName().hashCode();
+      if (hasValue()) {
+        hash = (37 * hash) + VALUE_FIELD_NUMBER;
+        hash = (53 * hash) + getValue().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.channelz.v1.Address.OtherAddress parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Address.OtherAddress parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.OtherAddress parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Address.OtherAddress parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.OtherAddress parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Address.OtherAddress parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.OtherAddress parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Address.OtherAddress parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.OtherAddress parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Address.OtherAddress parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Address.OtherAddress parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Address.OtherAddress parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.channelz.v1.Address.OtherAddress prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * <pre>
+     * An address type not included above.
+     * </pre>
+     *
+     * Protobuf type {@code grpc.channelz.v1.Address.OtherAddress}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.Address.OtherAddress)
+        io.grpc.channelz.v1.Address.OtherAddressOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_OtherAddress_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_OtherAddress_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.channelz.v1.Address.OtherAddress.class, io.grpc.channelz.v1.Address.OtherAddress.Builder.class);
+      }
+
+      // Construct using io.grpc.channelz.v1.Address.OtherAddress.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        name_ = "";
+
+        if (valueBuilder_ == null) {
+          value_ = null;
+        } else {
+          value_ = null;
+          valueBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_OtherAddress_descriptor;
+      }
+
+      public io.grpc.channelz.v1.Address.OtherAddress getDefaultInstanceForType() {
+        return io.grpc.channelz.v1.Address.OtherAddress.getDefaultInstance();
+      }
+
+      public io.grpc.channelz.v1.Address.OtherAddress build() {
+        io.grpc.channelz.v1.Address.OtherAddress result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.channelz.v1.Address.OtherAddress buildPartial() {
+        io.grpc.channelz.v1.Address.OtherAddress result = new io.grpc.channelz.v1.Address.OtherAddress(this);
+        result.name_ = name_;
+        if (valueBuilder_ == null) {
+          result.value_ = value_;
+        } else {
+          result.value_ = valueBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.channelz.v1.Address.OtherAddress) {
+          return mergeFrom((io.grpc.channelz.v1.Address.OtherAddress)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.channelz.v1.Address.OtherAddress other) {
+        if (other == io.grpc.channelz.v1.Address.OtherAddress.getDefaultInstance()) return this;
+        if (!other.getName().isEmpty()) {
+          name_ = other.name_;
+          onChanged();
+        }
+        if (other.hasValue()) {
+          mergeValue(other.getValue());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.channelz.v1.Address.OtherAddress parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.channelz.v1.Address.OtherAddress) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private java.lang.Object name_ = "";
+      /**
+       * <pre>
+       * The human readable version of the value.  This value should be set.
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public java.lang.String getName() {
+        java.lang.Object ref = name_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          name_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The human readable version of the value.  This value should be set.
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getNameBytes() {
+        java.lang.Object ref = name_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          name_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The human readable version of the value.  This value should be set.
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public Builder setName(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        name_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The human readable version of the value.  This value should be set.
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public Builder clearName() {
+        
+        name_ = getDefaultInstance().getName();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The human readable version of the value.  This value should be set.
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public Builder setNameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        name_ = value;
+        onChanged();
+        return this;
+      }
+
+      private com.google.protobuf.Any value_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          com.google.protobuf.Any, com.google.protobuf.Any.Builder, com.google.protobuf.AnyOrBuilder> valueBuilder_;
+      /**
+       * <pre>
+       * The actual address message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public boolean hasValue() {
+        return valueBuilder_ != null || value_ != null;
+      }
+      /**
+       * <pre>
+       * The actual address message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public com.google.protobuf.Any getValue() {
+        if (valueBuilder_ == null) {
+          return value_ == null ? com.google.protobuf.Any.getDefaultInstance() : value_;
+        } else {
+          return valueBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * The actual address message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public Builder setValue(com.google.protobuf.Any value) {
+        if (valueBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          value_ = value;
+          onChanged();
+        } else {
+          valueBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The actual address message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public Builder setValue(
+          com.google.protobuf.Any.Builder builderForValue) {
+        if (valueBuilder_ == null) {
+          value_ = builderForValue.build();
+          onChanged();
+        } else {
+          valueBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The actual address message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public Builder mergeValue(com.google.protobuf.Any value) {
+        if (valueBuilder_ == null) {
+          if (value_ != null) {
+            value_ =
+              com.google.protobuf.Any.newBuilder(value_).mergeFrom(value).buildPartial();
+          } else {
+            value_ = value;
+          }
+          onChanged();
+        } else {
+          valueBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The actual address message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public Builder clearValue() {
+        if (valueBuilder_ == null) {
+          value_ = null;
+          onChanged();
+        } else {
+          value_ = null;
+          valueBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The actual address message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public com.google.protobuf.Any.Builder getValueBuilder() {
+        
+        onChanged();
+        return getValueFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * The actual address message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public com.google.protobuf.AnyOrBuilder getValueOrBuilder() {
+        if (valueBuilder_ != null) {
+          return valueBuilder_.getMessageOrBuilder();
+        } else {
+          return value_ == null ?
+              com.google.protobuf.Any.getDefaultInstance() : value_;
+        }
+      }
+      /**
+       * <pre>
+       * The actual address message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          com.google.protobuf.Any, com.google.protobuf.Any.Builder, com.google.protobuf.AnyOrBuilder> 
+          getValueFieldBuilder() {
+        if (valueBuilder_ == null) {
+          valueBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              com.google.protobuf.Any, com.google.protobuf.Any.Builder, com.google.protobuf.AnyOrBuilder>(
+                  getValue(),
+                  getParentForChildren(),
+                  isClean());
+          value_ = null;
+        }
+        return valueBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.Address.OtherAddress)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.channelz.v1.Address.OtherAddress)
+    private static final io.grpc.channelz.v1.Address.OtherAddress DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.channelz.v1.Address.OtherAddress();
+    }
+
+    public static io.grpc.channelz.v1.Address.OtherAddress getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<OtherAddress>
+        PARSER = new com.google.protobuf.AbstractParser<OtherAddress>() {
+      public OtherAddress parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new OtherAddress(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<OtherAddress> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<OtherAddress> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.channelz.v1.Address.OtherAddress getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  private int addressCase_ = 0;
+  private java.lang.Object address_;
+  public enum AddressCase
+      implements com.google.protobuf.Internal.EnumLite {
+    TCPIP_ADDRESS(1),
+    UDS_ADDRESS(2),
+    OTHER_ADDRESS(3),
+    ADDRESS_NOT_SET(0);
+    private final int value;
+    private AddressCase(int value) {
+      this.value = value;
+    }
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static AddressCase valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static AddressCase forNumber(int value) {
+      switch (value) {
+        case 1: return TCPIP_ADDRESS;
+        case 2: return UDS_ADDRESS;
+        case 3: return OTHER_ADDRESS;
+        case 0: return ADDRESS_NOT_SET;
+        default: return null;
+      }
+    }
+    public int getNumber() {
+      return this.value;
+    }
+  };
+
+  public AddressCase
+  getAddressCase() {
+    return AddressCase.forNumber(
+        addressCase_);
+  }
+
+  public static final int TCPIP_ADDRESS_FIELD_NUMBER = 1;
+  /**
+   * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+   */
+  public boolean hasTcpipAddress() {
+    return addressCase_ == 1;
+  }
+  /**
+   * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+   */
+  public io.grpc.channelz.v1.Address.TcpIpAddress getTcpipAddress() {
+    if (addressCase_ == 1) {
+       return (io.grpc.channelz.v1.Address.TcpIpAddress) address_;
+    }
+    return io.grpc.channelz.v1.Address.TcpIpAddress.getDefaultInstance();
+  }
+  /**
+   * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+   */
+  public io.grpc.channelz.v1.Address.TcpIpAddressOrBuilder getTcpipAddressOrBuilder() {
+    if (addressCase_ == 1) {
+       return (io.grpc.channelz.v1.Address.TcpIpAddress) address_;
+    }
+    return io.grpc.channelz.v1.Address.TcpIpAddress.getDefaultInstance();
+  }
+
+  public static final int UDS_ADDRESS_FIELD_NUMBER = 2;
+  /**
+   * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+   */
+  public boolean hasUdsAddress() {
+    return addressCase_ == 2;
+  }
+  /**
+   * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+   */
+  public io.grpc.channelz.v1.Address.UdsAddress getUdsAddress() {
+    if (addressCase_ == 2) {
+       return (io.grpc.channelz.v1.Address.UdsAddress) address_;
+    }
+    return io.grpc.channelz.v1.Address.UdsAddress.getDefaultInstance();
+  }
+  /**
+   * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+   */
+  public io.grpc.channelz.v1.Address.UdsAddressOrBuilder getUdsAddressOrBuilder() {
+    if (addressCase_ == 2) {
+       return (io.grpc.channelz.v1.Address.UdsAddress) address_;
+    }
+    return io.grpc.channelz.v1.Address.UdsAddress.getDefaultInstance();
+  }
+
+  public static final int OTHER_ADDRESS_FIELD_NUMBER = 3;
+  /**
+   * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+   */
+  public boolean hasOtherAddress() {
+    return addressCase_ == 3;
+  }
+  /**
+   * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+   */
+  public io.grpc.channelz.v1.Address.OtherAddress getOtherAddress() {
+    if (addressCase_ == 3) {
+       return (io.grpc.channelz.v1.Address.OtherAddress) address_;
+    }
+    return io.grpc.channelz.v1.Address.OtherAddress.getDefaultInstance();
+  }
+  /**
+   * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+   */
+  public io.grpc.channelz.v1.Address.OtherAddressOrBuilder getOtherAddressOrBuilder() {
+    if (addressCase_ == 3) {
+       return (io.grpc.channelz.v1.Address.OtherAddress) address_;
+    }
+    return io.grpc.channelz.v1.Address.OtherAddress.getDefaultInstance();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (addressCase_ == 1) {
+      output.writeMessage(1, (io.grpc.channelz.v1.Address.TcpIpAddress) address_);
+    }
+    if (addressCase_ == 2) {
+      output.writeMessage(2, (io.grpc.channelz.v1.Address.UdsAddress) address_);
+    }
+    if (addressCase_ == 3) {
+      output.writeMessage(3, (io.grpc.channelz.v1.Address.OtherAddress) address_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (addressCase_ == 1) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, (io.grpc.channelz.v1.Address.TcpIpAddress) address_);
+    }
+    if (addressCase_ == 2) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(2, (io.grpc.channelz.v1.Address.UdsAddress) address_);
+    }
+    if (addressCase_ == 3) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(3, (io.grpc.channelz.v1.Address.OtherAddress) address_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.Address)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.Address other = (io.grpc.channelz.v1.Address) obj;
+
+    boolean result = true;
+    result = result && getAddressCase().equals(
+        other.getAddressCase());
+    if (!result) return false;
+    switch (addressCase_) {
+      case 1:
+        result = result && getTcpipAddress()
+            .equals(other.getTcpipAddress());
+        break;
+      case 2:
+        result = result && getUdsAddress()
+            .equals(other.getUdsAddress());
+        break;
+      case 3:
+        result = result && getOtherAddress()
+            .equals(other.getOtherAddress());
+        break;
+      case 0:
+      default:
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    switch (addressCase_) {
+      case 1:
+        hash = (37 * hash) + TCPIP_ADDRESS_FIELD_NUMBER;
+        hash = (53 * hash) + getTcpipAddress().hashCode();
+        break;
+      case 2:
+        hash = (37 * hash) + UDS_ADDRESS_FIELD_NUMBER;
+        hash = (53 * hash) + getUdsAddress().hashCode();
+        break;
+      case 3:
+        hash = (37 * hash) + OTHER_ADDRESS_FIELD_NUMBER;
+        hash = (53 * hash) + getOtherAddress().hashCode();
+        break;
+      case 0:
+      default:
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.Address parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Address parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Address parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Address parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Address parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Address parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Address parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Address parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Address parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Address parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Address parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Address parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.Address prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Address represents the address used to create the socket.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.Address}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.Address)
+      io.grpc.channelz.v1.AddressOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.Address.class, io.grpc.channelz.v1.Address.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.Address.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      addressCase_ = 0;
+      address_ = null;
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Address_descriptor;
+    }
+
+    public io.grpc.channelz.v1.Address getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.Address.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.Address build() {
+      io.grpc.channelz.v1.Address result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.Address buildPartial() {
+      io.grpc.channelz.v1.Address result = new io.grpc.channelz.v1.Address(this);
+      if (addressCase_ == 1) {
+        if (tcpipAddressBuilder_ == null) {
+          result.address_ = address_;
+        } else {
+          result.address_ = tcpipAddressBuilder_.build();
+        }
+      }
+      if (addressCase_ == 2) {
+        if (udsAddressBuilder_ == null) {
+          result.address_ = address_;
+        } else {
+          result.address_ = udsAddressBuilder_.build();
+        }
+      }
+      if (addressCase_ == 3) {
+        if (otherAddressBuilder_ == null) {
+          result.address_ = address_;
+        } else {
+          result.address_ = otherAddressBuilder_.build();
+        }
+      }
+      result.addressCase_ = addressCase_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.Address) {
+        return mergeFrom((io.grpc.channelz.v1.Address)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.Address other) {
+      if (other == io.grpc.channelz.v1.Address.getDefaultInstance()) return this;
+      switch (other.getAddressCase()) {
+        case TCPIP_ADDRESS: {
+          mergeTcpipAddress(other.getTcpipAddress());
+          break;
+        }
+        case UDS_ADDRESS: {
+          mergeUdsAddress(other.getUdsAddress());
+          break;
+        }
+        case OTHER_ADDRESS: {
+          mergeOtherAddress(other.getOtherAddress());
+          break;
+        }
+        case ADDRESS_NOT_SET: {
+          break;
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.Address parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.Address) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int addressCase_ = 0;
+    private java.lang.Object address_;
+    public AddressCase
+        getAddressCase() {
+      return AddressCase.forNumber(
+          addressCase_);
+    }
+
+    public Builder clearAddress() {
+      addressCase_ = 0;
+      address_ = null;
+      onChanged();
+      return this;
+    }
+
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Address.TcpIpAddress, io.grpc.channelz.v1.Address.TcpIpAddress.Builder, io.grpc.channelz.v1.Address.TcpIpAddressOrBuilder> tcpipAddressBuilder_;
+    /**
+     * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+     */
+    public boolean hasTcpipAddress() {
+      return addressCase_ == 1;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+     */
+    public io.grpc.channelz.v1.Address.TcpIpAddress getTcpipAddress() {
+      if (tcpipAddressBuilder_ == null) {
+        if (addressCase_ == 1) {
+          return (io.grpc.channelz.v1.Address.TcpIpAddress) address_;
+        }
+        return io.grpc.channelz.v1.Address.TcpIpAddress.getDefaultInstance();
+      } else {
+        if (addressCase_ == 1) {
+          return tcpipAddressBuilder_.getMessage();
+        }
+        return io.grpc.channelz.v1.Address.TcpIpAddress.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+     */
+    public Builder setTcpipAddress(io.grpc.channelz.v1.Address.TcpIpAddress value) {
+      if (tcpipAddressBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        address_ = value;
+        onChanged();
+      } else {
+        tcpipAddressBuilder_.setMessage(value);
+      }
+      addressCase_ = 1;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+     */
+    public Builder setTcpipAddress(
+        io.grpc.channelz.v1.Address.TcpIpAddress.Builder builderForValue) {
+      if (tcpipAddressBuilder_ == null) {
+        address_ = builderForValue.build();
+        onChanged();
+      } else {
+        tcpipAddressBuilder_.setMessage(builderForValue.build());
+      }
+      addressCase_ = 1;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+     */
+    public Builder mergeTcpipAddress(io.grpc.channelz.v1.Address.TcpIpAddress value) {
+      if (tcpipAddressBuilder_ == null) {
+        if (addressCase_ == 1 &&
+            address_ != io.grpc.channelz.v1.Address.TcpIpAddress.getDefaultInstance()) {
+          address_ = io.grpc.channelz.v1.Address.TcpIpAddress.newBuilder((io.grpc.channelz.v1.Address.TcpIpAddress) address_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          address_ = value;
+        }
+        onChanged();
+      } else {
+        if (addressCase_ == 1) {
+          tcpipAddressBuilder_.mergeFrom(value);
+        }
+        tcpipAddressBuilder_.setMessage(value);
+      }
+      addressCase_ = 1;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+     */
+    public Builder clearTcpipAddress() {
+      if (tcpipAddressBuilder_ == null) {
+        if (addressCase_ == 1) {
+          addressCase_ = 0;
+          address_ = null;
+          onChanged();
+        }
+      } else {
+        if (addressCase_ == 1) {
+          addressCase_ = 0;
+          address_ = null;
+        }
+        tcpipAddressBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+     */
+    public io.grpc.channelz.v1.Address.TcpIpAddress.Builder getTcpipAddressBuilder() {
+      return getTcpipAddressFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+     */
+    public io.grpc.channelz.v1.Address.TcpIpAddressOrBuilder getTcpipAddressOrBuilder() {
+      if ((addressCase_ == 1) && (tcpipAddressBuilder_ != null)) {
+        return tcpipAddressBuilder_.getMessageOrBuilder();
+      } else {
+        if (addressCase_ == 1) {
+          return (io.grpc.channelz.v1.Address.TcpIpAddress) address_;
+        }
+        return io.grpc.channelz.v1.Address.TcpIpAddress.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Address.TcpIpAddress, io.grpc.channelz.v1.Address.TcpIpAddress.Builder, io.grpc.channelz.v1.Address.TcpIpAddressOrBuilder> 
+        getTcpipAddressFieldBuilder() {
+      if (tcpipAddressBuilder_ == null) {
+        if (!(addressCase_ == 1)) {
+          address_ = io.grpc.channelz.v1.Address.TcpIpAddress.getDefaultInstance();
+        }
+        tcpipAddressBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.Address.TcpIpAddress, io.grpc.channelz.v1.Address.TcpIpAddress.Builder, io.grpc.channelz.v1.Address.TcpIpAddressOrBuilder>(
+                (io.grpc.channelz.v1.Address.TcpIpAddress) address_,
+                getParentForChildren(),
+                isClean());
+        address_ = null;
+      }
+      addressCase_ = 1;
+      onChanged();;
+      return tcpipAddressBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Address.UdsAddress, io.grpc.channelz.v1.Address.UdsAddress.Builder, io.grpc.channelz.v1.Address.UdsAddressOrBuilder> udsAddressBuilder_;
+    /**
+     * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+     */
+    public boolean hasUdsAddress() {
+      return addressCase_ == 2;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+     */
+    public io.grpc.channelz.v1.Address.UdsAddress getUdsAddress() {
+      if (udsAddressBuilder_ == null) {
+        if (addressCase_ == 2) {
+          return (io.grpc.channelz.v1.Address.UdsAddress) address_;
+        }
+        return io.grpc.channelz.v1.Address.UdsAddress.getDefaultInstance();
+      } else {
+        if (addressCase_ == 2) {
+          return udsAddressBuilder_.getMessage();
+        }
+        return io.grpc.channelz.v1.Address.UdsAddress.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+     */
+    public Builder setUdsAddress(io.grpc.channelz.v1.Address.UdsAddress value) {
+      if (udsAddressBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        address_ = value;
+        onChanged();
+      } else {
+        udsAddressBuilder_.setMessage(value);
+      }
+      addressCase_ = 2;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+     */
+    public Builder setUdsAddress(
+        io.grpc.channelz.v1.Address.UdsAddress.Builder builderForValue) {
+      if (udsAddressBuilder_ == null) {
+        address_ = builderForValue.build();
+        onChanged();
+      } else {
+        udsAddressBuilder_.setMessage(builderForValue.build());
+      }
+      addressCase_ = 2;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+     */
+    public Builder mergeUdsAddress(io.grpc.channelz.v1.Address.UdsAddress value) {
+      if (udsAddressBuilder_ == null) {
+        if (addressCase_ == 2 &&
+            address_ != io.grpc.channelz.v1.Address.UdsAddress.getDefaultInstance()) {
+          address_ = io.grpc.channelz.v1.Address.UdsAddress.newBuilder((io.grpc.channelz.v1.Address.UdsAddress) address_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          address_ = value;
+        }
+        onChanged();
+      } else {
+        if (addressCase_ == 2) {
+          udsAddressBuilder_.mergeFrom(value);
+        }
+        udsAddressBuilder_.setMessage(value);
+      }
+      addressCase_ = 2;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+     */
+    public Builder clearUdsAddress() {
+      if (udsAddressBuilder_ == null) {
+        if (addressCase_ == 2) {
+          addressCase_ = 0;
+          address_ = null;
+          onChanged();
+        }
+      } else {
+        if (addressCase_ == 2) {
+          addressCase_ = 0;
+          address_ = null;
+        }
+        udsAddressBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+     */
+    public io.grpc.channelz.v1.Address.UdsAddress.Builder getUdsAddressBuilder() {
+      return getUdsAddressFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+     */
+    public io.grpc.channelz.v1.Address.UdsAddressOrBuilder getUdsAddressOrBuilder() {
+      if ((addressCase_ == 2) && (udsAddressBuilder_ != null)) {
+        return udsAddressBuilder_.getMessageOrBuilder();
+      } else {
+        if (addressCase_ == 2) {
+          return (io.grpc.channelz.v1.Address.UdsAddress) address_;
+        }
+        return io.grpc.channelz.v1.Address.UdsAddress.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Address.UdsAddress, io.grpc.channelz.v1.Address.UdsAddress.Builder, io.grpc.channelz.v1.Address.UdsAddressOrBuilder> 
+        getUdsAddressFieldBuilder() {
+      if (udsAddressBuilder_ == null) {
+        if (!(addressCase_ == 2)) {
+          address_ = io.grpc.channelz.v1.Address.UdsAddress.getDefaultInstance();
+        }
+        udsAddressBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.Address.UdsAddress, io.grpc.channelz.v1.Address.UdsAddress.Builder, io.grpc.channelz.v1.Address.UdsAddressOrBuilder>(
+                (io.grpc.channelz.v1.Address.UdsAddress) address_,
+                getParentForChildren(),
+                isClean());
+        address_ = null;
+      }
+      addressCase_ = 2;
+      onChanged();;
+      return udsAddressBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Address.OtherAddress, io.grpc.channelz.v1.Address.OtherAddress.Builder, io.grpc.channelz.v1.Address.OtherAddressOrBuilder> otherAddressBuilder_;
+    /**
+     * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+     */
+    public boolean hasOtherAddress() {
+      return addressCase_ == 3;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+     */
+    public io.grpc.channelz.v1.Address.OtherAddress getOtherAddress() {
+      if (otherAddressBuilder_ == null) {
+        if (addressCase_ == 3) {
+          return (io.grpc.channelz.v1.Address.OtherAddress) address_;
+        }
+        return io.grpc.channelz.v1.Address.OtherAddress.getDefaultInstance();
+      } else {
+        if (addressCase_ == 3) {
+          return otherAddressBuilder_.getMessage();
+        }
+        return io.grpc.channelz.v1.Address.OtherAddress.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+     */
+    public Builder setOtherAddress(io.grpc.channelz.v1.Address.OtherAddress value) {
+      if (otherAddressBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        address_ = value;
+        onChanged();
+      } else {
+        otherAddressBuilder_.setMessage(value);
+      }
+      addressCase_ = 3;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+     */
+    public Builder setOtherAddress(
+        io.grpc.channelz.v1.Address.OtherAddress.Builder builderForValue) {
+      if (otherAddressBuilder_ == null) {
+        address_ = builderForValue.build();
+        onChanged();
+      } else {
+        otherAddressBuilder_.setMessage(builderForValue.build());
+      }
+      addressCase_ = 3;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+     */
+    public Builder mergeOtherAddress(io.grpc.channelz.v1.Address.OtherAddress value) {
+      if (otherAddressBuilder_ == null) {
+        if (addressCase_ == 3 &&
+            address_ != io.grpc.channelz.v1.Address.OtherAddress.getDefaultInstance()) {
+          address_ = io.grpc.channelz.v1.Address.OtherAddress.newBuilder((io.grpc.channelz.v1.Address.OtherAddress) address_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          address_ = value;
+        }
+        onChanged();
+      } else {
+        if (addressCase_ == 3) {
+          otherAddressBuilder_.mergeFrom(value);
+        }
+        otherAddressBuilder_.setMessage(value);
+      }
+      addressCase_ = 3;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+     */
+    public Builder clearOtherAddress() {
+      if (otherAddressBuilder_ == null) {
+        if (addressCase_ == 3) {
+          addressCase_ = 0;
+          address_ = null;
+          onChanged();
+        }
+      } else {
+        if (addressCase_ == 3) {
+          addressCase_ = 0;
+          address_ = null;
+        }
+        otherAddressBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+     */
+    public io.grpc.channelz.v1.Address.OtherAddress.Builder getOtherAddressBuilder() {
+      return getOtherAddressFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+     */
+    public io.grpc.channelz.v1.Address.OtherAddressOrBuilder getOtherAddressOrBuilder() {
+      if ((addressCase_ == 3) && (otherAddressBuilder_ != null)) {
+        return otherAddressBuilder_.getMessageOrBuilder();
+      } else {
+        if (addressCase_ == 3) {
+          return (io.grpc.channelz.v1.Address.OtherAddress) address_;
+        }
+        return io.grpc.channelz.v1.Address.OtherAddress.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Address.OtherAddress, io.grpc.channelz.v1.Address.OtherAddress.Builder, io.grpc.channelz.v1.Address.OtherAddressOrBuilder> 
+        getOtherAddressFieldBuilder() {
+      if (otherAddressBuilder_ == null) {
+        if (!(addressCase_ == 3)) {
+          address_ = io.grpc.channelz.v1.Address.OtherAddress.getDefaultInstance();
+        }
+        otherAddressBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.Address.OtherAddress, io.grpc.channelz.v1.Address.OtherAddress.Builder, io.grpc.channelz.v1.Address.OtherAddressOrBuilder>(
+                (io.grpc.channelz.v1.Address.OtherAddress) address_,
+                getParentForChildren(),
+                isClean());
+        address_ = null;
+      }
+      addressCase_ = 3;
+      onChanged();;
+      return otherAddressBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.Address)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.Address)
+  private static final io.grpc.channelz.v1.Address DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.Address();
+  }
+
+  public static io.grpc.channelz.v1.Address getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<Address>
+      PARSER = new com.google.protobuf.AbstractParser<Address>() {
+    public Address parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Address(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Address> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Address> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.Address getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/AddressOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/AddressOrBuilder.java
new file mode 100644
index 0000000..2cdb6c7
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/AddressOrBuilder.java
@@ -0,0 +1,50 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface AddressOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.Address)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+   */
+  boolean hasTcpipAddress();
+  /**
+   * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+   */
+  io.grpc.channelz.v1.Address.TcpIpAddress getTcpipAddress();
+  /**
+   * <code>.grpc.channelz.v1.Address.TcpIpAddress tcpip_address = 1;</code>
+   */
+  io.grpc.channelz.v1.Address.TcpIpAddressOrBuilder getTcpipAddressOrBuilder();
+
+  /**
+   * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+   */
+  boolean hasUdsAddress();
+  /**
+   * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+   */
+  io.grpc.channelz.v1.Address.UdsAddress getUdsAddress();
+  /**
+   * <code>.grpc.channelz.v1.Address.UdsAddress uds_address = 2;</code>
+   */
+  io.grpc.channelz.v1.Address.UdsAddressOrBuilder getUdsAddressOrBuilder();
+
+  /**
+   * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+   */
+  boolean hasOtherAddress();
+  /**
+   * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+   */
+  io.grpc.channelz.v1.Address.OtherAddress getOtherAddress();
+  /**
+   * <code>.grpc.channelz.v1.Address.OtherAddress other_address = 3;</code>
+   */
+  io.grpc.channelz.v1.Address.OtherAddressOrBuilder getOtherAddressOrBuilder();
+
+  public io.grpc.channelz.v1.Address.AddressCase getAddressCase();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/Channel.java b/services/src/generated/main/java/io/grpc/channelz/v1/Channel.java
new file mode 100644
index 0000000..5299eb7
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/Channel.java
@@ -0,0 +1,2280 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * Channel is a logical grouping of channels, subchannels, and sockets.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.Channel}
+ */
+public  final class Channel extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.Channel)
+    ChannelOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Channel.newBuilder() to construct.
+  private Channel(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Channel() {
+    channelRef_ = java.util.Collections.emptyList();
+    subchannelRef_ = java.util.Collections.emptyList();
+    socketRef_ = java.util.Collections.emptyList();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Channel(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            io.grpc.channelz.v1.ChannelRef.Builder subBuilder = null;
+            if (ref_ != null) {
+              subBuilder = ref_.toBuilder();
+            }
+            ref_ = input.readMessage(io.grpc.channelz.v1.ChannelRef.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(ref_);
+              ref_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 18: {
+            io.grpc.channelz.v1.ChannelData.Builder subBuilder = null;
+            if (data_ != null) {
+              subBuilder = data_.toBuilder();
+            }
+            data_ = input.readMessage(io.grpc.channelz.v1.ChannelData.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(data_);
+              data_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 26: {
+            if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) {
+              channelRef_ = new java.util.ArrayList<io.grpc.channelz.v1.ChannelRef>();
+              mutable_bitField0_ |= 0x00000004;
+            }
+            channelRef_.add(
+                input.readMessage(io.grpc.channelz.v1.ChannelRef.parser(), extensionRegistry));
+            break;
+          }
+          case 34: {
+            if (!((mutable_bitField0_ & 0x00000008) == 0x00000008)) {
+              subchannelRef_ = new java.util.ArrayList<io.grpc.channelz.v1.SubchannelRef>();
+              mutable_bitField0_ |= 0x00000008;
+            }
+            subchannelRef_.add(
+                input.readMessage(io.grpc.channelz.v1.SubchannelRef.parser(), extensionRegistry));
+            break;
+          }
+          case 42: {
+            if (!((mutable_bitField0_ & 0x00000010) == 0x00000010)) {
+              socketRef_ = new java.util.ArrayList<io.grpc.channelz.v1.SocketRef>();
+              mutable_bitField0_ |= 0x00000010;
+            }
+            socketRef_.add(
+                input.readMessage(io.grpc.channelz.v1.SocketRef.parser(), extensionRegistry));
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) {
+        channelRef_ = java.util.Collections.unmodifiableList(channelRef_);
+      }
+      if (((mutable_bitField0_ & 0x00000008) == 0x00000008)) {
+        subchannelRef_ = java.util.Collections.unmodifiableList(subchannelRef_);
+      }
+      if (((mutable_bitField0_ & 0x00000010) == 0x00000010)) {
+        socketRef_ = java.util.Collections.unmodifiableList(socketRef_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Channel_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Channel_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.Channel.class, io.grpc.channelz.v1.Channel.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int REF_FIELD_NUMBER = 1;
+  private io.grpc.channelz.v1.ChannelRef ref_;
+  /**
+   * <pre>
+   * The identifier for this channel. This should bet set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+   */
+  public boolean hasRef() {
+    return ref_ != null;
+  }
+  /**
+   * <pre>
+   * The identifier for this channel. This should bet set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+   */
+  public io.grpc.channelz.v1.ChannelRef getRef() {
+    return ref_ == null ? io.grpc.channelz.v1.ChannelRef.getDefaultInstance() : ref_;
+  }
+  /**
+   * <pre>
+   * The identifier for this channel. This should bet set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+   */
+  public io.grpc.channelz.v1.ChannelRefOrBuilder getRefOrBuilder() {
+    return getRef();
+  }
+
+  public static final int DATA_FIELD_NUMBER = 2;
+  private io.grpc.channelz.v1.ChannelData data_;
+  /**
+   * <pre>
+   * Data specific to this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+   */
+  public boolean hasData() {
+    return data_ != null;
+  }
+  /**
+   * <pre>
+   * Data specific to this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+   */
+  public io.grpc.channelz.v1.ChannelData getData() {
+    return data_ == null ? io.grpc.channelz.v1.ChannelData.getDefaultInstance() : data_;
+  }
+  /**
+   * <pre>
+   * Data specific to this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+   */
+  public io.grpc.channelz.v1.ChannelDataOrBuilder getDataOrBuilder() {
+    return getData();
+  }
+
+  public static final int CHANNEL_REF_FIELD_NUMBER = 3;
+  private java.util.List<io.grpc.channelz.v1.ChannelRef> channelRef_;
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  public java.util.List<io.grpc.channelz.v1.ChannelRef> getChannelRefList() {
+    return channelRef_;
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  public java.util.List<? extends io.grpc.channelz.v1.ChannelRefOrBuilder> 
+      getChannelRefOrBuilderList() {
+    return channelRef_;
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  public int getChannelRefCount() {
+    return channelRef_.size();
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  public io.grpc.channelz.v1.ChannelRef getChannelRef(int index) {
+    return channelRef_.get(index);
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  public io.grpc.channelz.v1.ChannelRefOrBuilder getChannelRefOrBuilder(
+      int index) {
+    return channelRef_.get(index);
+  }
+
+  public static final int SUBCHANNEL_REF_FIELD_NUMBER = 4;
+  private java.util.List<io.grpc.channelz.v1.SubchannelRef> subchannelRef_;
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  public java.util.List<io.grpc.channelz.v1.SubchannelRef> getSubchannelRefList() {
+    return subchannelRef_;
+  }
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  public java.util.List<? extends io.grpc.channelz.v1.SubchannelRefOrBuilder> 
+      getSubchannelRefOrBuilderList() {
+    return subchannelRef_;
+  }
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  public int getSubchannelRefCount() {
+    return subchannelRef_.size();
+  }
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  public io.grpc.channelz.v1.SubchannelRef getSubchannelRef(int index) {
+    return subchannelRef_.get(index);
+  }
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  public io.grpc.channelz.v1.SubchannelRefOrBuilder getSubchannelRefOrBuilder(
+      int index) {
+    return subchannelRef_.get(index);
+  }
+
+  public static final int SOCKET_REF_FIELD_NUMBER = 5;
+  private java.util.List<io.grpc.channelz.v1.SocketRef> socketRef_;
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  public java.util.List<io.grpc.channelz.v1.SocketRef> getSocketRefList() {
+    return socketRef_;
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  public java.util.List<? extends io.grpc.channelz.v1.SocketRefOrBuilder> 
+      getSocketRefOrBuilderList() {
+    return socketRef_;
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  public int getSocketRefCount() {
+    return socketRef_.size();
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  public io.grpc.channelz.v1.SocketRef getSocketRef(int index) {
+    return socketRef_.get(index);
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  public io.grpc.channelz.v1.SocketRefOrBuilder getSocketRefOrBuilder(
+      int index) {
+    return socketRef_.get(index);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (ref_ != null) {
+      output.writeMessage(1, getRef());
+    }
+    if (data_ != null) {
+      output.writeMessage(2, getData());
+    }
+    for (int i = 0; i < channelRef_.size(); i++) {
+      output.writeMessage(3, channelRef_.get(i));
+    }
+    for (int i = 0; i < subchannelRef_.size(); i++) {
+      output.writeMessage(4, subchannelRef_.get(i));
+    }
+    for (int i = 0; i < socketRef_.size(); i++) {
+      output.writeMessage(5, socketRef_.get(i));
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (ref_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getRef());
+    }
+    if (data_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(2, getData());
+    }
+    for (int i = 0; i < channelRef_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(3, channelRef_.get(i));
+    }
+    for (int i = 0; i < subchannelRef_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(4, subchannelRef_.get(i));
+    }
+    for (int i = 0; i < socketRef_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(5, socketRef_.get(i));
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.Channel)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.Channel other = (io.grpc.channelz.v1.Channel) obj;
+
+    boolean result = true;
+    result = result && (hasRef() == other.hasRef());
+    if (hasRef()) {
+      result = result && getRef()
+          .equals(other.getRef());
+    }
+    result = result && (hasData() == other.hasData());
+    if (hasData()) {
+      result = result && getData()
+          .equals(other.getData());
+    }
+    result = result && getChannelRefList()
+        .equals(other.getChannelRefList());
+    result = result && getSubchannelRefList()
+        .equals(other.getSubchannelRefList());
+    result = result && getSocketRefList()
+        .equals(other.getSocketRefList());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasRef()) {
+      hash = (37 * hash) + REF_FIELD_NUMBER;
+      hash = (53 * hash) + getRef().hashCode();
+    }
+    if (hasData()) {
+      hash = (37 * hash) + DATA_FIELD_NUMBER;
+      hash = (53 * hash) + getData().hashCode();
+    }
+    if (getChannelRefCount() > 0) {
+      hash = (37 * hash) + CHANNEL_REF_FIELD_NUMBER;
+      hash = (53 * hash) + getChannelRefList().hashCode();
+    }
+    if (getSubchannelRefCount() > 0) {
+      hash = (37 * hash) + SUBCHANNEL_REF_FIELD_NUMBER;
+      hash = (53 * hash) + getSubchannelRefList().hashCode();
+    }
+    if (getSocketRefCount() > 0) {
+      hash = (37 * hash) + SOCKET_REF_FIELD_NUMBER;
+      hash = (53 * hash) + getSocketRefList().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.Channel parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Channel parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Channel parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Channel parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Channel parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Channel parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Channel parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Channel parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Channel parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Channel parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Channel parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Channel parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.Channel prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Channel is a logical grouping of channels, subchannels, and sockets.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.Channel}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.Channel)
+      io.grpc.channelz.v1.ChannelOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Channel_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Channel_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.Channel.class, io.grpc.channelz.v1.Channel.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.Channel.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+        getChannelRefFieldBuilder();
+        getSubchannelRefFieldBuilder();
+        getSocketRefFieldBuilder();
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (refBuilder_ == null) {
+        ref_ = null;
+      } else {
+        ref_ = null;
+        refBuilder_ = null;
+      }
+      if (dataBuilder_ == null) {
+        data_ = null;
+      } else {
+        data_ = null;
+        dataBuilder_ = null;
+      }
+      if (channelRefBuilder_ == null) {
+        channelRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000004);
+      } else {
+        channelRefBuilder_.clear();
+      }
+      if (subchannelRefBuilder_ == null) {
+        subchannelRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000008);
+      } else {
+        subchannelRefBuilder_.clear();
+      }
+      if (socketRefBuilder_ == null) {
+        socketRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000010);
+      } else {
+        socketRefBuilder_.clear();
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Channel_descriptor;
+    }
+
+    public io.grpc.channelz.v1.Channel getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.Channel.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.Channel build() {
+      io.grpc.channelz.v1.Channel result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.Channel buildPartial() {
+      io.grpc.channelz.v1.Channel result = new io.grpc.channelz.v1.Channel(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      if (refBuilder_ == null) {
+        result.ref_ = ref_;
+      } else {
+        result.ref_ = refBuilder_.build();
+      }
+      if (dataBuilder_ == null) {
+        result.data_ = data_;
+      } else {
+        result.data_ = dataBuilder_.build();
+      }
+      if (channelRefBuilder_ == null) {
+        if (((bitField0_ & 0x00000004) == 0x00000004)) {
+          channelRef_ = java.util.Collections.unmodifiableList(channelRef_);
+          bitField0_ = (bitField0_ & ~0x00000004);
+        }
+        result.channelRef_ = channelRef_;
+      } else {
+        result.channelRef_ = channelRefBuilder_.build();
+      }
+      if (subchannelRefBuilder_ == null) {
+        if (((bitField0_ & 0x00000008) == 0x00000008)) {
+          subchannelRef_ = java.util.Collections.unmodifiableList(subchannelRef_);
+          bitField0_ = (bitField0_ & ~0x00000008);
+        }
+        result.subchannelRef_ = subchannelRef_;
+      } else {
+        result.subchannelRef_ = subchannelRefBuilder_.build();
+      }
+      if (socketRefBuilder_ == null) {
+        if (((bitField0_ & 0x00000010) == 0x00000010)) {
+          socketRef_ = java.util.Collections.unmodifiableList(socketRef_);
+          bitField0_ = (bitField0_ & ~0x00000010);
+        }
+        result.socketRef_ = socketRef_;
+      } else {
+        result.socketRef_ = socketRefBuilder_.build();
+      }
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.Channel) {
+        return mergeFrom((io.grpc.channelz.v1.Channel)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.Channel other) {
+      if (other == io.grpc.channelz.v1.Channel.getDefaultInstance()) return this;
+      if (other.hasRef()) {
+        mergeRef(other.getRef());
+      }
+      if (other.hasData()) {
+        mergeData(other.getData());
+      }
+      if (channelRefBuilder_ == null) {
+        if (!other.channelRef_.isEmpty()) {
+          if (channelRef_.isEmpty()) {
+            channelRef_ = other.channelRef_;
+            bitField0_ = (bitField0_ & ~0x00000004);
+          } else {
+            ensureChannelRefIsMutable();
+            channelRef_.addAll(other.channelRef_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.channelRef_.isEmpty()) {
+          if (channelRefBuilder_.isEmpty()) {
+            channelRefBuilder_.dispose();
+            channelRefBuilder_ = null;
+            channelRef_ = other.channelRef_;
+            bitField0_ = (bitField0_ & ~0x00000004);
+            channelRefBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getChannelRefFieldBuilder() : null;
+          } else {
+            channelRefBuilder_.addAllMessages(other.channelRef_);
+          }
+        }
+      }
+      if (subchannelRefBuilder_ == null) {
+        if (!other.subchannelRef_.isEmpty()) {
+          if (subchannelRef_.isEmpty()) {
+            subchannelRef_ = other.subchannelRef_;
+            bitField0_ = (bitField0_ & ~0x00000008);
+          } else {
+            ensureSubchannelRefIsMutable();
+            subchannelRef_.addAll(other.subchannelRef_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.subchannelRef_.isEmpty()) {
+          if (subchannelRefBuilder_.isEmpty()) {
+            subchannelRefBuilder_.dispose();
+            subchannelRefBuilder_ = null;
+            subchannelRef_ = other.subchannelRef_;
+            bitField0_ = (bitField0_ & ~0x00000008);
+            subchannelRefBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getSubchannelRefFieldBuilder() : null;
+          } else {
+            subchannelRefBuilder_.addAllMessages(other.subchannelRef_);
+          }
+        }
+      }
+      if (socketRefBuilder_ == null) {
+        if (!other.socketRef_.isEmpty()) {
+          if (socketRef_.isEmpty()) {
+            socketRef_ = other.socketRef_;
+            bitField0_ = (bitField0_ & ~0x00000010);
+          } else {
+            ensureSocketRefIsMutable();
+            socketRef_.addAll(other.socketRef_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.socketRef_.isEmpty()) {
+          if (socketRefBuilder_.isEmpty()) {
+            socketRefBuilder_.dispose();
+            socketRefBuilder_ = null;
+            socketRef_ = other.socketRef_;
+            bitField0_ = (bitField0_ & ~0x00000010);
+            socketRefBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getSocketRefFieldBuilder() : null;
+          } else {
+            socketRefBuilder_.addAllMessages(other.socketRef_);
+          }
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.Channel parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.Channel) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private io.grpc.channelz.v1.ChannelRef ref_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelRef, io.grpc.channelz.v1.ChannelRef.Builder, io.grpc.channelz.v1.ChannelRefOrBuilder> refBuilder_;
+    /**
+     * <pre>
+     * The identifier for this channel. This should bet set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+     */
+    public boolean hasRef() {
+      return refBuilder_ != null || ref_ != null;
+    }
+    /**
+     * <pre>
+     * The identifier for this channel. This should bet set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRef getRef() {
+      if (refBuilder_ == null) {
+        return ref_ == null ? io.grpc.channelz.v1.ChannelRef.getDefaultInstance() : ref_;
+      } else {
+        return refBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The identifier for this channel. This should bet set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+     */
+    public Builder setRef(io.grpc.channelz.v1.ChannelRef value) {
+      if (refBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ref_ = value;
+        onChanged();
+      } else {
+        refBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for this channel. This should bet set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+     */
+    public Builder setRef(
+        io.grpc.channelz.v1.ChannelRef.Builder builderForValue) {
+      if (refBuilder_ == null) {
+        ref_ = builderForValue.build();
+        onChanged();
+      } else {
+        refBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for this channel. This should bet set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+     */
+    public Builder mergeRef(io.grpc.channelz.v1.ChannelRef value) {
+      if (refBuilder_ == null) {
+        if (ref_ != null) {
+          ref_ =
+            io.grpc.channelz.v1.ChannelRef.newBuilder(ref_).mergeFrom(value).buildPartial();
+        } else {
+          ref_ = value;
+        }
+        onChanged();
+      } else {
+        refBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for this channel. This should bet set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+     */
+    public Builder clearRef() {
+      if (refBuilder_ == null) {
+        ref_ = null;
+        onChanged();
+      } else {
+        ref_ = null;
+        refBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for this channel. This should bet set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRef.Builder getRefBuilder() {
+      
+      onChanged();
+      return getRefFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The identifier for this channel. This should bet set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRefOrBuilder getRefOrBuilder() {
+      if (refBuilder_ != null) {
+        return refBuilder_.getMessageOrBuilder();
+      } else {
+        return ref_ == null ?
+            io.grpc.channelz.v1.ChannelRef.getDefaultInstance() : ref_;
+      }
+    }
+    /**
+     * <pre>
+     * The identifier for this channel. This should bet set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelRef, io.grpc.channelz.v1.ChannelRef.Builder, io.grpc.channelz.v1.ChannelRefOrBuilder> 
+        getRefFieldBuilder() {
+      if (refBuilder_ == null) {
+        refBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.ChannelRef, io.grpc.channelz.v1.ChannelRef.Builder, io.grpc.channelz.v1.ChannelRefOrBuilder>(
+                getRef(),
+                getParentForChildren(),
+                isClean());
+        ref_ = null;
+      }
+      return refBuilder_;
+    }
+
+    private io.grpc.channelz.v1.ChannelData data_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelData, io.grpc.channelz.v1.ChannelData.Builder, io.grpc.channelz.v1.ChannelDataOrBuilder> dataBuilder_;
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public boolean hasData() {
+      return dataBuilder_ != null || data_ != null;
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public io.grpc.channelz.v1.ChannelData getData() {
+      if (dataBuilder_ == null) {
+        return data_ == null ? io.grpc.channelz.v1.ChannelData.getDefaultInstance() : data_;
+      } else {
+        return dataBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public Builder setData(io.grpc.channelz.v1.ChannelData value) {
+      if (dataBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        data_ = value;
+        onChanged();
+      } else {
+        dataBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public Builder setData(
+        io.grpc.channelz.v1.ChannelData.Builder builderForValue) {
+      if (dataBuilder_ == null) {
+        data_ = builderForValue.build();
+        onChanged();
+      } else {
+        dataBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public Builder mergeData(io.grpc.channelz.v1.ChannelData value) {
+      if (dataBuilder_ == null) {
+        if (data_ != null) {
+          data_ =
+            io.grpc.channelz.v1.ChannelData.newBuilder(data_).mergeFrom(value).buildPartial();
+        } else {
+          data_ = value;
+        }
+        onChanged();
+      } else {
+        dataBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public Builder clearData() {
+      if (dataBuilder_ == null) {
+        data_ = null;
+        onChanged();
+      } else {
+        data_ = null;
+        dataBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public io.grpc.channelz.v1.ChannelData.Builder getDataBuilder() {
+      
+      onChanged();
+      return getDataFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public io.grpc.channelz.v1.ChannelDataOrBuilder getDataOrBuilder() {
+      if (dataBuilder_ != null) {
+        return dataBuilder_.getMessageOrBuilder();
+      } else {
+        return data_ == null ?
+            io.grpc.channelz.v1.ChannelData.getDefaultInstance() : data_;
+      }
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelData, io.grpc.channelz.v1.ChannelData.Builder, io.grpc.channelz.v1.ChannelDataOrBuilder> 
+        getDataFieldBuilder() {
+      if (dataBuilder_ == null) {
+        dataBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.ChannelData, io.grpc.channelz.v1.ChannelData.Builder, io.grpc.channelz.v1.ChannelDataOrBuilder>(
+                getData(),
+                getParentForChildren(),
+                isClean());
+        data_ = null;
+      }
+      return dataBuilder_;
+    }
+
+    private java.util.List<io.grpc.channelz.v1.ChannelRef> channelRef_ =
+      java.util.Collections.emptyList();
+    private void ensureChannelRefIsMutable() {
+      if (!((bitField0_ & 0x00000004) == 0x00000004)) {
+        channelRef_ = new java.util.ArrayList<io.grpc.channelz.v1.ChannelRef>(channelRef_);
+        bitField0_ |= 0x00000004;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelRef, io.grpc.channelz.v1.ChannelRef.Builder, io.grpc.channelz.v1.ChannelRefOrBuilder> channelRefBuilder_;
+
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.ChannelRef> getChannelRefList() {
+      if (channelRefBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(channelRef_);
+      } else {
+        return channelRefBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public int getChannelRefCount() {
+      if (channelRefBuilder_ == null) {
+        return channelRef_.size();
+      } else {
+        return channelRefBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRef getChannelRef(int index) {
+      if (channelRefBuilder_ == null) {
+        return channelRef_.get(index);
+      } else {
+        return channelRefBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder setChannelRef(
+        int index, io.grpc.channelz.v1.ChannelRef value) {
+      if (channelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureChannelRefIsMutable();
+        channelRef_.set(index, value);
+        onChanged();
+      } else {
+        channelRefBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder setChannelRef(
+        int index, io.grpc.channelz.v1.ChannelRef.Builder builderForValue) {
+      if (channelRefBuilder_ == null) {
+        ensureChannelRefIsMutable();
+        channelRef_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        channelRefBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder addChannelRef(io.grpc.channelz.v1.ChannelRef value) {
+      if (channelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureChannelRefIsMutable();
+        channelRef_.add(value);
+        onChanged();
+      } else {
+        channelRefBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder addChannelRef(
+        int index, io.grpc.channelz.v1.ChannelRef value) {
+      if (channelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureChannelRefIsMutable();
+        channelRef_.add(index, value);
+        onChanged();
+      } else {
+        channelRefBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder addChannelRef(
+        io.grpc.channelz.v1.ChannelRef.Builder builderForValue) {
+      if (channelRefBuilder_ == null) {
+        ensureChannelRefIsMutable();
+        channelRef_.add(builderForValue.build());
+        onChanged();
+      } else {
+        channelRefBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder addChannelRef(
+        int index, io.grpc.channelz.v1.ChannelRef.Builder builderForValue) {
+      if (channelRefBuilder_ == null) {
+        ensureChannelRefIsMutable();
+        channelRef_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        channelRefBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder addAllChannelRef(
+        java.lang.Iterable<? extends io.grpc.channelz.v1.ChannelRef> values) {
+      if (channelRefBuilder_ == null) {
+        ensureChannelRefIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, channelRef_);
+        onChanged();
+      } else {
+        channelRefBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder clearChannelRef() {
+      if (channelRefBuilder_ == null) {
+        channelRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000004);
+        onChanged();
+      } else {
+        channelRefBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder removeChannelRef(int index) {
+      if (channelRefBuilder_ == null) {
+        ensureChannelRefIsMutable();
+        channelRef_.remove(index);
+        onChanged();
+      } else {
+        channelRefBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRef.Builder getChannelRefBuilder(
+        int index) {
+      return getChannelRefFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRefOrBuilder getChannelRefOrBuilder(
+        int index) {
+      if (channelRefBuilder_ == null) {
+        return channelRef_.get(index);  } else {
+        return channelRefBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public java.util.List<? extends io.grpc.channelz.v1.ChannelRefOrBuilder> 
+         getChannelRefOrBuilderList() {
+      if (channelRefBuilder_ != null) {
+        return channelRefBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(channelRef_);
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRef.Builder addChannelRefBuilder() {
+      return getChannelRefFieldBuilder().addBuilder(
+          io.grpc.channelz.v1.ChannelRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRef.Builder addChannelRefBuilder(
+        int index) {
+      return getChannelRefFieldBuilder().addBuilder(
+          index, io.grpc.channelz.v1.ChannelRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.ChannelRef.Builder> 
+         getChannelRefBuilderList() {
+      return getChannelRefFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelRef, io.grpc.channelz.v1.ChannelRef.Builder, io.grpc.channelz.v1.ChannelRefOrBuilder> 
+        getChannelRefFieldBuilder() {
+      if (channelRefBuilder_ == null) {
+        channelRefBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.channelz.v1.ChannelRef, io.grpc.channelz.v1.ChannelRef.Builder, io.grpc.channelz.v1.ChannelRefOrBuilder>(
+                channelRef_,
+                ((bitField0_ & 0x00000004) == 0x00000004),
+                getParentForChildren(),
+                isClean());
+        channelRef_ = null;
+      }
+      return channelRefBuilder_;
+    }
+
+    private java.util.List<io.grpc.channelz.v1.SubchannelRef> subchannelRef_ =
+      java.util.Collections.emptyList();
+    private void ensureSubchannelRefIsMutable() {
+      if (!((bitField0_ & 0x00000008) == 0x00000008)) {
+        subchannelRef_ = new java.util.ArrayList<io.grpc.channelz.v1.SubchannelRef>(subchannelRef_);
+        bitField0_ |= 0x00000008;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SubchannelRef, io.grpc.channelz.v1.SubchannelRef.Builder, io.grpc.channelz.v1.SubchannelRefOrBuilder> subchannelRefBuilder_;
+
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SubchannelRef> getSubchannelRefList() {
+      if (subchannelRefBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(subchannelRef_);
+      } else {
+        return subchannelRefBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public int getSubchannelRefCount() {
+      if (subchannelRefBuilder_ == null) {
+        return subchannelRef_.size();
+      } else {
+        return subchannelRefBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRef getSubchannelRef(int index) {
+      if (subchannelRefBuilder_ == null) {
+        return subchannelRef_.get(index);
+      } else {
+        return subchannelRefBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder setSubchannelRef(
+        int index, io.grpc.channelz.v1.SubchannelRef value) {
+      if (subchannelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.set(index, value);
+        onChanged();
+      } else {
+        subchannelRefBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder setSubchannelRef(
+        int index, io.grpc.channelz.v1.SubchannelRef.Builder builderForValue) {
+      if (subchannelRefBuilder_ == null) {
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        subchannelRefBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder addSubchannelRef(io.grpc.channelz.v1.SubchannelRef value) {
+      if (subchannelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.add(value);
+        onChanged();
+      } else {
+        subchannelRefBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder addSubchannelRef(
+        int index, io.grpc.channelz.v1.SubchannelRef value) {
+      if (subchannelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.add(index, value);
+        onChanged();
+      } else {
+        subchannelRefBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder addSubchannelRef(
+        io.grpc.channelz.v1.SubchannelRef.Builder builderForValue) {
+      if (subchannelRefBuilder_ == null) {
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.add(builderForValue.build());
+        onChanged();
+      } else {
+        subchannelRefBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder addSubchannelRef(
+        int index, io.grpc.channelz.v1.SubchannelRef.Builder builderForValue) {
+      if (subchannelRefBuilder_ == null) {
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        subchannelRefBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder addAllSubchannelRef(
+        java.lang.Iterable<? extends io.grpc.channelz.v1.SubchannelRef> values) {
+      if (subchannelRefBuilder_ == null) {
+        ensureSubchannelRefIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, subchannelRef_);
+        onChanged();
+      } else {
+        subchannelRefBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder clearSubchannelRef() {
+      if (subchannelRefBuilder_ == null) {
+        subchannelRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000008);
+        onChanged();
+      } else {
+        subchannelRefBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder removeSubchannelRef(int index) {
+      if (subchannelRefBuilder_ == null) {
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.remove(index);
+        onChanged();
+      } else {
+        subchannelRefBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRef.Builder getSubchannelRefBuilder(
+        int index) {
+      return getSubchannelRefFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRefOrBuilder getSubchannelRefOrBuilder(
+        int index) {
+      if (subchannelRefBuilder_ == null) {
+        return subchannelRef_.get(index);  } else {
+        return subchannelRefBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public java.util.List<? extends io.grpc.channelz.v1.SubchannelRefOrBuilder> 
+         getSubchannelRefOrBuilderList() {
+      if (subchannelRefBuilder_ != null) {
+        return subchannelRefBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(subchannelRef_);
+      }
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRef.Builder addSubchannelRefBuilder() {
+      return getSubchannelRefFieldBuilder().addBuilder(
+          io.grpc.channelz.v1.SubchannelRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRef.Builder addSubchannelRefBuilder(
+        int index) {
+      return getSubchannelRefFieldBuilder().addBuilder(
+          index, io.grpc.channelz.v1.SubchannelRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SubchannelRef.Builder> 
+         getSubchannelRefBuilderList() {
+      return getSubchannelRefFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SubchannelRef, io.grpc.channelz.v1.SubchannelRef.Builder, io.grpc.channelz.v1.SubchannelRefOrBuilder> 
+        getSubchannelRefFieldBuilder() {
+      if (subchannelRefBuilder_ == null) {
+        subchannelRefBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.channelz.v1.SubchannelRef, io.grpc.channelz.v1.SubchannelRef.Builder, io.grpc.channelz.v1.SubchannelRefOrBuilder>(
+                subchannelRef_,
+                ((bitField0_ & 0x00000008) == 0x00000008),
+                getParentForChildren(),
+                isClean());
+        subchannelRef_ = null;
+      }
+      return subchannelRefBuilder_;
+    }
+
+    private java.util.List<io.grpc.channelz.v1.SocketRef> socketRef_ =
+      java.util.Collections.emptyList();
+    private void ensureSocketRefIsMutable() {
+      if (!((bitField0_ & 0x00000010) == 0x00000010)) {
+        socketRef_ = new java.util.ArrayList<io.grpc.channelz.v1.SocketRef>(socketRef_);
+        bitField0_ |= 0x00000010;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder> socketRefBuilder_;
+
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SocketRef> getSocketRefList() {
+      if (socketRefBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(socketRef_);
+      } else {
+        return socketRefBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public int getSocketRefCount() {
+      if (socketRefBuilder_ == null) {
+        return socketRef_.size();
+      } else {
+        return socketRefBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef getSocketRef(int index) {
+      if (socketRefBuilder_ == null) {
+        return socketRef_.get(index);
+      } else {
+        return socketRefBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder setSocketRef(
+        int index, io.grpc.channelz.v1.SocketRef value) {
+      if (socketRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSocketRefIsMutable();
+        socketRef_.set(index, value);
+        onChanged();
+      } else {
+        socketRefBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder setSocketRef(
+        int index, io.grpc.channelz.v1.SocketRef.Builder builderForValue) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        socketRef_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        socketRefBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder addSocketRef(io.grpc.channelz.v1.SocketRef value) {
+      if (socketRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSocketRefIsMutable();
+        socketRef_.add(value);
+        onChanged();
+      } else {
+        socketRefBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder addSocketRef(
+        int index, io.grpc.channelz.v1.SocketRef value) {
+      if (socketRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSocketRefIsMutable();
+        socketRef_.add(index, value);
+        onChanged();
+      } else {
+        socketRefBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder addSocketRef(
+        io.grpc.channelz.v1.SocketRef.Builder builderForValue) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        socketRef_.add(builderForValue.build());
+        onChanged();
+      } else {
+        socketRefBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder addSocketRef(
+        int index, io.grpc.channelz.v1.SocketRef.Builder builderForValue) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        socketRef_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        socketRefBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder addAllSocketRef(
+        java.lang.Iterable<? extends io.grpc.channelz.v1.SocketRef> values) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, socketRef_);
+        onChanged();
+      } else {
+        socketRefBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder clearSocketRef() {
+      if (socketRefBuilder_ == null) {
+        socketRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000010);
+        onChanged();
+      } else {
+        socketRefBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder removeSocketRef(int index) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        socketRef_.remove(index);
+        onChanged();
+      } else {
+        socketRefBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef.Builder getSocketRefBuilder(
+        int index) {
+      return getSocketRefFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public io.grpc.channelz.v1.SocketRefOrBuilder getSocketRefOrBuilder(
+        int index) {
+      if (socketRefBuilder_ == null) {
+        return socketRef_.get(index);  } else {
+        return socketRefBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public java.util.List<? extends io.grpc.channelz.v1.SocketRefOrBuilder> 
+         getSocketRefOrBuilderList() {
+      if (socketRefBuilder_ != null) {
+        return socketRefBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(socketRef_);
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef.Builder addSocketRefBuilder() {
+      return getSocketRefFieldBuilder().addBuilder(
+          io.grpc.channelz.v1.SocketRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef.Builder addSocketRefBuilder(
+        int index) {
+      return getSocketRefFieldBuilder().addBuilder(
+          index, io.grpc.channelz.v1.SocketRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SocketRef.Builder> 
+         getSocketRefBuilderList() {
+      return getSocketRefFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder> 
+        getSocketRefFieldBuilder() {
+      if (socketRefBuilder_ == null) {
+        socketRefBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder>(
+                socketRef_,
+                ((bitField0_ & 0x00000010) == 0x00000010),
+                getParentForChildren(),
+                isClean());
+        socketRef_ = null;
+      }
+      return socketRefBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.Channel)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.Channel)
+  private static final io.grpc.channelz.v1.Channel DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.Channel();
+  }
+
+  public static io.grpc.channelz.v1.Channel getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<Channel>
+      PARSER = new com.google.protobuf.AbstractParser<Channel>() {
+    public Channel parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Channel(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Channel> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Channel> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.Channel getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ChannelConnectivityState.java b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelConnectivityState.java
new file mode 100644
index 0000000..b786c25
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelConnectivityState.java
@@ -0,0 +1,616 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * These come from the specified states in this document:
+ * https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.ChannelConnectivityState}
+ */
+public  final class ChannelConnectivityState extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.ChannelConnectivityState)
+    ChannelConnectivityStateOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ChannelConnectivityState.newBuilder() to construct.
+  private ChannelConnectivityState(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ChannelConnectivityState() {
+    state_ = 0;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ChannelConnectivityState(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+            int rawValue = input.readEnum();
+
+            state_ = rawValue;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelConnectivityState_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelConnectivityState_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.ChannelConnectivityState.class, io.grpc.channelz.v1.ChannelConnectivityState.Builder.class);
+  }
+
+  /**
+   * Protobuf enum {@code grpc.channelz.v1.ChannelConnectivityState.State}
+   */
+  public enum State
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>UNKNOWN = 0;</code>
+     */
+    UNKNOWN(0),
+    /**
+     * <code>IDLE = 1;</code>
+     */
+    IDLE(1),
+    /**
+     * <code>CONNECTING = 2;</code>
+     */
+    CONNECTING(2),
+    /**
+     * <code>READY = 3;</code>
+     */
+    READY(3),
+    /**
+     * <code>TRANSIENT_FAILURE = 4;</code>
+     */
+    TRANSIENT_FAILURE(4),
+    /**
+     * <code>SHUTDOWN = 5;</code>
+     */
+    SHUTDOWN(5),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <code>UNKNOWN = 0;</code>
+     */
+    public static final int UNKNOWN_VALUE = 0;
+    /**
+     * <code>IDLE = 1;</code>
+     */
+    public static final int IDLE_VALUE = 1;
+    /**
+     * <code>CONNECTING = 2;</code>
+     */
+    public static final int CONNECTING_VALUE = 2;
+    /**
+     * <code>READY = 3;</code>
+     */
+    public static final int READY_VALUE = 3;
+    /**
+     * <code>TRANSIENT_FAILURE = 4;</code>
+     */
+    public static final int TRANSIENT_FAILURE_VALUE = 4;
+    /**
+     * <code>SHUTDOWN = 5;</code>
+     */
+    public static final int SHUTDOWN_VALUE = 5;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static State valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static State forNumber(int value) {
+      switch (value) {
+        case 0: return UNKNOWN;
+        case 1: return IDLE;
+        case 2: return CONNECTING;
+        case 3: return READY;
+        case 4: return TRANSIENT_FAILURE;
+        case 5: return SHUTDOWN;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<State>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        State> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<State>() {
+            public State findValueByNumber(int number) {
+              return State.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelConnectivityState.getDescriptor().getEnumTypes().get(0);
+    }
+
+    private static final State[] VALUES = values();
+
+    public static State valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private State(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.channelz.v1.ChannelConnectivityState.State)
+  }
+
+  public static final int STATE_FIELD_NUMBER = 1;
+  private int state_;
+  /**
+   * <code>.grpc.channelz.v1.ChannelConnectivityState.State state = 1;</code>
+   */
+  public int getStateValue() {
+    return state_;
+  }
+  /**
+   * <code>.grpc.channelz.v1.ChannelConnectivityState.State state = 1;</code>
+   */
+  public io.grpc.channelz.v1.ChannelConnectivityState.State getState() {
+    io.grpc.channelz.v1.ChannelConnectivityState.State result = io.grpc.channelz.v1.ChannelConnectivityState.State.valueOf(state_);
+    return result == null ? io.grpc.channelz.v1.ChannelConnectivityState.State.UNRECOGNIZED : result;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (state_ != io.grpc.channelz.v1.ChannelConnectivityState.State.UNKNOWN.getNumber()) {
+      output.writeEnum(1, state_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (state_ != io.grpc.channelz.v1.ChannelConnectivityState.State.UNKNOWN.getNumber()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeEnumSize(1, state_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.ChannelConnectivityState)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.ChannelConnectivityState other = (io.grpc.channelz.v1.ChannelConnectivityState) obj;
+
+    boolean result = true;
+    result = result && state_ == other.state_;
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + STATE_FIELD_NUMBER;
+    hash = (53 * hash) + state_;
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.ChannelConnectivityState parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelConnectivityState parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelConnectivityState parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelConnectivityState parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelConnectivityState parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelConnectivityState parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelConnectivityState parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelConnectivityState parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelConnectivityState parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelConnectivityState parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelConnectivityState parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelConnectivityState parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.ChannelConnectivityState prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * These come from the specified states in this document:
+   * https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.ChannelConnectivityState}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.ChannelConnectivityState)
+      io.grpc.channelz.v1.ChannelConnectivityStateOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelConnectivityState_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelConnectivityState_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.ChannelConnectivityState.class, io.grpc.channelz.v1.ChannelConnectivityState.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.ChannelConnectivityState.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      state_ = 0;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelConnectivityState_descriptor;
+    }
+
+    public io.grpc.channelz.v1.ChannelConnectivityState getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.ChannelConnectivityState.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.ChannelConnectivityState build() {
+      io.grpc.channelz.v1.ChannelConnectivityState result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.ChannelConnectivityState buildPartial() {
+      io.grpc.channelz.v1.ChannelConnectivityState result = new io.grpc.channelz.v1.ChannelConnectivityState(this);
+      result.state_ = state_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.ChannelConnectivityState) {
+        return mergeFrom((io.grpc.channelz.v1.ChannelConnectivityState)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.ChannelConnectivityState other) {
+      if (other == io.grpc.channelz.v1.ChannelConnectivityState.getDefaultInstance()) return this;
+      if (other.state_ != 0) {
+        setStateValue(other.getStateValue());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.ChannelConnectivityState parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.ChannelConnectivityState) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private int state_ = 0;
+    /**
+     * <code>.grpc.channelz.v1.ChannelConnectivityState.State state = 1;</code>
+     */
+    public int getStateValue() {
+      return state_;
+    }
+    /**
+     * <code>.grpc.channelz.v1.ChannelConnectivityState.State state = 1;</code>
+     */
+    public Builder setStateValue(int value) {
+      state_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.ChannelConnectivityState.State state = 1;</code>
+     */
+    public io.grpc.channelz.v1.ChannelConnectivityState.State getState() {
+      io.grpc.channelz.v1.ChannelConnectivityState.State result = io.grpc.channelz.v1.ChannelConnectivityState.State.valueOf(state_);
+      return result == null ? io.grpc.channelz.v1.ChannelConnectivityState.State.UNRECOGNIZED : result;
+    }
+    /**
+     * <code>.grpc.channelz.v1.ChannelConnectivityState.State state = 1;</code>
+     */
+    public Builder setState(io.grpc.channelz.v1.ChannelConnectivityState.State value) {
+      if (value == null) {
+        throw new NullPointerException();
+      }
+      
+      state_ = value.getNumber();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.ChannelConnectivityState.State state = 1;</code>
+     */
+    public Builder clearState() {
+      
+      state_ = 0;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.ChannelConnectivityState)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.ChannelConnectivityState)
+  private static final io.grpc.channelz.v1.ChannelConnectivityState DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.ChannelConnectivityState();
+  }
+
+  public static io.grpc.channelz.v1.ChannelConnectivityState getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ChannelConnectivityState>
+      PARSER = new com.google.protobuf.AbstractParser<ChannelConnectivityState>() {
+    public ChannelConnectivityState parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ChannelConnectivityState(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ChannelConnectivityState> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ChannelConnectivityState> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.ChannelConnectivityState getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ChannelConnectivityStateOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelConnectivityStateOrBuilder.java
new file mode 100644
index 0000000..b2679bf
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelConnectivityStateOrBuilder.java
@@ -0,0 +1,18 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface ChannelConnectivityStateOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.ChannelConnectivityState)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>.grpc.channelz.v1.ChannelConnectivityState.State state = 1;</code>
+   */
+  int getStateValue();
+  /**
+   * <code>.grpc.channelz.v1.ChannelConnectivityState.State state = 1;</code>
+   */
+  io.grpc.channelz.v1.ChannelConnectivityState.State getState();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ChannelData.java b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelData.java
new file mode 100644
index 0000000..0733056
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelData.java
@@ -0,0 +1,1476 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * Channel data is data related to a specific Channel or Subchannel.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.ChannelData}
+ */
+public  final class ChannelData extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.ChannelData)
+    ChannelDataOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ChannelData.newBuilder() to construct.
+  private ChannelData(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ChannelData() {
+    target_ = "";
+    callsStarted_ = 0L;
+    callsSucceeded_ = 0L;
+    callsFailed_ = 0L;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ChannelData(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            io.grpc.channelz.v1.ChannelConnectivityState.Builder subBuilder = null;
+            if (state_ != null) {
+              subBuilder = state_.toBuilder();
+            }
+            state_ = input.readMessage(io.grpc.channelz.v1.ChannelConnectivityState.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(state_);
+              state_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 18: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            target_ = s;
+            break;
+          }
+          case 26: {
+            io.grpc.channelz.v1.ChannelTrace.Builder subBuilder = null;
+            if (trace_ != null) {
+              subBuilder = trace_.toBuilder();
+            }
+            trace_ = input.readMessage(io.grpc.channelz.v1.ChannelTrace.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(trace_);
+              trace_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 32: {
+
+            callsStarted_ = input.readInt64();
+            break;
+          }
+          case 40: {
+
+            callsSucceeded_ = input.readInt64();
+            break;
+          }
+          case 48: {
+
+            callsFailed_ = input.readInt64();
+            break;
+          }
+          case 58: {
+            com.google.protobuf.Timestamp.Builder subBuilder = null;
+            if (lastCallStartedTimestamp_ != null) {
+              subBuilder = lastCallStartedTimestamp_.toBuilder();
+            }
+            lastCallStartedTimestamp_ = input.readMessage(com.google.protobuf.Timestamp.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(lastCallStartedTimestamp_);
+              lastCallStartedTimestamp_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelData_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelData_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.ChannelData.class, io.grpc.channelz.v1.ChannelData.Builder.class);
+  }
+
+  public static final int STATE_FIELD_NUMBER = 1;
+  private io.grpc.channelz.v1.ChannelConnectivityState state_;
+  /**
+   * <pre>
+   * The connectivity state of the channel or subchannel.  Implementations
+   * should always set this.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+   */
+  public boolean hasState() {
+    return state_ != null;
+  }
+  /**
+   * <pre>
+   * The connectivity state of the channel or subchannel.  Implementations
+   * should always set this.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+   */
+  public io.grpc.channelz.v1.ChannelConnectivityState getState() {
+    return state_ == null ? io.grpc.channelz.v1.ChannelConnectivityState.getDefaultInstance() : state_;
+  }
+  /**
+   * <pre>
+   * The connectivity state of the channel or subchannel.  Implementations
+   * should always set this.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+   */
+  public io.grpc.channelz.v1.ChannelConnectivityStateOrBuilder getStateOrBuilder() {
+    return getState();
+  }
+
+  public static final int TARGET_FIELD_NUMBER = 2;
+  private volatile java.lang.Object target_;
+  /**
+   * <pre>
+   * The target this channel originally tried to connect to.  May be absent
+   * </pre>
+   *
+   * <code>string target = 2;</code>
+   */
+  public java.lang.String getTarget() {
+    java.lang.Object ref = target_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      target_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * The target this channel originally tried to connect to.  May be absent
+   * </pre>
+   *
+   * <code>string target = 2;</code>
+   */
+  public com.google.protobuf.ByteString
+      getTargetBytes() {
+    java.lang.Object ref = target_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      target_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int TRACE_FIELD_NUMBER = 3;
+  private io.grpc.channelz.v1.ChannelTrace trace_;
+  /**
+   * <pre>
+   * A trace of recent events on the channel.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+   */
+  public boolean hasTrace() {
+    return trace_ != null;
+  }
+  /**
+   * <pre>
+   * A trace of recent events on the channel.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+   */
+  public io.grpc.channelz.v1.ChannelTrace getTrace() {
+    return trace_ == null ? io.grpc.channelz.v1.ChannelTrace.getDefaultInstance() : trace_;
+  }
+  /**
+   * <pre>
+   * A trace of recent events on the channel.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+   */
+  public io.grpc.channelz.v1.ChannelTraceOrBuilder getTraceOrBuilder() {
+    return getTrace();
+  }
+
+  public static final int CALLS_STARTED_FIELD_NUMBER = 4;
+  private long callsStarted_;
+  /**
+   * <pre>
+   * The number of calls started on the channel
+   * </pre>
+   *
+   * <code>int64 calls_started = 4;</code>
+   */
+  public long getCallsStarted() {
+    return callsStarted_;
+  }
+
+  public static final int CALLS_SUCCEEDED_FIELD_NUMBER = 5;
+  private long callsSucceeded_;
+  /**
+   * <pre>
+   * The number of calls that have completed with an OK status
+   * </pre>
+   *
+   * <code>int64 calls_succeeded = 5;</code>
+   */
+  public long getCallsSucceeded() {
+    return callsSucceeded_;
+  }
+
+  public static final int CALLS_FAILED_FIELD_NUMBER = 6;
+  private long callsFailed_;
+  /**
+   * <pre>
+   * The number of calls that have completed with a non-OK status
+   * </pre>
+   *
+   * <code>int64 calls_failed = 6;</code>
+   */
+  public long getCallsFailed() {
+    return callsFailed_;
+  }
+
+  public static final int LAST_CALL_STARTED_TIMESTAMP_FIELD_NUMBER = 7;
+  private com.google.protobuf.Timestamp lastCallStartedTimestamp_;
+  /**
+   * <pre>
+   * The last time a call was started on the channel.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+   */
+  public boolean hasLastCallStartedTimestamp() {
+    return lastCallStartedTimestamp_ != null;
+  }
+  /**
+   * <pre>
+   * The last time a call was started on the channel.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+   */
+  public com.google.protobuf.Timestamp getLastCallStartedTimestamp() {
+    return lastCallStartedTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : lastCallStartedTimestamp_;
+  }
+  /**
+   * <pre>
+   * The last time a call was started on the channel.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+   */
+  public com.google.protobuf.TimestampOrBuilder getLastCallStartedTimestampOrBuilder() {
+    return getLastCallStartedTimestamp();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (state_ != null) {
+      output.writeMessage(1, getState());
+    }
+    if (!getTargetBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 2, target_);
+    }
+    if (trace_ != null) {
+      output.writeMessage(3, getTrace());
+    }
+    if (callsStarted_ != 0L) {
+      output.writeInt64(4, callsStarted_);
+    }
+    if (callsSucceeded_ != 0L) {
+      output.writeInt64(5, callsSucceeded_);
+    }
+    if (callsFailed_ != 0L) {
+      output.writeInt64(6, callsFailed_);
+    }
+    if (lastCallStartedTimestamp_ != null) {
+      output.writeMessage(7, getLastCallStartedTimestamp());
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (state_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getState());
+    }
+    if (!getTargetBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, target_);
+    }
+    if (trace_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(3, getTrace());
+    }
+    if (callsStarted_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(4, callsStarted_);
+    }
+    if (callsSucceeded_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(5, callsSucceeded_);
+    }
+    if (callsFailed_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(6, callsFailed_);
+    }
+    if (lastCallStartedTimestamp_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(7, getLastCallStartedTimestamp());
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.ChannelData)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.ChannelData other = (io.grpc.channelz.v1.ChannelData) obj;
+
+    boolean result = true;
+    result = result && (hasState() == other.hasState());
+    if (hasState()) {
+      result = result && getState()
+          .equals(other.getState());
+    }
+    result = result && getTarget()
+        .equals(other.getTarget());
+    result = result && (hasTrace() == other.hasTrace());
+    if (hasTrace()) {
+      result = result && getTrace()
+          .equals(other.getTrace());
+    }
+    result = result && (getCallsStarted()
+        == other.getCallsStarted());
+    result = result && (getCallsSucceeded()
+        == other.getCallsSucceeded());
+    result = result && (getCallsFailed()
+        == other.getCallsFailed());
+    result = result && (hasLastCallStartedTimestamp() == other.hasLastCallStartedTimestamp());
+    if (hasLastCallStartedTimestamp()) {
+      result = result && getLastCallStartedTimestamp()
+          .equals(other.getLastCallStartedTimestamp());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasState()) {
+      hash = (37 * hash) + STATE_FIELD_NUMBER;
+      hash = (53 * hash) + getState().hashCode();
+    }
+    hash = (37 * hash) + TARGET_FIELD_NUMBER;
+    hash = (53 * hash) + getTarget().hashCode();
+    if (hasTrace()) {
+      hash = (37 * hash) + TRACE_FIELD_NUMBER;
+      hash = (53 * hash) + getTrace().hashCode();
+    }
+    hash = (37 * hash) + CALLS_STARTED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getCallsStarted());
+    hash = (37 * hash) + CALLS_SUCCEEDED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getCallsSucceeded());
+    hash = (37 * hash) + CALLS_FAILED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getCallsFailed());
+    if (hasLastCallStartedTimestamp()) {
+      hash = (37 * hash) + LAST_CALL_STARTED_TIMESTAMP_FIELD_NUMBER;
+      hash = (53 * hash) + getLastCallStartedTimestamp().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.ChannelData parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelData parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelData parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelData parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelData parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelData parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelData parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelData parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelData parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelData parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelData parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelData parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.ChannelData prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Channel data is data related to a specific Channel or Subchannel.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.ChannelData}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.ChannelData)
+      io.grpc.channelz.v1.ChannelDataOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelData_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelData_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.ChannelData.class, io.grpc.channelz.v1.ChannelData.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.ChannelData.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (stateBuilder_ == null) {
+        state_ = null;
+      } else {
+        state_ = null;
+        stateBuilder_ = null;
+      }
+      target_ = "";
+
+      if (traceBuilder_ == null) {
+        trace_ = null;
+      } else {
+        trace_ = null;
+        traceBuilder_ = null;
+      }
+      callsStarted_ = 0L;
+
+      callsSucceeded_ = 0L;
+
+      callsFailed_ = 0L;
+
+      if (lastCallStartedTimestampBuilder_ == null) {
+        lastCallStartedTimestamp_ = null;
+      } else {
+        lastCallStartedTimestamp_ = null;
+        lastCallStartedTimestampBuilder_ = null;
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelData_descriptor;
+    }
+
+    public io.grpc.channelz.v1.ChannelData getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.ChannelData.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.ChannelData build() {
+      io.grpc.channelz.v1.ChannelData result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.ChannelData buildPartial() {
+      io.grpc.channelz.v1.ChannelData result = new io.grpc.channelz.v1.ChannelData(this);
+      if (stateBuilder_ == null) {
+        result.state_ = state_;
+      } else {
+        result.state_ = stateBuilder_.build();
+      }
+      result.target_ = target_;
+      if (traceBuilder_ == null) {
+        result.trace_ = trace_;
+      } else {
+        result.trace_ = traceBuilder_.build();
+      }
+      result.callsStarted_ = callsStarted_;
+      result.callsSucceeded_ = callsSucceeded_;
+      result.callsFailed_ = callsFailed_;
+      if (lastCallStartedTimestampBuilder_ == null) {
+        result.lastCallStartedTimestamp_ = lastCallStartedTimestamp_;
+      } else {
+        result.lastCallStartedTimestamp_ = lastCallStartedTimestampBuilder_.build();
+      }
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.ChannelData) {
+        return mergeFrom((io.grpc.channelz.v1.ChannelData)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.ChannelData other) {
+      if (other == io.grpc.channelz.v1.ChannelData.getDefaultInstance()) return this;
+      if (other.hasState()) {
+        mergeState(other.getState());
+      }
+      if (!other.getTarget().isEmpty()) {
+        target_ = other.target_;
+        onChanged();
+      }
+      if (other.hasTrace()) {
+        mergeTrace(other.getTrace());
+      }
+      if (other.getCallsStarted() != 0L) {
+        setCallsStarted(other.getCallsStarted());
+      }
+      if (other.getCallsSucceeded() != 0L) {
+        setCallsSucceeded(other.getCallsSucceeded());
+      }
+      if (other.getCallsFailed() != 0L) {
+        setCallsFailed(other.getCallsFailed());
+      }
+      if (other.hasLastCallStartedTimestamp()) {
+        mergeLastCallStartedTimestamp(other.getLastCallStartedTimestamp());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.ChannelData parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.ChannelData) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private io.grpc.channelz.v1.ChannelConnectivityState state_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelConnectivityState, io.grpc.channelz.v1.ChannelConnectivityState.Builder, io.grpc.channelz.v1.ChannelConnectivityStateOrBuilder> stateBuilder_;
+    /**
+     * <pre>
+     * The connectivity state of the channel or subchannel.  Implementations
+     * should always set this.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+     */
+    public boolean hasState() {
+      return stateBuilder_ != null || state_ != null;
+    }
+    /**
+     * <pre>
+     * The connectivity state of the channel or subchannel.  Implementations
+     * should always set this.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+     */
+    public io.grpc.channelz.v1.ChannelConnectivityState getState() {
+      if (stateBuilder_ == null) {
+        return state_ == null ? io.grpc.channelz.v1.ChannelConnectivityState.getDefaultInstance() : state_;
+      } else {
+        return stateBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The connectivity state of the channel or subchannel.  Implementations
+     * should always set this.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+     */
+    public Builder setState(io.grpc.channelz.v1.ChannelConnectivityState value) {
+      if (stateBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        state_ = value;
+        onChanged();
+      } else {
+        stateBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The connectivity state of the channel or subchannel.  Implementations
+     * should always set this.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+     */
+    public Builder setState(
+        io.grpc.channelz.v1.ChannelConnectivityState.Builder builderForValue) {
+      if (stateBuilder_ == null) {
+        state_ = builderForValue.build();
+        onChanged();
+      } else {
+        stateBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The connectivity state of the channel or subchannel.  Implementations
+     * should always set this.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+     */
+    public Builder mergeState(io.grpc.channelz.v1.ChannelConnectivityState value) {
+      if (stateBuilder_ == null) {
+        if (state_ != null) {
+          state_ =
+            io.grpc.channelz.v1.ChannelConnectivityState.newBuilder(state_).mergeFrom(value).buildPartial();
+        } else {
+          state_ = value;
+        }
+        onChanged();
+      } else {
+        stateBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The connectivity state of the channel or subchannel.  Implementations
+     * should always set this.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+     */
+    public Builder clearState() {
+      if (stateBuilder_ == null) {
+        state_ = null;
+        onChanged();
+      } else {
+        state_ = null;
+        stateBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The connectivity state of the channel or subchannel.  Implementations
+     * should always set this.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+     */
+    public io.grpc.channelz.v1.ChannelConnectivityState.Builder getStateBuilder() {
+      
+      onChanged();
+      return getStateFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The connectivity state of the channel or subchannel.  Implementations
+     * should always set this.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+     */
+    public io.grpc.channelz.v1.ChannelConnectivityStateOrBuilder getStateOrBuilder() {
+      if (stateBuilder_ != null) {
+        return stateBuilder_.getMessageOrBuilder();
+      } else {
+        return state_ == null ?
+            io.grpc.channelz.v1.ChannelConnectivityState.getDefaultInstance() : state_;
+      }
+    }
+    /**
+     * <pre>
+     * The connectivity state of the channel or subchannel.  Implementations
+     * should always set this.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelConnectivityState, io.grpc.channelz.v1.ChannelConnectivityState.Builder, io.grpc.channelz.v1.ChannelConnectivityStateOrBuilder> 
+        getStateFieldBuilder() {
+      if (stateBuilder_ == null) {
+        stateBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.ChannelConnectivityState, io.grpc.channelz.v1.ChannelConnectivityState.Builder, io.grpc.channelz.v1.ChannelConnectivityStateOrBuilder>(
+                getState(),
+                getParentForChildren(),
+                isClean());
+        state_ = null;
+      }
+      return stateBuilder_;
+    }
+
+    private java.lang.Object target_ = "";
+    /**
+     * <pre>
+     * The target this channel originally tried to connect to.  May be absent
+     * </pre>
+     *
+     * <code>string target = 2;</code>
+     */
+    public java.lang.String getTarget() {
+      java.lang.Object ref = target_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        target_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * The target this channel originally tried to connect to.  May be absent
+     * </pre>
+     *
+     * <code>string target = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getTargetBytes() {
+      java.lang.Object ref = target_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        target_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * The target this channel originally tried to connect to.  May be absent
+     * </pre>
+     *
+     * <code>string target = 2;</code>
+     */
+    public Builder setTarget(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      target_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The target this channel originally tried to connect to.  May be absent
+     * </pre>
+     *
+     * <code>string target = 2;</code>
+     */
+    public Builder clearTarget() {
+      
+      target_ = getDefaultInstance().getTarget();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The target this channel originally tried to connect to.  May be absent
+     * </pre>
+     *
+     * <code>string target = 2;</code>
+     */
+    public Builder setTargetBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      target_ = value;
+      onChanged();
+      return this;
+    }
+
+    private io.grpc.channelz.v1.ChannelTrace trace_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelTrace, io.grpc.channelz.v1.ChannelTrace.Builder, io.grpc.channelz.v1.ChannelTraceOrBuilder> traceBuilder_;
+    /**
+     * <pre>
+     * A trace of recent events on the channel.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+     */
+    public boolean hasTrace() {
+      return traceBuilder_ != null || trace_ != null;
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the channel.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelTrace getTrace() {
+      if (traceBuilder_ == null) {
+        return trace_ == null ? io.grpc.channelz.v1.ChannelTrace.getDefaultInstance() : trace_;
+      } else {
+        return traceBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the channel.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+     */
+    public Builder setTrace(io.grpc.channelz.v1.ChannelTrace value) {
+      if (traceBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        trace_ = value;
+        onChanged();
+      } else {
+        traceBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the channel.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+     */
+    public Builder setTrace(
+        io.grpc.channelz.v1.ChannelTrace.Builder builderForValue) {
+      if (traceBuilder_ == null) {
+        trace_ = builderForValue.build();
+        onChanged();
+      } else {
+        traceBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the channel.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+     */
+    public Builder mergeTrace(io.grpc.channelz.v1.ChannelTrace value) {
+      if (traceBuilder_ == null) {
+        if (trace_ != null) {
+          trace_ =
+            io.grpc.channelz.v1.ChannelTrace.newBuilder(trace_).mergeFrom(value).buildPartial();
+        } else {
+          trace_ = value;
+        }
+        onChanged();
+      } else {
+        traceBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the channel.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+     */
+    public Builder clearTrace() {
+      if (traceBuilder_ == null) {
+        trace_ = null;
+        onChanged();
+      } else {
+        trace_ = null;
+        traceBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the channel.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelTrace.Builder getTraceBuilder() {
+      
+      onChanged();
+      return getTraceFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the channel.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelTraceOrBuilder getTraceOrBuilder() {
+      if (traceBuilder_ != null) {
+        return traceBuilder_.getMessageOrBuilder();
+      } else {
+        return trace_ == null ?
+            io.grpc.channelz.v1.ChannelTrace.getDefaultInstance() : trace_;
+      }
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the channel.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelTrace, io.grpc.channelz.v1.ChannelTrace.Builder, io.grpc.channelz.v1.ChannelTraceOrBuilder> 
+        getTraceFieldBuilder() {
+      if (traceBuilder_ == null) {
+        traceBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.ChannelTrace, io.grpc.channelz.v1.ChannelTrace.Builder, io.grpc.channelz.v1.ChannelTraceOrBuilder>(
+                getTrace(),
+                getParentForChildren(),
+                isClean());
+        trace_ = null;
+      }
+      return traceBuilder_;
+    }
+
+    private long callsStarted_ ;
+    /**
+     * <pre>
+     * The number of calls started on the channel
+     * </pre>
+     *
+     * <code>int64 calls_started = 4;</code>
+     */
+    public long getCallsStarted() {
+      return callsStarted_;
+    }
+    /**
+     * <pre>
+     * The number of calls started on the channel
+     * </pre>
+     *
+     * <code>int64 calls_started = 4;</code>
+     */
+    public Builder setCallsStarted(long value) {
+      
+      callsStarted_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The number of calls started on the channel
+     * </pre>
+     *
+     * <code>int64 calls_started = 4;</code>
+     */
+    public Builder clearCallsStarted() {
+      
+      callsStarted_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long callsSucceeded_ ;
+    /**
+     * <pre>
+     * The number of calls that have completed with an OK status
+     * </pre>
+     *
+     * <code>int64 calls_succeeded = 5;</code>
+     */
+    public long getCallsSucceeded() {
+      return callsSucceeded_;
+    }
+    /**
+     * <pre>
+     * The number of calls that have completed with an OK status
+     * </pre>
+     *
+     * <code>int64 calls_succeeded = 5;</code>
+     */
+    public Builder setCallsSucceeded(long value) {
+      
+      callsSucceeded_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The number of calls that have completed with an OK status
+     * </pre>
+     *
+     * <code>int64 calls_succeeded = 5;</code>
+     */
+    public Builder clearCallsSucceeded() {
+      
+      callsSucceeded_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long callsFailed_ ;
+    /**
+     * <pre>
+     * The number of calls that have completed with a non-OK status
+     * </pre>
+     *
+     * <code>int64 calls_failed = 6;</code>
+     */
+    public long getCallsFailed() {
+      return callsFailed_;
+    }
+    /**
+     * <pre>
+     * The number of calls that have completed with a non-OK status
+     * </pre>
+     *
+     * <code>int64 calls_failed = 6;</code>
+     */
+    public Builder setCallsFailed(long value) {
+      
+      callsFailed_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The number of calls that have completed with a non-OK status
+     * </pre>
+     *
+     * <code>int64 calls_failed = 6;</code>
+     */
+    public Builder clearCallsFailed() {
+      
+      callsFailed_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.Timestamp lastCallStartedTimestamp_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> lastCallStartedTimestampBuilder_;
+    /**
+     * <pre>
+     * The last time a call was started on the channel.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+     */
+    public boolean hasLastCallStartedTimestamp() {
+      return lastCallStartedTimestampBuilder_ != null || lastCallStartedTimestamp_ != null;
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the channel.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+     */
+    public com.google.protobuf.Timestamp getLastCallStartedTimestamp() {
+      if (lastCallStartedTimestampBuilder_ == null) {
+        return lastCallStartedTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : lastCallStartedTimestamp_;
+      } else {
+        return lastCallStartedTimestampBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the channel.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+     */
+    public Builder setLastCallStartedTimestamp(com.google.protobuf.Timestamp value) {
+      if (lastCallStartedTimestampBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        lastCallStartedTimestamp_ = value;
+        onChanged();
+      } else {
+        lastCallStartedTimestampBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the channel.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+     */
+    public Builder setLastCallStartedTimestamp(
+        com.google.protobuf.Timestamp.Builder builderForValue) {
+      if (lastCallStartedTimestampBuilder_ == null) {
+        lastCallStartedTimestamp_ = builderForValue.build();
+        onChanged();
+      } else {
+        lastCallStartedTimestampBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the channel.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+     */
+    public Builder mergeLastCallStartedTimestamp(com.google.protobuf.Timestamp value) {
+      if (lastCallStartedTimestampBuilder_ == null) {
+        if (lastCallStartedTimestamp_ != null) {
+          lastCallStartedTimestamp_ =
+            com.google.protobuf.Timestamp.newBuilder(lastCallStartedTimestamp_).mergeFrom(value).buildPartial();
+        } else {
+          lastCallStartedTimestamp_ = value;
+        }
+        onChanged();
+      } else {
+        lastCallStartedTimestampBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the channel.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+     */
+    public Builder clearLastCallStartedTimestamp() {
+      if (lastCallStartedTimestampBuilder_ == null) {
+        lastCallStartedTimestamp_ = null;
+        onChanged();
+      } else {
+        lastCallStartedTimestamp_ = null;
+        lastCallStartedTimestampBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the channel.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+     */
+    public com.google.protobuf.Timestamp.Builder getLastCallStartedTimestampBuilder() {
+      
+      onChanged();
+      return getLastCallStartedTimestampFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the channel.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+     */
+    public com.google.protobuf.TimestampOrBuilder getLastCallStartedTimestampOrBuilder() {
+      if (lastCallStartedTimestampBuilder_ != null) {
+        return lastCallStartedTimestampBuilder_.getMessageOrBuilder();
+      } else {
+        return lastCallStartedTimestamp_ == null ?
+            com.google.protobuf.Timestamp.getDefaultInstance() : lastCallStartedTimestamp_;
+      }
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the channel.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> 
+        getLastCallStartedTimestampFieldBuilder() {
+      if (lastCallStartedTimestampBuilder_ == null) {
+        lastCallStartedTimestampBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder>(
+                getLastCallStartedTimestamp(),
+                getParentForChildren(),
+                isClean());
+        lastCallStartedTimestamp_ = null;
+      }
+      return lastCallStartedTimestampBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.ChannelData)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.ChannelData)
+  private static final io.grpc.channelz.v1.ChannelData DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.ChannelData();
+  }
+
+  public static io.grpc.channelz.v1.ChannelData getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ChannelData>
+      PARSER = new com.google.protobuf.AbstractParser<ChannelData>() {
+    public ChannelData parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ChannelData(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ChannelData> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ChannelData> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.ChannelData getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ChannelDataOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelDataOrBuilder.java
new file mode 100644
index 0000000..42d9efd
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelDataOrBuilder.java
@@ -0,0 +1,132 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface ChannelDataOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.ChannelData)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The connectivity state of the channel or subchannel.  Implementations
+   * should always set this.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+   */
+  boolean hasState();
+  /**
+   * <pre>
+   * The connectivity state of the channel or subchannel.  Implementations
+   * should always set this.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+   */
+  io.grpc.channelz.v1.ChannelConnectivityState getState();
+  /**
+   * <pre>
+   * The connectivity state of the channel or subchannel.  Implementations
+   * should always set this.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelConnectivityState state = 1;</code>
+   */
+  io.grpc.channelz.v1.ChannelConnectivityStateOrBuilder getStateOrBuilder();
+
+  /**
+   * <pre>
+   * The target this channel originally tried to connect to.  May be absent
+   * </pre>
+   *
+   * <code>string target = 2;</code>
+   */
+  java.lang.String getTarget();
+  /**
+   * <pre>
+   * The target this channel originally tried to connect to.  May be absent
+   * </pre>
+   *
+   * <code>string target = 2;</code>
+   */
+  com.google.protobuf.ByteString
+      getTargetBytes();
+
+  /**
+   * <pre>
+   * A trace of recent events on the channel.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+   */
+  boolean hasTrace();
+  /**
+   * <pre>
+   * A trace of recent events on the channel.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+   */
+  io.grpc.channelz.v1.ChannelTrace getTrace();
+  /**
+   * <pre>
+   * A trace of recent events on the channel.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTrace trace = 3;</code>
+   */
+  io.grpc.channelz.v1.ChannelTraceOrBuilder getTraceOrBuilder();
+
+  /**
+   * <pre>
+   * The number of calls started on the channel
+   * </pre>
+   *
+   * <code>int64 calls_started = 4;</code>
+   */
+  long getCallsStarted();
+
+  /**
+   * <pre>
+   * The number of calls that have completed with an OK status
+   * </pre>
+   *
+   * <code>int64 calls_succeeded = 5;</code>
+   */
+  long getCallsSucceeded();
+
+  /**
+   * <pre>
+   * The number of calls that have completed with a non-OK status
+   * </pre>
+   *
+   * <code>int64 calls_failed = 6;</code>
+   */
+  long getCallsFailed();
+
+  /**
+   * <pre>
+   * The last time a call was started on the channel.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+   */
+  boolean hasLastCallStartedTimestamp();
+  /**
+   * <pre>
+   * The last time a call was started on the channel.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+   */
+  com.google.protobuf.Timestamp getLastCallStartedTimestamp();
+  /**
+   * <pre>
+   * The last time a call was started on the channel.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_call_started_timestamp = 7;</code>
+   */
+  com.google.protobuf.TimestampOrBuilder getLastCallStartedTimestampOrBuilder();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ChannelOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelOrBuilder.java
new file mode 100644
index 0000000..06d4da9
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelOrBuilder.java
@@ -0,0 +1,216 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface ChannelOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.Channel)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The identifier for this channel. This should bet set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+   */
+  boolean hasRef();
+  /**
+   * <pre>
+   * The identifier for this channel. This should bet set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+   */
+  io.grpc.channelz.v1.ChannelRef getRef();
+  /**
+   * <pre>
+   * The identifier for this channel. This should bet set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelRef ref = 1;</code>
+   */
+  io.grpc.channelz.v1.ChannelRefOrBuilder getRefOrBuilder();
+
+  /**
+   * <pre>
+   * Data specific to this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+   */
+  boolean hasData();
+  /**
+   * <pre>
+   * Data specific to this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+   */
+  io.grpc.channelz.v1.ChannelData getData();
+  /**
+   * <pre>
+   * Data specific to this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+   */
+  io.grpc.channelz.v1.ChannelDataOrBuilder getDataOrBuilder();
+
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  java.util.List<io.grpc.channelz.v1.ChannelRef> 
+      getChannelRefList();
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  io.grpc.channelz.v1.ChannelRef getChannelRef(int index);
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  int getChannelRefCount();
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  java.util.List<? extends io.grpc.channelz.v1.ChannelRefOrBuilder> 
+      getChannelRefOrBuilderList();
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  io.grpc.channelz.v1.ChannelRefOrBuilder getChannelRefOrBuilder(
+      int index);
+
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  java.util.List<io.grpc.channelz.v1.SubchannelRef> 
+      getSubchannelRefList();
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  io.grpc.channelz.v1.SubchannelRef getSubchannelRef(int index);
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  int getSubchannelRefCount();
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  java.util.List<? extends io.grpc.channelz.v1.SubchannelRefOrBuilder> 
+      getSubchannelRefOrBuilderList();
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  io.grpc.channelz.v1.SubchannelRefOrBuilder getSubchannelRefOrBuilder(
+      int index);
+
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  java.util.List<io.grpc.channelz.v1.SocketRef> 
+      getSocketRefList();
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  io.grpc.channelz.v1.SocketRef getSocketRef(int index);
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  int getSocketRefCount();
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  java.util.List<? extends io.grpc.channelz.v1.SocketRefOrBuilder> 
+      getSocketRefOrBuilderList();
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  io.grpc.channelz.v1.SocketRefOrBuilder getSocketRefOrBuilder(
+      int index);
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ChannelRef.java b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelRef.java
new file mode 100644
index 0000000..fae75b7
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelRef.java
@@ -0,0 +1,627 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * ChannelRef is a reference to a Channel.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.ChannelRef}
+ */
+public  final class ChannelRef extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.ChannelRef)
+    ChannelRefOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ChannelRef.newBuilder() to construct.
+  private ChannelRef(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ChannelRef() {
+    channelId_ = 0L;
+    name_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ChannelRef(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+
+            channelId_ = input.readInt64();
+            break;
+          }
+          case 18: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            name_ = s;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelRef_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelRef_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.ChannelRef.class, io.grpc.channelz.v1.ChannelRef.Builder.class);
+  }
+
+  public static final int CHANNEL_ID_FIELD_NUMBER = 1;
+  private long channelId_;
+  /**
+   * <pre>
+   * The globally unique id for this channel.  Must be a positive number.
+   * </pre>
+   *
+   * <code>int64 channel_id = 1;</code>
+   */
+  public long getChannelId() {
+    return channelId_;
+  }
+
+  public static final int NAME_FIELD_NUMBER = 2;
+  private volatile java.lang.Object name_;
+  /**
+   * <pre>
+   * An optional name associated with the channel.
+   * </pre>
+   *
+   * <code>string name = 2;</code>
+   */
+  public java.lang.String getName() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      name_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * An optional name associated with the channel.
+   * </pre>
+   *
+   * <code>string name = 2;</code>
+   */
+  public com.google.protobuf.ByteString
+      getNameBytes() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      name_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (channelId_ != 0L) {
+      output.writeInt64(1, channelId_);
+    }
+    if (!getNameBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 2, name_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (channelId_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(1, channelId_);
+    }
+    if (!getNameBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, name_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.ChannelRef)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.ChannelRef other = (io.grpc.channelz.v1.ChannelRef) obj;
+
+    boolean result = true;
+    result = result && (getChannelId()
+        == other.getChannelId());
+    result = result && getName()
+        .equals(other.getName());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + CHANNEL_ID_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getChannelId());
+    hash = (37 * hash) + NAME_FIELD_NUMBER;
+    hash = (53 * hash) + getName().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.ChannelRef parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelRef parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelRef parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelRef parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelRef parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelRef parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelRef parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelRef parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelRef parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelRef parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelRef parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelRef parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.ChannelRef prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * ChannelRef is a reference to a Channel.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.ChannelRef}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.ChannelRef)
+      io.grpc.channelz.v1.ChannelRefOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelRef_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelRef_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.ChannelRef.class, io.grpc.channelz.v1.ChannelRef.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.ChannelRef.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      channelId_ = 0L;
+
+      name_ = "";
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelRef_descriptor;
+    }
+
+    public io.grpc.channelz.v1.ChannelRef getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.ChannelRef.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.ChannelRef build() {
+      io.grpc.channelz.v1.ChannelRef result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.ChannelRef buildPartial() {
+      io.grpc.channelz.v1.ChannelRef result = new io.grpc.channelz.v1.ChannelRef(this);
+      result.channelId_ = channelId_;
+      result.name_ = name_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.ChannelRef) {
+        return mergeFrom((io.grpc.channelz.v1.ChannelRef)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.ChannelRef other) {
+      if (other == io.grpc.channelz.v1.ChannelRef.getDefaultInstance()) return this;
+      if (other.getChannelId() != 0L) {
+        setChannelId(other.getChannelId());
+      }
+      if (!other.getName().isEmpty()) {
+        name_ = other.name_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.ChannelRef parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.ChannelRef) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private long channelId_ ;
+    /**
+     * <pre>
+     * The globally unique id for this channel.  Must be a positive number.
+     * </pre>
+     *
+     * <code>int64 channel_id = 1;</code>
+     */
+    public long getChannelId() {
+      return channelId_;
+    }
+    /**
+     * <pre>
+     * The globally unique id for this channel.  Must be a positive number.
+     * </pre>
+     *
+     * <code>int64 channel_id = 1;</code>
+     */
+    public Builder setChannelId(long value) {
+      
+      channelId_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The globally unique id for this channel.  Must be a positive number.
+     * </pre>
+     *
+     * <code>int64 channel_id = 1;</code>
+     */
+    public Builder clearChannelId() {
+      
+      channelId_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object name_ = "";
+    /**
+     * <pre>
+     * An optional name associated with the channel.
+     * </pre>
+     *
+     * <code>string name = 2;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        name_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An optional name associated with the channel.
+     * </pre>
+     *
+     * <code>string name = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An optional name associated with the channel.
+     * </pre>
+     *
+     * <code>string name = 2;</code>
+     */
+    public Builder setName(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      name_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An optional name associated with the channel.
+     * </pre>
+     *
+     * <code>string name = 2;</code>
+     */
+    public Builder clearName() {
+      
+      name_ = getDefaultInstance().getName();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An optional name associated with the channel.
+     * </pre>
+     *
+     * <code>string name = 2;</code>
+     */
+    public Builder setNameBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      name_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.ChannelRef)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.ChannelRef)
+  private static final io.grpc.channelz.v1.ChannelRef DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.ChannelRef();
+  }
+
+  public static io.grpc.channelz.v1.ChannelRef getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ChannelRef>
+      PARSER = new com.google.protobuf.AbstractParser<ChannelRef>() {
+    public ChannelRef parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ChannelRef(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ChannelRef> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ChannelRef> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.ChannelRef getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ChannelRefOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelRefOrBuilder.java
new file mode 100644
index 0000000..121394a
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelRefOrBuilder.java
@@ -0,0 +1,36 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface ChannelRefOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.ChannelRef)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The globally unique id for this channel.  Must be a positive number.
+   * </pre>
+   *
+   * <code>int64 channel_id = 1;</code>
+   */
+  long getChannelId();
+
+  /**
+   * <pre>
+   * An optional name associated with the channel.
+   * </pre>
+   *
+   * <code>string name = 2;</code>
+   */
+  java.lang.String getName();
+  /**
+   * <pre>
+   * An optional name associated with the channel.
+   * </pre>
+   *
+   * <code>string name = 2;</code>
+   */
+  com.google.protobuf.ByteString
+      getNameBytes();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ChannelTrace.java b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelTrace.java
new file mode 100644
index 0000000..37e5b13
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelTrace.java
@@ -0,0 +1,1149 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * ChannelTrace represents the recent events that have occurred on the channel.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.ChannelTrace}
+ */
+public  final class ChannelTrace extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.ChannelTrace)
+    ChannelTraceOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ChannelTrace.newBuilder() to construct.
+  private ChannelTrace(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ChannelTrace() {
+    numEventsLogged_ = 0L;
+    events_ = java.util.Collections.emptyList();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ChannelTrace(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+
+            numEventsLogged_ = input.readInt64();
+            break;
+          }
+          case 18: {
+            com.google.protobuf.Timestamp.Builder subBuilder = null;
+            if (creationTimestamp_ != null) {
+              subBuilder = creationTimestamp_.toBuilder();
+            }
+            creationTimestamp_ = input.readMessage(com.google.protobuf.Timestamp.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(creationTimestamp_);
+              creationTimestamp_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 26: {
+            if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) {
+              events_ = new java.util.ArrayList<io.grpc.channelz.v1.ChannelTraceEvent>();
+              mutable_bitField0_ |= 0x00000004;
+            }
+            events_.add(
+                input.readMessage(io.grpc.channelz.v1.ChannelTraceEvent.parser(), extensionRegistry));
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) {
+        events_ = java.util.Collections.unmodifiableList(events_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelTrace_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelTrace_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.ChannelTrace.class, io.grpc.channelz.v1.ChannelTrace.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int NUM_EVENTS_LOGGED_FIELD_NUMBER = 1;
+  private long numEventsLogged_;
+  /**
+   * <pre>
+   * Number of events ever logged in this tracing object. This can differ from
+   * events.size() because events can be overwritten or garbage collected by
+   * implementations.
+   * </pre>
+   *
+   * <code>int64 num_events_logged = 1;</code>
+   */
+  public long getNumEventsLogged() {
+    return numEventsLogged_;
+  }
+
+  public static final int CREATION_TIMESTAMP_FIELD_NUMBER = 2;
+  private com.google.protobuf.Timestamp creationTimestamp_;
+  /**
+   * <pre>
+   * Time that this channel was created.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+   */
+  public boolean hasCreationTimestamp() {
+    return creationTimestamp_ != null;
+  }
+  /**
+   * <pre>
+   * Time that this channel was created.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+   */
+  public com.google.protobuf.Timestamp getCreationTimestamp() {
+    return creationTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : creationTimestamp_;
+  }
+  /**
+   * <pre>
+   * Time that this channel was created.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+   */
+  public com.google.protobuf.TimestampOrBuilder getCreationTimestampOrBuilder() {
+    return getCreationTimestamp();
+  }
+
+  public static final int EVENTS_FIELD_NUMBER = 3;
+  private java.util.List<io.grpc.channelz.v1.ChannelTraceEvent> events_;
+  /**
+   * <pre>
+   * List of events that have occurred on this channel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+   */
+  public java.util.List<io.grpc.channelz.v1.ChannelTraceEvent> getEventsList() {
+    return events_;
+  }
+  /**
+   * <pre>
+   * List of events that have occurred on this channel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+   */
+  public java.util.List<? extends io.grpc.channelz.v1.ChannelTraceEventOrBuilder> 
+      getEventsOrBuilderList() {
+    return events_;
+  }
+  /**
+   * <pre>
+   * List of events that have occurred on this channel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+   */
+  public int getEventsCount() {
+    return events_.size();
+  }
+  /**
+   * <pre>
+   * List of events that have occurred on this channel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+   */
+  public io.grpc.channelz.v1.ChannelTraceEvent getEvents(int index) {
+    return events_.get(index);
+  }
+  /**
+   * <pre>
+   * List of events that have occurred on this channel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+   */
+  public io.grpc.channelz.v1.ChannelTraceEventOrBuilder getEventsOrBuilder(
+      int index) {
+    return events_.get(index);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (numEventsLogged_ != 0L) {
+      output.writeInt64(1, numEventsLogged_);
+    }
+    if (creationTimestamp_ != null) {
+      output.writeMessage(2, getCreationTimestamp());
+    }
+    for (int i = 0; i < events_.size(); i++) {
+      output.writeMessage(3, events_.get(i));
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (numEventsLogged_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(1, numEventsLogged_);
+    }
+    if (creationTimestamp_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(2, getCreationTimestamp());
+    }
+    for (int i = 0; i < events_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(3, events_.get(i));
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.ChannelTrace)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.ChannelTrace other = (io.grpc.channelz.v1.ChannelTrace) obj;
+
+    boolean result = true;
+    result = result && (getNumEventsLogged()
+        == other.getNumEventsLogged());
+    result = result && (hasCreationTimestamp() == other.hasCreationTimestamp());
+    if (hasCreationTimestamp()) {
+      result = result && getCreationTimestamp()
+          .equals(other.getCreationTimestamp());
+    }
+    result = result && getEventsList()
+        .equals(other.getEventsList());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + NUM_EVENTS_LOGGED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getNumEventsLogged());
+    if (hasCreationTimestamp()) {
+      hash = (37 * hash) + CREATION_TIMESTAMP_FIELD_NUMBER;
+      hash = (53 * hash) + getCreationTimestamp().hashCode();
+    }
+    if (getEventsCount() > 0) {
+      hash = (37 * hash) + EVENTS_FIELD_NUMBER;
+      hash = (53 * hash) + getEventsList().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.ChannelTrace parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelTrace parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelTrace parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelTrace parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelTrace parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelTrace parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelTrace parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelTrace parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelTrace parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelTrace parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelTrace parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelTrace parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.ChannelTrace prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * ChannelTrace represents the recent events that have occurred on the channel.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.ChannelTrace}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.ChannelTrace)
+      io.grpc.channelz.v1.ChannelTraceOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelTrace_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelTrace_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.ChannelTrace.class, io.grpc.channelz.v1.ChannelTrace.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.ChannelTrace.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+        getEventsFieldBuilder();
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      numEventsLogged_ = 0L;
+
+      if (creationTimestampBuilder_ == null) {
+        creationTimestamp_ = null;
+      } else {
+        creationTimestamp_ = null;
+        creationTimestampBuilder_ = null;
+      }
+      if (eventsBuilder_ == null) {
+        events_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000004);
+      } else {
+        eventsBuilder_.clear();
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelTrace_descriptor;
+    }
+
+    public io.grpc.channelz.v1.ChannelTrace getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.ChannelTrace.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.ChannelTrace build() {
+      io.grpc.channelz.v1.ChannelTrace result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.ChannelTrace buildPartial() {
+      io.grpc.channelz.v1.ChannelTrace result = new io.grpc.channelz.v1.ChannelTrace(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      result.numEventsLogged_ = numEventsLogged_;
+      if (creationTimestampBuilder_ == null) {
+        result.creationTimestamp_ = creationTimestamp_;
+      } else {
+        result.creationTimestamp_ = creationTimestampBuilder_.build();
+      }
+      if (eventsBuilder_ == null) {
+        if (((bitField0_ & 0x00000004) == 0x00000004)) {
+          events_ = java.util.Collections.unmodifiableList(events_);
+          bitField0_ = (bitField0_ & ~0x00000004);
+        }
+        result.events_ = events_;
+      } else {
+        result.events_ = eventsBuilder_.build();
+      }
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.ChannelTrace) {
+        return mergeFrom((io.grpc.channelz.v1.ChannelTrace)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.ChannelTrace other) {
+      if (other == io.grpc.channelz.v1.ChannelTrace.getDefaultInstance()) return this;
+      if (other.getNumEventsLogged() != 0L) {
+        setNumEventsLogged(other.getNumEventsLogged());
+      }
+      if (other.hasCreationTimestamp()) {
+        mergeCreationTimestamp(other.getCreationTimestamp());
+      }
+      if (eventsBuilder_ == null) {
+        if (!other.events_.isEmpty()) {
+          if (events_.isEmpty()) {
+            events_ = other.events_;
+            bitField0_ = (bitField0_ & ~0x00000004);
+          } else {
+            ensureEventsIsMutable();
+            events_.addAll(other.events_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.events_.isEmpty()) {
+          if (eventsBuilder_.isEmpty()) {
+            eventsBuilder_.dispose();
+            eventsBuilder_ = null;
+            events_ = other.events_;
+            bitField0_ = (bitField0_ & ~0x00000004);
+            eventsBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getEventsFieldBuilder() : null;
+          } else {
+            eventsBuilder_.addAllMessages(other.events_);
+          }
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.ChannelTrace parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.ChannelTrace) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private long numEventsLogged_ ;
+    /**
+     * <pre>
+     * Number of events ever logged in this tracing object. This can differ from
+     * events.size() because events can be overwritten or garbage collected by
+     * implementations.
+     * </pre>
+     *
+     * <code>int64 num_events_logged = 1;</code>
+     */
+    public long getNumEventsLogged() {
+      return numEventsLogged_;
+    }
+    /**
+     * <pre>
+     * Number of events ever logged in this tracing object. This can differ from
+     * events.size() because events can be overwritten or garbage collected by
+     * implementations.
+     * </pre>
+     *
+     * <code>int64 num_events_logged = 1;</code>
+     */
+    public Builder setNumEventsLogged(long value) {
+      
+      numEventsLogged_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Number of events ever logged in this tracing object. This can differ from
+     * events.size() because events can be overwritten or garbage collected by
+     * implementations.
+     * </pre>
+     *
+     * <code>int64 num_events_logged = 1;</code>
+     */
+    public Builder clearNumEventsLogged() {
+      
+      numEventsLogged_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.Timestamp creationTimestamp_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> creationTimestampBuilder_;
+    /**
+     * <pre>
+     * Time that this channel was created.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+     */
+    public boolean hasCreationTimestamp() {
+      return creationTimestampBuilder_ != null || creationTimestamp_ != null;
+    }
+    /**
+     * <pre>
+     * Time that this channel was created.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+     */
+    public com.google.protobuf.Timestamp getCreationTimestamp() {
+      if (creationTimestampBuilder_ == null) {
+        return creationTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : creationTimestamp_;
+      } else {
+        return creationTimestampBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * Time that this channel was created.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+     */
+    public Builder setCreationTimestamp(com.google.protobuf.Timestamp value) {
+      if (creationTimestampBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        creationTimestamp_ = value;
+        onChanged();
+      } else {
+        creationTimestampBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Time that this channel was created.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+     */
+    public Builder setCreationTimestamp(
+        com.google.protobuf.Timestamp.Builder builderForValue) {
+      if (creationTimestampBuilder_ == null) {
+        creationTimestamp_ = builderForValue.build();
+        onChanged();
+      } else {
+        creationTimestampBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Time that this channel was created.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+     */
+    public Builder mergeCreationTimestamp(com.google.protobuf.Timestamp value) {
+      if (creationTimestampBuilder_ == null) {
+        if (creationTimestamp_ != null) {
+          creationTimestamp_ =
+            com.google.protobuf.Timestamp.newBuilder(creationTimestamp_).mergeFrom(value).buildPartial();
+        } else {
+          creationTimestamp_ = value;
+        }
+        onChanged();
+      } else {
+        creationTimestampBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Time that this channel was created.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+     */
+    public Builder clearCreationTimestamp() {
+      if (creationTimestampBuilder_ == null) {
+        creationTimestamp_ = null;
+        onChanged();
+      } else {
+        creationTimestamp_ = null;
+        creationTimestampBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Time that this channel was created.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+     */
+    public com.google.protobuf.Timestamp.Builder getCreationTimestampBuilder() {
+      
+      onChanged();
+      return getCreationTimestampFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Time that this channel was created.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+     */
+    public com.google.protobuf.TimestampOrBuilder getCreationTimestampOrBuilder() {
+      if (creationTimestampBuilder_ != null) {
+        return creationTimestampBuilder_.getMessageOrBuilder();
+      } else {
+        return creationTimestamp_ == null ?
+            com.google.protobuf.Timestamp.getDefaultInstance() : creationTimestamp_;
+      }
+    }
+    /**
+     * <pre>
+     * Time that this channel was created.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> 
+        getCreationTimestampFieldBuilder() {
+      if (creationTimestampBuilder_ == null) {
+        creationTimestampBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder>(
+                getCreationTimestamp(),
+                getParentForChildren(),
+                isClean());
+        creationTimestamp_ = null;
+      }
+      return creationTimestampBuilder_;
+    }
+
+    private java.util.List<io.grpc.channelz.v1.ChannelTraceEvent> events_ =
+      java.util.Collections.emptyList();
+    private void ensureEventsIsMutable() {
+      if (!((bitField0_ & 0x00000004) == 0x00000004)) {
+        events_ = new java.util.ArrayList<io.grpc.channelz.v1.ChannelTraceEvent>(events_);
+        bitField0_ |= 0x00000004;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelTraceEvent, io.grpc.channelz.v1.ChannelTraceEvent.Builder, io.grpc.channelz.v1.ChannelTraceEventOrBuilder> eventsBuilder_;
+
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.ChannelTraceEvent> getEventsList() {
+      if (eventsBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(events_);
+      } else {
+        return eventsBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public int getEventsCount() {
+      if (eventsBuilder_ == null) {
+        return events_.size();
+      } else {
+        return eventsBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelTraceEvent getEvents(int index) {
+      if (eventsBuilder_ == null) {
+        return events_.get(index);
+      } else {
+        return eventsBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public Builder setEvents(
+        int index, io.grpc.channelz.v1.ChannelTraceEvent value) {
+      if (eventsBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureEventsIsMutable();
+        events_.set(index, value);
+        onChanged();
+      } else {
+        eventsBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public Builder setEvents(
+        int index, io.grpc.channelz.v1.ChannelTraceEvent.Builder builderForValue) {
+      if (eventsBuilder_ == null) {
+        ensureEventsIsMutable();
+        events_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        eventsBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public Builder addEvents(io.grpc.channelz.v1.ChannelTraceEvent value) {
+      if (eventsBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureEventsIsMutable();
+        events_.add(value);
+        onChanged();
+      } else {
+        eventsBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public Builder addEvents(
+        int index, io.grpc.channelz.v1.ChannelTraceEvent value) {
+      if (eventsBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureEventsIsMutable();
+        events_.add(index, value);
+        onChanged();
+      } else {
+        eventsBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public Builder addEvents(
+        io.grpc.channelz.v1.ChannelTraceEvent.Builder builderForValue) {
+      if (eventsBuilder_ == null) {
+        ensureEventsIsMutable();
+        events_.add(builderForValue.build());
+        onChanged();
+      } else {
+        eventsBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public Builder addEvents(
+        int index, io.grpc.channelz.v1.ChannelTraceEvent.Builder builderForValue) {
+      if (eventsBuilder_ == null) {
+        ensureEventsIsMutable();
+        events_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        eventsBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public Builder addAllEvents(
+        java.lang.Iterable<? extends io.grpc.channelz.v1.ChannelTraceEvent> values) {
+      if (eventsBuilder_ == null) {
+        ensureEventsIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, events_);
+        onChanged();
+      } else {
+        eventsBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public Builder clearEvents() {
+      if (eventsBuilder_ == null) {
+        events_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000004);
+        onChanged();
+      } else {
+        eventsBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public Builder removeEvents(int index) {
+      if (eventsBuilder_ == null) {
+        ensureEventsIsMutable();
+        events_.remove(index);
+        onChanged();
+      } else {
+        eventsBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelTraceEvent.Builder getEventsBuilder(
+        int index) {
+      return getEventsFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelTraceEventOrBuilder getEventsOrBuilder(
+        int index) {
+      if (eventsBuilder_ == null) {
+        return events_.get(index);  } else {
+        return eventsBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public java.util.List<? extends io.grpc.channelz.v1.ChannelTraceEventOrBuilder> 
+         getEventsOrBuilderList() {
+      if (eventsBuilder_ != null) {
+        return eventsBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(events_);
+      }
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelTraceEvent.Builder addEventsBuilder() {
+      return getEventsFieldBuilder().addBuilder(
+          io.grpc.channelz.v1.ChannelTraceEvent.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelTraceEvent.Builder addEventsBuilder(
+        int index) {
+      return getEventsFieldBuilder().addBuilder(
+          index, io.grpc.channelz.v1.ChannelTraceEvent.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * List of events that have occurred on this channel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.ChannelTraceEvent.Builder> 
+         getEventsBuilderList() {
+      return getEventsFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelTraceEvent, io.grpc.channelz.v1.ChannelTraceEvent.Builder, io.grpc.channelz.v1.ChannelTraceEventOrBuilder> 
+        getEventsFieldBuilder() {
+      if (eventsBuilder_ == null) {
+        eventsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.channelz.v1.ChannelTraceEvent, io.grpc.channelz.v1.ChannelTraceEvent.Builder, io.grpc.channelz.v1.ChannelTraceEventOrBuilder>(
+                events_,
+                ((bitField0_ & 0x00000004) == 0x00000004),
+                getParentForChildren(),
+                isClean());
+        events_ = null;
+      }
+      return eventsBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.ChannelTrace)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.ChannelTrace)
+  private static final io.grpc.channelz.v1.ChannelTrace DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.ChannelTrace();
+  }
+
+  public static io.grpc.channelz.v1.ChannelTrace getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ChannelTrace>
+      PARSER = new com.google.protobuf.AbstractParser<ChannelTrace>() {
+    public ChannelTrace parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ChannelTrace(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ChannelTrace> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ChannelTrace> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.ChannelTrace getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ChannelTraceEvent.java b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelTraceEvent.java
new file mode 100644
index 0000000..3220684
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelTraceEvent.java
@@ -0,0 +1,1490 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * A trace event is an interesting thing that happened to a channel or
+ * subchannel, such as creation, address resolution, subchannel creation, etc.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.ChannelTraceEvent}
+ */
+public  final class ChannelTraceEvent extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.ChannelTraceEvent)
+    ChannelTraceEventOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ChannelTraceEvent.newBuilder() to construct.
+  private ChannelTraceEvent(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ChannelTraceEvent() {
+    description_ = "";
+    severity_ = 0;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ChannelTraceEvent(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            description_ = s;
+            break;
+          }
+          case 16: {
+            int rawValue = input.readEnum();
+
+            severity_ = rawValue;
+            break;
+          }
+          case 26: {
+            com.google.protobuf.Timestamp.Builder subBuilder = null;
+            if (timestamp_ != null) {
+              subBuilder = timestamp_.toBuilder();
+            }
+            timestamp_ = input.readMessage(com.google.protobuf.Timestamp.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(timestamp_);
+              timestamp_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 34: {
+            io.grpc.channelz.v1.ChannelRef.Builder subBuilder = null;
+            if (childRefCase_ == 4) {
+              subBuilder = ((io.grpc.channelz.v1.ChannelRef) childRef_).toBuilder();
+            }
+            childRef_ =
+                input.readMessage(io.grpc.channelz.v1.ChannelRef.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.channelz.v1.ChannelRef) childRef_);
+              childRef_ = subBuilder.buildPartial();
+            }
+            childRefCase_ = 4;
+            break;
+          }
+          case 42: {
+            io.grpc.channelz.v1.SubchannelRef.Builder subBuilder = null;
+            if (childRefCase_ == 5) {
+              subBuilder = ((io.grpc.channelz.v1.SubchannelRef) childRef_).toBuilder();
+            }
+            childRef_ =
+                input.readMessage(io.grpc.channelz.v1.SubchannelRef.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.channelz.v1.SubchannelRef) childRef_);
+              childRef_ = subBuilder.buildPartial();
+            }
+            childRefCase_ = 5;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelTraceEvent_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelTraceEvent_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.ChannelTraceEvent.class, io.grpc.channelz.v1.ChannelTraceEvent.Builder.class);
+  }
+
+  /**
+   * <pre>
+   * The supported severity levels of trace events.
+   * </pre>
+   *
+   * Protobuf enum {@code grpc.channelz.v1.ChannelTraceEvent.Severity}
+   */
+  public enum Severity
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>CT_UNKNOWN = 0;</code>
+     */
+    CT_UNKNOWN(0),
+    /**
+     * <code>CT_INFO = 1;</code>
+     */
+    CT_INFO(1),
+    /**
+     * <code>CT_WARNING = 2;</code>
+     */
+    CT_WARNING(2),
+    /**
+     * <code>CT_ERROR = 3;</code>
+     */
+    CT_ERROR(3),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <code>CT_UNKNOWN = 0;</code>
+     */
+    public static final int CT_UNKNOWN_VALUE = 0;
+    /**
+     * <code>CT_INFO = 1;</code>
+     */
+    public static final int CT_INFO_VALUE = 1;
+    /**
+     * <code>CT_WARNING = 2;</code>
+     */
+    public static final int CT_WARNING_VALUE = 2;
+    /**
+     * <code>CT_ERROR = 3;</code>
+     */
+    public static final int CT_ERROR_VALUE = 3;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static Severity valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static Severity forNumber(int value) {
+      switch (value) {
+        case 0: return CT_UNKNOWN;
+        case 1: return CT_INFO;
+        case 2: return CT_WARNING;
+        case 3: return CT_ERROR;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<Severity>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        Severity> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<Severity>() {
+            public Severity findValueByNumber(int number) {
+              return Severity.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelTraceEvent.getDescriptor().getEnumTypes().get(0);
+    }
+
+    private static final Severity[] VALUES = values();
+
+    public static Severity valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private Severity(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.channelz.v1.ChannelTraceEvent.Severity)
+  }
+
+  private int childRefCase_ = 0;
+  private java.lang.Object childRef_;
+  public enum ChildRefCase
+      implements com.google.protobuf.Internal.EnumLite {
+    CHANNEL_REF(4),
+    SUBCHANNEL_REF(5),
+    CHILDREF_NOT_SET(0);
+    private final int value;
+    private ChildRefCase(int value) {
+      this.value = value;
+    }
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static ChildRefCase valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static ChildRefCase forNumber(int value) {
+      switch (value) {
+        case 4: return CHANNEL_REF;
+        case 5: return SUBCHANNEL_REF;
+        case 0: return CHILDREF_NOT_SET;
+        default: return null;
+      }
+    }
+    public int getNumber() {
+      return this.value;
+    }
+  };
+
+  public ChildRefCase
+  getChildRefCase() {
+    return ChildRefCase.forNumber(
+        childRefCase_);
+  }
+
+  public static final int DESCRIPTION_FIELD_NUMBER = 1;
+  private volatile java.lang.Object description_;
+  /**
+   * <pre>
+   * High level description of the event.
+   * </pre>
+   *
+   * <code>string description = 1;</code>
+   */
+  public java.lang.String getDescription() {
+    java.lang.Object ref = description_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      description_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * High level description of the event.
+   * </pre>
+   *
+   * <code>string description = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getDescriptionBytes() {
+    java.lang.Object ref = description_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      description_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int SEVERITY_FIELD_NUMBER = 2;
+  private int severity_;
+  /**
+   * <pre>
+   * the severity of the trace event
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTraceEvent.Severity severity = 2;</code>
+   */
+  public int getSeverityValue() {
+    return severity_;
+  }
+  /**
+   * <pre>
+   * the severity of the trace event
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTraceEvent.Severity severity = 2;</code>
+   */
+  public io.grpc.channelz.v1.ChannelTraceEvent.Severity getSeverity() {
+    io.grpc.channelz.v1.ChannelTraceEvent.Severity result = io.grpc.channelz.v1.ChannelTraceEvent.Severity.valueOf(severity_);
+    return result == null ? io.grpc.channelz.v1.ChannelTraceEvent.Severity.UNRECOGNIZED : result;
+  }
+
+  public static final int TIMESTAMP_FIELD_NUMBER = 3;
+  private com.google.protobuf.Timestamp timestamp_;
+  /**
+   * <pre>
+   * When this event occurred.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+   */
+  public boolean hasTimestamp() {
+    return timestamp_ != null;
+  }
+  /**
+   * <pre>
+   * When this event occurred.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+   */
+  public com.google.protobuf.Timestamp getTimestamp() {
+    return timestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : timestamp_;
+  }
+  /**
+   * <pre>
+   * When this event occurred.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+   */
+  public com.google.protobuf.TimestampOrBuilder getTimestampOrBuilder() {
+    return getTimestamp();
+  }
+
+  public static final int CHANNEL_REF_FIELD_NUMBER = 4;
+  /**
+   * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+   */
+  public boolean hasChannelRef() {
+    return childRefCase_ == 4;
+  }
+  /**
+   * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+   */
+  public io.grpc.channelz.v1.ChannelRef getChannelRef() {
+    if (childRefCase_ == 4) {
+       return (io.grpc.channelz.v1.ChannelRef) childRef_;
+    }
+    return io.grpc.channelz.v1.ChannelRef.getDefaultInstance();
+  }
+  /**
+   * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+   */
+  public io.grpc.channelz.v1.ChannelRefOrBuilder getChannelRefOrBuilder() {
+    if (childRefCase_ == 4) {
+       return (io.grpc.channelz.v1.ChannelRef) childRef_;
+    }
+    return io.grpc.channelz.v1.ChannelRef.getDefaultInstance();
+  }
+
+  public static final int SUBCHANNEL_REF_FIELD_NUMBER = 5;
+  /**
+   * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+   */
+  public boolean hasSubchannelRef() {
+    return childRefCase_ == 5;
+  }
+  /**
+   * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+   */
+  public io.grpc.channelz.v1.SubchannelRef getSubchannelRef() {
+    if (childRefCase_ == 5) {
+       return (io.grpc.channelz.v1.SubchannelRef) childRef_;
+    }
+    return io.grpc.channelz.v1.SubchannelRef.getDefaultInstance();
+  }
+  /**
+   * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+   */
+  public io.grpc.channelz.v1.SubchannelRefOrBuilder getSubchannelRefOrBuilder() {
+    if (childRefCase_ == 5) {
+       return (io.grpc.channelz.v1.SubchannelRef) childRef_;
+    }
+    return io.grpc.channelz.v1.SubchannelRef.getDefaultInstance();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getDescriptionBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, description_);
+    }
+    if (severity_ != io.grpc.channelz.v1.ChannelTraceEvent.Severity.CT_UNKNOWN.getNumber()) {
+      output.writeEnum(2, severity_);
+    }
+    if (timestamp_ != null) {
+      output.writeMessage(3, getTimestamp());
+    }
+    if (childRefCase_ == 4) {
+      output.writeMessage(4, (io.grpc.channelz.v1.ChannelRef) childRef_);
+    }
+    if (childRefCase_ == 5) {
+      output.writeMessage(5, (io.grpc.channelz.v1.SubchannelRef) childRef_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getDescriptionBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, description_);
+    }
+    if (severity_ != io.grpc.channelz.v1.ChannelTraceEvent.Severity.CT_UNKNOWN.getNumber()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeEnumSize(2, severity_);
+    }
+    if (timestamp_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(3, getTimestamp());
+    }
+    if (childRefCase_ == 4) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(4, (io.grpc.channelz.v1.ChannelRef) childRef_);
+    }
+    if (childRefCase_ == 5) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(5, (io.grpc.channelz.v1.SubchannelRef) childRef_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.ChannelTraceEvent)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.ChannelTraceEvent other = (io.grpc.channelz.v1.ChannelTraceEvent) obj;
+
+    boolean result = true;
+    result = result && getDescription()
+        .equals(other.getDescription());
+    result = result && severity_ == other.severity_;
+    result = result && (hasTimestamp() == other.hasTimestamp());
+    if (hasTimestamp()) {
+      result = result && getTimestamp()
+          .equals(other.getTimestamp());
+    }
+    result = result && getChildRefCase().equals(
+        other.getChildRefCase());
+    if (!result) return false;
+    switch (childRefCase_) {
+      case 4:
+        result = result && getChannelRef()
+            .equals(other.getChannelRef());
+        break;
+      case 5:
+        result = result && getSubchannelRef()
+            .equals(other.getSubchannelRef());
+        break;
+      case 0:
+      default:
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + DESCRIPTION_FIELD_NUMBER;
+    hash = (53 * hash) + getDescription().hashCode();
+    hash = (37 * hash) + SEVERITY_FIELD_NUMBER;
+    hash = (53 * hash) + severity_;
+    if (hasTimestamp()) {
+      hash = (37 * hash) + TIMESTAMP_FIELD_NUMBER;
+      hash = (53 * hash) + getTimestamp().hashCode();
+    }
+    switch (childRefCase_) {
+      case 4:
+        hash = (37 * hash) + CHANNEL_REF_FIELD_NUMBER;
+        hash = (53 * hash) + getChannelRef().hashCode();
+        break;
+      case 5:
+        hash = (37 * hash) + SUBCHANNEL_REF_FIELD_NUMBER;
+        hash = (53 * hash) + getSubchannelRef().hashCode();
+        break;
+      case 0:
+      default:
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.ChannelTraceEvent parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelTraceEvent parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelTraceEvent parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelTraceEvent parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelTraceEvent parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ChannelTraceEvent parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelTraceEvent parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelTraceEvent parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelTraceEvent parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelTraceEvent parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ChannelTraceEvent parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ChannelTraceEvent parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.ChannelTraceEvent prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * A trace event is an interesting thing that happened to a channel or
+   * subchannel, such as creation, address resolution, subchannel creation, etc.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.ChannelTraceEvent}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.ChannelTraceEvent)
+      io.grpc.channelz.v1.ChannelTraceEventOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelTraceEvent_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelTraceEvent_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.ChannelTraceEvent.class, io.grpc.channelz.v1.ChannelTraceEvent.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.ChannelTraceEvent.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      description_ = "";
+
+      severity_ = 0;
+
+      if (timestampBuilder_ == null) {
+        timestamp_ = null;
+      } else {
+        timestamp_ = null;
+        timestampBuilder_ = null;
+      }
+      childRefCase_ = 0;
+      childRef_ = null;
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ChannelTraceEvent_descriptor;
+    }
+
+    public io.grpc.channelz.v1.ChannelTraceEvent getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.ChannelTraceEvent.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.ChannelTraceEvent build() {
+      io.grpc.channelz.v1.ChannelTraceEvent result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.ChannelTraceEvent buildPartial() {
+      io.grpc.channelz.v1.ChannelTraceEvent result = new io.grpc.channelz.v1.ChannelTraceEvent(this);
+      result.description_ = description_;
+      result.severity_ = severity_;
+      if (timestampBuilder_ == null) {
+        result.timestamp_ = timestamp_;
+      } else {
+        result.timestamp_ = timestampBuilder_.build();
+      }
+      if (childRefCase_ == 4) {
+        if (channelRefBuilder_ == null) {
+          result.childRef_ = childRef_;
+        } else {
+          result.childRef_ = channelRefBuilder_.build();
+        }
+      }
+      if (childRefCase_ == 5) {
+        if (subchannelRefBuilder_ == null) {
+          result.childRef_ = childRef_;
+        } else {
+          result.childRef_ = subchannelRefBuilder_.build();
+        }
+      }
+      result.childRefCase_ = childRefCase_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.ChannelTraceEvent) {
+        return mergeFrom((io.grpc.channelz.v1.ChannelTraceEvent)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.ChannelTraceEvent other) {
+      if (other == io.grpc.channelz.v1.ChannelTraceEvent.getDefaultInstance()) return this;
+      if (!other.getDescription().isEmpty()) {
+        description_ = other.description_;
+        onChanged();
+      }
+      if (other.severity_ != 0) {
+        setSeverityValue(other.getSeverityValue());
+      }
+      if (other.hasTimestamp()) {
+        mergeTimestamp(other.getTimestamp());
+      }
+      switch (other.getChildRefCase()) {
+        case CHANNEL_REF: {
+          mergeChannelRef(other.getChannelRef());
+          break;
+        }
+        case SUBCHANNEL_REF: {
+          mergeSubchannelRef(other.getSubchannelRef());
+          break;
+        }
+        case CHILDREF_NOT_SET: {
+          break;
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.ChannelTraceEvent parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.ChannelTraceEvent) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int childRefCase_ = 0;
+    private java.lang.Object childRef_;
+    public ChildRefCase
+        getChildRefCase() {
+      return ChildRefCase.forNumber(
+          childRefCase_);
+    }
+
+    public Builder clearChildRef() {
+      childRefCase_ = 0;
+      childRef_ = null;
+      onChanged();
+      return this;
+    }
+
+
+    private java.lang.Object description_ = "";
+    /**
+     * <pre>
+     * High level description of the event.
+     * </pre>
+     *
+     * <code>string description = 1;</code>
+     */
+    public java.lang.String getDescription() {
+      java.lang.Object ref = description_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        description_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * High level description of the event.
+     * </pre>
+     *
+     * <code>string description = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getDescriptionBytes() {
+      java.lang.Object ref = description_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        description_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * High level description of the event.
+     * </pre>
+     *
+     * <code>string description = 1;</code>
+     */
+    public Builder setDescription(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      description_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * High level description of the event.
+     * </pre>
+     *
+     * <code>string description = 1;</code>
+     */
+    public Builder clearDescription() {
+      
+      description_ = getDefaultInstance().getDescription();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * High level description of the event.
+     * </pre>
+     *
+     * <code>string description = 1;</code>
+     */
+    public Builder setDescriptionBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      description_ = value;
+      onChanged();
+      return this;
+    }
+
+    private int severity_ = 0;
+    /**
+     * <pre>
+     * the severity of the trace event
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTraceEvent.Severity severity = 2;</code>
+     */
+    public int getSeverityValue() {
+      return severity_;
+    }
+    /**
+     * <pre>
+     * the severity of the trace event
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTraceEvent.Severity severity = 2;</code>
+     */
+    public Builder setSeverityValue(int value) {
+      severity_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * the severity of the trace event
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTraceEvent.Severity severity = 2;</code>
+     */
+    public io.grpc.channelz.v1.ChannelTraceEvent.Severity getSeverity() {
+      io.grpc.channelz.v1.ChannelTraceEvent.Severity result = io.grpc.channelz.v1.ChannelTraceEvent.Severity.valueOf(severity_);
+      return result == null ? io.grpc.channelz.v1.ChannelTraceEvent.Severity.UNRECOGNIZED : result;
+    }
+    /**
+     * <pre>
+     * the severity of the trace event
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTraceEvent.Severity severity = 2;</code>
+     */
+    public Builder setSeverity(io.grpc.channelz.v1.ChannelTraceEvent.Severity value) {
+      if (value == null) {
+        throw new NullPointerException();
+      }
+      
+      severity_ = value.getNumber();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * the severity of the trace event
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTraceEvent.Severity severity = 2;</code>
+     */
+    public Builder clearSeverity() {
+      
+      severity_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.Timestamp timestamp_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> timestampBuilder_;
+    /**
+     * <pre>
+     * When this event occurred.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+     */
+    public boolean hasTimestamp() {
+      return timestampBuilder_ != null || timestamp_ != null;
+    }
+    /**
+     * <pre>
+     * When this event occurred.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+     */
+    public com.google.protobuf.Timestamp getTimestamp() {
+      if (timestampBuilder_ == null) {
+        return timestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : timestamp_;
+      } else {
+        return timestampBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * When this event occurred.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+     */
+    public Builder setTimestamp(com.google.protobuf.Timestamp value) {
+      if (timestampBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        timestamp_ = value;
+        onChanged();
+      } else {
+        timestampBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * When this event occurred.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+     */
+    public Builder setTimestamp(
+        com.google.protobuf.Timestamp.Builder builderForValue) {
+      if (timestampBuilder_ == null) {
+        timestamp_ = builderForValue.build();
+        onChanged();
+      } else {
+        timestampBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * When this event occurred.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+     */
+    public Builder mergeTimestamp(com.google.protobuf.Timestamp value) {
+      if (timestampBuilder_ == null) {
+        if (timestamp_ != null) {
+          timestamp_ =
+            com.google.protobuf.Timestamp.newBuilder(timestamp_).mergeFrom(value).buildPartial();
+        } else {
+          timestamp_ = value;
+        }
+        onChanged();
+      } else {
+        timestampBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * When this event occurred.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+     */
+    public Builder clearTimestamp() {
+      if (timestampBuilder_ == null) {
+        timestamp_ = null;
+        onChanged();
+      } else {
+        timestamp_ = null;
+        timestampBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * When this event occurred.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+     */
+    public com.google.protobuf.Timestamp.Builder getTimestampBuilder() {
+      
+      onChanged();
+      return getTimestampFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * When this event occurred.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+     */
+    public com.google.protobuf.TimestampOrBuilder getTimestampOrBuilder() {
+      if (timestampBuilder_ != null) {
+        return timestampBuilder_.getMessageOrBuilder();
+      } else {
+        return timestamp_ == null ?
+            com.google.protobuf.Timestamp.getDefaultInstance() : timestamp_;
+      }
+    }
+    /**
+     * <pre>
+     * When this event occurred.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> 
+        getTimestampFieldBuilder() {
+      if (timestampBuilder_ == null) {
+        timestampBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder>(
+                getTimestamp(),
+                getParentForChildren(),
+                isClean());
+        timestamp_ = null;
+      }
+      return timestampBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelRef, io.grpc.channelz.v1.ChannelRef.Builder, io.grpc.channelz.v1.ChannelRefOrBuilder> channelRefBuilder_;
+    /**
+     * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+     */
+    public boolean hasChannelRef() {
+      return childRefCase_ == 4;
+    }
+    /**
+     * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRef getChannelRef() {
+      if (channelRefBuilder_ == null) {
+        if (childRefCase_ == 4) {
+          return (io.grpc.channelz.v1.ChannelRef) childRef_;
+        }
+        return io.grpc.channelz.v1.ChannelRef.getDefaultInstance();
+      } else {
+        if (childRefCase_ == 4) {
+          return channelRefBuilder_.getMessage();
+        }
+        return io.grpc.channelz.v1.ChannelRef.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+     */
+    public Builder setChannelRef(io.grpc.channelz.v1.ChannelRef value) {
+      if (channelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        childRef_ = value;
+        onChanged();
+      } else {
+        channelRefBuilder_.setMessage(value);
+      }
+      childRefCase_ = 4;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+     */
+    public Builder setChannelRef(
+        io.grpc.channelz.v1.ChannelRef.Builder builderForValue) {
+      if (channelRefBuilder_ == null) {
+        childRef_ = builderForValue.build();
+        onChanged();
+      } else {
+        channelRefBuilder_.setMessage(builderForValue.build());
+      }
+      childRefCase_ = 4;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+     */
+    public Builder mergeChannelRef(io.grpc.channelz.v1.ChannelRef value) {
+      if (channelRefBuilder_ == null) {
+        if (childRefCase_ == 4 &&
+            childRef_ != io.grpc.channelz.v1.ChannelRef.getDefaultInstance()) {
+          childRef_ = io.grpc.channelz.v1.ChannelRef.newBuilder((io.grpc.channelz.v1.ChannelRef) childRef_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          childRef_ = value;
+        }
+        onChanged();
+      } else {
+        if (childRefCase_ == 4) {
+          channelRefBuilder_.mergeFrom(value);
+        }
+        channelRefBuilder_.setMessage(value);
+      }
+      childRefCase_ = 4;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+     */
+    public Builder clearChannelRef() {
+      if (channelRefBuilder_ == null) {
+        if (childRefCase_ == 4) {
+          childRefCase_ = 0;
+          childRef_ = null;
+          onChanged();
+        }
+      } else {
+        if (childRefCase_ == 4) {
+          childRefCase_ = 0;
+          childRef_ = null;
+        }
+        channelRefBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRef.Builder getChannelRefBuilder() {
+      return getChannelRefFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRefOrBuilder getChannelRefOrBuilder() {
+      if ((childRefCase_ == 4) && (channelRefBuilder_ != null)) {
+        return channelRefBuilder_.getMessageOrBuilder();
+      } else {
+        if (childRefCase_ == 4) {
+          return (io.grpc.channelz.v1.ChannelRef) childRef_;
+        }
+        return io.grpc.channelz.v1.ChannelRef.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelRef, io.grpc.channelz.v1.ChannelRef.Builder, io.grpc.channelz.v1.ChannelRefOrBuilder> 
+        getChannelRefFieldBuilder() {
+      if (channelRefBuilder_ == null) {
+        if (!(childRefCase_ == 4)) {
+          childRef_ = io.grpc.channelz.v1.ChannelRef.getDefaultInstance();
+        }
+        channelRefBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.ChannelRef, io.grpc.channelz.v1.ChannelRef.Builder, io.grpc.channelz.v1.ChannelRefOrBuilder>(
+                (io.grpc.channelz.v1.ChannelRef) childRef_,
+                getParentForChildren(),
+                isClean());
+        childRef_ = null;
+      }
+      childRefCase_ = 4;
+      onChanged();;
+      return channelRefBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.SubchannelRef, io.grpc.channelz.v1.SubchannelRef.Builder, io.grpc.channelz.v1.SubchannelRefOrBuilder> subchannelRefBuilder_;
+    /**
+     * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+     */
+    public boolean hasSubchannelRef() {
+      return childRefCase_ == 5;
+    }
+    /**
+     * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRef getSubchannelRef() {
+      if (subchannelRefBuilder_ == null) {
+        if (childRefCase_ == 5) {
+          return (io.grpc.channelz.v1.SubchannelRef) childRef_;
+        }
+        return io.grpc.channelz.v1.SubchannelRef.getDefaultInstance();
+      } else {
+        if (childRefCase_ == 5) {
+          return subchannelRefBuilder_.getMessage();
+        }
+        return io.grpc.channelz.v1.SubchannelRef.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+     */
+    public Builder setSubchannelRef(io.grpc.channelz.v1.SubchannelRef value) {
+      if (subchannelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        childRef_ = value;
+        onChanged();
+      } else {
+        subchannelRefBuilder_.setMessage(value);
+      }
+      childRefCase_ = 5;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+     */
+    public Builder setSubchannelRef(
+        io.grpc.channelz.v1.SubchannelRef.Builder builderForValue) {
+      if (subchannelRefBuilder_ == null) {
+        childRef_ = builderForValue.build();
+        onChanged();
+      } else {
+        subchannelRefBuilder_.setMessage(builderForValue.build());
+      }
+      childRefCase_ = 5;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+     */
+    public Builder mergeSubchannelRef(io.grpc.channelz.v1.SubchannelRef value) {
+      if (subchannelRefBuilder_ == null) {
+        if (childRefCase_ == 5 &&
+            childRef_ != io.grpc.channelz.v1.SubchannelRef.getDefaultInstance()) {
+          childRef_ = io.grpc.channelz.v1.SubchannelRef.newBuilder((io.grpc.channelz.v1.SubchannelRef) childRef_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          childRef_ = value;
+        }
+        onChanged();
+      } else {
+        if (childRefCase_ == 5) {
+          subchannelRefBuilder_.mergeFrom(value);
+        }
+        subchannelRefBuilder_.setMessage(value);
+      }
+      childRefCase_ = 5;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+     */
+    public Builder clearSubchannelRef() {
+      if (subchannelRefBuilder_ == null) {
+        if (childRefCase_ == 5) {
+          childRefCase_ = 0;
+          childRef_ = null;
+          onChanged();
+        }
+      } else {
+        if (childRefCase_ == 5) {
+          childRefCase_ = 0;
+          childRef_ = null;
+        }
+        subchannelRefBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRef.Builder getSubchannelRefBuilder() {
+      return getSubchannelRefFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRefOrBuilder getSubchannelRefOrBuilder() {
+      if ((childRefCase_ == 5) && (subchannelRefBuilder_ != null)) {
+        return subchannelRefBuilder_.getMessageOrBuilder();
+      } else {
+        if (childRefCase_ == 5) {
+          return (io.grpc.channelz.v1.SubchannelRef) childRef_;
+        }
+        return io.grpc.channelz.v1.SubchannelRef.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.SubchannelRef, io.grpc.channelz.v1.SubchannelRef.Builder, io.grpc.channelz.v1.SubchannelRefOrBuilder> 
+        getSubchannelRefFieldBuilder() {
+      if (subchannelRefBuilder_ == null) {
+        if (!(childRefCase_ == 5)) {
+          childRef_ = io.grpc.channelz.v1.SubchannelRef.getDefaultInstance();
+        }
+        subchannelRefBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.SubchannelRef, io.grpc.channelz.v1.SubchannelRef.Builder, io.grpc.channelz.v1.SubchannelRefOrBuilder>(
+                (io.grpc.channelz.v1.SubchannelRef) childRef_,
+                getParentForChildren(),
+                isClean());
+        childRef_ = null;
+      }
+      childRefCase_ = 5;
+      onChanged();;
+      return subchannelRefBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.ChannelTraceEvent)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.ChannelTraceEvent)
+  private static final io.grpc.channelz.v1.ChannelTraceEvent DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.ChannelTraceEvent();
+  }
+
+  public static io.grpc.channelz.v1.ChannelTraceEvent getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ChannelTraceEvent>
+      PARSER = new com.google.protobuf.AbstractParser<ChannelTraceEvent>() {
+    public ChannelTraceEvent parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ChannelTraceEvent(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ChannelTraceEvent> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ChannelTraceEvent> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.ChannelTraceEvent getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ChannelTraceEventOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelTraceEventOrBuilder.java
new file mode 100644
index 0000000..c18ba61
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelTraceEventOrBuilder.java
@@ -0,0 +1,97 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface ChannelTraceEventOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.ChannelTraceEvent)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * High level description of the event.
+   * </pre>
+   *
+   * <code>string description = 1;</code>
+   */
+  java.lang.String getDescription();
+  /**
+   * <pre>
+   * High level description of the event.
+   * </pre>
+   *
+   * <code>string description = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getDescriptionBytes();
+
+  /**
+   * <pre>
+   * the severity of the trace event
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTraceEvent.Severity severity = 2;</code>
+   */
+  int getSeverityValue();
+  /**
+   * <pre>
+   * the severity of the trace event
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTraceEvent.Severity severity = 2;</code>
+   */
+  io.grpc.channelz.v1.ChannelTraceEvent.Severity getSeverity();
+
+  /**
+   * <pre>
+   * When this event occurred.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+   */
+  boolean hasTimestamp();
+  /**
+   * <pre>
+   * When this event occurred.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+   */
+  com.google.protobuf.Timestamp getTimestamp();
+  /**
+   * <pre>
+   * When this event occurred.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp timestamp = 3;</code>
+   */
+  com.google.protobuf.TimestampOrBuilder getTimestampOrBuilder();
+
+  /**
+   * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+   */
+  boolean hasChannelRef();
+  /**
+   * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+   */
+  io.grpc.channelz.v1.ChannelRef getChannelRef();
+  /**
+   * <code>.grpc.channelz.v1.ChannelRef channel_ref = 4;</code>
+   */
+  io.grpc.channelz.v1.ChannelRefOrBuilder getChannelRefOrBuilder();
+
+  /**
+   * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+   */
+  boolean hasSubchannelRef();
+  /**
+   * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+   */
+  io.grpc.channelz.v1.SubchannelRef getSubchannelRef();
+  /**
+   * <code>.grpc.channelz.v1.SubchannelRef subchannel_ref = 5;</code>
+   */
+  io.grpc.channelz.v1.SubchannelRefOrBuilder getSubchannelRefOrBuilder();
+
+  public io.grpc.channelz.v1.ChannelTraceEvent.ChildRefCase getChildRefCase();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ChannelTraceOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelTraceOrBuilder.java
new file mode 100644
index 0000000..aabe298
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelTraceOrBuilder.java
@@ -0,0 +1,89 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface ChannelTraceOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.ChannelTrace)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * Number of events ever logged in this tracing object. This can differ from
+   * events.size() because events can be overwritten or garbage collected by
+   * implementations.
+   * </pre>
+   *
+   * <code>int64 num_events_logged = 1;</code>
+   */
+  long getNumEventsLogged();
+
+  /**
+   * <pre>
+   * Time that this channel was created.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+   */
+  boolean hasCreationTimestamp();
+  /**
+   * <pre>
+   * Time that this channel was created.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+   */
+  com.google.protobuf.Timestamp getCreationTimestamp();
+  /**
+   * <pre>
+   * Time that this channel was created.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp creation_timestamp = 2;</code>
+   */
+  com.google.protobuf.TimestampOrBuilder getCreationTimestampOrBuilder();
+
+  /**
+   * <pre>
+   * List of events that have occurred on this channel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+   */
+  java.util.List<io.grpc.channelz.v1.ChannelTraceEvent> 
+      getEventsList();
+  /**
+   * <pre>
+   * List of events that have occurred on this channel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+   */
+  io.grpc.channelz.v1.ChannelTraceEvent getEvents(int index);
+  /**
+   * <pre>
+   * List of events that have occurred on this channel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+   */
+  int getEventsCount();
+  /**
+   * <pre>
+   * List of events that have occurred on this channel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+   */
+  java.util.List<? extends io.grpc.channelz.v1.ChannelTraceEventOrBuilder> 
+      getEventsOrBuilderList();
+  /**
+   * <pre>
+   * List of events that have occurred on this channel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelTraceEvent events = 3;</code>
+   */
+  io.grpc.channelz.v1.ChannelTraceEventOrBuilder getEventsOrBuilder(
+      int index);
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ChannelzProto.java b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelzProto.java
new file mode 100644
index 0000000..1c56589
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ChannelzProto.java
@@ -0,0 +1,614 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public final class ChannelzProto {
+  private ChannelzProto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_Channel_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_Channel_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_Subchannel_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_Subchannel_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_ChannelConnectivityState_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_ChannelConnectivityState_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_ChannelData_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_ChannelData_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_ChannelTraceEvent_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_ChannelTraceEvent_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_ChannelTrace_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_ChannelTrace_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_ChannelRef_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_ChannelRef_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_SubchannelRef_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_SubchannelRef_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_SocketRef_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_SocketRef_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_ServerRef_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_ServerRef_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_Server_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_Server_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_ServerData_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_ServerData_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_Socket_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_Socket_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_SocketData_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_SocketData_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_Address_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_Address_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_Address_TcpIpAddress_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_Address_TcpIpAddress_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_Address_UdsAddress_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_Address_UdsAddress_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_Address_OtherAddress_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_Address_OtherAddress_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_Security_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_Security_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_Security_Tls_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_Security_Tls_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_Security_OtherSecurity_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_Security_OtherSecurity_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_SocketOption_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_SocketOption_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_SocketOptionTimeout_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_SocketOptionTimeout_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_SocketOptionLinger_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_SocketOptionLinger_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_SocketOptionTcpInfo_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_SocketOptionTcpInfo_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_GetTopChannelsRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_GetTopChannelsRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_GetTopChannelsResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_GetTopChannelsResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_GetServersRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_GetServersRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_GetServersResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_GetServersResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_GetServerSocketsRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_GetServerSocketsRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_GetServerSocketsResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_GetServerSocketsResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_GetChannelRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_GetChannelRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_GetChannelResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_GetChannelResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_GetSubchannelRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_GetSubchannelRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_GetSubchannelResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_GetSubchannelResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_GetSocketRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_GetSocketRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_channelz_v1_GetSocketResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_channelz_v1_GetSocketResponse_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\037grpc/channelz/v1/channelz.proto\022\020grpc." +
+      "channelz.v1\032\031google/protobuf/any.proto\032\036" +
+      "google/protobuf/duration.proto\032\037google/p" +
+      "rotobuf/timestamp.proto\032\036google/protobuf" +
+      "/wrappers.proto\"\376\001\n\007Channel\022)\n\003ref\030\001 \001(\013" +
+      "2\034.grpc.channelz.v1.ChannelRef\022+\n\004data\030\002" +
+      " \001(\0132\035.grpc.channelz.v1.ChannelData\0221\n\013c" +
+      "hannel_ref\030\003 \003(\0132\034.grpc.channelz.v1.Chan" +
+      "nelRef\0227\n\016subchannel_ref\030\004 \003(\0132\037.grpc.ch" +
+      "annelz.v1.SubchannelRef\022/\n\nsocket_ref\030\005 " +
+      "\003(\0132\033.grpc.channelz.v1.SocketRef\"\204\002\n\nSub" +
+      "channel\022,\n\003ref\030\001 \001(\0132\037.grpc.channelz.v1." +
+      "SubchannelRef\022+\n\004data\030\002 \001(\0132\035.grpc.chann" +
+      "elz.v1.ChannelData\0221\n\013channel_ref\030\003 \003(\0132" +
+      "\034.grpc.channelz.v1.ChannelRef\0227\n\016subchan" +
+      "nel_ref\030\004 \003(\0132\037.grpc.channelz.v1.Subchan" +
+      "nelRef\022/\n\nsocket_ref\030\005 \003(\0132\033.grpc.channe" +
+      "lz.v1.SocketRef\"\273\001\n\030ChannelConnectivityS" +
+      "tate\022?\n\005state\030\001 \001(\01620.grpc.channelz.v1.C" +
+      "hannelConnectivityState.State\"^\n\005State\022\013" +
+      "\n\007UNKNOWN\020\000\022\010\n\004IDLE\020\001\022\016\n\nCONNECTING\020\002\022\t\n" +
+      "\005READY\020\003\022\025\n\021TRANSIENT_FAILURE\020\004\022\014\n\010SHUTD" +
+      "OWN\020\005\"\216\002\n\013ChannelData\0229\n\005state\030\001 \001(\0132*.g" +
+      "rpc.channelz.v1.ChannelConnectivityState" +
+      "\022\016\n\006target\030\002 \001(\t\022-\n\005trace\030\003 \001(\0132\036.grpc.c" +
+      "hannelz.v1.ChannelTrace\022\025\n\rcalls_started" +
+      "\030\004 \001(\003\022\027\n\017calls_succeeded\030\005 \001(\003\022\024\n\014calls" +
+      "_failed\030\006 \001(\003\022?\n\033last_call_started_times" +
+      "tamp\030\007 \001(\0132\032.google.protobuf.Timestamp\"\333" +
+      "\002\n\021ChannelTraceEvent\022\023\n\013description\030\001 \001(" +
+      "\t\022>\n\010severity\030\002 \001(\0162,.grpc.channelz.v1.C" +
+      "hannelTraceEvent.Severity\022-\n\ttimestamp\030\003" +
+      " \001(\0132\032.google.protobuf.Timestamp\0223\n\013chan" +
+      "nel_ref\030\004 \001(\0132\034.grpc.channelz.v1.Channel" +
+      "RefH\000\0229\n\016subchannel_ref\030\005 \001(\0132\037.grpc.cha" +
+      "nnelz.v1.SubchannelRefH\000\"E\n\010Severity\022\016\n\n" +
+      "CT_UNKNOWN\020\000\022\013\n\007CT_INFO\020\001\022\016\n\nCT_WARNING\020" +
+      "\002\022\014\n\010CT_ERROR\020\003B\013\n\tchild_ref\"\226\001\n\014Channel" +
+      "Trace\022\031\n\021num_events_logged\030\001 \001(\003\0226\n\022crea" +
+      "tion_timestamp\030\002 \001(\0132\032.google.protobuf.T" +
+      "imestamp\0223\n\006events\030\003 \003(\0132#.grpc.channelz" +
+      ".v1.ChannelTraceEvent\"R\n\nChannelRef\022\022\n\nc" +
+      "hannel_id\030\001 \001(\003\022\014\n\004name\030\002 \001(\tJ\004\010\003\020\004J\004\010\004\020" +
+      "\005J\004\010\005\020\006J\004\010\006\020\007J\004\010\007\020\010J\004\010\010\020\t\"X\n\rSubchannelR" +
+      "ef\022\025\n\rsubchannel_id\030\007 \001(\003\022\014\n\004name\030\010 \001(\tJ" +
+      "\004\010\001\020\002J\004\010\002\020\003J\004\010\003\020\004J\004\010\004\020\005J\004\010\005\020\006J\004\010\006\020\007\"P\n\tS" +
+      "ocketRef\022\021\n\tsocket_id\030\003 \001(\003\022\014\n\004name\030\004 \001(" +
+      "\tJ\004\010\001\020\002J\004\010\002\020\003J\004\010\005\020\006J\004\010\006\020\007J\004\010\007\020\010J\004\010\010\020\t\"P\n" +
+      "\tServerRef\022\021\n\tserver_id\030\005 \001(\003\022\014\n\004name\030\006 " +
+      "\001(\tJ\004\010\001\020\002J\004\010\002\020\003J\004\010\003\020\004J\004\010\004\020\005J\004\010\007\020\010J\004\010\010\020\t\"" +
+      "\222\001\n\006Server\022(\n\003ref\030\001 \001(\0132\033.grpc.channelz." +
+      "v1.ServerRef\022*\n\004data\030\002 \001(\0132\034.grpc.channe" +
+      "lz.v1.ServerData\0222\n\rlisten_socket\030\003 \003(\0132" +
+      "\033.grpc.channelz.v1.SocketRef\"\302\001\n\nServerD" +
+      "ata\022-\n\005trace\030\001 \001(\0132\036.grpc.channelz.v1.Ch" +
+      "annelTrace\022\025\n\rcalls_started\030\002 \001(\003\022\027\n\017cal" +
+      "ls_succeeded\030\003 \001(\003\022\024\n\014calls_failed\030\004 \001(\003" +
+      "\022?\n\033last_call_started_timestamp\030\005 \001(\0132\032." +
+      "google.protobuf.Timestamp\"\366\001\n\006Socket\022(\n\003" +
+      "ref\030\001 \001(\0132\033.grpc.channelz.v1.SocketRef\022*" +
+      "\n\004data\030\002 \001(\0132\034.grpc.channelz.v1.SocketDa" +
+      "ta\022(\n\005local\030\003 \001(\0132\031.grpc.channelz.v1.Add" +
+      "ress\022)\n\006remote\030\004 \001(\0132\031.grpc.channelz.v1." +
+      "Address\022,\n\010security\030\005 \001(\0132\032.grpc.channel" +
+      "z.v1.Security\022\023\n\013remote_name\030\006 \001(\t\"\356\004\n\nS" +
+      "ocketData\022\027\n\017streams_started\030\001 \001(\003\022\031\n\021st" +
+      "reams_succeeded\030\002 \001(\003\022\026\n\016streams_failed\030" +
+      "\003 \001(\003\022\025\n\rmessages_sent\030\004 \001(\003\022\031\n\021messages" +
+      "_received\030\005 \001(\003\022\030\n\020keep_alives_sent\030\006 \001(" +
+      "\003\022G\n#last_local_stream_created_timestamp" +
+      "\030\007 \001(\0132\032.google.protobuf.Timestamp\022H\n$la" +
+      "st_remote_stream_created_timestamp\030\010 \001(\013" +
+      "2\032.google.protobuf.Timestamp\022?\n\033last_mes" +
+      "sage_sent_timestamp\030\t \001(\0132\032.google.proto" +
+      "buf.Timestamp\022C\n\037last_message_received_t" +
+      "imestamp\030\n \001(\0132\032.google.protobuf.Timesta" +
+      "mp\022>\n\031local_flow_control_window\030\013 \001(\0132\033." +
+      "google.protobuf.Int64Value\022?\n\032remote_flo" +
+      "w_control_window\030\014 \001(\0132\033.google.protobuf" +
+      ".Int64Value\022.\n\006option\030\r \003(\0132\036.grpc.chann" +
+      "elz.v1.SocketOption\"\350\002\n\007Address\022?\n\rtcpip" +
+      "_address\030\001 \001(\0132&.grpc.channelz.v1.Addres" +
+      "s.TcpIpAddressH\000\022;\n\013uds_address\030\002 \001(\0132$." +
+      "grpc.channelz.v1.Address.UdsAddressH\000\022?\n" +
+      "\rother_address\030\003 \001(\0132&.grpc.channelz.v1." +
+      "Address.OtherAddressH\000\0320\n\014TcpIpAddress\022\022" +
+      "\n\nip_address\030\001 \001(\014\022\014\n\004port\030\002 \001(\005\032\036\n\nUdsA" +
+      "ddress\022\020\n\010filename\030\001 \001(\t\032A\n\014OtherAddress" +
+      "\022\014\n\004name\030\001 \001(\t\022#\n\005value\030\002 \001(\0132\024.google.p" +
+      "rotobuf.AnyB\t\n\007address\"\276\002\n\010Security\022-\n\003t" +
+      "ls\030\001 \001(\0132\036.grpc.channelz.v1.Security.Tls" +
+      "H\000\0229\n\005other\030\002 \001(\0132(.grpc.channelz.v1.Sec" +
+      "urity.OtherSecurityH\000\032{\n\003Tls\022\027\n\rstandard" +
+      "_name\030\001 \001(\tH\000\022\024\n\nother_name\030\002 \001(\tH\000\022\031\n\021l" +
+      "ocal_certificate\030\003 \001(\014\022\032\n\022remote_certifi" +
+      "cate\030\004 \001(\014B\016\n\014cipher_suite\032B\n\rOtherSecur" +
+      "ity\022\014\n\004name\030\001 \001(\t\022#\n\005value\030\002 \001(\0132\024.googl" +
+      "e.protobuf.AnyB\007\n\005model\"U\n\014SocketOption\022" +
+      "\014\n\004name\030\001 \001(\t\022\r\n\005value\030\002 \001(\t\022(\n\naddition" +
+      "al\030\003 \001(\0132\024.google.protobuf.Any\"B\n\023Socket" +
+      "OptionTimeout\022+\n\010duration\030\001 \001(\0132\031.google" +
+      ".protobuf.Duration\"Q\n\022SocketOptionLinger" +
+      "\022\016\n\006active\030\001 \001(\010\022+\n\010duration\030\002 \001(\0132\031.goo" +
+      "gle.protobuf.Duration\"\256\005\n\023SocketOptionTc" +
+      "pInfo\022\022\n\ntcpi_state\030\001 \001(\r\022\025\n\rtcpi_ca_sta" +
+      "te\030\002 \001(\r\022\030\n\020tcpi_retransmits\030\003 \001(\r\022\023\n\013tc" +
+      "pi_probes\030\004 \001(\r\022\024\n\014tcpi_backoff\030\005 \001(\r\022\024\n" +
+      "\014tcpi_options\030\006 \001(\r\022\027\n\017tcpi_snd_wscale\030\007" +
+      " \001(\r\022\027\n\017tcpi_rcv_wscale\030\010 \001(\r\022\020\n\010tcpi_rt" +
+      "o\030\t \001(\r\022\020\n\010tcpi_ato\030\n \001(\r\022\024\n\014tcpi_snd_ms" +
+      "s\030\013 \001(\r\022\024\n\014tcpi_rcv_mss\030\014 \001(\r\022\024\n\014tcpi_un" +
+      "acked\030\r \001(\r\022\023\n\013tcpi_sacked\030\016 \001(\r\022\021\n\ttcpi" +
+      "_lost\030\017 \001(\r\022\024\n\014tcpi_retrans\030\020 \001(\r\022\024\n\014tcp" +
+      "i_fackets\030\021 \001(\r\022\033\n\023tcpi_last_data_sent\030\022" +
+      " \001(\r\022\032\n\022tcpi_last_ack_sent\030\023 \001(\r\022\033\n\023tcpi" +
+      "_last_data_recv\030\024 \001(\r\022\032\n\022tcpi_last_ack_r" +
+      "ecv\030\025 \001(\r\022\021\n\ttcpi_pmtu\030\026 \001(\r\022\031\n\021tcpi_rcv" +
+      "_ssthresh\030\027 \001(\r\022\020\n\010tcpi_rtt\030\030 \001(\r\022\023\n\013tcp" +
+      "i_rttvar\030\031 \001(\r\022\031\n\021tcpi_snd_ssthresh\030\032 \001(" +
+      "\r\022\025\n\rtcpi_snd_cwnd\030\033 \001(\r\022\023\n\013tcpi_advmss\030" +
+      "\034 \001(\r\022\027\n\017tcpi_reordering\030\035 \001(\r\"1\n\025GetTop" +
+      "ChannelsRequest\022\030\n\020start_channel_id\030\001 \001(" +
+      "\003\"Q\n\026GetTopChannelsResponse\022*\n\007channel\030\001" +
+      " \003(\0132\031.grpc.channelz.v1.Channel\022\013\n\003end\030\002" +
+      " \001(\010\",\n\021GetServersRequest\022\027\n\017start_serve" +
+      "r_id\030\001 \001(\003\"K\n\022GetServersResponse\022(\n\006serv" +
+      "er\030\001 \003(\0132\030.grpc.channelz.v1.Server\022\013\n\003en" +
+      "d\030\002 \001(\010\"E\n\027GetServerSocketsRequest\022\021\n\tse" +
+      "rver_id\030\001 \001(\003\022\027\n\017start_socket_id\030\002 \001(\003\"X" +
+      "\n\030GetServerSocketsResponse\022/\n\nsocket_ref" +
+      "\030\001 \003(\0132\033.grpc.channelz.v1.SocketRef\022\013\n\003e" +
+      "nd\030\002 \001(\010\"\'\n\021GetChannelRequest\022\022\n\nchannel" +
+      "_id\030\001 \001(\003\"@\n\022GetChannelResponse\022*\n\007chann" +
+      "el\030\001 \001(\0132\031.grpc.channelz.v1.Channel\"-\n\024G" +
+      "etSubchannelRequest\022\025\n\rsubchannel_id\030\001 \001" +
+      "(\003\"I\n\025GetSubchannelResponse\0220\n\nsubchanne" +
+      "l\030\001 \001(\0132\034.grpc.channelz.v1.Subchannel\"%\n" +
+      "\020GetSocketRequest\022\021\n\tsocket_id\030\001 \001(\003\"=\n\021" +
+      "GetSocketResponse\022(\n\006socket\030\001 \001(\0132\030.grpc" +
+      ".channelz.v1.Socket2\304\004\n\010Channelz\022c\n\016GetT" +
+      "opChannels\022\'.grpc.channelz.v1.GetTopChan" +
+      "nelsRequest\032(.grpc.channelz.v1.GetTopCha" +
+      "nnelsResponse\022W\n\nGetServers\022#.grpc.chann" +
+      "elz.v1.GetServersRequest\032$.grpc.channelz" +
+      ".v1.GetServersResponse\022i\n\020GetServerSocke" +
+      "ts\022).grpc.channelz.v1.GetServerSocketsRe" +
+      "quest\032*.grpc.channelz.v1.GetServerSocket" +
+      "sResponse\022W\n\nGetChannel\022#.grpc.channelz." +
+      "v1.GetChannelRequest\032$.grpc.channelz.v1." +
+      "GetChannelResponse\022`\n\rGetSubchannel\022&.gr" +
+      "pc.channelz.v1.GetSubchannelRequest\032\'.gr" +
+      "pc.channelz.v1.GetSubchannelResponse\022T\n\t" +
+      "GetSocket\022\".grpc.channelz.v1.GetSocketRe" +
+      "quest\032#.grpc.channelz.v1.GetSocketRespon" +
+      "seBX\n\023io.grpc.channelz.v1B\rChannelzProto" +
+      "P\001Z0google.golang.org/grpc/channelz/grpc" +
+      "_channelz_v1b\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          com.google.protobuf.AnyProto.getDescriptor(),
+          com.google.protobuf.DurationProto.getDescriptor(),
+          com.google.protobuf.TimestampProto.getDescriptor(),
+          com.google.protobuf.WrappersProto.getDescriptor(),
+        }, assigner);
+    internal_static_grpc_channelz_v1_Channel_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_channelz_v1_Channel_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_Channel_descriptor,
+        new java.lang.String[] { "Ref", "Data", "ChannelRef", "SubchannelRef", "SocketRef", });
+    internal_static_grpc_channelz_v1_Subchannel_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_channelz_v1_Subchannel_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_Subchannel_descriptor,
+        new java.lang.String[] { "Ref", "Data", "ChannelRef", "SubchannelRef", "SocketRef", });
+    internal_static_grpc_channelz_v1_ChannelConnectivityState_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_channelz_v1_ChannelConnectivityState_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_ChannelConnectivityState_descriptor,
+        new java.lang.String[] { "State", });
+    internal_static_grpc_channelz_v1_ChannelData_descriptor =
+      getDescriptor().getMessageTypes().get(3);
+    internal_static_grpc_channelz_v1_ChannelData_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_ChannelData_descriptor,
+        new java.lang.String[] { "State", "Target", "Trace", "CallsStarted", "CallsSucceeded", "CallsFailed", "LastCallStartedTimestamp", });
+    internal_static_grpc_channelz_v1_ChannelTraceEvent_descriptor =
+      getDescriptor().getMessageTypes().get(4);
+    internal_static_grpc_channelz_v1_ChannelTraceEvent_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_ChannelTraceEvent_descriptor,
+        new java.lang.String[] { "Description", "Severity", "Timestamp", "ChannelRef", "SubchannelRef", "ChildRef", });
+    internal_static_grpc_channelz_v1_ChannelTrace_descriptor =
+      getDescriptor().getMessageTypes().get(5);
+    internal_static_grpc_channelz_v1_ChannelTrace_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_ChannelTrace_descriptor,
+        new java.lang.String[] { "NumEventsLogged", "CreationTimestamp", "Events", });
+    internal_static_grpc_channelz_v1_ChannelRef_descriptor =
+      getDescriptor().getMessageTypes().get(6);
+    internal_static_grpc_channelz_v1_ChannelRef_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_ChannelRef_descriptor,
+        new java.lang.String[] { "ChannelId", "Name", });
+    internal_static_grpc_channelz_v1_SubchannelRef_descriptor =
+      getDescriptor().getMessageTypes().get(7);
+    internal_static_grpc_channelz_v1_SubchannelRef_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_SubchannelRef_descriptor,
+        new java.lang.String[] { "SubchannelId", "Name", });
+    internal_static_grpc_channelz_v1_SocketRef_descriptor =
+      getDescriptor().getMessageTypes().get(8);
+    internal_static_grpc_channelz_v1_SocketRef_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_SocketRef_descriptor,
+        new java.lang.String[] { "SocketId", "Name", });
+    internal_static_grpc_channelz_v1_ServerRef_descriptor =
+      getDescriptor().getMessageTypes().get(9);
+    internal_static_grpc_channelz_v1_ServerRef_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_ServerRef_descriptor,
+        new java.lang.String[] { "ServerId", "Name", });
+    internal_static_grpc_channelz_v1_Server_descriptor =
+      getDescriptor().getMessageTypes().get(10);
+    internal_static_grpc_channelz_v1_Server_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_Server_descriptor,
+        new java.lang.String[] { "Ref", "Data", "ListenSocket", });
+    internal_static_grpc_channelz_v1_ServerData_descriptor =
+      getDescriptor().getMessageTypes().get(11);
+    internal_static_grpc_channelz_v1_ServerData_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_ServerData_descriptor,
+        new java.lang.String[] { "Trace", "CallsStarted", "CallsSucceeded", "CallsFailed", "LastCallStartedTimestamp", });
+    internal_static_grpc_channelz_v1_Socket_descriptor =
+      getDescriptor().getMessageTypes().get(12);
+    internal_static_grpc_channelz_v1_Socket_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_Socket_descriptor,
+        new java.lang.String[] { "Ref", "Data", "Local", "Remote", "Security", "RemoteName", });
+    internal_static_grpc_channelz_v1_SocketData_descriptor =
+      getDescriptor().getMessageTypes().get(13);
+    internal_static_grpc_channelz_v1_SocketData_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_SocketData_descriptor,
+        new java.lang.String[] { "StreamsStarted", "StreamsSucceeded", "StreamsFailed", "MessagesSent", "MessagesReceived", "KeepAlivesSent", "LastLocalStreamCreatedTimestamp", "LastRemoteStreamCreatedTimestamp", "LastMessageSentTimestamp", "LastMessageReceivedTimestamp", "LocalFlowControlWindow", "RemoteFlowControlWindow", "Option", });
+    internal_static_grpc_channelz_v1_Address_descriptor =
+      getDescriptor().getMessageTypes().get(14);
+    internal_static_grpc_channelz_v1_Address_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_Address_descriptor,
+        new java.lang.String[] { "TcpipAddress", "UdsAddress", "OtherAddress", "Address", });
+    internal_static_grpc_channelz_v1_Address_TcpIpAddress_descriptor =
+      internal_static_grpc_channelz_v1_Address_descriptor.getNestedTypes().get(0);
+    internal_static_grpc_channelz_v1_Address_TcpIpAddress_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_Address_TcpIpAddress_descriptor,
+        new java.lang.String[] { "IpAddress", "Port", });
+    internal_static_grpc_channelz_v1_Address_UdsAddress_descriptor =
+      internal_static_grpc_channelz_v1_Address_descriptor.getNestedTypes().get(1);
+    internal_static_grpc_channelz_v1_Address_UdsAddress_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_Address_UdsAddress_descriptor,
+        new java.lang.String[] { "Filename", });
+    internal_static_grpc_channelz_v1_Address_OtherAddress_descriptor =
+      internal_static_grpc_channelz_v1_Address_descriptor.getNestedTypes().get(2);
+    internal_static_grpc_channelz_v1_Address_OtherAddress_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_Address_OtherAddress_descriptor,
+        new java.lang.String[] { "Name", "Value", });
+    internal_static_grpc_channelz_v1_Security_descriptor =
+      getDescriptor().getMessageTypes().get(15);
+    internal_static_grpc_channelz_v1_Security_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_Security_descriptor,
+        new java.lang.String[] { "Tls", "Other", "Model", });
+    internal_static_grpc_channelz_v1_Security_Tls_descriptor =
+      internal_static_grpc_channelz_v1_Security_descriptor.getNestedTypes().get(0);
+    internal_static_grpc_channelz_v1_Security_Tls_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_Security_Tls_descriptor,
+        new java.lang.String[] { "StandardName", "OtherName", "LocalCertificate", "RemoteCertificate", "CipherSuite", });
+    internal_static_grpc_channelz_v1_Security_OtherSecurity_descriptor =
+      internal_static_grpc_channelz_v1_Security_descriptor.getNestedTypes().get(1);
+    internal_static_grpc_channelz_v1_Security_OtherSecurity_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_Security_OtherSecurity_descriptor,
+        new java.lang.String[] { "Name", "Value", });
+    internal_static_grpc_channelz_v1_SocketOption_descriptor =
+      getDescriptor().getMessageTypes().get(16);
+    internal_static_grpc_channelz_v1_SocketOption_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_SocketOption_descriptor,
+        new java.lang.String[] { "Name", "Value", "Additional", });
+    internal_static_grpc_channelz_v1_SocketOptionTimeout_descriptor =
+      getDescriptor().getMessageTypes().get(17);
+    internal_static_grpc_channelz_v1_SocketOptionTimeout_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_SocketOptionTimeout_descriptor,
+        new java.lang.String[] { "Duration", });
+    internal_static_grpc_channelz_v1_SocketOptionLinger_descriptor =
+      getDescriptor().getMessageTypes().get(18);
+    internal_static_grpc_channelz_v1_SocketOptionLinger_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_SocketOptionLinger_descriptor,
+        new java.lang.String[] { "Active", "Duration", });
+    internal_static_grpc_channelz_v1_SocketOptionTcpInfo_descriptor =
+      getDescriptor().getMessageTypes().get(19);
+    internal_static_grpc_channelz_v1_SocketOptionTcpInfo_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_SocketOptionTcpInfo_descriptor,
+        new java.lang.String[] { "TcpiState", "TcpiCaState", "TcpiRetransmits", "TcpiProbes", "TcpiBackoff", "TcpiOptions", "TcpiSndWscale", "TcpiRcvWscale", "TcpiRto", "TcpiAto", "TcpiSndMss", "TcpiRcvMss", "TcpiUnacked", "TcpiSacked", "TcpiLost", "TcpiRetrans", "TcpiFackets", "TcpiLastDataSent", "TcpiLastAckSent", "TcpiLastDataRecv", "TcpiLastAckRecv", "TcpiPmtu", "TcpiRcvSsthresh", "TcpiRtt", "TcpiRttvar", "TcpiSndSsthresh", "TcpiSndCwnd", "TcpiAdvmss", "TcpiReordering", });
+    internal_static_grpc_channelz_v1_GetTopChannelsRequest_descriptor =
+      getDescriptor().getMessageTypes().get(20);
+    internal_static_grpc_channelz_v1_GetTopChannelsRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_GetTopChannelsRequest_descriptor,
+        new java.lang.String[] { "StartChannelId", });
+    internal_static_grpc_channelz_v1_GetTopChannelsResponse_descriptor =
+      getDescriptor().getMessageTypes().get(21);
+    internal_static_grpc_channelz_v1_GetTopChannelsResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_GetTopChannelsResponse_descriptor,
+        new java.lang.String[] { "Channel", "End", });
+    internal_static_grpc_channelz_v1_GetServersRequest_descriptor =
+      getDescriptor().getMessageTypes().get(22);
+    internal_static_grpc_channelz_v1_GetServersRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_GetServersRequest_descriptor,
+        new java.lang.String[] { "StartServerId", });
+    internal_static_grpc_channelz_v1_GetServersResponse_descriptor =
+      getDescriptor().getMessageTypes().get(23);
+    internal_static_grpc_channelz_v1_GetServersResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_GetServersResponse_descriptor,
+        new java.lang.String[] { "Server", "End", });
+    internal_static_grpc_channelz_v1_GetServerSocketsRequest_descriptor =
+      getDescriptor().getMessageTypes().get(24);
+    internal_static_grpc_channelz_v1_GetServerSocketsRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_GetServerSocketsRequest_descriptor,
+        new java.lang.String[] { "ServerId", "StartSocketId", });
+    internal_static_grpc_channelz_v1_GetServerSocketsResponse_descriptor =
+      getDescriptor().getMessageTypes().get(25);
+    internal_static_grpc_channelz_v1_GetServerSocketsResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_GetServerSocketsResponse_descriptor,
+        new java.lang.String[] { "SocketRef", "End", });
+    internal_static_grpc_channelz_v1_GetChannelRequest_descriptor =
+      getDescriptor().getMessageTypes().get(26);
+    internal_static_grpc_channelz_v1_GetChannelRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_GetChannelRequest_descriptor,
+        new java.lang.String[] { "ChannelId", });
+    internal_static_grpc_channelz_v1_GetChannelResponse_descriptor =
+      getDescriptor().getMessageTypes().get(27);
+    internal_static_grpc_channelz_v1_GetChannelResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_GetChannelResponse_descriptor,
+        new java.lang.String[] { "Channel", });
+    internal_static_grpc_channelz_v1_GetSubchannelRequest_descriptor =
+      getDescriptor().getMessageTypes().get(28);
+    internal_static_grpc_channelz_v1_GetSubchannelRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_GetSubchannelRequest_descriptor,
+        new java.lang.String[] { "SubchannelId", });
+    internal_static_grpc_channelz_v1_GetSubchannelResponse_descriptor =
+      getDescriptor().getMessageTypes().get(29);
+    internal_static_grpc_channelz_v1_GetSubchannelResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_GetSubchannelResponse_descriptor,
+        new java.lang.String[] { "Subchannel", });
+    internal_static_grpc_channelz_v1_GetSocketRequest_descriptor =
+      getDescriptor().getMessageTypes().get(30);
+    internal_static_grpc_channelz_v1_GetSocketRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_GetSocketRequest_descriptor,
+        new java.lang.String[] { "SocketId", });
+    internal_static_grpc_channelz_v1_GetSocketResponse_descriptor =
+      getDescriptor().getMessageTypes().get(31);
+    internal_static_grpc_channelz_v1_GetSocketResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_channelz_v1_GetSocketResponse_descriptor,
+        new java.lang.String[] { "Socket", });
+    com.google.protobuf.AnyProto.getDescriptor();
+    com.google.protobuf.DurationProto.getDescriptor();
+    com.google.protobuf.TimestampProto.getDescriptor();
+    com.google.protobuf.WrappersProto.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetChannelRequest.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetChannelRequest.java
new file mode 100644
index 0000000..0aa4c1f
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetChannelRequest.java
@@ -0,0 +1,464 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * Protobuf type {@code grpc.channelz.v1.GetChannelRequest}
+ */
+public  final class GetChannelRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.GetChannelRequest)
+    GetChannelRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetChannelRequest.newBuilder() to construct.
+  private GetChannelRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetChannelRequest() {
+    channelId_ = 0L;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetChannelRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+
+            channelId_ = input.readInt64();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetChannelRequest_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetChannelRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.GetChannelRequest.class, io.grpc.channelz.v1.GetChannelRequest.Builder.class);
+  }
+
+  public static final int CHANNEL_ID_FIELD_NUMBER = 1;
+  private long channelId_;
+  /**
+   * <pre>
+   * channel_id is the the identifier of the specific channel to get.
+   * </pre>
+   *
+   * <code>int64 channel_id = 1;</code>
+   */
+  public long getChannelId() {
+    return channelId_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (channelId_ != 0L) {
+      output.writeInt64(1, channelId_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (channelId_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(1, channelId_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.GetChannelRequest)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.GetChannelRequest other = (io.grpc.channelz.v1.GetChannelRequest) obj;
+
+    boolean result = true;
+    result = result && (getChannelId()
+        == other.getChannelId());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + CHANNEL_ID_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getChannelId());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.GetChannelRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetChannelRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetChannelRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetChannelRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetChannelRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetChannelRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetChannelRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetChannelRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetChannelRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetChannelRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetChannelRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetChannelRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.GetChannelRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.GetChannelRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.GetChannelRequest)
+      io.grpc.channelz.v1.GetChannelRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetChannelRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetChannelRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.GetChannelRequest.class, io.grpc.channelz.v1.GetChannelRequest.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.GetChannelRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      channelId_ = 0L;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetChannelRequest_descriptor;
+    }
+
+    public io.grpc.channelz.v1.GetChannelRequest getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.GetChannelRequest.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.GetChannelRequest build() {
+      io.grpc.channelz.v1.GetChannelRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.GetChannelRequest buildPartial() {
+      io.grpc.channelz.v1.GetChannelRequest result = new io.grpc.channelz.v1.GetChannelRequest(this);
+      result.channelId_ = channelId_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.GetChannelRequest) {
+        return mergeFrom((io.grpc.channelz.v1.GetChannelRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.GetChannelRequest other) {
+      if (other == io.grpc.channelz.v1.GetChannelRequest.getDefaultInstance()) return this;
+      if (other.getChannelId() != 0L) {
+        setChannelId(other.getChannelId());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.GetChannelRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.GetChannelRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private long channelId_ ;
+    /**
+     * <pre>
+     * channel_id is the the identifier of the specific channel to get.
+     * </pre>
+     *
+     * <code>int64 channel_id = 1;</code>
+     */
+    public long getChannelId() {
+      return channelId_;
+    }
+    /**
+     * <pre>
+     * channel_id is the the identifier of the specific channel to get.
+     * </pre>
+     *
+     * <code>int64 channel_id = 1;</code>
+     */
+    public Builder setChannelId(long value) {
+      
+      channelId_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * channel_id is the the identifier of the specific channel to get.
+     * </pre>
+     *
+     * <code>int64 channel_id = 1;</code>
+     */
+    public Builder clearChannelId() {
+      
+      channelId_ = 0L;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.GetChannelRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.GetChannelRequest)
+  private static final io.grpc.channelz.v1.GetChannelRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.GetChannelRequest();
+  }
+
+  public static io.grpc.channelz.v1.GetChannelRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetChannelRequest>
+      PARSER = new com.google.protobuf.AbstractParser<GetChannelRequest>() {
+    public GetChannelRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetChannelRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetChannelRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetChannelRequest> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.GetChannelRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetChannelRequestOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetChannelRequestOrBuilder.java
new file mode 100644
index 0000000..dbb7a9c
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetChannelRequestOrBuilder.java
@@ -0,0 +1,18 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface GetChannelRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.GetChannelRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * channel_id is the the identifier of the specific channel to get.
+   * </pre>
+   *
+   * <code>int64 channel_id = 1;</code>
+   */
+  long getChannelId();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetChannelResponse.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetChannelResponse.java
new file mode 100644
index 0000000..a477c8d
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetChannelResponse.java
@@ -0,0 +1,630 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * Protobuf type {@code grpc.channelz.v1.GetChannelResponse}
+ */
+public  final class GetChannelResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.GetChannelResponse)
+    GetChannelResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetChannelResponse.newBuilder() to construct.
+  private GetChannelResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetChannelResponse() {
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetChannelResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            io.grpc.channelz.v1.Channel.Builder subBuilder = null;
+            if (channel_ != null) {
+              subBuilder = channel_.toBuilder();
+            }
+            channel_ = input.readMessage(io.grpc.channelz.v1.Channel.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(channel_);
+              channel_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetChannelResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetChannelResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.GetChannelResponse.class, io.grpc.channelz.v1.GetChannelResponse.Builder.class);
+  }
+
+  public static final int CHANNEL_FIELD_NUMBER = 1;
+  private io.grpc.channelz.v1.Channel channel_;
+  /**
+   * <pre>
+   * The Channel that corresponds to the requested channel_id.  This field
+   * should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  public boolean hasChannel() {
+    return channel_ != null;
+  }
+  /**
+   * <pre>
+   * The Channel that corresponds to the requested channel_id.  This field
+   * should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  public io.grpc.channelz.v1.Channel getChannel() {
+    return channel_ == null ? io.grpc.channelz.v1.Channel.getDefaultInstance() : channel_;
+  }
+  /**
+   * <pre>
+   * The Channel that corresponds to the requested channel_id.  This field
+   * should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  public io.grpc.channelz.v1.ChannelOrBuilder getChannelOrBuilder() {
+    return getChannel();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (channel_ != null) {
+      output.writeMessage(1, getChannel());
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (channel_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getChannel());
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.GetChannelResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.GetChannelResponse other = (io.grpc.channelz.v1.GetChannelResponse) obj;
+
+    boolean result = true;
+    result = result && (hasChannel() == other.hasChannel());
+    if (hasChannel()) {
+      result = result && getChannel()
+          .equals(other.getChannel());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasChannel()) {
+      hash = (37 * hash) + CHANNEL_FIELD_NUMBER;
+      hash = (53 * hash) + getChannel().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.GetChannelResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetChannelResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetChannelResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetChannelResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetChannelResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetChannelResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetChannelResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetChannelResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetChannelResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetChannelResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetChannelResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetChannelResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.GetChannelResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.GetChannelResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.GetChannelResponse)
+      io.grpc.channelz.v1.GetChannelResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetChannelResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetChannelResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.GetChannelResponse.class, io.grpc.channelz.v1.GetChannelResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.GetChannelResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (channelBuilder_ == null) {
+        channel_ = null;
+      } else {
+        channel_ = null;
+        channelBuilder_ = null;
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetChannelResponse_descriptor;
+    }
+
+    public io.grpc.channelz.v1.GetChannelResponse getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.GetChannelResponse.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.GetChannelResponse build() {
+      io.grpc.channelz.v1.GetChannelResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.GetChannelResponse buildPartial() {
+      io.grpc.channelz.v1.GetChannelResponse result = new io.grpc.channelz.v1.GetChannelResponse(this);
+      if (channelBuilder_ == null) {
+        result.channel_ = channel_;
+      } else {
+        result.channel_ = channelBuilder_.build();
+      }
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.GetChannelResponse) {
+        return mergeFrom((io.grpc.channelz.v1.GetChannelResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.GetChannelResponse other) {
+      if (other == io.grpc.channelz.v1.GetChannelResponse.getDefaultInstance()) return this;
+      if (other.hasChannel()) {
+        mergeChannel(other.getChannel());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.GetChannelResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.GetChannelResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private io.grpc.channelz.v1.Channel channel_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Channel, io.grpc.channelz.v1.Channel.Builder, io.grpc.channelz.v1.ChannelOrBuilder> channelBuilder_;
+    /**
+     * <pre>
+     * The Channel that corresponds to the requested channel_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public boolean hasChannel() {
+      return channelBuilder_ != null || channel_ != null;
+    }
+    /**
+     * <pre>
+     * The Channel that corresponds to the requested channel_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public io.grpc.channelz.v1.Channel getChannel() {
+      if (channelBuilder_ == null) {
+        return channel_ == null ? io.grpc.channelz.v1.Channel.getDefaultInstance() : channel_;
+      } else {
+        return channelBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The Channel that corresponds to the requested channel_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public Builder setChannel(io.grpc.channelz.v1.Channel value) {
+      if (channelBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        channel_ = value;
+        onChanged();
+      } else {
+        channelBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The Channel that corresponds to the requested channel_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public Builder setChannel(
+        io.grpc.channelz.v1.Channel.Builder builderForValue) {
+      if (channelBuilder_ == null) {
+        channel_ = builderForValue.build();
+        onChanged();
+      } else {
+        channelBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The Channel that corresponds to the requested channel_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public Builder mergeChannel(io.grpc.channelz.v1.Channel value) {
+      if (channelBuilder_ == null) {
+        if (channel_ != null) {
+          channel_ =
+            io.grpc.channelz.v1.Channel.newBuilder(channel_).mergeFrom(value).buildPartial();
+        } else {
+          channel_ = value;
+        }
+        onChanged();
+      } else {
+        channelBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The Channel that corresponds to the requested channel_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public Builder clearChannel() {
+      if (channelBuilder_ == null) {
+        channel_ = null;
+        onChanged();
+      } else {
+        channel_ = null;
+        channelBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The Channel that corresponds to the requested channel_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public io.grpc.channelz.v1.Channel.Builder getChannelBuilder() {
+      
+      onChanged();
+      return getChannelFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The Channel that corresponds to the requested channel_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public io.grpc.channelz.v1.ChannelOrBuilder getChannelOrBuilder() {
+      if (channelBuilder_ != null) {
+        return channelBuilder_.getMessageOrBuilder();
+      } else {
+        return channel_ == null ?
+            io.grpc.channelz.v1.Channel.getDefaultInstance() : channel_;
+      }
+    }
+    /**
+     * <pre>
+     * The Channel that corresponds to the requested channel_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Channel, io.grpc.channelz.v1.Channel.Builder, io.grpc.channelz.v1.ChannelOrBuilder> 
+        getChannelFieldBuilder() {
+      if (channelBuilder_ == null) {
+        channelBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.Channel, io.grpc.channelz.v1.Channel.Builder, io.grpc.channelz.v1.ChannelOrBuilder>(
+                getChannel(),
+                getParentForChildren(),
+                isClean());
+        channel_ = null;
+      }
+      return channelBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.GetChannelResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.GetChannelResponse)
+  private static final io.grpc.channelz.v1.GetChannelResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.GetChannelResponse();
+  }
+
+  public static io.grpc.channelz.v1.GetChannelResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetChannelResponse>
+      PARSER = new com.google.protobuf.AbstractParser<GetChannelResponse>() {
+    public GetChannelResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetChannelResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetChannelResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetChannelResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.GetChannelResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetChannelResponseOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetChannelResponseOrBuilder.java
new file mode 100644
index 0000000..50a8dab
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetChannelResponseOrBuilder.java
@@ -0,0 +1,37 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface GetChannelResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.GetChannelResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The Channel that corresponds to the requested channel_id.  This field
+   * should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  boolean hasChannel();
+  /**
+   * <pre>
+   * The Channel that corresponds to the requested channel_id.  This field
+   * should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  io.grpc.channelz.v1.Channel getChannel();
+  /**
+   * <pre>
+   * The Channel that corresponds to the requested channel_id.  This field
+   * should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  io.grpc.channelz.v1.ChannelOrBuilder getChannelOrBuilder();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetServerSocketsRequest.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetServerSocketsRequest.java
new file mode 100644
index 0000000..7707b82
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetServerSocketsRequest.java
@@ -0,0 +1,527 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * Protobuf type {@code grpc.channelz.v1.GetServerSocketsRequest}
+ */
+public  final class GetServerSocketsRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.GetServerSocketsRequest)
+    GetServerSocketsRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetServerSocketsRequest.newBuilder() to construct.
+  private GetServerSocketsRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetServerSocketsRequest() {
+    serverId_ = 0L;
+    startSocketId_ = 0L;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetServerSocketsRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+
+            serverId_ = input.readInt64();
+            break;
+          }
+          case 16: {
+
+            startSocketId_ = input.readInt64();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServerSocketsRequest_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServerSocketsRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.GetServerSocketsRequest.class, io.grpc.channelz.v1.GetServerSocketsRequest.Builder.class);
+  }
+
+  public static final int SERVER_ID_FIELD_NUMBER = 1;
+  private long serverId_;
+  /**
+   * <code>int64 server_id = 1;</code>
+   */
+  public long getServerId() {
+    return serverId_;
+  }
+
+  public static final int START_SOCKET_ID_FIELD_NUMBER = 2;
+  private long startSocketId_;
+  /**
+   * <pre>
+   * start_socket_id indicates that only sockets at or above this id should be
+   * included in the results.
+   * </pre>
+   *
+   * <code>int64 start_socket_id = 2;</code>
+   */
+  public long getStartSocketId() {
+    return startSocketId_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (serverId_ != 0L) {
+      output.writeInt64(1, serverId_);
+    }
+    if (startSocketId_ != 0L) {
+      output.writeInt64(2, startSocketId_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (serverId_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(1, serverId_);
+    }
+    if (startSocketId_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(2, startSocketId_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.GetServerSocketsRequest)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.GetServerSocketsRequest other = (io.grpc.channelz.v1.GetServerSocketsRequest) obj;
+
+    boolean result = true;
+    result = result && (getServerId()
+        == other.getServerId());
+    result = result && (getStartSocketId()
+        == other.getStartSocketId());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + SERVER_ID_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getServerId());
+    hash = (37 * hash) + START_SOCKET_ID_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getStartSocketId());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.GetServerSocketsRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.GetServerSocketsRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.GetServerSocketsRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.GetServerSocketsRequest)
+      io.grpc.channelz.v1.GetServerSocketsRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServerSocketsRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServerSocketsRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.GetServerSocketsRequest.class, io.grpc.channelz.v1.GetServerSocketsRequest.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.GetServerSocketsRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      serverId_ = 0L;
+
+      startSocketId_ = 0L;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServerSocketsRequest_descriptor;
+    }
+
+    public io.grpc.channelz.v1.GetServerSocketsRequest getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.GetServerSocketsRequest.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.GetServerSocketsRequest build() {
+      io.grpc.channelz.v1.GetServerSocketsRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.GetServerSocketsRequest buildPartial() {
+      io.grpc.channelz.v1.GetServerSocketsRequest result = new io.grpc.channelz.v1.GetServerSocketsRequest(this);
+      result.serverId_ = serverId_;
+      result.startSocketId_ = startSocketId_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.GetServerSocketsRequest) {
+        return mergeFrom((io.grpc.channelz.v1.GetServerSocketsRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.GetServerSocketsRequest other) {
+      if (other == io.grpc.channelz.v1.GetServerSocketsRequest.getDefaultInstance()) return this;
+      if (other.getServerId() != 0L) {
+        setServerId(other.getServerId());
+      }
+      if (other.getStartSocketId() != 0L) {
+        setStartSocketId(other.getStartSocketId());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.GetServerSocketsRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.GetServerSocketsRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private long serverId_ ;
+    /**
+     * <code>int64 server_id = 1;</code>
+     */
+    public long getServerId() {
+      return serverId_;
+    }
+    /**
+     * <code>int64 server_id = 1;</code>
+     */
+    public Builder setServerId(long value) {
+      
+      serverId_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>int64 server_id = 1;</code>
+     */
+    public Builder clearServerId() {
+      
+      serverId_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long startSocketId_ ;
+    /**
+     * <pre>
+     * start_socket_id indicates that only sockets at or above this id should be
+     * included in the results.
+     * </pre>
+     *
+     * <code>int64 start_socket_id = 2;</code>
+     */
+    public long getStartSocketId() {
+      return startSocketId_;
+    }
+    /**
+     * <pre>
+     * start_socket_id indicates that only sockets at or above this id should be
+     * included in the results.
+     * </pre>
+     *
+     * <code>int64 start_socket_id = 2;</code>
+     */
+    public Builder setStartSocketId(long value) {
+      
+      startSocketId_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * start_socket_id indicates that only sockets at or above this id should be
+     * included in the results.
+     * </pre>
+     *
+     * <code>int64 start_socket_id = 2;</code>
+     */
+    public Builder clearStartSocketId() {
+      
+      startSocketId_ = 0L;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.GetServerSocketsRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.GetServerSocketsRequest)
+  private static final io.grpc.channelz.v1.GetServerSocketsRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.GetServerSocketsRequest();
+  }
+
+  public static io.grpc.channelz.v1.GetServerSocketsRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetServerSocketsRequest>
+      PARSER = new com.google.protobuf.AbstractParser<GetServerSocketsRequest>() {
+    public GetServerSocketsRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetServerSocketsRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetServerSocketsRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetServerSocketsRequest> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.GetServerSocketsRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetServerSocketsRequestOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetServerSocketsRequestOrBuilder.java
new file mode 100644
index 0000000..8ff1d50
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetServerSocketsRequestOrBuilder.java
@@ -0,0 +1,24 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface GetServerSocketsRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.GetServerSocketsRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>int64 server_id = 1;</code>
+   */
+  long getServerId();
+
+  /**
+   * <pre>
+   * start_socket_id indicates that only sockets at or above this id should be
+   * included in the results.
+   * </pre>
+   *
+   * <code>int64 start_socket_id = 2;</code>
+   */
+  long getStartSocketId();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetServerSocketsResponse.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetServerSocketsResponse.java
new file mode 100644
index 0000000..2f7df8e
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetServerSocketsResponse.java
@@ -0,0 +1,935 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * Protobuf type {@code grpc.channelz.v1.GetServerSocketsResponse}
+ */
+public  final class GetServerSocketsResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.GetServerSocketsResponse)
+    GetServerSocketsResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetServerSocketsResponse.newBuilder() to construct.
+  private GetServerSocketsResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetServerSocketsResponse() {
+    socketRef_ = java.util.Collections.emptyList();
+    end_ = false;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetServerSocketsResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+              socketRef_ = new java.util.ArrayList<io.grpc.channelz.v1.SocketRef>();
+              mutable_bitField0_ |= 0x00000001;
+            }
+            socketRef_.add(
+                input.readMessage(io.grpc.channelz.v1.SocketRef.parser(), extensionRegistry));
+            break;
+          }
+          case 16: {
+
+            end_ = input.readBool();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+        socketRef_ = java.util.Collections.unmodifiableList(socketRef_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServerSocketsResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServerSocketsResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.GetServerSocketsResponse.class, io.grpc.channelz.v1.GetServerSocketsResponse.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int SOCKET_REF_FIELD_NUMBER = 1;
+  private java.util.List<io.grpc.channelz.v1.SocketRef> socketRef_;
+  /**
+   * <pre>
+   * list of socket refs that the connection detail service knows about.  Sorted in
+   * ascending socket_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+   */
+  public java.util.List<io.grpc.channelz.v1.SocketRef> getSocketRefList() {
+    return socketRef_;
+  }
+  /**
+   * <pre>
+   * list of socket refs that the connection detail service knows about.  Sorted in
+   * ascending socket_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+   */
+  public java.util.List<? extends io.grpc.channelz.v1.SocketRefOrBuilder> 
+      getSocketRefOrBuilderList() {
+    return socketRef_;
+  }
+  /**
+   * <pre>
+   * list of socket refs that the connection detail service knows about.  Sorted in
+   * ascending socket_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+   */
+  public int getSocketRefCount() {
+    return socketRef_.size();
+  }
+  /**
+   * <pre>
+   * list of socket refs that the connection detail service knows about.  Sorted in
+   * ascending socket_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+   */
+  public io.grpc.channelz.v1.SocketRef getSocketRef(int index) {
+    return socketRef_.get(index);
+  }
+  /**
+   * <pre>
+   * list of socket refs that the connection detail service knows about.  Sorted in
+   * ascending socket_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+   */
+  public io.grpc.channelz.v1.SocketRefOrBuilder getSocketRefOrBuilder(
+      int index) {
+    return socketRef_.get(index);
+  }
+
+  public static final int END_FIELD_NUMBER = 2;
+  private boolean end_;
+  /**
+   * <pre>
+   * If set, indicates that the list of sockets is the final list.  Requesting
+   * more sockets will only return more if they are created after this RPC
+   * completes.
+   * </pre>
+   *
+   * <code>bool end = 2;</code>
+   */
+  public boolean getEnd() {
+    return end_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    for (int i = 0; i < socketRef_.size(); i++) {
+      output.writeMessage(1, socketRef_.get(i));
+    }
+    if (end_ != false) {
+      output.writeBool(2, end_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    for (int i = 0; i < socketRef_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, socketRef_.get(i));
+    }
+    if (end_ != false) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBoolSize(2, end_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.GetServerSocketsResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.GetServerSocketsResponse other = (io.grpc.channelz.v1.GetServerSocketsResponse) obj;
+
+    boolean result = true;
+    result = result && getSocketRefList()
+        .equals(other.getSocketRefList());
+    result = result && (getEnd()
+        == other.getEnd());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (getSocketRefCount() > 0) {
+      hash = (37 * hash) + SOCKET_REF_FIELD_NUMBER;
+      hash = (53 * hash) + getSocketRefList().hashCode();
+    }
+    hash = (37 * hash) + END_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+        getEnd());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.GetServerSocketsResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetServerSocketsResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.GetServerSocketsResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.GetServerSocketsResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.GetServerSocketsResponse)
+      io.grpc.channelz.v1.GetServerSocketsResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServerSocketsResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServerSocketsResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.GetServerSocketsResponse.class, io.grpc.channelz.v1.GetServerSocketsResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.GetServerSocketsResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+        getSocketRefFieldBuilder();
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (socketRefBuilder_ == null) {
+        socketRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+      } else {
+        socketRefBuilder_.clear();
+      }
+      end_ = false;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServerSocketsResponse_descriptor;
+    }
+
+    public io.grpc.channelz.v1.GetServerSocketsResponse getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.GetServerSocketsResponse.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.GetServerSocketsResponse build() {
+      io.grpc.channelz.v1.GetServerSocketsResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.GetServerSocketsResponse buildPartial() {
+      io.grpc.channelz.v1.GetServerSocketsResponse result = new io.grpc.channelz.v1.GetServerSocketsResponse(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      if (socketRefBuilder_ == null) {
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          socketRef_ = java.util.Collections.unmodifiableList(socketRef_);
+          bitField0_ = (bitField0_ & ~0x00000001);
+        }
+        result.socketRef_ = socketRef_;
+      } else {
+        result.socketRef_ = socketRefBuilder_.build();
+      }
+      result.end_ = end_;
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.GetServerSocketsResponse) {
+        return mergeFrom((io.grpc.channelz.v1.GetServerSocketsResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.GetServerSocketsResponse other) {
+      if (other == io.grpc.channelz.v1.GetServerSocketsResponse.getDefaultInstance()) return this;
+      if (socketRefBuilder_ == null) {
+        if (!other.socketRef_.isEmpty()) {
+          if (socketRef_.isEmpty()) {
+            socketRef_ = other.socketRef_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+          } else {
+            ensureSocketRefIsMutable();
+            socketRef_.addAll(other.socketRef_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.socketRef_.isEmpty()) {
+          if (socketRefBuilder_.isEmpty()) {
+            socketRefBuilder_.dispose();
+            socketRefBuilder_ = null;
+            socketRef_ = other.socketRef_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+            socketRefBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getSocketRefFieldBuilder() : null;
+          } else {
+            socketRefBuilder_.addAllMessages(other.socketRef_);
+          }
+        }
+      }
+      if (other.getEnd() != false) {
+        setEnd(other.getEnd());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.GetServerSocketsResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.GetServerSocketsResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.util.List<io.grpc.channelz.v1.SocketRef> socketRef_ =
+      java.util.Collections.emptyList();
+    private void ensureSocketRefIsMutable() {
+      if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+        socketRef_ = new java.util.ArrayList<io.grpc.channelz.v1.SocketRef>(socketRef_);
+        bitField0_ |= 0x00000001;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder> socketRefBuilder_;
+
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SocketRef> getSocketRefList() {
+      if (socketRefBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(socketRef_);
+      } else {
+        return socketRefBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public int getSocketRefCount() {
+      if (socketRefBuilder_ == null) {
+        return socketRef_.size();
+      } else {
+        return socketRefBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef getSocketRef(int index) {
+      if (socketRefBuilder_ == null) {
+        return socketRef_.get(index);
+      } else {
+        return socketRefBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public Builder setSocketRef(
+        int index, io.grpc.channelz.v1.SocketRef value) {
+      if (socketRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSocketRefIsMutable();
+        socketRef_.set(index, value);
+        onChanged();
+      } else {
+        socketRefBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public Builder setSocketRef(
+        int index, io.grpc.channelz.v1.SocketRef.Builder builderForValue) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        socketRef_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        socketRefBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public Builder addSocketRef(io.grpc.channelz.v1.SocketRef value) {
+      if (socketRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSocketRefIsMutable();
+        socketRef_.add(value);
+        onChanged();
+      } else {
+        socketRefBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public Builder addSocketRef(
+        int index, io.grpc.channelz.v1.SocketRef value) {
+      if (socketRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSocketRefIsMutable();
+        socketRef_.add(index, value);
+        onChanged();
+      } else {
+        socketRefBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public Builder addSocketRef(
+        io.grpc.channelz.v1.SocketRef.Builder builderForValue) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        socketRef_.add(builderForValue.build());
+        onChanged();
+      } else {
+        socketRefBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public Builder addSocketRef(
+        int index, io.grpc.channelz.v1.SocketRef.Builder builderForValue) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        socketRef_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        socketRefBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public Builder addAllSocketRef(
+        java.lang.Iterable<? extends io.grpc.channelz.v1.SocketRef> values) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, socketRef_);
+        onChanged();
+      } else {
+        socketRefBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public Builder clearSocketRef() {
+      if (socketRefBuilder_ == null) {
+        socketRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+        onChanged();
+      } else {
+        socketRefBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public Builder removeSocketRef(int index) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        socketRef_.remove(index);
+        onChanged();
+      } else {
+        socketRefBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef.Builder getSocketRefBuilder(
+        int index) {
+      return getSocketRefFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.SocketRefOrBuilder getSocketRefOrBuilder(
+        int index) {
+      if (socketRefBuilder_ == null) {
+        return socketRef_.get(index);  } else {
+        return socketRefBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public java.util.List<? extends io.grpc.channelz.v1.SocketRefOrBuilder> 
+         getSocketRefOrBuilderList() {
+      if (socketRefBuilder_ != null) {
+        return socketRefBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(socketRef_);
+      }
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef.Builder addSocketRefBuilder() {
+      return getSocketRefFieldBuilder().addBuilder(
+          io.grpc.channelz.v1.SocketRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef.Builder addSocketRefBuilder(
+        int index) {
+      return getSocketRefFieldBuilder().addBuilder(
+          index, io.grpc.channelz.v1.SocketRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * list of socket refs that the connection detail service knows about.  Sorted in
+     * ascending socket_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SocketRef.Builder> 
+         getSocketRefBuilderList() {
+      return getSocketRefFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder> 
+        getSocketRefFieldBuilder() {
+      if (socketRefBuilder_ == null) {
+        socketRefBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder>(
+                socketRef_,
+                ((bitField0_ & 0x00000001) == 0x00000001),
+                getParentForChildren(),
+                isClean());
+        socketRef_ = null;
+      }
+      return socketRefBuilder_;
+    }
+
+    private boolean end_ ;
+    /**
+     * <pre>
+     * If set, indicates that the list of sockets is the final list.  Requesting
+     * more sockets will only return more if they are created after this RPC
+     * completes.
+     * </pre>
+     *
+     * <code>bool end = 2;</code>
+     */
+    public boolean getEnd() {
+      return end_;
+    }
+    /**
+     * <pre>
+     * If set, indicates that the list of sockets is the final list.  Requesting
+     * more sockets will only return more if they are created after this RPC
+     * completes.
+     * </pre>
+     *
+     * <code>bool end = 2;</code>
+     */
+    public Builder setEnd(boolean value) {
+      
+      end_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * If set, indicates that the list of sockets is the final list.  Requesting
+     * more sockets will only return more if they are created after this RPC
+     * completes.
+     * </pre>
+     *
+     * <code>bool end = 2;</code>
+     */
+    public Builder clearEnd() {
+      
+      end_ = false;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.GetServerSocketsResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.GetServerSocketsResponse)
+  private static final io.grpc.channelz.v1.GetServerSocketsResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.GetServerSocketsResponse();
+  }
+
+  public static io.grpc.channelz.v1.GetServerSocketsResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetServerSocketsResponse>
+      PARSER = new com.google.protobuf.AbstractParser<GetServerSocketsResponse>() {
+    public GetServerSocketsResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetServerSocketsResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetServerSocketsResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetServerSocketsResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.GetServerSocketsResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetServerSocketsResponseOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetServerSocketsResponseOrBuilder.java
new file mode 100644
index 0000000..8eba7d9
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetServerSocketsResponseOrBuilder.java
@@ -0,0 +1,69 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface GetServerSocketsResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.GetServerSocketsResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * list of socket refs that the connection detail service knows about.  Sorted in
+   * ascending socket_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+   */
+  java.util.List<io.grpc.channelz.v1.SocketRef> 
+      getSocketRefList();
+  /**
+   * <pre>
+   * list of socket refs that the connection detail service knows about.  Sorted in
+   * ascending socket_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+   */
+  io.grpc.channelz.v1.SocketRef getSocketRef(int index);
+  /**
+   * <pre>
+   * list of socket refs that the connection detail service knows about.  Sorted in
+   * ascending socket_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+   */
+  int getSocketRefCount();
+  /**
+   * <pre>
+   * list of socket refs that the connection detail service knows about.  Sorted in
+   * ascending socket_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+   */
+  java.util.List<? extends io.grpc.channelz.v1.SocketRefOrBuilder> 
+      getSocketRefOrBuilderList();
+  /**
+   * <pre>
+   * list of socket refs that the connection detail service knows about.  Sorted in
+   * ascending socket_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 1;</code>
+   */
+  io.grpc.channelz.v1.SocketRefOrBuilder getSocketRefOrBuilder(
+      int index);
+
+  /**
+   * <pre>
+   * If set, indicates that the list of sockets is the final list.  Requesting
+   * more sockets will only return more if they are created after this RPC
+   * completes.
+   * </pre>
+   *
+   * <code>bool end = 2;</code>
+   */
+  boolean getEnd();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetServersRequest.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetServersRequest.java
new file mode 100644
index 0000000..32e70df
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetServersRequest.java
@@ -0,0 +1,468 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * Protobuf type {@code grpc.channelz.v1.GetServersRequest}
+ */
+public  final class GetServersRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.GetServersRequest)
+    GetServersRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetServersRequest.newBuilder() to construct.
+  private GetServersRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetServersRequest() {
+    startServerId_ = 0L;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetServersRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+
+            startServerId_ = input.readInt64();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServersRequest_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServersRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.GetServersRequest.class, io.grpc.channelz.v1.GetServersRequest.Builder.class);
+  }
+
+  public static final int START_SERVER_ID_FIELD_NUMBER = 1;
+  private long startServerId_;
+  /**
+   * <pre>
+   * start_server_id indicates that only servers at or above this id should be
+   * included in the results.
+   * </pre>
+   *
+   * <code>int64 start_server_id = 1;</code>
+   */
+  public long getStartServerId() {
+    return startServerId_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (startServerId_ != 0L) {
+      output.writeInt64(1, startServerId_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (startServerId_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(1, startServerId_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.GetServersRequest)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.GetServersRequest other = (io.grpc.channelz.v1.GetServersRequest) obj;
+
+    boolean result = true;
+    result = result && (getStartServerId()
+        == other.getStartServerId());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + START_SERVER_ID_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getStartServerId());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.GetServersRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetServersRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServersRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetServersRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServersRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetServersRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServersRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetServersRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServersRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetServersRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServersRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetServersRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.GetServersRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.GetServersRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.GetServersRequest)
+      io.grpc.channelz.v1.GetServersRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServersRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServersRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.GetServersRequest.class, io.grpc.channelz.v1.GetServersRequest.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.GetServersRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      startServerId_ = 0L;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServersRequest_descriptor;
+    }
+
+    public io.grpc.channelz.v1.GetServersRequest getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.GetServersRequest.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.GetServersRequest build() {
+      io.grpc.channelz.v1.GetServersRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.GetServersRequest buildPartial() {
+      io.grpc.channelz.v1.GetServersRequest result = new io.grpc.channelz.v1.GetServersRequest(this);
+      result.startServerId_ = startServerId_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.GetServersRequest) {
+        return mergeFrom((io.grpc.channelz.v1.GetServersRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.GetServersRequest other) {
+      if (other == io.grpc.channelz.v1.GetServersRequest.getDefaultInstance()) return this;
+      if (other.getStartServerId() != 0L) {
+        setStartServerId(other.getStartServerId());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.GetServersRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.GetServersRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private long startServerId_ ;
+    /**
+     * <pre>
+     * start_server_id indicates that only servers at or above this id should be
+     * included in the results.
+     * </pre>
+     *
+     * <code>int64 start_server_id = 1;</code>
+     */
+    public long getStartServerId() {
+      return startServerId_;
+    }
+    /**
+     * <pre>
+     * start_server_id indicates that only servers at or above this id should be
+     * included in the results.
+     * </pre>
+     *
+     * <code>int64 start_server_id = 1;</code>
+     */
+    public Builder setStartServerId(long value) {
+      
+      startServerId_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * start_server_id indicates that only servers at or above this id should be
+     * included in the results.
+     * </pre>
+     *
+     * <code>int64 start_server_id = 1;</code>
+     */
+    public Builder clearStartServerId() {
+      
+      startServerId_ = 0L;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.GetServersRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.GetServersRequest)
+  private static final io.grpc.channelz.v1.GetServersRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.GetServersRequest();
+  }
+
+  public static io.grpc.channelz.v1.GetServersRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetServersRequest>
+      PARSER = new com.google.protobuf.AbstractParser<GetServersRequest>() {
+    public GetServersRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetServersRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetServersRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetServersRequest> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.GetServersRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetServersRequestOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetServersRequestOrBuilder.java
new file mode 100644
index 0000000..c215762
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetServersRequestOrBuilder.java
@@ -0,0 +1,19 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface GetServersRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.GetServersRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * start_server_id indicates that only servers at or above this id should be
+   * included in the results.
+   * </pre>
+   *
+   * <code>int64 start_server_id = 1;</code>
+   */
+  long getStartServerId();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetServersResponse.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetServersResponse.java
new file mode 100644
index 0000000..dde0f16
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetServersResponse.java
@@ -0,0 +1,935 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * Protobuf type {@code grpc.channelz.v1.GetServersResponse}
+ */
+public  final class GetServersResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.GetServersResponse)
+    GetServersResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetServersResponse.newBuilder() to construct.
+  private GetServersResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetServersResponse() {
+    server_ = java.util.Collections.emptyList();
+    end_ = false;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetServersResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+              server_ = new java.util.ArrayList<io.grpc.channelz.v1.Server>();
+              mutable_bitField0_ |= 0x00000001;
+            }
+            server_.add(
+                input.readMessage(io.grpc.channelz.v1.Server.parser(), extensionRegistry));
+            break;
+          }
+          case 16: {
+
+            end_ = input.readBool();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+        server_ = java.util.Collections.unmodifiableList(server_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServersResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServersResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.GetServersResponse.class, io.grpc.channelz.v1.GetServersResponse.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int SERVER_FIELD_NUMBER = 1;
+  private java.util.List<io.grpc.channelz.v1.Server> server_;
+  /**
+   * <pre>
+   * list of servers that the connection detail service knows about.  Sorted in
+   * ascending server_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+   */
+  public java.util.List<io.grpc.channelz.v1.Server> getServerList() {
+    return server_;
+  }
+  /**
+   * <pre>
+   * list of servers that the connection detail service knows about.  Sorted in
+   * ascending server_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+   */
+  public java.util.List<? extends io.grpc.channelz.v1.ServerOrBuilder> 
+      getServerOrBuilderList() {
+    return server_;
+  }
+  /**
+   * <pre>
+   * list of servers that the connection detail service knows about.  Sorted in
+   * ascending server_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+   */
+  public int getServerCount() {
+    return server_.size();
+  }
+  /**
+   * <pre>
+   * list of servers that the connection detail service knows about.  Sorted in
+   * ascending server_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+   */
+  public io.grpc.channelz.v1.Server getServer(int index) {
+    return server_.get(index);
+  }
+  /**
+   * <pre>
+   * list of servers that the connection detail service knows about.  Sorted in
+   * ascending server_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+   */
+  public io.grpc.channelz.v1.ServerOrBuilder getServerOrBuilder(
+      int index) {
+    return server_.get(index);
+  }
+
+  public static final int END_FIELD_NUMBER = 2;
+  private boolean end_;
+  /**
+   * <pre>
+   * If set, indicates that the list of servers is the final list.  Requesting
+   * more servers will only return more if they are created after this RPC
+   * completes.
+   * </pre>
+   *
+   * <code>bool end = 2;</code>
+   */
+  public boolean getEnd() {
+    return end_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    for (int i = 0; i < server_.size(); i++) {
+      output.writeMessage(1, server_.get(i));
+    }
+    if (end_ != false) {
+      output.writeBool(2, end_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    for (int i = 0; i < server_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, server_.get(i));
+    }
+    if (end_ != false) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBoolSize(2, end_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.GetServersResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.GetServersResponse other = (io.grpc.channelz.v1.GetServersResponse) obj;
+
+    boolean result = true;
+    result = result && getServerList()
+        .equals(other.getServerList());
+    result = result && (getEnd()
+        == other.getEnd());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (getServerCount() > 0) {
+      hash = (37 * hash) + SERVER_FIELD_NUMBER;
+      hash = (53 * hash) + getServerList().hashCode();
+    }
+    hash = (37 * hash) + END_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+        getEnd());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.GetServersResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetServersResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServersResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetServersResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServersResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetServersResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServersResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetServersResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServersResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetServersResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetServersResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetServersResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.GetServersResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.GetServersResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.GetServersResponse)
+      io.grpc.channelz.v1.GetServersResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServersResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServersResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.GetServersResponse.class, io.grpc.channelz.v1.GetServersResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.GetServersResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+        getServerFieldBuilder();
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (serverBuilder_ == null) {
+        server_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+      } else {
+        serverBuilder_.clear();
+      }
+      end_ = false;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetServersResponse_descriptor;
+    }
+
+    public io.grpc.channelz.v1.GetServersResponse getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.GetServersResponse.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.GetServersResponse build() {
+      io.grpc.channelz.v1.GetServersResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.GetServersResponse buildPartial() {
+      io.grpc.channelz.v1.GetServersResponse result = new io.grpc.channelz.v1.GetServersResponse(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      if (serverBuilder_ == null) {
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          server_ = java.util.Collections.unmodifiableList(server_);
+          bitField0_ = (bitField0_ & ~0x00000001);
+        }
+        result.server_ = server_;
+      } else {
+        result.server_ = serverBuilder_.build();
+      }
+      result.end_ = end_;
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.GetServersResponse) {
+        return mergeFrom((io.grpc.channelz.v1.GetServersResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.GetServersResponse other) {
+      if (other == io.grpc.channelz.v1.GetServersResponse.getDefaultInstance()) return this;
+      if (serverBuilder_ == null) {
+        if (!other.server_.isEmpty()) {
+          if (server_.isEmpty()) {
+            server_ = other.server_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+          } else {
+            ensureServerIsMutable();
+            server_.addAll(other.server_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.server_.isEmpty()) {
+          if (serverBuilder_.isEmpty()) {
+            serverBuilder_.dispose();
+            serverBuilder_ = null;
+            server_ = other.server_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+            serverBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getServerFieldBuilder() : null;
+          } else {
+            serverBuilder_.addAllMessages(other.server_);
+          }
+        }
+      }
+      if (other.getEnd() != false) {
+        setEnd(other.getEnd());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.GetServersResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.GetServersResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.util.List<io.grpc.channelz.v1.Server> server_ =
+      java.util.Collections.emptyList();
+    private void ensureServerIsMutable() {
+      if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+        server_ = new java.util.ArrayList<io.grpc.channelz.v1.Server>(server_);
+        bitField0_ |= 0x00000001;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.Server, io.grpc.channelz.v1.Server.Builder, io.grpc.channelz.v1.ServerOrBuilder> serverBuilder_;
+
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.Server> getServerList() {
+      if (serverBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(server_);
+      } else {
+        return serverBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public int getServerCount() {
+      if (serverBuilder_ == null) {
+        return server_.size();
+      } else {
+        return serverBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public io.grpc.channelz.v1.Server getServer(int index) {
+      if (serverBuilder_ == null) {
+        return server_.get(index);
+      } else {
+        return serverBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public Builder setServer(
+        int index, io.grpc.channelz.v1.Server value) {
+      if (serverBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureServerIsMutable();
+        server_.set(index, value);
+        onChanged();
+      } else {
+        serverBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public Builder setServer(
+        int index, io.grpc.channelz.v1.Server.Builder builderForValue) {
+      if (serverBuilder_ == null) {
+        ensureServerIsMutable();
+        server_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        serverBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public Builder addServer(io.grpc.channelz.v1.Server value) {
+      if (serverBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureServerIsMutable();
+        server_.add(value);
+        onChanged();
+      } else {
+        serverBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public Builder addServer(
+        int index, io.grpc.channelz.v1.Server value) {
+      if (serverBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureServerIsMutable();
+        server_.add(index, value);
+        onChanged();
+      } else {
+        serverBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public Builder addServer(
+        io.grpc.channelz.v1.Server.Builder builderForValue) {
+      if (serverBuilder_ == null) {
+        ensureServerIsMutable();
+        server_.add(builderForValue.build());
+        onChanged();
+      } else {
+        serverBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public Builder addServer(
+        int index, io.grpc.channelz.v1.Server.Builder builderForValue) {
+      if (serverBuilder_ == null) {
+        ensureServerIsMutable();
+        server_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        serverBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public Builder addAllServer(
+        java.lang.Iterable<? extends io.grpc.channelz.v1.Server> values) {
+      if (serverBuilder_ == null) {
+        ensureServerIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, server_);
+        onChanged();
+      } else {
+        serverBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public Builder clearServer() {
+      if (serverBuilder_ == null) {
+        server_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+        onChanged();
+      } else {
+        serverBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public Builder removeServer(int index) {
+      if (serverBuilder_ == null) {
+        ensureServerIsMutable();
+        server_.remove(index);
+        onChanged();
+      } else {
+        serverBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public io.grpc.channelz.v1.Server.Builder getServerBuilder(
+        int index) {
+      return getServerFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public io.grpc.channelz.v1.ServerOrBuilder getServerOrBuilder(
+        int index) {
+      if (serverBuilder_ == null) {
+        return server_.get(index);  } else {
+        return serverBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public java.util.List<? extends io.grpc.channelz.v1.ServerOrBuilder> 
+         getServerOrBuilderList() {
+      if (serverBuilder_ != null) {
+        return serverBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(server_);
+      }
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public io.grpc.channelz.v1.Server.Builder addServerBuilder() {
+      return getServerFieldBuilder().addBuilder(
+          io.grpc.channelz.v1.Server.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public io.grpc.channelz.v1.Server.Builder addServerBuilder(
+        int index) {
+      return getServerFieldBuilder().addBuilder(
+          index, io.grpc.channelz.v1.Server.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * list of servers that the connection detail service knows about.  Sorted in
+     * ascending server_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.Server.Builder> 
+         getServerBuilderList() {
+      return getServerFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.Server, io.grpc.channelz.v1.Server.Builder, io.grpc.channelz.v1.ServerOrBuilder> 
+        getServerFieldBuilder() {
+      if (serverBuilder_ == null) {
+        serverBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.channelz.v1.Server, io.grpc.channelz.v1.Server.Builder, io.grpc.channelz.v1.ServerOrBuilder>(
+                server_,
+                ((bitField0_ & 0x00000001) == 0x00000001),
+                getParentForChildren(),
+                isClean());
+        server_ = null;
+      }
+      return serverBuilder_;
+    }
+
+    private boolean end_ ;
+    /**
+     * <pre>
+     * If set, indicates that the list of servers is the final list.  Requesting
+     * more servers will only return more if they are created after this RPC
+     * completes.
+     * </pre>
+     *
+     * <code>bool end = 2;</code>
+     */
+    public boolean getEnd() {
+      return end_;
+    }
+    /**
+     * <pre>
+     * If set, indicates that the list of servers is the final list.  Requesting
+     * more servers will only return more if they are created after this RPC
+     * completes.
+     * </pre>
+     *
+     * <code>bool end = 2;</code>
+     */
+    public Builder setEnd(boolean value) {
+      
+      end_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * If set, indicates that the list of servers is the final list.  Requesting
+     * more servers will only return more if they are created after this RPC
+     * completes.
+     * </pre>
+     *
+     * <code>bool end = 2;</code>
+     */
+    public Builder clearEnd() {
+      
+      end_ = false;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.GetServersResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.GetServersResponse)
+  private static final io.grpc.channelz.v1.GetServersResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.GetServersResponse();
+  }
+
+  public static io.grpc.channelz.v1.GetServersResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetServersResponse>
+      PARSER = new com.google.protobuf.AbstractParser<GetServersResponse>() {
+    public GetServersResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetServersResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetServersResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetServersResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.GetServersResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetServersResponseOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetServersResponseOrBuilder.java
new file mode 100644
index 0000000..7aeb36c
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetServersResponseOrBuilder.java
@@ -0,0 +1,69 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface GetServersResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.GetServersResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * list of servers that the connection detail service knows about.  Sorted in
+   * ascending server_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+   */
+  java.util.List<io.grpc.channelz.v1.Server> 
+      getServerList();
+  /**
+   * <pre>
+   * list of servers that the connection detail service knows about.  Sorted in
+   * ascending server_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+   */
+  io.grpc.channelz.v1.Server getServer(int index);
+  /**
+   * <pre>
+   * list of servers that the connection detail service knows about.  Sorted in
+   * ascending server_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+   */
+  int getServerCount();
+  /**
+   * <pre>
+   * list of servers that the connection detail service knows about.  Sorted in
+   * ascending server_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+   */
+  java.util.List<? extends io.grpc.channelz.v1.ServerOrBuilder> 
+      getServerOrBuilderList();
+  /**
+   * <pre>
+   * list of servers that the connection detail service knows about.  Sorted in
+   * ascending server_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Server server = 1;</code>
+   */
+  io.grpc.channelz.v1.ServerOrBuilder getServerOrBuilder(
+      int index);
+
+  /**
+   * <pre>
+   * If set, indicates that the list of servers is the final list.  Requesting
+   * more servers will only return more if they are created after this RPC
+   * completes.
+   * </pre>
+   *
+   * <code>bool end = 2;</code>
+   */
+  boolean getEnd();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetSocketRequest.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetSocketRequest.java
new file mode 100644
index 0000000..b370a45
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetSocketRequest.java
@@ -0,0 +1,464 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * Protobuf type {@code grpc.channelz.v1.GetSocketRequest}
+ */
+public  final class GetSocketRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.GetSocketRequest)
+    GetSocketRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetSocketRequest.newBuilder() to construct.
+  private GetSocketRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetSocketRequest() {
+    socketId_ = 0L;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetSocketRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+
+            socketId_ = input.readInt64();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSocketRequest_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSocketRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.GetSocketRequest.class, io.grpc.channelz.v1.GetSocketRequest.Builder.class);
+  }
+
+  public static final int SOCKET_ID_FIELD_NUMBER = 1;
+  private long socketId_;
+  /**
+   * <pre>
+   * socket_id is the the identifier of the specific socket to get.
+   * </pre>
+   *
+   * <code>int64 socket_id = 1;</code>
+   */
+  public long getSocketId() {
+    return socketId_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (socketId_ != 0L) {
+      output.writeInt64(1, socketId_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (socketId_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(1, socketId_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.GetSocketRequest)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.GetSocketRequest other = (io.grpc.channelz.v1.GetSocketRequest) obj;
+
+    boolean result = true;
+    result = result && (getSocketId()
+        == other.getSocketId());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + SOCKET_ID_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getSocketId());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.GetSocketRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetSocketRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSocketRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetSocketRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSocketRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetSocketRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSocketRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetSocketRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSocketRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetSocketRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSocketRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetSocketRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.GetSocketRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.GetSocketRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.GetSocketRequest)
+      io.grpc.channelz.v1.GetSocketRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSocketRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSocketRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.GetSocketRequest.class, io.grpc.channelz.v1.GetSocketRequest.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.GetSocketRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      socketId_ = 0L;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSocketRequest_descriptor;
+    }
+
+    public io.grpc.channelz.v1.GetSocketRequest getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.GetSocketRequest.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.GetSocketRequest build() {
+      io.grpc.channelz.v1.GetSocketRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.GetSocketRequest buildPartial() {
+      io.grpc.channelz.v1.GetSocketRequest result = new io.grpc.channelz.v1.GetSocketRequest(this);
+      result.socketId_ = socketId_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.GetSocketRequest) {
+        return mergeFrom((io.grpc.channelz.v1.GetSocketRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.GetSocketRequest other) {
+      if (other == io.grpc.channelz.v1.GetSocketRequest.getDefaultInstance()) return this;
+      if (other.getSocketId() != 0L) {
+        setSocketId(other.getSocketId());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.GetSocketRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.GetSocketRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private long socketId_ ;
+    /**
+     * <pre>
+     * socket_id is the the identifier of the specific socket to get.
+     * </pre>
+     *
+     * <code>int64 socket_id = 1;</code>
+     */
+    public long getSocketId() {
+      return socketId_;
+    }
+    /**
+     * <pre>
+     * socket_id is the the identifier of the specific socket to get.
+     * </pre>
+     *
+     * <code>int64 socket_id = 1;</code>
+     */
+    public Builder setSocketId(long value) {
+      
+      socketId_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * socket_id is the the identifier of the specific socket to get.
+     * </pre>
+     *
+     * <code>int64 socket_id = 1;</code>
+     */
+    public Builder clearSocketId() {
+      
+      socketId_ = 0L;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.GetSocketRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.GetSocketRequest)
+  private static final io.grpc.channelz.v1.GetSocketRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.GetSocketRequest();
+  }
+
+  public static io.grpc.channelz.v1.GetSocketRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetSocketRequest>
+      PARSER = new com.google.protobuf.AbstractParser<GetSocketRequest>() {
+    public GetSocketRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetSocketRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetSocketRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetSocketRequest> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.GetSocketRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetSocketRequestOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetSocketRequestOrBuilder.java
new file mode 100644
index 0000000..ea5deb9
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetSocketRequestOrBuilder.java
@@ -0,0 +1,18 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface GetSocketRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.GetSocketRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * socket_id is the the identifier of the specific socket to get.
+   * </pre>
+   *
+   * <code>int64 socket_id = 1;</code>
+   */
+  long getSocketId();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetSocketResponse.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetSocketResponse.java
new file mode 100644
index 0000000..c8efba8
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetSocketResponse.java
@@ -0,0 +1,630 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * Protobuf type {@code grpc.channelz.v1.GetSocketResponse}
+ */
+public  final class GetSocketResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.GetSocketResponse)
+    GetSocketResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetSocketResponse.newBuilder() to construct.
+  private GetSocketResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetSocketResponse() {
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetSocketResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            io.grpc.channelz.v1.Socket.Builder subBuilder = null;
+            if (socket_ != null) {
+              subBuilder = socket_.toBuilder();
+            }
+            socket_ = input.readMessage(io.grpc.channelz.v1.Socket.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(socket_);
+              socket_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSocketResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSocketResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.GetSocketResponse.class, io.grpc.channelz.v1.GetSocketResponse.Builder.class);
+  }
+
+  public static final int SOCKET_FIELD_NUMBER = 1;
+  private io.grpc.channelz.v1.Socket socket_;
+  /**
+   * <pre>
+   * The Socket that corresponds to the requested socket_id.  This field
+   * should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+   */
+  public boolean hasSocket() {
+    return socket_ != null;
+  }
+  /**
+   * <pre>
+   * The Socket that corresponds to the requested socket_id.  This field
+   * should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+   */
+  public io.grpc.channelz.v1.Socket getSocket() {
+    return socket_ == null ? io.grpc.channelz.v1.Socket.getDefaultInstance() : socket_;
+  }
+  /**
+   * <pre>
+   * The Socket that corresponds to the requested socket_id.  This field
+   * should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+   */
+  public io.grpc.channelz.v1.SocketOrBuilder getSocketOrBuilder() {
+    return getSocket();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (socket_ != null) {
+      output.writeMessage(1, getSocket());
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (socket_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getSocket());
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.GetSocketResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.GetSocketResponse other = (io.grpc.channelz.v1.GetSocketResponse) obj;
+
+    boolean result = true;
+    result = result && (hasSocket() == other.hasSocket());
+    if (hasSocket()) {
+      result = result && getSocket()
+          .equals(other.getSocket());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasSocket()) {
+      hash = (37 * hash) + SOCKET_FIELD_NUMBER;
+      hash = (53 * hash) + getSocket().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.GetSocketResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetSocketResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSocketResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetSocketResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSocketResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetSocketResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSocketResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetSocketResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSocketResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetSocketResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSocketResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetSocketResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.GetSocketResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.GetSocketResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.GetSocketResponse)
+      io.grpc.channelz.v1.GetSocketResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSocketResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSocketResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.GetSocketResponse.class, io.grpc.channelz.v1.GetSocketResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.GetSocketResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (socketBuilder_ == null) {
+        socket_ = null;
+      } else {
+        socket_ = null;
+        socketBuilder_ = null;
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSocketResponse_descriptor;
+    }
+
+    public io.grpc.channelz.v1.GetSocketResponse getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.GetSocketResponse.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.GetSocketResponse build() {
+      io.grpc.channelz.v1.GetSocketResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.GetSocketResponse buildPartial() {
+      io.grpc.channelz.v1.GetSocketResponse result = new io.grpc.channelz.v1.GetSocketResponse(this);
+      if (socketBuilder_ == null) {
+        result.socket_ = socket_;
+      } else {
+        result.socket_ = socketBuilder_.build();
+      }
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.GetSocketResponse) {
+        return mergeFrom((io.grpc.channelz.v1.GetSocketResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.GetSocketResponse other) {
+      if (other == io.grpc.channelz.v1.GetSocketResponse.getDefaultInstance()) return this;
+      if (other.hasSocket()) {
+        mergeSocket(other.getSocket());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.GetSocketResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.GetSocketResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private io.grpc.channelz.v1.Socket socket_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Socket, io.grpc.channelz.v1.Socket.Builder, io.grpc.channelz.v1.SocketOrBuilder> socketBuilder_;
+    /**
+     * <pre>
+     * The Socket that corresponds to the requested socket_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+     */
+    public boolean hasSocket() {
+      return socketBuilder_ != null || socket_ != null;
+    }
+    /**
+     * <pre>
+     * The Socket that corresponds to the requested socket_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+     */
+    public io.grpc.channelz.v1.Socket getSocket() {
+      if (socketBuilder_ == null) {
+        return socket_ == null ? io.grpc.channelz.v1.Socket.getDefaultInstance() : socket_;
+      } else {
+        return socketBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The Socket that corresponds to the requested socket_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+     */
+    public Builder setSocket(io.grpc.channelz.v1.Socket value) {
+      if (socketBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        socket_ = value;
+        onChanged();
+      } else {
+        socketBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The Socket that corresponds to the requested socket_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+     */
+    public Builder setSocket(
+        io.grpc.channelz.v1.Socket.Builder builderForValue) {
+      if (socketBuilder_ == null) {
+        socket_ = builderForValue.build();
+        onChanged();
+      } else {
+        socketBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The Socket that corresponds to the requested socket_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+     */
+    public Builder mergeSocket(io.grpc.channelz.v1.Socket value) {
+      if (socketBuilder_ == null) {
+        if (socket_ != null) {
+          socket_ =
+            io.grpc.channelz.v1.Socket.newBuilder(socket_).mergeFrom(value).buildPartial();
+        } else {
+          socket_ = value;
+        }
+        onChanged();
+      } else {
+        socketBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The Socket that corresponds to the requested socket_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+     */
+    public Builder clearSocket() {
+      if (socketBuilder_ == null) {
+        socket_ = null;
+        onChanged();
+      } else {
+        socket_ = null;
+        socketBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The Socket that corresponds to the requested socket_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+     */
+    public io.grpc.channelz.v1.Socket.Builder getSocketBuilder() {
+      
+      onChanged();
+      return getSocketFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The Socket that corresponds to the requested socket_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+     */
+    public io.grpc.channelz.v1.SocketOrBuilder getSocketOrBuilder() {
+      if (socketBuilder_ != null) {
+        return socketBuilder_.getMessageOrBuilder();
+      } else {
+        return socket_ == null ?
+            io.grpc.channelz.v1.Socket.getDefaultInstance() : socket_;
+      }
+    }
+    /**
+     * <pre>
+     * The Socket that corresponds to the requested socket_id.  This field
+     * should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Socket, io.grpc.channelz.v1.Socket.Builder, io.grpc.channelz.v1.SocketOrBuilder> 
+        getSocketFieldBuilder() {
+      if (socketBuilder_ == null) {
+        socketBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.Socket, io.grpc.channelz.v1.Socket.Builder, io.grpc.channelz.v1.SocketOrBuilder>(
+                getSocket(),
+                getParentForChildren(),
+                isClean());
+        socket_ = null;
+      }
+      return socketBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.GetSocketResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.GetSocketResponse)
+  private static final io.grpc.channelz.v1.GetSocketResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.GetSocketResponse();
+  }
+
+  public static io.grpc.channelz.v1.GetSocketResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetSocketResponse>
+      PARSER = new com.google.protobuf.AbstractParser<GetSocketResponse>() {
+    public GetSocketResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetSocketResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetSocketResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetSocketResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.GetSocketResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetSocketResponseOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetSocketResponseOrBuilder.java
new file mode 100644
index 0000000..f86735d
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetSocketResponseOrBuilder.java
@@ -0,0 +1,37 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface GetSocketResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.GetSocketResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The Socket that corresponds to the requested socket_id.  This field
+   * should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+   */
+  boolean hasSocket();
+  /**
+   * <pre>
+   * The Socket that corresponds to the requested socket_id.  This field
+   * should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+   */
+  io.grpc.channelz.v1.Socket getSocket();
+  /**
+   * <pre>
+   * The Socket that corresponds to the requested socket_id.  This field
+   * should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Socket socket = 1;</code>
+   */
+  io.grpc.channelz.v1.SocketOrBuilder getSocketOrBuilder();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetSubchannelRequest.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetSubchannelRequest.java
new file mode 100644
index 0000000..fdaa0ec
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetSubchannelRequest.java
@@ -0,0 +1,464 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * Protobuf type {@code grpc.channelz.v1.GetSubchannelRequest}
+ */
+public  final class GetSubchannelRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.GetSubchannelRequest)
+    GetSubchannelRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetSubchannelRequest.newBuilder() to construct.
+  private GetSubchannelRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetSubchannelRequest() {
+    subchannelId_ = 0L;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetSubchannelRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+
+            subchannelId_ = input.readInt64();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSubchannelRequest_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSubchannelRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.GetSubchannelRequest.class, io.grpc.channelz.v1.GetSubchannelRequest.Builder.class);
+  }
+
+  public static final int SUBCHANNEL_ID_FIELD_NUMBER = 1;
+  private long subchannelId_;
+  /**
+   * <pre>
+   * subchannel_id is the the identifier of the specific subchannel to get.
+   * </pre>
+   *
+   * <code>int64 subchannel_id = 1;</code>
+   */
+  public long getSubchannelId() {
+    return subchannelId_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (subchannelId_ != 0L) {
+      output.writeInt64(1, subchannelId_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (subchannelId_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(1, subchannelId_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.GetSubchannelRequest)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.GetSubchannelRequest other = (io.grpc.channelz.v1.GetSubchannelRequest) obj;
+
+    boolean result = true;
+    result = result && (getSubchannelId()
+        == other.getSubchannelId());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + SUBCHANNEL_ID_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getSubchannelId());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.GetSubchannelRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.GetSubchannelRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.GetSubchannelRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.GetSubchannelRequest)
+      io.grpc.channelz.v1.GetSubchannelRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSubchannelRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSubchannelRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.GetSubchannelRequest.class, io.grpc.channelz.v1.GetSubchannelRequest.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.GetSubchannelRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      subchannelId_ = 0L;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSubchannelRequest_descriptor;
+    }
+
+    public io.grpc.channelz.v1.GetSubchannelRequest getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.GetSubchannelRequest.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.GetSubchannelRequest build() {
+      io.grpc.channelz.v1.GetSubchannelRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.GetSubchannelRequest buildPartial() {
+      io.grpc.channelz.v1.GetSubchannelRequest result = new io.grpc.channelz.v1.GetSubchannelRequest(this);
+      result.subchannelId_ = subchannelId_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.GetSubchannelRequest) {
+        return mergeFrom((io.grpc.channelz.v1.GetSubchannelRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.GetSubchannelRequest other) {
+      if (other == io.grpc.channelz.v1.GetSubchannelRequest.getDefaultInstance()) return this;
+      if (other.getSubchannelId() != 0L) {
+        setSubchannelId(other.getSubchannelId());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.GetSubchannelRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.GetSubchannelRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private long subchannelId_ ;
+    /**
+     * <pre>
+     * subchannel_id is the the identifier of the specific subchannel to get.
+     * </pre>
+     *
+     * <code>int64 subchannel_id = 1;</code>
+     */
+    public long getSubchannelId() {
+      return subchannelId_;
+    }
+    /**
+     * <pre>
+     * subchannel_id is the the identifier of the specific subchannel to get.
+     * </pre>
+     *
+     * <code>int64 subchannel_id = 1;</code>
+     */
+    public Builder setSubchannelId(long value) {
+      
+      subchannelId_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * subchannel_id is the the identifier of the specific subchannel to get.
+     * </pre>
+     *
+     * <code>int64 subchannel_id = 1;</code>
+     */
+    public Builder clearSubchannelId() {
+      
+      subchannelId_ = 0L;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.GetSubchannelRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.GetSubchannelRequest)
+  private static final io.grpc.channelz.v1.GetSubchannelRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.GetSubchannelRequest();
+  }
+
+  public static io.grpc.channelz.v1.GetSubchannelRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetSubchannelRequest>
+      PARSER = new com.google.protobuf.AbstractParser<GetSubchannelRequest>() {
+    public GetSubchannelRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetSubchannelRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetSubchannelRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetSubchannelRequest> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.GetSubchannelRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetSubchannelRequestOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetSubchannelRequestOrBuilder.java
new file mode 100644
index 0000000..64e2fcc
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetSubchannelRequestOrBuilder.java
@@ -0,0 +1,18 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface GetSubchannelRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.GetSubchannelRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * subchannel_id is the the identifier of the specific subchannel to get.
+   * </pre>
+   *
+   * <code>int64 subchannel_id = 1;</code>
+   */
+  long getSubchannelId();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetSubchannelResponse.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetSubchannelResponse.java
new file mode 100644
index 0000000..3a1e97a
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetSubchannelResponse.java
@@ -0,0 +1,630 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * Protobuf type {@code grpc.channelz.v1.GetSubchannelResponse}
+ */
+public  final class GetSubchannelResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.GetSubchannelResponse)
+    GetSubchannelResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetSubchannelResponse.newBuilder() to construct.
+  private GetSubchannelResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetSubchannelResponse() {
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetSubchannelResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            io.grpc.channelz.v1.Subchannel.Builder subBuilder = null;
+            if (subchannel_ != null) {
+              subBuilder = subchannel_.toBuilder();
+            }
+            subchannel_ = input.readMessage(io.grpc.channelz.v1.Subchannel.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(subchannel_);
+              subchannel_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSubchannelResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSubchannelResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.GetSubchannelResponse.class, io.grpc.channelz.v1.GetSubchannelResponse.Builder.class);
+  }
+
+  public static final int SUBCHANNEL_FIELD_NUMBER = 1;
+  private io.grpc.channelz.v1.Subchannel subchannel_;
+  /**
+   * <pre>
+   * The Subchannel that corresponds to the requested subchannel_id.  This
+   * field should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+   */
+  public boolean hasSubchannel() {
+    return subchannel_ != null;
+  }
+  /**
+   * <pre>
+   * The Subchannel that corresponds to the requested subchannel_id.  This
+   * field should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+   */
+  public io.grpc.channelz.v1.Subchannel getSubchannel() {
+    return subchannel_ == null ? io.grpc.channelz.v1.Subchannel.getDefaultInstance() : subchannel_;
+  }
+  /**
+   * <pre>
+   * The Subchannel that corresponds to the requested subchannel_id.  This
+   * field should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+   */
+  public io.grpc.channelz.v1.SubchannelOrBuilder getSubchannelOrBuilder() {
+    return getSubchannel();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (subchannel_ != null) {
+      output.writeMessage(1, getSubchannel());
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (subchannel_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getSubchannel());
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.GetSubchannelResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.GetSubchannelResponse other = (io.grpc.channelz.v1.GetSubchannelResponse) obj;
+
+    boolean result = true;
+    result = result && (hasSubchannel() == other.hasSubchannel());
+    if (hasSubchannel()) {
+      result = result && getSubchannel()
+          .equals(other.getSubchannel());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasSubchannel()) {
+      hash = (37 * hash) + SUBCHANNEL_FIELD_NUMBER;
+      hash = (53 * hash) + getSubchannel().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.GetSubchannelResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetSubchannelResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.GetSubchannelResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.GetSubchannelResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.GetSubchannelResponse)
+      io.grpc.channelz.v1.GetSubchannelResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSubchannelResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSubchannelResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.GetSubchannelResponse.class, io.grpc.channelz.v1.GetSubchannelResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.GetSubchannelResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (subchannelBuilder_ == null) {
+        subchannel_ = null;
+      } else {
+        subchannel_ = null;
+        subchannelBuilder_ = null;
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetSubchannelResponse_descriptor;
+    }
+
+    public io.grpc.channelz.v1.GetSubchannelResponse getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.GetSubchannelResponse.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.GetSubchannelResponse build() {
+      io.grpc.channelz.v1.GetSubchannelResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.GetSubchannelResponse buildPartial() {
+      io.grpc.channelz.v1.GetSubchannelResponse result = new io.grpc.channelz.v1.GetSubchannelResponse(this);
+      if (subchannelBuilder_ == null) {
+        result.subchannel_ = subchannel_;
+      } else {
+        result.subchannel_ = subchannelBuilder_.build();
+      }
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.GetSubchannelResponse) {
+        return mergeFrom((io.grpc.channelz.v1.GetSubchannelResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.GetSubchannelResponse other) {
+      if (other == io.grpc.channelz.v1.GetSubchannelResponse.getDefaultInstance()) return this;
+      if (other.hasSubchannel()) {
+        mergeSubchannel(other.getSubchannel());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.GetSubchannelResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.GetSubchannelResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private io.grpc.channelz.v1.Subchannel subchannel_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Subchannel, io.grpc.channelz.v1.Subchannel.Builder, io.grpc.channelz.v1.SubchannelOrBuilder> subchannelBuilder_;
+    /**
+     * <pre>
+     * The Subchannel that corresponds to the requested subchannel_id.  This
+     * field should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+     */
+    public boolean hasSubchannel() {
+      return subchannelBuilder_ != null || subchannel_ != null;
+    }
+    /**
+     * <pre>
+     * The Subchannel that corresponds to the requested subchannel_id.  This
+     * field should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+     */
+    public io.grpc.channelz.v1.Subchannel getSubchannel() {
+      if (subchannelBuilder_ == null) {
+        return subchannel_ == null ? io.grpc.channelz.v1.Subchannel.getDefaultInstance() : subchannel_;
+      } else {
+        return subchannelBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The Subchannel that corresponds to the requested subchannel_id.  This
+     * field should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+     */
+    public Builder setSubchannel(io.grpc.channelz.v1.Subchannel value) {
+      if (subchannelBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        subchannel_ = value;
+        onChanged();
+      } else {
+        subchannelBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The Subchannel that corresponds to the requested subchannel_id.  This
+     * field should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+     */
+    public Builder setSubchannel(
+        io.grpc.channelz.v1.Subchannel.Builder builderForValue) {
+      if (subchannelBuilder_ == null) {
+        subchannel_ = builderForValue.build();
+        onChanged();
+      } else {
+        subchannelBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The Subchannel that corresponds to the requested subchannel_id.  This
+     * field should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+     */
+    public Builder mergeSubchannel(io.grpc.channelz.v1.Subchannel value) {
+      if (subchannelBuilder_ == null) {
+        if (subchannel_ != null) {
+          subchannel_ =
+            io.grpc.channelz.v1.Subchannel.newBuilder(subchannel_).mergeFrom(value).buildPartial();
+        } else {
+          subchannel_ = value;
+        }
+        onChanged();
+      } else {
+        subchannelBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The Subchannel that corresponds to the requested subchannel_id.  This
+     * field should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+     */
+    public Builder clearSubchannel() {
+      if (subchannelBuilder_ == null) {
+        subchannel_ = null;
+        onChanged();
+      } else {
+        subchannel_ = null;
+        subchannelBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The Subchannel that corresponds to the requested subchannel_id.  This
+     * field should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+     */
+    public io.grpc.channelz.v1.Subchannel.Builder getSubchannelBuilder() {
+      
+      onChanged();
+      return getSubchannelFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The Subchannel that corresponds to the requested subchannel_id.  This
+     * field should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelOrBuilder getSubchannelOrBuilder() {
+      if (subchannelBuilder_ != null) {
+        return subchannelBuilder_.getMessageOrBuilder();
+      } else {
+        return subchannel_ == null ?
+            io.grpc.channelz.v1.Subchannel.getDefaultInstance() : subchannel_;
+      }
+    }
+    /**
+     * <pre>
+     * The Subchannel that corresponds to the requested subchannel_id.  This
+     * field should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Subchannel, io.grpc.channelz.v1.Subchannel.Builder, io.grpc.channelz.v1.SubchannelOrBuilder> 
+        getSubchannelFieldBuilder() {
+      if (subchannelBuilder_ == null) {
+        subchannelBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.Subchannel, io.grpc.channelz.v1.Subchannel.Builder, io.grpc.channelz.v1.SubchannelOrBuilder>(
+                getSubchannel(),
+                getParentForChildren(),
+                isClean());
+        subchannel_ = null;
+      }
+      return subchannelBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.GetSubchannelResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.GetSubchannelResponse)
+  private static final io.grpc.channelz.v1.GetSubchannelResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.GetSubchannelResponse();
+  }
+
+  public static io.grpc.channelz.v1.GetSubchannelResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetSubchannelResponse>
+      PARSER = new com.google.protobuf.AbstractParser<GetSubchannelResponse>() {
+    public GetSubchannelResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetSubchannelResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetSubchannelResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetSubchannelResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.GetSubchannelResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetSubchannelResponseOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetSubchannelResponseOrBuilder.java
new file mode 100644
index 0000000..9a2712d
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetSubchannelResponseOrBuilder.java
@@ -0,0 +1,37 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface GetSubchannelResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.GetSubchannelResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The Subchannel that corresponds to the requested subchannel_id.  This
+   * field should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+   */
+  boolean hasSubchannel();
+  /**
+   * <pre>
+   * The Subchannel that corresponds to the requested subchannel_id.  This
+   * field should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+   */
+  io.grpc.channelz.v1.Subchannel getSubchannel();
+  /**
+   * <pre>
+   * The Subchannel that corresponds to the requested subchannel_id.  This
+   * field should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Subchannel subchannel = 1;</code>
+   */
+  io.grpc.channelz.v1.SubchannelOrBuilder getSubchannelOrBuilder();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetTopChannelsRequest.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetTopChannelsRequest.java
new file mode 100644
index 0000000..699b9d4
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetTopChannelsRequest.java
@@ -0,0 +1,468 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * Protobuf type {@code grpc.channelz.v1.GetTopChannelsRequest}
+ */
+public  final class GetTopChannelsRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.GetTopChannelsRequest)
+    GetTopChannelsRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetTopChannelsRequest.newBuilder() to construct.
+  private GetTopChannelsRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetTopChannelsRequest() {
+    startChannelId_ = 0L;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetTopChannelsRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+
+            startChannelId_ = input.readInt64();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetTopChannelsRequest_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetTopChannelsRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.GetTopChannelsRequest.class, io.grpc.channelz.v1.GetTopChannelsRequest.Builder.class);
+  }
+
+  public static final int START_CHANNEL_ID_FIELD_NUMBER = 1;
+  private long startChannelId_;
+  /**
+   * <pre>
+   * start_channel_id indicates that only channels at or above this id should be
+   * included in the results.
+   * </pre>
+   *
+   * <code>int64 start_channel_id = 1;</code>
+   */
+  public long getStartChannelId() {
+    return startChannelId_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (startChannelId_ != 0L) {
+      output.writeInt64(1, startChannelId_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (startChannelId_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(1, startChannelId_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.GetTopChannelsRequest)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.GetTopChannelsRequest other = (io.grpc.channelz.v1.GetTopChannelsRequest) obj;
+
+    boolean result = true;
+    result = result && (getStartChannelId()
+        == other.getStartChannelId());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + START_CHANNEL_ID_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getStartChannelId());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.GetTopChannelsRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.GetTopChannelsRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.GetTopChannelsRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.GetTopChannelsRequest)
+      io.grpc.channelz.v1.GetTopChannelsRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetTopChannelsRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetTopChannelsRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.GetTopChannelsRequest.class, io.grpc.channelz.v1.GetTopChannelsRequest.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.GetTopChannelsRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      startChannelId_ = 0L;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetTopChannelsRequest_descriptor;
+    }
+
+    public io.grpc.channelz.v1.GetTopChannelsRequest getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.GetTopChannelsRequest.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.GetTopChannelsRequest build() {
+      io.grpc.channelz.v1.GetTopChannelsRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.GetTopChannelsRequest buildPartial() {
+      io.grpc.channelz.v1.GetTopChannelsRequest result = new io.grpc.channelz.v1.GetTopChannelsRequest(this);
+      result.startChannelId_ = startChannelId_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.GetTopChannelsRequest) {
+        return mergeFrom((io.grpc.channelz.v1.GetTopChannelsRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.GetTopChannelsRequest other) {
+      if (other == io.grpc.channelz.v1.GetTopChannelsRequest.getDefaultInstance()) return this;
+      if (other.getStartChannelId() != 0L) {
+        setStartChannelId(other.getStartChannelId());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.GetTopChannelsRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.GetTopChannelsRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private long startChannelId_ ;
+    /**
+     * <pre>
+     * start_channel_id indicates that only channels at or above this id should be
+     * included in the results.
+     * </pre>
+     *
+     * <code>int64 start_channel_id = 1;</code>
+     */
+    public long getStartChannelId() {
+      return startChannelId_;
+    }
+    /**
+     * <pre>
+     * start_channel_id indicates that only channels at or above this id should be
+     * included in the results.
+     * </pre>
+     *
+     * <code>int64 start_channel_id = 1;</code>
+     */
+    public Builder setStartChannelId(long value) {
+      
+      startChannelId_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * start_channel_id indicates that only channels at or above this id should be
+     * included in the results.
+     * </pre>
+     *
+     * <code>int64 start_channel_id = 1;</code>
+     */
+    public Builder clearStartChannelId() {
+      
+      startChannelId_ = 0L;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.GetTopChannelsRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.GetTopChannelsRequest)
+  private static final io.grpc.channelz.v1.GetTopChannelsRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.GetTopChannelsRequest();
+  }
+
+  public static io.grpc.channelz.v1.GetTopChannelsRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetTopChannelsRequest>
+      PARSER = new com.google.protobuf.AbstractParser<GetTopChannelsRequest>() {
+    public GetTopChannelsRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetTopChannelsRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetTopChannelsRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetTopChannelsRequest> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.GetTopChannelsRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetTopChannelsRequestOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetTopChannelsRequestOrBuilder.java
new file mode 100644
index 0000000..bc03cf3
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetTopChannelsRequestOrBuilder.java
@@ -0,0 +1,19 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface GetTopChannelsRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.GetTopChannelsRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * start_channel_id indicates that only channels at or above this id should be
+   * included in the results.
+   * </pre>
+   *
+   * <code>int64 start_channel_id = 1;</code>
+   */
+  long getStartChannelId();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetTopChannelsResponse.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetTopChannelsResponse.java
new file mode 100644
index 0000000..c0ef291
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetTopChannelsResponse.java
@@ -0,0 +1,935 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * Protobuf type {@code grpc.channelz.v1.GetTopChannelsResponse}
+ */
+public  final class GetTopChannelsResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.GetTopChannelsResponse)
+    GetTopChannelsResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use GetTopChannelsResponse.newBuilder() to construct.
+  private GetTopChannelsResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private GetTopChannelsResponse() {
+    channel_ = java.util.Collections.emptyList();
+    end_ = false;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private GetTopChannelsResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+              channel_ = new java.util.ArrayList<io.grpc.channelz.v1.Channel>();
+              mutable_bitField0_ |= 0x00000001;
+            }
+            channel_.add(
+                input.readMessage(io.grpc.channelz.v1.Channel.parser(), extensionRegistry));
+            break;
+          }
+          case 16: {
+
+            end_ = input.readBool();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+        channel_ = java.util.Collections.unmodifiableList(channel_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetTopChannelsResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetTopChannelsResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.GetTopChannelsResponse.class, io.grpc.channelz.v1.GetTopChannelsResponse.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int CHANNEL_FIELD_NUMBER = 1;
+  private java.util.List<io.grpc.channelz.v1.Channel> channel_;
+  /**
+   * <pre>
+   * list of channels that the connection detail service knows about.  Sorted in
+   * ascending channel_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  public java.util.List<io.grpc.channelz.v1.Channel> getChannelList() {
+    return channel_;
+  }
+  /**
+   * <pre>
+   * list of channels that the connection detail service knows about.  Sorted in
+   * ascending channel_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  public java.util.List<? extends io.grpc.channelz.v1.ChannelOrBuilder> 
+      getChannelOrBuilderList() {
+    return channel_;
+  }
+  /**
+   * <pre>
+   * list of channels that the connection detail service knows about.  Sorted in
+   * ascending channel_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  public int getChannelCount() {
+    return channel_.size();
+  }
+  /**
+   * <pre>
+   * list of channels that the connection detail service knows about.  Sorted in
+   * ascending channel_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  public io.grpc.channelz.v1.Channel getChannel(int index) {
+    return channel_.get(index);
+  }
+  /**
+   * <pre>
+   * list of channels that the connection detail service knows about.  Sorted in
+   * ascending channel_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  public io.grpc.channelz.v1.ChannelOrBuilder getChannelOrBuilder(
+      int index) {
+    return channel_.get(index);
+  }
+
+  public static final int END_FIELD_NUMBER = 2;
+  private boolean end_;
+  /**
+   * <pre>
+   * If set, indicates that the list of channels is the final list.  Requesting
+   * more channels can only return more if they are created after this RPC
+   * completes.
+   * </pre>
+   *
+   * <code>bool end = 2;</code>
+   */
+  public boolean getEnd() {
+    return end_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    for (int i = 0; i < channel_.size(); i++) {
+      output.writeMessage(1, channel_.get(i));
+    }
+    if (end_ != false) {
+      output.writeBool(2, end_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    for (int i = 0; i < channel_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, channel_.get(i));
+    }
+    if (end_ != false) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBoolSize(2, end_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.GetTopChannelsResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.GetTopChannelsResponse other = (io.grpc.channelz.v1.GetTopChannelsResponse) obj;
+
+    boolean result = true;
+    result = result && getChannelList()
+        .equals(other.getChannelList());
+    result = result && (getEnd()
+        == other.getEnd());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (getChannelCount() > 0) {
+      hash = (37 * hash) + CHANNEL_FIELD_NUMBER;
+      hash = (53 * hash) + getChannelList().hashCode();
+    }
+    hash = (37 * hash) + END_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+        getEnd());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.GetTopChannelsResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.GetTopChannelsResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.GetTopChannelsResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.GetTopChannelsResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.GetTopChannelsResponse)
+      io.grpc.channelz.v1.GetTopChannelsResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetTopChannelsResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetTopChannelsResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.GetTopChannelsResponse.class, io.grpc.channelz.v1.GetTopChannelsResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.GetTopChannelsResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+        getChannelFieldBuilder();
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (channelBuilder_ == null) {
+        channel_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+      } else {
+        channelBuilder_.clear();
+      }
+      end_ = false;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_GetTopChannelsResponse_descriptor;
+    }
+
+    public io.grpc.channelz.v1.GetTopChannelsResponse getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.GetTopChannelsResponse.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.GetTopChannelsResponse build() {
+      io.grpc.channelz.v1.GetTopChannelsResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.GetTopChannelsResponse buildPartial() {
+      io.grpc.channelz.v1.GetTopChannelsResponse result = new io.grpc.channelz.v1.GetTopChannelsResponse(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      if (channelBuilder_ == null) {
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          channel_ = java.util.Collections.unmodifiableList(channel_);
+          bitField0_ = (bitField0_ & ~0x00000001);
+        }
+        result.channel_ = channel_;
+      } else {
+        result.channel_ = channelBuilder_.build();
+      }
+      result.end_ = end_;
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.GetTopChannelsResponse) {
+        return mergeFrom((io.grpc.channelz.v1.GetTopChannelsResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.GetTopChannelsResponse other) {
+      if (other == io.grpc.channelz.v1.GetTopChannelsResponse.getDefaultInstance()) return this;
+      if (channelBuilder_ == null) {
+        if (!other.channel_.isEmpty()) {
+          if (channel_.isEmpty()) {
+            channel_ = other.channel_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+          } else {
+            ensureChannelIsMutable();
+            channel_.addAll(other.channel_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.channel_.isEmpty()) {
+          if (channelBuilder_.isEmpty()) {
+            channelBuilder_.dispose();
+            channelBuilder_ = null;
+            channel_ = other.channel_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+            channelBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getChannelFieldBuilder() : null;
+          } else {
+            channelBuilder_.addAllMessages(other.channel_);
+          }
+        }
+      }
+      if (other.getEnd() != false) {
+        setEnd(other.getEnd());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.GetTopChannelsResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.GetTopChannelsResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.util.List<io.grpc.channelz.v1.Channel> channel_ =
+      java.util.Collections.emptyList();
+    private void ensureChannelIsMutable() {
+      if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+        channel_ = new java.util.ArrayList<io.grpc.channelz.v1.Channel>(channel_);
+        bitField0_ |= 0x00000001;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.Channel, io.grpc.channelz.v1.Channel.Builder, io.grpc.channelz.v1.ChannelOrBuilder> channelBuilder_;
+
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.Channel> getChannelList() {
+      if (channelBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(channel_);
+      } else {
+        return channelBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public int getChannelCount() {
+      if (channelBuilder_ == null) {
+        return channel_.size();
+      } else {
+        return channelBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public io.grpc.channelz.v1.Channel getChannel(int index) {
+      if (channelBuilder_ == null) {
+        return channel_.get(index);
+      } else {
+        return channelBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public Builder setChannel(
+        int index, io.grpc.channelz.v1.Channel value) {
+      if (channelBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureChannelIsMutable();
+        channel_.set(index, value);
+        onChanged();
+      } else {
+        channelBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public Builder setChannel(
+        int index, io.grpc.channelz.v1.Channel.Builder builderForValue) {
+      if (channelBuilder_ == null) {
+        ensureChannelIsMutable();
+        channel_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        channelBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public Builder addChannel(io.grpc.channelz.v1.Channel value) {
+      if (channelBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureChannelIsMutable();
+        channel_.add(value);
+        onChanged();
+      } else {
+        channelBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public Builder addChannel(
+        int index, io.grpc.channelz.v1.Channel value) {
+      if (channelBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureChannelIsMutable();
+        channel_.add(index, value);
+        onChanged();
+      } else {
+        channelBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public Builder addChannel(
+        io.grpc.channelz.v1.Channel.Builder builderForValue) {
+      if (channelBuilder_ == null) {
+        ensureChannelIsMutable();
+        channel_.add(builderForValue.build());
+        onChanged();
+      } else {
+        channelBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public Builder addChannel(
+        int index, io.grpc.channelz.v1.Channel.Builder builderForValue) {
+      if (channelBuilder_ == null) {
+        ensureChannelIsMutable();
+        channel_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        channelBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public Builder addAllChannel(
+        java.lang.Iterable<? extends io.grpc.channelz.v1.Channel> values) {
+      if (channelBuilder_ == null) {
+        ensureChannelIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, channel_);
+        onChanged();
+      } else {
+        channelBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public Builder clearChannel() {
+      if (channelBuilder_ == null) {
+        channel_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+        onChanged();
+      } else {
+        channelBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public Builder removeChannel(int index) {
+      if (channelBuilder_ == null) {
+        ensureChannelIsMutable();
+        channel_.remove(index);
+        onChanged();
+      } else {
+        channelBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public io.grpc.channelz.v1.Channel.Builder getChannelBuilder(
+        int index) {
+      return getChannelFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public io.grpc.channelz.v1.ChannelOrBuilder getChannelOrBuilder(
+        int index) {
+      if (channelBuilder_ == null) {
+        return channel_.get(index);  } else {
+        return channelBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public java.util.List<? extends io.grpc.channelz.v1.ChannelOrBuilder> 
+         getChannelOrBuilderList() {
+      if (channelBuilder_ != null) {
+        return channelBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(channel_);
+      }
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public io.grpc.channelz.v1.Channel.Builder addChannelBuilder() {
+      return getChannelFieldBuilder().addBuilder(
+          io.grpc.channelz.v1.Channel.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public io.grpc.channelz.v1.Channel.Builder addChannelBuilder(
+        int index) {
+      return getChannelFieldBuilder().addBuilder(
+          index, io.grpc.channelz.v1.Channel.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * list of channels that the connection detail service knows about.  Sorted in
+     * ascending channel_id order.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.Channel.Builder> 
+         getChannelBuilderList() {
+      return getChannelFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.Channel, io.grpc.channelz.v1.Channel.Builder, io.grpc.channelz.v1.ChannelOrBuilder> 
+        getChannelFieldBuilder() {
+      if (channelBuilder_ == null) {
+        channelBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.channelz.v1.Channel, io.grpc.channelz.v1.Channel.Builder, io.grpc.channelz.v1.ChannelOrBuilder>(
+                channel_,
+                ((bitField0_ & 0x00000001) == 0x00000001),
+                getParentForChildren(),
+                isClean());
+        channel_ = null;
+      }
+      return channelBuilder_;
+    }
+
+    private boolean end_ ;
+    /**
+     * <pre>
+     * If set, indicates that the list of channels is the final list.  Requesting
+     * more channels can only return more if they are created after this RPC
+     * completes.
+     * </pre>
+     *
+     * <code>bool end = 2;</code>
+     */
+    public boolean getEnd() {
+      return end_;
+    }
+    /**
+     * <pre>
+     * If set, indicates that the list of channels is the final list.  Requesting
+     * more channels can only return more if they are created after this RPC
+     * completes.
+     * </pre>
+     *
+     * <code>bool end = 2;</code>
+     */
+    public Builder setEnd(boolean value) {
+      
+      end_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * If set, indicates that the list of channels is the final list.  Requesting
+     * more channels can only return more if they are created after this RPC
+     * completes.
+     * </pre>
+     *
+     * <code>bool end = 2;</code>
+     */
+    public Builder clearEnd() {
+      
+      end_ = false;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.GetTopChannelsResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.GetTopChannelsResponse)
+  private static final io.grpc.channelz.v1.GetTopChannelsResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.GetTopChannelsResponse();
+  }
+
+  public static io.grpc.channelz.v1.GetTopChannelsResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<GetTopChannelsResponse>
+      PARSER = new com.google.protobuf.AbstractParser<GetTopChannelsResponse>() {
+    public GetTopChannelsResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new GetTopChannelsResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<GetTopChannelsResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<GetTopChannelsResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.GetTopChannelsResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/GetTopChannelsResponseOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/GetTopChannelsResponseOrBuilder.java
new file mode 100644
index 0000000..bbbe3de
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/GetTopChannelsResponseOrBuilder.java
@@ -0,0 +1,69 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface GetTopChannelsResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.GetTopChannelsResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * list of channels that the connection detail service knows about.  Sorted in
+   * ascending channel_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  java.util.List<io.grpc.channelz.v1.Channel> 
+      getChannelList();
+  /**
+   * <pre>
+   * list of channels that the connection detail service knows about.  Sorted in
+   * ascending channel_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  io.grpc.channelz.v1.Channel getChannel(int index);
+  /**
+   * <pre>
+   * list of channels that the connection detail service knows about.  Sorted in
+   * ascending channel_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  int getChannelCount();
+  /**
+   * <pre>
+   * list of channels that the connection detail service knows about.  Sorted in
+   * ascending channel_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  java.util.List<? extends io.grpc.channelz.v1.ChannelOrBuilder> 
+      getChannelOrBuilderList();
+  /**
+   * <pre>
+   * list of channels that the connection detail service knows about.  Sorted in
+   * ascending channel_id order.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.Channel channel = 1;</code>
+   */
+  io.grpc.channelz.v1.ChannelOrBuilder getChannelOrBuilder(
+      int index);
+
+  /**
+   * <pre>
+   * If set, indicates that the list of channels is the final list.  Requesting
+   * more channels can only return more if they are created after this RPC
+   * completes.
+   * </pre>
+   *
+   * <code>bool end = 2;</code>
+   */
+  boolean getEnd();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/Security.java b/services/src/generated/main/java/io/grpc/channelz/v1/Security.java
new file mode 100644
index 0000000..9b8e6c8
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/Security.java
@@ -0,0 +1,2736 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * Security represents details about how secure the socket is.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.Security}
+ */
+public  final class Security extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.Security)
+    SecurityOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Security.newBuilder() to construct.
+  private Security(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Security() {
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Security(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            io.grpc.channelz.v1.Security.Tls.Builder subBuilder = null;
+            if (modelCase_ == 1) {
+              subBuilder = ((io.grpc.channelz.v1.Security.Tls) model_).toBuilder();
+            }
+            model_ =
+                input.readMessage(io.grpc.channelz.v1.Security.Tls.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.channelz.v1.Security.Tls) model_);
+              model_ = subBuilder.buildPartial();
+            }
+            modelCase_ = 1;
+            break;
+          }
+          case 18: {
+            io.grpc.channelz.v1.Security.OtherSecurity.Builder subBuilder = null;
+            if (modelCase_ == 2) {
+              subBuilder = ((io.grpc.channelz.v1.Security.OtherSecurity) model_).toBuilder();
+            }
+            model_ =
+                input.readMessage(io.grpc.channelz.v1.Security.OtherSecurity.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.channelz.v1.Security.OtherSecurity) model_);
+              model_ = subBuilder.buildPartial();
+            }
+            modelCase_ = 2;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.Security.class, io.grpc.channelz.v1.Security.Builder.class);
+  }
+
+  public interface TlsOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.Security.Tls)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * The cipher suite name in the RFC 4346 format:
+     * https://tools.ietf.org/html/rfc4346#appendix-C
+     * </pre>
+     *
+     * <code>string standard_name = 1;</code>
+     */
+    java.lang.String getStandardName();
+    /**
+     * <pre>
+     * The cipher suite name in the RFC 4346 format:
+     * https://tools.ietf.org/html/rfc4346#appendix-C
+     * </pre>
+     *
+     * <code>string standard_name = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getStandardNameBytes();
+
+    /**
+     * <pre>
+     * Some other way to describe the cipher suite if
+     * the RFC 4346 name is not available.
+     * </pre>
+     *
+     * <code>string other_name = 2;</code>
+     */
+    java.lang.String getOtherName();
+    /**
+     * <pre>
+     * Some other way to describe the cipher suite if
+     * the RFC 4346 name is not available.
+     * </pre>
+     *
+     * <code>string other_name = 2;</code>
+     */
+    com.google.protobuf.ByteString
+        getOtherNameBytes();
+
+    /**
+     * <pre>
+     * the certificate used by this endpoint.
+     * </pre>
+     *
+     * <code>bytes local_certificate = 3;</code>
+     */
+    com.google.protobuf.ByteString getLocalCertificate();
+
+    /**
+     * <pre>
+     * the certificate used by the remote endpoint.
+     * </pre>
+     *
+     * <code>bytes remote_certificate = 4;</code>
+     */
+    com.google.protobuf.ByteString getRemoteCertificate();
+
+    public io.grpc.channelz.v1.Security.Tls.CipherSuiteCase getCipherSuiteCase();
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.Security.Tls}
+   */
+  public  static final class Tls extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.channelz.v1.Security.Tls)
+      TlsOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use Tls.newBuilder() to construct.
+    private Tls(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private Tls() {
+      localCertificate_ = com.google.protobuf.ByteString.EMPTY;
+      remoteCertificate_ = com.google.protobuf.ByteString.EMPTY;
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private Tls(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+              cipherSuiteCase_ = 1;
+              cipherSuite_ = s;
+              break;
+            }
+            case 18: {
+              java.lang.String s = input.readStringRequireUtf8();
+              cipherSuiteCase_ = 2;
+              cipherSuite_ = s;
+              break;
+            }
+            case 26: {
+
+              localCertificate_ = input.readBytes();
+              break;
+            }
+            case 34: {
+
+              remoteCertificate_ = input.readBytes();
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_Tls_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_Tls_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.Security.Tls.class, io.grpc.channelz.v1.Security.Tls.Builder.class);
+    }
+
+    private int cipherSuiteCase_ = 0;
+    private java.lang.Object cipherSuite_;
+    public enum CipherSuiteCase
+        implements com.google.protobuf.Internal.EnumLite {
+      STANDARD_NAME(1),
+      OTHER_NAME(2),
+      CIPHERSUITE_NOT_SET(0);
+      private final int value;
+      private CipherSuiteCase(int value) {
+        this.value = value;
+      }
+      /**
+       * @deprecated Use {@link #forNumber(int)} instead.
+       */
+      @java.lang.Deprecated
+      public static CipherSuiteCase valueOf(int value) {
+        return forNumber(value);
+      }
+
+      public static CipherSuiteCase forNumber(int value) {
+        switch (value) {
+          case 1: return STANDARD_NAME;
+          case 2: return OTHER_NAME;
+          case 0: return CIPHERSUITE_NOT_SET;
+          default: return null;
+        }
+      }
+      public int getNumber() {
+        return this.value;
+      }
+    };
+
+    public CipherSuiteCase
+    getCipherSuiteCase() {
+      return CipherSuiteCase.forNumber(
+          cipherSuiteCase_);
+    }
+
+    public static final int STANDARD_NAME_FIELD_NUMBER = 1;
+    /**
+     * <pre>
+     * The cipher suite name in the RFC 4346 format:
+     * https://tools.ietf.org/html/rfc4346#appendix-C
+     * </pre>
+     *
+     * <code>string standard_name = 1;</code>
+     */
+    public java.lang.String getStandardName() {
+      java.lang.Object ref = "";
+      if (cipherSuiteCase_ == 1) {
+        ref = cipherSuite_;
+      }
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (cipherSuiteCase_ == 1) {
+          cipherSuite_ = s;
+        }
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * The cipher suite name in the RFC 4346 format:
+     * https://tools.ietf.org/html/rfc4346#appendix-C
+     * </pre>
+     *
+     * <code>string standard_name = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getStandardNameBytes() {
+      java.lang.Object ref = "";
+      if (cipherSuiteCase_ == 1) {
+        ref = cipherSuite_;
+      }
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        if (cipherSuiteCase_ == 1) {
+          cipherSuite_ = b;
+        }
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int OTHER_NAME_FIELD_NUMBER = 2;
+    /**
+     * <pre>
+     * Some other way to describe the cipher suite if
+     * the RFC 4346 name is not available.
+     * </pre>
+     *
+     * <code>string other_name = 2;</code>
+     */
+    public java.lang.String getOtherName() {
+      java.lang.Object ref = "";
+      if (cipherSuiteCase_ == 2) {
+        ref = cipherSuite_;
+      }
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (cipherSuiteCase_ == 2) {
+          cipherSuite_ = s;
+        }
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * Some other way to describe the cipher suite if
+     * the RFC 4346 name is not available.
+     * </pre>
+     *
+     * <code>string other_name = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getOtherNameBytes() {
+      java.lang.Object ref = "";
+      if (cipherSuiteCase_ == 2) {
+        ref = cipherSuite_;
+      }
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        if (cipherSuiteCase_ == 2) {
+          cipherSuite_ = b;
+        }
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int LOCAL_CERTIFICATE_FIELD_NUMBER = 3;
+    private com.google.protobuf.ByteString localCertificate_;
+    /**
+     * <pre>
+     * the certificate used by this endpoint.
+     * </pre>
+     *
+     * <code>bytes local_certificate = 3;</code>
+     */
+    public com.google.protobuf.ByteString getLocalCertificate() {
+      return localCertificate_;
+    }
+
+    public static final int REMOTE_CERTIFICATE_FIELD_NUMBER = 4;
+    private com.google.protobuf.ByteString remoteCertificate_;
+    /**
+     * <pre>
+     * the certificate used by the remote endpoint.
+     * </pre>
+     *
+     * <code>bytes remote_certificate = 4;</code>
+     */
+    public com.google.protobuf.ByteString getRemoteCertificate() {
+      return remoteCertificate_;
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (cipherSuiteCase_ == 1) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, cipherSuite_);
+      }
+      if (cipherSuiteCase_ == 2) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 2, cipherSuite_);
+      }
+      if (!localCertificate_.isEmpty()) {
+        output.writeBytes(3, localCertificate_);
+      }
+      if (!remoteCertificate_.isEmpty()) {
+        output.writeBytes(4, remoteCertificate_);
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (cipherSuiteCase_ == 1) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, cipherSuite_);
+      }
+      if (cipherSuiteCase_ == 2) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, cipherSuite_);
+      }
+      if (!localCertificate_.isEmpty()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBytesSize(3, localCertificate_);
+      }
+      if (!remoteCertificate_.isEmpty()) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeBytesSize(4, remoteCertificate_);
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.channelz.v1.Security.Tls)) {
+        return super.equals(obj);
+      }
+      io.grpc.channelz.v1.Security.Tls other = (io.grpc.channelz.v1.Security.Tls) obj;
+
+      boolean result = true;
+      result = result && getLocalCertificate()
+          .equals(other.getLocalCertificate());
+      result = result && getRemoteCertificate()
+          .equals(other.getRemoteCertificate());
+      result = result && getCipherSuiteCase().equals(
+          other.getCipherSuiteCase());
+      if (!result) return false;
+      switch (cipherSuiteCase_) {
+        case 1:
+          result = result && getStandardName()
+              .equals(other.getStandardName());
+          break;
+        case 2:
+          result = result && getOtherName()
+              .equals(other.getOtherName());
+          break;
+        case 0:
+        default:
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + LOCAL_CERTIFICATE_FIELD_NUMBER;
+      hash = (53 * hash) + getLocalCertificate().hashCode();
+      hash = (37 * hash) + REMOTE_CERTIFICATE_FIELD_NUMBER;
+      hash = (53 * hash) + getRemoteCertificate().hashCode();
+      switch (cipherSuiteCase_) {
+        case 1:
+          hash = (37 * hash) + STANDARD_NAME_FIELD_NUMBER;
+          hash = (53 * hash) + getStandardName().hashCode();
+          break;
+        case 2:
+          hash = (37 * hash) + OTHER_NAME_FIELD_NUMBER;
+          hash = (53 * hash) + getOtherName().hashCode();
+          break;
+        case 0:
+        default:
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.channelz.v1.Security.Tls parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Security.Tls parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Security.Tls parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Security.Tls parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Security.Tls parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Security.Tls parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Security.Tls parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Security.Tls parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Security.Tls parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Security.Tls parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Security.Tls parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Security.Tls parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.channelz.v1.Security.Tls prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.channelz.v1.Security.Tls}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.Security.Tls)
+        io.grpc.channelz.v1.Security.TlsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_Tls_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_Tls_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.channelz.v1.Security.Tls.class, io.grpc.channelz.v1.Security.Tls.Builder.class);
+      }
+
+      // Construct using io.grpc.channelz.v1.Security.Tls.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        localCertificate_ = com.google.protobuf.ByteString.EMPTY;
+
+        remoteCertificate_ = com.google.protobuf.ByteString.EMPTY;
+
+        cipherSuiteCase_ = 0;
+        cipherSuite_ = null;
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_Tls_descriptor;
+      }
+
+      public io.grpc.channelz.v1.Security.Tls getDefaultInstanceForType() {
+        return io.grpc.channelz.v1.Security.Tls.getDefaultInstance();
+      }
+
+      public io.grpc.channelz.v1.Security.Tls build() {
+        io.grpc.channelz.v1.Security.Tls result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.channelz.v1.Security.Tls buildPartial() {
+        io.grpc.channelz.v1.Security.Tls result = new io.grpc.channelz.v1.Security.Tls(this);
+        if (cipherSuiteCase_ == 1) {
+          result.cipherSuite_ = cipherSuite_;
+        }
+        if (cipherSuiteCase_ == 2) {
+          result.cipherSuite_ = cipherSuite_;
+        }
+        result.localCertificate_ = localCertificate_;
+        result.remoteCertificate_ = remoteCertificate_;
+        result.cipherSuiteCase_ = cipherSuiteCase_;
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.channelz.v1.Security.Tls) {
+          return mergeFrom((io.grpc.channelz.v1.Security.Tls)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.channelz.v1.Security.Tls other) {
+        if (other == io.grpc.channelz.v1.Security.Tls.getDefaultInstance()) return this;
+        if (other.getLocalCertificate() != com.google.protobuf.ByteString.EMPTY) {
+          setLocalCertificate(other.getLocalCertificate());
+        }
+        if (other.getRemoteCertificate() != com.google.protobuf.ByteString.EMPTY) {
+          setRemoteCertificate(other.getRemoteCertificate());
+        }
+        switch (other.getCipherSuiteCase()) {
+          case STANDARD_NAME: {
+            cipherSuiteCase_ = 1;
+            cipherSuite_ = other.cipherSuite_;
+            onChanged();
+            break;
+          }
+          case OTHER_NAME: {
+            cipherSuiteCase_ = 2;
+            cipherSuite_ = other.cipherSuite_;
+            onChanged();
+            break;
+          }
+          case CIPHERSUITE_NOT_SET: {
+            break;
+          }
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.channelz.v1.Security.Tls parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.channelz.v1.Security.Tls) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      private int cipherSuiteCase_ = 0;
+      private java.lang.Object cipherSuite_;
+      public CipherSuiteCase
+          getCipherSuiteCase() {
+        return CipherSuiteCase.forNumber(
+            cipherSuiteCase_);
+      }
+
+      public Builder clearCipherSuite() {
+        cipherSuiteCase_ = 0;
+        cipherSuite_ = null;
+        onChanged();
+        return this;
+      }
+
+
+      /**
+       * <pre>
+       * The cipher suite name in the RFC 4346 format:
+       * https://tools.ietf.org/html/rfc4346#appendix-C
+       * </pre>
+       *
+       * <code>string standard_name = 1;</code>
+       */
+      public java.lang.String getStandardName() {
+        java.lang.Object ref = "";
+        if (cipherSuiteCase_ == 1) {
+          ref = cipherSuite_;
+        }
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          if (cipherSuiteCase_ == 1) {
+            cipherSuite_ = s;
+          }
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The cipher suite name in the RFC 4346 format:
+       * https://tools.ietf.org/html/rfc4346#appendix-C
+       * </pre>
+       *
+       * <code>string standard_name = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getStandardNameBytes() {
+        java.lang.Object ref = "";
+        if (cipherSuiteCase_ == 1) {
+          ref = cipherSuite_;
+        }
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          if (cipherSuiteCase_ == 1) {
+            cipherSuite_ = b;
+          }
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The cipher suite name in the RFC 4346 format:
+       * https://tools.ietf.org/html/rfc4346#appendix-C
+       * </pre>
+       *
+       * <code>string standard_name = 1;</code>
+       */
+      public Builder setStandardName(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  cipherSuiteCase_ = 1;
+        cipherSuite_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The cipher suite name in the RFC 4346 format:
+       * https://tools.ietf.org/html/rfc4346#appendix-C
+       * </pre>
+       *
+       * <code>string standard_name = 1;</code>
+       */
+      public Builder clearStandardName() {
+        if (cipherSuiteCase_ == 1) {
+          cipherSuiteCase_ = 0;
+          cipherSuite_ = null;
+          onChanged();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * The cipher suite name in the RFC 4346 format:
+       * https://tools.ietf.org/html/rfc4346#appendix-C
+       * </pre>
+       *
+       * <code>string standard_name = 1;</code>
+       */
+      public Builder setStandardNameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        cipherSuiteCase_ = 1;
+        cipherSuite_ = value;
+        onChanged();
+        return this;
+      }
+
+      /**
+       * <pre>
+       * Some other way to describe the cipher suite if
+       * the RFC 4346 name is not available.
+       * </pre>
+       *
+       * <code>string other_name = 2;</code>
+       */
+      public java.lang.String getOtherName() {
+        java.lang.Object ref = "";
+        if (cipherSuiteCase_ == 2) {
+          ref = cipherSuite_;
+        }
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          if (cipherSuiteCase_ == 2) {
+            cipherSuite_ = s;
+          }
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * Some other way to describe the cipher suite if
+       * the RFC 4346 name is not available.
+       * </pre>
+       *
+       * <code>string other_name = 2;</code>
+       */
+      public com.google.protobuf.ByteString
+          getOtherNameBytes() {
+        java.lang.Object ref = "";
+        if (cipherSuiteCase_ == 2) {
+          ref = cipherSuite_;
+        }
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          if (cipherSuiteCase_ == 2) {
+            cipherSuite_ = b;
+          }
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * Some other way to describe the cipher suite if
+       * the RFC 4346 name is not available.
+       * </pre>
+       *
+       * <code>string other_name = 2;</code>
+       */
+      public Builder setOtherName(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  cipherSuiteCase_ = 2;
+        cipherSuite_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * Some other way to describe the cipher suite if
+       * the RFC 4346 name is not available.
+       * </pre>
+       *
+       * <code>string other_name = 2;</code>
+       */
+      public Builder clearOtherName() {
+        if (cipherSuiteCase_ == 2) {
+          cipherSuiteCase_ = 0;
+          cipherSuite_ = null;
+          onChanged();
+        }
+        return this;
+      }
+      /**
+       * <pre>
+       * Some other way to describe the cipher suite if
+       * the RFC 4346 name is not available.
+       * </pre>
+       *
+       * <code>string other_name = 2;</code>
+       */
+      public Builder setOtherNameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        cipherSuiteCase_ = 2;
+        cipherSuite_ = value;
+        onChanged();
+        return this;
+      }
+
+      private com.google.protobuf.ByteString localCertificate_ = com.google.protobuf.ByteString.EMPTY;
+      /**
+       * <pre>
+       * the certificate used by this endpoint.
+       * </pre>
+       *
+       * <code>bytes local_certificate = 3;</code>
+       */
+      public com.google.protobuf.ByteString getLocalCertificate() {
+        return localCertificate_;
+      }
+      /**
+       * <pre>
+       * the certificate used by this endpoint.
+       * </pre>
+       *
+       * <code>bytes local_certificate = 3;</code>
+       */
+      public Builder setLocalCertificate(com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        localCertificate_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * the certificate used by this endpoint.
+       * </pre>
+       *
+       * <code>bytes local_certificate = 3;</code>
+       */
+      public Builder clearLocalCertificate() {
+        
+        localCertificate_ = getDefaultInstance().getLocalCertificate();
+        onChanged();
+        return this;
+      }
+
+      private com.google.protobuf.ByteString remoteCertificate_ = com.google.protobuf.ByteString.EMPTY;
+      /**
+       * <pre>
+       * the certificate used by the remote endpoint.
+       * </pre>
+       *
+       * <code>bytes remote_certificate = 4;</code>
+       */
+      public com.google.protobuf.ByteString getRemoteCertificate() {
+        return remoteCertificate_;
+      }
+      /**
+       * <pre>
+       * the certificate used by the remote endpoint.
+       * </pre>
+       *
+       * <code>bytes remote_certificate = 4;</code>
+       */
+      public Builder setRemoteCertificate(com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        remoteCertificate_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * the certificate used by the remote endpoint.
+       * </pre>
+       *
+       * <code>bytes remote_certificate = 4;</code>
+       */
+      public Builder clearRemoteCertificate() {
+        
+        remoteCertificate_ = getDefaultInstance().getRemoteCertificate();
+        onChanged();
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.Security.Tls)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.channelz.v1.Security.Tls)
+    private static final io.grpc.channelz.v1.Security.Tls DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.channelz.v1.Security.Tls();
+    }
+
+    public static io.grpc.channelz.v1.Security.Tls getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<Tls>
+        PARSER = new com.google.protobuf.AbstractParser<Tls>() {
+      public Tls parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new Tls(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<Tls> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<Tls> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.channelz.v1.Security.Tls getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  public interface OtherSecurityOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.Security.OtherSecurity)
+      com.google.protobuf.MessageOrBuilder {
+
+    /**
+     * <pre>
+     * The human readable version of the value.
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    java.lang.String getName();
+    /**
+     * <pre>
+     * The human readable version of the value.
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    com.google.protobuf.ByteString
+        getNameBytes();
+
+    /**
+     * <pre>
+     * The actual security details message.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any value = 2;</code>
+     */
+    boolean hasValue();
+    /**
+     * <pre>
+     * The actual security details message.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any value = 2;</code>
+     */
+    com.google.protobuf.Any getValue();
+    /**
+     * <pre>
+     * The actual security details message.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any value = 2;</code>
+     */
+    com.google.protobuf.AnyOrBuilder getValueOrBuilder();
+  }
+  /**
+   * Protobuf type {@code grpc.channelz.v1.Security.OtherSecurity}
+   */
+  public  static final class OtherSecurity extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.channelz.v1.Security.OtherSecurity)
+      OtherSecurityOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use OtherSecurity.newBuilder() to construct.
+    private OtherSecurity(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private OtherSecurity() {
+      name_ = "";
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private OtherSecurity(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      int mutable_bitField0_ = 0;
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownFieldProto3(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+            case 10: {
+              java.lang.String s = input.readStringRequireUtf8();
+
+              name_ = s;
+              break;
+            }
+            case 18: {
+              com.google.protobuf.Any.Builder subBuilder = null;
+              if (value_ != null) {
+                subBuilder = value_.toBuilder();
+              }
+              value_ = input.readMessage(com.google.protobuf.Any.parser(), extensionRegistry);
+              if (subBuilder != null) {
+                subBuilder.mergeFrom(value_);
+                value_ = subBuilder.buildPartial();
+              }
+
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_OtherSecurity_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_OtherSecurity_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.Security.OtherSecurity.class, io.grpc.channelz.v1.Security.OtherSecurity.Builder.class);
+    }
+
+    public static final int NAME_FIELD_NUMBER = 1;
+    private volatile java.lang.Object name_;
+    /**
+     * <pre>
+     * The human readable version of the value.
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        return (java.lang.String) ref;
+      } else {
+        com.google.protobuf.ByteString bs = 
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        name_ = s;
+        return s;
+      }
+    }
+    /**
+     * <pre>
+     * The human readable version of the value.
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof java.lang.String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+
+    public static final int VALUE_FIELD_NUMBER = 2;
+    private com.google.protobuf.Any value_;
+    /**
+     * <pre>
+     * The actual security details message.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any value = 2;</code>
+     */
+    public boolean hasValue() {
+      return value_ != null;
+    }
+    /**
+     * <pre>
+     * The actual security details message.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any value = 2;</code>
+     */
+    public com.google.protobuf.Any getValue() {
+      return value_ == null ? com.google.protobuf.Any.getDefaultInstance() : value_;
+    }
+    /**
+     * <pre>
+     * The actual security details message.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any value = 2;</code>
+     */
+    public com.google.protobuf.AnyOrBuilder getValueOrBuilder() {
+      return getValue();
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      if (!getNameBytes().isEmpty()) {
+        com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_);
+      }
+      if (value_ != null) {
+        output.writeMessage(2, getValue());
+      }
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      if (!getNameBytes().isEmpty()) {
+        size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_);
+      }
+      if (value_ != null) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(2, getValue());
+      }
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.channelz.v1.Security.OtherSecurity)) {
+        return super.equals(obj);
+      }
+      io.grpc.channelz.v1.Security.OtherSecurity other = (io.grpc.channelz.v1.Security.OtherSecurity) obj;
+
+      boolean result = true;
+      result = result && getName()
+          .equals(other.getName());
+      result = result && (hasValue() == other.hasValue());
+      if (hasValue()) {
+        result = result && getValue()
+            .equals(other.getValue());
+      }
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (37 * hash) + NAME_FIELD_NUMBER;
+      hash = (53 * hash) + getName().hashCode();
+      if (hasValue()) {
+        hash = (37 * hash) + VALUE_FIELD_NUMBER;
+        hash = (53 * hash) + getValue().hashCode();
+      }
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.channelz.v1.Security.OtherSecurity parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Security.OtherSecurity parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Security.OtherSecurity parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Security.OtherSecurity parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Security.OtherSecurity parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.channelz.v1.Security.OtherSecurity parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Security.OtherSecurity parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Security.OtherSecurity parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Security.OtherSecurity parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Security.OtherSecurity parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.channelz.v1.Security.OtherSecurity parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.channelz.v1.Security.OtherSecurity parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.channelz.v1.Security.OtherSecurity prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.channelz.v1.Security.OtherSecurity}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.Security.OtherSecurity)
+        io.grpc.channelz.v1.Security.OtherSecurityOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_OtherSecurity_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_OtherSecurity_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.channelz.v1.Security.OtherSecurity.class, io.grpc.channelz.v1.Security.OtherSecurity.Builder.class);
+      }
+
+      // Construct using io.grpc.channelz.v1.Security.OtherSecurity.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        name_ = "";
+
+        if (valueBuilder_ == null) {
+          value_ = null;
+        } else {
+          value_ = null;
+          valueBuilder_ = null;
+        }
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_OtherSecurity_descriptor;
+      }
+
+      public io.grpc.channelz.v1.Security.OtherSecurity getDefaultInstanceForType() {
+        return io.grpc.channelz.v1.Security.OtherSecurity.getDefaultInstance();
+      }
+
+      public io.grpc.channelz.v1.Security.OtherSecurity build() {
+        io.grpc.channelz.v1.Security.OtherSecurity result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.channelz.v1.Security.OtherSecurity buildPartial() {
+        io.grpc.channelz.v1.Security.OtherSecurity result = new io.grpc.channelz.v1.Security.OtherSecurity(this);
+        result.name_ = name_;
+        if (valueBuilder_ == null) {
+          result.value_ = value_;
+        } else {
+          result.value_ = valueBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.channelz.v1.Security.OtherSecurity) {
+          return mergeFrom((io.grpc.channelz.v1.Security.OtherSecurity)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.channelz.v1.Security.OtherSecurity other) {
+        if (other == io.grpc.channelz.v1.Security.OtherSecurity.getDefaultInstance()) return this;
+        if (!other.getName().isEmpty()) {
+          name_ = other.name_;
+          onChanged();
+        }
+        if (other.hasValue()) {
+          mergeValue(other.getValue());
+        }
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.channelz.v1.Security.OtherSecurity parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.channelz.v1.Security.OtherSecurity) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+
+      private java.lang.Object name_ = "";
+      /**
+       * <pre>
+       * The human readable version of the value.
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public java.lang.String getName() {
+        java.lang.Object ref = name_;
+        if (!(ref instanceof java.lang.String)) {
+          com.google.protobuf.ByteString bs =
+              (com.google.protobuf.ByteString) ref;
+          java.lang.String s = bs.toStringUtf8();
+          name_ = s;
+          return s;
+        } else {
+          return (java.lang.String) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The human readable version of the value.
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public com.google.protobuf.ByteString
+          getNameBytes() {
+        java.lang.Object ref = name_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8(
+                  (java.lang.String) ref);
+          name_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      /**
+       * <pre>
+       * The human readable version of the value.
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public Builder setName(
+          java.lang.String value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  
+        name_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The human readable version of the value.
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public Builder clearName() {
+        
+        name_ = getDefaultInstance().getName();
+        onChanged();
+        return this;
+      }
+      /**
+       * <pre>
+       * The human readable version of the value.
+       * </pre>
+       *
+       * <code>string name = 1;</code>
+       */
+      public Builder setNameBytes(
+          com.google.protobuf.ByteString value) {
+        if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+        
+        name_ = value;
+        onChanged();
+        return this;
+      }
+
+      private com.google.protobuf.Any value_ = null;
+      private com.google.protobuf.SingleFieldBuilderV3<
+          com.google.protobuf.Any, com.google.protobuf.Any.Builder, com.google.protobuf.AnyOrBuilder> valueBuilder_;
+      /**
+       * <pre>
+       * The actual security details message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public boolean hasValue() {
+        return valueBuilder_ != null || value_ != null;
+      }
+      /**
+       * <pre>
+       * The actual security details message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public com.google.protobuf.Any getValue() {
+        if (valueBuilder_ == null) {
+          return value_ == null ? com.google.protobuf.Any.getDefaultInstance() : value_;
+        } else {
+          return valueBuilder_.getMessage();
+        }
+      }
+      /**
+       * <pre>
+       * The actual security details message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public Builder setValue(com.google.protobuf.Any value) {
+        if (valueBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          value_ = value;
+          onChanged();
+        } else {
+          valueBuilder_.setMessage(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The actual security details message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public Builder setValue(
+          com.google.protobuf.Any.Builder builderForValue) {
+        if (valueBuilder_ == null) {
+          value_ = builderForValue.build();
+          onChanged();
+        } else {
+          valueBuilder_.setMessage(builderForValue.build());
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The actual security details message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public Builder mergeValue(com.google.protobuf.Any value) {
+        if (valueBuilder_ == null) {
+          if (value_ != null) {
+            value_ =
+              com.google.protobuf.Any.newBuilder(value_).mergeFrom(value).buildPartial();
+          } else {
+            value_ = value;
+          }
+          onChanged();
+        } else {
+          valueBuilder_.mergeFrom(value);
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The actual security details message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public Builder clearValue() {
+        if (valueBuilder_ == null) {
+          value_ = null;
+          onChanged();
+        } else {
+          value_ = null;
+          valueBuilder_ = null;
+        }
+
+        return this;
+      }
+      /**
+       * <pre>
+       * The actual security details message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public com.google.protobuf.Any.Builder getValueBuilder() {
+        
+        onChanged();
+        return getValueFieldBuilder().getBuilder();
+      }
+      /**
+       * <pre>
+       * The actual security details message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      public com.google.protobuf.AnyOrBuilder getValueOrBuilder() {
+        if (valueBuilder_ != null) {
+          return valueBuilder_.getMessageOrBuilder();
+        } else {
+          return value_ == null ?
+              com.google.protobuf.Any.getDefaultInstance() : value_;
+        }
+      }
+      /**
+       * <pre>
+       * The actual security details message.
+       * </pre>
+       *
+       * <code>.google.protobuf.Any value = 2;</code>
+       */
+      private com.google.protobuf.SingleFieldBuilderV3<
+          com.google.protobuf.Any, com.google.protobuf.Any.Builder, com.google.protobuf.AnyOrBuilder> 
+          getValueFieldBuilder() {
+        if (valueBuilder_ == null) {
+          valueBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+              com.google.protobuf.Any, com.google.protobuf.Any.Builder, com.google.protobuf.AnyOrBuilder>(
+                  getValue(),
+                  getParentForChildren(),
+                  isClean());
+          value_ = null;
+        }
+        return valueBuilder_;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFieldsProto3(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.Security.OtherSecurity)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.channelz.v1.Security.OtherSecurity)
+    private static final io.grpc.channelz.v1.Security.OtherSecurity DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.channelz.v1.Security.OtherSecurity();
+    }
+
+    public static io.grpc.channelz.v1.Security.OtherSecurity getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    private static final com.google.protobuf.Parser<OtherSecurity>
+        PARSER = new com.google.protobuf.AbstractParser<OtherSecurity>() {
+      public OtherSecurity parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new OtherSecurity(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<OtherSecurity> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<OtherSecurity> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.channelz.v1.Security.OtherSecurity getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  private int modelCase_ = 0;
+  private java.lang.Object model_;
+  public enum ModelCase
+      implements com.google.protobuf.Internal.EnumLite {
+    TLS(1),
+    OTHER(2),
+    MODEL_NOT_SET(0);
+    private final int value;
+    private ModelCase(int value) {
+      this.value = value;
+    }
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static ModelCase valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static ModelCase forNumber(int value) {
+      switch (value) {
+        case 1: return TLS;
+        case 2: return OTHER;
+        case 0: return MODEL_NOT_SET;
+        default: return null;
+      }
+    }
+    public int getNumber() {
+      return this.value;
+    }
+  };
+
+  public ModelCase
+  getModelCase() {
+    return ModelCase.forNumber(
+        modelCase_);
+  }
+
+  public static final int TLS_FIELD_NUMBER = 1;
+  /**
+   * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+   */
+  public boolean hasTls() {
+    return modelCase_ == 1;
+  }
+  /**
+   * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+   */
+  public io.grpc.channelz.v1.Security.Tls getTls() {
+    if (modelCase_ == 1) {
+       return (io.grpc.channelz.v1.Security.Tls) model_;
+    }
+    return io.grpc.channelz.v1.Security.Tls.getDefaultInstance();
+  }
+  /**
+   * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+   */
+  public io.grpc.channelz.v1.Security.TlsOrBuilder getTlsOrBuilder() {
+    if (modelCase_ == 1) {
+       return (io.grpc.channelz.v1.Security.Tls) model_;
+    }
+    return io.grpc.channelz.v1.Security.Tls.getDefaultInstance();
+  }
+
+  public static final int OTHER_FIELD_NUMBER = 2;
+  /**
+   * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+   */
+  public boolean hasOther() {
+    return modelCase_ == 2;
+  }
+  /**
+   * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+   */
+  public io.grpc.channelz.v1.Security.OtherSecurity getOther() {
+    if (modelCase_ == 2) {
+       return (io.grpc.channelz.v1.Security.OtherSecurity) model_;
+    }
+    return io.grpc.channelz.v1.Security.OtherSecurity.getDefaultInstance();
+  }
+  /**
+   * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+   */
+  public io.grpc.channelz.v1.Security.OtherSecurityOrBuilder getOtherOrBuilder() {
+    if (modelCase_ == 2) {
+       return (io.grpc.channelz.v1.Security.OtherSecurity) model_;
+    }
+    return io.grpc.channelz.v1.Security.OtherSecurity.getDefaultInstance();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (modelCase_ == 1) {
+      output.writeMessage(1, (io.grpc.channelz.v1.Security.Tls) model_);
+    }
+    if (modelCase_ == 2) {
+      output.writeMessage(2, (io.grpc.channelz.v1.Security.OtherSecurity) model_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (modelCase_ == 1) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, (io.grpc.channelz.v1.Security.Tls) model_);
+    }
+    if (modelCase_ == 2) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(2, (io.grpc.channelz.v1.Security.OtherSecurity) model_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.Security)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.Security other = (io.grpc.channelz.v1.Security) obj;
+
+    boolean result = true;
+    result = result && getModelCase().equals(
+        other.getModelCase());
+    if (!result) return false;
+    switch (modelCase_) {
+      case 1:
+        result = result && getTls()
+            .equals(other.getTls());
+        break;
+      case 2:
+        result = result && getOther()
+            .equals(other.getOther());
+        break;
+      case 0:
+      default:
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    switch (modelCase_) {
+      case 1:
+        hash = (37 * hash) + TLS_FIELD_NUMBER;
+        hash = (53 * hash) + getTls().hashCode();
+        break;
+      case 2:
+        hash = (37 * hash) + OTHER_FIELD_NUMBER;
+        hash = (53 * hash) + getOther().hashCode();
+        break;
+      case 0:
+      default:
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.Security parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Security parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Security parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Security parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Security parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Security parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Security parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Security parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Security parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Security parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Security parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Security parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.Security prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Security represents details about how secure the socket is.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.Security}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.Security)
+      io.grpc.channelz.v1.SecurityOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.Security.class, io.grpc.channelz.v1.Security.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.Security.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      modelCase_ = 0;
+      model_ = null;
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Security_descriptor;
+    }
+
+    public io.grpc.channelz.v1.Security getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.Security.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.Security build() {
+      io.grpc.channelz.v1.Security result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.Security buildPartial() {
+      io.grpc.channelz.v1.Security result = new io.grpc.channelz.v1.Security(this);
+      if (modelCase_ == 1) {
+        if (tlsBuilder_ == null) {
+          result.model_ = model_;
+        } else {
+          result.model_ = tlsBuilder_.build();
+        }
+      }
+      if (modelCase_ == 2) {
+        if (otherBuilder_ == null) {
+          result.model_ = model_;
+        } else {
+          result.model_ = otherBuilder_.build();
+        }
+      }
+      result.modelCase_ = modelCase_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.Security) {
+        return mergeFrom((io.grpc.channelz.v1.Security)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.Security other) {
+      if (other == io.grpc.channelz.v1.Security.getDefaultInstance()) return this;
+      switch (other.getModelCase()) {
+        case TLS: {
+          mergeTls(other.getTls());
+          break;
+        }
+        case OTHER: {
+          mergeOther(other.getOther());
+          break;
+        }
+        case MODEL_NOT_SET: {
+          break;
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.Security parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.Security) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int modelCase_ = 0;
+    private java.lang.Object model_;
+    public ModelCase
+        getModelCase() {
+      return ModelCase.forNumber(
+          modelCase_);
+    }
+
+    public Builder clearModel() {
+      modelCase_ = 0;
+      model_ = null;
+      onChanged();
+      return this;
+    }
+
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Security.Tls, io.grpc.channelz.v1.Security.Tls.Builder, io.grpc.channelz.v1.Security.TlsOrBuilder> tlsBuilder_;
+    /**
+     * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+     */
+    public boolean hasTls() {
+      return modelCase_ == 1;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+     */
+    public io.grpc.channelz.v1.Security.Tls getTls() {
+      if (tlsBuilder_ == null) {
+        if (modelCase_ == 1) {
+          return (io.grpc.channelz.v1.Security.Tls) model_;
+        }
+        return io.grpc.channelz.v1.Security.Tls.getDefaultInstance();
+      } else {
+        if (modelCase_ == 1) {
+          return tlsBuilder_.getMessage();
+        }
+        return io.grpc.channelz.v1.Security.Tls.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+     */
+    public Builder setTls(io.grpc.channelz.v1.Security.Tls value) {
+      if (tlsBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        model_ = value;
+        onChanged();
+      } else {
+        tlsBuilder_.setMessage(value);
+      }
+      modelCase_ = 1;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+     */
+    public Builder setTls(
+        io.grpc.channelz.v1.Security.Tls.Builder builderForValue) {
+      if (tlsBuilder_ == null) {
+        model_ = builderForValue.build();
+        onChanged();
+      } else {
+        tlsBuilder_.setMessage(builderForValue.build());
+      }
+      modelCase_ = 1;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+     */
+    public Builder mergeTls(io.grpc.channelz.v1.Security.Tls value) {
+      if (tlsBuilder_ == null) {
+        if (modelCase_ == 1 &&
+            model_ != io.grpc.channelz.v1.Security.Tls.getDefaultInstance()) {
+          model_ = io.grpc.channelz.v1.Security.Tls.newBuilder((io.grpc.channelz.v1.Security.Tls) model_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          model_ = value;
+        }
+        onChanged();
+      } else {
+        if (modelCase_ == 1) {
+          tlsBuilder_.mergeFrom(value);
+        }
+        tlsBuilder_.setMessage(value);
+      }
+      modelCase_ = 1;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+     */
+    public Builder clearTls() {
+      if (tlsBuilder_ == null) {
+        if (modelCase_ == 1) {
+          modelCase_ = 0;
+          model_ = null;
+          onChanged();
+        }
+      } else {
+        if (modelCase_ == 1) {
+          modelCase_ = 0;
+          model_ = null;
+        }
+        tlsBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+     */
+    public io.grpc.channelz.v1.Security.Tls.Builder getTlsBuilder() {
+      return getTlsFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+     */
+    public io.grpc.channelz.v1.Security.TlsOrBuilder getTlsOrBuilder() {
+      if ((modelCase_ == 1) && (tlsBuilder_ != null)) {
+        return tlsBuilder_.getMessageOrBuilder();
+      } else {
+        if (modelCase_ == 1) {
+          return (io.grpc.channelz.v1.Security.Tls) model_;
+        }
+        return io.grpc.channelz.v1.Security.Tls.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Security.Tls, io.grpc.channelz.v1.Security.Tls.Builder, io.grpc.channelz.v1.Security.TlsOrBuilder> 
+        getTlsFieldBuilder() {
+      if (tlsBuilder_ == null) {
+        if (!(modelCase_ == 1)) {
+          model_ = io.grpc.channelz.v1.Security.Tls.getDefaultInstance();
+        }
+        tlsBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.Security.Tls, io.grpc.channelz.v1.Security.Tls.Builder, io.grpc.channelz.v1.Security.TlsOrBuilder>(
+                (io.grpc.channelz.v1.Security.Tls) model_,
+                getParentForChildren(),
+                isClean());
+        model_ = null;
+      }
+      modelCase_ = 1;
+      onChanged();;
+      return tlsBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Security.OtherSecurity, io.grpc.channelz.v1.Security.OtherSecurity.Builder, io.grpc.channelz.v1.Security.OtherSecurityOrBuilder> otherBuilder_;
+    /**
+     * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+     */
+    public boolean hasOther() {
+      return modelCase_ == 2;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+     */
+    public io.grpc.channelz.v1.Security.OtherSecurity getOther() {
+      if (otherBuilder_ == null) {
+        if (modelCase_ == 2) {
+          return (io.grpc.channelz.v1.Security.OtherSecurity) model_;
+        }
+        return io.grpc.channelz.v1.Security.OtherSecurity.getDefaultInstance();
+      } else {
+        if (modelCase_ == 2) {
+          return otherBuilder_.getMessage();
+        }
+        return io.grpc.channelz.v1.Security.OtherSecurity.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+     */
+    public Builder setOther(io.grpc.channelz.v1.Security.OtherSecurity value) {
+      if (otherBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        model_ = value;
+        onChanged();
+      } else {
+        otherBuilder_.setMessage(value);
+      }
+      modelCase_ = 2;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+     */
+    public Builder setOther(
+        io.grpc.channelz.v1.Security.OtherSecurity.Builder builderForValue) {
+      if (otherBuilder_ == null) {
+        model_ = builderForValue.build();
+        onChanged();
+      } else {
+        otherBuilder_.setMessage(builderForValue.build());
+      }
+      modelCase_ = 2;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+     */
+    public Builder mergeOther(io.grpc.channelz.v1.Security.OtherSecurity value) {
+      if (otherBuilder_ == null) {
+        if (modelCase_ == 2 &&
+            model_ != io.grpc.channelz.v1.Security.OtherSecurity.getDefaultInstance()) {
+          model_ = io.grpc.channelz.v1.Security.OtherSecurity.newBuilder((io.grpc.channelz.v1.Security.OtherSecurity) model_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          model_ = value;
+        }
+        onChanged();
+      } else {
+        if (modelCase_ == 2) {
+          otherBuilder_.mergeFrom(value);
+        }
+        otherBuilder_.setMessage(value);
+      }
+      modelCase_ = 2;
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+     */
+    public Builder clearOther() {
+      if (otherBuilder_ == null) {
+        if (modelCase_ == 2) {
+          modelCase_ = 0;
+          model_ = null;
+          onChanged();
+        }
+      } else {
+        if (modelCase_ == 2) {
+          modelCase_ = 0;
+          model_ = null;
+        }
+        otherBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+     */
+    public io.grpc.channelz.v1.Security.OtherSecurity.Builder getOtherBuilder() {
+      return getOtherFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+     */
+    public io.grpc.channelz.v1.Security.OtherSecurityOrBuilder getOtherOrBuilder() {
+      if ((modelCase_ == 2) && (otherBuilder_ != null)) {
+        return otherBuilder_.getMessageOrBuilder();
+      } else {
+        if (modelCase_ == 2) {
+          return (io.grpc.channelz.v1.Security.OtherSecurity) model_;
+        }
+        return io.grpc.channelz.v1.Security.OtherSecurity.getDefaultInstance();
+      }
+    }
+    /**
+     * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Security.OtherSecurity, io.grpc.channelz.v1.Security.OtherSecurity.Builder, io.grpc.channelz.v1.Security.OtherSecurityOrBuilder> 
+        getOtherFieldBuilder() {
+      if (otherBuilder_ == null) {
+        if (!(modelCase_ == 2)) {
+          model_ = io.grpc.channelz.v1.Security.OtherSecurity.getDefaultInstance();
+        }
+        otherBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.Security.OtherSecurity, io.grpc.channelz.v1.Security.OtherSecurity.Builder, io.grpc.channelz.v1.Security.OtherSecurityOrBuilder>(
+                (io.grpc.channelz.v1.Security.OtherSecurity) model_,
+                getParentForChildren(),
+                isClean());
+        model_ = null;
+      }
+      modelCase_ = 2;
+      onChanged();;
+      return otherBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.Security)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.Security)
+  private static final io.grpc.channelz.v1.Security DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.Security();
+  }
+
+  public static io.grpc.channelz.v1.Security getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<Security>
+      PARSER = new com.google.protobuf.AbstractParser<Security>() {
+    public Security parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Security(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Security> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Security> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.Security getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SecurityOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/SecurityOrBuilder.java
new file mode 100644
index 0000000..cb4d42e
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SecurityOrBuilder.java
@@ -0,0 +1,37 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface SecurityOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.Security)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+   */
+  boolean hasTls();
+  /**
+   * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+   */
+  io.grpc.channelz.v1.Security.Tls getTls();
+  /**
+   * <code>.grpc.channelz.v1.Security.Tls tls = 1;</code>
+   */
+  io.grpc.channelz.v1.Security.TlsOrBuilder getTlsOrBuilder();
+
+  /**
+   * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+   */
+  boolean hasOther();
+  /**
+   * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+   */
+  io.grpc.channelz.v1.Security.OtherSecurity getOther();
+  /**
+   * <code>.grpc.channelz.v1.Security.OtherSecurity other = 2;</code>
+   */
+  io.grpc.channelz.v1.Security.OtherSecurityOrBuilder getOtherOrBuilder();
+
+  public io.grpc.channelz.v1.Security.ModelCase getModelCase();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/Server.java b/services/src/generated/main/java/io/grpc/channelz/v1/Server.java
new file mode 100644
index 0000000..d420301
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/Server.java
@@ -0,0 +1,1320 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * Server represents a single server.  There may be multiple servers in a single
+ * program.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.Server}
+ */
+public  final class Server extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.Server)
+    ServerOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Server.newBuilder() to construct.
+  private Server(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Server() {
+    listenSocket_ = java.util.Collections.emptyList();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Server(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            io.grpc.channelz.v1.ServerRef.Builder subBuilder = null;
+            if (ref_ != null) {
+              subBuilder = ref_.toBuilder();
+            }
+            ref_ = input.readMessage(io.grpc.channelz.v1.ServerRef.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(ref_);
+              ref_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 18: {
+            io.grpc.channelz.v1.ServerData.Builder subBuilder = null;
+            if (data_ != null) {
+              subBuilder = data_.toBuilder();
+            }
+            data_ = input.readMessage(io.grpc.channelz.v1.ServerData.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(data_);
+              data_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 26: {
+            if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) {
+              listenSocket_ = new java.util.ArrayList<io.grpc.channelz.v1.SocketRef>();
+              mutable_bitField0_ |= 0x00000004;
+            }
+            listenSocket_.add(
+                input.readMessage(io.grpc.channelz.v1.SocketRef.parser(), extensionRegistry));
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) {
+        listenSocket_ = java.util.Collections.unmodifiableList(listenSocket_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Server_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Server_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.Server.class, io.grpc.channelz.v1.Server.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int REF_FIELD_NUMBER = 1;
+  private io.grpc.channelz.v1.ServerRef ref_;
+  /**
+   * <pre>
+   * The identifier for a Server.  This should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+   */
+  public boolean hasRef() {
+    return ref_ != null;
+  }
+  /**
+   * <pre>
+   * The identifier for a Server.  This should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+   */
+  public io.grpc.channelz.v1.ServerRef getRef() {
+    return ref_ == null ? io.grpc.channelz.v1.ServerRef.getDefaultInstance() : ref_;
+  }
+  /**
+   * <pre>
+   * The identifier for a Server.  This should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+   */
+  public io.grpc.channelz.v1.ServerRefOrBuilder getRefOrBuilder() {
+    return getRef();
+  }
+
+  public static final int DATA_FIELD_NUMBER = 2;
+  private io.grpc.channelz.v1.ServerData data_;
+  /**
+   * <pre>
+   * The associated data of the Server.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+   */
+  public boolean hasData() {
+    return data_ != null;
+  }
+  /**
+   * <pre>
+   * The associated data of the Server.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+   */
+  public io.grpc.channelz.v1.ServerData getData() {
+    return data_ == null ? io.grpc.channelz.v1.ServerData.getDefaultInstance() : data_;
+  }
+  /**
+   * <pre>
+   * The associated data of the Server.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+   */
+  public io.grpc.channelz.v1.ServerDataOrBuilder getDataOrBuilder() {
+    return getData();
+  }
+
+  public static final int LISTEN_SOCKET_FIELD_NUMBER = 3;
+  private java.util.List<io.grpc.channelz.v1.SocketRef> listenSocket_;
+  /**
+   * <pre>
+   * The sockets that the server is listening on.  There are no ordering
+   * guarantees.  This may be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+   */
+  public java.util.List<io.grpc.channelz.v1.SocketRef> getListenSocketList() {
+    return listenSocket_;
+  }
+  /**
+   * <pre>
+   * The sockets that the server is listening on.  There are no ordering
+   * guarantees.  This may be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+   */
+  public java.util.List<? extends io.grpc.channelz.v1.SocketRefOrBuilder> 
+      getListenSocketOrBuilderList() {
+    return listenSocket_;
+  }
+  /**
+   * <pre>
+   * The sockets that the server is listening on.  There are no ordering
+   * guarantees.  This may be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+   */
+  public int getListenSocketCount() {
+    return listenSocket_.size();
+  }
+  /**
+   * <pre>
+   * The sockets that the server is listening on.  There are no ordering
+   * guarantees.  This may be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+   */
+  public io.grpc.channelz.v1.SocketRef getListenSocket(int index) {
+    return listenSocket_.get(index);
+  }
+  /**
+   * <pre>
+   * The sockets that the server is listening on.  There are no ordering
+   * guarantees.  This may be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+   */
+  public io.grpc.channelz.v1.SocketRefOrBuilder getListenSocketOrBuilder(
+      int index) {
+    return listenSocket_.get(index);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (ref_ != null) {
+      output.writeMessage(1, getRef());
+    }
+    if (data_ != null) {
+      output.writeMessage(2, getData());
+    }
+    for (int i = 0; i < listenSocket_.size(); i++) {
+      output.writeMessage(3, listenSocket_.get(i));
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (ref_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getRef());
+    }
+    if (data_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(2, getData());
+    }
+    for (int i = 0; i < listenSocket_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(3, listenSocket_.get(i));
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.Server)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.Server other = (io.grpc.channelz.v1.Server) obj;
+
+    boolean result = true;
+    result = result && (hasRef() == other.hasRef());
+    if (hasRef()) {
+      result = result && getRef()
+          .equals(other.getRef());
+    }
+    result = result && (hasData() == other.hasData());
+    if (hasData()) {
+      result = result && getData()
+          .equals(other.getData());
+    }
+    result = result && getListenSocketList()
+        .equals(other.getListenSocketList());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasRef()) {
+      hash = (37 * hash) + REF_FIELD_NUMBER;
+      hash = (53 * hash) + getRef().hashCode();
+    }
+    if (hasData()) {
+      hash = (37 * hash) + DATA_FIELD_NUMBER;
+      hash = (53 * hash) + getData().hashCode();
+    }
+    if (getListenSocketCount() > 0) {
+      hash = (37 * hash) + LISTEN_SOCKET_FIELD_NUMBER;
+      hash = (53 * hash) + getListenSocketList().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.Server parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Server parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Server parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Server parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Server parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Server parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Server parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Server parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Server parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Server parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Server parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Server parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.Server prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Server represents a single server.  There may be multiple servers in a single
+   * program.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.Server}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.Server)
+      io.grpc.channelz.v1.ServerOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Server_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Server_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.Server.class, io.grpc.channelz.v1.Server.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.Server.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+        getListenSocketFieldBuilder();
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (refBuilder_ == null) {
+        ref_ = null;
+      } else {
+        ref_ = null;
+        refBuilder_ = null;
+      }
+      if (dataBuilder_ == null) {
+        data_ = null;
+      } else {
+        data_ = null;
+        dataBuilder_ = null;
+      }
+      if (listenSocketBuilder_ == null) {
+        listenSocket_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000004);
+      } else {
+        listenSocketBuilder_.clear();
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Server_descriptor;
+    }
+
+    public io.grpc.channelz.v1.Server getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.Server.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.Server build() {
+      io.grpc.channelz.v1.Server result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.Server buildPartial() {
+      io.grpc.channelz.v1.Server result = new io.grpc.channelz.v1.Server(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      if (refBuilder_ == null) {
+        result.ref_ = ref_;
+      } else {
+        result.ref_ = refBuilder_.build();
+      }
+      if (dataBuilder_ == null) {
+        result.data_ = data_;
+      } else {
+        result.data_ = dataBuilder_.build();
+      }
+      if (listenSocketBuilder_ == null) {
+        if (((bitField0_ & 0x00000004) == 0x00000004)) {
+          listenSocket_ = java.util.Collections.unmodifiableList(listenSocket_);
+          bitField0_ = (bitField0_ & ~0x00000004);
+        }
+        result.listenSocket_ = listenSocket_;
+      } else {
+        result.listenSocket_ = listenSocketBuilder_.build();
+      }
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.Server) {
+        return mergeFrom((io.grpc.channelz.v1.Server)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.Server other) {
+      if (other == io.grpc.channelz.v1.Server.getDefaultInstance()) return this;
+      if (other.hasRef()) {
+        mergeRef(other.getRef());
+      }
+      if (other.hasData()) {
+        mergeData(other.getData());
+      }
+      if (listenSocketBuilder_ == null) {
+        if (!other.listenSocket_.isEmpty()) {
+          if (listenSocket_.isEmpty()) {
+            listenSocket_ = other.listenSocket_;
+            bitField0_ = (bitField0_ & ~0x00000004);
+          } else {
+            ensureListenSocketIsMutable();
+            listenSocket_.addAll(other.listenSocket_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.listenSocket_.isEmpty()) {
+          if (listenSocketBuilder_.isEmpty()) {
+            listenSocketBuilder_.dispose();
+            listenSocketBuilder_ = null;
+            listenSocket_ = other.listenSocket_;
+            bitField0_ = (bitField0_ & ~0x00000004);
+            listenSocketBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getListenSocketFieldBuilder() : null;
+          } else {
+            listenSocketBuilder_.addAllMessages(other.listenSocket_);
+          }
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.Server parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.Server) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private io.grpc.channelz.v1.ServerRef ref_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ServerRef, io.grpc.channelz.v1.ServerRef.Builder, io.grpc.channelz.v1.ServerRefOrBuilder> refBuilder_;
+    /**
+     * <pre>
+     * The identifier for a Server.  This should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+     */
+    public boolean hasRef() {
+      return refBuilder_ != null || ref_ != null;
+    }
+    /**
+     * <pre>
+     * The identifier for a Server.  This should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.ServerRef getRef() {
+      if (refBuilder_ == null) {
+        return ref_ == null ? io.grpc.channelz.v1.ServerRef.getDefaultInstance() : ref_;
+      } else {
+        return refBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The identifier for a Server.  This should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+     */
+    public Builder setRef(io.grpc.channelz.v1.ServerRef value) {
+      if (refBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ref_ = value;
+        onChanged();
+      } else {
+        refBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for a Server.  This should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+     */
+    public Builder setRef(
+        io.grpc.channelz.v1.ServerRef.Builder builderForValue) {
+      if (refBuilder_ == null) {
+        ref_ = builderForValue.build();
+        onChanged();
+      } else {
+        refBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for a Server.  This should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+     */
+    public Builder mergeRef(io.grpc.channelz.v1.ServerRef value) {
+      if (refBuilder_ == null) {
+        if (ref_ != null) {
+          ref_ =
+            io.grpc.channelz.v1.ServerRef.newBuilder(ref_).mergeFrom(value).buildPartial();
+        } else {
+          ref_ = value;
+        }
+        onChanged();
+      } else {
+        refBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for a Server.  This should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+     */
+    public Builder clearRef() {
+      if (refBuilder_ == null) {
+        ref_ = null;
+        onChanged();
+      } else {
+        ref_ = null;
+        refBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for a Server.  This should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.ServerRef.Builder getRefBuilder() {
+      
+      onChanged();
+      return getRefFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The identifier for a Server.  This should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.ServerRefOrBuilder getRefOrBuilder() {
+      if (refBuilder_ != null) {
+        return refBuilder_.getMessageOrBuilder();
+      } else {
+        return ref_ == null ?
+            io.grpc.channelz.v1.ServerRef.getDefaultInstance() : ref_;
+      }
+    }
+    /**
+     * <pre>
+     * The identifier for a Server.  This should be set.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ServerRef, io.grpc.channelz.v1.ServerRef.Builder, io.grpc.channelz.v1.ServerRefOrBuilder> 
+        getRefFieldBuilder() {
+      if (refBuilder_ == null) {
+        refBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.ServerRef, io.grpc.channelz.v1.ServerRef.Builder, io.grpc.channelz.v1.ServerRefOrBuilder>(
+                getRef(),
+                getParentForChildren(),
+                isClean());
+        ref_ = null;
+      }
+      return refBuilder_;
+    }
+
+    private io.grpc.channelz.v1.ServerData data_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ServerData, io.grpc.channelz.v1.ServerData.Builder, io.grpc.channelz.v1.ServerDataOrBuilder> dataBuilder_;
+    /**
+     * <pre>
+     * The associated data of the Server.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+     */
+    public boolean hasData() {
+      return dataBuilder_ != null || data_ != null;
+    }
+    /**
+     * <pre>
+     * The associated data of the Server.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+     */
+    public io.grpc.channelz.v1.ServerData getData() {
+      if (dataBuilder_ == null) {
+        return data_ == null ? io.grpc.channelz.v1.ServerData.getDefaultInstance() : data_;
+      } else {
+        return dataBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The associated data of the Server.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+     */
+    public Builder setData(io.grpc.channelz.v1.ServerData value) {
+      if (dataBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        data_ = value;
+        onChanged();
+      } else {
+        dataBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The associated data of the Server.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+     */
+    public Builder setData(
+        io.grpc.channelz.v1.ServerData.Builder builderForValue) {
+      if (dataBuilder_ == null) {
+        data_ = builderForValue.build();
+        onChanged();
+      } else {
+        dataBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The associated data of the Server.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+     */
+    public Builder mergeData(io.grpc.channelz.v1.ServerData value) {
+      if (dataBuilder_ == null) {
+        if (data_ != null) {
+          data_ =
+            io.grpc.channelz.v1.ServerData.newBuilder(data_).mergeFrom(value).buildPartial();
+        } else {
+          data_ = value;
+        }
+        onChanged();
+      } else {
+        dataBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The associated data of the Server.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+     */
+    public Builder clearData() {
+      if (dataBuilder_ == null) {
+        data_ = null;
+        onChanged();
+      } else {
+        data_ = null;
+        dataBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The associated data of the Server.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+     */
+    public io.grpc.channelz.v1.ServerData.Builder getDataBuilder() {
+      
+      onChanged();
+      return getDataFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The associated data of the Server.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+     */
+    public io.grpc.channelz.v1.ServerDataOrBuilder getDataOrBuilder() {
+      if (dataBuilder_ != null) {
+        return dataBuilder_.getMessageOrBuilder();
+      } else {
+        return data_ == null ?
+            io.grpc.channelz.v1.ServerData.getDefaultInstance() : data_;
+      }
+    }
+    /**
+     * <pre>
+     * The associated data of the Server.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ServerData, io.grpc.channelz.v1.ServerData.Builder, io.grpc.channelz.v1.ServerDataOrBuilder> 
+        getDataFieldBuilder() {
+      if (dataBuilder_ == null) {
+        dataBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.ServerData, io.grpc.channelz.v1.ServerData.Builder, io.grpc.channelz.v1.ServerDataOrBuilder>(
+                getData(),
+                getParentForChildren(),
+                isClean());
+        data_ = null;
+      }
+      return dataBuilder_;
+    }
+
+    private java.util.List<io.grpc.channelz.v1.SocketRef> listenSocket_ =
+      java.util.Collections.emptyList();
+    private void ensureListenSocketIsMutable() {
+      if (!((bitField0_ & 0x00000004) == 0x00000004)) {
+        listenSocket_ = new java.util.ArrayList<io.grpc.channelz.v1.SocketRef>(listenSocket_);
+        bitField0_ |= 0x00000004;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder> listenSocketBuilder_;
+
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SocketRef> getListenSocketList() {
+      if (listenSocketBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(listenSocket_);
+      } else {
+        return listenSocketBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public int getListenSocketCount() {
+      if (listenSocketBuilder_ == null) {
+        return listenSocket_.size();
+      } else {
+        return listenSocketBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef getListenSocket(int index) {
+      if (listenSocketBuilder_ == null) {
+        return listenSocket_.get(index);
+      } else {
+        return listenSocketBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public Builder setListenSocket(
+        int index, io.grpc.channelz.v1.SocketRef value) {
+      if (listenSocketBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureListenSocketIsMutable();
+        listenSocket_.set(index, value);
+        onChanged();
+      } else {
+        listenSocketBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public Builder setListenSocket(
+        int index, io.grpc.channelz.v1.SocketRef.Builder builderForValue) {
+      if (listenSocketBuilder_ == null) {
+        ensureListenSocketIsMutable();
+        listenSocket_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        listenSocketBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public Builder addListenSocket(io.grpc.channelz.v1.SocketRef value) {
+      if (listenSocketBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureListenSocketIsMutable();
+        listenSocket_.add(value);
+        onChanged();
+      } else {
+        listenSocketBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public Builder addListenSocket(
+        int index, io.grpc.channelz.v1.SocketRef value) {
+      if (listenSocketBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureListenSocketIsMutable();
+        listenSocket_.add(index, value);
+        onChanged();
+      } else {
+        listenSocketBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public Builder addListenSocket(
+        io.grpc.channelz.v1.SocketRef.Builder builderForValue) {
+      if (listenSocketBuilder_ == null) {
+        ensureListenSocketIsMutable();
+        listenSocket_.add(builderForValue.build());
+        onChanged();
+      } else {
+        listenSocketBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public Builder addListenSocket(
+        int index, io.grpc.channelz.v1.SocketRef.Builder builderForValue) {
+      if (listenSocketBuilder_ == null) {
+        ensureListenSocketIsMutable();
+        listenSocket_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        listenSocketBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public Builder addAllListenSocket(
+        java.lang.Iterable<? extends io.grpc.channelz.v1.SocketRef> values) {
+      if (listenSocketBuilder_ == null) {
+        ensureListenSocketIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, listenSocket_);
+        onChanged();
+      } else {
+        listenSocketBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public Builder clearListenSocket() {
+      if (listenSocketBuilder_ == null) {
+        listenSocket_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000004);
+        onChanged();
+      } else {
+        listenSocketBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public Builder removeListenSocket(int index) {
+      if (listenSocketBuilder_ == null) {
+        ensureListenSocketIsMutable();
+        listenSocket_.remove(index);
+        onChanged();
+      } else {
+        listenSocketBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef.Builder getListenSocketBuilder(
+        int index) {
+      return getListenSocketFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public io.grpc.channelz.v1.SocketRefOrBuilder getListenSocketOrBuilder(
+        int index) {
+      if (listenSocketBuilder_ == null) {
+        return listenSocket_.get(index);  } else {
+        return listenSocketBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public java.util.List<? extends io.grpc.channelz.v1.SocketRefOrBuilder> 
+         getListenSocketOrBuilderList() {
+      if (listenSocketBuilder_ != null) {
+        return listenSocketBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(listenSocket_);
+      }
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef.Builder addListenSocketBuilder() {
+      return getListenSocketFieldBuilder().addBuilder(
+          io.grpc.channelz.v1.SocketRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef.Builder addListenSocketBuilder(
+        int index) {
+      return getListenSocketFieldBuilder().addBuilder(
+          index, io.grpc.channelz.v1.SocketRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * The sockets that the server is listening on.  There are no ordering
+     * guarantees.  This may be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SocketRef.Builder> 
+         getListenSocketBuilderList() {
+      return getListenSocketFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder> 
+        getListenSocketFieldBuilder() {
+      if (listenSocketBuilder_ == null) {
+        listenSocketBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder>(
+                listenSocket_,
+                ((bitField0_ & 0x00000004) == 0x00000004),
+                getParentForChildren(),
+                isClean());
+        listenSocket_ = null;
+      }
+      return listenSocketBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.Server)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.Server)
+  private static final io.grpc.channelz.v1.Server DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.Server();
+  }
+
+  public static io.grpc.channelz.v1.Server getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<Server>
+      PARSER = new com.google.protobuf.AbstractParser<Server>() {
+    public Server parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Server(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Server> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Server> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.Server getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ServerData.java b/services/src/generated/main/java/io/grpc/channelz/v1/ServerData.java
new file mode 100644
index 0000000..7777a9f
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ServerData.java
@@ -0,0 +1,1080 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * ServerData is data for a specific Server.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.ServerData}
+ */
+public  final class ServerData extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.ServerData)
+    ServerDataOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ServerData.newBuilder() to construct.
+  private ServerData(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ServerData() {
+    callsStarted_ = 0L;
+    callsSucceeded_ = 0L;
+    callsFailed_ = 0L;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ServerData(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            io.grpc.channelz.v1.ChannelTrace.Builder subBuilder = null;
+            if (trace_ != null) {
+              subBuilder = trace_.toBuilder();
+            }
+            trace_ = input.readMessage(io.grpc.channelz.v1.ChannelTrace.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(trace_);
+              trace_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 16: {
+
+            callsStarted_ = input.readInt64();
+            break;
+          }
+          case 24: {
+
+            callsSucceeded_ = input.readInt64();
+            break;
+          }
+          case 32: {
+
+            callsFailed_ = input.readInt64();
+            break;
+          }
+          case 42: {
+            com.google.protobuf.Timestamp.Builder subBuilder = null;
+            if (lastCallStartedTimestamp_ != null) {
+              subBuilder = lastCallStartedTimestamp_.toBuilder();
+            }
+            lastCallStartedTimestamp_ = input.readMessage(com.google.protobuf.Timestamp.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(lastCallStartedTimestamp_);
+              lastCallStartedTimestamp_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ServerData_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ServerData_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.ServerData.class, io.grpc.channelz.v1.ServerData.Builder.class);
+  }
+
+  public static final int TRACE_FIELD_NUMBER = 1;
+  private io.grpc.channelz.v1.ChannelTrace trace_;
+  /**
+   * <pre>
+   * A trace of recent events on the server.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+   */
+  public boolean hasTrace() {
+    return trace_ != null;
+  }
+  /**
+   * <pre>
+   * A trace of recent events on the server.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+   */
+  public io.grpc.channelz.v1.ChannelTrace getTrace() {
+    return trace_ == null ? io.grpc.channelz.v1.ChannelTrace.getDefaultInstance() : trace_;
+  }
+  /**
+   * <pre>
+   * A trace of recent events on the server.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+   */
+  public io.grpc.channelz.v1.ChannelTraceOrBuilder getTraceOrBuilder() {
+    return getTrace();
+  }
+
+  public static final int CALLS_STARTED_FIELD_NUMBER = 2;
+  private long callsStarted_;
+  /**
+   * <pre>
+   * The number of incoming calls started on the server
+   * </pre>
+   *
+   * <code>int64 calls_started = 2;</code>
+   */
+  public long getCallsStarted() {
+    return callsStarted_;
+  }
+
+  public static final int CALLS_SUCCEEDED_FIELD_NUMBER = 3;
+  private long callsSucceeded_;
+  /**
+   * <pre>
+   * The number of incoming calls that have completed with an OK status
+   * </pre>
+   *
+   * <code>int64 calls_succeeded = 3;</code>
+   */
+  public long getCallsSucceeded() {
+    return callsSucceeded_;
+  }
+
+  public static final int CALLS_FAILED_FIELD_NUMBER = 4;
+  private long callsFailed_;
+  /**
+   * <pre>
+   * The number of incoming calls that have a completed with a non-OK status
+   * </pre>
+   *
+   * <code>int64 calls_failed = 4;</code>
+   */
+  public long getCallsFailed() {
+    return callsFailed_;
+  }
+
+  public static final int LAST_CALL_STARTED_TIMESTAMP_FIELD_NUMBER = 5;
+  private com.google.protobuf.Timestamp lastCallStartedTimestamp_;
+  /**
+   * <pre>
+   * The last time a call was started on the server.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+   */
+  public boolean hasLastCallStartedTimestamp() {
+    return lastCallStartedTimestamp_ != null;
+  }
+  /**
+   * <pre>
+   * The last time a call was started on the server.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+   */
+  public com.google.protobuf.Timestamp getLastCallStartedTimestamp() {
+    return lastCallStartedTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : lastCallStartedTimestamp_;
+  }
+  /**
+   * <pre>
+   * The last time a call was started on the server.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+   */
+  public com.google.protobuf.TimestampOrBuilder getLastCallStartedTimestampOrBuilder() {
+    return getLastCallStartedTimestamp();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (trace_ != null) {
+      output.writeMessage(1, getTrace());
+    }
+    if (callsStarted_ != 0L) {
+      output.writeInt64(2, callsStarted_);
+    }
+    if (callsSucceeded_ != 0L) {
+      output.writeInt64(3, callsSucceeded_);
+    }
+    if (callsFailed_ != 0L) {
+      output.writeInt64(4, callsFailed_);
+    }
+    if (lastCallStartedTimestamp_ != null) {
+      output.writeMessage(5, getLastCallStartedTimestamp());
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (trace_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getTrace());
+    }
+    if (callsStarted_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(2, callsStarted_);
+    }
+    if (callsSucceeded_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(3, callsSucceeded_);
+    }
+    if (callsFailed_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(4, callsFailed_);
+    }
+    if (lastCallStartedTimestamp_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(5, getLastCallStartedTimestamp());
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.ServerData)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.ServerData other = (io.grpc.channelz.v1.ServerData) obj;
+
+    boolean result = true;
+    result = result && (hasTrace() == other.hasTrace());
+    if (hasTrace()) {
+      result = result && getTrace()
+          .equals(other.getTrace());
+    }
+    result = result && (getCallsStarted()
+        == other.getCallsStarted());
+    result = result && (getCallsSucceeded()
+        == other.getCallsSucceeded());
+    result = result && (getCallsFailed()
+        == other.getCallsFailed());
+    result = result && (hasLastCallStartedTimestamp() == other.hasLastCallStartedTimestamp());
+    if (hasLastCallStartedTimestamp()) {
+      result = result && getLastCallStartedTimestamp()
+          .equals(other.getLastCallStartedTimestamp());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasTrace()) {
+      hash = (37 * hash) + TRACE_FIELD_NUMBER;
+      hash = (53 * hash) + getTrace().hashCode();
+    }
+    hash = (37 * hash) + CALLS_STARTED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getCallsStarted());
+    hash = (37 * hash) + CALLS_SUCCEEDED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getCallsSucceeded());
+    hash = (37 * hash) + CALLS_FAILED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getCallsFailed());
+    if (hasLastCallStartedTimestamp()) {
+      hash = (37 * hash) + LAST_CALL_STARTED_TIMESTAMP_FIELD_NUMBER;
+      hash = (53 * hash) + getLastCallStartedTimestamp().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.ServerData parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ServerData parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ServerData parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ServerData parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ServerData parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ServerData parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ServerData parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ServerData parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ServerData parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ServerData parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ServerData parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ServerData parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.ServerData prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * ServerData is data for a specific Server.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.ServerData}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.ServerData)
+      io.grpc.channelz.v1.ServerDataOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ServerData_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ServerData_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.ServerData.class, io.grpc.channelz.v1.ServerData.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.ServerData.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (traceBuilder_ == null) {
+        trace_ = null;
+      } else {
+        trace_ = null;
+        traceBuilder_ = null;
+      }
+      callsStarted_ = 0L;
+
+      callsSucceeded_ = 0L;
+
+      callsFailed_ = 0L;
+
+      if (lastCallStartedTimestampBuilder_ == null) {
+        lastCallStartedTimestamp_ = null;
+      } else {
+        lastCallStartedTimestamp_ = null;
+        lastCallStartedTimestampBuilder_ = null;
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ServerData_descriptor;
+    }
+
+    public io.grpc.channelz.v1.ServerData getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.ServerData.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.ServerData build() {
+      io.grpc.channelz.v1.ServerData result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.ServerData buildPartial() {
+      io.grpc.channelz.v1.ServerData result = new io.grpc.channelz.v1.ServerData(this);
+      if (traceBuilder_ == null) {
+        result.trace_ = trace_;
+      } else {
+        result.trace_ = traceBuilder_.build();
+      }
+      result.callsStarted_ = callsStarted_;
+      result.callsSucceeded_ = callsSucceeded_;
+      result.callsFailed_ = callsFailed_;
+      if (lastCallStartedTimestampBuilder_ == null) {
+        result.lastCallStartedTimestamp_ = lastCallStartedTimestamp_;
+      } else {
+        result.lastCallStartedTimestamp_ = lastCallStartedTimestampBuilder_.build();
+      }
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.ServerData) {
+        return mergeFrom((io.grpc.channelz.v1.ServerData)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.ServerData other) {
+      if (other == io.grpc.channelz.v1.ServerData.getDefaultInstance()) return this;
+      if (other.hasTrace()) {
+        mergeTrace(other.getTrace());
+      }
+      if (other.getCallsStarted() != 0L) {
+        setCallsStarted(other.getCallsStarted());
+      }
+      if (other.getCallsSucceeded() != 0L) {
+        setCallsSucceeded(other.getCallsSucceeded());
+      }
+      if (other.getCallsFailed() != 0L) {
+        setCallsFailed(other.getCallsFailed());
+      }
+      if (other.hasLastCallStartedTimestamp()) {
+        mergeLastCallStartedTimestamp(other.getLastCallStartedTimestamp());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.ServerData parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.ServerData) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private io.grpc.channelz.v1.ChannelTrace trace_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelTrace, io.grpc.channelz.v1.ChannelTrace.Builder, io.grpc.channelz.v1.ChannelTraceOrBuilder> traceBuilder_;
+    /**
+     * <pre>
+     * A trace of recent events on the server.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+     */
+    public boolean hasTrace() {
+      return traceBuilder_ != null || trace_ != null;
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the server.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+     */
+    public io.grpc.channelz.v1.ChannelTrace getTrace() {
+      if (traceBuilder_ == null) {
+        return trace_ == null ? io.grpc.channelz.v1.ChannelTrace.getDefaultInstance() : trace_;
+      } else {
+        return traceBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the server.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+     */
+    public Builder setTrace(io.grpc.channelz.v1.ChannelTrace value) {
+      if (traceBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        trace_ = value;
+        onChanged();
+      } else {
+        traceBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the server.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+     */
+    public Builder setTrace(
+        io.grpc.channelz.v1.ChannelTrace.Builder builderForValue) {
+      if (traceBuilder_ == null) {
+        trace_ = builderForValue.build();
+        onChanged();
+      } else {
+        traceBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the server.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+     */
+    public Builder mergeTrace(io.grpc.channelz.v1.ChannelTrace value) {
+      if (traceBuilder_ == null) {
+        if (trace_ != null) {
+          trace_ =
+            io.grpc.channelz.v1.ChannelTrace.newBuilder(trace_).mergeFrom(value).buildPartial();
+        } else {
+          trace_ = value;
+        }
+        onChanged();
+      } else {
+        traceBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the server.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+     */
+    public Builder clearTrace() {
+      if (traceBuilder_ == null) {
+        trace_ = null;
+        onChanged();
+      } else {
+        trace_ = null;
+        traceBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the server.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+     */
+    public io.grpc.channelz.v1.ChannelTrace.Builder getTraceBuilder() {
+      
+      onChanged();
+      return getTraceFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the server.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+     */
+    public io.grpc.channelz.v1.ChannelTraceOrBuilder getTraceOrBuilder() {
+      if (traceBuilder_ != null) {
+        return traceBuilder_.getMessageOrBuilder();
+      } else {
+        return trace_ == null ?
+            io.grpc.channelz.v1.ChannelTrace.getDefaultInstance() : trace_;
+      }
+    }
+    /**
+     * <pre>
+     * A trace of recent events on the server.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelTrace, io.grpc.channelz.v1.ChannelTrace.Builder, io.grpc.channelz.v1.ChannelTraceOrBuilder> 
+        getTraceFieldBuilder() {
+      if (traceBuilder_ == null) {
+        traceBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.ChannelTrace, io.grpc.channelz.v1.ChannelTrace.Builder, io.grpc.channelz.v1.ChannelTraceOrBuilder>(
+                getTrace(),
+                getParentForChildren(),
+                isClean());
+        trace_ = null;
+      }
+      return traceBuilder_;
+    }
+
+    private long callsStarted_ ;
+    /**
+     * <pre>
+     * The number of incoming calls started on the server
+     * </pre>
+     *
+     * <code>int64 calls_started = 2;</code>
+     */
+    public long getCallsStarted() {
+      return callsStarted_;
+    }
+    /**
+     * <pre>
+     * The number of incoming calls started on the server
+     * </pre>
+     *
+     * <code>int64 calls_started = 2;</code>
+     */
+    public Builder setCallsStarted(long value) {
+      
+      callsStarted_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The number of incoming calls started on the server
+     * </pre>
+     *
+     * <code>int64 calls_started = 2;</code>
+     */
+    public Builder clearCallsStarted() {
+      
+      callsStarted_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long callsSucceeded_ ;
+    /**
+     * <pre>
+     * The number of incoming calls that have completed with an OK status
+     * </pre>
+     *
+     * <code>int64 calls_succeeded = 3;</code>
+     */
+    public long getCallsSucceeded() {
+      return callsSucceeded_;
+    }
+    /**
+     * <pre>
+     * The number of incoming calls that have completed with an OK status
+     * </pre>
+     *
+     * <code>int64 calls_succeeded = 3;</code>
+     */
+    public Builder setCallsSucceeded(long value) {
+      
+      callsSucceeded_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The number of incoming calls that have completed with an OK status
+     * </pre>
+     *
+     * <code>int64 calls_succeeded = 3;</code>
+     */
+    public Builder clearCallsSucceeded() {
+      
+      callsSucceeded_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long callsFailed_ ;
+    /**
+     * <pre>
+     * The number of incoming calls that have a completed with a non-OK status
+     * </pre>
+     *
+     * <code>int64 calls_failed = 4;</code>
+     */
+    public long getCallsFailed() {
+      return callsFailed_;
+    }
+    /**
+     * <pre>
+     * The number of incoming calls that have a completed with a non-OK status
+     * </pre>
+     *
+     * <code>int64 calls_failed = 4;</code>
+     */
+    public Builder setCallsFailed(long value) {
+      
+      callsFailed_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The number of incoming calls that have a completed with a non-OK status
+     * </pre>
+     *
+     * <code>int64 calls_failed = 4;</code>
+     */
+    public Builder clearCallsFailed() {
+      
+      callsFailed_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.Timestamp lastCallStartedTimestamp_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> lastCallStartedTimestampBuilder_;
+    /**
+     * <pre>
+     * The last time a call was started on the server.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+     */
+    public boolean hasLastCallStartedTimestamp() {
+      return lastCallStartedTimestampBuilder_ != null || lastCallStartedTimestamp_ != null;
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the server.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+     */
+    public com.google.protobuf.Timestamp getLastCallStartedTimestamp() {
+      if (lastCallStartedTimestampBuilder_ == null) {
+        return lastCallStartedTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : lastCallStartedTimestamp_;
+      } else {
+        return lastCallStartedTimestampBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the server.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+     */
+    public Builder setLastCallStartedTimestamp(com.google.protobuf.Timestamp value) {
+      if (lastCallStartedTimestampBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        lastCallStartedTimestamp_ = value;
+        onChanged();
+      } else {
+        lastCallStartedTimestampBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the server.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+     */
+    public Builder setLastCallStartedTimestamp(
+        com.google.protobuf.Timestamp.Builder builderForValue) {
+      if (lastCallStartedTimestampBuilder_ == null) {
+        lastCallStartedTimestamp_ = builderForValue.build();
+        onChanged();
+      } else {
+        lastCallStartedTimestampBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the server.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+     */
+    public Builder mergeLastCallStartedTimestamp(com.google.protobuf.Timestamp value) {
+      if (lastCallStartedTimestampBuilder_ == null) {
+        if (lastCallStartedTimestamp_ != null) {
+          lastCallStartedTimestamp_ =
+            com.google.protobuf.Timestamp.newBuilder(lastCallStartedTimestamp_).mergeFrom(value).buildPartial();
+        } else {
+          lastCallStartedTimestamp_ = value;
+        }
+        onChanged();
+      } else {
+        lastCallStartedTimestampBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the server.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+     */
+    public Builder clearLastCallStartedTimestamp() {
+      if (lastCallStartedTimestampBuilder_ == null) {
+        lastCallStartedTimestamp_ = null;
+        onChanged();
+      } else {
+        lastCallStartedTimestamp_ = null;
+        lastCallStartedTimestampBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the server.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+     */
+    public com.google.protobuf.Timestamp.Builder getLastCallStartedTimestampBuilder() {
+      
+      onChanged();
+      return getLastCallStartedTimestampFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the server.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+     */
+    public com.google.protobuf.TimestampOrBuilder getLastCallStartedTimestampOrBuilder() {
+      if (lastCallStartedTimestampBuilder_ != null) {
+        return lastCallStartedTimestampBuilder_.getMessageOrBuilder();
+      } else {
+        return lastCallStartedTimestamp_ == null ?
+            com.google.protobuf.Timestamp.getDefaultInstance() : lastCallStartedTimestamp_;
+      }
+    }
+    /**
+     * <pre>
+     * The last time a call was started on the server.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> 
+        getLastCallStartedTimestampFieldBuilder() {
+      if (lastCallStartedTimestampBuilder_ == null) {
+        lastCallStartedTimestampBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder>(
+                getLastCallStartedTimestamp(),
+                getParentForChildren(),
+                isClean());
+        lastCallStartedTimestamp_ = null;
+      }
+      return lastCallStartedTimestampBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.ServerData)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.ServerData)
+  private static final io.grpc.channelz.v1.ServerData DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.ServerData();
+  }
+
+  public static io.grpc.channelz.v1.ServerData getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ServerData>
+      PARSER = new com.google.protobuf.AbstractParser<ServerData>() {
+    public ServerData parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ServerData(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ServerData> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ServerData> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.ServerData getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ServerDataOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/ServerDataOrBuilder.java
new file mode 100644
index 0000000..99c6f6f
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ServerDataOrBuilder.java
@@ -0,0 +1,86 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface ServerDataOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.ServerData)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * A trace of recent events on the server.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+   */
+  boolean hasTrace();
+  /**
+   * <pre>
+   * A trace of recent events on the server.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+   */
+  io.grpc.channelz.v1.ChannelTrace getTrace();
+  /**
+   * <pre>
+   * A trace of recent events on the server.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelTrace trace = 1;</code>
+   */
+  io.grpc.channelz.v1.ChannelTraceOrBuilder getTraceOrBuilder();
+
+  /**
+   * <pre>
+   * The number of incoming calls started on the server
+   * </pre>
+   *
+   * <code>int64 calls_started = 2;</code>
+   */
+  long getCallsStarted();
+
+  /**
+   * <pre>
+   * The number of incoming calls that have completed with an OK status
+   * </pre>
+   *
+   * <code>int64 calls_succeeded = 3;</code>
+   */
+  long getCallsSucceeded();
+
+  /**
+   * <pre>
+   * The number of incoming calls that have a completed with a non-OK status
+   * </pre>
+   *
+   * <code>int64 calls_failed = 4;</code>
+   */
+  long getCallsFailed();
+
+  /**
+   * <pre>
+   * The last time a call was started on the server.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+   */
+  boolean hasLastCallStartedTimestamp();
+  /**
+   * <pre>
+   * The last time a call was started on the server.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+   */
+  com.google.protobuf.Timestamp getLastCallStartedTimestamp();
+  /**
+   * <pre>
+   * The last time a call was started on the server.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_call_started_timestamp = 5;</code>
+   */
+  com.google.protobuf.TimestampOrBuilder getLastCallStartedTimestampOrBuilder();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ServerOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/ServerOrBuilder.java
new file mode 100644
index 0000000..fb91778
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ServerOrBuilder.java
@@ -0,0 +1,108 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface ServerOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.Server)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The identifier for a Server.  This should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+   */
+  boolean hasRef();
+  /**
+   * <pre>
+   * The identifier for a Server.  This should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+   */
+  io.grpc.channelz.v1.ServerRef getRef();
+  /**
+   * <pre>
+   * The identifier for a Server.  This should be set.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ServerRef ref = 1;</code>
+   */
+  io.grpc.channelz.v1.ServerRefOrBuilder getRefOrBuilder();
+
+  /**
+   * <pre>
+   * The associated data of the Server.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+   */
+  boolean hasData();
+  /**
+   * <pre>
+   * The associated data of the Server.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+   */
+  io.grpc.channelz.v1.ServerData getData();
+  /**
+   * <pre>
+   * The associated data of the Server.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ServerData data = 2;</code>
+   */
+  io.grpc.channelz.v1.ServerDataOrBuilder getDataOrBuilder();
+
+  /**
+   * <pre>
+   * The sockets that the server is listening on.  There are no ordering
+   * guarantees.  This may be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+   */
+  java.util.List<io.grpc.channelz.v1.SocketRef> 
+      getListenSocketList();
+  /**
+   * <pre>
+   * The sockets that the server is listening on.  There are no ordering
+   * guarantees.  This may be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+   */
+  io.grpc.channelz.v1.SocketRef getListenSocket(int index);
+  /**
+   * <pre>
+   * The sockets that the server is listening on.  There are no ordering
+   * guarantees.  This may be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+   */
+  int getListenSocketCount();
+  /**
+   * <pre>
+   * The sockets that the server is listening on.  There are no ordering
+   * guarantees.  This may be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+   */
+  java.util.List<? extends io.grpc.channelz.v1.SocketRefOrBuilder> 
+      getListenSocketOrBuilderList();
+  /**
+   * <pre>
+   * The sockets that the server is listening on.  There are no ordering
+   * guarantees.  This may be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef listen_socket = 3;</code>
+   */
+  io.grpc.channelz.v1.SocketRefOrBuilder getListenSocketOrBuilder(
+      int index);
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ServerRef.java b/services/src/generated/main/java/io/grpc/channelz/v1/ServerRef.java
new file mode 100644
index 0000000..ce9bda7
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ServerRef.java
@@ -0,0 +1,627 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * ServerRef is a reference to a Server.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.ServerRef}
+ */
+public  final class ServerRef extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.ServerRef)
+    ServerRefOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ServerRef.newBuilder() to construct.
+  private ServerRef(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ServerRef() {
+    serverId_ = 0L;
+    name_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ServerRef(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 40: {
+
+            serverId_ = input.readInt64();
+            break;
+          }
+          case 50: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            name_ = s;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ServerRef_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ServerRef_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.ServerRef.class, io.grpc.channelz.v1.ServerRef.Builder.class);
+  }
+
+  public static final int SERVER_ID_FIELD_NUMBER = 5;
+  private long serverId_;
+  /**
+   * <pre>
+   * A globally unique identifier for this server.  Must be a positive number.
+   * </pre>
+   *
+   * <code>int64 server_id = 5;</code>
+   */
+  public long getServerId() {
+    return serverId_;
+  }
+
+  public static final int NAME_FIELD_NUMBER = 6;
+  private volatile java.lang.Object name_;
+  /**
+   * <pre>
+   * An optional name associated with the server.
+   * </pre>
+   *
+   * <code>string name = 6;</code>
+   */
+  public java.lang.String getName() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      name_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * An optional name associated with the server.
+   * </pre>
+   *
+   * <code>string name = 6;</code>
+   */
+  public com.google.protobuf.ByteString
+      getNameBytes() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      name_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (serverId_ != 0L) {
+      output.writeInt64(5, serverId_);
+    }
+    if (!getNameBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 6, name_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (serverId_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(5, serverId_);
+    }
+    if (!getNameBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(6, name_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.ServerRef)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.ServerRef other = (io.grpc.channelz.v1.ServerRef) obj;
+
+    boolean result = true;
+    result = result && (getServerId()
+        == other.getServerId());
+    result = result && getName()
+        .equals(other.getName());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + SERVER_ID_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getServerId());
+    hash = (37 * hash) + NAME_FIELD_NUMBER;
+    hash = (53 * hash) + getName().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.ServerRef parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ServerRef parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ServerRef parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ServerRef parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ServerRef parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.ServerRef parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ServerRef parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ServerRef parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ServerRef parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ServerRef parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.ServerRef parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.ServerRef parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.ServerRef prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * ServerRef is a reference to a Server.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.ServerRef}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.ServerRef)
+      io.grpc.channelz.v1.ServerRefOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ServerRef_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ServerRef_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.ServerRef.class, io.grpc.channelz.v1.ServerRef.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.ServerRef.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      serverId_ = 0L;
+
+      name_ = "";
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_ServerRef_descriptor;
+    }
+
+    public io.grpc.channelz.v1.ServerRef getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.ServerRef.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.ServerRef build() {
+      io.grpc.channelz.v1.ServerRef result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.ServerRef buildPartial() {
+      io.grpc.channelz.v1.ServerRef result = new io.grpc.channelz.v1.ServerRef(this);
+      result.serverId_ = serverId_;
+      result.name_ = name_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.ServerRef) {
+        return mergeFrom((io.grpc.channelz.v1.ServerRef)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.ServerRef other) {
+      if (other == io.grpc.channelz.v1.ServerRef.getDefaultInstance()) return this;
+      if (other.getServerId() != 0L) {
+        setServerId(other.getServerId());
+      }
+      if (!other.getName().isEmpty()) {
+        name_ = other.name_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.ServerRef parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.ServerRef) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private long serverId_ ;
+    /**
+     * <pre>
+     * A globally unique identifier for this server.  Must be a positive number.
+     * </pre>
+     *
+     * <code>int64 server_id = 5;</code>
+     */
+    public long getServerId() {
+      return serverId_;
+    }
+    /**
+     * <pre>
+     * A globally unique identifier for this server.  Must be a positive number.
+     * </pre>
+     *
+     * <code>int64 server_id = 5;</code>
+     */
+    public Builder setServerId(long value) {
+      
+      serverId_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * A globally unique identifier for this server.  Must be a positive number.
+     * </pre>
+     *
+     * <code>int64 server_id = 5;</code>
+     */
+    public Builder clearServerId() {
+      
+      serverId_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object name_ = "";
+    /**
+     * <pre>
+     * An optional name associated with the server.
+     * </pre>
+     *
+     * <code>string name = 6;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        name_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An optional name associated with the server.
+     * </pre>
+     *
+     * <code>string name = 6;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An optional name associated with the server.
+     * </pre>
+     *
+     * <code>string name = 6;</code>
+     */
+    public Builder setName(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      name_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An optional name associated with the server.
+     * </pre>
+     *
+     * <code>string name = 6;</code>
+     */
+    public Builder clearName() {
+      
+      name_ = getDefaultInstance().getName();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An optional name associated with the server.
+     * </pre>
+     *
+     * <code>string name = 6;</code>
+     */
+    public Builder setNameBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      name_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.ServerRef)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.ServerRef)
+  private static final io.grpc.channelz.v1.ServerRef DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.ServerRef();
+  }
+
+  public static io.grpc.channelz.v1.ServerRef getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ServerRef>
+      PARSER = new com.google.protobuf.AbstractParser<ServerRef>() {
+    public ServerRef parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ServerRef(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ServerRef> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ServerRef> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.ServerRef getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/ServerRefOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/ServerRefOrBuilder.java
new file mode 100644
index 0000000..40f247e
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/ServerRefOrBuilder.java
@@ -0,0 +1,36 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface ServerRefOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.ServerRef)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * A globally unique identifier for this server.  Must be a positive number.
+   * </pre>
+   *
+   * <code>int64 server_id = 5;</code>
+   */
+  long getServerId();
+
+  /**
+   * <pre>
+   * An optional name associated with the server.
+   * </pre>
+   *
+   * <code>string name = 6;</code>
+   */
+  java.lang.String getName();
+  /**
+   * <pre>
+   * An optional name associated with the server.
+   * </pre>
+   *
+   * <code>string name = 6;</code>
+   */
+  com.google.protobuf.ByteString
+      getNameBytes();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/Socket.java b/services/src/generated/main/java/io/grpc/channelz/v1/Socket.java
new file mode 100644
index 0000000..cdff1cc
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/Socket.java
@@ -0,0 +1,1716 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * Information about an actual connection.  Pronounced "sock-ay".
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.Socket}
+ */
+public  final class Socket extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.Socket)
+    SocketOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Socket.newBuilder() to construct.
+  private Socket(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Socket() {
+    remoteName_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Socket(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            io.grpc.channelz.v1.SocketRef.Builder subBuilder = null;
+            if (ref_ != null) {
+              subBuilder = ref_.toBuilder();
+            }
+            ref_ = input.readMessage(io.grpc.channelz.v1.SocketRef.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(ref_);
+              ref_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 18: {
+            io.grpc.channelz.v1.SocketData.Builder subBuilder = null;
+            if (data_ != null) {
+              subBuilder = data_.toBuilder();
+            }
+            data_ = input.readMessage(io.grpc.channelz.v1.SocketData.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(data_);
+              data_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 26: {
+            io.grpc.channelz.v1.Address.Builder subBuilder = null;
+            if (local_ != null) {
+              subBuilder = local_.toBuilder();
+            }
+            local_ = input.readMessage(io.grpc.channelz.v1.Address.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(local_);
+              local_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 34: {
+            io.grpc.channelz.v1.Address.Builder subBuilder = null;
+            if (remote_ != null) {
+              subBuilder = remote_.toBuilder();
+            }
+            remote_ = input.readMessage(io.grpc.channelz.v1.Address.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(remote_);
+              remote_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 42: {
+            io.grpc.channelz.v1.Security.Builder subBuilder = null;
+            if (security_ != null) {
+              subBuilder = security_.toBuilder();
+            }
+            security_ = input.readMessage(io.grpc.channelz.v1.Security.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(security_);
+              security_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 50: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            remoteName_ = s;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Socket_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Socket_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.Socket.class, io.grpc.channelz.v1.Socket.Builder.class);
+  }
+
+  public static final int REF_FIELD_NUMBER = 1;
+  private io.grpc.channelz.v1.SocketRef ref_;
+  /**
+   * <pre>
+   * The identifier for the Socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+   */
+  public boolean hasRef() {
+    return ref_ != null;
+  }
+  /**
+   * <pre>
+   * The identifier for the Socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+   */
+  public io.grpc.channelz.v1.SocketRef getRef() {
+    return ref_ == null ? io.grpc.channelz.v1.SocketRef.getDefaultInstance() : ref_;
+  }
+  /**
+   * <pre>
+   * The identifier for the Socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+   */
+  public io.grpc.channelz.v1.SocketRefOrBuilder getRefOrBuilder() {
+    return getRef();
+  }
+
+  public static final int DATA_FIELD_NUMBER = 2;
+  private io.grpc.channelz.v1.SocketData data_;
+  /**
+   * <pre>
+   * Data specific to this Socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+   */
+  public boolean hasData() {
+    return data_ != null;
+  }
+  /**
+   * <pre>
+   * Data specific to this Socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+   */
+  public io.grpc.channelz.v1.SocketData getData() {
+    return data_ == null ? io.grpc.channelz.v1.SocketData.getDefaultInstance() : data_;
+  }
+  /**
+   * <pre>
+   * Data specific to this Socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+   */
+  public io.grpc.channelz.v1.SocketDataOrBuilder getDataOrBuilder() {
+    return getData();
+  }
+
+  public static final int LOCAL_FIELD_NUMBER = 3;
+  private io.grpc.channelz.v1.Address local_;
+  /**
+   * <pre>
+   * The locally bound address.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Address local = 3;</code>
+   */
+  public boolean hasLocal() {
+    return local_ != null;
+  }
+  /**
+   * <pre>
+   * The locally bound address.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Address local = 3;</code>
+   */
+  public io.grpc.channelz.v1.Address getLocal() {
+    return local_ == null ? io.grpc.channelz.v1.Address.getDefaultInstance() : local_;
+  }
+  /**
+   * <pre>
+   * The locally bound address.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Address local = 3;</code>
+   */
+  public io.grpc.channelz.v1.AddressOrBuilder getLocalOrBuilder() {
+    return getLocal();
+  }
+
+  public static final int REMOTE_FIELD_NUMBER = 4;
+  private io.grpc.channelz.v1.Address remote_;
+  /**
+   * <pre>
+   * The remote bound address.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Address remote = 4;</code>
+   */
+  public boolean hasRemote() {
+    return remote_ != null;
+  }
+  /**
+   * <pre>
+   * The remote bound address.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Address remote = 4;</code>
+   */
+  public io.grpc.channelz.v1.Address getRemote() {
+    return remote_ == null ? io.grpc.channelz.v1.Address.getDefaultInstance() : remote_;
+  }
+  /**
+   * <pre>
+   * The remote bound address.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Address remote = 4;</code>
+   */
+  public io.grpc.channelz.v1.AddressOrBuilder getRemoteOrBuilder() {
+    return getRemote();
+  }
+
+  public static final int SECURITY_FIELD_NUMBER = 5;
+  private io.grpc.channelz.v1.Security security_;
+  /**
+   * <pre>
+   * Security details for this socket.  May be absent if not available, or
+   * there is no security on the socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Security security = 5;</code>
+   */
+  public boolean hasSecurity() {
+    return security_ != null;
+  }
+  /**
+   * <pre>
+   * Security details for this socket.  May be absent if not available, or
+   * there is no security on the socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Security security = 5;</code>
+   */
+  public io.grpc.channelz.v1.Security getSecurity() {
+    return security_ == null ? io.grpc.channelz.v1.Security.getDefaultInstance() : security_;
+  }
+  /**
+   * <pre>
+   * Security details for this socket.  May be absent if not available, or
+   * there is no security on the socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Security security = 5;</code>
+   */
+  public io.grpc.channelz.v1.SecurityOrBuilder getSecurityOrBuilder() {
+    return getSecurity();
+  }
+
+  public static final int REMOTE_NAME_FIELD_NUMBER = 6;
+  private volatile java.lang.Object remoteName_;
+  /**
+   * <pre>
+   * Optional, represents the name of the remote endpoint, if different than
+   * the original target name.
+   * </pre>
+   *
+   * <code>string remote_name = 6;</code>
+   */
+  public java.lang.String getRemoteName() {
+    java.lang.Object ref = remoteName_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      remoteName_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * Optional, represents the name of the remote endpoint, if different than
+   * the original target name.
+   * </pre>
+   *
+   * <code>string remote_name = 6;</code>
+   */
+  public com.google.protobuf.ByteString
+      getRemoteNameBytes() {
+    java.lang.Object ref = remoteName_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      remoteName_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (ref_ != null) {
+      output.writeMessage(1, getRef());
+    }
+    if (data_ != null) {
+      output.writeMessage(2, getData());
+    }
+    if (local_ != null) {
+      output.writeMessage(3, getLocal());
+    }
+    if (remote_ != null) {
+      output.writeMessage(4, getRemote());
+    }
+    if (security_ != null) {
+      output.writeMessage(5, getSecurity());
+    }
+    if (!getRemoteNameBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 6, remoteName_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (ref_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getRef());
+    }
+    if (data_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(2, getData());
+    }
+    if (local_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(3, getLocal());
+    }
+    if (remote_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(4, getRemote());
+    }
+    if (security_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(5, getSecurity());
+    }
+    if (!getRemoteNameBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(6, remoteName_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.Socket)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.Socket other = (io.grpc.channelz.v1.Socket) obj;
+
+    boolean result = true;
+    result = result && (hasRef() == other.hasRef());
+    if (hasRef()) {
+      result = result && getRef()
+          .equals(other.getRef());
+    }
+    result = result && (hasData() == other.hasData());
+    if (hasData()) {
+      result = result && getData()
+          .equals(other.getData());
+    }
+    result = result && (hasLocal() == other.hasLocal());
+    if (hasLocal()) {
+      result = result && getLocal()
+          .equals(other.getLocal());
+    }
+    result = result && (hasRemote() == other.hasRemote());
+    if (hasRemote()) {
+      result = result && getRemote()
+          .equals(other.getRemote());
+    }
+    result = result && (hasSecurity() == other.hasSecurity());
+    if (hasSecurity()) {
+      result = result && getSecurity()
+          .equals(other.getSecurity());
+    }
+    result = result && getRemoteName()
+        .equals(other.getRemoteName());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasRef()) {
+      hash = (37 * hash) + REF_FIELD_NUMBER;
+      hash = (53 * hash) + getRef().hashCode();
+    }
+    if (hasData()) {
+      hash = (37 * hash) + DATA_FIELD_NUMBER;
+      hash = (53 * hash) + getData().hashCode();
+    }
+    if (hasLocal()) {
+      hash = (37 * hash) + LOCAL_FIELD_NUMBER;
+      hash = (53 * hash) + getLocal().hashCode();
+    }
+    if (hasRemote()) {
+      hash = (37 * hash) + REMOTE_FIELD_NUMBER;
+      hash = (53 * hash) + getRemote().hashCode();
+    }
+    if (hasSecurity()) {
+      hash = (37 * hash) + SECURITY_FIELD_NUMBER;
+      hash = (53 * hash) + getSecurity().hashCode();
+    }
+    hash = (37 * hash) + REMOTE_NAME_FIELD_NUMBER;
+    hash = (53 * hash) + getRemoteName().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.Socket parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Socket parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Socket parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Socket parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Socket parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Socket parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Socket parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Socket parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Socket parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Socket parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Socket parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Socket parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.Socket prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Information about an actual connection.  Pronounced "sock-ay".
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.Socket}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.Socket)
+      io.grpc.channelz.v1.SocketOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Socket_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Socket_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.Socket.class, io.grpc.channelz.v1.Socket.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.Socket.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (refBuilder_ == null) {
+        ref_ = null;
+      } else {
+        ref_ = null;
+        refBuilder_ = null;
+      }
+      if (dataBuilder_ == null) {
+        data_ = null;
+      } else {
+        data_ = null;
+        dataBuilder_ = null;
+      }
+      if (localBuilder_ == null) {
+        local_ = null;
+      } else {
+        local_ = null;
+        localBuilder_ = null;
+      }
+      if (remoteBuilder_ == null) {
+        remote_ = null;
+      } else {
+        remote_ = null;
+        remoteBuilder_ = null;
+      }
+      if (securityBuilder_ == null) {
+        security_ = null;
+      } else {
+        security_ = null;
+        securityBuilder_ = null;
+      }
+      remoteName_ = "";
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Socket_descriptor;
+    }
+
+    public io.grpc.channelz.v1.Socket getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.Socket.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.Socket build() {
+      io.grpc.channelz.v1.Socket result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.Socket buildPartial() {
+      io.grpc.channelz.v1.Socket result = new io.grpc.channelz.v1.Socket(this);
+      if (refBuilder_ == null) {
+        result.ref_ = ref_;
+      } else {
+        result.ref_ = refBuilder_.build();
+      }
+      if (dataBuilder_ == null) {
+        result.data_ = data_;
+      } else {
+        result.data_ = dataBuilder_.build();
+      }
+      if (localBuilder_ == null) {
+        result.local_ = local_;
+      } else {
+        result.local_ = localBuilder_.build();
+      }
+      if (remoteBuilder_ == null) {
+        result.remote_ = remote_;
+      } else {
+        result.remote_ = remoteBuilder_.build();
+      }
+      if (securityBuilder_ == null) {
+        result.security_ = security_;
+      } else {
+        result.security_ = securityBuilder_.build();
+      }
+      result.remoteName_ = remoteName_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.Socket) {
+        return mergeFrom((io.grpc.channelz.v1.Socket)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.Socket other) {
+      if (other == io.grpc.channelz.v1.Socket.getDefaultInstance()) return this;
+      if (other.hasRef()) {
+        mergeRef(other.getRef());
+      }
+      if (other.hasData()) {
+        mergeData(other.getData());
+      }
+      if (other.hasLocal()) {
+        mergeLocal(other.getLocal());
+      }
+      if (other.hasRemote()) {
+        mergeRemote(other.getRemote());
+      }
+      if (other.hasSecurity()) {
+        mergeSecurity(other.getSecurity());
+      }
+      if (!other.getRemoteName().isEmpty()) {
+        remoteName_ = other.remoteName_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.Socket parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.Socket) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private io.grpc.channelz.v1.SocketRef ref_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder> refBuilder_;
+    /**
+     * <pre>
+     * The identifier for the Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+     */
+    public boolean hasRef() {
+      return refBuilder_ != null || ref_ != null;
+    }
+    /**
+     * <pre>
+     * The identifier for the Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef getRef() {
+      if (refBuilder_ == null) {
+        return ref_ == null ? io.grpc.channelz.v1.SocketRef.getDefaultInstance() : ref_;
+      } else {
+        return refBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The identifier for the Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+     */
+    public Builder setRef(io.grpc.channelz.v1.SocketRef value) {
+      if (refBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ref_ = value;
+        onChanged();
+      } else {
+        refBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for the Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+     */
+    public Builder setRef(
+        io.grpc.channelz.v1.SocketRef.Builder builderForValue) {
+      if (refBuilder_ == null) {
+        ref_ = builderForValue.build();
+        onChanged();
+      } else {
+        refBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for the Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+     */
+    public Builder mergeRef(io.grpc.channelz.v1.SocketRef value) {
+      if (refBuilder_ == null) {
+        if (ref_ != null) {
+          ref_ =
+            io.grpc.channelz.v1.SocketRef.newBuilder(ref_).mergeFrom(value).buildPartial();
+        } else {
+          ref_ = value;
+        }
+        onChanged();
+      } else {
+        refBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for the Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+     */
+    public Builder clearRef() {
+      if (refBuilder_ == null) {
+        ref_ = null;
+        onChanged();
+      } else {
+        ref_ = null;
+        refBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for the Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef.Builder getRefBuilder() {
+      
+      onChanged();
+      return getRefFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The identifier for the Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.SocketRefOrBuilder getRefOrBuilder() {
+      if (refBuilder_ != null) {
+        return refBuilder_.getMessageOrBuilder();
+      } else {
+        return ref_ == null ?
+            io.grpc.channelz.v1.SocketRef.getDefaultInstance() : ref_;
+      }
+    }
+    /**
+     * <pre>
+     * The identifier for the Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder> 
+        getRefFieldBuilder() {
+      if (refBuilder_ == null) {
+        refBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder>(
+                getRef(),
+                getParentForChildren(),
+                isClean());
+        ref_ = null;
+      }
+      return refBuilder_;
+    }
+
+    private io.grpc.channelz.v1.SocketData data_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.SocketData, io.grpc.channelz.v1.SocketData.Builder, io.grpc.channelz.v1.SocketDataOrBuilder> dataBuilder_;
+    /**
+     * <pre>
+     * Data specific to this Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+     */
+    public boolean hasData() {
+      return dataBuilder_ != null || data_ != null;
+    }
+    /**
+     * <pre>
+     * Data specific to this Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+     */
+    public io.grpc.channelz.v1.SocketData getData() {
+      if (dataBuilder_ == null) {
+        return data_ == null ? io.grpc.channelz.v1.SocketData.getDefaultInstance() : data_;
+      } else {
+        return dataBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * Data specific to this Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+     */
+    public Builder setData(io.grpc.channelz.v1.SocketData value) {
+      if (dataBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        data_ = value;
+        onChanged();
+      } else {
+        dataBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Data specific to this Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+     */
+    public Builder setData(
+        io.grpc.channelz.v1.SocketData.Builder builderForValue) {
+      if (dataBuilder_ == null) {
+        data_ = builderForValue.build();
+        onChanged();
+      } else {
+        dataBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Data specific to this Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+     */
+    public Builder mergeData(io.grpc.channelz.v1.SocketData value) {
+      if (dataBuilder_ == null) {
+        if (data_ != null) {
+          data_ =
+            io.grpc.channelz.v1.SocketData.newBuilder(data_).mergeFrom(value).buildPartial();
+        } else {
+          data_ = value;
+        }
+        onChanged();
+      } else {
+        dataBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Data specific to this Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+     */
+    public Builder clearData() {
+      if (dataBuilder_ == null) {
+        data_ = null;
+        onChanged();
+      } else {
+        data_ = null;
+        dataBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Data specific to this Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+     */
+    public io.grpc.channelz.v1.SocketData.Builder getDataBuilder() {
+      
+      onChanged();
+      return getDataFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Data specific to this Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+     */
+    public io.grpc.channelz.v1.SocketDataOrBuilder getDataOrBuilder() {
+      if (dataBuilder_ != null) {
+        return dataBuilder_.getMessageOrBuilder();
+      } else {
+        return data_ == null ?
+            io.grpc.channelz.v1.SocketData.getDefaultInstance() : data_;
+      }
+    }
+    /**
+     * <pre>
+     * Data specific to this Socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.SocketData, io.grpc.channelz.v1.SocketData.Builder, io.grpc.channelz.v1.SocketDataOrBuilder> 
+        getDataFieldBuilder() {
+      if (dataBuilder_ == null) {
+        dataBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.SocketData, io.grpc.channelz.v1.SocketData.Builder, io.grpc.channelz.v1.SocketDataOrBuilder>(
+                getData(),
+                getParentForChildren(),
+                isClean());
+        data_ = null;
+      }
+      return dataBuilder_;
+    }
+
+    private io.grpc.channelz.v1.Address local_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Address, io.grpc.channelz.v1.Address.Builder, io.grpc.channelz.v1.AddressOrBuilder> localBuilder_;
+    /**
+     * <pre>
+     * The locally bound address.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address local = 3;</code>
+     */
+    public boolean hasLocal() {
+      return localBuilder_ != null || local_ != null;
+    }
+    /**
+     * <pre>
+     * The locally bound address.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address local = 3;</code>
+     */
+    public io.grpc.channelz.v1.Address getLocal() {
+      if (localBuilder_ == null) {
+        return local_ == null ? io.grpc.channelz.v1.Address.getDefaultInstance() : local_;
+      } else {
+        return localBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The locally bound address.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address local = 3;</code>
+     */
+    public Builder setLocal(io.grpc.channelz.v1.Address value) {
+      if (localBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        local_ = value;
+        onChanged();
+      } else {
+        localBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The locally bound address.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address local = 3;</code>
+     */
+    public Builder setLocal(
+        io.grpc.channelz.v1.Address.Builder builderForValue) {
+      if (localBuilder_ == null) {
+        local_ = builderForValue.build();
+        onChanged();
+      } else {
+        localBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The locally bound address.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address local = 3;</code>
+     */
+    public Builder mergeLocal(io.grpc.channelz.v1.Address value) {
+      if (localBuilder_ == null) {
+        if (local_ != null) {
+          local_ =
+            io.grpc.channelz.v1.Address.newBuilder(local_).mergeFrom(value).buildPartial();
+        } else {
+          local_ = value;
+        }
+        onChanged();
+      } else {
+        localBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The locally bound address.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address local = 3;</code>
+     */
+    public Builder clearLocal() {
+      if (localBuilder_ == null) {
+        local_ = null;
+        onChanged();
+      } else {
+        local_ = null;
+        localBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The locally bound address.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address local = 3;</code>
+     */
+    public io.grpc.channelz.v1.Address.Builder getLocalBuilder() {
+      
+      onChanged();
+      return getLocalFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The locally bound address.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address local = 3;</code>
+     */
+    public io.grpc.channelz.v1.AddressOrBuilder getLocalOrBuilder() {
+      if (localBuilder_ != null) {
+        return localBuilder_.getMessageOrBuilder();
+      } else {
+        return local_ == null ?
+            io.grpc.channelz.v1.Address.getDefaultInstance() : local_;
+      }
+    }
+    /**
+     * <pre>
+     * The locally bound address.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address local = 3;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Address, io.grpc.channelz.v1.Address.Builder, io.grpc.channelz.v1.AddressOrBuilder> 
+        getLocalFieldBuilder() {
+      if (localBuilder_ == null) {
+        localBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.Address, io.grpc.channelz.v1.Address.Builder, io.grpc.channelz.v1.AddressOrBuilder>(
+                getLocal(),
+                getParentForChildren(),
+                isClean());
+        local_ = null;
+      }
+      return localBuilder_;
+    }
+
+    private io.grpc.channelz.v1.Address remote_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Address, io.grpc.channelz.v1.Address.Builder, io.grpc.channelz.v1.AddressOrBuilder> remoteBuilder_;
+    /**
+     * <pre>
+     * The remote bound address.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address remote = 4;</code>
+     */
+    public boolean hasRemote() {
+      return remoteBuilder_ != null || remote_ != null;
+    }
+    /**
+     * <pre>
+     * The remote bound address.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address remote = 4;</code>
+     */
+    public io.grpc.channelz.v1.Address getRemote() {
+      if (remoteBuilder_ == null) {
+        return remote_ == null ? io.grpc.channelz.v1.Address.getDefaultInstance() : remote_;
+      } else {
+        return remoteBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The remote bound address.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address remote = 4;</code>
+     */
+    public Builder setRemote(io.grpc.channelz.v1.Address value) {
+      if (remoteBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        remote_ = value;
+        onChanged();
+      } else {
+        remoteBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The remote bound address.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address remote = 4;</code>
+     */
+    public Builder setRemote(
+        io.grpc.channelz.v1.Address.Builder builderForValue) {
+      if (remoteBuilder_ == null) {
+        remote_ = builderForValue.build();
+        onChanged();
+      } else {
+        remoteBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The remote bound address.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address remote = 4;</code>
+     */
+    public Builder mergeRemote(io.grpc.channelz.v1.Address value) {
+      if (remoteBuilder_ == null) {
+        if (remote_ != null) {
+          remote_ =
+            io.grpc.channelz.v1.Address.newBuilder(remote_).mergeFrom(value).buildPartial();
+        } else {
+          remote_ = value;
+        }
+        onChanged();
+      } else {
+        remoteBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The remote bound address.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address remote = 4;</code>
+     */
+    public Builder clearRemote() {
+      if (remoteBuilder_ == null) {
+        remote_ = null;
+        onChanged();
+      } else {
+        remote_ = null;
+        remoteBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The remote bound address.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address remote = 4;</code>
+     */
+    public io.grpc.channelz.v1.Address.Builder getRemoteBuilder() {
+      
+      onChanged();
+      return getRemoteFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The remote bound address.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address remote = 4;</code>
+     */
+    public io.grpc.channelz.v1.AddressOrBuilder getRemoteOrBuilder() {
+      if (remoteBuilder_ != null) {
+        return remoteBuilder_.getMessageOrBuilder();
+      } else {
+        return remote_ == null ?
+            io.grpc.channelz.v1.Address.getDefaultInstance() : remote_;
+      }
+    }
+    /**
+     * <pre>
+     * The remote bound address.  May be absent.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Address remote = 4;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Address, io.grpc.channelz.v1.Address.Builder, io.grpc.channelz.v1.AddressOrBuilder> 
+        getRemoteFieldBuilder() {
+      if (remoteBuilder_ == null) {
+        remoteBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.Address, io.grpc.channelz.v1.Address.Builder, io.grpc.channelz.v1.AddressOrBuilder>(
+                getRemote(),
+                getParentForChildren(),
+                isClean());
+        remote_ = null;
+      }
+      return remoteBuilder_;
+    }
+
+    private io.grpc.channelz.v1.Security security_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Security, io.grpc.channelz.v1.Security.Builder, io.grpc.channelz.v1.SecurityOrBuilder> securityBuilder_;
+    /**
+     * <pre>
+     * Security details for this socket.  May be absent if not available, or
+     * there is no security on the socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Security security = 5;</code>
+     */
+    public boolean hasSecurity() {
+      return securityBuilder_ != null || security_ != null;
+    }
+    /**
+     * <pre>
+     * Security details for this socket.  May be absent if not available, or
+     * there is no security on the socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Security security = 5;</code>
+     */
+    public io.grpc.channelz.v1.Security getSecurity() {
+      if (securityBuilder_ == null) {
+        return security_ == null ? io.grpc.channelz.v1.Security.getDefaultInstance() : security_;
+      } else {
+        return securityBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * Security details for this socket.  May be absent if not available, or
+     * there is no security on the socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Security security = 5;</code>
+     */
+    public Builder setSecurity(io.grpc.channelz.v1.Security value) {
+      if (securityBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        security_ = value;
+        onChanged();
+      } else {
+        securityBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Security details for this socket.  May be absent if not available, or
+     * there is no security on the socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Security security = 5;</code>
+     */
+    public Builder setSecurity(
+        io.grpc.channelz.v1.Security.Builder builderForValue) {
+      if (securityBuilder_ == null) {
+        security_ = builderForValue.build();
+        onChanged();
+      } else {
+        securityBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Security details for this socket.  May be absent if not available, or
+     * there is no security on the socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Security security = 5;</code>
+     */
+    public Builder mergeSecurity(io.grpc.channelz.v1.Security value) {
+      if (securityBuilder_ == null) {
+        if (security_ != null) {
+          security_ =
+            io.grpc.channelz.v1.Security.newBuilder(security_).mergeFrom(value).buildPartial();
+        } else {
+          security_ = value;
+        }
+        onChanged();
+      } else {
+        securityBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Security details for this socket.  May be absent if not available, or
+     * there is no security on the socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Security security = 5;</code>
+     */
+    public Builder clearSecurity() {
+      if (securityBuilder_ == null) {
+        security_ = null;
+        onChanged();
+      } else {
+        security_ = null;
+        securityBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Security details for this socket.  May be absent if not available, or
+     * there is no security on the socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Security security = 5;</code>
+     */
+    public io.grpc.channelz.v1.Security.Builder getSecurityBuilder() {
+      
+      onChanged();
+      return getSecurityFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Security details for this socket.  May be absent if not available, or
+     * there is no security on the socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Security security = 5;</code>
+     */
+    public io.grpc.channelz.v1.SecurityOrBuilder getSecurityOrBuilder() {
+      if (securityBuilder_ != null) {
+        return securityBuilder_.getMessageOrBuilder();
+      } else {
+        return security_ == null ?
+            io.grpc.channelz.v1.Security.getDefaultInstance() : security_;
+      }
+    }
+    /**
+     * <pre>
+     * Security details for this socket.  May be absent if not available, or
+     * there is no security on the socket.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.Security security = 5;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.Security, io.grpc.channelz.v1.Security.Builder, io.grpc.channelz.v1.SecurityOrBuilder> 
+        getSecurityFieldBuilder() {
+      if (securityBuilder_ == null) {
+        securityBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.Security, io.grpc.channelz.v1.Security.Builder, io.grpc.channelz.v1.SecurityOrBuilder>(
+                getSecurity(),
+                getParentForChildren(),
+                isClean());
+        security_ = null;
+      }
+      return securityBuilder_;
+    }
+
+    private java.lang.Object remoteName_ = "";
+    /**
+     * <pre>
+     * Optional, represents the name of the remote endpoint, if different than
+     * the original target name.
+     * </pre>
+     *
+     * <code>string remote_name = 6;</code>
+     */
+    public java.lang.String getRemoteName() {
+      java.lang.Object ref = remoteName_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        remoteName_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Optional, represents the name of the remote endpoint, if different than
+     * the original target name.
+     * </pre>
+     *
+     * <code>string remote_name = 6;</code>
+     */
+    public com.google.protobuf.ByteString
+        getRemoteNameBytes() {
+      java.lang.Object ref = remoteName_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        remoteName_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Optional, represents the name of the remote endpoint, if different than
+     * the original target name.
+     * </pre>
+     *
+     * <code>string remote_name = 6;</code>
+     */
+    public Builder setRemoteName(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      remoteName_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Optional, represents the name of the remote endpoint, if different than
+     * the original target name.
+     * </pre>
+     *
+     * <code>string remote_name = 6;</code>
+     */
+    public Builder clearRemoteName() {
+      
+      remoteName_ = getDefaultInstance().getRemoteName();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Optional, represents the name of the remote endpoint, if different than
+     * the original target name.
+     * </pre>
+     *
+     * <code>string remote_name = 6;</code>
+     */
+    public Builder setRemoteNameBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      remoteName_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.Socket)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.Socket)
+  private static final io.grpc.channelz.v1.Socket DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.Socket();
+  }
+
+  public static io.grpc.channelz.v1.Socket getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<Socket>
+      PARSER = new com.google.protobuf.AbstractParser<Socket>() {
+    public Socket parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Socket(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Socket> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Socket> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.Socket getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SocketData.java b/services/src/generated/main/java/io/grpc/channelz/v1/SocketData.java
new file mode 100644
index 0000000..33f3b7d
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SocketData.java
@@ -0,0 +1,2757 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * SocketData is data associated for a specific Socket.  The fields present
+ * are specific to the implementation, so there may be minor differences in
+ * the semantics.  (e.g. flow control windows)
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.SocketData}
+ */
+public  final class SocketData extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.SocketData)
+    SocketDataOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use SocketData.newBuilder() to construct.
+  private SocketData(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private SocketData() {
+    streamsStarted_ = 0L;
+    streamsSucceeded_ = 0L;
+    streamsFailed_ = 0L;
+    messagesSent_ = 0L;
+    messagesReceived_ = 0L;
+    keepAlivesSent_ = 0L;
+    option_ = java.util.Collections.emptyList();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private SocketData(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+
+            streamsStarted_ = input.readInt64();
+            break;
+          }
+          case 16: {
+
+            streamsSucceeded_ = input.readInt64();
+            break;
+          }
+          case 24: {
+
+            streamsFailed_ = input.readInt64();
+            break;
+          }
+          case 32: {
+
+            messagesSent_ = input.readInt64();
+            break;
+          }
+          case 40: {
+
+            messagesReceived_ = input.readInt64();
+            break;
+          }
+          case 48: {
+
+            keepAlivesSent_ = input.readInt64();
+            break;
+          }
+          case 58: {
+            com.google.protobuf.Timestamp.Builder subBuilder = null;
+            if (lastLocalStreamCreatedTimestamp_ != null) {
+              subBuilder = lastLocalStreamCreatedTimestamp_.toBuilder();
+            }
+            lastLocalStreamCreatedTimestamp_ = input.readMessage(com.google.protobuf.Timestamp.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(lastLocalStreamCreatedTimestamp_);
+              lastLocalStreamCreatedTimestamp_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 66: {
+            com.google.protobuf.Timestamp.Builder subBuilder = null;
+            if (lastRemoteStreamCreatedTimestamp_ != null) {
+              subBuilder = lastRemoteStreamCreatedTimestamp_.toBuilder();
+            }
+            lastRemoteStreamCreatedTimestamp_ = input.readMessage(com.google.protobuf.Timestamp.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(lastRemoteStreamCreatedTimestamp_);
+              lastRemoteStreamCreatedTimestamp_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 74: {
+            com.google.protobuf.Timestamp.Builder subBuilder = null;
+            if (lastMessageSentTimestamp_ != null) {
+              subBuilder = lastMessageSentTimestamp_.toBuilder();
+            }
+            lastMessageSentTimestamp_ = input.readMessage(com.google.protobuf.Timestamp.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(lastMessageSentTimestamp_);
+              lastMessageSentTimestamp_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 82: {
+            com.google.protobuf.Timestamp.Builder subBuilder = null;
+            if (lastMessageReceivedTimestamp_ != null) {
+              subBuilder = lastMessageReceivedTimestamp_.toBuilder();
+            }
+            lastMessageReceivedTimestamp_ = input.readMessage(com.google.protobuf.Timestamp.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(lastMessageReceivedTimestamp_);
+              lastMessageReceivedTimestamp_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 90: {
+            com.google.protobuf.Int64Value.Builder subBuilder = null;
+            if (localFlowControlWindow_ != null) {
+              subBuilder = localFlowControlWindow_.toBuilder();
+            }
+            localFlowControlWindow_ = input.readMessage(com.google.protobuf.Int64Value.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(localFlowControlWindow_);
+              localFlowControlWindow_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 98: {
+            com.google.protobuf.Int64Value.Builder subBuilder = null;
+            if (remoteFlowControlWindow_ != null) {
+              subBuilder = remoteFlowControlWindow_.toBuilder();
+            }
+            remoteFlowControlWindow_ = input.readMessage(com.google.protobuf.Int64Value.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(remoteFlowControlWindow_);
+              remoteFlowControlWindow_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 106: {
+            if (!((mutable_bitField0_ & 0x00001000) == 0x00001000)) {
+              option_ = new java.util.ArrayList<io.grpc.channelz.v1.SocketOption>();
+              mutable_bitField0_ |= 0x00001000;
+            }
+            option_.add(
+                input.readMessage(io.grpc.channelz.v1.SocketOption.parser(), extensionRegistry));
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00001000) == 0x00001000)) {
+        option_ = java.util.Collections.unmodifiableList(option_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketData_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketData_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.SocketData.class, io.grpc.channelz.v1.SocketData.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int STREAMS_STARTED_FIELD_NUMBER = 1;
+  private long streamsStarted_;
+  /**
+   * <pre>
+   * The number of streams that have been started.
+   * </pre>
+   *
+   * <code>int64 streams_started = 1;</code>
+   */
+  public long getStreamsStarted() {
+    return streamsStarted_;
+  }
+
+  public static final int STREAMS_SUCCEEDED_FIELD_NUMBER = 2;
+  private long streamsSucceeded_;
+  /**
+   * <pre>
+   * The number of streams that have ended successfully:
+   * On client side, received frame with eos bit set;
+   * On server side, sent frame with eos bit set.
+   * </pre>
+   *
+   * <code>int64 streams_succeeded = 2;</code>
+   */
+  public long getStreamsSucceeded() {
+    return streamsSucceeded_;
+  }
+
+  public static final int STREAMS_FAILED_FIELD_NUMBER = 3;
+  private long streamsFailed_;
+  /**
+   * <pre>
+   * The number of streams that have ended unsuccessfully:
+   * On client side, ended without receiving frame with eos bit set;
+   * On server side, ended without sending frame with eos bit set.
+   * </pre>
+   *
+   * <code>int64 streams_failed = 3;</code>
+   */
+  public long getStreamsFailed() {
+    return streamsFailed_;
+  }
+
+  public static final int MESSAGES_SENT_FIELD_NUMBER = 4;
+  private long messagesSent_;
+  /**
+   * <pre>
+   * The number of grpc messages successfully sent on this socket.
+   * </pre>
+   *
+   * <code>int64 messages_sent = 4;</code>
+   */
+  public long getMessagesSent() {
+    return messagesSent_;
+  }
+
+  public static final int MESSAGES_RECEIVED_FIELD_NUMBER = 5;
+  private long messagesReceived_;
+  /**
+   * <pre>
+   * The number of grpc messages received on this socket.
+   * </pre>
+   *
+   * <code>int64 messages_received = 5;</code>
+   */
+  public long getMessagesReceived() {
+    return messagesReceived_;
+  }
+
+  public static final int KEEP_ALIVES_SENT_FIELD_NUMBER = 6;
+  private long keepAlivesSent_;
+  /**
+   * <pre>
+   * The number of keep alives sent.  This is typically implemented with HTTP/2
+   * ping messages.
+   * </pre>
+   *
+   * <code>int64 keep_alives_sent = 6;</code>
+   */
+  public long getKeepAlivesSent() {
+    return keepAlivesSent_;
+  }
+
+  public static final int LAST_LOCAL_STREAM_CREATED_TIMESTAMP_FIELD_NUMBER = 7;
+  private com.google.protobuf.Timestamp lastLocalStreamCreatedTimestamp_;
+  /**
+   * <pre>
+   * The last time a stream was created by this endpoint.  Usually unset for
+   * servers.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+   */
+  public boolean hasLastLocalStreamCreatedTimestamp() {
+    return lastLocalStreamCreatedTimestamp_ != null;
+  }
+  /**
+   * <pre>
+   * The last time a stream was created by this endpoint.  Usually unset for
+   * servers.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+   */
+  public com.google.protobuf.Timestamp getLastLocalStreamCreatedTimestamp() {
+    return lastLocalStreamCreatedTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : lastLocalStreamCreatedTimestamp_;
+  }
+  /**
+   * <pre>
+   * The last time a stream was created by this endpoint.  Usually unset for
+   * servers.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+   */
+  public com.google.protobuf.TimestampOrBuilder getLastLocalStreamCreatedTimestampOrBuilder() {
+    return getLastLocalStreamCreatedTimestamp();
+  }
+
+  public static final int LAST_REMOTE_STREAM_CREATED_TIMESTAMP_FIELD_NUMBER = 8;
+  private com.google.protobuf.Timestamp lastRemoteStreamCreatedTimestamp_;
+  /**
+   * <pre>
+   * The last time a stream was created by the remote endpoint.  Usually unset
+   * for clients.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+   */
+  public boolean hasLastRemoteStreamCreatedTimestamp() {
+    return lastRemoteStreamCreatedTimestamp_ != null;
+  }
+  /**
+   * <pre>
+   * The last time a stream was created by the remote endpoint.  Usually unset
+   * for clients.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+   */
+  public com.google.protobuf.Timestamp getLastRemoteStreamCreatedTimestamp() {
+    return lastRemoteStreamCreatedTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : lastRemoteStreamCreatedTimestamp_;
+  }
+  /**
+   * <pre>
+   * The last time a stream was created by the remote endpoint.  Usually unset
+   * for clients.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+   */
+  public com.google.protobuf.TimestampOrBuilder getLastRemoteStreamCreatedTimestampOrBuilder() {
+    return getLastRemoteStreamCreatedTimestamp();
+  }
+
+  public static final int LAST_MESSAGE_SENT_TIMESTAMP_FIELD_NUMBER = 9;
+  private com.google.protobuf.Timestamp lastMessageSentTimestamp_;
+  /**
+   * <pre>
+   * The last time a message was sent by this endpoint.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+   */
+  public boolean hasLastMessageSentTimestamp() {
+    return lastMessageSentTimestamp_ != null;
+  }
+  /**
+   * <pre>
+   * The last time a message was sent by this endpoint.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+   */
+  public com.google.protobuf.Timestamp getLastMessageSentTimestamp() {
+    return lastMessageSentTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : lastMessageSentTimestamp_;
+  }
+  /**
+   * <pre>
+   * The last time a message was sent by this endpoint.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+   */
+  public com.google.protobuf.TimestampOrBuilder getLastMessageSentTimestampOrBuilder() {
+    return getLastMessageSentTimestamp();
+  }
+
+  public static final int LAST_MESSAGE_RECEIVED_TIMESTAMP_FIELD_NUMBER = 10;
+  private com.google.protobuf.Timestamp lastMessageReceivedTimestamp_;
+  /**
+   * <pre>
+   * The last time a message was received by this endpoint.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+   */
+  public boolean hasLastMessageReceivedTimestamp() {
+    return lastMessageReceivedTimestamp_ != null;
+  }
+  /**
+   * <pre>
+   * The last time a message was received by this endpoint.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+   */
+  public com.google.protobuf.Timestamp getLastMessageReceivedTimestamp() {
+    return lastMessageReceivedTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : lastMessageReceivedTimestamp_;
+  }
+  /**
+   * <pre>
+   * The last time a message was received by this endpoint.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+   */
+  public com.google.protobuf.TimestampOrBuilder getLastMessageReceivedTimestampOrBuilder() {
+    return getLastMessageReceivedTimestamp();
+  }
+
+  public static final int LOCAL_FLOW_CONTROL_WINDOW_FIELD_NUMBER = 11;
+  private com.google.protobuf.Int64Value localFlowControlWindow_;
+  /**
+   * <pre>
+   * The amount of window, granted to the local endpoint by the remote endpoint.
+   * This may be slightly out of date due to network latency.  This does NOT
+   * include stream level or TCP level flow control info.
+   * </pre>
+   *
+   * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+   */
+  public boolean hasLocalFlowControlWindow() {
+    return localFlowControlWindow_ != null;
+  }
+  /**
+   * <pre>
+   * The amount of window, granted to the local endpoint by the remote endpoint.
+   * This may be slightly out of date due to network latency.  This does NOT
+   * include stream level or TCP level flow control info.
+   * </pre>
+   *
+   * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+   */
+  public com.google.protobuf.Int64Value getLocalFlowControlWindow() {
+    return localFlowControlWindow_ == null ? com.google.protobuf.Int64Value.getDefaultInstance() : localFlowControlWindow_;
+  }
+  /**
+   * <pre>
+   * The amount of window, granted to the local endpoint by the remote endpoint.
+   * This may be slightly out of date due to network latency.  This does NOT
+   * include stream level or TCP level flow control info.
+   * </pre>
+   *
+   * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+   */
+  public com.google.protobuf.Int64ValueOrBuilder getLocalFlowControlWindowOrBuilder() {
+    return getLocalFlowControlWindow();
+  }
+
+  public static final int REMOTE_FLOW_CONTROL_WINDOW_FIELD_NUMBER = 12;
+  private com.google.protobuf.Int64Value remoteFlowControlWindow_;
+  /**
+   * <pre>
+   * The amount of window, granted to the remote endpoint by the local endpoint.
+   * This may be slightly out of date due to network latency.  This does NOT
+   * include stream level or TCP level flow control info.
+   * </pre>
+   *
+   * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+   */
+  public boolean hasRemoteFlowControlWindow() {
+    return remoteFlowControlWindow_ != null;
+  }
+  /**
+   * <pre>
+   * The amount of window, granted to the remote endpoint by the local endpoint.
+   * This may be slightly out of date due to network latency.  This does NOT
+   * include stream level or TCP level flow control info.
+   * </pre>
+   *
+   * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+   */
+  public com.google.protobuf.Int64Value getRemoteFlowControlWindow() {
+    return remoteFlowControlWindow_ == null ? com.google.protobuf.Int64Value.getDefaultInstance() : remoteFlowControlWindow_;
+  }
+  /**
+   * <pre>
+   * The amount of window, granted to the remote endpoint by the local endpoint.
+   * This may be slightly out of date due to network latency.  This does NOT
+   * include stream level or TCP level flow control info.
+   * </pre>
+   *
+   * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+   */
+  public com.google.protobuf.Int64ValueOrBuilder getRemoteFlowControlWindowOrBuilder() {
+    return getRemoteFlowControlWindow();
+  }
+
+  public static final int OPTION_FIELD_NUMBER = 13;
+  private java.util.List<io.grpc.channelz.v1.SocketOption> option_;
+  /**
+   * <pre>
+   * Socket options set on this socket.  May be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+   */
+  public java.util.List<io.grpc.channelz.v1.SocketOption> getOptionList() {
+    return option_;
+  }
+  /**
+   * <pre>
+   * Socket options set on this socket.  May be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+   */
+  public java.util.List<? extends io.grpc.channelz.v1.SocketOptionOrBuilder> 
+      getOptionOrBuilderList() {
+    return option_;
+  }
+  /**
+   * <pre>
+   * Socket options set on this socket.  May be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+   */
+  public int getOptionCount() {
+    return option_.size();
+  }
+  /**
+   * <pre>
+   * Socket options set on this socket.  May be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+   */
+  public io.grpc.channelz.v1.SocketOption getOption(int index) {
+    return option_.get(index);
+  }
+  /**
+   * <pre>
+   * Socket options set on this socket.  May be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+   */
+  public io.grpc.channelz.v1.SocketOptionOrBuilder getOptionOrBuilder(
+      int index) {
+    return option_.get(index);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (streamsStarted_ != 0L) {
+      output.writeInt64(1, streamsStarted_);
+    }
+    if (streamsSucceeded_ != 0L) {
+      output.writeInt64(2, streamsSucceeded_);
+    }
+    if (streamsFailed_ != 0L) {
+      output.writeInt64(3, streamsFailed_);
+    }
+    if (messagesSent_ != 0L) {
+      output.writeInt64(4, messagesSent_);
+    }
+    if (messagesReceived_ != 0L) {
+      output.writeInt64(5, messagesReceived_);
+    }
+    if (keepAlivesSent_ != 0L) {
+      output.writeInt64(6, keepAlivesSent_);
+    }
+    if (lastLocalStreamCreatedTimestamp_ != null) {
+      output.writeMessage(7, getLastLocalStreamCreatedTimestamp());
+    }
+    if (lastRemoteStreamCreatedTimestamp_ != null) {
+      output.writeMessage(8, getLastRemoteStreamCreatedTimestamp());
+    }
+    if (lastMessageSentTimestamp_ != null) {
+      output.writeMessage(9, getLastMessageSentTimestamp());
+    }
+    if (lastMessageReceivedTimestamp_ != null) {
+      output.writeMessage(10, getLastMessageReceivedTimestamp());
+    }
+    if (localFlowControlWindow_ != null) {
+      output.writeMessage(11, getLocalFlowControlWindow());
+    }
+    if (remoteFlowControlWindow_ != null) {
+      output.writeMessage(12, getRemoteFlowControlWindow());
+    }
+    for (int i = 0; i < option_.size(); i++) {
+      output.writeMessage(13, option_.get(i));
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (streamsStarted_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(1, streamsStarted_);
+    }
+    if (streamsSucceeded_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(2, streamsSucceeded_);
+    }
+    if (streamsFailed_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(3, streamsFailed_);
+    }
+    if (messagesSent_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(4, messagesSent_);
+    }
+    if (messagesReceived_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(5, messagesReceived_);
+    }
+    if (keepAlivesSent_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(6, keepAlivesSent_);
+    }
+    if (lastLocalStreamCreatedTimestamp_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(7, getLastLocalStreamCreatedTimestamp());
+    }
+    if (lastRemoteStreamCreatedTimestamp_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(8, getLastRemoteStreamCreatedTimestamp());
+    }
+    if (lastMessageSentTimestamp_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(9, getLastMessageSentTimestamp());
+    }
+    if (lastMessageReceivedTimestamp_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(10, getLastMessageReceivedTimestamp());
+    }
+    if (localFlowControlWindow_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(11, getLocalFlowControlWindow());
+    }
+    if (remoteFlowControlWindow_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(12, getRemoteFlowControlWindow());
+    }
+    for (int i = 0; i < option_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(13, option_.get(i));
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.SocketData)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.SocketData other = (io.grpc.channelz.v1.SocketData) obj;
+
+    boolean result = true;
+    result = result && (getStreamsStarted()
+        == other.getStreamsStarted());
+    result = result && (getStreamsSucceeded()
+        == other.getStreamsSucceeded());
+    result = result && (getStreamsFailed()
+        == other.getStreamsFailed());
+    result = result && (getMessagesSent()
+        == other.getMessagesSent());
+    result = result && (getMessagesReceived()
+        == other.getMessagesReceived());
+    result = result && (getKeepAlivesSent()
+        == other.getKeepAlivesSent());
+    result = result && (hasLastLocalStreamCreatedTimestamp() == other.hasLastLocalStreamCreatedTimestamp());
+    if (hasLastLocalStreamCreatedTimestamp()) {
+      result = result && getLastLocalStreamCreatedTimestamp()
+          .equals(other.getLastLocalStreamCreatedTimestamp());
+    }
+    result = result && (hasLastRemoteStreamCreatedTimestamp() == other.hasLastRemoteStreamCreatedTimestamp());
+    if (hasLastRemoteStreamCreatedTimestamp()) {
+      result = result && getLastRemoteStreamCreatedTimestamp()
+          .equals(other.getLastRemoteStreamCreatedTimestamp());
+    }
+    result = result && (hasLastMessageSentTimestamp() == other.hasLastMessageSentTimestamp());
+    if (hasLastMessageSentTimestamp()) {
+      result = result && getLastMessageSentTimestamp()
+          .equals(other.getLastMessageSentTimestamp());
+    }
+    result = result && (hasLastMessageReceivedTimestamp() == other.hasLastMessageReceivedTimestamp());
+    if (hasLastMessageReceivedTimestamp()) {
+      result = result && getLastMessageReceivedTimestamp()
+          .equals(other.getLastMessageReceivedTimestamp());
+    }
+    result = result && (hasLocalFlowControlWindow() == other.hasLocalFlowControlWindow());
+    if (hasLocalFlowControlWindow()) {
+      result = result && getLocalFlowControlWindow()
+          .equals(other.getLocalFlowControlWindow());
+    }
+    result = result && (hasRemoteFlowControlWindow() == other.hasRemoteFlowControlWindow());
+    if (hasRemoteFlowControlWindow()) {
+      result = result && getRemoteFlowControlWindow()
+          .equals(other.getRemoteFlowControlWindow());
+    }
+    result = result && getOptionList()
+        .equals(other.getOptionList());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + STREAMS_STARTED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getStreamsStarted());
+    hash = (37 * hash) + STREAMS_SUCCEEDED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getStreamsSucceeded());
+    hash = (37 * hash) + STREAMS_FAILED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getStreamsFailed());
+    hash = (37 * hash) + MESSAGES_SENT_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getMessagesSent());
+    hash = (37 * hash) + MESSAGES_RECEIVED_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getMessagesReceived());
+    hash = (37 * hash) + KEEP_ALIVES_SENT_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getKeepAlivesSent());
+    if (hasLastLocalStreamCreatedTimestamp()) {
+      hash = (37 * hash) + LAST_LOCAL_STREAM_CREATED_TIMESTAMP_FIELD_NUMBER;
+      hash = (53 * hash) + getLastLocalStreamCreatedTimestamp().hashCode();
+    }
+    if (hasLastRemoteStreamCreatedTimestamp()) {
+      hash = (37 * hash) + LAST_REMOTE_STREAM_CREATED_TIMESTAMP_FIELD_NUMBER;
+      hash = (53 * hash) + getLastRemoteStreamCreatedTimestamp().hashCode();
+    }
+    if (hasLastMessageSentTimestamp()) {
+      hash = (37 * hash) + LAST_MESSAGE_SENT_TIMESTAMP_FIELD_NUMBER;
+      hash = (53 * hash) + getLastMessageSentTimestamp().hashCode();
+    }
+    if (hasLastMessageReceivedTimestamp()) {
+      hash = (37 * hash) + LAST_MESSAGE_RECEIVED_TIMESTAMP_FIELD_NUMBER;
+      hash = (53 * hash) + getLastMessageReceivedTimestamp().hashCode();
+    }
+    if (hasLocalFlowControlWindow()) {
+      hash = (37 * hash) + LOCAL_FLOW_CONTROL_WINDOW_FIELD_NUMBER;
+      hash = (53 * hash) + getLocalFlowControlWindow().hashCode();
+    }
+    if (hasRemoteFlowControlWindow()) {
+      hash = (37 * hash) + REMOTE_FLOW_CONTROL_WINDOW_FIELD_NUMBER;
+      hash = (53 * hash) + getRemoteFlowControlWindow().hashCode();
+    }
+    if (getOptionCount() > 0) {
+      hash = (37 * hash) + OPTION_FIELD_NUMBER;
+      hash = (53 * hash) + getOptionList().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.SocketData parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketData parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketData parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketData parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketData parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketData parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketData parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketData parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketData parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketData parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketData parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketData parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.SocketData prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * SocketData is data associated for a specific Socket.  The fields present
+   * are specific to the implementation, so there may be minor differences in
+   * the semantics.  (e.g. flow control windows)
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.SocketData}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.SocketData)
+      io.grpc.channelz.v1.SocketDataOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketData_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketData_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.SocketData.class, io.grpc.channelz.v1.SocketData.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.SocketData.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+        getOptionFieldBuilder();
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      streamsStarted_ = 0L;
+
+      streamsSucceeded_ = 0L;
+
+      streamsFailed_ = 0L;
+
+      messagesSent_ = 0L;
+
+      messagesReceived_ = 0L;
+
+      keepAlivesSent_ = 0L;
+
+      if (lastLocalStreamCreatedTimestampBuilder_ == null) {
+        lastLocalStreamCreatedTimestamp_ = null;
+      } else {
+        lastLocalStreamCreatedTimestamp_ = null;
+        lastLocalStreamCreatedTimestampBuilder_ = null;
+      }
+      if (lastRemoteStreamCreatedTimestampBuilder_ == null) {
+        lastRemoteStreamCreatedTimestamp_ = null;
+      } else {
+        lastRemoteStreamCreatedTimestamp_ = null;
+        lastRemoteStreamCreatedTimestampBuilder_ = null;
+      }
+      if (lastMessageSentTimestampBuilder_ == null) {
+        lastMessageSentTimestamp_ = null;
+      } else {
+        lastMessageSentTimestamp_ = null;
+        lastMessageSentTimestampBuilder_ = null;
+      }
+      if (lastMessageReceivedTimestampBuilder_ == null) {
+        lastMessageReceivedTimestamp_ = null;
+      } else {
+        lastMessageReceivedTimestamp_ = null;
+        lastMessageReceivedTimestampBuilder_ = null;
+      }
+      if (localFlowControlWindowBuilder_ == null) {
+        localFlowControlWindow_ = null;
+      } else {
+        localFlowControlWindow_ = null;
+        localFlowControlWindowBuilder_ = null;
+      }
+      if (remoteFlowControlWindowBuilder_ == null) {
+        remoteFlowControlWindow_ = null;
+      } else {
+        remoteFlowControlWindow_ = null;
+        remoteFlowControlWindowBuilder_ = null;
+      }
+      if (optionBuilder_ == null) {
+        option_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00001000);
+      } else {
+        optionBuilder_.clear();
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketData_descriptor;
+    }
+
+    public io.grpc.channelz.v1.SocketData getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.SocketData.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.SocketData build() {
+      io.grpc.channelz.v1.SocketData result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.SocketData buildPartial() {
+      io.grpc.channelz.v1.SocketData result = new io.grpc.channelz.v1.SocketData(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      result.streamsStarted_ = streamsStarted_;
+      result.streamsSucceeded_ = streamsSucceeded_;
+      result.streamsFailed_ = streamsFailed_;
+      result.messagesSent_ = messagesSent_;
+      result.messagesReceived_ = messagesReceived_;
+      result.keepAlivesSent_ = keepAlivesSent_;
+      if (lastLocalStreamCreatedTimestampBuilder_ == null) {
+        result.lastLocalStreamCreatedTimestamp_ = lastLocalStreamCreatedTimestamp_;
+      } else {
+        result.lastLocalStreamCreatedTimestamp_ = lastLocalStreamCreatedTimestampBuilder_.build();
+      }
+      if (lastRemoteStreamCreatedTimestampBuilder_ == null) {
+        result.lastRemoteStreamCreatedTimestamp_ = lastRemoteStreamCreatedTimestamp_;
+      } else {
+        result.lastRemoteStreamCreatedTimestamp_ = lastRemoteStreamCreatedTimestampBuilder_.build();
+      }
+      if (lastMessageSentTimestampBuilder_ == null) {
+        result.lastMessageSentTimestamp_ = lastMessageSentTimestamp_;
+      } else {
+        result.lastMessageSentTimestamp_ = lastMessageSentTimestampBuilder_.build();
+      }
+      if (lastMessageReceivedTimestampBuilder_ == null) {
+        result.lastMessageReceivedTimestamp_ = lastMessageReceivedTimestamp_;
+      } else {
+        result.lastMessageReceivedTimestamp_ = lastMessageReceivedTimestampBuilder_.build();
+      }
+      if (localFlowControlWindowBuilder_ == null) {
+        result.localFlowControlWindow_ = localFlowControlWindow_;
+      } else {
+        result.localFlowControlWindow_ = localFlowControlWindowBuilder_.build();
+      }
+      if (remoteFlowControlWindowBuilder_ == null) {
+        result.remoteFlowControlWindow_ = remoteFlowControlWindow_;
+      } else {
+        result.remoteFlowControlWindow_ = remoteFlowControlWindowBuilder_.build();
+      }
+      if (optionBuilder_ == null) {
+        if (((bitField0_ & 0x00001000) == 0x00001000)) {
+          option_ = java.util.Collections.unmodifiableList(option_);
+          bitField0_ = (bitField0_ & ~0x00001000);
+        }
+        result.option_ = option_;
+      } else {
+        result.option_ = optionBuilder_.build();
+      }
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.SocketData) {
+        return mergeFrom((io.grpc.channelz.v1.SocketData)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.SocketData other) {
+      if (other == io.grpc.channelz.v1.SocketData.getDefaultInstance()) return this;
+      if (other.getStreamsStarted() != 0L) {
+        setStreamsStarted(other.getStreamsStarted());
+      }
+      if (other.getStreamsSucceeded() != 0L) {
+        setStreamsSucceeded(other.getStreamsSucceeded());
+      }
+      if (other.getStreamsFailed() != 0L) {
+        setStreamsFailed(other.getStreamsFailed());
+      }
+      if (other.getMessagesSent() != 0L) {
+        setMessagesSent(other.getMessagesSent());
+      }
+      if (other.getMessagesReceived() != 0L) {
+        setMessagesReceived(other.getMessagesReceived());
+      }
+      if (other.getKeepAlivesSent() != 0L) {
+        setKeepAlivesSent(other.getKeepAlivesSent());
+      }
+      if (other.hasLastLocalStreamCreatedTimestamp()) {
+        mergeLastLocalStreamCreatedTimestamp(other.getLastLocalStreamCreatedTimestamp());
+      }
+      if (other.hasLastRemoteStreamCreatedTimestamp()) {
+        mergeLastRemoteStreamCreatedTimestamp(other.getLastRemoteStreamCreatedTimestamp());
+      }
+      if (other.hasLastMessageSentTimestamp()) {
+        mergeLastMessageSentTimestamp(other.getLastMessageSentTimestamp());
+      }
+      if (other.hasLastMessageReceivedTimestamp()) {
+        mergeLastMessageReceivedTimestamp(other.getLastMessageReceivedTimestamp());
+      }
+      if (other.hasLocalFlowControlWindow()) {
+        mergeLocalFlowControlWindow(other.getLocalFlowControlWindow());
+      }
+      if (other.hasRemoteFlowControlWindow()) {
+        mergeRemoteFlowControlWindow(other.getRemoteFlowControlWindow());
+      }
+      if (optionBuilder_ == null) {
+        if (!other.option_.isEmpty()) {
+          if (option_.isEmpty()) {
+            option_ = other.option_;
+            bitField0_ = (bitField0_ & ~0x00001000);
+          } else {
+            ensureOptionIsMutable();
+            option_.addAll(other.option_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.option_.isEmpty()) {
+          if (optionBuilder_.isEmpty()) {
+            optionBuilder_.dispose();
+            optionBuilder_ = null;
+            option_ = other.option_;
+            bitField0_ = (bitField0_ & ~0x00001000);
+            optionBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getOptionFieldBuilder() : null;
+          } else {
+            optionBuilder_.addAllMessages(other.option_);
+          }
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.SocketData parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.SocketData) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private long streamsStarted_ ;
+    /**
+     * <pre>
+     * The number of streams that have been started.
+     * </pre>
+     *
+     * <code>int64 streams_started = 1;</code>
+     */
+    public long getStreamsStarted() {
+      return streamsStarted_;
+    }
+    /**
+     * <pre>
+     * The number of streams that have been started.
+     * </pre>
+     *
+     * <code>int64 streams_started = 1;</code>
+     */
+    public Builder setStreamsStarted(long value) {
+      
+      streamsStarted_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The number of streams that have been started.
+     * </pre>
+     *
+     * <code>int64 streams_started = 1;</code>
+     */
+    public Builder clearStreamsStarted() {
+      
+      streamsStarted_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long streamsSucceeded_ ;
+    /**
+     * <pre>
+     * The number of streams that have ended successfully:
+     * On client side, received frame with eos bit set;
+     * On server side, sent frame with eos bit set.
+     * </pre>
+     *
+     * <code>int64 streams_succeeded = 2;</code>
+     */
+    public long getStreamsSucceeded() {
+      return streamsSucceeded_;
+    }
+    /**
+     * <pre>
+     * The number of streams that have ended successfully:
+     * On client side, received frame with eos bit set;
+     * On server side, sent frame with eos bit set.
+     * </pre>
+     *
+     * <code>int64 streams_succeeded = 2;</code>
+     */
+    public Builder setStreamsSucceeded(long value) {
+      
+      streamsSucceeded_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The number of streams that have ended successfully:
+     * On client side, received frame with eos bit set;
+     * On server side, sent frame with eos bit set.
+     * </pre>
+     *
+     * <code>int64 streams_succeeded = 2;</code>
+     */
+    public Builder clearStreamsSucceeded() {
+      
+      streamsSucceeded_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long streamsFailed_ ;
+    /**
+     * <pre>
+     * The number of streams that have ended unsuccessfully:
+     * On client side, ended without receiving frame with eos bit set;
+     * On server side, ended without sending frame with eos bit set.
+     * </pre>
+     *
+     * <code>int64 streams_failed = 3;</code>
+     */
+    public long getStreamsFailed() {
+      return streamsFailed_;
+    }
+    /**
+     * <pre>
+     * The number of streams that have ended unsuccessfully:
+     * On client side, ended without receiving frame with eos bit set;
+     * On server side, ended without sending frame with eos bit set.
+     * </pre>
+     *
+     * <code>int64 streams_failed = 3;</code>
+     */
+    public Builder setStreamsFailed(long value) {
+      
+      streamsFailed_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The number of streams that have ended unsuccessfully:
+     * On client side, ended without receiving frame with eos bit set;
+     * On server side, ended without sending frame with eos bit set.
+     * </pre>
+     *
+     * <code>int64 streams_failed = 3;</code>
+     */
+    public Builder clearStreamsFailed() {
+      
+      streamsFailed_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long messagesSent_ ;
+    /**
+     * <pre>
+     * The number of grpc messages successfully sent on this socket.
+     * </pre>
+     *
+     * <code>int64 messages_sent = 4;</code>
+     */
+    public long getMessagesSent() {
+      return messagesSent_;
+    }
+    /**
+     * <pre>
+     * The number of grpc messages successfully sent on this socket.
+     * </pre>
+     *
+     * <code>int64 messages_sent = 4;</code>
+     */
+    public Builder setMessagesSent(long value) {
+      
+      messagesSent_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The number of grpc messages successfully sent on this socket.
+     * </pre>
+     *
+     * <code>int64 messages_sent = 4;</code>
+     */
+    public Builder clearMessagesSent() {
+      
+      messagesSent_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long messagesReceived_ ;
+    /**
+     * <pre>
+     * The number of grpc messages received on this socket.
+     * </pre>
+     *
+     * <code>int64 messages_received = 5;</code>
+     */
+    public long getMessagesReceived() {
+      return messagesReceived_;
+    }
+    /**
+     * <pre>
+     * The number of grpc messages received on this socket.
+     * </pre>
+     *
+     * <code>int64 messages_received = 5;</code>
+     */
+    public Builder setMessagesReceived(long value) {
+      
+      messagesReceived_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The number of grpc messages received on this socket.
+     * </pre>
+     *
+     * <code>int64 messages_received = 5;</code>
+     */
+    public Builder clearMessagesReceived() {
+      
+      messagesReceived_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private long keepAlivesSent_ ;
+    /**
+     * <pre>
+     * The number of keep alives sent.  This is typically implemented with HTTP/2
+     * ping messages.
+     * </pre>
+     *
+     * <code>int64 keep_alives_sent = 6;</code>
+     */
+    public long getKeepAlivesSent() {
+      return keepAlivesSent_;
+    }
+    /**
+     * <pre>
+     * The number of keep alives sent.  This is typically implemented with HTTP/2
+     * ping messages.
+     * </pre>
+     *
+     * <code>int64 keep_alives_sent = 6;</code>
+     */
+    public Builder setKeepAlivesSent(long value) {
+      
+      keepAlivesSent_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The number of keep alives sent.  This is typically implemented with HTTP/2
+     * ping messages.
+     * </pre>
+     *
+     * <code>int64 keep_alives_sent = 6;</code>
+     */
+    public Builder clearKeepAlivesSent() {
+      
+      keepAlivesSent_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.Timestamp lastLocalStreamCreatedTimestamp_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> lastLocalStreamCreatedTimestampBuilder_;
+    /**
+     * <pre>
+     * The last time a stream was created by this endpoint.  Usually unset for
+     * servers.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+     */
+    public boolean hasLastLocalStreamCreatedTimestamp() {
+      return lastLocalStreamCreatedTimestampBuilder_ != null || lastLocalStreamCreatedTimestamp_ != null;
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by this endpoint.  Usually unset for
+     * servers.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+     */
+    public com.google.protobuf.Timestamp getLastLocalStreamCreatedTimestamp() {
+      if (lastLocalStreamCreatedTimestampBuilder_ == null) {
+        return lastLocalStreamCreatedTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : lastLocalStreamCreatedTimestamp_;
+      } else {
+        return lastLocalStreamCreatedTimestampBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by this endpoint.  Usually unset for
+     * servers.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+     */
+    public Builder setLastLocalStreamCreatedTimestamp(com.google.protobuf.Timestamp value) {
+      if (lastLocalStreamCreatedTimestampBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        lastLocalStreamCreatedTimestamp_ = value;
+        onChanged();
+      } else {
+        lastLocalStreamCreatedTimestampBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by this endpoint.  Usually unset for
+     * servers.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+     */
+    public Builder setLastLocalStreamCreatedTimestamp(
+        com.google.protobuf.Timestamp.Builder builderForValue) {
+      if (lastLocalStreamCreatedTimestampBuilder_ == null) {
+        lastLocalStreamCreatedTimestamp_ = builderForValue.build();
+        onChanged();
+      } else {
+        lastLocalStreamCreatedTimestampBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by this endpoint.  Usually unset for
+     * servers.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+     */
+    public Builder mergeLastLocalStreamCreatedTimestamp(com.google.protobuf.Timestamp value) {
+      if (lastLocalStreamCreatedTimestampBuilder_ == null) {
+        if (lastLocalStreamCreatedTimestamp_ != null) {
+          lastLocalStreamCreatedTimestamp_ =
+            com.google.protobuf.Timestamp.newBuilder(lastLocalStreamCreatedTimestamp_).mergeFrom(value).buildPartial();
+        } else {
+          lastLocalStreamCreatedTimestamp_ = value;
+        }
+        onChanged();
+      } else {
+        lastLocalStreamCreatedTimestampBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by this endpoint.  Usually unset for
+     * servers.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+     */
+    public Builder clearLastLocalStreamCreatedTimestamp() {
+      if (lastLocalStreamCreatedTimestampBuilder_ == null) {
+        lastLocalStreamCreatedTimestamp_ = null;
+        onChanged();
+      } else {
+        lastLocalStreamCreatedTimestamp_ = null;
+        lastLocalStreamCreatedTimestampBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by this endpoint.  Usually unset for
+     * servers.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+     */
+    public com.google.protobuf.Timestamp.Builder getLastLocalStreamCreatedTimestampBuilder() {
+      
+      onChanged();
+      return getLastLocalStreamCreatedTimestampFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by this endpoint.  Usually unset for
+     * servers.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+     */
+    public com.google.protobuf.TimestampOrBuilder getLastLocalStreamCreatedTimestampOrBuilder() {
+      if (lastLocalStreamCreatedTimestampBuilder_ != null) {
+        return lastLocalStreamCreatedTimestampBuilder_.getMessageOrBuilder();
+      } else {
+        return lastLocalStreamCreatedTimestamp_ == null ?
+            com.google.protobuf.Timestamp.getDefaultInstance() : lastLocalStreamCreatedTimestamp_;
+      }
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by this endpoint.  Usually unset for
+     * servers.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> 
+        getLastLocalStreamCreatedTimestampFieldBuilder() {
+      if (lastLocalStreamCreatedTimestampBuilder_ == null) {
+        lastLocalStreamCreatedTimestampBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder>(
+                getLastLocalStreamCreatedTimestamp(),
+                getParentForChildren(),
+                isClean());
+        lastLocalStreamCreatedTimestamp_ = null;
+      }
+      return lastLocalStreamCreatedTimestampBuilder_;
+    }
+
+    private com.google.protobuf.Timestamp lastRemoteStreamCreatedTimestamp_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> lastRemoteStreamCreatedTimestampBuilder_;
+    /**
+     * <pre>
+     * The last time a stream was created by the remote endpoint.  Usually unset
+     * for clients.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+     */
+    public boolean hasLastRemoteStreamCreatedTimestamp() {
+      return lastRemoteStreamCreatedTimestampBuilder_ != null || lastRemoteStreamCreatedTimestamp_ != null;
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by the remote endpoint.  Usually unset
+     * for clients.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+     */
+    public com.google.protobuf.Timestamp getLastRemoteStreamCreatedTimestamp() {
+      if (lastRemoteStreamCreatedTimestampBuilder_ == null) {
+        return lastRemoteStreamCreatedTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : lastRemoteStreamCreatedTimestamp_;
+      } else {
+        return lastRemoteStreamCreatedTimestampBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by the remote endpoint.  Usually unset
+     * for clients.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+     */
+    public Builder setLastRemoteStreamCreatedTimestamp(com.google.protobuf.Timestamp value) {
+      if (lastRemoteStreamCreatedTimestampBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        lastRemoteStreamCreatedTimestamp_ = value;
+        onChanged();
+      } else {
+        lastRemoteStreamCreatedTimestampBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by the remote endpoint.  Usually unset
+     * for clients.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+     */
+    public Builder setLastRemoteStreamCreatedTimestamp(
+        com.google.protobuf.Timestamp.Builder builderForValue) {
+      if (lastRemoteStreamCreatedTimestampBuilder_ == null) {
+        lastRemoteStreamCreatedTimestamp_ = builderForValue.build();
+        onChanged();
+      } else {
+        lastRemoteStreamCreatedTimestampBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by the remote endpoint.  Usually unset
+     * for clients.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+     */
+    public Builder mergeLastRemoteStreamCreatedTimestamp(com.google.protobuf.Timestamp value) {
+      if (lastRemoteStreamCreatedTimestampBuilder_ == null) {
+        if (lastRemoteStreamCreatedTimestamp_ != null) {
+          lastRemoteStreamCreatedTimestamp_ =
+            com.google.protobuf.Timestamp.newBuilder(lastRemoteStreamCreatedTimestamp_).mergeFrom(value).buildPartial();
+        } else {
+          lastRemoteStreamCreatedTimestamp_ = value;
+        }
+        onChanged();
+      } else {
+        lastRemoteStreamCreatedTimestampBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by the remote endpoint.  Usually unset
+     * for clients.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+     */
+    public Builder clearLastRemoteStreamCreatedTimestamp() {
+      if (lastRemoteStreamCreatedTimestampBuilder_ == null) {
+        lastRemoteStreamCreatedTimestamp_ = null;
+        onChanged();
+      } else {
+        lastRemoteStreamCreatedTimestamp_ = null;
+        lastRemoteStreamCreatedTimestampBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by the remote endpoint.  Usually unset
+     * for clients.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+     */
+    public com.google.protobuf.Timestamp.Builder getLastRemoteStreamCreatedTimestampBuilder() {
+      
+      onChanged();
+      return getLastRemoteStreamCreatedTimestampFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by the remote endpoint.  Usually unset
+     * for clients.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+     */
+    public com.google.protobuf.TimestampOrBuilder getLastRemoteStreamCreatedTimestampOrBuilder() {
+      if (lastRemoteStreamCreatedTimestampBuilder_ != null) {
+        return lastRemoteStreamCreatedTimestampBuilder_.getMessageOrBuilder();
+      } else {
+        return lastRemoteStreamCreatedTimestamp_ == null ?
+            com.google.protobuf.Timestamp.getDefaultInstance() : lastRemoteStreamCreatedTimestamp_;
+      }
+    }
+    /**
+     * <pre>
+     * The last time a stream was created by the remote endpoint.  Usually unset
+     * for clients.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> 
+        getLastRemoteStreamCreatedTimestampFieldBuilder() {
+      if (lastRemoteStreamCreatedTimestampBuilder_ == null) {
+        lastRemoteStreamCreatedTimestampBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder>(
+                getLastRemoteStreamCreatedTimestamp(),
+                getParentForChildren(),
+                isClean());
+        lastRemoteStreamCreatedTimestamp_ = null;
+      }
+      return lastRemoteStreamCreatedTimestampBuilder_;
+    }
+
+    private com.google.protobuf.Timestamp lastMessageSentTimestamp_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> lastMessageSentTimestampBuilder_;
+    /**
+     * <pre>
+     * The last time a message was sent by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+     */
+    public boolean hasLastMessageSentTimestamp() {
+      return lastMessageSentTimestampBuilder_ != null || lastMessageSentTimestamp_ != null;
+    }
+    /**
+     * <pre>
+     * The last time a message was sent by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+     */
+    public com.google.protobuf.Timestamp getLastMessageSentTimestamp() {
+      if (lastMessageSentTimestampBuilder_ == null) {
+        return lastMessageSentTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : lastMessageSentTimestamp_;
+      } else {
+        return lastMessageSentTimestampBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The last time a message was sent by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+     */
+    public Builder setLastMessageSentTimestamp(com.google.protobuf.Timestamp value) {
+      if (lastMessageSentTimestampBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        lastMessageSentTimestamp_ = value;
+        onChanged();
+      } else {
+        lastMessageSentTimestampBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a message was sent by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+     */
+    public Builder setLastMessageSentTimestamp(
+        com.google.protobuf.Timestamp.Builder builderForValue) {
+      if (lastMessageSentTimestampBuilder_ == null) {
+        lastMessageSentTimestamp_ = builderForValue.build();
+        onChanged();
+      } else {
+        lastMessageSentTimestampBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a message was sent by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+     */
+    public Builder mergeLastMessageSentTimestamp(com.google.protobuf.Timestamp value) {
+      if (lastMessageSentTimestampBuilder_ == null) {
+        if (lastMessageSentTimestamp_ != null) {
+          lastMessageSentTimestamp_ =
+            com.google.protobuf.Timestamp.newBuilder(lastMessageSentTimestamp_).mergeFrom(value).buildPartial();
+        } else {
+          lastMessageSentTimestamp_ = value;
+        }
+        onChanged();
+      } else {
+        lastMessageSentTimestampBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a message was sent by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+     */
+    public Builder clearLastMessageSentTimestamp() {
+      if (lastMessageSentTimestampBuilder_ == null) {
+        lastMessageSentTimestamp_ = null;
+        onChanged();
+      } else {
+        lastMessageSentTimestamp_ = null;
+        lastMessageSentTimestampBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a message was sent by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+     */
+    public com.google.protobuf.Timestamp.Builder getLastMessageSentTimestampBuilder() {
+      
+      onChanged();
+      return getLastMessageSentTimestampFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The last time a message was sent by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+     */
+    public com.google.protobuf.TimestampOrBuilder getLastMessageSentTimestampOrBuilder() {
+      if (lastMessageSentTimestampBuilder_ != null) {
+        return lastMessageSentTimestampBuilder_.getMessageOrBuilder();
+      } else {
+        return lastMessageSentTimestamp_ == null ?
+            com.google.protobuf.Timestamp.getDefaultInstance() : lastMessageSentTimestamp_;
+      }
+    }
+    /**
+     * <pre>
+     * The last time a message was sent by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> 
+        getLastMessageSentTimestampFieldBuilder() {
+      if (lastMessageSentTimestampBuilder_ == null) {
+        lastMessageSentTimestampBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder>(
+                getLastMessageSentTimestamp(),
+                getParentForChildren(),
+                isClean());
+        lastMessageSentTimestamp_ = null;
+      }
+      return lastMessageSentTimestampBuilder_;
+    }
+
+    private com.google.protobuf.Timestamp lastMessageReceivedTimestamp_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> lastMessageReceivedTimestampBuilder_;
+    /**
+     * <pre>
+     * The last time a message was received by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+     */
+    public boolean hasLastMessageReceivedTimestamp() {
+      return lastMessageReceivedTimestampBuilder_ != null || lastMessageReceivedTimestamp_ != null;
+    }
+    /**
+     * <pre>
+     * The last time a message was received by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+     */
+    public com.google.protobuf.Timestamp getLastMessageReceivedTimestamp() {
+      if (lastMessageReceivedTimestampBuilder_ == null) {
+        return lastMessageReceivedTimestamp_ == null ? com.google.protobuf.Timestamp.getDefaultInstance() : lastMessageReceivedTimestamp_;
+      } else {
+        return lastMessageReceivedTimestampBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The last time a message was received by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+     */
+    public Builder setLastMessageReceivedTimestamp(com.google.protobuf.Timestamp value) {
+      if (lastMessageReceivedTimestampBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        lastMessageReceivedTimestamp_ = value;
+        onChanged();
+      } else {
+        lastMessageReceivedTimestampBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a message was received by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+     */
+    public Builder setLastMessageReceivedTimestamp(
+        com.google.protobuf.Timestamp.Builder builderForValue) {
+      if (lastMessageReceivedTimestampBuilder_ == null) {
+        lastMessageReceivedTimestamp_ = builderForValue.build();
+        onChanged();
+      } else {
+        lastMessageReceivedTimestampBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a message was received by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+     */
+    public Builder mergeLastMessageReceivedTimestamp(com.google.protobuf.Timestamp value) {
+      if (lastMessageReceivedTimestampBuilder_ == null) {
+        if (lastMessageReceivedTimestamp_ != null) {
+          lastMessageReceivedTimestamp_ =
+            com.google.protobuf.Timestamp.newBuilder(lastMessageReceivedTimestamp_).mergeFrom(value).buildPartial();
+        } else {
+          lastMessageReceivedTimestamp_ = value;
+        }
+        onChanged();
+      } else {
+        lastMessageReceivedTimestampBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a message was received by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+     */
+    public Builder clearLastMessageReceivedTimestamp() {
+      if (lastMessageReceivedTimestampBuilder_ == null) {
+        lastMessageReceivedTimestamp_ = null;
+        onChanged();
+      } else {
+        lastMessageReceivedTimestamp_ = null;
+        lastMessageReceivedTimestampBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The last time a message was received by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+     */
+    public com.google.protobuf.Timestamp.Builder getLastMessageReceivedTimestampBuilder() {
+      
+      onChanged();
+      return getLastMessageReceivedTimestampFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The last time a message was received by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+     */
+    public com.google.protobuf.TimestampOrBuilder getLastMessageReceivedTimestampOrBuilder() {
+      if (lastMessageReceivedTimestampBuilder_ != null) {
+        return lastMessageReceivedTimestampBuilder_.getMessageOrBuilder();
+      } else {
+        return lastMessageReceivedTimestamp_ == null ?
+            com.google.protobuf.Timestamp.getDefaultInstance() : lastMessageReceivedTimestamp_;
+      }
+    }
+    /**
+     * <pre>
+     * The last time a message was received by this endpoint.
+     * </pre>
+     *
+     * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder> 
+        getLastMessageReceivedTimestampFieldBuilder() {
+      if (lastMessageReceivedTimestampBuilder_ == null) {
+        lastMessageReceivedTimestampBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Timestamp, com.google.protobuf.Timestamp.Builder, com.google.protobuf.TimestampOrBuilder>(
+                getLastMessageReceivedTimestamp(),
+                getParentForChildren(),
+                isClean());
+        lastMessageReceivedTimestamp_ = null;
+      }
+      return lastMessageReceivedTimestampBuilder_;
+    }
+
+    private com.google.protobuf.Int64Value localFlowControlWindow_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Int64Value, com.google.protobuf.Int64Value.Builder, com.google.protobuf.Int64ValueOrBuilder> localFlowControlWindowBuilder_;
+    /**
+     * <pre>
+     * The amount of window, granted to the local endpoint by the remote endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+     */
+    public boolean hasLocalFlowControlWindow() {
+      return localFlowControlWindowBuilder_ != null || localFlowControlWindow_ != null;
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the local endpoint by the remote endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+     */
+    public com.google.protobuf.Int64Value getLocalFlowControlWindow() {
+      if (localFlowControlWindowBuilder_ == null) {
+        return localFlowControlWindow_ == null ? com.google.protobuf.Int64Value.getDefaultInstance() : localFlowControlWindow_;
+      } else {
+        return localFlowControlWindowBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the local endpoint by the remote endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+     */
+    public Builder setLocalFlowControlWindow(com.google.protobuf.Int64Value value) {
+      if (localFlowControlWindowBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        localFlowControlWindow_ = value;
+        onChanged();
+      } else {
+        localFlowControlWindowBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the local endpoint by the remote endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+     */
+    public Builder setLocalFlowControlWindow(
+        com.google.protobuf.Int64Value.Builder builderForValue) {
+      if (localFlowControlWindowBuilder_ == null) {
+        localFlowControlWindow_ = builderForValue.build();
+        onChanged();
+      } else {
+        localFlowControlWindowBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the local endpoint by the remote endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+     */
+    public Builder mergeLocalFlowControlWindow(com.google.protobuf.Int64Value value) {
+      if (localFlowControlWindowBuilder_ == null) {
+        if (localFlowControlWindow_ != null) {
+          localFlowControlWindow_ =
+            com.google.protobuf.Int64Value.newBuilder(localFlowControlWindow_).mergeFrom(value).buildPartial();
+        } else {
+          localFlowControlWindow_ = value;
+        }
+        onChanged();
+      } else {
+        localFlowControlWindowBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the local endpoint by the remote endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+     */
+    public Builder clearLocalFlowControlWindow() {
+      if (localFlowControlWindowBuilder_ == null) {
+        localFlowControlWindow_ = null;
+        onChanged();
+      } else {
+        localFlowControlWindow_ = null;
+        localFlowControlWindowBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the local endpoint by the remote endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+     */
+    public com.google.protobuf.Int64Value.Builder getLocalFlowControlWindowBuilder() {
+      
+      onChanged();
+      return getLocalFlowControlWindowFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the local endpoint by the remote endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+     */
+    public com.google.protobuf.Int64ValueOrBuilder getLocalFlowControlWindowOrBuilder() {
+      if (localFlowControlWindowBuilder_ != null) {
+        return localFlowControlWindowBuilder_.getMessageOrBuilder();
+      } else {
+        return localFlowControlWindow_ == null ?
+            com.google.protobuf.Int64Value.getDefaultInstance() : localFlowControlWindow_;
+      }
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the local endpoint by the remote endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Int64Value, com.google.protobuf.Int64Value.Builder, com.google.protobuf.Int64ValueOrBuilder> 
+        getLocalFlowControlWindowFieldBuilder() {
+      if (localFlowControlWindowBuilder_ == null) {
+        localFlowControlWindowBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Int64Value, com.google.protobuf.Int64Value.Builder, com.google.protobuf.Int64ValueOrBuilder>(
+                getLocalFlowControlWindow(),
+                getParentForChildren(),
+                isClean());
+        localFlowControlWindow_ = null;
+      }
+      return localFlowControlWindowBuilder_;
+    }
+
+    private com.google.protobuf.Int64Value remoteFlowControlWindow_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Int64Value, com.google.protobuf.Int64Value.Builder, com.google.protobuf.Int64ValueOrBuilder> remoteFlowControlWindowBuilder_;
+    /**
+     * <pre>
+     * The amount of window, granted to the remote endpoint by the local endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+     */
+    public boolean hasRemoteFlowControlWindow() {
+      return remoteFlowControlWindowBuilder_ != null || remoteFlowControlWindow_ != null;
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the remote endpoint by the local endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+     */
+    public com.google.protobuf.Int64Value getRemoteFlowControlWindow() {
+      if (remoteFlowControlWindowBuilder_ == null) {
+        return remoteFlowControlWindow_ == null ? com.google.protobuf.Int64Value.getDefaultInstance() : remoteFlowControlWindow_;
+      } else {
+        return remoteFlowControlWindowBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the remote endpoint by the local endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+     */
+    public Builder setRemoteFlowControlWindow(com.google.protobuf.Int64Value value) {
+      if (remoteFlowControlWindowBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        remoteFlowControlWindow_ = value;
+        onChanged();
+      } else {
+        remoteFlowControlWindowBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the remote endpoint by the local endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+     */
+    public Builder setRemoteFlowControlWindow(
+        com.google.protobuf.Int64Value.Builder builderForValue) {
+      if (remoteFlowControlWindowBuilder_ == null) {
+        remoteFlowControlWindow_ = builderForValue.build();
+        onChanged();
+      } else {
+        remoteFlowControlWindowBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the remote endpoint by the local endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+     */
+    public Builder mergeRemoteFlowControlWindow(com.google.protobuf.Int64Value value) {
+      if (remoteFlowControlWindowBuilder_ == null) {
+        if (remoteFlowControlWindow_ != null) {
+          remoteFlowControlWindow_ =
+            com.google.protobuf.Int64Value.newBuilder(remoteFlowControlWindow_).mergeFrom(value).buildPartial();
+        } else {
+          remoteFlowControlWindow_ = value;
+        }
+        onChanged();
+      } else {
+        remoteFlowControlWindowBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the remote endpoint by the local endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+     */
+    public Builder clearRemoteFlowControlWindow() {
+      if (remoteFlowControlWindowBuilder_ == null) {
+        remoteFlowControlWindow_ = null;
+        onChanged();
+      } else {
+        remoteFlowControlWindow_ = null;
+        remoteFlowControlWindowBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the remote endpoint by the local endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+     */
+    public com.google.protobuf.Int64Value.Builder getRemoteFlowControlWindowBuilder() {
+      
+      onChanged();
+      return getRemoteFlowControlWindowFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the remote endpoint by the local endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+     */
+    public com.google.protobuf.Int64ValueOrBuilder getRemoteFlowControlWindowOrBuilder() {
+      if (remoteFlowControlWindowBuilder_ != null) {
+        return remoteFlowControlWindowBuilder_.getMessageOrBuilder();
+      } else {
+        return remoteFlowControlWindow_ == null ?
+            com.google.protobuf.Int64Value.getDefaultInstance() : remoteFlowControlWindow_;
+      }
+    }
+    /**
+     * <pre>
+     * The amount of window, granted to the remote endpoint by the local endpoint.
+     * This may be slightly out of date due to network latency.  This does NOT
+     * include stream level or TCP level flow control info.
+     * </pre>
+     *
+     * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Int64Value, com.google.protobuf.Int64Value.Builder, com.google.protobuf.Int64ValueOrBuilder> 
+        getRemoteFlowControlWindowFieldBuilder() {
+      if (remoteFlowControlWindowBuilder_ == null) {
+        remoteFlowControlWindowBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Int64Value, com.google.protobuf.Int64Value.Builder, com.google.protobuf.Int64ValueOrBuilder>(
+                getRemoteFlowControlWindow(),
+                getParentForChildren(),
+                isClean());
+        remoteFlowControlWindow_ = null;
+      }
+      return remoteFlowControlWindowBuilder_;
+    }
+
+    private java.util.List<io.grpc.channelz.v1.SocketOption> option_ =
+      java.util.Collections.emptyList();
+    private void ensureOptionIsMutable() {
+      if (!((bitField0_ & 0x00001000) == 0x00001000)) {
+        option_ = new java.util.ArrayList<io.grpc.channelz.v1.SocketOption>(option_);
+        bitField0_ |= 0x00001000;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SocketOption, io.grpc.channelz.v1.SocketOption.Builder, io.grpc.channelz.v1.SocketOptionOrBuilder> optionBuilder_;
+
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SocketOption> getOptionList() {
+      if (optionBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(option_);
+      } else {
+        return optionBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public int getOptionCount() {
+      if (optionBuilder_ == null) {
+        return option_.size();
+      } else {
+        return optionBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public io.grpc.channelz.v1.SocketOption getOption(int index) {
+      if (optionBuilder_ == null) {
+        return option_.get(index);
+      } else {
+        return optionBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public Builder setOption(
+        int index, io.grpc.channelz.v1.SocketOption value) {
+      if (optionBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureOptionIsMutable();
+        option_.set(index, value);
+        onChanged();
+      } else {
+        optionBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public Builder setOption(
+        int index, io.grpc.channelz.v1.SocketOption.Builder builderForValue) {
+      if (optionBuilder_ == null) {
+        ensureOptionIsMutable();
+        option_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        optionBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public Builder addOption(io.grpc.channelz.v1.SocketOption value) {
+      if (optionBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureOptionIsMutable();
+        option_.add(value);
+        onChanged();
+      } else {
+        optionBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public Builder addOption(
+        int index, io.grpc.channelz.v1.SocketOption value) {
+      if (optionBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureOptionIsMutable();
+        option_.add(index, value);
+        onChanged();
+      } else {
+        optionBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public Builder addOption(
+        io.grpc.channelz.v1.SocketOption.Builder builderForValue) {
+      if (optionBuilder_ == null) {
+        ensureOptionIsMutable();
+        option_.add(builderForValue.build());
+        onChanged();
+      } else {
+        optionBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public Builder addOption(
+        int index, io.grpc.channelz.v1.SocketOption.Builder builderForValue) {
+      if (optionBuilder_ == null) {
+        ensureOptionIsMutable();
+        option_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        optionBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public Builder addAllOption(
+        java.lang.Iterable<? extends io.grpc.channelz.v1.SocketOption> values) {
+      if (optionBuilder_ == null) {
+        ensureOptionIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, option_);
+        onChanged();
+      } else {
+        optionBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public Builder clearOption() {
+      if (optionBuilder_ == null) {
+        option_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00001000);
+        onChanged();
+      } else {
+        optionBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public Builder removeOption(int index) {
+      if (optionBuilder_ == null) {
+        ensureOptionIsMutable();
+        option_.remove(index);
+        onChanged();
+      } else {
+        optionBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public io.grpc.channelz.v1.SocketOption.Builder getOptionBuilder(
+        int index) {
+      return getOptionFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public io.grpc.channelz.v1.SocketOptionOrBuilder getOptionOrBuilder(
+        int index) {
+      if (optionBuilder_ == null) {
+        return option_.get(index);  } else {
+        return optionBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public java.util.List<? extends io.grpc.channelz.v1.SocketOptionOrBuilder> 
+         getOptionOrBuilderList() {
+      if (optionBuilder_ != null) {
+        return optionBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(option_);
+      }
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public io.grpc.channelz.v1.SocketOption.Builder addOptionBuilder() {
+      return getOptionFieldBuilder().addBuilder(
+          io.grpc.channelz.v1.SocketOption.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public io.grpc.channelz.v1.SocketOption.Builder addOptionBuilder(
+        int index) {
+      return getOptionFieldBuilder().addBuilder(
+          index, io.grpc.channelz.v1.SocketOption.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * Socket options set on this socket.  May be absent.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SocketOption.Builder> 
+         getOptionBuilderList() {
+      return getOptionFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SocketOption, io.grpc.channelz.v1.SocketOption.Builder, io.grpc.channelz.v1.SocketOptionOrBuilder> 
+        getOptionFieldBuilder() {
+      if (optionBuilder_ == null) {
+        optionBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.channelz.v1.SocketOption, io.grpc.channelz.v1.SocketOption.Builder, io.grpc.channelz.v1.SocketOptionOrBuilder>(
+                option_,
+                ((bitField0_ & 0x00001000) == 0x00001000),
+                getParentForChildren(),
+                isClean());
+        option_ = null;
+      }
+      return optionBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.SocketData)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.SocketData)
+  private static final io.grpc.channelz.v1.SocketData DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.SocketData();
+  }
+
+  public static io.grpc.channelz.v1.SocketData getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<SocketData>
+      PARSER = new com.google.protobuf.AbstractParser<SocketData>() {
+    public SocketData parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new SocketData(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<SocketData> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<SocketData> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.SocketData getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SocketDataOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/SocketDataOrBuilder.java
new file mode 100644
index 0000000..5740383
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SocketDataOrBuilder.java
@@ -0,0 +1,280 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface SocketDataOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.SocketData)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The number of streams that have been started.
+   * </pre>
+   *
+   * <code>int64 streams_started = 1;</code>
+   */
+  long getStreamsStarted();
+
+  /**
+   * <pre>
+   * The number of streams that have ended successfully:
+   * On client side, received frame with eos bit set;
+   * On server side, sent frame with eos bit set.
+   * </pre>
+   *
+   * <code>int64 streams_succeeded = 2;</code>
+   */
+  long getStreamsSucceeded();
+
+  /**
+   * <pre>
+   * The number of streams that have ended unsuccessfully:
+   * On client side, ended without receiving frame with eos bit set;
+   * On server side, ended without sending frame with eos bit set.
+   * </pre>
+   *
+   * <code>int64 streams_failed = 3;</code>
+   */
+  long getStreamsFailed();
+
+  /**
+   * <pre>
+   * The number of grpc messages successfully sent on this socket.
+   * </pre>
+   *
+   * <code>int64 messages_sent = 4;</code>
+   */
+  long getMessagesSent();
+
+  /**
+   * <pre>
+   * The number of grpc messages received on this socket.
+   * </pre>
+   *
+   * <code>int64 messages_received = 5;</code>
+   */
+  long getMessagesReceived();
+
+  /**
+   * <pre>
+   * The number of keep alives sent.  This is typically implemented with HTTP/2
+   * ping messages.
+   * </pre>
+   *
+   * <code>int64 keep_alives_sent = 6;</code>
+   */
+  long getKeepAlivesSent();
+
+  /**
+   * <pre>
+   * The last time a stream was created by this endpoint.  Usually unset for
+   * servers.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+   */
+  boolean hasLastLocalStreamCreatedTimestamp();
+  /**
+   * <pre>
+   * The last time a stream was created by this endpoint.  Usually unset for
+   * servers.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+   */
+  com.google.protobuf.Timestamp getLastLocalStreamCreatedTimestamp();
+  /**
+   * <pre>
+   * The last time a stream was created by this endpoint.  Usually unset for
+   * servers.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_local_stream_created_timestamp = 7;</code>
+   */
+  com.google.protobuf.TimestampOrBuilder getLastLocalStreamCreatedTimestampOrBuilder();
+
+  /**
+   * <pre>
+   * The last time a stream was created by the remote endpoint.  Usually unset
+   * for clients.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+   */
+  boolean hasLastRemoteStreamCreatedTimestamp();
+  /**
+   * <pre>
+   * The last time a stream was created by the remote endpoint.  Usually unset
+   * for clients.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+   */
+  com.google.protobuf.Timestamp getLastRemoteStreamCreatedTimestamp();
+  /**
+   * <pre>
+   * The last time a stream was created by the remote endpoint.  Usually unset
+   * for clients.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;</code>
+   */
+  com.google.protobuf.TimestampOrBuilder getLastRemoteStreamCreatedTimestampOrBuilder();
+
+  /**
+   * <pre>
+   * The last time a message was sent by this endpoint.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+   */
+  boolean hasLastMessageSentTimestamp();
+  /**
+   * <pre>
+   * The last time a message was sent by this endpoint.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+   */
+  com.google.protobuf.Timestamp getLastMessageSentTimestamp();
+  /**
+   * <pre>
+   * The last time a message was sent by this endpoint.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_message_sent_timestamp = 9;</code>
+   */
+  com.google.protobuf.TimestampOrBuilder getLastMessageSentTimestampOrBuilder();
+
+  /**
+   * <pre>
+   * The last time a message was received by this endpoint.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+   */
+  boolean hasLastMessageReceivedTimestamp();
+  /**
+   * <pre>
+   * The last time a message was received by this endpoint.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+   */
+  com.google.protobuf.Timestamp getLastMessageReceivedTimestamp();
+  /**
+   * <pre>
+   * The last time a message was received by this endpoint.
+   * </pre>
+   *
+   * <code>.google.protobuf.Timestamp last_message_received_timestamp = 10;</code>
+   */
+  com.google.protobuf.TimestampOrBuilder getLastMessageReceivedTimestampOrBuilder();
+
+  /**
+   * <pre>
+   * The amount of window, granted to the local endpoint by the remote endpoint.
+   * This may be slightly out of date due to network latency.  This does NOT
+   * include stream level or TCP level flow control info.
+   * </pre>
+   *
+   * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+   */
+  boolean hasLocalFlowControlWindow();
+  /**
+   * <pre>
+   * The amount of window, granted to the local endpoint by the remote endpoint.
+   * This may be slightly out of date due to network latency.  This does NOT
+   * include stream level or TCP level flow control info.
+   * </pre>
+   *
+   * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+   */
+  com.google.protobuf.Int64Value getLocalFlowControlWindow();
+  /**
+   * <pre>
+   * The amount of window, granted to the local endpoint by the remote endpoint.
+   * This may be slightly out of date due to network latency.  This does NOT
+   * include stream level or TCP level flow control info.
+   * </pre>
+   *
+   * <code>.google.protobuf.Int64Value local_flow_control_window = 11;</code>
+   */
+  com.google.protobuf.Int64ValueOrBuilder getLocalFlowControlWindowOrBuilder();
+
+  /**
+   * <pre>
+   * The amount of window, granted to the remote endpoint by the local endpoint.
+   * This may be slightly out of date due to network latency.  This does NOT
+   * include stream level or TCP level flow control info.
+   * </pre>
+   *
+   * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+   */
+  boolean hasRemoteFlowControlWindow();
+  /**
+   * <pre>
+   * The amount of window, granted to the remote endpoint by the local endpoint.
+   * This may be slightly out of date due to network latency.  This does NOT
+   * include stream level or TCP level flow control info.
+   * </pre>
+   *
+   * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+   */
+  com.google.protobuf.Int64Value getRemoteFlowControlWindow();
+  /**
+   * <pre>
+   * The amount of window, granted to the remote endpoint by the local endpoint.
+   * This may be slightly out of date due to network latency.  This does NOT
+   * include stream level or TCP level flow control info.
+   * </pre>
+   *
+   * <code>.google.protobuf.Int64Value remote_flow_control_window = 12;</code>
+   */
+  com.google.protobuf.Int64ValueOrBuilder getRemoteFlowControlWindowOrBuilder();
+
+  /**
+   * <pre>
+   * Socket options set on this socket.  May be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+   */
+  java.util.List<io.grpc.channelz.v1.SocketOption> 
+      getOptionList();
+  /**
+   * <pre>
+   * Socket options set on this socket.  May be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+   */
+  io.grpc.channelz.v1.SocketOption getOption(int index);
+  /**
+   * <pre>
+   * Socket options set on this socket.  May be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+   */
+  int getOptionCount();
+  /**
+   * <pre>
+   * Socket options set on this socket.  May be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+   */
+  java.util.List<? extends io.grpc.channelz.v1.SocketOptionOrBuilder> 
+      getOptionOrBuilderList();
+  /**
+   * <pre>
+   * Socket options set on this socket.  May be absent.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketOption option = 13;</code>
+   */
+  io.grpc.channelz.v1.SocketOptionOrBuilder getOptionOrBuilder(
+      int index);
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SocketOption.java b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOption.java
new file mode 100644
index 0000000..d70539d
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOption.java
@@ -0,0 +1,964 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * SocketOption represents socket options for a socket.  Specifically, these
+ * are the options returned by getsockopt().
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.SocketOption}
+ */
+public  final class SocketOption extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.SocketOption)
+    SocketOptionOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use SocketOption.newBuilder() to construct.
+  private SocketOption(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private SocketOption() {
+    name_ = "";
+    value_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private SocketOption(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            name_ = s;
+            break;
+          }
+          case 18: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            value_ = s;
+            break;
+          }
+          case 26: {
+            com.google.protobuf.Any.Builder subBuilder = null;
+            if (additional_ != null) {
+              subBuilder = additional_.toBuilder();
+            }
+            additional_ = input.readMessage(com.google.protobuf.Any.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(additional_);
+              additional_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOption_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOption_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.SocketOption.class, io.grpc.channelz.v1.SocketOption.Builder.class);
+  }
+
+  public static final int NAME_FIELD_NUMBER = 1;
+  private volatile java.lang.Object name_;
+  /**
+   * <pre>
+   * The full name of the socket option.  Typically this will be the upper case
+   * name, such as "SO_REUSEPORT".
+   * </pre>
+   *
+   * <code>string name = 1;</code>
+   */
+  public java.lang.String getName() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      name_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * The full name of the socket option.  Typically this will be the upper case
+   * name, such as "SO_REUSEPORT".
+   * </pre>
+   *
+   * <code>string name = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getNameBytes() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      name_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int VALUE_FIELD_NUMBER = 2;
+  private volatile java.lang.Object value_;
+  /**
+   * <pre>
+   * The human readable value of this socket option.  At least one of value or
+   * additional will be set.
+   * </pre>
+   *
+   * <code>string value = 2;</code>
+   */
+  public java.lang.String getValue() {
+    java.lang.Object ref = value_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      value_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * The human readable value of this socket option.  At least one of value or
+   * additional will be set.
+   * </pre>
+   *
+   * <code>string value = 2;</code>
+   */
+  public com.google.protobuf.ByteString
+      getValueBytes() {
+    java.lang.Object ref = value_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      value_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int ADDITIONAL_FIELD_NUMBER = 3;
+  private com.google.protobuf.Any additional_;
+  /**
+   * <pre>
+   * Additional data associated with the socket option.  At least one of value
+   * or additional will be set.
+   * </pre>
+   *
+   * <code>.google.protobuf.Any additional = 3;</code>
+   */
+  public boolean hasAdditional() {
+    return additional_ != null;
+  }
+  /**
+   * <pre>
+   * Additional data associated with the socket option.  At least one of value
+   * or additional will be set.
+   * </pre>
+   *
+   * <code>.google.protobuf.Any additional = 3;</code>
+   */
+  public com.google.protobuf.Any getAdditional() {
+    return additional_ == null ? com.google.protobuf.Any.getDefaultInstance() : additional_;
+  }
+  /**
+   * <pre>
+   * Additional data associated with the socket option.  At least one of value
+   * or additional will be set.
+   * </pre>
+   *
+   * <code>.google.protobuf.Any additional = 3;</code>
+   */
+  public com.google.protobuf.AnyOrBuilder getAdditionalOrBuilder() {
+    return getAdditional();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getNameBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_);
+    }
+    if (!getValueBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 2, value_);
+    }
+    if (additional_ != null) {
+      output.writeMessage(3, getAdditional());
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getNameBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_);
+    }
+    if (!getValueBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, value_);
+    }
+    if (additional_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(3, getAdditional());
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.SocketOption)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.SocketOption other = (io.grpc.channelz.v1.SocketOption) obj;
+
+    boolean result = true;
+    result = result && getName()
+        .equals(other.getName());
+    result = result && getValue()
+        .equals(other.getValue());
+    result = result && (hasAdditional() == other.hasAdditional());
+    if (hasAdditional()) {
+      result = result && getAdditional()
+          .equals(other.getAdditional());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + NAME_FIELD_NUMBER;
+    hash = (53 * hash) + getName().hashCode();
+    hash = (37 * hash) + VALUE_FIELD_NUMBER;
+    hash = (53 * hash) + getValue().hashCode();
+    if (hasAdditional()) {
+      hash = (37 * hash) + ADDITIONAL_FIELD_NUMBER;
+      hash = (53 * hash) + getAdditional().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.SocketOption parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketOption parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOption parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketOption parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOption parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketOption parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOption parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketOption parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOption parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketOption parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOption parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketOption parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.SocketOption prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * SocketOption represents socket options for a socket.  Specifically, these
+   * are the options returned by getsockopt().
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.SocketOption}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.SocketOption)
+      io.grpc.channelz.v1.SocketOptionOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOption_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOption_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.SocketOption.class, io.grpc.channelz.v1.SocketOption.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.SocketOption.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      name_ = "";
+
+      value_ = "";
+
+      if (additionalBuilder_ == null) {
+        additional_ = null;
+      } else {
+        additional_ = null;
+        additionalBuilder_ = null;
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOption_descriptor;
+    }
+
+    public io.grpc.channelz.v1.SocketOption getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.SocketOption.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.SocketOption build() {
+      io.grpc.channelz.v1.SocketOption result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.SocketOption buildPartial() {
+      io.grpc.channelz.v1.SocketOption result = new io.grpc.channelz.v1.SocketOption(this);
+      result.name_ = name_;
+      result.value_ = value_;
+      if (additionalBuilder_ == null) {
+        result.additional_ = additional_;
+      } else {
+        result.additional_ = additionalBuilder_.build();
+      }
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.SocketOption) {
+        return mergeFrom((io.grpc.channelz.v1.SocketOption)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.SocketOption other) {
+      if (other == io.grpc.channelz.v1.SocketOption.getDefaultInstance()) return this;
+      if (!other.getName().isEmpty()) {
+        name_ = other.name_;
+        onChanged();
+      }
+      if (!other.getValue().isEmpty()) {
+        value_ = other.value_;
+        onChanged();
+      }
+      if (other.hasAdditional()) {
+        mergeAdditional(other.getAdditional());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.SocketOption parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.SocketOption) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private java.lang.Object name_ = "";
+    /**
+     * <pre>
+     * The full name of the socket option.  Typically this will be the upper case
+     * name, such as "SO_REUSEPORT".
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        name_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * The full name of the socket option.  Typically this will be the upper case
+     * name, such as "SO_REUSEPORT".
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * The full name of the socket option.  Typically this will be the upper case
+     * name, such as "SO_REUSEPORT".
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public Builder setName(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      name_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The full name of the socket option.  Typically this will be the upper case
+     * name, such as "SO_REUSEPORT".
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public Builder clearName() {
+      
+      name_ = getDefaultInstance().getName();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The full name of the socket option.  Typically this will be the upper case
+     * name, such as "SO_REUSEPORT".
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public Builder setNameBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      name_ = value;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object value_ = "";
+    /**
+     * <pre>
+     * The human readable value of this socket option.  At least one of value or
+     * additional will be set.
+     * </pre>
+     *
+     * <code>string value = 2;</code>
+     */
+    public java.lang.String getValue() {
+      java.lang.Object ref = value_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        value_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * The human readable value of this socket option.  At least one of value or
+     * additional will be set.
+     * </pre>
+     *
+     * <code>string value = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getValueBytes() {
+      java.lang.Object ref = value_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        value_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * The human readable value of this socket option.  At least one of value or
+     * additional will be set.
+     * </pre>
+     *
+     * <code>string value = 2;</code>
+     */
+    public Builder setValue(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      value_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The human readable value of this socket option.  At least one of value or
+     * additional will be set.
+     * </pre>
+     *
+     * <code>string value = 2;</code>
+     */
+    public Builder clearValue() {
+      
+      value_ = getDefaultInstance().getValue();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The human readable value of this socket option.  At least one of value or
+     * additional will be set.
+     * </pre>
+     *
+     * <code>string value = 2;</code>
+     */
+    public Builder setValueBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      value_ = value;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.Any additional_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Any, com.google.protobuf.Any.Builder, com.google.protobuf.AnyOrBuilder> additionalBuilder_;
+    /**
+     * <pre>
+     * Additional data associated with the socket option.  At least one of value
+     * or additional will be set.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any additional = 3;</code>
+     */
+    public boolean hasAdditional() {
+      return additionalBuilder_ != null || additional_ != null;
+    }
+    /**
+     * <pre>
+     * Additional data associated with the socket option.  At least one of value
+     * or additional will be set.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any additional = 3;</code>
+     */
+    public com.google.protobuf.Any getAdditional() {
+      if (additionalBuilder_ == null) {
+        return additional_ == null ? com.google.protobuf.Any.getDefaultInstance() : additional_;
+      } else {
+        return additionalBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * Additional data associated with the socket option.  At least one of value
+     * or additional will be set.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any additional = 3;</code>
+     */
+    public Builder setAdditional(com.google.protobuf.Any value) {
+      if (additionalBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        additional_ = value;
+        onChanged();
+      } else {
+        additionalBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Additional data associated with the socket option.  At least one of value
+     * or additional will be set.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any additional = 3;</code>
+     */
+    public Builder setAdditional(
+        com.google.protobuf.Any.Builder builderForValue) {
+      if (additionalBuilder_ == null) {
+        additional_ = builderForValue.build();
+        onChanged();
+      } else {
+        additionalBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Additional data associated with the socket option.  At least one of value
+     * or additional will be set.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any additional = 3;</code>
+     */
+    public Builder mergeAdditional(com.google.protobuf.Any value) {
+      if (additionalBuilder_ == null) {
+        if (additional_ != null) {
+          additional_ =
+            com.google.protobuf.Any.newBuilder(additional_).mergeFrom(value).buildPartial();
+        } else {
+          additional_ = value;
+        }
+        onChanged();
+      } else {
+        additionalBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Additional data associated with the socket option.  At least one of value
+     * or additional will be set.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any additional = 3;</code>
+     */
+    public Builder clearAdditional() {
+      if (additionalBuilder_ == null) {
+        additional_ = null;
+        onChanged();
+      } else {
+        additional_ = null;
+        additionalBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Additional data associated with the socket option.  At least one of value
+     * or additional will be set.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any additional = 3;</code>
+     */
+    public com.google.protobuf.Any.Builder getAdditionalBuilder() {
+      
+      onChanged();
+      return getAdditionalFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Additional data associated with the socket option.  At least one of value
+     * or additional will be set.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any additional = 3;</code>
+     */
+    public com.google.protobuf.AnyOrBuilder getAdditionalOrBuilder() {
+      if (additionalBuilder_ != null) {
+        return additionalBuilder_.getMessageOrBuilder();
+      } else {
+        return additional_ == null ?
+            com.google.protobuf.Any.getDefaultInstance() : additional_;
+      }
+    }
+    /**
+     * <pre>
+     * Additional data associated with the socket option.  At least one of value
+     * or additional will be set.
+     * </pre>
+     *
+     * <code>.google.protobuf.Any additional = 3;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Any, com.google.protobuf.Any.Builder, com.google.protobuf.AnyOrBuilder> 
+        getAdditionalFieldBuilder() {
+      if (additionalBuilder_ == null) {
+        additionalBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Any, com.google.protobuf.Any.Builder, com.google.protobuf.AnyOrBuilder>(
+                getAdditional(),
+                getParentForChildren(),
+                isClean());
+        additional_ = null;
+      }
+      return additionalBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.SocketOption)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.SocketOption)
+  private static final io.grpc.channelz.v1.SocketOption DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.SocketOption();
+  }
+
+  public static io.grpc.channelz.v1.SocketOption getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<SocketOption>
+      PARSER = new com.google.protobuf.AbstractParser<SocketOption>() {
+    public SocketOption parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new SocketOption(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<SocketOption> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<SocketOption> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.SocketOption getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionLinger.java b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionLinger.java
new file mode 100644
index 0000000..7354b41
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionLinger.java
@@ -0,0 +1,703 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * For use with SocketOption's additional field.  This is primarily used for
+ * SO_LINGER.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.SocketOptionLinger}
+ */
+public  final class SocketOptionLinger extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.SocketOptionLinger)
+    SocketOptionLingerOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use SocketOptionLinger.newBuilder() to construct.
+  private SocketOptionLinger(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private SocketOptionLinger() {
+    active_ = false;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private SocketOptionLinger(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+
+            active_ = input.readBool();
+            break;
+          }
+          case 18: {
+            com.google.protobuf.Duration.Builder subBuilder = null;
+            if (duration_ != null) {
+              subBuilder = duration_.toBuilder();
+            }
+            duration_ = input.readMessage(com.google.protobuf.Duration.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(duration_);
+              duration_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionLinger_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionLinger_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.SocketOptionLinger.class, io.grpc.channelz.v1.SocketOptionLinger.Builder.class);
+  }
+
+  public static final int ACTIVE_FIELD_NUMBER = 1;
+  private boolean active_;
+  /**
+   * <pre>
+   * active maps to `struct linger.l_onoff`
+   * </pre>
+   *
+   * <code>bool active = 1;</code>
+   */
+  public boolean getActive() {
+    return active_;
+  }
+
+  public static final int DURATION_FIELD_NUMBER = 2;
+  private com.google.protobuf.Duration duration_;
+  /**
+   * <pre>
+   * duration maps to `struct linger.l_linger`
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration duration = 2;</code>
+   */
+  public boolean hasDuration() {
+    return duration_ != null;
+  }
+  /**
+   * <pre>
+   * duration maps to `struct linger.l_linger`
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration duration = 2;</code>
+   */
+  public com.google.protobuf.Duration getDuration() {
+    return duration_ == null ? com.google.protobuf.Duration.getDefaultInstance() : duration_;
+  }
+  /**
+   * <pre>
+   * duration maps to `struct linger.l_linger`
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration duration = 2;</code>
+   */
+  public com.google.protobuf.DurationOrBuilder getDurationOrBuilder() {
+    return getDuration();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (active_ != false) {
+      output.writeBool(1, active_);
+    }
+    if (duration_ != null) {
+      output.writeMessage(2, getDuration());
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (active_ != false) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeBoolSize(1, active_);
+    }
+    if (duration_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(2, getDuration());
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.SocketOptionLinger)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.SocketOptionLinger other = (io.grpc.channelz.v1.SocketOptionLinger) obj;
+
+    boolean result = true;
+    result = result && (getActive()
+        == other.getActive());
+    result = result && (hasDuration() == other.hasDuration());
+    if (hasDuration()) {
+      result = result && getDuration()
+          .equals(other.getDuration());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + ACTIVE_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean(
+        getActive());
+    if (hasDuration()) {
+      hash = (37 * hash) + DURATION_FIELD_NUMBER;
+      hash = (53 * hash) + getDuration().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.SocketOptionLinger parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketOptionLinger parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionLinger parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketOptionLinger parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionLinger parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketOptionLinger parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionLinger parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketOptionLinger parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionLinger parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketOptionLinger parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionLinger parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketOptionLinger parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.SocketOptionLinger prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * For use with SocketOption's additional field.  This is primarily used for
+   * SO_LINGER.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.SocketOptionLinger}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.SocketOptionLinger)
+      io.grpc.channelz.v1.SocketOptionLingerOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionLinger_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionLinger_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.SocketOptionLinger.class, io.grpc.channelz.v1.SocketOptionLinger.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.SocketOptionLinger.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      active_ = false;
+
+      if (durationBuilder_ == null) {
+        duration_ = null;
+      } else {
+        duration_ = null;
+        durationBuilder_ = null;
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionLinger_descriptor;
+    }
+
+    public io.grpc.channelz.v1.SocketOptionLinger getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.SocketOptionLinger.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.SocketOptionLinger build() {
+      io.grpc.channelz.v1.SocketOptionLinger result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.SocketOptionLinger buildPartial() {
+      io.grpc.channelz.v1.SocketOptionLinger result = new io.grpc.channelz.v1.SocketOptionLinger(this);
+      result.active_ = active_;
+      if (durationBuilder_ == null) {
+        result.duration_ = duration_;
+      } else {
+        result.duration_ = durationBuilder_.build();
+      }
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.SocketOptionLinger) {
+        return mergeFrom((io.grpc.channelz.v1.SocketOptionLinger)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.SocketOptionLinger other) {
+      if (other == io.grpc.channelz.v1.SocketOptionLinger.getDefaultInstance()) return this;
+      if (other.getActive() != false) {
+        setActive(other.getActive());
+      }
+      if (other.hasDuration()) {
+        mergeDuration(other.getDuration());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.SocketOptionLinger parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.SocketOptionLinger) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private boolean active_ ;
+    /**
+     * <pre>
+     * active maps to `struct linger.l_onoff`
+     * </pre>
+     *
+     * <code>bool active = 1;</code>
+     */
+    public boolean getActive() {
+      return active_;
+    }
+    /**
+     * <pre>
+     * active maps to `struct linger.l_onoff`
+     * </pre>
+     *
+     * <code>bool active = 1;</code>
+     */
+    public Builder setActive(boolean value) {
+      
+      active_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * active maps to `struct linger.l_onoff`
+     * </pre>
+     *
+     * <code>bool active = 1;</code>
+     */
+    public Builder clearActive() {
+      
+      active_ = false;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.Duration duration_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Duration, com.google.protobuf.Duration.Builder, com.google.protobuf.DurationOrBuilder> durationBuilder_;
+    /**
+     * <pre>
+     * duration maps to `struct linger.l_linger`
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration duration = 2;</code>
+     */
+    public boolean hasDuration() {
+      return durationBuilder_ != null || duration_ != null;
+    }
+    /**
+     * <pre>
+     * duration maps to `struct linger.l_linger`
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration duration = 2;</code>
+     */
+    public com.google.protobuf.Duration getDuration() {
+      if (durationBuilder_ == null) {
+        return duration_ == null ? com.google.protobuf.Duration.getDefaultInstance() : duration_;
+      } else {
+        return durationBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * duration maps to `struct linger.l_linger`
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration duration = 2;</code>
+     */
+    public Builder setDuration(com.google.protobuf.Duration value) {
+      if (durationBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        duration_ = value;
+        onChanged();
+      } else {
+        durationBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * duration maps to `struct linger.l_linger`
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration duration = 2;</code>
+     */
+    public Builder setDuration(
+        com.google.protobuf.Duration.Builder builderForValue) {
+      if (durationBuilder_ == null) {
+        duration_ = builderForValue.build();
+        onChanged();
+      } else {
+        durationBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * duration maps to `struct linger.l_linger`
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration duration = 2;</code>
+     */
+    public Builder mergeDuration(com.google.protobuf.Duration value) {
+      if (durationBuilder_ == null) {
+        if (duration_ != null) {
+          duration_ =
+            com.google.protobuf.Duration.newBuilder(duration_).mergeFrom(value).buildPartial();
+        } else {
+          duration_ = value;
+        }
+        onChanged();
+      } else {
+        durationBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * duration maps to `struct linger.l_linger`
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration duration = 2;</code>
+     */
+    public Builder clearDuration() {
+      if (durationBuilder_ == null) {
+        duration_ = null;
+        onChanged();
+      } else {
+        duration_ = null;
+        durationBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * duration maps to `struct linger.l_linger`
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration duration = 2;</code>
+     */
+    public com.google.protobuf.Duration.Builder getDurationBuilder() {
+      
+      onChanged();
+      return getDurationFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * duration maps to `struct linger.l_linger`
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration duration = 2;</code>
+     */
+    public com.google.protobuf.DurationOrBuilder getDurationOrBuilder() {
+      if (durationBuilder_ != null) {
+        return durationBuilder_.getMessageOrBuilder();
+      } else {
+        return duration_ == null ?
+            com.google.protobuf.Duration.getDefaultInstance() : duration_;
+      }
+    }
+    /**
+     * <pre>
+     * duration maps to `struct linger.l_linger`
+     * </pre>
+     *
+     * <code>.google.protobuf.Duration duration = 2;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Duration, com.google.protobuf.Duration.Builder, com.google.protobuf.DurationOrBuilder> 
+        getDurationFieldBuilder() {
+      if (durationBuilder_ == null) {
+        durationBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Duration, com.google.protobuf.Duration.Builder, com.google.protobuf.DurationOrBuilder>(
+                getDuration(),
+                getParentForChildren(),
+                isClean());
+        duration_ = null;
+      }
+      return durationBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.SocketOptionLinger)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.SocketOptionLinger)
+  private static final io.grpc.channelz.v1.SocketOptionLinger DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.SocketOptionLinger();
+  }
+
+  public static io.grpc.channelz.v1.SocketOptionLinger getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<SocketOptionLinger>
+      PARSER = new com.google.protobuf.AbstractParser<SocketOptionLinger>() {
+    public SocketOptionLinger parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new SocketOptionLinger(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<SocketOptionLinger> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<SocketOptionLinger> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.SocketOptionLinger getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionLingerOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionLingerOrBuilder.java
new file mode 100644
index 0000000..9f57230
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionLingerOrBuilder.java
@@ -0,0 +1,43 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface SocketOptionLingerOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.SocketOptionLinger)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * active maps to `struct linger.l_onoff`
+   * </pre>
+   *
+   * <code>bool active = 1;</code>
+   */
+  boolean getActive();
+
+  /**
+   * <pre>
+   * duration maps to `struct linger.l_linger`
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration duration = 2;</code>
+   */
+  boolean hasDuration();
+  /**
+   * <pre>
+   * duration maps to `struct linger.l_linger`
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration duration = 2;</code>
+   */
+  com.google.protobuf.Duration getDuration();
+  /**
+   * <pre>
+   * duration maps to `struct linger.l_linger`
+   * </pre>
+   *
+   * <code>.google.protobuf.Duration duration = 2;</code>
+   */
+  com.google.protobuf.DurationOrBuilder getDurationOrBuilder();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionOrBuilder.java
new file mode 100644
index 0000000..3fe39f8
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionOrBuilder.java
@@ -0,0 +1,77 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface SocketOptionOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.SocketOption)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The full name of the socket option.  Typically this will be the upper case
+   * name, such as "SO_REUSEPORT".
+   * </pre>
+   *
+   * <code>string name = 1;</code>
+   */
+  java.lang.String getName();
+  /**
+   * <pre>
+   * The full name of the socket option.  Typically this will be the upper case
+   * name, such as "SO_REUSEPORT".
+   * </pre>
+   *
+   * <code>string name = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getNameBytes();
+
+  /**
+   * <pre>
+   * The human readable value of this socket option.  At least one of value or
+   * additional will be set.
+   * </pre>
+   *
+   * <code>string value = 2;</code>
+   */
+  java.lang.String getValue();
+  /**
+   * <pre>
+   * The human readable value of this socket option.  At least one of value or
+   * additional will be set.
+   * </pre>
+   *
+   * <code>string value = 2;</code>
+   */
+  com.google.protobuf.ByteString
+      getValueBytes();
+
+  /**
+   * <pre>
+   * Additional data associated with the socket option.  At least one of value
+   * or additional will be set.
+   * </pre>
+   *
+   * <code>.google.protobuf.Any additional = 3;</code>
+   */
+  boolean hasAdditional();
+  /**
+   * <pre>
+   * Additional data associated with the socket option.  At least one of value
+   * or additional will be set.
+   * </pre>
+   *
+   * <code>.google.protobuf.Any additional = 3;</code>
+   */
+  com.google.protobuf.Any getAdditional();
+  /**
+   * <pre>
+   * Additional data associated with the socket option.  At least one of value
+   * or additional will be set.
+   * </pre>
+   *
+   * <code>.google.protobuf.Any additional = 3;</code>
+   */
+  com.google.protobuf.AnyOrBuilder getAdditionalOrBuilder();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionTcpInfo.java b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionTcpInfo.java
new file mode 100644
index 0000000..ab35465
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionTcpInfo.java
@@ -0,0 +1,2081 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * For use with SocketOption's additional field.  Tcp info for
+ * SOL_TCP and TCP_INFO.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.SocketOptionTcpInfo}
+ */
+public  final class SocketOptionTcpInfo extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.SocketOptionTcpInfo)
+    SocketOptionTcpInfoOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use SocketOptionTcpInfo.newBuilder() to construct.
+  private SocketOptionTcpInfo(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private SocketOptionTcpInfo() {
+    tcpiState_ = 0;
+    tcpiCaState_ = 0;
+    tcpiRetransmits_ = 0;
+    tcpiProbes_ = 0;
+    tcpiBackoff_ = 0;
+    tcpiOptions_ = 0;
+    tcpiSndWscale_ = 0;
+    tcpiRcvWscale_ = 0;
+    tcpiRto_ = 0;
+    tcpiAto_ = 0;
+    tcpiSndMss_ = 0;
+    tcpiRcvMss_ = 0;
+    tcpiUnacked_ = 0;
+    tcpiSacked_ = 0;
+    tcpiLost_ = 0;
+    tcpiRetrans_ = 0;
+    tcpiFackets_ = 0;
+    tcpiLastDataSent_ = 0;
+    tcpiLastAckSent_ = 0;
+    tcpiLastDataRecv_ = 0;
+    tcpiLastAckRecv_ = 0;
+    tcpiPmtu_ = 0;
+    tcpiRcvSsthresh_ = 0;
+    tcpiRtt_ = 0;
+    tcpiRttvar_ = 0;
+    tcpiSndSsthresh_ = 0;
+    tcpiSndCwnd_ = 0;
+    tcpiAdvmss_ = 0;
+    tcpiReordering_ = 0;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private SocketOptionTcpInfo(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+
+            tcpiState_ = input.readUInt32();
+            break;
+          }
+          case 16: {
+
+            tcpiCaState_ = input.readUInt32();
+            break;
+          }
+          case 24: {
+
+            tcpiRetransmits_ = input.readUInt32();
+            break;
+          }
+          case 32: {
+
+            tcpiProbes_ = input.readUInt32();
+            break;
+          }
+          case 40: {
+
+            tcpiBackoff_ = input.readUInt32();
+            break;
+          }
+          case 48: {
+
+            tcpiOptions_ = input.readUInt32();
+            break;
+          }
+          case 56: {
+
+            tcpiSndWscale_ = input.readUInt32();
+            break;
+          }
+          case 64: {
+
+            tcpiRcvWscale_ = input.readUInt32();
+            break;
+          }
+          case 72: {
+
+            tcpiRto_ = input.readUInt32();
+            break;
+          }
+          case 80: {
+
+            tcpiAto_ = input.readUInt32();
+            break;
+          }
+          case 88: {
+
+            tcpiSndMss_ = input.readUInt32();
+            break;
+          }
+          case 96: {
+
+            tcpiRcvMss_ = input.readUInt32();
+            break;
+          }
+          case 104: {
+
+            tcpiUnacked_ = input.readUInt32();
+            break;
+          }
+          case 112: {
+
+            tcpiSacked_ = input.readUInt32();
+            break;
+          }
+          case 120: {
+
+            tcpiLost_ = input.readUInt32();
+            break;
+          }
+          case 128: {
+
+            tcpiRetrans_ = input.readUInt32();
+            break;
+          }
+          case 136: {
+
+            tcpiFackets_ = input.readUInt32();
+            break;
+          }
+          case 144: {
+
+            tcpiLastDataSent_ = input.readUInt32();
+            break;
+          }
+          case 152: {
+
+            tcpiLastAckSent_ = input.readUInt32();
+            break;
+          }
+          case 160: {
+
+            tcpiLastDataRecv_ = input.readUInt32();
+            break;
+          }
+          case 168: {
+
+            tcpiLastAckRecv_ = input.readUInt32();
+            break;
+          }
+          case 176: {
+
+            tcpiPmtu_ = input.readUInt32();
+            break;
+          }
+          case 184: {
+
+            tcpiRcvSsthresh_ = input.readUInt32();
+            break;
+          }
+          case 192: {
+
+            tcpiRtt_ = input.readUInt32();
+            break;
+          }
+          case 200: {
+
+            tcpiRttvar_ = input.readUInt32();
+            break;
+          }
+          case 208: {
+
+            tcpiSndSsthresh_ = input.readUInt32();
+            break;
+          }
+          case 216: {
+
+            tcpiSndCwnd_ = input.readUInt32();
+            break;
+          }
+          case 224: {
+
+            tcpiAdvmss_ = input.readUInt32();
+            break;
+          }
+          case 232: {
+
+            tcpiReordering_ = input.readUInt32();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionTcpInfo_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionTcpInfo_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.SocketOptionTcpInfo.class, io.grpc.channelz.v1.SocketOptionTcpInfo.Builder.class);
+  }
+
+  public static final int TCPI_STATE_FIELD_NUMBER = 1;
+  private int tcpiState_;
+  /**
+   * <code>uint32 tcpi_state = 1;</code>
+   */
+  public int getTcpiState() {
+    return tcpiState_;
+  }
+
+  public static final int TCPI_CA_STATE_FIELD_NUMBER = 2;
+  private int tcpiCaState_;
+  /**
+   * <code>uint32 tcpi_ca_state = 2;</code>
+   */
+  public int getTcpiCaState() {
+    return tcpiCaState_;
+  }
+
+  public static final int TCPI_RETRANSMITS_FIELD_NUMBER = 3;
+  private int tcpiRetransmits_;
+  /**
+   * <code>uint32 tcpi_retransmits = 3;</code>
+   */
+  public int getTcpiRetransmits() {
+    return tcpiRetransmits_;
+  }
+
+  public static final int TCPI_PROBES_FIELD_NUMBER = 4;
+  private int tcpiProbes_;
+  /**
+   * <code>uint32 tcpi_probes = 4;</code>
+   */
+  public int getTcpiProbes() {
+    return tcpiProbes_;
+  }
+
+  public static final int TCPI_BACKOFF_FIELD_NUMBER = 5;
+  private int tcpiBackoff_;
+  /**
+   * <code>uint32 tcpi_backoff = 5;</code>
+   */
+  public int getTcpiBackoff() {
+    return tcpiBackoff_;
+  }
+
+  public static final int TCPI_OPTIONS_FIELD_NUMBER = 6;
+  private int tcpiOptions_;
+  /**
+   * <code>uint32 tcpi_options = 6;</code>
+   */
+  public int getTcpiOptions() {
+    return tcpiOptions_;
+  }
+
+  public static final int TCPI_SND_WSCALE_FIELD_NUMBER = 7;
+  private int tcpiSndWscale_;
+  /**
+   * <code>uint32 tcpi_snd_wscale = 7;</code>
+   */
+  public int getTcpiSndWscale() {
+    return tcpiSndWscale_;
+  }
+
+  public static final int TCPI_RCV_WSCALE_FIELD_NUMBER = 8;
+  private int tcpiRcvWscale_;
+  /**
+   * <code>uint32 tcpi_rcv_wscale = 8;</code>
+   */
+  public int getTcpiRcvWscale() {
+    return tcpiRcvWscale_;
+  }
+
+  public static final int TCPI_RTO_FIELD_NUMBER = 9;
+  private int tcpiRto_;
+  /**
+   * <code>uint32 tcpi_rto = 9;</code>
+   */
+  public int getTcpiRto() {
+    return tcpiRto_;
+  }
+
+  public static final int TCPI_ATO_FIELD_NUMBER = 10;
+  private int tcpiAto_;
+  /**
+   * <code>uint32 tcpi_ato = 10;</code>
+   */
+  public int getTcpiAto() {
+    return tcpiAto_;
+  }
+
+  public static final int TCPI_SND_MSS_FIELD_NUMBER = 11;
+  private int tcpiSndMss_;
+  /**
+   * <code>uint32 tcpi_snd_mss = 11;</code>
+   */
+  public int getTcpiSndMss() {
+    return tcpiSndMss_;
+  }
+
+  public static final int TCPI_RCV_MSS_FIELD_NUMBER = 12;
+  private int tcpiRcvMss_;
+  /**
+   * <code>uint32 tcpi_rcv_mss = 12;</code>
+   */
+  public int getTcpiRcvMss() {
+    return tcpiRcvMss_;
+  }
+
+  public static final int TCPI_UNACKED_FIELD_NUMBER = 13;
+  private int tcpiUnacked_;
+  /**
+   * <code>uint32 tcpi_unacked = 13;</code>
+   */
+  public int getTcpiUnacked() {
+    return tcpiUnacked_;
+  }
+
+  public static final int TCPI_SACKED_FIELD_NUMBER = 14;
+  private int tcpiSacked_;
+  /**
+   * <code>uint32 tcpi_sacked = 14;</code>
+   */
+  public int getTcpiSacked() {
+    return tcpiSacked_;
+  }
+
+  public static final int TCPI_LOST_FIELD_NUMBER = 15;
+  private int tcpiLost_;
+  /**
+   * <code>uint32 tcpi_lost = 15;</code>
+   */
+  public int getTcpiLost() {
+    return tcpiLost_;
+  }
+
+  public static final int TCPI_RETRANS_FIELD_NUMBER = 16;
+  private int tcpiRetrans_;
+  /**
+   * <code>uint32 tcpi_retrans = 16;</code>
+   */
+  public int getTcpiRetrans() {
+    return tcpiRetrans_;
+  }
+
+  public static final int TCPI_FACKETS_FIELD_NUMBER = 17;
+  private int tcpiFackets_;
+  /**
+   * <code>uint32 tcpi_fackets = 17;</code>
+   */
+  public int getTcpiFackets() {
+    return tcpiFackets_;
+  }
+
+  public static final int TCPI_LAST_DATA_SENT_FIELD_NUMBER = 18;
+  private int tcpiLastDataSent_;
+  /**
+   * <code>uint32 tcpi_last_data_sent = 18;</code>
+   */
+  public int getTcpiLastDataSent() {
+    return tcpiLastDataSent_;
+  }
+
+  public static final int TCPI_LAST_ACK_SENT_FIELD_NUMBER = 19;
+  private int tcpiLastAckSent_;
+  /**
+   * <code>uint32 tcpi_last_ack_sent = 19;</code>
+   */
+  public int getTcpiLastAckSent() {
+    return tcpiLastAckSent_;
+  }
+
+  public static final int TCPI_LAST_DATA_RECV_FIELD_NUMBER = 20;
+  private int tcpiLastDataRecv_;
+  /**
+   * <code>uint32 tcpi_last_data_recv = 20;</code>
+   */
+  public int getTcpiLastDataRecv() {
+    return tcpiLastDataRecv_;
+  }
+
+  public static final int TCPI_LAST_ACK_RECV_FIELD_NUMBER = 21;
+  private int tcpiLastAckRecv_;
+  /**
+   * <code>uint32 tcpi_last_ack_recv = 21;</code>
+   */
+  public int getTcpiLastAckRecv() {
+    return tcpiLastAckRecv_;
+  }
+
+  public static final int TCPI_PMTU_FIELD_NUMBER = 22;
+  private int tcpiPmtu_;
+  /**
+   * <code>uint32 tcpi_pmtu = 22;</code>
+   */
+  public int getTcpiPmtu() {
+    return tcpiPmtu_;
+  }
+
+  public static final int TCPI_RCV_SSTHRESH_FIELD_NUMBER = 23;
+  private int tcpiRcvSsthresh_;
+  /**
+   * <code>uint32 tcpi_rcv_ssthresh = 23;</code>
+   */
+  public int getTcpiRcvSsthresh() {
+    return tcpiRcvSsthresh_;
+  }
+
+  public static final int TCPI_RTT_FIELD_NUMBER = 24;
+  private int tcpiRtt_;
+  /**
+   * <code>uint32 tcpi_rtt = 24;</code>
+   */
+  public int getTcpiRtt() {
+    return tcpiRtt_;
+  }
+
+  public static final int TCPI_RTTVAR_FIELD_NUMBER = 25;
+  private int tcpiRttvar_;
+  /**
+   * <code>uint32 tcpi_rttvar = 25;</code>
+   */
+  public int getTcpiRttvar() {
+    return tcpiRttvar_;
+  }
+
+  public static final int TCPI_SND_SSTHRESH_FIELD_NUMBER = 26;
+  private int tcpiSndSsthresh_;
+  /**
+   * <code>uint32 tcpi_snd_ssthresh = 26;</code>
+   */
+  public int getTcpiSndSsthresh() {
+    return tcpiSndSsthresh_;
+  }
+
+  public static final int TCPI_SND_CWND_FIELD_NUMBER = 27;
+  private int tcpiSndCwnd_;
+  /**
+   * <code>uint32 tcpi_snd_cwnd = 27;</code>
+   */
+  public int getTcpiSndCwnd() {
+    return tcpiSndCwnd_;
+  }
+
+  public static final int TCPI_ADVMSS_FIELD_NUMBER = 28;
+  private int tcpiAdvmss_;
+  /**
+   * <code>uint32 tcpi_advmss = 28;</code>
+   */
+  public int getTcpiAdvmss() {
+    return tcpiAdvmss_;
+  }
+
+  public static final int TCPI_REORDERING_FIELD_NUMBER = 29;
+  private int tcpiReordering_;
+  /**
+   * <code>uint32 tcpi_reordering = 29;</code>
+   */
+  public int getTcpiReordering() {
+    return tcpiReordering_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (tcpiState_ != 0) {
+      output.writeUInt32(1, tcpiState_);
+    }
+    if (tcpiCaState_ != 0) {
+      output.writeUInt32(2, tcpiCaState_);
+    }
+    if (tcpiRetransmits_ != 0) {
+      output.writeUInt32(3, tcpiRetransmits_);
+    }
+    if (tcpiProbes_ != 0) {
+      output.writeUInt32(4, tcpiProbes_);
+    }
+    if (tcpiBackoff_ != 0) {
+      output.writeUInt32(5, tcpiBackoff_);
+    }
+    if (tcpiOptions_ != 0) {
+      output.writeUInt32(6, tcpiOptions_);
+    }
+    if (tcpiSndWscale_ != 0) {
+      output.writeUInt32(7, tcpiSndWscale_);
+    }
+    if (tcpiRcvWscale_ != 0) {
+      output.writeUInt32(8, tcpiRcvWscale_);
+    }
+    if (tcpiRto_ != 0) {
+      output.writeUInt32(9, tcpiRto_);
+    }
+    if (tcpiAto_ != 0) {
+      output.writeUInt32(10, tcpiAto_);
+    }
+    if (tcpiSndMss_ != 0) {
+      output.writeUInt32(11, tcpiSndMss_);
+    }
+    if (tcpiRcvMss_ != 0) {
+      output.writeUInt32(12, tcpiRcvMss_);
+    }
+    if (tcpiUnacked_ != 0) {
+      output.writeUInt32(13, tcpiUnacked_);
+    }
+    if (tcpiSacked_ != 0) {
+      output.writeUInt32(14, tcpiSacked_);
+    }
+    if (tcpiLost_ != 0) {
+      output.writeUInt32(15, tcpiLost_);
+    }
+    if (tcpiRetrans_ != 0) {
+      output.writeUInt32(16, tcpiRetrans_);
+    }
+    if (tcpiFackets_ != 0) {
+      output.writeUInt32(17, tcpiFackets_);
+    }
+    if (tcpiLastDataSent_ != 0) {
+      output.writeUInt32(18, tcpiLastDataSent_);
+    }
+    if (tcpiLastAckSent_ != 0) {
+      output.writeUInt32(19, tcpiLastAckSent_);
+    }
+    if (tcpiLastDataRecv_ != 0) {
+      output.writeUInt32(20, tcpiLastDataRecv_);
+    }
+    if (tcpiLastAckRecv_ != 0) {
+      output.writeUInt32(21, tcpiLastAckRecv_);
+    }
+    if (tcpiPmtu_ != 0) {
+      output.writeUInt32(22, tcpiPmtu_);
+    }
+    if (tcpiRcvSsthresh_ != 0) {
+      output.writeUInt32(23, tcpiRcvSsthresh_);
+    }
+    if (tcpiRtt_ != 0) {
+      output.writeUInt32(24, tcpiRtt_);
+    }
+    if (tcpiRttvar_ != 0) {
+      output.writeUInt32(25, tcpiRttvar_);
+    }
+    if (tcpiSndSsthresh_ != 0) {
+      output.writeUInt32(26, tcpiSndSsthresh_);
+    }
+    if (tcpiSndCwnd_ != 0) {
+      output.writeUInt32(27, tcpiSndCwnd_);
+    }
+    if (tcpiAdvmss_ != 0) {
+      output.writeUInt32(28, tcpiAdvmss_);
+    }
+    if (tcpiReordering_ != 0) {
+      output.writeUInt32(29, tcpiReordering_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (tcpiState_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(1, tcpiState_);
+    }
+    if (tcpiCaState_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(2, tcpiCaState_);
+    }
+    if (tcpiRetransmits_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(3, tcpiRetransmits_);
+    }
+    if (tcpiProbes_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(4, tcpiProbes_);
+    }
+    if (tcpiBackoff_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(5, tcpiBackoff_);
+    }
+    if (tcpiOptions_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(6, tcpiOptions_);
+    }
+    if (tcpiSndWscale_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(7, tcpiSndWscale_);
+    }
+    if (tcpiRcvWscale_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(8, tcpiRcvWscale_);
+    }
+    if (tcpiRto_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(9, tcpiRto_);
+    }
+    if (tcpiAto_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(10, tcpiAto_);
+    }
+    if (tcpiSndMss_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(11, tcpiSndMss_);
+    }
+    if (tcpiRcvMss_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(12, tcpiRcvMss_);
+    }
+    if (tcpiUnacked_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(13, tcpiUnacked_);
+    }
+    if (tcpiSacked_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(14, tcpiSacked_);
+    }
+    if (tcpiLost_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(15, tcpiLost_);
+    }
+    if (tcpiRetrans_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(16, tcpiRetrans_);
+    }
+    if (tcpiFackets_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(17, tcpiFackets_);
+    }
+    if (tcpiLastDataSent_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(18, tcpiLastDataSent_);
+    }
+    if (tcpiLastAckSent_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(19, tcpiLastAckSent_);
+    }
+    if (tcpiLastDataRecv_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(20, tcpiLastDataRecv_);
+    }
+    if (tcpiLastAckRecv_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(21, tcpiLastAckRecv_);
+    }
+    if (tcpiPmtu_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(22, tcpiPmtu_);
+    }
+    if (tcpiRcvSsthresh_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(23, tcpiRcvSsthresh_);
+    }
+    if (tcpiRtt_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(24, tcpiRtt_);
+    }
+    if (tcpiRttvar_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(25, tcpiRttvar_);
+    }
+    if (tcpiSndSsthresh_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(26, tcpiSndSsthresh_);
+    }
+    if (tcpiSndCwnd_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(27, tcpiSndCwnd_);
+    }
+    if (tcpiAdvmss_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(28, tcpiAdvmss_);
+    }
+    if (tcpiReordering_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeUInt32Size(29, tcpiReordering_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.SocketOptionTcpInfo)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.SocketOptionTcpInfo other = (io.grpc.channelz.v1.SocketOptionTcpInfo) obj;
+
+    boolean result = true;
+    result = result && (getTcpiState()
+        == other.getTcpiState());
+    result = result && (getTcpiCaState()
+        == other.getTcpiCaState());
+    result = result && (getTcpiRetransmits()
+        == other.getTcpiRetransmits());
+    result = result && (getTcpiProbes()
+        == other.getTcpiProbes());
+    result = result && (getTcpiBackoff()
+        == other.getTcpiBackoff());
+    result = result && (getTcpiOptions()
+        == other.getTcpiOptions());
+    result = result && (getTcpiSndWscale()
+        == other.getTcpiSndWscale());
+    result = result && (getTcpiRcvWscale()
+        == other.getTcpiRcvWscale());
+    result = result && (getTcpiRto()
+        == other.getTcpiRto());
+    result = result && (getTcpiAto()
+        == other.getTcpiAto());
+    result = result && (getTcpiSndMss()
+        == other.getTcpiSndMss());
+    result = result && (getTcpiRcvMss()
+        == other.getTcpiRcvMss());
+    result = result && (getTcpiUnacked()
+        == other.getTcpiUnacked());
+    result = result && (getTcpiSacked()
+        == other.getTcpiSacked());
+    result = result && (getTcpiLost()
+        == other.getTcpiLost());
+    result = result && (getTcpiRetrans()
+        == other.getTcpiRetrans());
+    result = result && (getTcpiFackets()
+        == other.getTcpiFackets());
+    result = result && (getTcpiLastDataSent()
+        == other.getTcpiLastDataSent());
+    result = result && (getTcpiLastAckSent()
+        == other.getTcpiLastAckSent());
+    result = result && (getTcpiLastDataRecv()
+        == other.getTcpiLastDataRecv());
+    result = result && (getTcpiLastAckRecv()
+        == other.getTcpiLastAckRecv());
+    result = result && (getTcpiPmtu()
+        == other.getTcpiPmtu());
+    result = result && (getTcpiRcvSsthresh()
+        == other.getTcpiRcvSsthresh());
+    result = result && (getTcpiRtt()
+        == other.getTcpiRtt());
+    result = result && (getTcpiRttvar()
+        == other.getTcpiRttvar());
+    result = result && (getTcpiSndSsthresh()
+        == other.getTcpiSndSsthresh());
+    result = result && (getTcpiSndCwnd()
+        == other.getTcpiSndCwnd());
+    result = result && (getTcpiAdvmss()
+        == other.getTcpiAdvmss());
+    result = result && (getTcpiReordering()
+        == other.getTcpiReordering());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + TCPI_STATE_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiState();
+    hash = (37 * hash) + TCPI_CA_STATE_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiCaState();
+    hash = (37 * hash) + TCPI_RETRANSMITS_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiRetransmits();
+    hash = (37 * hash) + TCPI_PROBES_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiProbes();
+    hash = (37 * hash) + TCPI_BACKOFF_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiBackoff();
+    hash = (37 * hash) + TCPI_OPTIONS_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiOptions();
+    hash = (37 * hash) + TCPI_SND_WSCALE_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiSndWscale();
+    hash = (37 * hash) + TCPI_RCV_WSCALE_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiRcvWscale();
+    hash = (37 * hash) + TCPI_RTO_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiRto();
+    hash = (37 * hash) + TCPI_ATO_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiAto();
+    hash = (37 * hash) + TCPI_SND_MSS_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiSndMss();
+    hash = (37 * hash) + TCPI_RCV_MSS_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiRcvMss();
+    hash = (37 * hash) + TCPI_UNACKED_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiUnacked();
+    hash = (37 * hash) + TCPI_SACKED_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiSacked();
+    hash = (37 * hash) + TCPI_LOST_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiLost();
+    hash = (37 * hash) + TCPI_RETRANS_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiRetrans();
+    hash = (37 * hash) + TCPI_FACKETS_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiFackets();
+    hash = (37 * hash) + TCPI_LAST_DATA_SENT_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiLastDataSent();
+    hash = (37 * hash) + TCPI_LAST_ACK_SENT_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiLastAckSent();
+    hash = (37 * hash) + TCPI_LAST_DATA_RECV_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiLastDataRecv();
+    hash = (37 * hash) + TCPI_LAST_ACK_RECV_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiLastAckRecv();
+    hash = (37 * hash) + TCPI_PMTU_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiPmtu();
+    hash = (37 * hash) + TCPI_RCV_SSTHRESH_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiRcvSsthresh();
+    hash = (37 * hash) + TCPI_RTT_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiRtt();
+    hash = (37 * hash) + TCPI_RTTVAR_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiRttvar();
+    hash = (37 * hash) + TCPI_SND_SSTHRESH_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiSndSsthresh();
+    hash = (37 * hash) + TCPI_SND_CWND_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiSndCwnd();
+    hash = (37 * hash) + TCPI_ADVMSS_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiAdvmss();
+    hash = (37 * hash) + TCPI_REORDERING_FIELD_NUMBER;
+    hash = (53 * hash) + getTcpiReordering();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.SocketOptionTcpInfo parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTcpInfo parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTcpInfo parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTcpInfo parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTcpInfo parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTcpInfo parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTcpInfo parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTcpInfo parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTcpInfo parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTcpInfo parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTcpInfo parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTcpInfo parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.SocketOptionTcpInfo prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * For use with SocketOption's additional field.  Tcp info for
+   * SOL_TCP and TCP_INFO.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.SocketOptionTcpInfo}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.SocketOptionTcpInfo)
+      io.grpc.channelz.v1.SocketOptionTcpInfoOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionTcpInfo_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionTcpInfo_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.SocketOptionTcpInfo.class, io.grpc.channelz.v1.SocketOptionTcpInfo.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.SocketOptionTcpInfo.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      tcpiState_ = 0;
+
+      tcpiCaState_ = 0;
+
+      tcpiRetransmits_ = 0;
+
+      tcpiProbes_ = 0;
+
+      tcpiBackoff_ = 0;
+
+      tcpiOptions_ = 0;
+
+      tcpiSndWscale_ = 0;
+
+      tcpiRcvWscale_ = 0;
+
+      tcpiRto_ = 0;
+
+      tcpiAto_ = 0;
+
+      tcpiSndMss_ = 0;
+
+      tcpiRcvMss_ = 0;
+
+      tcpiUnacked_ = 0;
+
+      tcpiSacked_ = 0;
+
+      tcpiLost_ = 0;
+
+      tcpiRetrans_ = 0;
+
+      tcpiFackets_ = 0;
+
+      tcpiLastDataSent_ = 0;
+
+      tcpiLastAckSent_ = 0;
+
+      tcpiLastDataRecv_ = 0;
+
+      tcpiLastAckRecv_ = 0;
+
+      tcpiPmtu_ = 0;
+
+      tcpiRcvSsthresh_ = 0;
+
+      tcpiRtt_ = 0;
+
+      tcpiRttvar_ = 0;
+
+      tcpiSndSsthresh_ = 0;
+
+      tcpiSndCwnd_ = 0;
+
+      tcpiAdvmss_ = 0;
+
+      tcpiReordering_ = 0;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionTcpInfo_descriptor;
+    }
+
+    public io.grpc.channelz.v1.SocketOptionTcpInfo getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.SocketOptionTcpInfo.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.SocketOptionTcpInfo build() {
+      io.grpc.channelz.v1.SocketOptionTcpInfo result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.SocketOptionTcpInfo buildPartial() {
+      io.grpc.channelz.v1.SocketOptionTcpInfo result = new io.grpc.channelz.v1.SocketOptionTcpInfo(this);
+      result.tcpiState_ = tcpiState_;
+      result.tcpiCaState_ = tcpiCaState_;
+      result.tcpiRetransmits_ = tcpiRetransmits_;
+      result.tcpiProbes_ = tcpiProbes_;
+      result.tcpiBackoff_ = tcpiBackoff_;
+      result.tcpiOptions_ = tcpiOptions_;
+      result.tcpiSndWscale_ = tcpiSndWscale_;
+      result.tcpiRcvWscale_ = tcpiRcvWscale_;
+      result.tcpiRto_ = tcpiRto_;
+      result.tcpiAto_ = tcpiAto_;
+      result.tcpiSndMss_ = tcpiSndMss_;
+      result.tcpiRcvMss_ = tcpiRcvMss_;
+      result.tcpiUnacked_ = tcpiUnacked_;
+      result.tcpiSacked_ = tcpiSacked_;
+      result.tcpiLost_ = tcpiLost_;
+      result.tcpiRetrans_ = tcpiRetrans_;
+      result.tcpiFackets_ = tcpiFackets_;
+      result.tcpiLastDataSent_ = tcpiLastDataSent_;
+      result.tcpiLastAckSent_ = tcpiLastAckSent_;
+      result.tcpiLastDataRecv_ = tcpiLastDataRecv_;
+      result.tcpiLastAckRecv_ = tcpiLastAckRecv_;
+      result.tcpiPmtu_ = tcpiPmtu_;
+      result.tcpiRcvSsthresh_ = tcpiRcvSsthresh_;
+      result.tcpiRtt_ = tcpiRtt_;
+      result.tcpiRttvar_ = tcpiRttvar_;
+      result.tcpiSndSsthresh_ = tcpiSndSsthresh_;
+      result.tcpiSndCwnd_ = tcpiSndCwnd_;
+      result.tcpiAdvmss_ = tcpiAdvmss_;
+      result.tcpiReordering_ = tcpiReordering_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.SocketOptionTcpInfo) {
+        return mergeFrom((io.grpc.channelz.v1.SocketOptionTcpInfo)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.SocketOptionTcpInfo other) {
+      if (other == io.grpc.channelz.v1.SocketOptionTcpInfo.getDefaultInstance()) return this;
+      if (other.getTcpiState() != 0) {
+        setTcpiState(other.getTcpiState());
+      }
+      if (other.getTcpiCaState() != 0) {
+        setTcpiCaState(other.getTcpiCaState());
+      }
+      if (other.getTcpiRetransmits() != 0) {
+        setTcpiRetransmits(other.getTcpiRetransmits());
+      }
+      if (other.getTcpiProbes() != 0) {
+        setTcpiProbes(other.getTcpiProbes());
+      }
+      if (other.getTcpiBackoff() != 0) {
+        setTcpiBackoff(other.getTcpiBackoff());
+      }
+      if (other.getTcpiOptions() != 0) {
+        setTcpiOptions(other.getTcpiOptions());
+      }
+      if (other.getTcpiSndWscale() != 0) {
+        setTcpiSndWscale(other.getTcpiSndWscale());
+      }
+      if (other.getTcpiRcvWscale() != 0) {
+        setTcpiRcvWscale(other.getTcpiRcvWscale());
+      }
+      if (other.getTcpiRto() != 0) {
+        setTcpiRto(other.getTcpiRto());
+      }
+      if (other.getTcpiAto() != 0) {
+        setTcpiAto(other.getTcpiAto());
+      }
+      if (other.getTcpiSndMss() != 0) {
+        setTcpiSndMss(other.getTcpiSndMss());
+      }
+      if (other.getTcpiRcvMss() != 0) {
+        setTcpiRcvMss(other.getTcpiRcvMss());
+      }
+      if (other.getTcpiUnacked() != 0) {
+        setTcpiUnacked(other.getTcpiUnacked());
+      }
+      if (other.getTcpiSacked() != 0) {
+        setTcpiSacked(other.getTcpiSacked());
+      }
+      if (other.getTcpiLost() != 0) {
+        setTcpiLost(other.getTcpiLost());
+      }
+      if (other.getTcpiRetrans() != 0) {
+        setTcpiRetrans(other.getTcpiRetrans());
+      }
+      if (other.getTcpiFackets() != 0) {
+        setTcpiFackets(other.getTcpiFackets());
+      }
+      if (other.getTcpiLastDataSent() != 0) {
+        setTcpiLastDataSent(other.getTcpiLastDataSent());
+      }
+      if (other.getTcpiLastAckSent() != 0) {
+        setTcpiLastAckSent(other.getTcpiLastAckSent());
+      }
+      if (other.getTcpiLastDataRecv() != 0) {
+        setTcpiLastDataRecv(other.getTcpiLastDataRecv());
+      }
+      if (other.getTcpiLastAckRecv() != 0) {
+        setTcpiLastAckRecv(other.getTcpiLastAckRecv());
+      }
+      if (other.getTcpiPmtu() != 0) {
+        setTcpiPmtu(other.getTcpiPmtu());
+      }
+      if (other.getTcpiRcvSsthresh() != 0) {
+        setTcpiRcvSsthresh(other.getTcpiRcvSsthresh());
+      }
+      if (other.getTcpiRtt() != 0) {
+        setTcpiRtt(other.getTcpiRtt());
+      }
+      if (other.getTcpiRttvar() != 0) {
+        setTcpiRttvar(other.getTcpiRttvar());
+      }
+      if (other.getTcpiSndSsthresh() != 0) {
+        setTcpiSndSsthresh(other.getTcpiSndSsthresh());
+      }
+      if (other.getTcpiSndCwnd() != 0) {
+        setTcpiSndCwnd(other.getTcpiSndCwnd());
+      }
+      if (other.getTcpiAdvmss() != 0) {
+        setTcpiAdvmss(other.getTcpiAdvmss());
+      }
+      if (other.getTcpiReordering() != 0) {
+        setTcpiReordering(other.getTcpiReordering());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.SocketOptionTcpInfo parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.SocketOptionTcpInfo) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private int tcpiState_ ;
+    /**
+     * <code>uint32 tcpi_state = 1;</code>
+     */
+    public int getTcpiState() {
+      return tcpiState_;
+    }
+    /**
+     * <code>uint32 tcpi_state = 1;</code>
+     */
+    public Builder setTcpiState(int value) {
+      
+      tcpiState_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_state = 1;</code>
+     */
+    public Builder clearTcpiState() {
+      
+      tcpiState_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiCaState_ ;
+    /**
+     * <code>uint32 tcpi_ca_state = 2;</code>
+     */
+    public int getTcpiCaState() {
+      return tcpiCaState_;
+    }
+    /**
+     * <code>uint32 tcpi_ca_state = 2;</code>
+     */
+    public Builder setTcpiCaState(int value) {
+      
+      tcpiCaState_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_ca_state = 2;</code>
+     */
+    public Builder clearTcpiCaState() {
+      
+      tcpiCaState_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiRetransmits_ ;
+    /**
+     * <code>uint32 tcpi_retransmits = 3;</code>
+     */
+    public int getTcpiRetransmits() {
+      return tcpiRetransmits_;
+    }
+    /**
+     * <code>uint32 tcpi_retransmits = 3;</code>
+     */
+    public Builder setTcpiRetransmits(int value) {
+      
+      tcpiRetransmits_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_retransmits = 3;</code>
+     */
+    public Builder clearTcpiRetransmits() {
+      
+      tcpiRetransmits_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiProbes_ ;
+    /**
+     * <code>uint32 tcpi_probes = 4;</code>
+     */
+    public int getTcpiProbes() {
+      return tcpiProbes_;
+    }
+    /**
+     * <code>uint32 tcpi_probes = 4;</code>
+     */
+    public Builder setTcpiProbes(int value) {
+      
+      tcpiProbes_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_probes = 4;</code>
+     */
+    public Builder clearTcpiProbes() {
+      
+      tcpiProbes_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiBackoff_ ;
+    /**
+     * <code>uint32 tcpi_backoff = 5;</code>
+     */
+    public int getTcpiBackoff() {
+      return tcpiBackoff_;
+    }
+    /**
+     * <code>uint32 tcpi_backoff = 5;</code>
+     */
+    public Builder setTcpiBackoff(int value) {
+      
+      tcpiBackoff_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_backoff = 5;</code>
+     */
+    public Builder clearTcpiBackoff() {
+      
+      tcpiBackoff_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiOptions_ ;
+    /**
+     * <code>uint32 tcpi_options = 6;</code>
+     */
+    public int getTcpiOptions() {
+      return tcpiOptions_;
+    }
+    /**
+     * <code>uint32 tcpi_options = 6;</code>
+     */
+    public Builder setTcpiOptions(int value) {
+      
+      tcpiOptions_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_options = 6;</code>
+     */
+    public Builder clearTcpiOptions() {
+      
+      tcpiOptions_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiSndWscale_ ;
+    /**
+     * <code>uint32 tcpi_snd_wscale = 7;</code>
+     */
+    public int getTcpiSndWscale() {
+      return tcpiSndWscale_;
+    }
+    /**
+     * <code>uint32 tcpi_snd_wscale = 7;</code>
+     */
+    public Builder setTcpiSndWscale(int value) {
+      
+      tcpiSndWscale_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_snd_wscale = 7;</code>
+     */
+    public Builder clearTcpiSndWscale() {
+      
+      tcpiSndWscale_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiRcvWscale_ ;
+    /**
+     * <code>uint32 tcpi_rcv_wscale = 8;</code>
+     */
+    public int getTcpiRcvWscale() {
+      return tcpiRcvWscale_;
+    }
+    /**
+     * <code>uint32 tcpi_rcv_wscale = 8;</code>
+     */
+    public Builder setTcpiRcvWscale(int value) {
+      
+      tcpiRcvWscale_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_rcv_wscale = 8;</code>
+     */
+    public Builder clearTcpiRcvWscale() {
+      
+      tcpiRcvWscale_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiRto_ ;
+    /**
+     * <code>uint32 tcpi_rto = 9;</code>
+     */
+    public int getTcpiRto() {
+      return tcpiRto_;
+    }
+    /**
+     * <code>uint32 tcpi_rto = 9;</code>
+     */
+    public Builder setTcpiRto(int value) {
+      
+      tcpiRto_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_rto = 9;</code>
+     */
+    public Builder clearTcpiRto() {
+      
+      tcpiRto_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiAto_ ;
+    /**
+     * <code>uint32 tcpi_ato = 10;</code>
+     */
+    public int getTcpiAto() {
+      return tcpiAto_;
+    }
+    /**
+     * <code>uint32 tcpi_ato = 10;</code>
+     */
+    public Builder setTcpiAto(int value) {
+      
+      tcpiAto_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_ato = 10;</code>
+     */
+    public Builder clearTcpiAto() {
+      
+      tcpiAto_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiSndMss_ ;
+    /**
+     * <code>uint32 tcpi_snd_mss = 11;</code>
+     */
+    public int getTcpiSndMss() {
+      return tcpiSndMss_;
+    }
+    /**
+     * <code>uint32 tcpi_snd_mss = 11;</code>
+     */
+    public Builder setTcpiSndMss(int value) {
+      
+      tcpiSndMss_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_snd_mss = 11;</code>
+     */
+    public Builder clearTcpiSndMss() {
+      
+      tcpiSndMss_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiRcvMss_ ;
+    /**
+     * <code>uint32 tcpi_rcv_mss = 12;</code>
+     */
+    public int getTcpiRcvMss() {
+      return tcpiRcvMss_;
+    }
+    /**
+     * <code>uint32 tcpi_rcv_mss = 12;</code>
+     */
+    public Builder setTcpiRcvMss(int value) {
+      
+      tcpiRcvMss_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_rcv_mss = 12;</code>
+     */
+    public Builder clearTcpiRcvMss() {
+      
+      tcpiRcvMss_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiUnacked_ ;
+    /**
+     * <code>uint32 tcpi_unacked = 13;</code>
+     */
+    public int getTcpiUnacked() {
+      return tcpiUnacked_;
+    }
+    /**
+     * <code>uint32 tcpi_unacked = 13;</code>
+     */
+    public Builder setTcpiUnacked(int value) {
+      
+      tcpiUnacked_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_unacked = 13;</code>
+     */
+    public Builder clearTcpiUnacked() {
+      
+      tcpiUnacked_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiSacked_ ;
+    /**
+     * <code>uint32 tcpi_sacked = 14;</code>
+     */
+    public int getTcpiSacked() {
+      return tcpiSacked_;
+    }
+    /**
+     * <code>uint32 tcpi_sacked = 14;</code>
+     */
+    public Builder setTcpiSacked(int value) {
+      
+      tcpiSacked_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_sacked = 14;</code>
+     */
+    public Builder clearTcpiSacked() {
+      
+      tcpiSacked_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiLost_ ;
+    /**
+     * <code>uint32 tcpi_lost = 15;</code>
+     */
+    public int getTcpiLost() {
+      return tcpiLost_;
+    }
+    /**
+     * <code>uint32 tcpi_lost = 15;</code>
+     */
+    public Builder setTcpiLost(int value) {
+      
+      tcpiLost_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_lost = 15;</code>
+     */
+    public Builder clearTcpiLost() {
+      
+      tcpiLost_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiRetrans_ ;
+    /**
+     * <code>uint32 tcpi_retrans = 16;</code>
+     */
+    public int getTcpiRetrans() {
+      return tcpiRetrans_;
+    }
+    /**
+     * <code>uint32 tcpi_retrans = 16;</code>
+     */
+    public Builder setTcpiRetrans(int value) {
+      
+      tcpiRetrans_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_retrans = 16;</code>
+     */
+    public Builder clearTcpiRetrans() {
+      
+      tcpiRetrans_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiFackets_ ;
+    /**
+     * <code>uint32 tcpi_fackets = 17;</code>
+     */
+    public int getTcpiFackets() {
+      return tcpiFackets_;
+    }
+    /**
+     * <code>uint32 tcpi_fackets = 17;</code>
+     */
+    public Builder setTcpiFackets(int value) {
+      
+      tcpiFackets_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_fackets = 17;</code>
+     */
+    public Builder clearTcpiFackets() {
+      
+      tcpiFackets_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiLastDataSent_ ;
+    /**
+     * <code>uint32 tcpi_last_data_sent = 18;</code>
+     */
+    public int getTcpiLastDataSent() {
+      return tcpiLastDataSent_;
+    }
+    /**
+     * <code>uint32 tcpi_last_data_sent = 18;</code>
+     */
+    public Builder setTcpiLastDataSent(int value) {
+      
+      tcpiLastDataSent_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_last_data_sent = 18;</code>
+     */
+    public Builder clearTcpiLastDataSent() {
+      
+      tcpiLastDataSent_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiLastAckSent_ ;
+    /**
+     * <code>uint32 tcpi_last_ack_sent = 19;</code>
+     */
+    public int getTcpiLastAckSent() {
+      return tcpiLastAckSent_;
+    }
+    /**
+     * <code>uint32 tcpi_last_ack_sent = 19;</code>
+     */
+    public Builder setTcpiLastAckSent(int value) {
+      
+      tcpiLastAckSent_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_last_ack_sent = 19;</code>
+     */
+    public Builder clearTcpiLastAckSent() {
+      
+      tcpiLastAckSent_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiLastDataRecv_ ;
+    /**
+     * <code>uint32 tcpi_last_data_recv = 20;</code>
+     */
+    public int getTcpiLastDataRecv() {
+      return tcpiLastDataRecv_;
+    }
+    /**
+     * <code>uint32 tcpi_last_data_recv = 20;</code>
+     */
+    public Builder setTcpiLastDataRecv(int value) {
+      
+      tcpiLastDataRecv_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_last_data_recv = 20;</code>
+     */
+    public Builder clearTcpiLastDataRecv() {
+      
+      tcpiLastDataRecv_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiLastAckRecv_ ;
+    /**
+     * <code>uint32 tcpi_last_ack_recv = 21;</code>
+     */
+    public int getTcpiLastAckRecv() {
+      return tcpiLastAckRecv_;
+    }
+    /**
+     * <code>uint32 tcpi_last_ack_recv = 21;</code>
+     */
+    public Builder setTcpiLastAckRecv(int value) {
+      
+      tcpiLastAckRecv_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_last_ack_recv = 21;</code>
+     */
+    public Builder clearTcpiLastAckRecv() {
+      
+      tcpiLastAckRecv_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiPmtu_ ;
+    /**
+     * <code>uint32 tcpi_pmtu = 22;</code>
+     */
+    public int getTcpiPmtu() {
+      return tcpiPmtu_;
+    }
+    /**
+     * <code>uint32 tcpi_pmtu = 22;</code>
+     */
+    public Builder setTcpiPmtu(int value) {
+      
+      tcpiPmtu_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_pmtu = 22;</code>
+     */
+    public Builder clearTcpiPmtu() {
+      
+      tcpiPmtu_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiRcvSsthresh_ ;
+    /**
+     * <code>uint32 tcpi_rcv_ssthresh = 23;</code>
+     */
+    public int getTcpiRcvSsthresh() {
+      return tcpiRcvSsthresh_;
+    }
+    /**
+     * <code>uint32 tcpi_rcv_ssthresh = 23;</code>
+     */
+    public Builder setTcpiRcvSsthresh(int value) {
+      
+      tcpiRcvSsthresh_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_rcv_ssthresh = 23;</code>
+     */
+    public Builder clearTcpiRcvSsthresh() {
+      
+      tcpiRcvSsthresh_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiRtt_ ;
+    /**
+     * <code>uint32 tcpi_rtt = 24;</code>
+     */
+    public int getTcpiRtt() {
+      return tcpiRtt_;
+    }
+    /**
+     * <code>uint32 tcpi_rtt = 24;</code>
+     */
+    public Builder setTcpiRtt(int value) {
+      
+      tcpiRtt_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_rtt = 24;</code>
+     */
+    public Builder clearTcpiRtt() {
+      
+      tcpiRtt_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiRttvar_ ;
+    /**
+     * <code>uint32 tcpi_rttvar = 25;</code>
+     */
+    public int getTcpiRttvar() {
+      return tcpiRttvar_;
+    }
+    /**
+     * <code>uint32 tcpi_rttvar = 25;</code>
+     */
+    public Builder setTcpiRttvar(int value) {
+      
+      tcpiRttvar_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_rttvar = 25;</code>
+     */
+    public Builder clearTcpiRttvar() {
+      
+      tcpiRttvar_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiSndSsthresh_ ;
+    /**
+     * <code>uint32 tcpi_snd_ssthresh = 26;</code>
+     */
+    public int getTcpiSndSsthresh() {
+      return tcpiSndSsthresh_;
+    }
+    /**
+     * <code>uint32 tcpi_snd_ssthresh = 26;</code>
+     */
+    public Builder setTcpiSndSsthresh(int value) {
+      
+      tcpiSndSsthresh_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_snd_ssthresh = 26;</code>
+     */
+    public Builder clearTcpiSndSsthresh() {
+      
+      tcpiSndSsthresh_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiSndCwnd_ ;
+    /**
+     * <code>uint32 tcpi_snd_cwnd = 27;</code>
+     */
+    public int getTcpiSndCwnd() {
+      return tcpiSndCwnd_;
+    }
+    /**
+     * <code>uint32 tcpi_snd_cwnd = 27;</code>
+     */
+    public Builder setTcpiSndCwnd(int value) {
+      
+      tcpiSndCwnd_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_snd_cwnd = 27;</code>
+     */
+    public Builder clearTcpiSndCwnd() {
+      
+      tcpiSndCwnd_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiAdvmss_ ;
+    /**
+     * <code>uint32 tcpi_advmss = 28;</code>
+     */
+    public int getTcpiAdvmss() {
+      return tcpiAdvmss_;
+    }
+    /**
+     * <code>uint32 tcpi_advmss = 28;</code>
+     */
+    public Builder setTcpiAdvmss(int value) {
+      
+      tcpiAdvmss_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_advmss = 28;</code>
+     */
+    public Builder clearTcpiAdvmss() {
+      
+      tcpiAdvmss_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private int tcpiReordering_ ;
+    /**
+     * <code>uint32 tcpi_reordering = 29;</code>
+     */
+    public int getTcpiReordering() {
+      return tcpiReordering_;
+    }
+    /**
+     * <code>uint32 tcpi_reordering = 29;</code>
+     */
+    public Builder setTcpiReordering(int value) {
+      
+      tcpiReordering_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>uint32 tcpi_reordering = 29;</code>
+     */
+    public Builder clearTcpiReordering() {
+      
+      tcpiReordering_ = 0;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.SocketOptionTcpInfo)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.SocketOptionTcpInfo)
+  private static final io.grpc.channelz.v1.SocketOptionTcpInfo DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.SocketOptionTcpInfo();
+  }
+
+  public static io.grpc.channelz.v1.SocketOptionTcpInfo getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<SocketOptionTcpInfo>
+      PARSER = new com.google.protobuf.AbstractParser<SocketOptionTcpInfo>() {
+    public SocketOptionTcpInfo parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new SocketOptionTcpInfo(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<SocketOptionTcpInfo> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<SocketOptionTcpInfo> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.SocketOptionTcpInfo getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionTcpInfoOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionTcpInfoOrBuilder.java
new file mode 100644
index 0000000..ce61679
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionTcpInfoOrBuilder.java
@@ -0,0 +1,154 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface SocketOptionTcpInfoOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.SocketOptionTcpInfo)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>uint32 tcpi_state = 1;</code>
+   */
+  int getTcpiState();
+
+  /**
+   * <code>uint32 tcpi_ca_state = 2;</code>
+   */
+  int getTcpiCaState();
+
+  /**
+   * <code>uint32 tcpi_retransmits = 3;</code>
+   */
+  int getTcpiRetransmits();
+
+  /**
+   * <code>uint32 tcpi_probes = 4;</code>
+   */
+  int getTcpiProbes();
+
+  /**
+   * <code>uint32 tcpi_backoff = 5;</code>
+   */
+  int getTcpiBackoff();
+
+  /**
+   * <code>uint32 tcpi_options = 6;</code>
+   */
+  int getTcpiOptions();
+
+  /**
+   * <code>uint32 tcpi_snd_wscale = 7;</code>
+   */
+  int getTcpiSndWscale();
+
+  /**
+   * <code>uint32 tcpi_rcv_wscale = 8;</code>
+   */
+  int getTcpiRcvWscale();
+
+  /**
+   * <code>uint32 tcpi_rto = 9;</code>
+   */
+  int getTcpiRto();
+
+  /**
+   * <code>uint32 tcpi_ato = 10;</code>
+   */
+  int getTcpiAto();
+
+  /**
+   * <code>uint32 tcpi_snd_mss = 11;</code>
+   */
+  int getTcpiSndMss();
+
+  /**
+   * <code>uint32 tcpi_rcv_mss = 12;</code>
+   */
+  int getTcpiRcvMss();
+
+  /**
+   * <code>uint32 tcpi_unacked = 13;</code>
+   */
+  int getTcpiUnacked();
+
+  /**
+   * <code>uint32 tcpi_sacked = 14;</code>
+   */
+  int getTcpiSacked();
+
+  /**
+   * <code>uint32 tcpi_lost = 15;</code>
+   */
+  int getTcpiLost();
+
+  /**
+   * <code>uint32 tcpi_retrans = 16;</code>
+   */
+  int getTcpiRetrans();
+
+  /**
+   * <code>uint32 tcpi_fackets = 17;</code>
+   */
+  int getTcpiFackets();
+
+  /**
+   * <code>uint32 tcpi_last_data_sent = 18;</code>
+   */
+  int getTcpiLastDataSent();
+
+  /**
+   * <code>uint32 tcpi_last_ack_sent = 19;</code>
+   */
+  int getTcpiLastAckSent();
+
+  /**
+   * <code>uint32 tcpi_last_data_recv = 20;</code>
+   */
+  int getTcpiLastDataRecv();
+
+  /**
+   * <code>uint32 tcpi_last_ack_recv = 21;</code>
+   */
+  int getTcpiLastAckRecv();
+
+  /**
+   * <code>uint32 tcpi_pmtu = 22;</code>
+   */
+  int getTcpiPmtu();
+
+  /**
+   * <code>uint32 tcpi_rcv_ssthresh = 23;</code>
+   */
+  int getTcpiRcvSsthresh();
+
+  /**
+   * <code>uint32 tcpi_rtt = 24;</code>
+   */
+  int getTcpiRtt();
+
+  /**
+   * <code>uint32 tcpi_rttvar = 25;</code>
+   */
+  int getTcpiRttvar();
+
+  /**
+   * <code>uint32 tcpi_snd_ssthresh = 26;</code>
+   */
+  int getTcpiSndSsthresh();
+
+  /**
+   * <code>uint32 tcpi_snd_cwnd = 27;</code>
+   */
+  int getTcpiSndCwnd();
+
+  /**
+   * <code>uint32 tcpi_advmss = 28;</code>
+   */
+  int getTcpiAdvmss();
+
+  /**
+   * <code>uint32 tcpi_reordering = 29;</code>
+   */
+  int getTcpiReordering();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionTimeout.java b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionTimeout.java
new file mode 100644
index 0000000..1c9ddb6
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionTimeout.java
@@ -0,0 +1,580 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * For use with SocketOption's additional field.  This is primarily used for
+ * SO_RCVTIMEO and SO_SNDTIMEO
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.SocketOptionTimeout}
+ */
+public  final class SocketOptionTimeout extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.SocketOptionTimeout)
+    SocketOptionTimeoutOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use SocketOptionTimeout.newBuilder() to construct.
+  private SocketOptionTimeout(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private SocketOptionTimeout() {
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private SocketOptionTimeout(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            com.google.protobuf.Duration.Builder subBuilder = null;
+            if (duration_ != null) {
+              subBuilder = duration_.toBuilder();
+            }
+            duration_ = input.readMessage(com.google.protobuf.Duration.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(duration_);
+              duration_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionTimeout_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionTimeout_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.SocketOptionTimeout.class, io.grpc.channelz.v1.SocketOptionTimeout.Builder.class);
+  }
+
+  public static final int DURATION_FIELD_NUMBER = 1;
+  private com.google.protobuf.Duration duration_;
+  /**
+   * <code>.google.protobuf.Duration duration = 1;</code>
+   */
+  public boolean hasDuration() {
+    return duration_ != null;
+  }
+  /**
+   * <code>.google.protobuf.Duration duration = 1;</code>
+   */
+  public com.google.protobuf.Duration getDuration() {
+    return duration_ == null ? com.google.protobuf.Duration.getDefaultInstance() : duration_;
+  }
+  /**
+   * <code>.google.protobuf.Duration duration = 1;</code>
+   */
+  public com.google.protobuf.DurationOrBuilder getDurationOrBuilder() {
+    return getDuration();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (duration_ != null) {
+      output.writeMessage(1, getDuration());
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (duration_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getDuration());
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.SocketOptionTimeout)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.SocketOptionTimeout other = (io.grpc.channelz.v1.SocketOptionTimeout) obj;
+
+    boolean result = true;
+    result = result && (hasDuration() == other.hasDuration());
+    if (hasDuration()) {
+      result = result && getDuration()
+          .equals(other.getDuration());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasDuration()) {
+      hash = (37 * hash) + DURATION_FIELD_NUMBER;
+      hash = (53 * hash) + getDuration().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.SocketOptionTimeout parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTimeout parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTimeout parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTimeout parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTimeout parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTimeout parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTimeout parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTimeout parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTimeout parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTimeout parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTimeout parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketOptionTimeout parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.SocketOptionTimeout prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * For use with SocketOption's additional field.  This is primarily used for
+   * SO_RCVTIMEO and SO_SNDTIMEO
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.SocketOptionTimeout}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.SocketOptionTimeout)
+      io.grpc.channelz.v1.SocketOptionTimeoutOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionTimeout_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionTimeout_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.SocketOptionTimeout.class, io.grpc.channelz.v1.SocketOptionTimeout.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.SocketOptionTimeout.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (durationBuilder_ == null) {
+        duration_ = null;
+      } else {
+        duration_ = null;
+        durationBuilder_ = null;
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketOptionTimeout_descriptor;
+    }
+
+    public io.grpc.channelz.v1.SocketOptionTimeout getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.SocketOptionTimeout.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.SocketOptionTimeout build() {
+      io.grpc.channelz.v1.SocketOptionTimeout result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.SocketOptionTimeout buildPartial() {
+      io.grpc.channelz.v1.SocketOptionTimeout result = new io.grpc.channelz.v1.SocketOptionTimeout(this);
+      if (durationBuilder_ == null) {
+        result.duration_ = duration_;
+      } else {
+        result.duration_ = durationBuilder_.build();
+      }
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.SocketOptionTimeout) {
+        return mergeFrom((io.grpc.channelz.v1.SocketOptionTimeout)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.SocketOptionTimeout other) {
+      if (other == io.grpc.channelz.v1.SocketOptionTimeout.getDefaultInstance()) return this;
+      if (other.hasDuration()) {
+        mergeDuration(other.getDuration());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.SocketOptionTimeout parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.SocketOptionTimeout) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private com.google.protobuf.Duration duration_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Duration, com.google.protobuf.Duration.Builder, com.google.protobuf.DurationOrBuilder> durationBuilder_;
+    /**
+     * <code>.google.protobuf.Duration duration = 1;</code>
+     */
+    public boolean hasDuration() {
+      return durationBuilder_ != null || duration_ != null;
+    }
+    /**
+     * <code>.google.protobuf.Duration duration = 1;</code>
+     */
+    public com.google.protobuf.Duration getDuration() {
+      if (durationBuilder_ == null) {
+        return duration_ == null ? com.google.protobuf.Duration.getDefaultInstance() : duration_;
+      } else {
+        return durationBuilder_.getMessage();
+      }
+    }
+    /**
+     * <code>.google.protobuf.Duration duration = 1;</code>
+     */
+    public Builder setDuration(com.google.protobuf.Duration value) {
+      if (durationBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        duration_ = value;
+        onChanged();
+      } else {
+        durationBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <code>.google.protobuf.Duration duration = 1;</code>
+     */
+    public Builder setDuration(
+        com.google.protobuf.Duration.Builder builderForValue) {
+      if (durationBuilder_ == null) {
+        duration_ = builderForValue.build();
+        onChanged();
+      } else {
+        durationBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <code>.google.protobuf.Duration duration = 1;</code>
+     */
+    public Builder mergeDuration(com.google.protobuf.Duration value) {
+      if (durationBuilder_ == null) {
+        if (duration_ != null) {
+          duration_ =
+            com.google.protobuf.Duration.newBuilder(duration_).mergeFrom(value).buildPartial();
+        } else {
+          duration_ = value;
+        }
+        onChanged();
+      } else {
+        durationBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <code>.google.protobuf.Duration duration = 1;</code>
+     */
+    public Builder clearDuration() {
+      if (durationBuilder_ == null) {
+        duration_ = null;
+        onChanged();
+      } else {
+        duration_ = null;
+        durationBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <code>.google.protobuf.Duration duration = 1;</code>
+     */
+    public com.google.protobuf.Duration.Builder getDurationBuilder() {
+      
+      onChanged();
+      return getDurationFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.google.protobuf.Duration duration = 1;</code>
+     */
+    public com.google.protobuf.DurationOrBuilder getDurationOrBuilder() {
+      if (durationBuilder_ != null) {
+        return durationBuilder_.getMessageOrBuilder();
+      } else {
+        return duration_ == null ?
+            com.google.protobuf.Duration.getDefaultInstance() : duration_;
+      }
+    }
+    /**
+     * <code>.google.protobuf.Duration duration = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        com.google.protobuf.Duration, com.google.protobuf.Duration.Builder, com.google.protobuf.DurationOrBuilder> 
+        getDurationFieldBuilder() {
+      if (durationBuilder_ == null) {
+        durationBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            com.google.protobuf.Duration, com.google.protobuf.Duration.Builder, com.google.protobuf.DurationOrBuilder>(
+                getDuration(),
+                getParentForChildren(),
+                isClean());
+        duration_ = null;
+      }
+      return durationBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.SocketOptionTimeout)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.SocketOptionTimeout)
+  private static final io.grpc.channelz.v1.SocketOptionTimeout DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.SocketOptionTimeout();
+  }
+
+  public static io.grpc.channelz.v1.SocketOptionTimeout getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<SocketOptionTimeout>
+      PARSER = new com.google.protobuf.AbstractParser<SocketOptionTimeout>() {
+    public SocketOptionTimeout parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new SocketOptionTimeout(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<SocketOptionTimeout> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<SocketOptionTimeout> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.SocketOptionTimeout getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionTimeoutOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionTimeoutOrBuilder.java
new file mode 100644
index 0000000..f49a02d
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOptionTimeoutOrBuilder.java
@@ -0,0 +1,22 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface SocketOptionTimeoutOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.SocketOptionTimeout)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>.google.protobuf.Duration duration = 1;</code>
+   */
+  boolean hasDuration();
+  /**
+   * <code>.google.protobuf.Duration duration = 1;</code>
+   */
+  com.google.protobuf.Duration getDuration();
+  /**
+   * <code>.google.protobuf.Duration duration = 1;</code>
+   */
+  com.google.protobuf.DurationOrBuilder getDurationOrBuilder();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SocketOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOrBuilder.java
new file mode 100644
index 0000000..ae9a892
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SocketOrBuilder.java
@@ -0,0 +1,157 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface SocketOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.Socket)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The identifier for the Socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+   */
+  boolean hasRef();
+  /**
+   * <pre>
+   * The identifier for the Socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+   */
+  io.grpc.channelz.v1.SocketRef getRef();
+  /**
+   * <pre>
+   * The identifier for the Socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SocketRef ref = 1;</code>
+   */
+  io.grpc.channelz.v1.SocketRefOrBuilder getRefOrBuilder();
+
+  /**
+   * <pre>
+   * Data specific to this Socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+   */
+  boolean hasData();
+  /**
+   * <pre>
+   * Data specific to this Socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+   */
+  io.grpc.channelz.v1.SocketData getData();
+  /**
+   * <pre>
+   * Data specific to this Socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SocketData data = 2;</code>
+   */
+  io.grpc.channelz.v1.SocketDataOrBuilder getDataOrBuilder();
+
+  /**
+   * <pre>
+   * The locally bound address.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Address local = 3;</code>
+   */
+  boolean hasLocal();
+  /**
+   * <pre>
+   * The locally bound address.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Address local = 3;</code>
+   */
+  io.grpc.channelz.v1.Address getLocal();
+  /**
+   * <pre>
+   * The locally bound address.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Address local = 3;</code>
+   */
+  io.grpc.channelz.v1.AddressOrBuilder getLocalOrBuilder();
+
+  /**
+   * <pre>
+   * The remote bound address.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Address remote = 4;</code>
+   */
+  boolean hasRemote();
+  /**
+   * <pre>
+   * The remote bound address.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Address remote = 4;</code>
+   */
+  io.grpc.channelz.v1.Address getRemote();
+  /**
+   * <pre>
+   * The remote bound address.  May be absent.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Address remote = 4;</code>
+   */
+  io.grpc.channelz.v1.AddressOrBuilder getRemoteOrBuilder();
+
+  /**
+   * <pre>
+   * Security details for this socket.  May be absent if not available, or
+   * there is no security on the socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Security security = 5;</code>
+   */
+  boolean hasSecurity();
+  /**
+   * <pre>
+   * Security details for this socket.  May be absent if not available, or
+   * there is no security on the socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Security security = 5;</code>
+   */
+  io.grpc.channelz.v1.Security getSecurity();
+  /**
+   * <pre>
+   * Security details for this socket.  May be absent if not available, or
+   * there is no security on the socket.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.Security security = 5;</code>
+   */
+  io.grpc.channelz.v1.SecurityOrBuilder getSecurityOrBuilder();
+
+  /**
+   * <pre>
+   * Optional, represents the name of the remote endpoint, if different than
+   * the original target name.
+   * </pre>
+   *
+   * <code>string remote_name = 6;</code>
+   */
+  java.lang.String getRemoteName();
+  /**
+   * <pre>
+   * Optional, represents the name of the remote endpoint, if different than
+   * the original target name.
+   * </pre>
+   *
+   * <code>string remote_name = 6;</code>
+   */
+  com.google.protobuf.ByteString
+      getRemoteNameBytes();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SocketRef.java b/services/src/generated/main/java/io/grpc/channelz/v1/SocketRef.java
new file mode 100644
index 0000000..089c374
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SocketRef.java
@@ -0,0 +1,611 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * SocketRef is a reference to a Socket.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.SocketRef}
+ */
+public  final class SocketRef extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.SocketRef)
+    SocketRefOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use SocketRef.newBuilder() to construct.
+  private SocketRef(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private SocketRef() {
+    socketId_ = 0L;
+    name_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private SocketRef(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 24: {
+
+            socketId_ = input.readInt64();
+            break;
+          }
+          case 34: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            name_ = s;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketRef_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketRef_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.SocketRef.class, io.grpc.channelz.v1.SocketRef.Builder.class);
+  }
+
+  public static final int SOCKET_ID_FIELD_NUMBER = 3;
+  private long socketId_;
+  /**
+   * <code>int64 socket_id = 3;</code>
+   */
+  public long getSocketId() {
+    return socketId_;
+  }
+
+  public static final int NAME_FIELD_NUMBER = 4;
+  private volatile java.lang.Object name_;
+  /**
+   * <pre>
+   * An optional name associated with the socket.
+   * </pre>
+   *
+   * <code>string name = 4;</code>
+   */
+  public java.lang.String getName() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      name_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * An optional name associated with the socket.
+   * </pre>
+   *
+   * <code>string name = 4;</code>
+   */
+  public com.google.protobuf.ByteString
+      getNameBytes() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      name_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (socketId_ != 0L) {
+      output.writeInt64(3, socketId_);
+    }
+    if (!getNameBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 4, name_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (socketId_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(3, socketId_);
+    }
+    if (!getNameBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(4, name_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.SocketRef)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.SocketRef other = (io.grpc.channelz.v1.SocketRef) obj;
+
+    boolean result = true;
+    result = result && (getSocketId()
+        == other.getSocketId());
+    result = result && getName()
+        .equals(other.getName());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + SOCKET_ID_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getSocketId());
+    hash = (37 * hash) + NAME_FIELD_NUMBER;
+    hash = (53 * hash) + getName().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.SocketRef parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketRef parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketRef parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketRef parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketRef parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SocketRef parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketRef parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketRef parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketRef parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketRef parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SocketRef parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SocketRef parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.SocketRef prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * SocketRef is a reference to a Socket.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.SocketRef}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.SocketRef)
+      io.grpc.channelz.v1.SocketRefOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketRef_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketRef_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.SocketRef.class, io.grpc.channelz.v1.SocketRef.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.SocketRef.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      socketId_ = 0L;
+
+      name_ = "";
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SocketRef_descriptor;
+    }
+
+    public io.grpc.channelz.v1.SocketRef getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.SocketRef.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.SocketRef build() {
+      io.grpc.channelz.v1.SocketRef result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.SocketRef buildPartial() {
+      io.grpc.channelz.v1.SocketRef result = new io.grpc.channelz.v1.SocketRef(this);
+      result.socketId_ = socketId_;
+      result.name_ = name_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.SocketRef) {
+        return mergeFrom((io.grpc.channelz.v1.SocketRef)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.SocketRef other) {
+      if (other == io.grpc.channelz.v1.SocketRef.getDefaultInstance()) return this;
+      if (other.getSocketId() != 0L) {
+        setSocketId(other.getSocketId());
+      }
+      if (!other.getName().isEmpty()) {
+        name_ = other.name_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.SocketRef parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.SocketRef) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private long socketId_ ;
+    /**
+     * <code>int64 socket_id = 3;</code>
+     */
+    public long getSocketId() {
+      return socketId_;
+    }
+    /**
+     * <code>int64 socket_id = 3;</code>
+     */
+    public Builder setSocketId(long value) {
+      
+      socketId_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>int64 socket_id = 3;</code>
+     */
+    public Builder clearSocketId() {
+      
+      socketId_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object name_ = "";
+    /**
+     * <pre>
+     * An optional name associated with the socket.
+     * </pre>
+     *
+     * <code>string name = 4;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        name_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An optional name associated with the socket.
+     * </pre>
+     *
+     * <code>string name = 4;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An optional name associated with the socket.
+     * </pre>
+     *
+     * <code>string name = 4;</code>
+     */
+    public Builder setName(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      name_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An optional name associated with the socket.
+     * </pre>
+     *
+     * <code>string name = 4;</code>
+     */
+    public Builder clearName() {
+      
+      name_ = getDefaultInstance().getName();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An optional name associated with the socket.
+     * </pre>
+     *
+     * <code>string name = 4;</code>
+     */
+    public Builder setNameBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      name_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.SocketRef)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.SocketRef)
+  private static final io.grpc.channelz.v1.SocketRef DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.SocketRef();
+  }
+
+  public static io.grpc.channelz.v1.SocketRef getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<SocketRef>
+      PARSER = new com.google.protobuf.AbstractParser<SocketRef>() {
+    public SocketRef parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new SocketRef(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<SocketRef> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<SocketRef> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.SocketRef getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SocketRefOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/SocketRefOrBuilder.java
new file mode 100644
index 0000000..3767016
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SocketRefOrBuilder.java
@@ -0,0 +1,32 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface SocketRefOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.SocketRef)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>int64 socket_id = 3;</code>
+   */
+  long getSocketId();
+
+  /**
+   * <pre>
+   * An optional name associated with the socket.
+   * </pre>
+   *
+   * <code>string name = 4;</code>
+   */
+  java.lang.String getName();
+  /**
+   * <pre>
+   * An optional name associated with the socket.
+   * </pre>
+   *
+   * <code>string name = 4;</code>
+   */
+  com.google.protobuf.ByteString
+      getNameBytes();
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/Subchannel.java b/services/src/generated/main/java/io/grpc/channelz/v1/Subchannel.java
new file mode 100644
index 0000000..1025f18
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/Subchannel.java
@@ -0,0 +1,2282 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * Subchannel is a logical grouping of channels, subchannels, and sockets.
+ * A subchannel is load balanced over by it's ancestor
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.Subchannel}
+ */
+public  final class Subchannel extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.Subchannel)
+    SubchannelOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Subchannel.newBuilder() to construct.
+  private Subchannel(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Subchannel() {
+    channelRef_ = java.util.Collections.emptyList();
+    subchannelRef_ = java.util.Collections.emptyList();
+    socketRef_ = java.util.Collections.emptyList();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Subchannel(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            io.grpc.channelz.v1.SubchannelRef.Builder subBuilder = null;
+            if (ref_ != null) {
+              subBuilder = ref_.toBuilder();
+            }
+            ref_ = input.readMessage(io.grpc.channelz.v1.SubchannelRef.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(ref_);
+              ref_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 18: {
+            io.grpc.channelz.v1.ChannelData.Builder subBuilder = null;
+            if (data_ != null) {
+              subBuilder = data_.toBuilder();
+            }
+            data_ = input.readMessage(io.grpc.channelz.v1.ChannelData.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(data_);
+              data_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 26: {
+            if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) {
+              channelRef_ = new java.util.ArrayList<io.grpc.channelz.v1.ChannelRef>();
+              mutable_bitField0_ |= 0x00000004;
+            }
+            channelRef_.add(
+                input.readMessage(io.grpc.channelz.v1.ChannelRef.parser(), extensionRegistry));
+            break;
+          }
+          case 34: {
+            if (!((mutable_bitField0_ & 0x00000008) == 0x00000008)) {
+              subchannelRef_ = new java.util.ArrayList<io.grpc.channelz.v1.SubchannelRef>();
+              mutable_bitField0_ |= 0x00000008;
+            }
+            subchannelRef_.add(
+                input.readMessage(io.grpc.channelz.v1.SubchannelRef.parser(), extensionRegistry));
+            break;
+          }
+          case 42: {
+            if (!((mutable_bitField0_ & 0x00000010) == 0x00000010)) {
+              socketRef_ = new java.util.ArrayList<io.grpc.channelz.v1.SocketRef>();
+              mutable_bitField0_ |= 0x00000010;
+            }
+            socketRef_.add(
+                input.readMessage(io.grpc.channelz.v1.SocketRef.parser(), extensionRegistry));
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) {
+        channelRef_ = java.util.Collections.unmodifiableList(channelRef_);
+      }
+      if (((mutable_bitField0_ & 0x00000008) == 0x00000008)) {
+        subchannelRef_ = java.util.Collections.unmodifiableList(subchannelRef_);
+      }
+      if (((mutable_bitField0_ & 0x00000010) == 0x00000010)) {
+        socketRef_ = java.util.Collections.unmodifiableList(socketRef_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Subchannel_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Subchannel_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.Subchannel.class, io.grpc.channelz.v1.Subchannel.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int REF_FIELD_NUMBER = 1;
+  private io.grpc.channelz.v1.SubchannelRef ref_;
+  /**
+   * <pre>
+   * The identifier for this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+   */
+  public boolean hasRef() {
+    return ref_ != null;
+  }
+  /**
+   * <pre>
+   * The identifier for this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+   */
+  public io.grpc.channelz.v1.SubchannelRef getRef() {
+    return ref_ == null ? io.grpc.channelz.v1.SubchannelRef.getDefaultInstance() : ref_;
+  }
+  /**
+   * <pre>
+   * The identifier for this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+   */
+  public io.grpc.channelz.v1.SubchannelRefOrBuilder getRefOrBuilder() {
+    return getRef();
+  }
+
+  public static final int DATA_FIELD_NUMBER = 2;
+  private io.grpc.channelz.v1.ChannelData data_;
+  /**
+   * <pre>
+   * Data specific to this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+   */
+  public boolean hasData() {
+    return data_ != null;
+  }
+  /**
+   * <pre>
+   * Data specific to this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+   */
+  public io.grpc.channelz.v1.ChannelData getData() {
+    return data_ == null ? io.grpc.channelz.v1.ChannelData.getDefaultInstance() : data_;
+  }
+  /**
+   * <pre>
+   * Data specific to this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+   */
+  public io.grpc.channelz.v1.ChannelDataOrBuilder getDataOrBuilder() {
+    return getData();
+  }
+
+  public static final int CHANNEL_REF_FIELD_NUMBER = 3;
+  private java.util.List<io.grpc.channelz.v1.ChannelRef> channelRef_;
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  public java.util.List<io.grpc.channelz.v1.ChannelRef> getChannelRefList() {
+    return channelRef_;
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  public java.util.List<? extends io.grpc.channelz.v1.ChannelRefOrBuilder> 
+      getChannelRefOrBuilderList() {
+    return channelRef_;
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  public int getChannelRefCount() {
+    return channelRef_.size();
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  public io.grpc.channelz.v1.ChannelRef getChannelRef(int index) {
+    return channelRef_.get(index);
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  public io.grpc.channelz.v1.ChannelRefOrBuilder getChannelRefOrBuilder(
+      int index) {
+    return channelRef_.get(index);
+  }
+
+  public static final int SUBCHANNEL_REF_FIELD_NUMBER = 4;
+  private java.util.List<io.grpc.channelz.v1.SubchannelRef> subchannelRef_;
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  public java.util.List<io.grpc.channelz.v1.SubchannelRef> getSubchannelRefList() {
+    return subchannelRef_;
+  }
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  public java.util.List<? extends io.grpc.channelz.v1.SubchannelRefOrBuilder> 
+      getSubchannelRefOrBuilderList() {
+    return subchannelRef_;
+  }
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  public int getSubchannelRefCount() {
+    return subchannelRef_.size();
+  }
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  public io.grpc.channelz.v1.SubchannelRef getSubchannelRef(int index) {
+    return subchannelRef_.get(index);
+  }
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  public io.grpc.channelz.v1.SubchannelRefOrBuilder getSubchannelRefOrBuilder(
+      int index) {
+    return subchannelRef_.get(index);
+  }
+
+  public static final int SOCKET_REF_FIELD_NUMBER = 5;
+  private java.util.List<io.grpc.channelz.v1.SocketRef> socketRef_;
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  public java.util.List<io.grpc.channelz.v1.SocketRef> getSocketRefList() {
+    return socketRef_;
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  public java.util.List<? extends io.grpc.channelz.v1.SocketRefOrBuilder> 
+      getSocketRefOrBuilderList() {
+    return socketRef_;
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  public int getSocketRefCount() {
+    return socketRef_.size();
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  public io.grpc.channelz.v1.SocketRef getSocketRef(int index) {
+    return socketRef_.get(index);
+  }
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  public io.grpc.channelz.v1.SocketRefOrBuilder getSocketRefOrBuilder(
+      int index) {
+    return socketRef_.get(index);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (ref_ != null) {
+      output.writeMessage(1, getRef());
+    }
+    if (data_ != null) {
+      output.writeMessage(2, getData());
+    }
+    for (int i = 0; i < channelRef_.size(); i++) {
+      output.writeMessage(3, channelRef_.get(i));
+    }
+    for (int i = 0; i < subchannelRef_.size(); i++) {
+      output.writeMessage(4, subchannelRef_.get(i));
+    }
+    for (int i = 0; i < socketRef_.size(); i++) {
+      output.writeMessage(5, socketRef_.get(i));
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (ref_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, getRef());
+    }
+    if (data_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(2, getData());
+    }
+    for (int i = 0; i < channelRef_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(3, channelRef_.get(i));
+    }
+    for (int i = 0; i < subchannelRef_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(4, subchannelRef_.get(i));
+    }
+    for (int i = 0; i < socketRef_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(5, socketRef_.get(i));
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.Subchannel)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.Subchannel other = (io.grpc.channelz.v1.Subchannel) obj;
+
+    boolean result = true;
+    result = result && (hasRef() == other.hasRef());
+    if (hasRef()) {
+      result = result && getRef()
+          .equals(other.getRef());
+    }
+    result = result && (hasData() == other.hasData());
+    if (hasData()) {
+      result = result && getData()
+          .equals(other.getData());
+    }
+    result = result && getChannelRefList()
+        .equals(other.getChannelRefList());
+    result = result && getSubchannelRefList()
+        .equals(other.getSubchannelRefList());
+    result = result && getSocketRefList()
+        .equals(other.getSocketRefList());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasRef()) {
+      hash = (37 * hash) + REF_FIELD_NUMBER;
+      hash = (53 * hash) + getRef().hashCode();
+    }
+    if (hasData()) {
+      hash = (37 * hash) + DATA_FIELD_NUMBER;
+      hash = (53 * hash) + getData().hashCode();
+    }
+    if (getChannelRefCount() > 0) {
+      hash = (37 * hash) + CHANNEL_REF_FIELD_NUMBER;
+      hash = (53 * hash) + getChannelRefList().hashCode();
+    }
+    if (getSubchannelRefCount() > 0) {
+      hash = (37 * hash) + SUBCHANNEL_REF_FIELD_NUMBER;
+      hash = (53 * hash) + getSubchannelRefList().hashCode();
+    }
+    if (getSocketRefCount() > 0) {
+      hash = (37 * hash) + SOCKET_REF_FIELD_NUMBER;
+      hash = (53 * hash) + getSocketRefList().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.Subchannel parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Subchannel parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Subchannel parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Subchannel parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Subchannel parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.Subchannel parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Subchannel parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Subchannel parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Subchannel parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Subchannel parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.Subchannel parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.Subchannel parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.Subchannel prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Subchannel is a logical grouping of channels, subchannels, and sockets.
+   * A subchannel is load balanced over by it's ancestor
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.Subchannel}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.Subchannel)
+      io.grpc.channelz.v1.SubchannelOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Subchannel_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Subchannel_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.Subchannel.class, io.grpc.channelz.v1.Subchannel.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.Subchannel.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+        getChannelRefFieldBuilder();
+        getSubchannelRefFieldBuilder();
+        getSocketRefFieldBuilder();
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (refBuilder_ == null) {
+        ref_ = null;
+      } else {
+        ref_ = null;
+        refBuilder_ = null;
+      }
+      if (dataBuilder_ == null) {
+        data_ = null;
+      } else {
+        data_ = null;
+        dataBuilder_ = null;
+      }
+      if (channelRefBuilder_ == null) {
+        channelRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000004);
+      } else {
+        channelRefBuilder_.clear();
+      }
+      if (subchannelRefBuilder_ == null) {
+        subchannelRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000008);
+      } else {
+        subchannelRefBuilder_.clear();
+      }
+      if (socketRefBuilder_ == null) {
+        socketRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000010);
+      } else {
+        socketRefBuilder_.clear();
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_Subchannel_descriptor;
+    }
+
+    public io.grpc.channelz.v1.Subchannel getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.Subchannel.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.Subchannel build() {
+      io.grpc.channelz.v1.Subchannel result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.Subchannel buildPartial() {
+      io.grpc.channelz.v1.Subchannel result = new io.grpc.channelz.v1.Subchannel(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      if (refBuilder_ == null) {
+        result.ref_ = ref_;
+      } else {
+        result.ref_ = refBuilder_.build();
+      }
+      if (dataBuilder_ == null) {
+        result.data_ = data_;
+      } else {
+        result.data_ = dataBuilder_.build();
+      }
+      if (channelRefBuilder_ == null) {
+        if (((bitField0_ & 0x00000004) == 0x00000004)) {
+          channelRef_ = java.util.Collections.unmodifiableList(channelRef_);
+          bitField0_ = (bitField0_ & ~0x00000004);
+        }
+        result.channelRef_ = channelRef_;
+      } else {
+        result.channelRef_ = channelRefBuilder_.build();
+      }
+      if (subchannelRefBuilder_ == null) {
+        if (((bitField0_ & 0x00000008) == 0x00000008)) {
+          subchannelRef_ = java.util.Collections.unmodifiableList(subchannelRef_);
+          bitField0_ = (bitField0_ & ~0x00000008);
+        }
+        result.subchannelRef_ = subchannelRef_;
+      } else {
+        result.subchannelRef_ = subchannelRefBuilder_.build();
+      }
+      if (socketRefBuilder_ == null) {
+        if (((bitField0_ & 0x00000010) == 0x00000010)) {
+          socketRef_ = java.util.Collections.unmodifiableList(socketRef_);
+          bitField0_ = (bitField0_ & ~0x00000010);
+        }
+        result.socketRef_ = socketRef_;
+      } else {
+        result.socketRef_ = socketRefBuilder_.build();
+      }
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.Subchannel) {
+        return mergeFrom((io.grpc.channelz.v1.Subchannel)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.Subchannel other) {
+      if (other == io.grpc.channelz.v1.Subchannel.getDefaultInstance()) return this;
+      if (other.hasRef()) {
+        mergeRef(other.getRef());
+      }
+      if (other.hasData()) {
+        mergeData(other.getData());
+      }
+      if (channelRefBuilder_ == null) {
+        if (!other.channelRef_.isEmpty()) {
+          if (channelRef_.isEmpty()) {
+            channelRef_ = other.channelRef_;
+            bitField0_ = (bitField0_ & ~0x00000004);
+          } else {
+            ensureChannelRefIsMutable();
+            channelRef_.addAll(other.channelRef_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.channelRef_.isEmpty()) {
+          if (channelRefBuilder_.isEmpty()) {
+            channelRefBuilder_.dispose();
+            channelRefBuilder_ = null;
+            channelRef_ = other.channelRef_;
+            bitField0_ = (bitField0_ & ~0x00000004);
+            channelRefBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getChannelRefFieldBuilder() : null;
+          } else {
+            channelRefBuilder_.addAllMessages(other.channelRef_);
+          }
+        }
+      }
+      if (subchannelRefBuilder_ == null) {
+        if (!other.subchannelRef_.isEmpty()) {
+          if (subchannelRef_.isEmpty()) {
+            subchannelRef_ = other.subchannelRef_;
+            bitField0_ = (bitField0_ & ~0x00000008);
+          } else {
+            ensureSubchannelRefIsMutable();
+            subchannelRef_.addAll(other.subchannelRef_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.subchannelRef_.isEmpty()) {
+          if (subchannelRefBuilder_.isEmpty()) {
+            subchannelRefBuilder_.dispose();
+            subchannelRefBuilder_ = null;
+            subchannelRef_ = other.subchannelRef_;
+            bitField0_ = (bitField0_ & ~0x00000008);
+            subchannelRefBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getSubchannelRefFieldBuilder() : null;
+          } else {
+            subchannelRefBuilder_.addAllMessages(other.subchannelRef_);
+          }
+        }
+      }
+      if (socketRefBuilder_ == null) {
+        if (!other.socketRef_.isEmpty()) {
+          if (socketRef_.isEmpty()) {
+            socketRef_ = other.socketRef_;
+            bitField0_ = (bitField0_ & ~0x00000010);
+          } else {
+            ensureSocketRefIsMutable();
+            socketRef_.addAll(other.socketRef_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.socketRef_.isEmpty()) {
+          if (socketRefBuilder_.isEmpty()) {
+            socketRefBuilder_.dispose();
+            socketRefBuilder_ = null;
+            socketRef_ = other.socketRef_;
+            bitField0_ = (bitField0_ & ~0x00000010);
+            socketRefBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getSocketRefFieldBuilder() : null;
+          } else {
+            socketRefBuilder_.addAllMessages(other.socketRef_);
+          }
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.Subchannel parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.Subchannel) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private io.grpc.channelz.v1.SubchannelRef ref_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.SubchannelRef, io.grpc.channelz.v1.SubchannelRef.Builder, io.grpc.channelz.v1.SubchannelRefOrBuilder> refBuilder_;
+    /**
+     * <pre>
+     * The identifier for this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+     */
+    public boolean hasRef() {
+      return refBuilder_ != null || ref_ != null;
+    }
+    /**
+     * <pre>
+     * The identifier for this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRef getRef() {
+      if (refBuilder_ == null) {
+        return ref_ == null ? io.grpc.channelz.v1.SubchannelRef.getDefaultInstance() : ref_;
+      } else {
+        return refBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * The identifier for this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+     */
+    public Builder setRef(io.grpc.channelz.v1.SubchannelRef value) {
+      if (refBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ref_ = value;
+        onChanged();
+      } else {
+        refBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+     */
+    public Builder setRef(
+        io.grpc.channelz.v1.SubchannelRef.Builder builderForValue) {
+      if (refBuilder_ == null) {
+        ref_ = builderForValue.build();
+        onChanged();
+      } else {
+        refBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+     */
+    public Builder mergeRef(io.grpc.channelz.v1.SubchannelRef value) {
+      if (refBuilder_ == null) {
+        if (ref_ != null) {
+          ref_ =
+            io.grpc.channelz.v1.SubchannelRef.newBuilder(ref_).mergeFrom(value).buildPartial();
+        } else {
+          ref_ = value;
+        }
+        onChanged();
+      } else {
+        refBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+     */
+    public Builder clearRef() {
+      if (refBuilder_ == null) {
+        ref_ = null;
+        onChanged();
+      } else {
+        ref_ = null;
+        refBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * The identifier for this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRef.Builder getRefBuilder() {
+      
+      onChanged();
+      return getRefFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * The identifier for this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRefOrBuilder getRefOrBuilder() {
+      if (refBuilder_ != null) {
+        return refBuilder_.getMessageOrBuilder();
+      } else {
+        return ref_ == null ?
+            io.grpc.channelz.v1.SubchannelRef.getDefaultInstance() : ref_;
+      }
+    }
+    /**
+     * <pre>
+     * The identifier for this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.SubchannelRef, io.grpc.channelz.v1.SubchannelRef.Builder, io.grpc.channelz.v1.SubchannelRefOrBuilder> 
+        getRefFieldBuilder() {
+      if (refBuilder_ == null) {
+        refBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.SubchannelRef, io.grpc.channelz.v1.SubchannelRef.Builder, io.grpc.channelz.v1.SubchannelRefOrBuilder>(
+                getRef(),
+                getParentForChildren(),
+                isClean());
+        ref_ = null;
+      }
+      return refBuilder_;
+    }
+
+    private io.grpc.channelz.v1.ChannelData data_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelData, io.grpc.channelz.v1.ChannelData.Builder, io.grpc.channelz.v1.ChannelDataOrBuilder> dataBuilder_;
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public boolean hasData() {
+      return dataBuilder_ != null || data_ != null;
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public io.grpc.channelz.v1.ChannelData getData() {
+      if (dataBuilder_ == null) {
+        return data_ == null ? io.grpc.channelz.v1.ChannelData.getDefaultInstance() : data_;
+      } else {
+        return dataBuilder_.getMessage();
+      }
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public Builder setData(io.grpc.channelz.v1.ChannelData value) {
+      if (dataBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        data_ = value;
+        onChanged();
+      } else {
+        dataBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public Builder setData(
+        io.grpc.channelz.v1.ChannelData.Builder builderForValue) {
+      if (dataBuilder_ == null) {
+        data_ = builderForValue.build();
+        onChanged();
+      } else {
+        dataBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public Builder mergeData(io.grpc.channelz.v1.ChannelData value) {
+      if (dataBuilder_ == null) {
+        if (data_ != null) {
+          data_ =
+            io.grpc.channelz.v1.ChannelData.newBuilder(data_).mergeFrom(value).buildPartial();
+        } else {
+          data_ = value;
+        }
+        onChanged();
+      } else {
+        dataBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public Builder clearData() {
+      if (dataBuilder_ == null) {
+        data_ = null;
+        onChanged();
+      } else {
+        data_ = null;
+        dataBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public io.grpc.channelz.v1.ChannelData.Builder getDataBuilder() {
+      
+      onChanged();
+      return getDataFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    public io.grpc.channelz.v1.ChannelDataOrBuilder getDataOrBuilder() {
+      if (dataBuilder_ != null) {
+        return dataBuilder_.getMessageOrBuilder();
+      } else {
+        return data_ == null ?
+            io.grpc.channelz.v1.ChannelData.getDefaultInstance() : data_;
+      }
+    }
+    /**
+     * <pre>
+     * Data specific to this channel.
+     * </pre>
+     *
+     * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelData, io.grpc.channelz.v1.ChannelData.Builder, io.grpc.channelz.v1.ChannelDataOrBuilder> 
+        getDataFieldBuilder() {
+      if (dataBuilder_ == null) {
+        dataBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.channelz.v1.ChannelData, io.grpc.channelz.v1.ChannelData.Builder, io.grpc.channelz.v1.ChannelDataOrBuilder>(
+                getData(),
+                getParentForChildren(),
+                isClean());
+        data_ = null;
+      }
+      return dataBuilder_;
+    }
+
+    private java.util.List<io.grpc.channelz.v1.ChannelRef> channelRef_ =
+      java.util.Collections.emptyList();
+    private void ensureChannelRefIsMutable() {
+      if (!((bitField0_ & 0x00000004) == 0x00000004)) {
+        channelRef_ = new java.util.ArrayList<io.grpc.channelz.v1.ChannelRef>(channelRef_);
+        bitField0_ |= 0x00000004;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelRef, io.grpc.channelz.v1.ChannelRef.Builder, io.grpc.channelz.v1.ChannelRefOrBuilder> channelRefBuilder_;
+
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.ChannelRef> getChannelRefList() {
+      if (channelRefBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(channelRef_);
+      } else {
+        return channelRefBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public int getChannelRefCount() {
+      if (channelRefBuilder_ == null) {
+        return channelRef_.size();
+      } else {
+        return channelRefBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRef getChannelRef(int index) {
+      if (channelRefBuilder_ == null) {
+        return channelRef_.get(index);
+      } else {
+        return channelRefBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder setChannelRef(
+        int index, io.grpc.channelz.v1.ChannelRef value) {
+      if (channelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureChannelRefIsMutable();
+        channelRef_.set(index, value);
+        onChanged();
+      } else {
+        channelRefBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder setChannelRef(
+        int index, io.grpc.channelz.v1.ChannelRef.Builder builderForValue) {
+      if (channelRefBuilder_ == null) {
+        ensureChannelRefIsMutable();
+        channelRef_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        channelRefBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder addChannelRef(io.grpc.channelz.v1.ChannelRef value) {
+      if (channelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureChannelRefIsMutable();
+        channelRef_.add(value);
+        onChanged();
+      } else {
+        channelRefBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder addChannelRef(
+        int index, io.grpc.channelz.v1.ChannelRef value) {
+      if (channelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureChannelRefIsMutable();
+        channelRef_.add(index, value);
+        onChanged();
+      } else {
+        channelRefBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder addChannelRef(
+        io.grpc.channelz.v1.ChannelRef.Builder builderForValue) {
+      if (channelRefBuilder_ == null) {
+        ensureChannelRefIsMutable();
+        channelRef_.add(builderForValue.build());
+        onChanged();
+      } else {
+        channelRefBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder addChannelRef(
+        int index, io.grpc.channelz.v1.ChannelRef.Builder builderForValue) {
+      if (channelRefBuilder_ == null) {
+        ensureChannelRefIsMutable();
+        channelRef_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        channelRefBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder addAllChannelRef(
+        java.lang.Iterable<? extends io.grpc.channelz.v1.ChannelRef> values) {
+      if (channelRefBuilder_ == null) {
+        ensureChannelRefIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, channelRef_);
+        onChanged();
+      } else {
+        channelRefBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder clearChannelRef() {
+      if (channelRefBuilder_ == null) {
+        channelRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000004);
+        onChanged();
+      } else {
+        channelRefBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public Builder removeChannelRef(int index) {
+      if (channelRefBuilder_ == null) {
+        ensureChannelRefIsMutable();
+        channelRef_.remove(index);
+        onChanged();
+      } else {
+        channelRefBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRef.Builder getChannelRefBuilder(
+        int index) {
+      return getChannelRefFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRefOrBuilder getChannelRefOrBuilder(
+        int index) {
+      if (channelRefBuilder_ == null) {
+        return channelRef_.get(index);  } else {
+        return channelRefBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public java.util.List<? extends io.grpc.channelz.v1.ChannelRefOrBuilder> 
+         getChannelRefOrBuilderList() {
+      if (channelRefBuilder_ != null) {
+        return channelRefBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(channelRef_);
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRef.Builder addChannelRefBuilder() {
+      return getChannelRefFieldBuilder().addBuilder(
+          io.grpc.channelz.v1.ChannelRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public io.grpc.channelz.v1.ChannelRef.Builder addChannelRefBuilder(
+        int index) {
+      return getChannelRefFieldBuilder().addBuilder(
+          index, io.grpc.channelz.v1.ChannelRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of channel refs.
+     * There may not be cycles in the ref graph.
+     * A channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.ChannelRef.Builder> 
+         getChannelRefBuilderList() {
+      return getChannelRefFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.ChannelRef, io.grpc.channelz.v1.ChannelRef.Builder, io.grpc.channelz.v1.ChannelRefOrBuilder> 
+        getChannelRefFieldBuilder() {
+      if (channelRefBuilder_ == null) {
+        channelRefBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.channelz.v1.ChannelRef, io.grpc.channelz.v1.ChannelRef.Builder, io.grpc.channelz.v1.ChannelRefOrBuilder>(
+                channelRef_,
+                ((bitField0_ & 0x00000004) == 0x00000004),
+                getParentForChildren(),
+                isClean());
+        channelRef_ = null;
+      }
+      return channelRefBuilder_;
+    }
+
+    private java.util.List<io.grpc.channelz.v1.SubchannelRef> subchannelRef_ =
+      java.util.Collections.emptyList();
+    private void ensureSubchannelRefIsMutable() {
+      if (!((bitField0_ & 0x00000008) == 0x00000008)) {
+        subchannelRef_ = new java.util.ArrayList<io.grpc.channelz.v1.SubchannelRef>(subchannelRef_);
+        bitField0_ |= 0x00000008;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SubchannelRef, io.grpc.channelz.v1.SubchannelRef.Builder, io.grpc.channelz.v1.SubchannelRefOrBuilder> subchannelRefBuilder_;
+
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SubchannelRef> getSubchannelRefList() {
+      if (subchannelRefBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(subchannelRef_);
+      } else {
+        return subchannelRefBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public int getSubchannelRefCount() {
+      if (subchannelRefBuilder_ == null) {
+        return subchannelRef_.size();
+      } else {
+        return subchannelRefBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRef getSubchannelRef(int index) {
+      if (subchannelRefBuilder_ == null) {
+        return subchannelRef_.get(index);
+      } else {
+        return subchannelRefBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder setSubchannelRef(
+        int index, io.grpc.channelz.v1.SubchannelRef value) {
+      if (subchannelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.set(index, value);
+        onChanged();
+      } else {
+        subchannelRefBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder setSubchannelRef(
+        int index, io.grpc.channelz.v1.SubchannelRef.Builder builderForValue) {
+      if (subchannelRefBuilder_ == null) {
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        subchannelRefBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder addSubchannelRef(io.grpc.channelz.v1.SubchannelRef value) {
+      if (subchannelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.add(value);
+        onChanged();
+      } else {
+        subchannelRefBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder addSubchannelRef(
+        int index, io.grpc.channelz.v1.SubchannelRef value) {
+      if (subchannelRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.add(index, value);
+        onChanged();
+      } else {
+        subchannelRefBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder addSubchannelRef(
+        io.grpc.channelz.v1.SubchannelRef.Builder builderForValue) {
+      if (subchannelRefBuilder_ == null) {
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.add(builderForValue.build());
+        onChanged();
+      } else {
+        subchannelRefBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder addSubchannelRef(
+        int index, io.grpc.channelz.v1.SubchannelRef.Builder builderForValue) {
+      if (subchannelRefBuilder_ == null) {
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        subchannelRefBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder addAllSubchannelRef(
+        java.lang.Iterable<? extends io.grpc.channelz.v1.SubchannelRef> values) {
+      if (subchannelRefBuilder_ == null) {
+        ensureSubchannelRefIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, subchannelRef_);
+        onChanged();
+      } else {
+        subchannelRefBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder clearSubchannelRef() {
+      if (subchannelRefBuilder_ == null) {
+        subchannelRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000008);
+        onChanged();
+      } else {
+        subchannelRefBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public Builder removeSubchannelRef(int index) {
+      if (subchannelRefBuilder_ == null) {
+        ensureSubchannelRefIsMutable();
+        subchannelRef_.remove(index);
+        onChanged();
+      } else {
+        subchannelRefBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRef.Builder getSubchannelRefBuilder(
+        int index) {
+      return getSubchannelRefFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRefOrBuilder getSubchannelRefOrBuilder(
+        int index) {
+      if (subchannelRefBuilder_ == null) {
+        return subchannelRef_.get(index);  } else {
+        return subchannelRefBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public java.util.List<? extends io.grpc.channelz.v1.SubchannelRefOrBuilder> 
+         getSubchannelRefOrBuilderList() {
+      if (subchannelRefBuilder_ != null) {
+        return subchannelRefBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(subchannelRef_);
+      }
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRef.Builder addSubchannelRefBuilder() {
+      return getSubchannelRefFieldBuilder().addBuilder(
+          io.grpc.channelz.v1.SubchannelRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public io.grpc.channelz.v1.SubchannelRef.Builder addSubchannelRefBuilder(
+        int index) {
+      return getSubchannelRefFieldBuilder().addBuilder(
+          index, io.grpc.channelz.v1.SubchannelRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+     * There are no ordering guarantees on the order of subchannel refs.
+     * There may not be cycles in the ref graph.
+     * A sub channel ref may be present in more than one channel or subchannel.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SubchannelRef.Builder> 
+         getSubchannelRefBuilderList() {
+      return getSubchannelRefFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SubchannelRef, io.grpc.channelz.v1.SubchannelRef.Builder, io.grpc.channelz.v1.SubchannelRefOrBuilder> 
+        getSubchannelRefFieldBuilder() {
+      if (subchannelRefBuilder_ == null) {
+        subchannelRefBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.channelz.v1.SubchannelRef, io.grpc.channelz.v1.SubchannelRef.Builder, io.grpc.channelz.v1.SubchannelRefOrBuilder>(
+                subchannelRef_,
+                ((bitField0_ & 0x00000008) == 0x00000008),
+                getParentForChildren(),
+                isClean());
+        subchannelRef_ = null;
+      }
+      return subchannelRefBuilder_;
+    }
+
+    private java.util.List<io.grpc.channelz.v1.SocketRef> socketRef_ =
+      java.util.Collections.emptyList();
+    private void ensureSocketRefIsMutable() {
+      if (!((bitField0_ & 0x00000010) == 0x00000010)) {
+        socketRef_ = new java.util.ArrayList<io.grpc.channelz.v1.SocketRef>(socketRef_);
+        bitField0_ |= 0x00000010;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder> socketRefBuilder_;
+
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SocketRef> getSocketRefList() {
+      if (socketRefBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(socketRef_);
+      } else {
+        return socketRefBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public int getSocketRefCount() {
+      if (socketRefBuilder_ == null) {
+        return socketRef_.size();
+      } else {
+        return socketRefBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef getSocketRef(int index) {
+      if (socketRefBuilder_ == null) {
+        return socketRef_.get(index);
+      } else {
+        return socketRefBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder setSocketRef(
+        int index, io.grpc.channelz.v1.SocketRef value) {
+      if (socketRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSocketRefIsMutable();
+        socketRef_.set(index, value);
+        onChanged();
+      } else {
+        socketRefBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder setSocketRef(
+        int index, io.grpc.channelz.v1.SocketRef.Builder builderForValue) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        socketRef_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        socketRefBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder addSocketRef(io.grpc.channelz.v1.SocketRef value) {
+      if (socketRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSocketRefIsMutable();
+        socketRef_.add(value);
+        onChanged();
+      } else {
+        socketRefBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder addSocketRef(
+        int index, io.grpc.channelz.v1.SocketRef value) {
+      if (socketRefBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureSocketRefIsMutable();
+        socketRef_.add(index, value);
+        onChanged();
+      } else {
+        socketRefBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder addSocketRef(
+        io.grpc.channelz.v1.SocketRef.Builder builderForValue) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        socketRef_.add(builderForValue.build());
+        onChanged();
+      } else {
+        socketRefBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder addSocketRef(
+        int index, io.grpc.channelz.v1.SocketRef.Builder builderForValue) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        socketRef_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        socketRefBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder addAllSocketRef(
+        java.lang.Iterable<? extends io.grpc.channelz.v1.SocketRef> values) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, socketRef_);
+        onChanged();
+      } else {
+        socketRefBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder clearSocketRef() {
+      if (socketRefBuilder_ == null) {
+        socketRef_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000010);
+        onChanged();
+      } else {
+        socketRefBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public Builder removeSocketRef(int index) {
+      if (socketRefBuilder_ == null) {
+        ensureSocketRefIsMutable();
+        socketRef_.remove(index);
+        onChanged();
+      } else {
+        socketRefBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef.Builder getSocketRefBuilder(
+        int index) {
+      return getSocketRefFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public io.grpc.channelz.v1.SocketRefOrBuilder getSocketRefOrBuilder(
+        int index) {
+      if (socketRefBuilder_ == null) {
+        return socketRef_.get(index);  } else {
+        return socketRefBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public java.util.List<? extends io.grpc.channelz.v1.SocketRefOrBuilder> 
+         getSocketRefOrBuilderList() {
+      if (socketRefBuilder_ != null) {
+        return socketRefBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(socketRef_);
+      }
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef.Builder addSocketRefBuilder() {
+      return getSocketRefFieldBuilder().addBuilder(
+          io.grpc.channelz.v1.SocketRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public io.grpc.channelz.v1.SocketRef.Builder addSocketRefBuilder(
+        int index) {
+      return getSocketRefFieldBuilder().addBuilder(
+          index, io.grpc.channelz.v1.SocketRef.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * There are no ordering guarantees on the order of sockets.
+     * </pre>
+     *
+     * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+     */
+    public java.util.List<io.grpc.channelz.v1.SocketRef.Builder> 
+         getSocketRefBuilderList() {
+      return getSocketRefFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder> 
+        getSocketRefFieldBuilder() {
+      if (socketRefBuilder_ == null) {
+        socketRefBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.channelz.v1.SocketRef, io.grpc.channelz.v1.SocketRef.Builder, io.grpc.channelz.v1.SocketRefOrBuilder>(
+                socketRef_,
+                ((bitField0_ & 0x00000010) == 0x00000010),
+                getParentForChildren(),
+                isClean());
+        socketRef_ = null;
+      }
+      return socketRefBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.Subchannel)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.Subchannel)
+  private static final io.grpc.channelz.v1.Subchannel DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.Subchannel();
+  }
+
+  public static io.grpc.channelz.v1.Subchannel getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<Subchannel>
+      PARSER = new com.google.protobuf.AbstractParser<Subchannel>() {
+    public Subchannel parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Subchannel(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Subchannel> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Subchannel> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.Subchannel getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SubchannelOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/SubchannelOrBuilder.java
new file mode 100644
index 0000000..6f89dc3
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SubchannelOrBuilder.java
@@ -0,0 +1,216 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface SubchannelOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.Subchannel)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The identifier for this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+   */
+  boolean hasRef();
+  /**
+   * <pre>
+   * The identifier for this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+   */
+  io.grpc.channelz.v1.SubchannelRef getRef();
+  /**
+   * <pre>
+   * The identifier for this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.SubchannelRef ref = 1;</code>
+   */
+  io.grpc.channelz.v1.SubchannelRefOrBuilder getRefOrBuilder();
+
+  /**
+   * <pre>
+   * Data specific to this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+   */
+  boolean hasData();
+  /**
+   * <pre>
+   * Data specific to this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+   */
+  io.grpc.channelz.v1.ChannelData getData();
+  /**
+   * <pre>
+   * Data specific to this channel.
+   * </pre>
+   *
+   * <code>.grpc.channelz.v1.ChannelData data = 2;</code>
+   */
+  io.grpc.channelz.v1.ChannelDataOrBuilder getDataOrBuilder();
+
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  java.util.List<io.grpc.channelz.v1.ChannelRef> 
+      getChannelRefList();
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  io.grpc.channelz.v1.ChannelRef getChannelRef(int index);
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  int getChannelRefCount();
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  java.util.List<? extends io.grpc.channelz.v1.ChannelRefOrBuilder> 
+      getChannelRefOrBuilderList();
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of channel refs.
+   * There may not be cycles in the ref graph.
+   * A channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.ChannelRef channel_ref = 3;</code>
+   */
+  io.grpc.channelz.v1.ChannelRefOrBuilder getChannelRefOrBuilder(
+      int index);
+
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  java.util.List<io.grpc.channelz.v1.SubchannelRef> 
+      getSubchannelRefList();
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  io.grpc.channelz.v1.SubchannelRef getSubchannelRef(int index);
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  int getSubchannelRefCount();
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  java.util.List<? extends io.grpc.channelz.v1.SubchannelRefOrBuilder> 
+      getSubchannelRefOrBuilderList();
+  /**
+   * <pre>
+   * At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+   * There are no ordering guarantees on the order of subchannel refs.
+   * There may not be cycles in the ref graph.
+   * A sub channel ref may be present in more than one channel or subchannel.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SubchannelRef subchannel_ref = 4;</code>
+   */
+  io.grpc.channelz.v1.SubchannelRefOrBuilder getSubchannelRefOrBuilder(
+      int index);
+
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  java.util.List<io.grpc.channelz.v1.SocketRef> 
+      getSocketRefList();
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  io.grpc.channelz.v1.SocketRef getSocketRef(int index);
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  int getSocketRefCount();
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  java.util.List<? extends io.grpc.channelz.v1.SocketRefOrBuilder> 
+      getSocketRefOrBuilderList();
+  /**
+   * <pre>
+   * There are no ordering guarantees on the order of sockets.
+   * </pre>
+   *
+   * <code>repeated .grpc.channelz.v1.SocketRef socket_ref = 5;</code>
+   */
+  io.grpc.channelz.v1.SocketRefOrBuilder getSocketRefOrBuilder(
+      int index);
+}
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SubchannelRef.java b/services/src/generated/main/java/io/grpc/channelz/v1/SubchannelRef.java
new file mode 100644
index 0000000..2c1e18b
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SubchannelRef.java
@@ -0,0 +1,627 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+/**
+ * <pre>
+ * ChannelRef is a reference to a Subchannel.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.channelz.v1.SubchannelRef}
+ */
+public  final class SubchannelRef extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.channelz.v1.SubchannelRef)
+    SubchannelRefOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use SubchannelRef.newBuilder() to construct.
+  private SubchannelRef(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private SubchannelRef() {
+    subchannelId_ = 0L;
+    name_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private SubchannelRef(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 56: {
+
+            subchannelId_ = input.readInt64();
+            break;
+          }
+          case 66: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            name_ = s;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SubchannelRef_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SubchannelRef_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.channelz.v1.SubchannelRef.class, io.grpc.channelz.v1.SubchannelRef.Builder.class);
+  }
+
+  public static final int SUBCHANNEL_ID_FIELD_NUMBER = 7;
+  private long subchannelId_;
+  /**
+   * <pre>
+   * The globally unique id for this subchannel.  Must be a positive number.
+   * </pre>
+   *
+   * <code>int64 subchannel_id = 7;</code>
+   */
+  public long getSubchannelId() {
+    return subchannelId_;
+  }
+
+  public static final int NAME_FIELD_NUMBER = 8;
+  private volatile java.lang.Object name_;
+  /**
+   * <pre>
+   * An optional name associated with the subchannel.
+   * </pre>
+   *
+   * <code>string name = 8;</code>
+   */
+  public java.lang.String getName() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      name_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * An optional name associated with the subchannel.
+   * </pre>
+   *
+   * <code>string name = 8;</code>
+   */
+  public com.google.protobuf.ByteString
+      getNameBytes() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      name_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (subchannelId_ != 0L) {
+      output.writeInt64(7, subchannelId_);
+    }
+    if (!getNameBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 8, name_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (subchannelId_ != 0L) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt64Size(7, subchannelId_);
+    }
+    if (!getNameBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(8, name_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.channelz.v1.SubchannelRef)) {
+      return super.equals(obj);
+    }
+    io.grpc.channelz.v1.SubchannelRef other = (io.grpc.channelz.v1.SubchannelRef) obj;
+
+    boolean result = true;
+    result = result && (getSubchannelId()
+        == other.getSubchannelId());
+    result = result && getName()
+        .equals(other.getName());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + SUBCHANNEL_ID_FIELD_NUMBER;
+    hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
+        getSubchannelId());
+    hash = (37 * hash) + NAME_FIELD_NUMBER;
+    hash = (53 * hash) + getName().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.channelz.v1.SubchannelRef parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SubchannelRef parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SubchannelRef parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SubchannelRef parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SubchannelRef parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.channelz.v1.SubchannelRef parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SubchannelRef parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SubchannelRef parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SubchannelRef parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SubchannelRef parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.channelz.v1.SubchannelRef parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.channelz.v1.SubchannelRef parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.channelz.v1.SubchannelRef prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * ChannelRef is a reference to a Subchannel.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.channelz.v1.SubchannelRef}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.channelz.v1.SubchannelRef)
+      io.grpc.channelz.v1.SubchannelRefOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SubchannelRef_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SubchannelRef_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.channelz.v1.SubchannelRef.class, io.grpc.channelz.v1.SubchannelRef.Builder.class);
+    }
+
+    // Construct using io.grpc.channelz.v1.SubchannelRef.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      subchannelId_ = 0L;
+
+      name_ = "";
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.channelz.v1.ChannelzProto.internal_static_grpc_channelz_v1_SubchannelRef_descriptor;
+    }
+
+    public io.grpc.channelz.v1.SubchannelRef getDefaultInstanceForType() {
+      return io.grpc.channelz.v1.SubchannelRef.getDefaultInstance();
+    }
+
+    public io.grpc.channelz.v1.SubchannelRef build() {
+      io.grpc.channelz.v1.SubchannelRef result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.channelz.v1.SubchannelRef buildPartial() {
+      io.grpc.channelz.v1.SubchannelRef result = new io.grpc.channelz.v1.SubchannelRef(this);
+      result.subchannelId_ = subchannelId_;
+      result.name_ = name_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.channelz.v1.SubchannelRef) {
+        return mergeFrom((io.grpc.channelz.v1.SubchannelRef)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.channelz.v1.SubchannelRef other) {
+      if (other == io.grpc.channelz.v1.SubchannelRef.getDefaultInstance()) return this;
+      if (other.getSubchannelId() != 0L) {
+        setSubchannelId(other.getSubchannelId());
+      }
+      if (!other.getName().isEmpty()) {
+        name_ = other.name_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.channelz.v1.SubchannelRef parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.channelz.v1.SubchannelRef) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private long subchannelId_ ;
+    /**
+     * <pre>
+     * The globally unique id for this subchannel.  Must be a positive number.
+     * </pre>
+     *
+     * <code>int64 subchannel_id = 7;</code>
+     */
+    public long getSubchannelId() {
+      return subchannelId_;
+    }
+    /**
+     * <pre>
+     * The globally unique id for this subchannel.  Must be a positive number.
+     * </pre>
+     *
+     * <code>int64 subchannel_id = 7;</code>
+     */
+    public Builder setSubchannelId(long value) {
+      
+      subchannelId_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * The globally unique id for this subchannel.  Must be a positive number.
+     * </pre>
+     *
+     * <code>int64 subchannel_id = 7;</code>
+     */
+    public Builder clearSubchannelId() {
+      
+      subchannelId_ = 0L;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object name_ = "";
+    /**
+     * <pre>
+     * An optional name associated with the subchannel.
+     * </pre>
+     *
+     * <code>string name = 8;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        name_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An optional name associated with the subchannel.
+     * </pre>
+     *
+     * <code>string name = 8;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An optional name associated with the subchannel.
+     * </pre>
+     *
+     * <code>string name = 8;</code>
+     */
+    public Builder setName(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      name_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An optional name associated with the subchannel.
+     * </pre>
+     *
+     * <code>string name = 8;</code>
+     */
+    public Builder clearName() {
+      
+      name_ = getDefaultInstance().getName();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An optional name associated with the subchannel.
+     * </pre>
+     *
+     * <code>string name = 8;</code>
+     */
+    public Builder setNameBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      name_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.channelz.v1.SubchannelRef)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.channelz.v1.SubchannelRef)
+  private static final io.grpc.channelz.v1.SubchannelRef DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.channelz.v1.SubchannelRef();
+  }
+
+  public static io.grpc.channelz.v1.SubchannelRef getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<SubchannelRef>
+      PARSER = new com.google.protobuf.AbstractParser<SubchannelRef>() {
+    public SubchannelRef parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new SubchannelRef(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<SubchannelRef> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<SubchannelRef> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.channelz.v1.SubchannelRef getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/channelz/v1/SubchannelRefOrBuilder.java b/services/src/generated/main/java/io/grpc/channelz/v1/SubchannelRefOrBuilder.java
new file mode 100644
index 0000000..2120816
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/channelz/v1/SubchannelRefOrBuilder.java
@@ -0,0 +1,36 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/channelz/v1/channelz.proto
+
+package io.grpc.channelz.v1;
+
+public interface SubchannelRefOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.channelz.v1.SubchannelRef)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The globally unique id for this subchannel.  Must be a positive number.
+   * </pre>
+   *
+   * <code>int64 subchannel_id = 7;</code>
+   */
+  long getSubchannelId();
+
+  /**
+   * <pre>
+   * An optional name associated with the subchannel.
+   * </pre>
+   *
+   * <code>string name = 8;</code>
+   */
+  java.lang.String getName();
+  /**
+   * <pre>
+   * An optional name associated with the subchannel.
+   * </pre>
+   *
+   * <code>string name = 8;</code>
+   */
+  com.google.protobuf.ByteString
+      getNameBytes();
+}
diff --git a/services/src/generated/main/java/io/grpc/health/v1/HealthCheckRequest.java b/services/src/generated/main/java/io/grpc/health/v1/HealthCheckRequest.java
new file mode 100644
index 0000000..ef6268a
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/health/v1/HealthCheckRequest.java
@@ -0,0 +1,516 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/health/v1/health.proto
+
+package io.grpc.health.v1;
+
+/**
+ * Protobuf type {@code grpc.health.v1.HealthCheckRequest}
+ */
+public  final class HealthCheckRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.health.v1.HealthCheckRequest)
+    HealthCheckRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use HealthCheckRequest.newBuilder() to construct.
+  private HealthCheckRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private HealthCheckRequest() {
+    service_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private HealthCheckRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            service_ = s;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.health.v1.HealthProto.internal_static_grpc_health_v1_HealthCheckRequest_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.health.v1.HealthProto.internal_static_grpc_health_v1_HealthCheckRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.health.v1.HealthCheckRequest.class, io.grpc.health.v1.HealthCheckRequest.Builder.class);
+  }
+
+  public static final int SERVICE_FIELD_NUMBER = 1;
+  private volatile java.lang.Object service_;
+  /**
+   * <code>string service = 1;</code>
+   */
+  public java.lang.String getService() {
+    java.lang.Object ref = service_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      service_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string service = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getServiceBytes() {
+    java.lang.Object ref = service_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      service_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getServiceBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, service_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getServiceBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, service_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.health.v1.HealthCheckRequest)) {
+      return super.equals(obj);
+    }
+    io.grpc.health.v1.HealthCheckRequest other = (io.grpc.health.v1.HealthCheckRequest) obj;
+
+    boolean result = true;
+    result = result && getService()
+        .equals(other.getService());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + SERVICE_FIELD_NUMBER;
+    hash = (53 * hash) + getService().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.health.v1.HealthCheckRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.health.v1.HealthCheckRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.health.v1.HealthCheckRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.health.v1.HealthCheckRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.health.v1.HealthCheckRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.health.v1.HealthCheckRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.health.v1.HealthCheckRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.health.v1.HealthCheckRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.health.v1.HealthCheckRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.health.v1.HealthCheckRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.health.v1.HealthCheckRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.health.v1.HealthCheckRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.health.v1.HealthCheckRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.health.v1.HealthCheckRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.health.v1.HealthCheckRequest)
+      io.grpc.health.v1.HealthCheckRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.health.v1.HealthProto.internal_static_grpc_health_v1_HealthCheckRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.health.v1.HealthProto.internal_static_grpc_health_v1_HealthCheckRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.health.v1.HealthCheckRequest.class, io.grpc.health.v1.HealthCheckRequest.Builder.class);
+    }
+
+    // Construct using io.grpc.health.v1.HealthCheckRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      service_ = "";
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.health.v1.HealthProto.internal_static_grpc_health_v1_HealthCheckRequest_descriptor;
+    }
+
+    public io.grpc.health.v1.HealthCheckRequest getDefaultInstanceForType() {
+      return io.grpc.health.v1.HealthCheckRequest.getDefaultInstance();
+    }
+
+    public io.grpc.health.v1.HealthCheckRequest build() {
+      io.grpc.health.v1.HealthCheckRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.health.v1.HealthCheckRequest buildPartial() {
+      io.grpc.health.v1.HealthCheckRequest result = new io.grpc.health.v1.HealthCheckRequest(this);
+      result.service_ = service_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.health.v1.HealthCheckRequest) {
+        return mergeFrom((io.grpc.health.v1.HealthCheckRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.health.v1.HealthCheckRequest other) {
+      if (other == io.grpc.health.v1.HealthCheckRequest.getDefaultInstance()) return this;
+      if (!other.getService().isEmpty()) {
+        service_ = other.service_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.health.v1.HealthCheckRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.health.v1.HealthCheckRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private java.lang.Object service_ = "";
+    /**
+     * <code>string service = 1;</code>
+     */
+    public java.lang.String getService() {
+      java.lang.Object ref = service_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        service_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string service = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getServiceBytes() {
+      java.lang.Object ref = service_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        service_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string service = 1;</code>
+     */
+    public Builder setService(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      service_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string service = 1;</code>
+     */
+    public Builder clearService() {
+      
+      service_ = getDefaultInstance().getService();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string service = 1;</code>
+     */
+    public Builder setServiceBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      service_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.health.v1.HealthCheckRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.health.v1.HealthCheckRequest)
+  private static final io.grpc.health.v1.HealthCheckRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.health.v1.HealthCheckRequest();
+  }
+
+  public static io.grpc.health.v1.HealthCheckRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<HealthCheckRequest>
+      PARSER = new com.google.protobuf.AbstractParser<HealthCheckRequest>() {
+    public HealthCheckRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new HealthCheckRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<HealthCheckRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<HealthCheckRequest> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.health.v1.HealthCheckRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/health/v1/HealthCheckRequestOrBuilder.java b/services/src/generated/main/java/io/grpc/health/v1/HealthCheckRequestOrBuilder.java
new file mode 100644
index 0000000..4613f1c
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/health/v1/HealthCheckRequestOrBuilder.java
@@ -0,0 +1,19 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/health/v1/health.proto
+
+package io.grpc.health.v1;
+
+public interface HealthCheckRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.health.v1.HealthCheckRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>string service = 1;</code>
+   */
+  java.lang.String getService();
+  /**
+   * <code>string service = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getServiceBytes();
+}
diff --git a/services/src/generated/main/java/io/grpc/health/v1/HealthCheckResponse.java b/services/src/generated/main/java/io/grpc/health/v1/HealthCheckResponse.java
new file mode 100644
index 0000000..efe312c
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/health/v1/HealthCheckResponse.java
@@ -0,0 +1,579 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/health/v1/health.proto
+
+package io.grpc.health.v1;
+
+/**
+ * Protobuf type {@code grpc.health.v1.HealthCheckResponse}
+ */
+public  final class HealthCheckResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.health.v1.HealthCheckResponse)
+    HealthCheckResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use HealthCheckResponse.newBuilder() to construct.
+  private HealthCheckResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private HealthCheckResponse() {
+    status_ = 0;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private HealthCheckResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+            int rawValue = input.readEnum();
+
+            status_ = rawValue;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.health.v1.HealthProto.internal_static_grpc_health_v1_HealthCheckResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.health.v1.HealthProto.internal_static_grpc_health_v1_HealthCheckResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.health.v1.HealthCheckResponse.class, io.grpc.health.v1.HealthCheckResponse.Builder.class);
+  }
+
+  /**
+   * Protobuf enum {@code grpc.health.v1.HealthCheckResponse.ServingStatus}
+   */
+  public enum ServingStatus
+      implements com.google.protobuf.ProtocolMessageEnum {
+    /**
+     * <code>UNKNOWN = 0;</code>
+     */
+    UNKNOWN(0),
+    /**
+     * <code>SERVING = 1;</code>
+     */
+    SERVING(1),
+    /**
+     * <code>NOT_SERVING = 2;</code>
+     */
+    NOT_SERVING(2),
+    UNRECOGNIZED(-1),
+    ;
+
+    /**
+     * <code>UNKNOWN = 0;</code>
+     */
+    public static final int UNKNOWN_VALUE = 0;
+    /**
+     * <code>SERVING = 1;</code>
+     */
+    public static final int SERVING_VALUE = 1;
+    /**
+     * <code>NOT_SERVING = 2;</code>
+     */
+    public static final int NOT_SERVING_VALUE = 2;
+
+
+    public final int getNumber() {
+      if (this == UNRECOGNIZED) {
+        throw new java.lang.IllegalArgumentException(
+            "Can't get the number of an unknown enum value.");
+      }
+      return value;
+    }
+
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static ServingStatus valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static ServingStatus forNumber(int value) {
+      switch (value) {
+        case 0: return UNKNOWN;
+        case 1: return SERVING;
+        case 2: return NOT_SERVING;
+        default: return null;
+      }
+    }
+
+    public static com.google.protobuf.Internal.EnumLiteMap<ServingStatus>
+        internalGetValueMap() {
+      return internalValueMap;
+    }
+    private static final com.google.protobuf.Internal.EnumLiteMap<
+        ServingStatus> internalValueMap =
+          new com.google.protobuf.Internal.EnumLiteMap<ServingStatus>() {
+            public ServingStatus findValueByNumber(int number) {
+              return ServingStatus.forNumber(number);
+            }
+          };
+
+    public final com.google.protobuf.Descriptors.EnumValueDescriptor
+        getValueDescriptor() {
+      return getDescriptor().getValues().get(ordinal());
+    }
+    public final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptorForType() {
+      return getDescriptor();
+    }
+    public static final com.google.protobuf.Descriptors.EnumDescriptor
+        getDescriptor() {
+      return io.grpc.health.v1.HealthCheckResponse.getDescriptor().getEnumTypes().get(0);
+    }
+
+    private static final ServingStatus[] VALUES = values();
+
+    public static ServingStatus valueOf(
+        com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+      if (desc.getType() != getDescriptor()) {
+        throw new java.lang.IllegalArgumentException(
+          "EnumValueDescriptor is not for this type.");
+      }
+      if (desc.getIndex() == -1) {
+        return UNRECOGNIZED;
+      }
+      return VALUES[desc.getIndex()];
+    }
+
+    private final int value;
+
+    private ServingStatus(int value) {
+      this.value = value;
+    }
+
+    // @@protoc_insertion_point(enum_scope:grpc.health.v1.HealthCheckResponse.ServingStatus)
+  }
+
+  public static final int STATUS_FIELD_NUMBER = 1;
+  private int status_;
+  /**
+   * <code>.grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;</code>
+   */
+  public int getStatusValue() {
+    return status_;
+  }
+  /**
+   * <code>.grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;</code>
+   */
+  public io.grpc.health.v1.HealthCheckResponse.ServingStatus getStatus() {
+    io.grpc.health.v1.HealthCheckResponse.ServingStatus result = io.grpc.health.v1.HealthCheckResponse.ServingStatus.valueOf(status_);
+    return result == null ? io.grpc.health.v1.HealthCheckResponse.ServingStatus.UNRECOGNIZED : result;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (status_ != io.grpc.health.v1.HealthCheckResponse.ServingStatus.UNKNOWN.getNumber()) {
+      output.writeEnum(1, status_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (status_ != io.grpc.health.v1.HealthCheckResponse.ServingStatus.UNKNOWN.getNumber()) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeEnumSize(1, status_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.health.v1.HealthCheckResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.health.v1.HealthCheckResponse other = (io.grpc.health.v1.HealthCheckResponse) obj;
+
+    boolean result = true;
+    result = result && status_ == other.status_;
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + STATUS_FIELD_NUMBER;
+    hash = (53 * hash) + status_;
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.health.v1.HealthCheckResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.health.v1.HealthCheckResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.health.v1.HealthCheckResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.health.v1.HealthCheckResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.health.v1.HealthCheckResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.health.v1.HealthCheckResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.health.v1.HealthCheckResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.health.v1.HealthCheckResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.health.v1.HealthCheckResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.health.v1.HealthCheckResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.health.v1.HealthCheckResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.health.v1.HealthCheckResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.health.v1.HealthCheckResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.health.v1.HealthCheckResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.health.v1.HealthCheckResponse)
+      io.grpc.health.v1.HealthCheckResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.health.v1.HealthProto.internal_static_grpc_health_v1_HealthCheckResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.health.v1.HealthProto.internal_static_grpc_health_v1_HealthCheckResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.health.v1.HealthCheckResponse.class, io.grpc.health.v1.HealthCheckResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.health.v1.HealthCheckResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      status_ = 0;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.health.v1.HealthProto.internal_static_grpc_health_v1_HealthCheckResponse_descriptor;
+    }
+
+    public io.grpc.health.v1.HealthCheckResponse getDefaultInstanceForType() {
+      return io.grpc.health.v1.HealthCheckResponse.getDefaultInstance();
+    }
+
+    public io.grpc.health.v1.HealthCheckResponse build() {
+      io.grpc.health.v1.HealthCheckResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.health.v1.HealthCheckResponse buildPartial() {
+      io.grpc.health.v1.HealthCheckResponse result = new io.grpc.health.v1.HealthCheckResponse(this);
+      result.status_ = status_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.health.v1.HealthCheckResponse) {
+        return mergeFrom((io.grpc.health.v1.HealthCheckResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.health.v1.HealthCheckResponse other) {
+      if (other == io.grpc.health.v1.HealthCheckResponse.getDefaultInstance()) return this;
+      if (other.status_ != 0) {
+        setStatusValue(other.getStatusValue());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.health.v1.HealthCheckResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.health.v1.HealthCheckResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private int status_ = 0;
+    /**
+     * <code>.grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;</code>
+     */
+    public int getStatusValue() {
+      return status_;
+    }
+    /**
+     * <code>.grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;</code>
+     */
+    public Builder setStatusValue(int value) {
+      status_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;</code>
+     */
+    public io.grpc.health.v1.HealthCheckResponse.ServingStatus getStatus() {
+      io.grpc.health.v1.HealthCheckResponse.ServingStatus result = io.grpc.health.v1.HealthCheckResponse.ServingStatus.valueOf(status_);
+      return result == null ? io.grpc.health.v1.HealthCheckResponse.ServingStatus.UNRECOGNIZED : result;
+    }
+    /**
+     * <code>.grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;</code>
+     */
+    public Builder setStatus(io.grpc.health.v1.HealthCheckResponse.ServingStatus value) {
+      if (value == null) {
+        throw new NullPointerException();
+      }
+      
+      status_ = value.getNumber();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;</code>
+     */
+    public Builder clearStatus() {
+      
+      status_ = 0;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.health.v1.HealthCheckResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.health.v1.HealthCheckResponse)
+  private static final io.grpc.health.v1.HealthCheckResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.health.v1.HealthCheckResponse();
+  }
+
+  public static io.grpc.health.v1.HealthCheckResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<HealthCheckResponse>
+      PARSER = new com.google.protobuf.AbstractParser<HealthCheckResponse>() {
+    public HealthCheckResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new HealthCheckResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<HealthCheckResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<HealthCheckResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.health.v1.HealthCheckResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/health/v1/HealthCheckResponseOrBuilder.java b/services/src/generated/main/java/io/grpc/health/v1/HealthCheckResponseOrBuilder.java
new file mode 100644
index 0000000..1479a08
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/health/v1/HealthCheckResponseOrBuilder.java
@@ -0,0 +1,18 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/health/v1/health.proto
+
+package io.grpc.health.v1;
+
+public interface HealthCheckResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.health.v1.HealthCheckResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>.grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;</code>
+   */
+  int getStatusValue();
+  /**
+   * <code>.grpc.health.v1.HealthCheckResponse.ServingStatus status = 1;</code>
+   */
+  io.grpc.health.v1.HealthCheckResponse.ServingStatus getStatus();
+}
diff --git a/services/src/generated/main/java/io/grpc/health/v1/HealthProto.java b/services/src/generated/main/java/io/grpc/health/v1/HealthProto.java
new file mode 100644
index 0000000..ce0d1bd
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/health/v1/HealthProto.java
@@ -0,0 +1,74 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: grpc/health/v1/health.proto
+
+package io.grpc.health.v1;
+
+public final class HealthProto {
+  private HealthProto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_health_v1_HealthCheckRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_health_v1_HealthCheckRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_health_v1_HealthCheckResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_health_v1_HealthCheckResponse_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\033grpc/health/v1/health.proto\022\016grpc.heal" +
+      "th.v1\"%\n\022HealthCheckRequest\022\017\n\007service\030\001" +
+      " \001(\t\"\224\001\n\023HealthCheckResponse\022A\n\006status\030\001" +
+      " \001(\01621.grpc.health.v1.HealthCheckRespons" +
+      "e.ServingStatus\":\n\rServingStatus\022\013\n\007UNKN" +
+      "OWN\020\000\022\013\n\007SERVING\020\001\022\017\n\013NOT_SERVING\020\0022Z\n\006H" +
+      "ealth\022P\n\005Check\022\".grpc.health.v1.HealthCh" +
+      "eckRequest\032#.grpc.health.v1.HealthCheckR" +
+      "esponseB3\n\021io.grpc.health.v1B\013HealthProt" +
+      "oP\001\252\002\016Grpc.Health.V1b\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        }, assigner);
+    internal_static_grpc_health_v1_HealthCheckRequest_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_health_v1_HealthCheckRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_health_v1_HealthCheckRequest_descriptor,
+        new java.lang.String[] { "Service", });
+    internal_static_grpc_health_v1_HealthCheckResponse_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_health_v1_HealthCheckResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_health_v1_HealthCheckResponse_descriptor,
+        new java.lang.String[] { "Status", });
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ErrorResponse.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ErrorResponse.java
new file mode 100644
index 0000000..5bfe392
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ErrorResponse.java
@@ -0,0 +1,598 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+/**
+ * <pre>
+ * The error code and error message sent by the server when an error occurs.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.reflection.v1alpha.ErrorResponse}
+ */
+public  final class ErrorResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.v1alpha.ErrorResponse)
+    ErrorResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ErrorResponse.newBuilder() to construct.
+  private ErrorResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ErrorResponse() {
+    errorCode_ = 0;
+    errorMessage_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ErrorResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 8: {
+
+            errorCode_ = input.readInt32();
+            break;
+          }
+          case 18: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            errorMessage_ = s;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ErrorResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ErrorResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.v1alpha.ErrorResponse.class, io.grpc.reflection.v1alpha.ErrorResponse.Builder.class);
+  }
+
+  public static final int ERROR_CODE_FIELD_NUMBER = 1;
+  private int errorCode_;
+  /**
+   * <pre>
+   * This field uses the error codes defined in grpc::StatusCode.
+   * </pre>
+   *
+   * <code>int32 error_code = 1;</code>
+   */
+  public int getErrorCode() {
+    return errorCode_;
+  }
+
+  public static final int ERROR_MESSAGE_FIELD_NUMBER = 2;
+  private volatile java.lang.Object errorMessage_;
+  /**
+   * <code>string error_message = 2;</code>
+   */
+  public java.lang.String getErrorMessage() {
+    java.lang.Object ref = errorMessage_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      errorMessage_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string error_message = 2;</code>
+   */
+  public com.google.protobuf.ByteString
+      getErrorMessageBytes() {
+    java.lang.Object ref = errorMessage_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      errorMessage_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (errorCode_ != 0) {
+      output.writeInt32(1, errorCode_);
+    }
+    if (!getErrorMessageBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 2, errorMessage_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (errorCode_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt32Size(1, errorCode_);
+    }
+    if (!getErrorMessageBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, errorMessage_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.v1alpha.ErrorResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.v1alpha.ErrorResponse other = (io.grpc.reflection.v1alpha.ErrorResponse) obj;
+
+    boolean result = true;
+    result = result && (getErrorCode()
+        == other.getErrorCode());
+    result = result && getErrorMessage()
+        .equals(other.getErrorMessage());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + ERROR_CODE_FIELD_NUMBER;
+    hash = (53 * hash) + getErrorCode();
+    hash = (37 * hash) + ERROR_MESSAGE_FIELD_NUMBER;
+    hash = (53 * hash) + getErrorMessage().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.v1alpha.ErrorResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ErrorResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ErrorResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ErrorResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ErrorResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ErrorResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ErrorResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ErrorResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ErrorResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ErrorResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ErrorResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ErrorResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.v1alpha.ErrorResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * The error code and error message sent by the server when an error occurs.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.reflection.v1alpha.ErrorResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.v1alpha.ErrorResponse)
+      io.grpc.reflection.v1alpha.ErrorResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ErrorResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ErrorResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.v1alpha.ErrorResponse.class, io.grpc.reflection.v1alpha.ErrorResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.v1alpha.ErrorResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      errorCode_ = 0;
+
+      errorMessage_ = "";
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ErrorResponse_descriptor;
+    }
+
+    public io.grpc.reflection.v1alpha.ErrorResponse getDefaultInstanceForType() {
+      return io.grpc.reflection.v1alpha.ErrorResponse.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.v1alpha.ErrorResponse build() {
+      io.grpc.reflection.v1alpha.ErrorResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.v1alpha.ErrorResponse buildPartial() {
+      io.grpc.reflection.v1alpha.ErrorResponse result = new io.grpc.reflection.v1alpha.ErrorResponse(this);
+      result.errorCode_ = errorCode_;
+      result.errorMessage_ = errorMessage_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.v1alpha.ErrorResponse) {
+        return mergeFrom((io.grpc.reflection.v1alpha.ErrorResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.v1alpha.ErrorResponse other) {
+      if (other == io.grpc.reflection.v1alpha.ErrorResponse.getDefaultInstance()) return this;
+      if (other.getErrorCode() != 0) {
+        setErrorCode(other.getErrorCode());
+      }
+      if (!other.getErrorMessage().isEmpty()) {
+        errorMessage_ = other.errorMessage_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.v1alpha.ErrorResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.v1alpha.ErrorResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private int errorCode_ ;
+    /**
+     * <pre>
+     * This field uses the error codes defined in grpc::StatusCode.
+     * </pre>
+     *
+     * <code>int32 error_code = 1;</code>
+     */
+    public int getErrorCode() {
+      return errorCode_;
+    }
+    /**
+     * <pre>
+     * This field uses the error codes defined in grpc::StatusCode.
+     * </pre>
+     *
+     * <code>int32 error_code = 1;</code>
+     */
+    public Builder setErrorCode(int value) {
+      
+      errorCode_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * This field uses the error codes defined in grpc::StatusCode.
+     * </pre>
+     *
+     * <code>int32 error_code = 1;</code>
+     */
+    public Builder clearErrorCode() {
+      
+      errorCode_ = 0;
+      onChanged();
+      return this;
+    }
+
+    private java.lang.Object errorMessage_ = "";
+    /**
+     * <code>string error_message = 2;</code>
+     */
+    public java.lang.String getErrorMessage() {
+      java.lang.Object ref = errorMessage_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        errorMessage_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string error_message = 2;</code>
+     */
+    public com.google.protobuf.ByteString
+        getErrorMessageBytes() {
+      java.lang.Object ref = errorMessage_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        errorMessage_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string error_message = 2;</code>
+     */
+    public Builder setErrorMessage(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      errorMessage_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string error_message = 2;</code>
+     */
+    public Builder clearErrorMessage() {
+      
+      errorMessage_ = getDefaultInstance().getErrorMessage();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string error_message = 2;</code>
+     */
+    public Builder setErrorMessageBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      errorMessage_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.v1alpha.ErrorResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.v1alpha.ErrorResponse)
+  private static final io.grpc.reflection.v1alpha.ErrorResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.v1alpha.ErrorResponse();
+  }
+
+  public static io.grpc.reflection.v1alpha.ErrorResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ErrorResponse>
+      PARSER = new com.google.protobuf.AbstractParser<ErrorResponse>() {
+    public ErrorResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ErrorResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ErrorResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ErrorResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.v1alpha.ErrorResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ErrorResponseOrBuilder.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ErrorResponseOrBuilder.java
new file mode 100644
index 0000000..f531686
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ErrorResponseOrBuilder.java
@@ -0,0 +1,28 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+public interface ErrorResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.v1alpha.ErrorResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * This field uses the error codes defined in grpc::StatusCode.
+   * </pre>
+   *
+   * <code>int32 error_code = 1;</code>
+   */
+  int getErrorCode();
+
+  /**
+   * <code>string error_message = 2;</code>
+   */
+  java.lang.String getErrorMessage();
+  /**
+   * <code>string error_message = 2;</code>
+   */
+  com.google.protobuf.ByteString
+      getErrorMessageBytes();
+}
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ExtensionNumberResponse.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ExtensionNumberResponse.java
new file mode 100644
index 0000000..8d982e5
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ExtensionNumberResponse.java
@@ -0,0 +1,725 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+/**
+ * <pre>
+ * A list of extension numbers sent by the server answering
+ * all_extension_numbers_of_type request.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.reflection.v1alpha.ExtensionNumberResponse}
+ */
+public  final class ExtensionNumberResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.v1alpha.ExtensionNumberResponse)
+    ExtensionNumberResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ExtensionNumberResponse.newBuilder() to construct.
+  private ExtensionNumberResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ExtensionNumberResponse() {
+    baseTypeName_ = "";
+    extensionNumber_ = java.util.Collections.emptyList();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ExtensionNumberResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            baseTypeName_ = s;
+            break;
+          }
+          case 16: {
+            if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+              extensionNumber_ = new java.util.ArrayList<java.lang.Integer>();
+              mutable_bitField0_ |= 0x00000002;
+            }
+            extensionNumber_.add(input.readInt32());
+            break;
+          }
+          case 18: {
+            int length = input.readRawVarint32();
+            int limit = input.pushLimit(length);
+            if (!((mutable_bitField0_ & 0x00000002) == 0x00000002) && input.getBytesUntilLimit() > 0) {
+              extensionNumber_ = new java.util.ArrayList<java.lang.Integer>();
+              mutable_bitField0_ |= 0x00000002;
+            }
+            while (input.getBytesUntilLimit() > 0) {
+              extensionNumber_.add(input.readInt32());
+            }
+            input.popLimit(limit);
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) {
+        extensionNumber_ = java.util.Collections.unmodifiableList(extensionNumber_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ExtensionNumberResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ExtensionNumberResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.v1alpha.ExtensionNumberResponse.class, io.grpc.reflection.v1alpha.ExtensionNumberResponse.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int BASE_TYPE_NAME_FIELD_NUMBER = 1;
+  private volatile java.lang.Object baseTypeName_;
+  /**
+   * <pre>
+   * Full name of the base type, including the package name. The format
+   * is &lt;package&gt;.&lt;type&gt;
+   * </pre>
+   *
+   * <code>string base_type_name = 1;</code>
+   */
+  public java.lang.String getBaseTypeName() {
+    java.lang.Object ref = baseTypeName_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      baseTypeName_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * Full name of the base type, including the package name. The format
+   * is &lt;package&gt;.&lt;type&gt;
+   * </pre>
+   *
+   * <code>string base_type_name = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getBaseTypeNameBytes() {
+    java.lang.Object ref = baseTypeName_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      baseTypeName_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int EXTENSION_NUMBER_FIELD_NUMBER = 2;
+  private java.util.List<java.lang.Integer> extensionNumber_;
+  /**
+   * <code>repeated int32 extension_number = 2;</code>
+   */
+  public java.util.List<java.lang.Integer>
+      getExtensionNumberList() {
+    return extensionNumber_;
+  }
+  /**
+   * <code>repeated int32 extension_number = 2;</code>
+   */
+  public int getExtensionNumberCount() {
+    return extensionNumber_.size();
+  }
+  /**
+   * <code>repeated int32 extension_number = 2;</code>
+   */
+  public int getExtensionNumber(int index) {
+    return extensionNumber_.get(index);
+  }
+  private int extensionNumberMemoizedSerializedSize = -1;
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    getSerializedSize();
+    if (!getBaseTypeNameBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, baseTypeName_);
+    }
+    if (getExtensionNumberList().size() > 0) {
+      output.writeUInt32NoTag(18);
+      output.writeUInt32NoTag(extensionNumberMemoizedSerializedSize);
+    }
+    for (int i = 0; i < extensionNumber_.size(); i++) {
+      output.writeInt32NoTag(extensionNumber_.get(i));
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getBaseTypeNameBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, baseTypeName_);
+    }
+    {
+      int dataSize = 0;
+      for (int i = 0; i < extensionNumber_.size(); i++) {
+        dataSize += com.google.protobuf.CodedOutputStream
+          .computeInt32SizeNoTag(extensionNumber_.get(i));
+      }
+      size += dataSize;
+      if (!getExtensionNumberList().isEmpty()) {
+        size += 1;
+        size += com.google.protobuf.CodedOutputStream
+            .computeInt32SizeNoTag(dataSize);
+      }
+      extensionNumberMemoizedSerializedSize = dataSize;
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.v1alpha.ExtensionNumberResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.v1alpha.ExtensionNumberResponse other = (io.grpc.reflection.v1alpha.ExtensionNumberResponse) obj;
+
+    boolean result = true;
+    result = result && getBaseTypeName()
+        .equals(other.getBaseTypeName());
+    result = result && getExtensionNumberList()
+        .equals(other.getExtensionNumberList());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + BASE_TYPE_NAME_FIELD_NUMBER;
+    hash = (53 * hash) + getBaseTypeName().hashCode();
+    if (getExtensionNumberCount() > 0) {
+      hash = (37 * hash) + EXTENSION_NUMBER_FIELD_NUMBER;
+      hash = (53 * hash) + getExtensionNumberList().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.v1alpha.ExtensionNumberResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionNumberResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionNumberResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionNumberResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionNumberResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionNumberResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionNumberResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionNumberResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionNumberResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionNumberResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionNumberResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionNumberResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.v1alpha.ExtensionNumberResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * A list of extension numbers sent by the server answering
+   * all_extension_numbers_of_type request.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.reflection.v1alpha.ExtensionNumberResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.v1alpha.ExtensionNumberResponse)
+      io.grpc.reflection.v1alpha.ExtensionNumberResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ExtensionNumberResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ExtensionNumberResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.v1alpha.ExtensionNumberResponse.class, io.grpc.reflection.v1alpha.ExtensionNumberResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.v1alpha.ExtensionNumberResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      baseTypeName_ = "";
+
+      extensionNumber_ = java.util.Collections.emptyList();
+      bitField0_ = (bitField0_ & ~0x00000002);
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ExtensionNumberResponse_descriptor;
+    }
+
+    public io.grpc.reflection.v1alpha.ExtensionNumberResponse getDefaultInstanceForType() {
+      return io.grpc.reflection.v1alpha.ExtensionNumberResponse.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.v1alpha.ExtensionNumberResponse build() {
+      io.grpc.reflection.v1alpha.ExtensionNumberResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.v1alpha.ExtensionNumberResponse buildPartial() {
+      io.grpc.reflection.v1alpha.ExtensionNumberResponse result = new io.grpc.reflection.v1alpha.ExtensionNumberResponse(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      result.baseTypeName_ = baseTypeName_;
+      if (((bitField0_ & 0x00000002) == 0x00000002)) {
+        extensionNumber_ = java.util.Collections.unmodifiableList(extensionNumber_);
+        bitField0_ = (bitField0_ & ~0x00000002);
+      }
+      result.extensionNumber_ = extensionNumber_;
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.v1alpha.ExtensionNumberResponse) {
+        return mergeFrom((io.grpc.reflection.v1alpha.ExtensionNumberResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.v1alpha.ExtensionNumberResponse other) {
+      if (other == io.grpc.reflection.v1alpha.ExtensionNumberResponse.getDefaultInstance()) return this;
+      if (!other.getBaseTypeName().isEmpty()) {
+        baseTypeName_ = other.baseTypeName_;
+        onChanged();
+      }
+      if (!other.extensionNumber_.isEmpty()) {
+        if (extensionNumber_.isEmpty()) {
+          extensionNumber_ = other.extensionNumber_;
+          bitField0_ = (bitField0_ & ~0x00000002);
+        } else {
+          ensureExtensionNumberIsMutable();
+          extensionNumber_.addAll(other.extensionNumber_);
+        }
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.v1alpha.ExtensionNumberResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.v1alpha.ExtensionNumberResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.lang.Object baseTypeName_ = "";
+    /**
+     * <pre>
+     * Full name of the base type, including the package name. The format
+     * is &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string base_type_name = 1;</code>
+     */
+    public java.lang.String getBaseTypeName() {
+      java.lang.Object ref = baseTypeName_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        baseTypeName_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Full name of the base type, including the package name. The format
+     * is &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string base_type_name = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getBaseTypeNameBytes() {
+      java.lang.Object ref = baseTypeName_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        baseTypeName_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Full name of the base type, including the package name. The format
+     * is &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string base_type_name = 1;</code>
+     */
+    public Builder setBaseTypeName(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      baseTypeName_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Full name of the base type, including the package name. The format
+     * is &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string base_type_name = 1;</code>
+     */
+    public Builder clearBaseTypeName() {
+      
+      baseTypeName_ = getDefaultInstance().getBaseTypeName();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Full name of the base type, including the package name. The format
+     * is &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string base_type_name = 1;</code>
+     */
+    public Builder setBaseTypeNameBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      baseTypeName_ = value;
+      onChanged();
+      return this;
+    }
+
+    private java.util.List<java.lang.Integer> extensionNumber_ = java.util.Collections.emptyList();
+    private void ensureExtensionNumberIsMutable() {
+      if (!((bitField0_ & 0x00000002) == 0x00000002)) {
+        extensionNumber_ = new java.util.ArrayList<java.lang.Integer>(extensionNumber_);
+        bitField0_ |= 0x00000002;
+       }
+    }
+    /**
+     * <code>repeated int32 extension_number = 2;</code>
+     */
+    public java.util.List<java.lang.Integer>
+        getExtensionNumberList() {
+      return java.util.Collections.unmodifiableList(extensionNumber_);
+    }
+    /**
+     * <code>repeated int32 extension_number = 2;</code>
+     */
+    public int getExtensionNumberCount() {
+      return extensionNumber_.size();
+    }
+    /**
+     * <code>repeated int32 extension_number = 2;</code>
+     */
+    public int getExtensionNumber(int index) {
+      return extensionNumber_.get(index);
+    }
+    /**
+     * <code>repeated int32 extension_number = 2;</code>
+     */
+    public Builder setExtensionNumber(
+        int index, int value) {
+      ensureExtensionNumberIsMutable();
+      extensionNumber_.set(index, value);
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>repeated int32 extension_number = 2;</code>
+     */
+    public Builder addExtensionNumber(int value) {
+      ensureExtensionNumberIsMutable();
+      extensionNumber_.add(value);
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>repeated int32 extension_number = 2;</code>
+     */
+    public Builder addAllExtensionNumber(
+        java.lang.Iterable<? extends java.lang.Integer> values) {
+      ensureExtensionNumberIsMutable();
+      com.google.protobuf.AbstractMessageLite.Builder.addAll(
+          values, extensionNumber_);
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>repeated int32 extension_number = 2;</code>
+     */
+    public Builder clearExtensionNumber() {
+      extensionNumber_ = java.util.Collections.emptyList();
+      bitField0_ = (bitField0_ & ~0x00000002);
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.v1alpha.ExtensionNumberResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.v1alpha.ExtensionNumberResponse)
+  private static final io.grpc.reflection.v1alpha.ExtensionNumberResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.v1alpha.ExtensionNumberResponse();
+  }
+
+  public static io.grpc.reflection.v1alpha.ExtensionNumberResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ExtensionNumberResponse>
+      PARSER = new com.google.protobuf.AbstractParser<ExtensionNumberResponse>() {
+    public ExtensionNumberResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ExtensionNumberResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ExtensionNumberResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ExtensionNumberResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.v1alpha.ExtensionNumberResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ExtensionNumberResponseOrBuilder.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ExtensionNumberResponseOrBuilder.java
new file mode 100644
index 0000000..a979579
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ExtensionNumberResponseOrBuilder.java
@@ -0,0 +1,42 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+public interface ExtensionNumberResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.v1alpha.ExtensionNumberResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * Full name of the base type, including the package name. The format
+   * is &lt;package&gt;.&lt;type&gt;
+   * </pre>
+   *
+   * <code>string base_type_name = 1;</code>
+   */
+  java.lang.String getBaseTypeName();
+  /**
+   * <pre>
+   * Full name of the base type, including the package name. The format
+   * is &lt;package&gt;.&lt;type&gt;
+   * </pre>
+   *
+   * <code>string base_type_name = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getBaseTypeNameBytes();
+
+  /**
+   * <code>repeated int32 extension_number = 2;</code>
+   */
+  java.util.List<java.lang.Integer> getExtensionNumberList();
+  /**
+   * <code>repeated int32 extension_number = 2;</code>
+   */
+  int getExtensionNumberCount();
+  /**
+   * <code>repeated int32 extension_number = 2;</code>
+   */
+  int getExtensionNumber(int index);
+}
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ExtensionRequest.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ExtensionRequest.java
new file mode 100644
index 0000000..4f49b8e
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ExtensionRequest.java
@@ -0,0 +1,612 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+/**
+ * <pre>
+ * The type name and extension number sent by the client when requesting
+ * file_containing_extension.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.reflection.v1alpha.ExtensionRequest}
+ */
+public  final class ExtensionRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.v1alpha.ExtensionRequest)
+    ExtensionRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ExtensionRequest.newBuilder() to construct.
+  private ExtensionRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ExtensionRequest() {
+    containingType_ = "";
+    extensionNumber_ = 0;
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ExtensionRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            containingType_ = s;
+            break;
+          }
+          case 16: {
+
+            extensionNumber_ = input.readInt32();
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ExtensionRequest_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ExtensionRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.v1alpha.ExtensionRequest.class, io.grpc.reflection.v1alpha.ExtensionRequest.Builder.class);
+  }
+
+  public static final int CONTAINING_TYPE_FIELD_NUMBER = 1;
+  private volatile java.lang.Object containingType_;
+  /**
+   * <pre>
+   * Fully-qualified type name. The format should be &lt;package&gt;.&lt;type&gt;
+   * </pre>
+   *
+   * <code>string containing_type = 1;</code>
+   */
+  public java.lang.String getContainingType() {
+    java.lang.Object ref = containingType_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      containingType_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * Fully-qualified type name. The format should be &lt;package&gt;.&lt;type&gt;
+   * </pre>
+   *
+   * <code>string containing_type = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getContainingTypeBytes() {
+    java.lang.Object ref = containingType_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      containingType_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int EXTENSION_NUMBER_FIELD_NUMBER = 2;
+  private int extensionNumber_;
+  /**
+   * <code>int32 extension_number = 2;</code>
+   */
+  public int getExtensionNumber() {
+    return extensionNumber_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getContainingTypeBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, containingType_);
+    }
+    if (extensionNumber_ != 0) {
+      output.writeInt32(2, extensionNumber_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getContainingTypeBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, containingType_);
+    }
+    if (extensionNumber_ != 0) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeInt32Size(2, extensionNumber_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.v1alpha.ExtensionRequest)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.v1alpha.ExtensionRequest other = (io.grpc.reflection.v1alpha.ExtensionRequest) obj;
+
+    boolean result = true;
+    result = result && getContainingType()
+        .equals(other.getContainingType());
+    result = result && (getExtensionNumber()
+        == other.getExtensionNumber());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + CONTAINING_TYPE_FIELD_NUMBER;
+    hash = (53 * hash) + getContainingType().hashCode();
+    hash = (37 * hash) + EXTENSION_NUMBER_FIELD_NUMBER;
+    hash = (53 * hash) + getExtensionNumber();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.v1alpha.ExtensionRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ExtensionRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.v1alpha.ExtensionRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * The type name and extension number sent by the client when requesting
+   * file_containing_extension.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.reflection.v1alpha.ExtensionRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.v1alpha.ExtensionRequest)
+      io.grpc.reflection.v1alpha.ExtensionRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ExtensionRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ExtensionRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.v1alpha.ExtensionRequest.class, io.grpc.reflection.v1alpha.ExtensionRequest.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.v1alpha.ExtensionRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      containingType_ = "";
+
+      extensionNumber_ = 0;
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ExtensionRequest_descriptor;
+    }
+
+    public io.grpc.reflection.v1alpha.ExtensionRequest getDefaultInstanceForType() {
+      return io.grpc.reflection.v1alpha.ExtensionRequest.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.v1alpha.ExtensionRequest build() {
+      io.grpc.reflection.v1alpha.ExtensionRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.v1alpha.ExtensionRequest buildPartial() {
+      io.grpc.reflection.v1alpha.ExtensionRequest result = new io.grpc.reflection.v1alpha.ExtensionRequest(this);
+      result.containingType_ = containingType_;
+      result.extensionNumber_ = extensionNumber_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.v1alpha.ExtensionRequest) {
+        return mergeFrom((io.grpc.reflection.v1alpha.ExtensionRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.v1alpha.ExtensionRequest other) {
+      if (other == io.grpc.reflection.v1alpha.ExtensionRequest.getDefaultInstance()) return this;
+      if (!other.getContainingType().isEmpty()) {
+        containingType_ = other.containingType_;
+        onChanged();
+      }
+      if (other.getExtensionNumber() != 0) {
+        setExtensionNumber(other.getExtensionNumber());
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.v1alpha.ExtensionRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.v1alpha.ExtensionRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private java.lang.Object containingType_ = "";
+    /**
+     * <pre>
+     * Fully-qualified type name. The format should be &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string containing_type = 1;</code>
+     */
+    public java.lang.String getContainingType() {
+      java.lang.Object ref = containingType_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        containingType_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Fully-qualified type name. The format should be &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string containing_type = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getContainingTypeBytes() {
+      java.lang.Object ref = containingType_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        containingType_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Fully-qualified type name. The format should be &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string containing_type = 1;</code>
+     */
+    public Builder setContainingType(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      containingType_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Fully-qualified type name. The format should be &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string containing_type = 1;</code>
+     */
+    public Builder clearContainingType() {
+      
+      containingType_ = getDefaultInstance().getContainingType();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Fully-qualified type name. The format should be &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string containing_type = 1;</code>
+     */
+    public Builder setContainingTypeBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      containingType_ = value;
+      onChanged();
+      return this;
+    }
+
+    private int extensionNumber_ ;
+    /**
+     * <code>int32 extension_number = 2;</code>
+     */
+    public int getExtensionNumber() {
+      return extensionNumber_;
+    }
+    /**
+     * <code>int32 extension_number = 2;</code>
+     */
+    public Builder setExtensionNumber(int value) {
+      
+      extensionNumber_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>int32 extension_number = 2;</code>
+     */
+    public Builder clearExtensionNumber() {
+      
+      extensionNumber_ = 0;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.v1alpha.ExtensionRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.v1alpha.ExtensionRequest)
+  private static final io.grpc.reflection.v1alpha.ExtensionRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.v1alpha.ExtensionRequest();
+  }
+
+  public static io.grpc.reflection.v1alpha.ExtensionRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ExtensionRequest>
+      PARSER = new com.google.protobuf.AbstractParser<ExtensionRequest>() {
+    public ExtensionRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ExtensionRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ExtensionRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ExtensionRequest> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.v1alpha.ExtensionRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ExtensionRequestOrBuilder.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ExtensionRequestOrBuilder.java
new file mode 100644
index 0000000..d49e3d9
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ExtensionRequestOrBuilder.java
@@ -0,0 +1,32 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+public interface ExtensionRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.v1alpha.ExtensionRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * Fully-qualified type name. The format should be &lt;package&gt;.&lt;type&gt;
+   * </pre>
+   *
+   * <code>string containing_type = 1;</code>
+   */
+  java.lang.String getContainingType();
+  /**
+   * <pre>
+   * Fully-qualified type name. The format should be &lt;package&gt;.&lt;type&gt;
+   * </pre>
+   *
+   * <code>string containing_type = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getContainingTypeBytes();
+
+  /**
+   * <code>int32 extension_number = 2;</code>
+   */
+  int getExtensionNumber();
+}
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/FileDescriptorResponse.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/FileDescriptorResponse.java
new file mode 100644
index 0000000..ea66a2b
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/FileDescriptorResponse.java
@@ -0,0 +1,604 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+/**
+ * <pre>
+ * Serialized FileDescriptorProto messages sent by the server answering
+ * a file_by_filename, file_containing_symbol, or file_containing_extension
+ * request.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.reflection.v1alpha.FileDescriptorResponse}
+ */
+public  final class FileDescriptorResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.v1alpha.FileDescriptorResponse)
+    FileDescriptorResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use FileDescriptorResponse.newBuilder() to construct.
+  private FileDescriptorResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private FileDescriptorResponse() {
+    fileDescriptorProto_ = java.util.Collections.emptyList();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private FileDescriptorResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+              fileDescriptorProto_ = new java.util.ArrayList<com.google.protobuf.ByteString>();
+              mutable_bitField0_ |= 0x00000001;
+            }
+            fileDescriptorProto_.add(input.readBytes());
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+        fileDescriptorProto_ = java.util.Collections.unmodifiableList(fileDescriptorProto_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_FileDescriptorResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_FileDescriptorResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.v1alpha.FileDescriptorResponse.class, io.grpc.reflection.v1alpha.FileDescriptorResponse.Builder.class);
+  }
+
+  public static final int FILE_DESCRIPTOR_PROTO_FIELD_NUMBER = 1;
+  private java.util.List<com.google.protobuf.ByteString> fileDescriptorProto_;
+  /**
+   * <pre>
+   * Serialized FileDescriptorProto messages. We avoid taking a dependency on
+   * descriptor.proto, which uses proto2 only features, by making them opaque
+   * bytes instead.
+   * </pre>
+   *
+   * <code>repeated bytes file_descriptor_proto = 1;</code>
+   */
+  public java.util.List<com.google.protobuf.ByteString>
+      getFileDescriptorProtoList() {
+    return fileDescriptorProto_;
+  }
+  /**
+   * <pre>
+   * Serialized FileDescriptorProto messages. We avoid taking a dependency on
+   * descriptor.proto, which uses proto2 only features, by making them opaque
+   * bytes instead.
+   * </pre>
+   *
+   * <code>repeated bytes file_descriptor_proto = 1;</code>
+   */
+  public int getFileDescriptorProtoCount() {
+    return fileDescriptorProto_.size();
+  }
+  /**
+   * <pre>
+   * Serialized FileDescriptorProto messages. We avoid taking a dependency on
+   * descriptor.proto, which uses proto2 only features, by making them opaque
+   * bytes instead.
+   * </pre>
+   *
+   * <code>repeated bytes file_descriptor_proto = 1;</code>
+   */
+  public com.google.protobuf.ByteString getFileDescriptorProto(int index) {
+    return fileDescriptorProto_.get(index);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    for (int i = 0; i < fileDescriptorProto_.size(); i++) {
+      output.writeBytes(1, fileDescriptorProto_.get(i));
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    {
+      int dataSize = 0;
+      for (int i = 0; i < fileDescriptorProto_.size(); i++) {
+        dataSize += com.google.protobuf.CodedOutputStream
+          .computeBytesSizeNoTag(fileDescriptorProto_.get(i));
+      }
+      size += dataSize;
+      size += 1 * getFileDescriptorProtoList().size();
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.v1alpha.FileDescriptorResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.v1alpha.FileDescriptorResponse other = (io.grpc.reflection.v1alpha.FileDescriptorResponse) obj;
+
+    boolean result = true;
+    result = result && getFileDescriptorProtoList()
+        .equals(other.getFileDescriptorProtoList());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (getFileDescriptorProtoCount() > 0) {
+      hash = (37 * hash) + FILE_DESCRIPTOR_PROTO_FIELD_NUMBER;
+      hash = (53 * hash) + getFileDescriptorProtoList().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.v1alpha.FileDescriptorResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.FileDescriptorResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.FileDescriptorResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.FileDescriptorResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.FileDescriptorResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.FileDescriptorResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.FileDescriptorResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.FileDescriptorResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.FileDescriptorResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.FileDescriptorResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.FileDescriptorResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.FileDescriptorResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.v1alpha.FileDescriptorResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * Serialized FileDescriptorProto messages sent by the server answering
+   * a file_by_filename, file_containing_symbol, or file_containing_extension
+   * request.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.reflection.v1alpha.FileDescriptorResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.v1alpha.FileDescriptorResponse)
+      io.grpc.reflection.v1alpha.FileDescriptorResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_FileDescriptorResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_FileDescriptorResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.v1alpha.FileDescriptorResponse.class, io.grpc.reflection.v1alpha.FileDescriptorResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.v1alpha.FileDescriptorResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      fileDescriptorProto_ = java.util.Collections.emptyList();
+      bitField0_ = (bitField0_ & ~0x00000001);
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_FileDescriptorResponse_descriptor;
+    }
+
+    public io.grpc.reflection.v1alpha.FileDescriptorResponse getDefaultInstanceForType() {
+      return io.grpc.reflection.v1alpha.FileDescriptorResponse.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.v1alpha.FileDescriptorResponse build() {
+      io.grpc.reflection.v1alpha.FileDescriptorResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.v1alpha.FileDescriptorResponse buildPartial() {
+      io.grpc.reflection.v1alpha.FileDescriptorResponse result = new io.grpc.reflection.v1alpha.FileDescriptorResponse(this);
+      int from_bitField0_ = bitField0_;
+      if (((bitField0_ & 0x00000001) == 0x00000001)) {
+        fileDescriptorProto_ = java.util.Collections.unmodifiableList(fileDescriptorProto_);
+        bitField0_ = (bitField0_ & ~0x00000001);
+      }
+      result.fileDescriptorProto_ = fileDescriptorProto_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.v1alpha.FileDescriptorResponse) {
+        return mergeFrom((io.grpc.reflection.v1alpha.FileDescriptorResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.v1alpha.FileDescriptorResponse other) {
+      if (other == io.grpc.reflection.v1alpha.FileDescriptorResponse.getDefaultInstance()) return this;
+      if (!other.fileDescriptorProto_.isEmpty()) {
+        if (fileDescriptorProto_.isEmpty()) {
+          fileDescriptorProto_ = other.fileDescriptorProto_;
+          bitField0_ = (bitField0_ & ~0x00000001);
+        } else {
+          ensureFileDescriptorProtoIsMutable();
+          fileDescriptorProto_.addAll(other.fileDescriptorProto_);
+        }
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.v1alpha.FileDescriptorResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.v1alpha.FileDescriptorResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.util.List<com.google.protobuf.ByteString> fileDescriptorProto_ = java.util.Collections.emptyList();
+    private void ensureFileDescriptorProtoIsMutable() {
+      if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+        fileDescriptorProto_ = new java.util.ArrayList<com.google.protobuf.ByteString>(fileDescriptorProto_);
+        bitField0_ |= 0x00000001;
+       }
+    }
+    /**
+     * <pre>
+     * Serialized FileDescriptorProto messages. We avoid taking a dependency on
+     * descriptor.proto, which uses proto2 only features, by making them opaque
+     * bytes instead.
+     * </pre>
+     *
+     * <code>repeated bytes file_descriptor_proto = 1;</code>
+     */
+    public java.util.List<com.google.protobuf.ByteString>
+        getFileDescriptorProtoList() {
+      return java.util.Collections.unmodifiableList(fileDescriptorProto_);
+    }
+    /**
+     * <pre>
+     * Serialized FileDescriptorProto messages. We avoid taking a dependency on
+     * descriptor.proto, which uses proto2 only features, by making them opaque
+     * bytes instead.
+     * </pre>
+     *
+     * <code>repeated bytes file_descriptor_proto = 1;</code>
+     */
+    public int getFileDescriptorProtoCount() {
+      return fileDescriptorProto_.size();
+    }
+    /**
+     * <pre>
+     * Serialized FileDescriptorProto messages. We avoid taking a dependency on
+     * descriptor.proto, which uses proto2 only features, by making them opaque
+     * bytes instead.
+     * </pre>
+     *
+     * <code>repeated bytes file_descriptor_proto = 1;</code>
+     */
+    public com.google.protobuf.ByteString getFileDescriptorProto(int index) {
+      return fileDescriptorProto_.get(index);
+    }
+    /**
+     * <pre>
+     * Serialized FileDescriptorProto messages. We avoid taking a dependency on
+     * descriptor.proto, which uses proto2 only features, by making them opaque
+     * bytes instead.
+     * </pre>
+     *
+     * <code>repeated bytes file_descriptor_proto = 1;</code>
+     */
+    public Builder setFileDescriptorProto(
+        int index, com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureFileDescriptorProtoIsMutable();
+      fileDescriptorProto_.set(index, value);
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Serialized FileDescriptorProto messages. We avoid taking a dependency on
+     * descriptor.proto, which uses proto2 only features, by making them opaque
+     * bytes instead.
+     * </pre>
+     *
+     * <code>repeated bytes file_descriptor_proto = 1;</code>
+     */
+    public Builder addFileDescriptorProto(com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  ensureFileDescriptorProtoIsMutable();
+      fileDescriptorProto_.add(value);
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Serialized FileDescriptorProto messages. We avoid taking a dependency on
+     * descriptor.proto, which uses proto2 only features, by making them opaque
+     * bytes instead.
+     * </pre>
+     *
+     * <code>repeated bytes file_descriptor_proto = 1;</code>
+     */
+    public Builder addAllFileDescriptorProto(
+        java.lang.Iterable<? extends com.google.protobuf.ByteString> values) {
+      ensureFileDescriptorProtoIsMutable();
+      com.google.protobuf.AbstractMessageLite.Builder.addAll(
+          values, fileDescriptorProto_);
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Serialized FileDescriptorProto messages. We avoid taking a dependency on
+     * descriptor.proto, which uses proto2 only features, by making them opaque
+     * bytes instead.
+     * </pre>
+     *
+     * <code>repeated bytes file_descriptor_proto = 1;</code>
+     */
+    public Builder clearFileDescriptorProto() {
+      fileDescriptorProto_ = java.util.Collections.emptyList();
+      bitField0_ = (bitField0_ & ~0x00000001);
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.v1alpha.FileDescriptorResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.v1alpha.FileDescriptorResponse)
+  private static final io.grpc.reflection.v1alpha.FileDescriptorResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.v1alpha.FileDescriptorResponse();
+  }
+
+  public static io.grpc.reflection.v1alpha.FileDescriptorResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<FileDescriptorResponse>
+      PARSER = new com.google.protobuf.AbstractParser<FileDescriptorResponse>() {
+    public FileDescriptorResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new FileDescriptorResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<FileDescriptorResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<FileDescriptorResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.v1alpha.FileDescriptorResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/FileDescriptorResponseOrBuilder.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/FileDescriptorResponseOrBuilder.java
new file mode 100644
index 0000000..82a69d7
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/FileDescriptorResponseOrBuilder.java
@@ -0,0 +1,40 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+public interface FileDescriptorResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.v1alpha.FileDescriptorResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * Serialized FileDescriptorProto messages. We avoid taking a dependency on
+   * descriptor.proto, which uses proto2 only features, by making them opaque
+   * bytes instead.
+   * </pre>
+   *
+   * <code>repeated bytes file_descriptor_proto = 1;</code>
+   */
+  java.util.List<com.google.protobuf.ByteString> getFileDescriptorProtoList();
+  /**
+   * <pre>
+   * Serialized FileDescriptorProto messages. We avoid taking a dependency on
+   * descriptor.proto, which uses proto2 only features, by making them opaque
+   * bytes instead.
+   * </pre>
+   *
+   * <code>repeated bytes file_descriptor_proto = 1;</code>
+   */
+  int getFileDescriptorProtoCount();
+  /**
+   * <pre>
+   * Serialized FileDescriptorProto messages. We avoid taking a dependency on
+   * descriptor.proto, which uses proto2 only features, by making them opaque
+   * bytes instead.
+   * </pre>
+   *
+   * <code>repeated bytes file_descriptor_proto = 1;</code>
+   */
+  com.google.protobuf.ByteString getFileDescriptorProto(int index);
+}
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ListServiceResponse.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ListServiceResponse.java
new file mode 100644
index 0000000..0692e88
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ListServiceResponse.java
@@ -0,0 +1,857 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+/**
+ * <pre>
+ * A list of ServiceResponse sent by the server answering list_services request.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.reflection.v1alpha.ListServiceResponse}
+ */
+public  final class ListServiceResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.v1alpha.ListServiceResponse)
+    ListServiceResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ListServiceResponse.newBuilder() to construct.
+  private ListServiceResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ListServiceResponse() {
+    service_ = java.util.Collections.emptyList();
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ListServiceResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+              service_ = new java.util.ArrayList<io.grpc.reflection.v1alpha.ServiceResponse>();
+              mutable_bitField0_ |= 0x00000001;
+            }
+            service_.add(
+                input.readMessage(io.grpc.reflection.v1alpha.ServiceResponse.parser(), extensionRegistry));
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) {
+        service_ = java.util.Collections.unmodifiableList(service_);
+      }
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ListServiceResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ListServiceResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.v1alpha.ListServiceResponse.class, io.grpc.reflection.v1alpha.ListServiceResponse.Builder.class);
+  }
+
+  public static final int SERVICE_FIELD_NUMBER = 1;
+  private java.util.List<io.grpc.reflection.v1alpha.ServiceResponse> service_;
+  /**
+   * <pre>
+   * The information of each service may be expanded in the future, so we use
+   * ServiceResponse message to encapsulate it.
+   * </pre>
+   *
+   * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+   */
+  public java.util.List<io.grpc.reflection.v1alpha.ServiceResponse> getServiceList() {
+    return service_;
+  }
+  /**
+   * <pre>
+   * The information of each service may be expanded in the future, so we use
+   * ServiceResponse message to encapsulate it.
+   * </pre>
+   *
+   * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+   */
+  public java.util.List<? extends io.grpc.reflection.v1alpha.ServiceResponseOrBuilder> 
+      getServiceOrBuilderList() {
+    return service_;
+  }
+  /**
+   * <pre>
+   * The information of each service may be expanded in the future, so we use
+   * ServiceResponse message to encapsulate it.
+   * </pre>
+   *
+   * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+   */
+  public int getServiceCount() {
+    return service_.size();
+  }
+  /**
+   * <pre>
+   * The information of each service may be expanded in the future, so we use
+   * ServiceResponse message to encapsulate it.
+   * </pre>
+   *
+   * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+   */
+  public io.grpc.reflection.v1alpha.ServiceResponse getService(int index) {
+    return service_.get(index);
+  }
+  /**
+   * <pre>
+   * The information of each service may be expanded in the future, so we use
+   * ServiceResponse message to encapsulate it.
+   * </pre>
+   *
+   * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+   */
+  public io.grpc.reflection.v1alpha.ServiceResponseOrBuilder getServiceOrBuilder(
+      int index) {
+    return service_.get(index);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    for (int i = 0; i < service_.size(); i++) {
+      output.writeMessage(1, service_.get(i));
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    for (int i = 0; i < service_.size(); i++) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(1, service_.get(i));
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.v1alpha.ListServiceResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.v1alpha.ListServiceResponse other = (io.grpc.reflection.v1alpha.ListServiceResponse) obj;
+
+    boolean result = true;
+    result = result && getServiceList()
+        .equals(other.getServiceList());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (getServiceCount() > 0) {
+      hash = (37 * hash) + SERVICE_FIELD_NUMBER;
+      hash = (53 * hash) + getServiceList().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.v1alpha.ListServiceResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ListServiceResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ListServiceResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ListServiceResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ListServiceResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ListServiceResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ListServiceResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ListServiceResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ListServiceResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ListServiceResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ListServiceResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ListServiceResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.v1alpha.ListServiceResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * A list of ServiceResponse sent by the server answering list_services request.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.reflection.v1alpha.ListServiceResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.v1alpha.ListServiceResponse)
+      io.grpc.reflection.v1alpha.ListServiceResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ListServiceResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ListServiceResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.v1alpha.ListServiceResponse.class, io.grpc.reflection.v1alpha.ListServiceResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.v1alpha.ListServiceResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+        getServiceFieldBuilder();
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      if (serviceBuilder_ == null) {
+        service_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+      } else {
+        serviceBuilder_.clear();
+      }
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ListServiceResponse_descriptor;
+    }
+
+    public io.grpc.reflection.v1alpha.ListServiceResponse getDefaultInstanceForType() {
+      return io.grpc.reflection.v1alpha.ListServiceResponse.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.v1alpha.ListServiceResponse build() {
+      io.grpc.reflection.v1alpha.ListServiceResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.v1alpha.ListServiceResponse buildPartial() {
+      io.grpc.reflection.v1alpha.ListServiceResponse result = new io.grpc.reflection.v1alpha.ListServiceResponse(this);
+      int from_bitField0_ = bitField0_;
+      if (serviceBuilder_ == null) {
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          service_ = java.util.Collections.unmodifiableList(service_);
+          bitField0_ = (bitField0_ & ~0x00000001);
+        }
+        result.service_ = service_;
+      } else {
+        result.service_ = serviceBuilder_.build();
+      }
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.v1alpha.ListServiceResponse) {
+        return mergeFrom((io.grpc.reflection.v1alpha.ListServiceResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.v1alpha.ListServiceResponse other) {
+      if (other == io.grpc.reflection.v1alpha.ListServiceResponse.getDefaultInstance()) return this;
+      if (serviceBuilder_ == null) {
+        if (!other.service_.isEmpty()) {
+          if (service_.isEmpty()) {
+            service_ = other.service_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+          } else {
+            ensureServiceIsMutable();
+            service_.addAll(other.service_);
+          }
+          onChanged();
+        }
+      } else {
+        if (!other.service_.isEmpty()) {
+          if (serviceBuilder_.isEmpty()) {
+            serviceBuilder_.dispose();
+            serviceBuilder_ = null;
+            service_ = other.service_;
+            bitField0_ = (bitField0_ & ~0x00000001);
+            serviceBuilder_ = 
+              com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ?
+                 getServiceFieldBuilder() : null;
+          } else {
+            serviceBuilder_.addAllMessages(other.service_);
+          }
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.v1alpha.ListServiceResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.v1alpha.ListServiceResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.util.List<io.grpc.reflection.v1alpha.ServiceResponse> service_ =
+      java.util.Collections.emptyList();
+    private void ensureServiceIsMutable() {
+      if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+        service_ = new java.util.ArrayList<io.grpc.reflection.v1alpha.ServiceResponse>(service_);
+        bitField0_ |= 0x00000001;
+       }
+    }
+
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.reflection.v1alpha.ServiceResponse, io.grpc.reflection.v1alpha.ServiceResponse.Builder, io.grpc.reflection.v1alpha.ServiceResponseOrBuilder> serviceBuilder_;
+
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public java.util.List<io.grpc.reflection.v1alpha.ServiceResponse> getServiceList() {
+      if (serviceBuilder_ == null) {
+        return java.util.Collections.unmodifiableList(service_);
+      } else {
+        return serviceBuilder_.getMessageList();
+      }
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public int getServiceCount() {
+      if (serviceBuilder_ == null) {
+        return service_.size();
+      } else {
+        return serviceBuilder_.getCount();
+      }
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public io.grpc.reflection.v1alpha.ServiceResponse getService(int index) {
+      if (serviceBuilder_ == null) {
+        return service_.get(index);
+      } else {
+        return serviceBuilder_.getMessage(index);
+      }
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public Builder setService(
+        int index, io.grpc.reflection.v1alpha.ServiceResponse value) {
+      if (serviceBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureServiceIsMutable();
+        service_.set(index, value);
+        onChanged();
+      } else {
+        serviceBuilder_.setMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public Builder setService(
+        int index, io.grpc.reflection.v1alpha.ServiceResponse.Builder builderForValue) {
+      if (serviceBuilder_ == null) {
+        ensureServiceIsMutable();
+        service_.set(index, builderForValue.build());
+        onChanged();
+      } else {
+        serviceBuilder_.setMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public Builder addService(io.grpc.reflection.v1alpha.ServiceResponse value) {
+      if (serviceBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureServiceIsMutable();
+        service_.add(value);
+        onChanged();
+      } else {
+        serviceBuilder_.addMessage(value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public Builder addService(
+        int index, io.grpc.reflection.v1alpha.ServiceResponse value) {
+      if (serviceBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        ensureServiceIsMutable();
+        service_.add(index, value);
+        onChanged();
+      } else {
+        serviceBuilder_.addMessage(index, value);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public Builder addService(
+        io.grpc.reflection.v1alpha.ServiceResponse.Builder builderForValue) {
+      if (serviceBuilder_ == null) {
+        ensureServiceIsMutable();
+        service_.add(builderForValue.build());
+        onChanged();
+      } else {
+        serviceBuilder_.addMessage(builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public Builder addService(
+        int index, io.grpc.reflection.v1alpha.ServiceResponse.Builder builderForValue) {
+      if (serviceBuilder_ == null) {
+        ensureServiceIsMutable();
+        service_.add(index, builderForValue.build());
+        onChanged();
+      } else {
+        serviceBuilder_.addMessage(index, builderForValue.build());
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public Builder addAllService(
+        java.lang.Iterable<? extends io.grpc.reflection.v1alpha.ServiceResponse> values) {
+      if (serviceBuilder_ == null) {
+        ensureServiceIsMutable();
+        com.google.protobuf.AbstractMessageLite.Builder.addAll(
+            values, service_);
+        onChanged();
+      } else {
+        serviceBuilder_.addAllMessages(values);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public Builder clearService() {
+      if (serviceBuilder_ == null) {
+        service_ = java.util.Collections.emptyList();
+        bitField0_ = (bitField0_ & ~0x00000001);
+        onChanged();
+      } else {
+        serviceBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public Builder removeService(int index) {
+      if (serviceBuilder_ == null) {
+        ensureServiceIsMutable();
+        service_.remove(index);
+        onChanged();
+      } else {
+        serviceBuilder_.remove(index);
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public io.grpc.reflection.v1alpha.ServiceResponse.Builder getServiceBuilder(
+        int index) {
+      return getServiceFieldBuilder().getBuilder(index);
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public io.grpc.reflection.v1alpha.ServiceResponseOrBuilder getServiceOrBuilder(
+        int index) {
+      if (serviceBuilder_ == null) {
+        return service_.get(index);  } else {
+        return serviceBuilder_.getMessageOrBuilder(index);
+      }
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public java.util.List<? extends io.grpc.reflection.v1alpha.ServiceResponseOrBuilder> 
+         getServiceOrBuilderList() {
+      if (serviceBuilder_ != null) {
+        return serviceBuilder_.getMessageOrBuilderList();
+      } else {
+        return java.util.Collections.unmodifiableList(service_);
+      }
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public io.grpc.reflection.v1alpha.ServiceResponse.Builder addServiceBuilder() {
+      return getServiceFieldBuilder().addBuilder(
+          io.grpc.reflection.v1alpha.ServiceResponse.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public io.grpc.reflection.v1alpha.ServiceResponse.Builder addServiceBuilder(
+        int index) {
+      return getServiceFieldBuilder().addBuilder(
+          index, io.grpc.reflection.v1alpha.ServiceResponse.getDefaultInstance());
+    }
+    /**
+     * <pre>
+     * The information of each service may be expanded in the future, so we use
+     * ServiceResponse message to encapsulate it.
+     * </pre>
+     *
+     * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+     */
+    public java.util.List<io.grpc.reflection.v1alpha.ServiceResponse.Builder> 
+         getServiceBuilderList() {
+      return getServiceFieldBuilder().getBuilderList();
+    }
+    private com.google.protobuf.RepeatedFieldBuilderV3<
+        io.grpc.reflection.v1alpha.ServiceResponse, io.grpc.reflection.v1alpha.ServiceResponse.Builder, io.grpc.reflection.v1alpha.ServiceResponseOrBuilder> 
+        getServiceFieldBuilder() {
+      if (serviceBuilder_ == null) {
+        serviceBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3<
+            io.grpc.reflection.v1alpha.ServiceResponse, io.grpc.reflection.v1alpha.ServiceResponse.Builder, io.grpc.reflection.v1alpha.ServiceResponseOrBuilder>(
+                service_,
+                ((bitField0_ & 0x00000001) == 0x00000001),
+                getParentForChildren(),
+                isClean());
+        service_ = null;
+      }
+      return serviceBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.v1alpha.ListServiceResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.v1alpha.ListServiceResponse)
+  private static final io.grpc.reflection.v1alpha.ListServiceResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.v1alpha.ListServiceResponse();
+  }
+
+  public static io.grpc.reflection.v1alpha.ListServiceResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ListServiceResponse>
+      PARSER = new com.google.protobuf.AbstractParser<ListServiceResponse>() {
+    public ListServiceResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ListServiceResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ListServiceResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ListServiceResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.v1alpha.ListServiceResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ListServiceResponseOrBuilder.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ListServiceResponseOrBuilder.java
new file mode 100644
index 0000000..734ec96
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ListServiceResponseOrBuilder.java
@@ -0,0 +1,58 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+public interface ListServiceResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.v1alpha.ListServiceResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * The information of each service may be expanded in the future, so we use
+   * ServiceResponse message to encapsulate it.
+   * </pre>
+   *
+   * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+   */
+  java.util.List<io.grpc.reflection.v1alpha.ServiceResponse> 
+      getServiceList();
+  /**
+   * <pre>
+   * The information of each service may be expanded in the future, so we use
+   * ServiceResponse message to encapsulate it.
+   * </pre>
+   *
+   * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+   */
+  io.grpc.reflection.v1alpha.ServiceResponse getService(int index);
+  /**
+   * <pre>
+   * The information of each service may be expanded in the future, so we use
+   * ServiceResponse message to encapsulate it.
+   * </pre>
+   *
+   * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+   */
+  int getServiceCount();
+  /**
+   * <pre>
+   * The information of each service may be expanded in the future, so we use
+   * ServiceResponse message to encapsulate it.
+   * </pre>
+   *
+   * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+   */
+  java.util.List<? extends io.grpc.reflection.v1alpha.ServiceResponseOrBuilder> 
+      getServiceOrBuilderList();
+  /**
+   * <pre>
+   * The information of each service may be expanded in the future, so we use
+   * ServiceResponse message to encapsulate it.
+   * </pre>
+   *
+   * <code>repeated .grpc.reflection.v1alpha.ServiceResponse service = 1;</code>
+   */
+  io.grpc.reflection.v1alpha.ServiceResponseOrBuilder getServiceOrBuilder(
+      int index);
+}
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionProto.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionProto.java
new file mode 100644
index 0000000..b18131c
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionProto.java
@@ -0,0 +1,165 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+public final class ServerReflectionProto {
+  private ServerReflectionProto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_v1alpha_ServerReflectionRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_v1alpha_ServerReflectionRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_v1alpha_ExtensionRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_v1alpha_ExtensionRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_v1alpha_ServerReflectionResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_v1alpha_ServerReflectionResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_v1alpha_FileDescriptorResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_v1alpha_FileDescriptorResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_v1alpha_ExtensionNumberResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_v1alpha_ExtensionNumberResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_v1alpha_ListServiceResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_v1alpha_ListServiceResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_v1alpha_ServiceResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_v1alpha_ServiceResponse_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_v1alpha_ErrorResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_v1alpha_ErrorResponse_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n+io/grpc/reflection/v1alpha/reflection." +
+      "proto\022\027grpc.reflection.v1alpha\"\212\002\n\027Serve" +
+      "rReflectionRequest\022\014\n\004host\030\001 \001(\t\022\032\n\020file" +
+      "_by_filename\030\003 \001(\tH\000\022 \n\026file_containing_" +
+      "symbol\030\004 \001(\tH\000\022N\n\031file_containing_extens" +
+      "ion\030\005 \001(\0132).grpc.reflection.v1alpha.Exte" +
+      "nsionRequestH\000\022\'\n\035all_extension_numbers_" +
+      "of_type\030\006 \001(\tH\000\022\027\n\rlist_services\030\007 \001(\tH\000" +
+      "B\021\n\017message_request\"E\n\020ExtensionRequest\022" +
+      "\027\n\017containing_type\030\001 \001(\t\022\030\n\020extension_nu" +
+      "mber\030\002 \001(\005\"\321\003\n\030ServerReflectionResponse\022" +
+      "\022\n\nvalid_host\030\001 \001(\t\022J\n\020original_request\030" +
+      "\002 \001(\01320.grpc.reflection.v1alpha.ServerRe" +
+      "flectionRequest\022S\n\030file_descriptor_respo" +
+      "nse\030\004 \001(\0132/.grpc.reflection.v1alpha.File" +
+      "DescriptorResponseH\000\022Z\n\036all_extension_nu" +
+      "mbers_response\030\005 \001(\01320.grpc.reflection.v" +
+      "1alpha.ExtensionNumberResponseH\000\022N\n\026list" +
+      "_services_response\030\006 \001(\0132,.grpc.reflecti" +
+      "on.v1alpha.ListServiceResponseH\000\022@\n\016erro" +
+      "r_response\030\007 \001(\0132&.grpc.reflection.v1alp" +
+      "ha.ErrorResponseH\000B\022\n\020message_response\"7" +
+      "\n\026FileDescriptorResponse\022\035\n\025file_descrip" +
+      "tor_proto\030\001 \003(\014\"K\n\027ExtensionNumberRespon" +
+      "se\022\026\n\016base_type_name\030\001 \001(\t\022\030\n\020extension_" +
+      "number\030\002 \003(\005\"P\n\023ListServiceResponse\0229\n\007s" +
+      "ervice\030\001 \003(\0132(.grpc.reflection.v1alpha.S" +
+      "erviceResponse\"\037\n\017ServiceResponse\022\014\n\004nam" +
+      "e\030\001 \001(\t\":\n\rErrorResponse\022\022\n\nerror_code\030\001" +
+      " \001(\005\022\025\n\rerror_message\030\002 \001(\t2\223\001\n\020ServerRe" +
+      "flection\022\177\n\024ServerReflectionInfo\0220.grpc." +
+      "reflection.v1alpha.ServerReflectionReque" +
+      "st\0321.grpc.reflection.v1alpha.ServerRefle" +
+      "ctionResponse(\0010\001B5\n\032io.grpc.reflection." +
+      "v1alphaB\025ServerReflectionProtoP\001b\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        }, assigner);
+    internal_static_grpc_reflection_v1alpha_ServerReflectionRequest_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_reflection_v1alpha_ServerReflectionRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_v1alpha_ServerReflectionRequest_descriptor,
+        new java.lang.String[] { "Host", "FileByFilename", "FileContainingSymbol", "FileContainingExtension", "AllExtensionNumbersOfType", "ListServices", "MessageRequest", });
+    internal_static_grpc_reflection_v1alpha_ExtensionRequest_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_reflection_v1alpha_ExtensionRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_v1alpha_ExtensionRequest_descriptor,
+        new java.lang.String[] { "ContainingType", "ExtensionNumber", });
+    internal_static_grpc_reflection_v1alpha_ServerReflectionResponse_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_reflection_v1alpha_ServerReflectionResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_v1alpha_ServerReflectionResponse_descriptor,
+        new java.lang.String[] { "ValidHost", "OriginalRequest", "FileDescriptorResponse", "AllExtensionNumbersResponse", "ListServicesResponse", "ErrorResponse", "MessageResponse", });
+    internal_static_grpc_reflection_v1alpha_FileDescriptorResponse_descriptor =
+      getDescriptor().getMessageTypes().get(3);
+    internal_static_grpc_reflection_v1alpha_FileDescriptorResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_v1alpha_FileDescriptorResponse_descriptor,
+        new java.lang.String[] { "FileDescriptorProto", });
+    internal_static_grpc_reflection_v1alpha_ExtensionNumberResponse_descriptor =
+      getDescriptor().getMessageTypes().get(4);
+    internal_static_grpc_reflection_v1alpha_ExtensionNumberResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_v1alpha_ExtensionNumberResponse_descriptor,
+        new java.lang.String[] { "BaseTypeName", "ExtensionNumber", });
+    internal_static_grpc_reflection_v1alpha_ListServiceResponse_descriptor =
+      getDescriptor().getMessageTypes().get(5);
+    internal_static_grpc_reflection_v1alpha_ListServiceResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_v1alpha_ListServiceResponse_descriptor,
+        new java.lang.String[] { "Service", });
+    internal_static_grpc_reflection_v1alpha_ServiceResponse_descriptor =
+      getDescriptor().getMessageTypes().get(6);
+    internal_static_grpc_reflection_v1alpha_ServiceResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_v1alpha_ServiceResponse_descriptor,
+        new java.lang.String[] { "Name", });
+    internal_static_grpc_reflection_v1alpha_ErrorResponse_descriptor =
+      getDescriptor().getMessageTypes().get(7);
+    internal_static_grpc_reflection_v1alpha_ErrorResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_v1alpha_ErrorResponse_descriptor,
+        new java.lang.String[] { "ErrorCode", "ErrorMessage", });
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionRequest.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionRequest.java
new file mode 100644
index 0000000..e03a542
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionRequest.java
@@ -0,0 +1,1654 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+/**
+ * <pre>
+ * The message sent by the client when calling ServerReflectionInfo method.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.reflection.v1alpha.ServerReflectionRequest}
+ */
+public  final class ServerReflectionRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.v1alpha.ServerReflectionRequest)
+    ServerReflectionRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ServerReflectionRequest.newBuilder() to construct.
+  private ServerReflectionRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ServerReflectionRequest() {
+    host_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ServerReflectionRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            host_ = s;
+            break;
+          }
+          case 26: {
+            java.lang.String s = input.readStringRequireUtf8();
+            messageRequestCase_ = 3;
+            messageRequest_ = s;
+            break;
+          }
+          case 34: {
+            java.lang.String s = input.readStringRequireUtf8();
+            messageRequestCase_ = 4;
+            messageRequest_ = s;
+            break;
+          }
+          case 42: {
+            io.grpc.reflection.v1alpha.ExtensionRequest.Builder subBuilder = null;
+            if (messageRequestCase_ == 5) {
+              subBuilder = ((io.grpc.reflection.v1alpha.ExtensionRequest) messageRequest_).toBuilder();
+            }
+            messageRequest_ =
+                input.readMessage(io.grpc.reflection.v1alpha.ExtensionRequest.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.reflection.v1alpha.ExtensionRequest) messageRequest_);
+              messageRequest_ = subBuilder.buildPartial();
+            }
+            messageRequestCase_ = 5;
+            break;
+          }
+          case 50: {
+            java.lang.String s = input.readStringRequireUtf8();
+            messageRequestCase_ = 6;
+            messageRequest_ = s;
+            break;
+          }
+          case 58: {
+            java.lang.String s = input.readStringRequireUtf8();
+            messageRequestCase_ = 7;
+            messageRequest_ = s;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServerReflectionRequest_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServerReflectionRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.v1alpha.ServerReflectionRequest.class, io.grpc.reflection.v1alpha.ServerReflectionRequest.Builder.class);
+  }
+
+  private int messageRequestCase_ = 0;
+  private java.lang.Object messageRequest_;
+  public enum MessageRequestCase
+      implements com.google.protobuf.Internal.EnumLite {
+    FILE_BY_FILENAME(3),
+    FILE_CONTAINING_SYMBOL(4),
+    FILE_CONTAINING_EXTENSION(5),
+    ALL_EXTENSION_NUMBERS_OF_TYPE(6),
+    LIST_SERVICES(7),
+    MESSAGEREQUEST_NOT_SET(0);
+    private final int value;
+    private MessageRequestCase(int value) {
+      this.value = value;
+    }
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static MessageRequestCase valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static MessageRequestCase forNumber(int value) {
+      switch (value) {
+        case 3: return FILE_BY_FILENAME;
+        case 4: return FILE_CONTAINING_SYMBOL;
+        case 5: return FILE_CONTAINING_EXTENSION;
+        case 6: return ALL_EXTENSION_NUMBERS_OF_TYPE;
+        case 7: return LIST_SERVICES;
+        case 0: return MESSAGEREQUEST_NOT_SET;
+        default: return null;
+      }
+    }
+    public int getNumber() {
+      return this.value;
+    }
+  };
+
+  public MessageRequestCase
+  getMessageRequestCase() {
+    return MessageRequestCase.forNumber(
+        messageRequestCase_);
+  }
+
+  public static final int HOST_FIELD_NUMBER = 1;
+  private volatile java.lang.Object host_;
+  /**
+   * <code>string host = 1;</code>
+   */
+  public java.lang.String getHost() {
+    java.lang.Object ref = host_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      host_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string host = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getHostBytes() {
+    java.lang.Object ref = host_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      host_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int FILE_BY_FILENAME_FIELD_NUMBER = 3;
+  /**
+   * <pre>
+   * Find a proto file by the file name.
+   * </pre>
+   *
+   * <code>string file_by_filename = 3;</code>
+   */
+  public java.lang.String getFileByFilename() {
+    java.lang.Object ref = "";
+    if (messageRequestCase_ == 3) {
+      ref = messageRequest_;
+    }
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      if (messageRequestCase_ == 3) {
+        messageRequest_ = s;
+      }
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * Find a proto file by the file name.
+   * </pre>
+   *
+   * <code>string file_by_filename = 3;</code>
+   */
+  public com.google.protobuf.ByteString
+      getFileByFilenameBytes() {
+    java.lang.Object ref = "";
+    if (messageRequestCase_ == 3) {
+      ref = messageRequest_;
+    }
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      if (messageRequestCase_ == 3) {
+        messageRequest_ = b;
+      }
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int FILE_CONTAINING_SYMBOL_FIELD_NUMBER = 4;
+  /**
+   * <pre>
+   * Find the proto file that declares the given fully-qualified symbol name.
+   * This field should be a fully-qualified symbol name
+   * (e.g. &lt;package&gt;.&lt;service&gt;[.&lt;method&gt;] or &lt;package&gt;.&lt;type&gt;).
+   * </pre>
+   *
+   * <code>string file_containing_symbol = 4;</code>
+   */
+  public java.lang.String getFileContainingSymbol() {
+    java.lang.Object ref = "";
+    if (messageRequestCase_ == 4) {
+      ref = messageRequest_;
+    }
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      if (messageRequestCase_ == 4) {
+        messageRequest_ = s;
+      }
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * Find the proto file that declares the given fully-qualified symbol name.
+   * This field should be a fully-qualified symbol name
+   * (e.g. &lt;package&gt;.&lt;service&gt;[.&lt;method&gt;] or &lt;package&gt;.&lt;type&gt;).
+   * </pre>
+   *
+   * <code>string file_containing_symbol = 4;</code>
+   */
+  public com.google.protobuf.ByteString
+      getFileContainingSymbolBytes() {
+    java.lang.Object ref = "";
+    if (messageRequestCase_ == 4) {
+      ref = messageRequest_;
+    }
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      if (messageRequestCase_ == 4) {
+        messageRequest_ = b;
+      }
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int FILE_CONTAINING_EXTENSION_FIELD_NUMBER = 5;
+  /**
+   * <pre>
+   * Find the proto file which defines an extension extending the given
+   * message type with the given field number.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+   */
+  public boolean hasFileContainingExtension() {
+    return messageRequestCase_ == 5;
+  }
+  /**
+   * <pre>
+   * Find the proto file which defines an extension extending the given
+   * message type with the given field number.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+   */
+  public io.grpc.reflection.v1alpha.ExtensionRequest getFileContainingExtension() {
+    if (messageRequestCase_ == 5) {
+       return (io.grpc.reflection.v1alpha.ExtensionRequest) messageRequest_;
+    }
+    return io.grpc.reflection.v1alpha.ExtensionRequest.getDefaultInstance();
+  }
+  /**
+   * <pre>
+   * Find the proto file which defines an extension extending the given
+   * message type with the given field number.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+   */
+  public io.grpc.reflection.v1alpha.ExtensionRequestOrBuilder getFileContainingExtensionOrBuilder() {
+    if (messageRequestCase_ == 5) {
+       return (io.grpc.reflection.v1alpha.ExtensionRequest) messageRequest_;
+    }
+    return io.grpc.reflection.v1alpha.ExtensionRequest.getDefaultInstance();
+  }
+
+  public static final int ALL_EXTENSION_NUMBERS_OF_TYPE_FIELD_NUMBER = 6;
+  /**
+   * <pre>
+   * Finds the tag numbers used by all known extensions of extendee_type, and
+   * appends them to ExtensionNumberResponse in an undefined order.
+   * Its corresponding method is best-effort: it's not guaranteed that the
+   * reflection service will implement this method, and it's not guaranteed
+   * that this method will provide all extensions. Returns
+   * StatusCode::UNIMPLEMENTED if it's not implemented.
+   * This field should be a fully-qualified type name. The format is
+   * &lt;package&gt;.&lt;type&gt;
+   * </pre>
+   *
+   * <code>string all_extension_numbers_of_type = 6;</code>
+   */
+  public java.lang.String getAllExtensionNumbersOfType() {
+    java.lang.Object ref = "";
+    if (messageRequestCase_ == 6) {
+      ref = messageRequest_;
+    }
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      if (messageRequestCase_ == 6) {
+        messageRequest_ = s;
+      }
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * Finds the tag numbers used by all known extensions of extendee_type, and
+   * appends them to ExtensionNumberResponse in an undefined order.
+   * Its corresponding method is best-effort: it's not guaranteed that the
+   * reflection service will implement this method, and it's not guaranteed
+   * that this method will provide all extensions. Returns
+   * StatusCode::UNIMPLEMENTED if it's not implemented.
+   * This field should be a fully-qualified type name. The format is
+   * &lt;package&gt;.&lt;type&gt;
+   * </pre>
+   *
+   * <code>string all_extension_numbers_of_type = 6;</code>
+   */
+  public com.google.protobuf.ByteString
+      getAllExtensionNumbersOfTypeBytes() {
+    java.lang.Object ref = "";
+    if (messageRequestCase_ == 6) {
+      ref = messageRequest_;
+    }
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      if (messageRequestCase_ == 6) {
+        messageRequest_ = b;
+      }
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int LIST_SERVICES_FIELD_NUMBER = 7;
+  /**
+   * <pre>
+   * List the full names of registered services. The content will not be
+   * checked.
+   * </pre>
+   *
+   * <code>string list_services = 7;</code>
+   */
+  public java.lang.String getListServices() {
+    java.lang.Object ref = "";
+    if (messageRequestCase_ == 7) {
+      ref = messageRequest_;
+    }
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      if (messageRequestCase_ == 7) {
+        messageRequest_ = s;
+      }
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * List the full names of registered services. The content will not be
+   * checked.
+   * </pre>
+   *
+   * <code>string list_services = 7;</code>
+   */
+  public com.google.protobuf.ByteString
+      getListServicesBytes() {
+    java.lang.Object ref = "";
+    if (messageRequestCase_ == 7) {
+      ref = messageRequest_;
+    }
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      if (messageRequestCase_ == 7) {
+        messageRequest_ = b;
+      }
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getHostBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, host_);
+    }
+    if (messageRequestCase_ == 3) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 3, messageRequest_);
+    }
+    if (messageRequestCase_ == 4) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 4, messageRequest_);
+    }
+    if (messageRequestCase_ == 5) {
+      output.writeMessage(5, (io.grpc.reflection.v1alpha.ExtensionRequest) messageRequest_);
+    }
+    if (messageRequestCase_ == 6) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 6, messageRequest_);
+    }
+    if (messageRequestCase_ == 7) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 7, messageRequest_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getHostBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, host_);
+    }
+    if (messageRequestCase_ == 3) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, messageRequest_);
+    }
+    if (messageRequestCase_ == 4) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(4, messageRequest_);
+    }
+    if (messageRequestCase_ == 5) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(5, (io.grpc.reflection.v1alpha.ExtensionRequest) messageRequest_);
+    }
+    if (messageRequestCase_ == 6) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(6, messageRequest_);
+    }
+    if (messageRequestCase_ == 7) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(7, messageRequest_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.v1alpha.ServerReflectionRequest)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.v1alpha.ServerReflectionRequest other = (io.grpc.reflection.v1alpha.ServerReflectionRequest) obj;
+
+    boolean result = true;
+    result = result && getHost()
+        .equals(other.getHost());
+    result = result && getMessageRequestCase().equals(
+        other.getMessageRequestCase());
+    if (!result) return false;
+    switch (messageRequestCase_) {
+      case 3:
+        result = result && getFileByFilename()
+            .equals(other.getFileByFilename());
+        break;
+      case 4:
+        result = result && getFileContainingSymbol()
+            .equals(other.getFileContainingSymbol());
+        break;
+      case 5:
+        result = result && getFileContainingExtension()
+            .equals(other.getFileContainingExtension());
+        break;
+      case 6:
+        result = result && getAllExtensionNumbersOfType()
+            .equals(other.getAllExtensionNumbersOfType());
+        break;
+      case 7:
+        result = result && getListServices()
+            .equals(other.getListServices());
+        break;
+      case 0:
+      default:
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + HOST_FIELD_NUMBER;
+    hash = (53 * hash) + getHost().hashCode();
+    switch (messageRequestCase_) {
+      case 3:
+        hash = (37 * hash) + FILE_BY_FILENAME_FIELD_NUMBER;
+        hash = (53 * hash) + getFileByFilename().hashCode();
+        break;
+      case 4:
+        hash = (37 * hash) + FILE_CONTAINING_SYMBOL_FIELD_NUMBER;
+        hash = (53 * hash) + getFileContainingSymbol().hashCode();
+        break;
+      case 5:
+        hash = (37 * hash) + FILE_CONTAINING_EXTENSION_FIELD_NUMBER;
+        hash = (53 * hash) + getFileContainingExtension().hashCode();
+        break;
+      case 6:
+        hash = (37 * hash) + ALL_EXTENSION_NUMBERS_OF_TYPE_FIELD_NUMBER;
+        hash = (53 * hash) + getAllExtensionNumbersOfType().hashCode();
+        break;
+      case 7:
+        hash = (37 * hash) + LIST_SERVICES_FIELD_NUMBER;
+        hash = (53 * hash) + getListServices().hashCode();
+        break;
+      case 0:
+      default:
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.v1alpha.ServerReflectionRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.v1alpha.ServerReflectionRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * The message sent by the client when calling ServerReflectionInfo method.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.reflection.v1alpha.ServerReflectionRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.v1alpha.ServerReflectionRequest)
+      io.grpc.reflection.v1alpha.ServerReflectionRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServerReflectionRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServerReflectionRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.v1alpha.ServerReflectionRequest.class, io.grpc.reflection.v1alpha.ServerReflectionRequest.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.v1alpha.ServerReflectionRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      host_ = "";
+
+      messageRequestCase_ = 0;
+      messageRequest_ = null;
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServerReflectionRequest_descriptor;
+    }
+
+    public io.grpc.reflection.v1alpha.ServerReflectionRequest getDefaultInstanceForType() {
+      return io.grpc.reflection.v1alpha.ServerReflectionRequest.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.v1alpha.ServerReflectionRequest build() {
+      io.grpc.reflection.v1alpha.ServerReflectionRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.v1alpha.ServerReflectionRequest buildPartial() {
+      io.grpc.reflection.v1alpha.ServerReflectionRequest result = new io.grpc.reflection.v1alpha.ServerReflectionRequest(this);
+      result.host_ = host_;
+      if (messageRequestCase_ == 3) {
+        result.messageRequest_ = messageRequest_;
+      }
+      if (messageRequestCase_ == 4) {
+        result.messageRequest_ = messageRequest_;
+      }
+      if (messageRequestCase_ == 5) {
+        if (fileContainingExtensionBuilder_ == null) {
+          result.messageRequest_ = messageRequest_;
+        } else {
+          result.messageRequest_ = fileContainingExtensionBuilder_.build();
+        }
+      }
+      if (messageRequestCase_ == 6) {
+        result.messageRequest_ = messageRequest_;
+      }
+      if (messageRequestCase_ == 7) {
+        result.messageRequest_ = messageRequest_;
+      }
+      result.messageRequestCase_ = messageRequestCase_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.v1alpha.ServerReflectionRequest) {
+        return mergeFrom((io.grpc.reflection.v1alpha.ServerReflectionRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.v1alpha.ServerReflectionRequest other) {
+      if (other == io.grpc.reflection.v1alpha.ServerReflectionRequest.getDefaultInstance()) return this;
+      if (!other.getHost().isEmpty()) {
+        host_ = other.host_;
+        onChanged();
+      }
+      switch (other.getMessageRequestCase()) {
+        case FILE_BY_FILENAME: {
+          messageRequestCase_ = 3;
+          messageRequest_ = other.messageRequest_;
+          onChanged();
+          break;
+        }
+        case FILE_CONTAINING_SYMBOL: {
+          messageRequestCase_ = 4;
+          messageRequest_ = other.messageRequest_;
+          onChanged();
+          break;
+        }
+        case FILE_CONTAINING_EXTENSION: {
+          mergeFileContainingExtension(other.getFileContainingExtension());
+          break;
+        }
+        case ALL_EXTENSION_NUMBERS_OF_TYPE: {
+          messageRequestCase_ = 6;
+          messageRequest_ = other.messageRequest_;
+          onChanged();
+          break;
+        }
+        case LIST_SERVICES: {
+          messageRequestCase_ = 7;
+          messageRequest_ = other.messageRequest_;
+          onChanged();
+          break;
+        }
+        case MESSAGEREQUEST_NOT_SET: {
+          break;
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.v1alpha.ServerReflectionRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.v1alpha.ServerReflectionRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int messageRequestCase_ = 0;
+    private java.lang.Object messageRequest_;
+    public MessageRequestCase
+        getMessageRequestCase() {
+      return MessageRequestCase.forNumber(
+          messageRequestCase_);
+    }
+
+    public Builder clearMessageRequest() {
+      messageRequestCase_ = 0;
+      messageRequest_ = null;
+      onChanged();
+      return this;
+    }
+
+
+    private java.lang.Object host_ = "";
+    /**
+     * <code>string host = 1;</code>
+     */
+    public java.lang.String getHost() {
+      java.lang.Object ref = host_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        host_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string host = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getHostBytes() {
+      java.lang.Object ref = host_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        host_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string host = 1;</code>
+     */
+    public Builder setHost(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      host_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string host = 1;</code>
+     */
+    public Builder clearHost() {
+      
+      host_ = getDefaultInstance().getHost();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string host = 1;</code>
+     */
+    public Builder setHostBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      host_ = value;
+      onChanged();
+      return this;
+    }
+
+    /**
+     * <pre>
+     * Find a proto file by the file name.
+     * </pre>
+     *
+     * <code>string file_by_filename = 3;</code>
+     */
+    public java.lang.String getFileByFilename() {
+      java.lang.Object ref = "";
+      if (messageRequestCase_ == 3) {
+        ref = messageRequest_;
+      }
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (messageRequestCase_ == 3) {
+          messageRequest_ = s;
+        }
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Find a proto file by the file name.
+     * </pre>
+     *
+     * <code>string file_by_filename = 3;</code>
+     */
+    public com.google.protobuf.ByteString
+        getFileByFilenameBytes() {
+      java.lang.Object ref = "";
+      if (messageRequestCase_ == 3) {
+        ref = messageRequest_;
+      }
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        if (messageRequestCase_ == 3) {
+          messageRequest_ = b;
+        }
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Find a proto file by the file name.
+     * </pre>
+     *
+     * <code>string file_by_filename = 3;</code>
+     */
+    public Builder setFileByFilename(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  messageRequestCase_ = 3;
+      messageRequest_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Find a proto file by the file name.
+     * </pre>
+     *
+     * <code>string file_by_filename = 3;</code>
+     */
+    public Builder clearFileByFilename() {
+      if (messageRequestCase_ == 3) {
+        messageRequestCase_ = 0;
+        messageRequest_ = null;
+        onChanged();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Find a proto file by the file name.
+     * </pre>
+     *
+     * <code>string file_by_filename = 3;</code>
+     */
+    public Builder setFileByFilenameBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      messageRequestCase_ = 3;
+      messageRequest_ = value;
+      onChanged();
+      return this;
+    }
+
+    /**
+     * <pre>
+     * Find the proto file that declares the given fully-qualified symbol name.
+     * This field should be a fully-qualified symbol name
+     * (e.g. &lt;package&gt;.&lt;service&gt;[.&lt;method&gt;] or &lt;package&gt;.&lt;type&gt;).
+     * </pre>
+     *
+     * <code>string file_containing_symbol = 4;</code>
+     */
+    public java.lang.String getFileContainingSymbol() {
+      java.lang.Object ref = "";
+      if (messageRequestCase_ == 4) {
+        ref = messageRequest_;
+      }
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (messageRequestCase_ == 4) {
+          messageRequest_ = s;
+        }
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Find the proto file that declares the given fully-qualified symbol name.
+     * This field should be a fully-qualified symbol name
+     * (e.g. &lt;package&gt;.&lt;service&gt;[.&lt;method&gt;] or &lt;package&gt;.&lt;type&gt;).
+     * </pre>
+     *
+     * <code>string file_containing_symbol = 4;</code>
+     */
+    public com.google.protobuf.ByteString
+        getFileContainingSymbolBytes() {
+      java.lang.Object ref = "";
+      if (messageRequestCase_ == 4) {
+        ref = messageRequest_;
+      }
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        if (messageRequestCase_ == 4) {
+          messageRequest_ = b;
+        }
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Find the proto file that declares the given fully-qualified symbol name.
+     * This field should be a fully-qualified symbol name
+     * (e.g. &lt;package&gt;.&lt;service&gt;[.&lt;method&gt;] or &lt;package&gt;.&lt;type&gt;).
+     * </pre>
+     *
+     * <code>string file_containing_symbol = 4;</code>
+     */
+    public Builder setFileContainingSymbol(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  messageRequestCase_ = 4;
+      messageRequest_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Find the proto file that declares the given fully-qualified symbol name.
+     * This field should be a fully-qualified symbol name
+     * (e.g. &lt;package&gt;.&lt;service&gt;[.&lt;method&gt;] or &lt;package&gt;.&lt;type&gt;).
+     * </pre>
+     *
+     * <code>string file_containing_symbol = 4;</code>
+     */
+    public Builder clearFileContainingSymbol() {
+      if (messageRequestCase_ == 4) {
+        messageRequestCase_ = 0;
+        messageRequest_ = null;
+        onChanged();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Find the proto file that declares the given fully-qualified symbol name.
+     * This field should be a fully-qualified symbol name
+     * (e.g. &lt;package&gt;.&lt;service&gt;[.&lt;method&gt;] or &lt;package&gt;.&lt;type&gt;).
+     * </pre>
+     *
+     * <code>string file_containing_symbol = 4;</code>
+     */
+    public Builder setFileContainingSymbolBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      messageRequestCase_ = 4;
+      messageRequest_ = value;
+      onChanged();
+      return this;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.reflection.v1alpha.ExtensionRequest, io.grpc.reflection.v1alpha.ExtensionRequest.Builder, io.grpc.reflection.v1alpha.ExtensionRequestOrBuilder> fileContainingExtensionBuilder_;
+    /**
+     * <pre>
+     * Find the proto file which defines an extension extending the given
+     * message type with the given field number.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+     */
+    public boolean hasFileContainingExtension() {
+      return messageRequestCase_ == 5;
+    }
+    /**
+     * <pre>
+     * Find the proto file which defines an extension extending the given
+     * message type with the given field number.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+     */
+    public io.grpc.reflection.v1alpha.ExtensionRequest getFileContainingExtension() {
+      if (fileContainingExtensionBuilder_ == null) {
+        if (messageRequestCase_ == 5) {
+          return (io.grpc.reflection.v1alpha.ExtensionRequest) messageRequest_;
+        }
+        return io.grpc.reflection.v1alpha.ExtensionRequest.getDefaultInstance();
+      } else {
+        if (messageRequestCase_ == 5) {
+          return fileContainingExtensionBuilder_.getMessage();
+        }
+        return io.grpc.reflection.v1alpha.ExtensionRequest.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * Find the proto file which defines an extension extending the given
+     * message type with the given field number.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+     */
+    public Builder setFileContainingExtension(io.grpc.reflection.v1alpha.ExtensionRequest value) {
+      if (fileContainingExtensionBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        messageRequest_ = value;
+        onChanged();
+      } else {
+        fileContainingExtensionBuilder_.setMessage(value);
+      }
+      messageRequestCase_ = 5;
+      return this;
+    }
+    /**
+     * <pre>
+     * Find the proto file which defines an extension extending the given
+     * message type with the given field number.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+     */
+    public Builder setFileContainingExtension(
+        io.grpc.reflection.v1alpha.ExtensionRequest.Builder builderForValue) {
+      if (fileContainingExtensionBuilder_ == null) {
+        messageRequest_ = builderForValue.build();
+        onChanged();
+      } else {
+        fileContainingExtensionBuilder_.setMessage(builderForValue.build());
+      }
+      messageRequestCase_ = 5;
+      return this;
+    }
+    /**
+     * <pre>
+     * Find the proto file which defines an extension extending the given
+     * message type with the given field number.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+     */
+    public Builder mergeFileContainingExtension(io.grpc.reflection.v1alpha.ExtensionRequest value) {
+      if (fileContainingExtensionBuilder_ == null) {
+        if (messageRequestCase_ == 5 &&
+            messageRequest_ != io.grpc.reflection.v1alpha.ExtensionRequest.getDefaultInstance()) {
+          messageRequest_ = io.grpc.reflection.v1alpha.ExtensionRequest.newBuilder((io.grpc.reflection.v1alpha.ExtensionRequest) messageRequest_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          messageRequest_ = value;
+        }
+        onChanged();
+      } else {
+        if (messageRequestCase_ == 5) {
+          fileContainingExtensionBuilder_.mergeFrom(value);
+        }
+        fileContainingExtensionBuilder_.setMessage(value);
+      }
+      messageRequestCase_ = 5;
+      return this;
+    }
+    /**
+     * <pre>
+     * Find the proto file which defines an extension extending the given
+     * message type with the given field number.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+     */
+    public Builder clearFileContainingExtension() {
+      if (fileContainingExtensionBuilder_ == null) {
+        if (messageRequestCase_ == 5) {
+          messageRequestCase_ = 0;
+          messageRequest_ = null;
+          onChanged();
+        }
+      } else {
+        if (messageRequestCase_ == 5) {
+          messageRequestCase_ = 0;
+          messageRequest_ = null;
+        }
+        fileContainingExtensionBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Find the proto file which defines an extension extending the given
+     * message type with the given field number.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+     */
+    public io.grpc.reflection.v1alpha.ExtensionRequest.Builder getFileContainingExtensionBuilder() {
+      return getFileContainingExtensionFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * Find the proto file which defines an extension extending the given
+     * message type with the given field number.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+     */
+    public io.grpc.reflection.v1alpha.ExtensionRequestOrBuilder getFileContainingExtensionOrBuilder() {
+      if ((messageRequestCase_ == 5) && (fileContainingExtensionBuilder_ != null)) {
+        return fileContainingExtensionBuilder_.getMessageOrBuilder();
+      } else {
+        if (messageRequestCase_ == 5) {
+          return (io.grpc.reflection.v1alpha.ExtensionRequest) messageRequest_;
+        }
+        return io.grpc.reflection.v1alpha.ExtensionRequest.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * Find the proto file which defines an extension extending the given
+     * message type with the given field number.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.reflection.v1alpha.ExtensionRequest, io.grpc.reflection.v1alpha.ExtensionRequest.Builder, io.grpc.reflection.v1alpha.ExtensionRequestOrBuilder> 
+        getFileContainingExtensionFieldBuilder() {
+      if (fileContainingExtensionBuilder_ == null) {
+        if (!(messageRequestCase_ == 5)) {
+          messageRequest_ = io.grpc.reflection.v1alpha.ExtensionRequest.getDefaultInstance();
+        }
+        fileContainingExtensionBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.reflection.v1alpha.ExtensionRequest, io.grpc.reflection.v1alpha.ExtensionRequest.Builder, io.grpc.reflection.v1alpha.ExtensionRequestOrBuilder>(
+                (io.grpc.reflection.v1alpha.ExtensionRequest) messageRequest_,
+                getParentForChildren(),
+                isClean());
+        messageRequest_ = null;
+      }
+      messageRequestCase_ = 5;
+      onChanged();;
+      return fileContainingExtensionBuilder_;
+    }
+
+    /**
+     * <pre>
+     * Finds the tag numbers used by all known extensions of extendee_type, and
+     * appends them to ExtensionNumberResponse in an undefined order.
+     * Its corresponding method is best-effort: it's not guaranteed that the
+     * reflection service will implement this method, and it's not guaranteed
+     * that this method will provide all extensions. Returns
+     * StatusCode::UNIMPLEMENTED if it's not implemented.
+     * This field should be a fully-qualified type name. The format is
+     * &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string all_extension_numbers_of_type = 6;</code>
+     */
+    public java.lang.String getAllExtensionNumbersOfType() {
+      java.lang.Object ref = "";
+      if (messageRequestCase_ == 6) {
+        ref = messageRequest_;
+      }
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (messageRequestCase_ == 6) {
+          messageRequest_ = s;
+        }
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Finds the tag numbers used by all known extensions of extendee_type, and
+     * appends them to ExtensionNumberResponse in an undefined order.
+     * Its corresponding method is best-effort: it's not guaranteed that the
+     * reflection service will implement this method, and it's not guaranteed
+     * that this method will provide all extensions. Returns
+     * StatusCode::UNIMPLEMENTED if it's not implemented.
+     * This field should be a fully-qualified type name. The format is
+     * &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string all_extension_numbers_of_type = 6;</code>
+     */
+    public com.google.protobuf.ByteString
+        getAllExtensionNumbersOfTypeBytes() {
+      java.lang.Object ref = "";
+      if (messageRequestCase_ == 6) {
+        ref = messageRequest_;
+      }
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        if (messageRequestCase_ == 6) {
+          messageRequest_ = b;
+        }
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Finds the tag numbers used by all known extensions of extendee_type, and
+     * appends them to ExtensionNumberResponse in an undefined order.
+     * Its corresponding method is best-effort: it's not guaranteed that the
+     * reflection service will implement this method, and it's not guaranteed
+     * that this method will provide all extensions. Returns
+     * StatusCode::UNIMPLEMENTED if it's not implemented.
+     * This field should be a fully-qualified type name. The format is
+     * &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string all_extension_numbers_of_type = 6;</code>
+     */
+    public Builder setAllExtensionNumbersOfType(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  messageRequestCase_ = 6;
+      messageRequest_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Finds the tag numbers used by all known extensions of extendee_type, and
+     * appends them to ExtensionNumberResponse in an undefined order.
+     * Its corresponding method is best-effort: it's not guaranteed that the
+     * reflection service will implement this method, and it's not guaranteed
+     * that this method will provide all extensions. Returns
+     * StatusCode::UNIMPLEMENTED if it's not implemented.
+     * This field should be a fully-qualified type name. The format is
+     * &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string all_extension_numbers_of_type = 6;</code>
+     */
+    public Builder clearAllExtensionNumbersOfType() {
+      if (messageRequestCase_ == 6) {
+        messageRequestCase_ = 0;
+        messageRequest_ = null;
+        onChanged();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * Finds the tag numbers used by all known extensions of extendee_type, and
+     * appends them to ExtensionNumberResponse in an undefined order.
+     * Its corresponding method is best-effort: it's not guaranteed that the
+     * reflection service will implement this method, and it's not guaranteed
+     * that this method will provide all extensions. Returns
+     * StatusCode::UNIMPLEMENTED if it's not implemented.
+     * This field should be a fully-qualified type name. The format is
+     * &lt;package&gt;.&lt;type&gt;
+     * </pre>
+     *
+     * <code>string all_extension_numbers_of_type = 6;</code>
+     */
+    public Builder setAllExtensionNumbersOfTypeBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      messageRequestCase_ = 6;
+      messageRequest_ = value;
+      onChanged();
+      return this;
+    }
+
+    /**
+     * <pre>
+     * List the full names of registered services. The content will not be
+     * checked.
+     * </pre>
+     *
+     * <code>string list_services = 7;</code>
+     */
+    public java.lang.String getListServices() {
+      java.lang.Object ref = "";
+      if (messageRequestCase_ == 7) {
+        ref = messageRequest_;
+      }
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (messageRequestCase_ == 7) {
+          messageRequest_ = s;
+        }
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * List the full names of registered services. The content will not be
+     * checked.
+     * </pre>
+     *
+     * <code>string list_services = 7;</code>
+     */
+    public com.google.protobuf.ByteString
+        getListServicesBytes() {
+      java.lang.Object ref = "";
+      if (messageRequestCase_ == 7) {
+        ref = messageRequest_;
+      }
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        if (messageRequestCase_ == 7) {
+          messageRequest_ = b;
+        }
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * List the full names of registered services. The content will not be
+     * checked.
+     * </pre>
+     *
+     * <code>string list_services = 7;</code>
+     */
+    public Builder setListServices(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  messageRequestCase_ = 7;
+      messageRequest_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * List the full names of registered services. The content will not be
+     * checked.
+     * </pre>
+     *
+     * <code>string list_services = 7;</code>
+     */
+    public Builder clearListServices() {
+      if (messageRequestCase_ == 7) {
+        messageRequestCase_ = 0;
+        messageRequest_ = null;
+        onChanged();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * List the full names of registered services. The content will not be
+     * checked.
+     * </pre>
+     *
+     * <code>string list_services = 7;</code>
+     */
+    public Builder setListServicesBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      messageRequestCase_ = 7;
+      messageRequest_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.v1alpha.ServerReflectionRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.v1alpha.ServerReflectionRequest)
+  private static final io.grpc.reflection.v1alpha.ServerReflectionRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.v1alpha.ServerReflectionRequest();
+  }
+
+  public static io.grpc.reflection.v1alpha.ServerReflectionRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ServerReflectionRequest>
+      PARSER = new com.google.protobuf.AbstractParser<ServerReflectionRequest>() {
+    public ServerReflectionRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ServerReflectionRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ServerReflectionRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ServerReflectionRequest> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.v1alpha.ServerReflectionRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionRequestOrBuilder.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionRequestOrBuilder.java
new file mode 100644
index 0000000..efd0538
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionRequestOrBuilder.java
@@ -0,0 +1,141 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+public interface ServerReflectionRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.v1alpha.ServerReflectionRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>string host = 1;</code>
+   */
+  java.lang.String getHost();
+  /**
+   * <code>string host = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getHostBytes();
+
+  /**
+   * <pre>
+   * Find a proto file by the file name.
+   * </pre>
+   *
+   * <code>string file_by_filename = 3;</code>
+   */
+  java.lang.String getFileByFilename();
+  /**
+   * <pre>
+   * Find a proto file by the file name.
+   * </pre>
+   *
+   * <code>string file_by_filename = 3;</code>
+   */
+  com.google.protobuf.ByteString
+      getFileByFilenameBytes();
+
+  /**
+   * <pre>
+   * Find the proto file that declares the given fully-qualified symbol name.
+   * This field should be a fully-qualified symbol name
+   * (e.g. &lt;package&gt;.&lt;service&gt;[.&lt;method&gt;] or &lt;package&gt;.&lt;type&gt;).
+   * </pre>
+   *
+   * <code>string file_containing_symbol = 4;</code>
+   */
+  java.lang.String getFileContainingSymbol();
+  /**
+   * <pre>
+   * Find the proto file that declares the given fully-qualified symbol name.
+   * This field should be a fully-qualified symbol name
+   * (e.g. &lt;package&gt;.&lt;service&gt;[.&lt;method&gt;] or &lt;package&gt;.&lt;type&gt;).
+   * </pre>
+   *
+   * <code>string file_containing_symbol = 4;</code>
+   */
+  com.google.protobuf.ByteString
+      getFileContainingSymbolBytes();
+
+  /**
+   * <pre>
+   * Find the proto file which defines an extension extending the given
+   * message type with the given field number.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+   */
+  boolean hasFileContainingExtension();
+  /**
+   * <pre>
+   * Find the proto file which defines an extension extending the given
+   * message type with the given field number.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+   */
+  io.grpc.reflection.v1alpha.ExtensionRequest getFileContainingExtension();
+  /**
+   * <pre>
+   * Find the proto file which defines an extension extending the given
+   * message type with the given field number.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ExtensionRequest file_containing_extension = 5;</code>
+   */
+  io.grpc.reflection.v1alpha.ExtensionRequestOrBuilder getFileContainingExtensionOrBuilder();
+
+  /**
+   * <pre>
+   * Finds the tag numbers used by all known extensions of extendee_type, and
+   * appends them to ExtensionNumberResponse in an undefined order.
+   * Its corresponding method is best-effort: it's not guaranteed that the
+   * reflection service will implement this method, and it's not guaranteed
+   * that this method will provide all extensions. Returns
+   * StatusCode::UNIMPLEMENTED if it's not implemented.
+   * This field should be a fully-qualified type name. The format is
+   * &lt;package&gt;.&lt;type&gt;
+   * </pre>
+   *
+   * <code>string all_extension_numbers_of_type = 6;</code>
+   */
+  java.lang.String getAllExtensionNumbersOfType();
+  /**
+   * <pre>
+   * Finds the tag numbers used by all known extensions of extendee_type, and
+   * appends them to ExtensionNumberResponse in an undefined order.
+   * Its corresponding method is best-effort: it's not guaranteed that the
+   * reflection service will implement this method, and it's not guaranteed
+   * that this method will provide all extensions. Returns
+   * StatusCode::UNIMPLEMENTED if it's not implemented.
+   * This field should be a fully-qualified type name. The format is
+   * &lt;package&gt;.&lt;type&gt;
+   * </pre>
+   *
+   * <code>string all_extension_numbers_of_type = 6;</code>
+   */
+  com.google.protobuf.ByteString
+      getAllExtensionNumbersOfTypeBytes();
+
+  /**
+   * <pre>
+   * List the full names of registered services. The content will not be
+   * checked.
+   * </pre>
+   *
+   * <code>string list_services = 7;</code>
+   */
+  java.lang.String getListServices();
+  /**
+   * <pre>
+   * List the full names of registered services. The content will not be
+   * checked.
+   * </pre>
+   *
+   * <code>string list_services = 7;</code>
+   */
+  com.google.protobuf.ByteString
+      getListServicesBytes();
+
+  public io.grpc.reflection.v1alpha.ServerReflectionRequest.MessageRequestCase getMessageRequestCase();
+}
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionResponse.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionResponse.java
new file mode 100644
index 0000000..09254b1
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionResponse.java
@@ -0,0 +1,1841 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+/**
+ * <pre>
+ * The message sent by the server to answer ServerReflectionInfo method.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.reflection.v1alpha.ServerReflectionResponse}
+ */
+public  final class ServerReflectionResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.v1alpha.ServerReflectionResponse)
+    ServerReflectionResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ServerReflectionResponse.newBuilder() to construct.
+  private ServerReflectionResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ServerReflectionResponse() {
+    validHost_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ServerReflectionResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            validHost_ = s;
+            break;
+          }
+          case 18: {
+            io.grpc.reflection.v1alpha.ServerReflectionRequest.Builder subBuilder = null;
+            if (originalRequest_ != null) {
+              subBuilder = originalRequest_.toBuilder();
+            }
+            originalRequest_ = input.readMessage(io.grpc.reflection.v1alpha.ServerReflectionRequest.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom(originalRequest_);
+              originalRequest_ = subBuilder.buildPartial();
+            }
+
+            break;
+          }
+          case 34: {
+            io.grpc.reflection.v1alpha.FileDescriptorResponse.Builder subBuilder = null;
+            if (messageResponseCase_ == 4) {
+              subBuilder = ((io.grpc.reflection.v1alpha.FileDescriptorResponse) messageResponse_).toBuilder();
+            }
+            messageResponse_ =
+                input.readMessage(io.grpc.reflection.v1alpha.FileDescriptorResponse.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.reflection.v1alpha.FileDescriptorResponse) messageResponse_);
+              messageResponse_ = subBuilder.buildPartial();
+            }
+            messageResponseCase_ = 4;
+            break;
+          }
+          case 42: {
+            io.grpc.reflection.v1alpha.ExtensionNumberResponse.Builder subBuilder = null;
+            if (messageResponseCase_ == 5) {
+              subBuilder = ((io.grpc.reflection.v1alpha.ExtensionNumberResponse) messageResponse_).toBuilder();
+            }
+            messageResponse_ =
+                input.readMessage(io.grpc.reflection.v1alpha.ExtensionNumberResponse.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.reflection.v1alpha.ExtensionNumberResponse) messageResponse_);
+              messageResponse_ = subBuilder.buildPartial();
+            }
+            messageResponseCase_ = 5;
+            break;
+          }
+          case 50: {
+            io.grpc.reflection.v1alpha.ListServiceResponse.Builder subBuilder = null;
+            if (messageResponseCase_ == 6) {
+              subBuilder = ((io.grpc.reflection.v1alpha.ListServiceResponse) messageResponse_).toBuilder();
+            }
+            messageResponse_ =
+                input.readMessage(io.grpc.reflection.v1alpha.ListServiceResponse.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.reflection.v1alpha.ListServiceResponse) messageResponse_);
+              messageResponse_ = subBuilder.buildPartial();
+            }
+            messageResponseCase_ = 6;
+            break;
+          }
+          case 58: {
+            io.grpc.reflection.v1alpha.ErrorResponse.Builder subBuilder = null;
+            if (messageResponseCase_ == 7) {
+              subBuilder = ((io.grpc.reflection.v1alpha.ErrorResponse) messageResponse_).toBuilder();
+            }
+            messageResponse_ =
+                input.readMessage(io.grpc.reflection.v1alpha.ErrorResponse.parser(), extensionRegistry);
+            if (subBuilder != null) {
+              subBuilder.mergeFrom((io.grpc.reflection.v1alpha.ErrorResponse) messageResponse_);
+              messageResponse_ = subBuilder.buildPartial();
+            }
+            messageResponseCase_ = 7;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServerReflectionResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServerReflectionResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.v1alpha.ServerReflectionResponse.class, io.grpc.reflection.v1alpha.ServerReflectionResponse.Builder.class);
+  }
+
+  private int messageResponseCase_ = 0;
+  private java.lang.Object messageResponse_;
+  public enum MessageResponseCase
+      implements com.google.protobuf.Internal.EnumLite {
+    FILE_DESCRIPTOR_RESPONSE(4),
+    ALL_EXTENSION_NUMBERS_RESPONSE(5),
+    LIST_SERVICES_RESPONSE(6),
+    ERROR_RESPONSE(7),
+    MESSAGERESPONSE_NOT_SET(0);
+    private final int value;
+    private MessageResponseCase(int value) {
+      this.value = value;
+    }
+    /**
+     * @deprecated Use {@link #forNumber(int)} instead.
+     */
+    @java.lang.Deprecated
+    public static MessageResponseCase valueOf(int value) {
+      return forNumber(value);
+    }
+
+    public static MessageResponseCase forNumber(int value) {
+      switch (value) {
+        case 4: return FILE_DESCRIPTOR_RESPONSE;
+        case 5: return ALL_EXTENSION_NUMBERS_RESPONSE;
+        case 6: return LIST_SERVICES_RESPONSE;
+        case 7: return ERROR_RESPONSE;
+        case 0: return MESSAGERESPONSE_NOT_SET;
+        default: return null;
+      }
+    }
+    public int getNumber() {
+      return this.value;
+    }
+  };
+
+  public MessageResponseCase
+  getMessageResponseCase() {
+    return MessageResponseCase.forNumber(
+        messageResponseCase_);
+  }
+
+  public static final int VALID_HOST_FIELD_NUMBER = 1;
+  private volatile java.lang.Object validHost_;
+  /**
+   * <code>string valid_host = 1;</code>
+   */
+  public java.lang.String getValidHost() {
+    java.lang.Object ref = validHost_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      validHost_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string valid_host = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getValidHostBytes() {
+    java.lang.Object ref = validHost_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      validHost_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  public static final int ORIGINAL_REQUEST_FIELD_NUMBER = 2;
+  private io.grpc.reflection.v1alpha.ServerReflectionRequest originalRequest_;
+  /**
+   * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+   */
+  public boolean hasOriginalRequest() {
+    return originalRequest_ != null;
+  }
+  /**
+   * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+   */
+  public io.grpc.reflection.v1alpha.ServerReflectionRequest getOriginalRequest() {
+    return originalRequest_ == null ? io.grpc.reflection.v1alpha.ServerReflectionRequest.getDefaultInstance() : originalRequest_;
+  }
+  /**
+   * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+   */
+  public io.grpc.reflection.v1alpha.ServerReflectionRequestOrBuilder getOriginalRequestOrBuilder() {
+    return getOriginalRequest();
+  }
+
+  public static final int FILE_DESCRIPTOR_RESPONSE_FIELD_NUMBER = 4;
+  /**
+   * <pre>
+   * This message is used to answer file_by_filename, file_containing_symbol,
+   * file_containing_extension requests with transitive dependencies. As
+   * the repeated label is not allowed in oneof fields, we use a
+   * FileDescriptorResponse message to encapsulate the repeated fields.
+   * The reflection service is allowed to avoid sending FileDescriptorProtos
+   * that were previously sent in response to earlier requests in the stream.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+   */
+  public boolean hasFileDescriptorResponse() {
+    return messageResponseCase_ == 4;
+  }
+  /**
+   * <pre>
+   * This message is used to answer file_by_filename, file_containing_symbol,
+   * file_containing_extension requests with transitive dependencies. As
+   * the repeated label is not allowed in oneof fields, we use a
+   * FileDescriptorResponse message to encapsulate the repeated fields.
+   * The reflection service is allowed to avoid sending FileDescriptorProtos
+   * that were previously sent in response to earlier requests in the stream.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+   */
+  public io.grpc.reflection.v1alpha.FileDescriptorResponse getFileDescriptorResponse() {
+    if (messageResponseCase_ == 4) {
+       return (io.grpc.reflection.v1alpha.FileDescriptorResponse) messageResponse_;
+    }
+    return io.grpc.reflection.v1alpha.FileDescriptorResponse.getDefaultInstance();
+  }
+  /**
+   * <pre>
+   * This message is used to answer file_by_filename, file_containing_symbol,
+   * file_containing_extension requests with transitive dependencies. As
+   * the repeated label is not allowed in oneof fields, we use a
+   * FileDescriptorResponse message to encapsulate the repeated fields.
+   * The reflection service is allowed to avoid sending FileDescriptorProtos
+   * that were previously sent in response to earlier requests in the stream.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+   */
+  public io.grpc.reflection.v1alpha.FileDescriptorResponseOrBuilder getFileDescriptorResponseOrBuilder() {
+    if (messageResponseCase_ == 4) {
+       return (io.grpc.reflection.v1alpha.FileDescriptorResponse) messageResponse_;
+    }
+    return io.grpc.reflection.v1alpha.FileDescriptorResponse.getDefaultInstance();
+  }
+
+  public static final int ALL_EXTENSION_NUMBERS_RESPONSE_FIELD_NUMBER = 5;
+  /**
+   * <pre>
+   * This message is used to answer all_extension_numbers_of_type requst.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+   */
+  public boolean hasAllExtensionNumbersResponse() {
+    return messageResponseCase_ == 5;
+  }
+  /**
+   * <pre>
+   * This message is used to answer all_extension_numbers_of_type requst.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+   */
+  public io.grpc.reflection.v1alpha.ExtensionNumberResponse getAllExtensionNumbersResponse() {
+    if (messageResponseCase_ == 5) {
+       return (io.grpc.reflection.v1alpha.ExtensionNumberResponse) messageResponse_;
+    }
+    return io.grpc.reflection.v1alpha.ExtensionNumberResponse.getDefaultInstance();
+  }
+  /**
+   * <pre>
+   * This message is used to answer all_extension_numbers_of_type requst.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+   */
+  public io.grpc.reflection.v1alpha.ExtensionNumberResponseOrBuilder getAllExtensionNumbersResponseOrBuilder() {
+    if (messageResponseCase_ == 5) {
+       return (io.grpc.reflection.v1alpha.ExtensionNumberResponse) messageResponse_;
+    }
+    return io.grpc.reflection.v1alpha.ExtensionNumberResponse.getDefaultInstance();
+  }
+
+  public static final int LIST_SERVICES_RESPONSE_FIELD_NUMBER = 6;
+  /**
+   * <pre>
+   * This message is used to answer list_services request.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+   */
+  public boolean hasListServicesResponse() {
+    return messageResponseCase_ == 6;
+  }
+  /**
+   * <pre>
+   * This message is used to answer list_services request.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+   */
+  public io.grpc.reflection.v1alpha.ListServiceResponse getListServicesResponse() {
+    if (messageResponseCase_ == 6) {
+       return (io.grpc.reflection.v1alpha.ListServiceResponse) messageResponse_;
+    }
+    return io.grpc.reflection.v1alpha.ListServiceResponse.getDefaultInstance();
+  }
+  /**
+   * <pre>
+   * This message is used to answer list_services request.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+   */
+  public io.grpc.reflection.v1alpha.ListServiceResponseOrBuilder getListServicesResponseOrBuilder() {
+    if (messageResponseCase_ == 6) {
+       return (io.grpc.reflection.v1alpha.ListServiceResponse) messageResponse_;
+    }
+    return io.grpc.reflection.v1alpha.ListServiceResponse.getDefaultInstance();
+  }
+
+  public static final int ERROR_RESPONSE_FIELD_NUMBER = 7;
+  /**
+   * <pre>
+   * This message is used when an error occurs.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+   */
+  public boolean hasErrorResponse() {
+    return messageResponseCase_ == 7;
+  }
+  /**
+   * <pre>
+   * This message is used when an error occurs.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+   */
+  public io.grpc.reflection.v1alpha.ErrorResponse getErrorResponse() {
+    if (messageResponseCase_ == 7) {
+       return (io.grpc.reflection.v1alpha.ErrorResponse) messageResponse_;
+    }
+    return io.grpc.reflection.v1alpha.ErrorResponse.getDefaultInstance();
+  }
+  /**
+   * <pre>
+   * This message is used when an error occurs.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+   */
+  public io.grpc.reflection.v1alpha.ErrorResponseOrBuilder getErrorResponseOrBuilder() {
+    if (messageResponseCase_ == 7) {
+       return (io.grpc.reflection.v1alpha.ErrorResponse) messageResponse_;
+    }
+    return io.grpc.reflection.v1alpha.ErrorResponse.getDefaultInstance();
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getValidHostBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, validHost_);
+    }
+    if (originalRequest_ != null) {
+      output.writeMessage(2, getOriginalRequest());
+    }
+    if (messageResponseCase_ == 4) {
+      output.writeMessage(4, (io.grpc.reflection.v1alpha.FileDescriptorResponse) messageResponse_);
+    }
+    if (messageResponseCase_ == 5) {
+      output.writeMessage(5, (io.grpc.reflection.v1alpha.ExtensionNumberResponse) messageResponse_);
+    }
+    if (messageResponseCase_ == 6) {
+      output.writeMessage(6, (io.grpc.reflection.v1alpha.ListServiceResponse) messageResponse_);
+    }
+    if (messageResponseCase_ == 7) {
+      output.writeMessage(7, (io.grpc.reflection.v1alpha.ErrorResponse) messageResponse_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getValidHostBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, validHost_);
+    }
+    if (originalRequest_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(2, getOriginalRequest());
+    }
+    if (messageResponseCase_ == 4) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(4, (io.grpc.reflection.v1alpha.FileDescriptorResponse) messageResponse_);
+    }
+    if (messageResponseCase_ == 5) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(5, (io.grpc.reflection.v1alpha.ExtensionNumberResponse) messageResponse_);
+    }
+    if (messageResponseCase_ == 6) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(6, (io.grpc.reflection.v1alpha.ListServiceResponse) messageResponse_);
+    }
+    if (messageResponseCase_ == 7) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(7, (io.grpc.reflection.v1alpha.ErrorResponse) messageResponse_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.v1alpha.ServerReflectionResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.v1alpha.ServerReflectionResponse other = (io.grpc.reflection.v1alpha.ServerReflectionResponse) obj;
+
+    boolean result = true;
+    result = result && getValidHost()
+        .equals(other.getValidHost());
+    result = result && (hasOriginalRequest() == other.hasOriginalRequest());
+    if (hasOriginalRequest()) {
+      result = result && getOriginalRequest()
+          .equals(other.getOriginalRequest());
+    }
+    result = result && getMessageResponseCase().equals(
+        other.getMessageResponseCase());
+    if (!result) return false;
+    switch (messageResponseCase_) {
+      case 4:
+        result = result && getFileDescriptorResponse()
+            .equals(other.getFileDescriptorResponse());
+        break;
+      case 5:
+        result = result && getAllExtensionNumbersResponse()
+            .equals(other.getAllExtensionNumbersResponse());
+        break;
+      case 6:
+        result = result && getListServicesResponse()
+            .equals(other.getListServicesResponse());
+        break;
+      case 7:
+        result = result && getErrorResponse()
+            .equals(other.getErrorResponse());
+        break;
+      case 0:
+      default:
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + VALID_HOST_FIELD_NUMBER;
+    hash = (53 * hash) + getValidHost().hashCode();
+    if (hasOriginalRequest()) {
+      hash = (37 * hash) + ORIGINAL_REQUEST_FIELD_NUMBER;
+      hash = (53 * hash) + getOriginalRequest().hashCode();
+    }
+    switch (messageResponseCase_) {
+      case 4:
+        hash = (37 * hash) + FILE_DESCRIPTOR_RESPONSE_FIELD_NUMBER;
+        hash = (53 * hash) + getFileDescriptorResponse().hashCode();
+        break;
+      case 5:
+        hash = (37 * hash) + ALL_EXTENSION_NUMBERS_RESPONSE_FIELD_NUMBER;
+        hash = (53 * hash) + getAllExtensionNumbersResponse().hashCode();
+        break;
+      case 6:
+        hash = (37 * hash) + LIST_SERVICES_RESPONSE_FIELD_NUMBER;
+        hash = (53 * hash) + getListServicesResponse().hashCode();
+        break;
+      case 7:
+        hash = (37 * hash) + ERROR_RESPONSE_FIELD_NUMBER;
+        hash = (53 * hash) + getErrorResponse().hashCode();
+        break;
+      case 0:
+      default:
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.v1alpha.ServerReflectionResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ServerReflectionResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.v1alpha.ServerReflectionResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * The message sent by the server to answer ServerReflectionInfo method.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.reflection.v1alpha.ServerReflectionResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.v1alpha.ServerReflectionResponse)
+      io.grpc.reflection.v1alpha.ServerReflectionResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServerReflectionResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServerReflectionResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.v1alpha.ServerReflectionResponse.class, io.grpc.reflection.v1alpha.ServerReflectionResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.v1alpha.ServerReflectionResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      validHost_ = "";
+
+      if (originalRequestBuilder_ == null) {
+        originalRequest_ = null;
+      } else {
+        originalRequest_ = null;
+        originalRequestBuilder_ = null;
+      }
+      messageResponseCase_ = 0;
+      messageResponse_ = null;
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServerReflectionResponse_descriptor;
+    }
+
+    public io.grpc.reflection.v1alpha.ServerReflectionResponse getDefaultInstanceForType() {
+      return io.grpc.reflection.v1alpha.ServerReflectionResponse.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.v1alpha.ServerReflectionResponse build() {
+      io.grpc.reflection.v1alpha.ServerReflectionResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.v1alpha.ServerReflectionResponse buildPartial() {
+      io.grpc.reflection.v1alpha.ServerReflectionResponse result = new io.grpc.reflection.v1alpha.ServerReflectionResponse(this);
+      result.validHost_ = validHost_;
+      if (originalRequestBuilder_ == null) {
+        result.originalRequest_ = originalRequest_;
+      } else {
+        result.originalRequest_ = originalRequestBuilder_.build();
+      }
+      if (messageResponseCase_ == 4) {
+        if (fileDescriptorResponseBuilder_ == null) {
+          result.messageResponse_ = messageResponse_;
+        } else {
+          result.messageResponse_ = fileDescriptorResponseBuilder_.build();
+        }
+      }
+      if (messageResponseCase_ == 5) {
+        if (allExtensionNumbersResponseBuilder_ == null) {
+          result.messageResponse_ = messageResponse_;
+        } else {
+          result.messageResponse_ = allExtensionNumbersResponseBuilder_.build();
+        }
+      }
+      if (messageResponseCase_ == 6) {
+        if (listServicesResponseBuilder_ == null) {
+          result.messageResponse_ = messageResponse_;
+        } else {
+          result.messageResponse_ = listServicesResponseBuilder_.build();
+        }
+      }
+      if (messageResponseCase_ == 7) {
+        if (errorResponseBuilder_ == null) {
+          result.messageResponse_ = messageResponse_;
+        } else {
+          result.messageResponse_ = errorResponseBuilder_.build();
+        }
+      }
+      result.messageResponseCase_ = messageResponseCase_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.v1alpha.ServerReflectionResponse) {
+        return mergeFrom((io.grpc.reflection.v1alpha.ServerReflectionResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.v1alpha.ServerReflectionResponse other) {
+      if (other == io.grpc.reflection.v1alpha.ServerReflectionResponse.getDefaultInstance()) return this;
+      if (!other.getValidHost().isEmpty()) {
+        validHost_ = other.validHost_;
+        onChanged();
+      }
+      if (other.hasOriginalRequest()) {
+        mergeOriginalRequest(other.getOriginalRequest());
+      }
+      switch (other.getMessageResponseCase()) {
+        case FILE_DESCRIPTOR_RESPONSE: {
+          mergeFileDescriptorResponse(other.getFileDescriptorResponse());
+          break;
+        }
+        case ALL_EXTENSION_NUMBERS_RESPONSE: {
+          mergeAllExtensionNumbersResponse(other.getAllExtensionNumbersResponse());
+          break;
+        }
+        case LIST_SERVICES_RESPONSE: {
+          mergeListServicesResponse(other.getListServicesResponse());
+          break;
+        }
+        case ERROR_RESPONSE: {
+          mergeErrorResponse(other.getErrorResponse());
+          break;
+        }
+        case MESSAGERESPONSE_NOT_SET: {
+          break;
+        }
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.v1alpha.ServerReflectionResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.v1alpha.ServerReflectionResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int messageResponseCase_ = 0;
+    private java.lang.Object messageResponse_;
+    public MessageResponseCase
+        getMessageResponseCase() {
+      return MessageResponseCase.forNumber(
+          messageResponseCase_);
+    }
+
+    public Builder clearMessageResponse() {
+      messageResponseCase_ = 0;
+      messageResponse_ = null;
+      onChanged();
+      return this;
+    }
+
+
+    private java.lang.Object validHost_ = "";
+    /**
+     * <code>string valid_host = 1;</code>
+     */
+    public java.lang.String getValidHost() {
+      java.lang.Object ref = validHost_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        validHost_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>string valid_host = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getValidHostBytes() {
+      java.lang.Object ref = validHost_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        validHost_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string valid_host = 1;</code>
+     */
+    public Builder setValidHost(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      validHost_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string valid_host = 1;</code>
+     */
+    public Builder clearValidHost() {
+      
+      validHost_ = getDefaultInstance().getValidHost();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string valid_host = 1;</code>
+     */
+    public Builder setValidHostBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      validHost_ = value;
+      onChanged();
+      return this;
+    }
+
+    private io.grpc.reflection.v1alpha.ServerReflectionRequest originalRequest_ = null;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.reflection.v1alpha.ServerReflectionRequest, io.grpc.reflection.v1alpha.ServerReflectionRequest.Builder, io.grpc.reflection.v1alpha.ServerReflectionRequestOrBuilder> originalRequestBuilder_;
+    /**
+     * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+     */
+    public boolean hasOriginalRequest() {
+      return originalRequestBuilder_ != null || originalRequest_ != null;
+    }
+    /**
+     * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+     */
+    public io.grpc.reflection.v1alpha.ServerReflectionRequest getOriginalRequest() {
+      if (originalRequestBuilder_ == null) {
+        return originalRequest_ == null ? io.grpc.reflection.v1alpha.ServerReflectionRequest.getDefaultInstance() : originalRequest_;
+      } else {
+        return originalRequestBuilder_.getMessage();
+      }
+    }
+    /**
+     * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+     */
+    public Builder setOriginalRequest(io.grpc.reflection.v1alpha.ServerReflectionRequest value) {
+      if (originalRequestBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        originalRequest_ = value;
+        onChanged();
+      } else {
+        originalRequestBuilder_.setMessage(value);
+      }
+
+      return this;
+    }
+    /**
+     * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+     */
+    public Builder setOriginalRequest(
+        io.grpc.reflection.v1alpha.ServerReflectionRequest.Builder builderForValue) {
+      if (originalRequestBuilder_ == null) {
+        originalRequest_ = builderForValue.build();
+        onChanged();
+      } else {
+        originalRequestBuilder_.setMessage(builderForValue.build());
+      }
+
+      return this;
+    }
+    /**
+     * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+     */
+    public Builder mergeOriginalRequest(io.grpc.reflection.v1alpha.ServerReflectionRequest value) {
+      if (originalRequestBuilder_ == null) {
+        if (originalRequest_ != null) {
+          originalRequest_ =
+            io.grpc.reflection.v1alpha.ServerReflectionRequest.newBuilder(originalRequest_).mergeFrom(value).buildPartial();
+        } else {
+          originalRequest_ = value;
+        }
+        onChanged();
+      } else {
+        originalRequestBuilder_.mergeFrom(value);
+      }
+
+      return this;
+    }
+    /**
+     * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+     */
+    public Builder clearOriginalRequest() {
+      if (originalRequestBuilder_ == null) {
+        originalRequest_ = null;
+        onChanged();
+      } else {
+        originalRequest_ = null;
+        originalRequestBuilder_ = null;
+      }
+
+      return this;
+    }
+    /**
+     * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+     */
+    public io.grpc.reflection.v1alpha.ServerReflectionRequest.Builder getOriginalRequestBuilder() {
+      
+      onChanged();
+      return getOriginalRequestFieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+     */
+    public io.grpc.reflection.v1alpha.ServerReflectionRequestOrBuilder getOriginalRequestOrBuilder() {
+      if (originalRequestBuilder_ != null) {
+        return originalRequestBuilder_.getMessageOrBuilder();
+      } else {
+        return originalRequest_ == null ?
+            io.grpc.reflection.v1alpha.ServerReflectionRequest.getDefaultInstance() : originalRequest_;
+      }
+    }
+    /**
+     * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.reflection.v1alpha.ServerReflectionRequest, io.grpc.reflection.v1alpha.ServerReflectionRequest.Builder, io.grpc.reflection.v1alpha.ServerReflectionRequestOrBuilder> 
+        getOriginalRequestFieldBuilder() {
+      if (originalRequestBuilder_ == null) {
+        originalRequestBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.reflection.v1alpha.ServerReflectionRequest, io.grpc.reflection.v1alpha.ServerReflectionRequest.Builder, io.grpc.reflection.v1alpha.ServerReflectionRequestOrBuilder>(
+                getOriginalRequest(),
+                getParentForChildren(),
+                isClean());
+        originalRequest_ = null;
+      }
+      return originalRequestBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.reflection.v1alpha.FileDescriptorResponse, io.grpc.reflection.v1alpha.FileDescriptorResponse.Builder, io.grpc.reflection.v1alpha.FileDescriptorResponseOrBuilder> fileDescriptorResponseBuilder_;
+    /**
+     * <pre>
+     * This message is used to answer file_by_filename, file_containing_symbol,
+     * file_containing_extension requests with transitive dependencies. As
+     * the repeated label is not allowed in oneof fields, we use a
+     * FileDescriptorResponse message to encapsulate the repeated fields.
+     * The reflection service is allowed to avoid sending FileDescriptorProtos
+     * that were previously sent in response to earlier requests in the stream.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+     */
+    public boolean hasFileDescriptorResponse() {
+      return messageResponseCase_ == 4;
+    }
+    /**
+     * <pre>
+     * This message is used to answer file_by_filename, file_containing_symbol,
+     * file_containing_extension requests with transitive dependencies. As
+     * the repeated label is not allowed in oneof fields, we use a
+     * FileDescriptorResponse message to encapsulate the repeated fields.
+     * The reflection service is allowed to avoid sending FileDescriptorProtos
+     * that were previously sent in response to earlier requests in the stream.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+     */
+    public io.grpc.reflection.v1alpha.FileDescriptorResponse getFileDescriptorResponse() {
+      if (fileDescriptorResponseBuilder_ == null) {
+        if (messageResponseCase_ == 4) {
+          return (io.grpc.reflection.v1alpha.FileDescriptorResponse) messageResponse_;
+        }
+        return io.grpc.reflection.v1alpha.FileDescriptorResponse.getDefaultInstance();
+      } else {
+        if (messageResponseCase_ == 4) {
+          return fileDescriptorResponseBuilder_.getMessage();
+        }
+        return io.grpc.reflection.v1alpha.FileDescriptorResponse.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * This message is used to answer file_by_filename, file_containing_symbol,
+     * file_containing_extension requests with transitive dependencies. As
+     * the repeated label is not allowed in oneof fields, we use a
+     * FileDescriptorResponse message to encapsulate the repeated fields.
+     * The reflection service is allowed to avoid sending FileDescriptorProtos
+     * that were previously sent in response to earlier requests in the stream.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+     */
+    public Builder setFileDescriptorResponse(io.grpc.reflection.v1alpha.FileDescriptorResponse value) {
+      if (fileDescriptorResponseBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        messageResponse_ = value;
+        onChanged();
+      } else {
+        fileDescriptorResponseBuilder_.setMessage(value);
+      }
+      messageResponseCase_ = 4;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used to answer file_by_filename, file_containing_symbol,
+     * file_containing_extension requests with transitive dependencies. As
+     * the repeated label is not allowed in oneof fields, we use a
+     * FileDescriptorResponse message to encapsulate the repeated fields.
+     * The reflection service is allowed to avoid sending FileDescriptorProtos
+     * that were previously sent in response to earlier requests in the stream.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+     */
+    public Builder setFileDescriptorResponse(
+        io.grpc.reflection.v1alpha.FileDescriptorResponse.Builder builderForValue) {
+      if (fileDescriptorResponseBuilder_ == null) {
+        messageResponse_ = builderForValue.build();
+        onChanged();
+      } else {
+        fileDescriptorResponseBuilder_.setMessage(builderForValue.build());
+      }
+      messageResponseCase_ = 4;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used to answer file_by_filename, file_containing_symbol,
+     * file_containing_extension requests with transitive dependencies. As
+     * the repeated label is not allowed in oneof fields, we use a
+     * FileDescriptorResponse message to encapsulate the repeated fields.
+     * The reflection service is allowed to avoid sending FileDescriptorProtos
+     * that were previously sent in response to earlier requests in the stream.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+     */
+    public Builder mergeFileDescriptorResponse(io.grpc.reflection.v1alpha.FileDescriptorResponse value) {
+      if (fileDescriptorResponseBuilder_ == null) {
+        if (messageResponseCase_ == 4 &&
+            messageResponse_ != io.grpc.reflection.v1alpha.FileDescriptorResponse.getDefaultInstance()) {
+          messageResponse_ = io.grpc.reflection.v1alpha.FileDescriptorResponse.newBuilder((io.grpc.reflection.v1alpha.FileDescriptorResponse) messageResponse_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          messageResponse_ = value;
+        }
+        onChanged();
+      } else {
+        if (messageResponseCase_ == 4) {
+          fileDescriptorResponseBuilder_.mergeFrom(value);
+        }
+        fileDescriptorResponseBuilder_.setMessage(value);
+      }
+      messageResponseCase_ = 4;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used to answer file_by_filename, file_containing_symbol,
+     * file_containing_extension requests with transitive dependencies. As
+     * the repeated label is not allowed in oneof fields, we use a
+     * FileDescriptorResponse message to encapsulate the repeated fields.
+     * The reflection service is allowed to avoid sending FileDescriptorProtos
+     * that were previously sent in response to earlier requests in the stream.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+     */
+    public Builder clearFileDescriptorResponse() {
+      if (fileDescriptorResponseBuilder_ == null) {
+        if (messageResponseCase_ == 4) {
+          messageResponseCase_ = 0;
+          messageResponse_ = null;
+          onChanged();
+        }
+      } else {
+        if (messageResponseCase_ == 4) {
+          messageResponseCase_ = 0;
+          messageResponse_ = null;
+        }
+        fileDescriptorResponseBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used to answer file_by_filename, file_containing_symbol,
+     * file_containing_extension requests with transitive dependencies. As
+     * the repeated label is not allowed in oneof fields, we use a
+     * FileDescriptorResponse message to encapsulate the repeated fields.
+     * The reflection service is allowed to avoid sending FileDescriptorProtos
+     * that were previously sent in response to earlier requests in the stream.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+     */
+    public io.grpc.reflection.v1alpha.FileDescriptorResponse.Builder getFileDescriptorResponseBuilder() {
+      return getFileDescriptorResponseFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * This message is used to answer file_by_filename, file_containing_symbol,
+     * file_containing_extension requests with transitive dependencies. As
+     * the repeated label is not allowed in oneof fields, we use a
+     * FileDescriptorResponse message to encapsulate the repeated fields.
+     * The reflection service is allowed to avoid sending FileDescriptorProtos
+     * that were previously sent in response to earlier requests in the stream.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+     */
+    public io.grpc.reflection.v1alpha.FileDescriptorResponseOrBuilder getFileDescriptorResponseOrBuilder() {
+      if ((messageResponseCase_ == 4) && (fileDescriptorResponseBuilder_ != null)) {
+        return fileDescriptorResponseBuilder_.getMessageOrBuilder();
+      } else {
+        if (messageResponseCase_ == 4) {
+          return (io.grpc.reflection.v1alpha.FileDescriptorResponse) messageResponse_;
+        }
+        return io.grpc.reflection.v1alpha.FileDescriptorResponse.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * This message is used to answer file_by_filename, file_containing_symbol,
+     * file_containing_extension requests with transitive dependencies. As
+     * the repeated label is not allowed in oneof fields, we use a
+     * FileDescriptorResponse message to encapsulate the repeated fields.
+     * The reflection service is allowed to avoid sending FileDescriptorProtos
+     * that were previously sent in response to earlier requests in the stream.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.reflection.v1alpha.FileDescriptorResponse, io.grpc.reflection.v1alpha.FileDescriptorResponse.Builder, io.grpc.reflection.v1alpha.FileDescriptorResponseOrBuilder> 
+        getFileDescriptorResponseFieldBuilder() {
+      if (fileDescriptorResponseBuilder_ == null) {
+        if (!(messageResponseCase_ == 4)) {
+          messageResponse_ = io.grpc.reflection.v1alpha.FileDescriptorResponse.getDefaultInstance();
+        }
+        fileDescriptorResponseBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.reflection.v1alpha.FileDescriptorResponse, io.grpc.reflection.v1alpha.FileDescriptorResponse.Builder, io.grpc.reflection.v1alpha.FileDescriptorResponseOrBuilder>(
+                (io.grpc.reflection.v1alpha.FileDescriptorResponse) messageResponse_,
+                getParentForChildren(),
+                isClean());
+        messageResponse_ = null;
+      }
+      messageResponseCase_ = 4;
+      onChanged();;
+      return fileDescriptorResponseBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.reflection.v1alpha.ExtensionNumberResponse, io.grpc.reflection.v1alpha.ExtensionNumberResponse.Builder, io.grpc.reflection.v1alpha.ExtensionNumberResponseOrBuilder> allExtensionNumbersResponseBuilder_;
+    /**
+     * <pre>
+     * This message is used to answer all_extension_numbers_of_type requst.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+     */
+    public boolean hasAllExtensionNumbersResponse() {
+      return messageResponseCase_ == 5;
+    }
+    /**
+     * <pre>
+     * This message is used to answer all_extension_numbers_of_type requst.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+     */
+    public io.grpc.reflection.v1alpha.ExtensionNumberResponse getAllExtensionNumbersResponse() {
+      if (allExtensionNumbersResponseBuilder_ == null) {
+        if (messageResponseCase_ == 5) {
+          return (io.grpc.reflection.v1alpha.ExtensionNumberResponse) messageResponse_;
+        }
+        return io.grpc.reflection.v1alpha.ExtensionNumberResponse.getDefaultInstance();
+      } else {
+        if (messageResponseCase_ == 5) {
+          return allExtensionNumbersResponseBuilder_.getMessage();
+        }
+        return io.grpc.reflection.v1alpha.ExtensionNumberResponse.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * This message is used to answer all_extension_numbers_of_type requst.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+     */
+    public Builder setAllExtensionNumbersResponse(io.grpc.reflection.v1alpha.ExtensionNumberResponse value) {
+      if (allExtensionNumbersResponseBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        messageResponse_ = value;
+        onChanged();
+      } else {
+        allExtensionNumbersResponseBuilder_.setMessage(value);
+      }
+      messageResponseCase_ = 5;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used to answer all_extension_numbers_of_type requst.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+     */
+    public Builder setAllExtensionNumbersResponse(
+        io.grpc.reflection.v1alpha.ExtensionNumberResponse.Builder builderForValue) {
+      if (allExtensionNumbersResponseBuilder_ == null) {
+        messageResponse_ = builderForValue.build();
+        onChanged();
+      } else {
+        allExtensionNumbersResponseBuilder_.setMessage(builderForValue.build());
+      }
+      messageResponseCase_ = 5;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used to answer all_extension_numbers_of_type requst.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+     */
+    public Builder mergeAllExtensionNumbersResponse(io.grpc.reflection.v1alpha.ExtensionNumberResponse value) {
+      if (allExtensionNumbersResponseBuilder_ == null) {
+        if (messageResponseCase_ == 5 &&
+            messageResponse_ != io.grpc.reflection.v1alpha.ExtensionNumberResponse.getDefaultInstance()) {
+          messageResponse_ = io.grpc.reflection.v1alpha.ExtensionNumberResponse.newBuilder((io.grpc.reflection.v1alpha.ExtensionNumberResponse) messageResponse_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          messageResponse_ = value;
+        }
+        onChanged();
+      } else {
+        if (messageResponseCase_ == 5) {
+          allExtensionNumbersResponseBuilder_.mergeFrom(value);
+        }
+        allExtensionNumbersResponseBuilder_.setMessage(value);
+      }
+      messageResponseCase_ = 5;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used to answer all_extension_numbers_of_type requst.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+     */
+    public Builder clearAllExtensionNumbersResponse() {
+      if (allExtensionNumbersResponseBuilder_ == null) {
+        if (messageResponseCase_ == 5) {
+          messageResponseCase_ = 0;
+          messageResponse_ = null;
+          onChanged();
+        }
+      } else {
+        if (messageResponseCase_ == 5) {
+          messageResponseCase_ = 0;
+          messageResponse_ = null;
+        }
+        allExtensionNumbersResponseBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used to answer all_extension_numbers_of_type requst.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+     */
+    public io.grpc.reflection.v1alpha.ExtensionNumberResponse.Builder getAllExtensionNumbersResponseBuilder() {
+      return getAllExtensionNumbersResponseFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * This message is used to answer all_extension_numbers_of_type requst.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+     */
+    public io.grpc.reflection.v1alpha.ExtensionNumberResponseOrBuilder getAllExtensionNumbersResponseOrBuilder() {
+      if ((messageResponseCase_ == 5) && (allExtensionNumbersResponseBuilder_ != null)) {
+        return allExtensionNumbersResponseBuilder_.getMessageOrBuilder();
+      } else {
+        if (messageResponseCase_ == 5) {
+          return (io.grpc.reflection.v1alpha.ExtensionNumberResponse) messageResponse_;
+        }
+        return io.grpc.reflection.v1alpha.ExtensionNumberResponse.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * This message is used to answer all_extension_numbers_of_type requst.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.reflection.v1alpha.ExtensionNumberResponse, io.grpc.reflection.v1alpha.ExtensionNumberResponse.Builder, io.grpc.reflection.v1alpha.ExtensionNumberResponseOrBuilder> 
+        getAllExtensionNumbersResponseFieldBuilder() {
+      if (allExtensionNumbersResponseBuilder_ == null) {
+        if (!(messageResponseCase_ == 5)) {
+          messageResponse_ = io.grpc.reflection.v1alpha.ExtensionNumberResponse.getDefaultInstance();
+        }
+        allExtensionNumbersResponseBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.reflection.v1alpha.ExtensionNumberResponse, io.grpc.reflection.v1alpha.ExtensionNumberResponse.Builder, io.grpc.reflection.v1alpha.ExtensionNumberResponseOrBuilder>(
+                (io.grpc.reflection.v1alpha.ExtensionNumberResponse) messageResponse_,
+                getParentForChildren(),
+                isClean());
+        messageResponse_ = null;
+      }
+      messageResponseCase_ = 5;
+      onChanged();;
+      return allExtensionNumbersResponseBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.reflection.v1alpha.ListServiceResponse, io.grpc.reflection.v1alpha.ListServiceResponse.Builder, io.grpc.reflection.v1alpha.ListServiceResponseOrBuilder> listServicesResponseBuilder_;
+    /**
+     * <pre>
+     * This message is used to answer list_services request.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+     */
+    public boolean hasListServicesResponse() {
+      return messageResponseCase_ == 6;
+    }
+    /**
+     * <pre>
+     * This message is used to answer list_services request.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+     */
+    public io.grpc.reflection.v1alpha.ListServiceResponse getListServicesResponse() {
+      if (listServicesResponseBuilder_ == null) {
+        if (messageResponseCase_ == 6) {
+          return (io.grpc.reflection.v1alpha.ListServiceResponse) messageResponse_;
+        }
+        return io.grpc.reflection.v1alpha.ListServiceResponse.getDefaultInstance();
+      } else {
+        if (messageResponseCase_ == 6) {
+          return listServicesResponseBuilder_.getMessage();
+        }
+        return io.grpc.reflection.v1alpha.ListServiceResponse.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * This message is used to answer list_services request.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+     */
+    public Builder setListServicesResponse(io.grpc.reflection.v1alpha.ListServiceResponse value) {
+      if (listServicesResponseBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        messageResponse_ = value;
+        onChanged();
+      } else {
+        listServicesResponseBuilder_.setMessage(value);
+      }
+      messageResponseCase_ = 6;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used to answer list_services request.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+     */
+    public Builder setListServicesResponse(
+        io.grpc.reflection.v1alpha.ListServiceResponse.Builder builderForValue) {
+      if (listServicesResponseBuilder_ == null) {
+        messageResponse_ = builderForValue.build();
+        onChanged();
+      } else {
+        listServicesResponseBuilder_.setMessage(builderForValue.build());
+      }
+      messageResponseCase_ = 6;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used to answer list_services request.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+     */
+    public Builder mergeListServicesResponse(io.grpc.reflection.v1alpha.ListServiceResponse value) {
+      if (listServicesResponseBuilder_ == null) {
+        if (messageResponseCase_ == 6 &&
+            messageResponse_ != io.grpc.reflection.v1alpha.ListServiceResponse.getDefaultInstance()) {
+          messageResponse_ = io.grpc.reflection.v1alpha.ListServiceResponse.newBuilder((io.grpc.reflection.v1alpha.ListServiceResponse) messageResponse_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          messageResponse_ = value;
+        }
+        onChanged();
+      } else {
+        if (messageResponseCase_ == 6) {
+          listServicesResponseBuilder_.mergeFrom(value);
+        }
+        listServicesResponseBuilder_.setMessage(value);
+      }
+      messageResponseCase_ = 6;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used to answer list_services request.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+     */
+    public Builder clearListServicesResponse() {
+      if (listServicesResponseBuilder_ == null) {
+        if (messageResponseCase_ == 6) {
+          messageResponseCase_ = 0;
+          messageResponse_ = null;
+          onChanged();
+        }
+      } else {
+        if (messageResponseCase_ == 6) {
+          messageResponseCase_ = 0;
+          messageResponse_ = null;
+        }
+        listServicesResponseBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used to answer list_services request.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+     */
+    public io.grpc.reflection.v1alpha.ListServiceResponse.Builder getListServicesResponseBuilder() {
+      return getListServicesResponseFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * This message is used to answer list_services request.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+     */
+    public io.grpc.reflection.v1alpha.ListServiceResponseOrBuilder getListServicesResponseOrBuilder() {
+      if ((messageResponseCase_ == 6) && (listServicesResponseBuilder_ != null)) {
+        return listServicesResponseBuilder_.getMessageOrBuilder();
+      } else {
+        if (messageResponseCase_ == 6) {
+          return (io.grpc.reflection.v1alpha.ListServiceResponse) messageResponse_;
+        }
+        return io.grpc.reflection.v1alpha.ListServiceResponse.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * This message is used to answer list_services request.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.reflection.v1alpha.ListServiceResponse, io.grpc.reflection.v1alpha.ListServiceResponse.Builder, io.grpc.reflection.v1alpha.ListServiceResponseOrBuilder> 
+        getListServicesResponseFieldBuilder() {
+      if (listServicesResponseBuilder_ == null) {
+        if (!(messageResponseCase_ == 6)) {
+          messageResponse_ = io.grpc.reflection.v1alpha.ListServiceResponse.getDefaultInstance();
+        }
+        listServicesResponseBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.reflection.v1alpha.ListServiceResponse, io.grpc.reflection.v1alpha.ListServiceResponse.Builder, io.grpc.reflection.v1alpha.ListServiceResponseOrBuilder>(
+                (io.grpc.reflection.v1alpha.ListServiceResponse) messageResponse_,
+                getParentForChildren(),
+                isClean());
+        messageResponse_ = null;
+      }
+      messageResponseCase_ = 6;
+      onChanged();;
+      return listServicesResponseBuilder_;
+    }
+
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.reflection.v1alpha.ErrorResponse, io.grpc.reflection.v1alpha.ErrorResponse.Builder, io.grpc.reflection.v1alpha.ErrorResponseOrBuilder> errorResponseBuilder_;
+    /**
+     * <pre>
+     * This message is used when an error occurs.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+     */
+    public boolean hasErrorResponse() {
+      return messageResponseCase_ == 7;
+    }
+    /**
+     * <pre>
+     * This message is used when an error occurs.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+     */
+    public io.grpc.reflection.v1alpha.ErrorResponse getErrorResponse() {
+      if (errorResponseBuilder_ == null) {
+        if (messageResponseCase_ == 7) {
+          return (io.grpc.reflection.v1alpha.ErrorResponse) messageResponse_;
+        }
+        return io.grpc.reflection.v1alpha.ErrorResponse.getDefaultInstance();
+      } else {
+        if (messageResponseCase_ == 7) {
+          return errorResponseBuilder_.getMessage();
+        }
+        return io.grpc.reflection.v1alpha.ErrorResponse.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * This message is used when an error occurs.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+     */
+    public Builder setErrorResponse(io.grpc.reflection.v1alpha.ErrorResponse value) {
+      if (errorResponseBuilder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        messageResponse_ = value;
+        onChanged();
+      } else {
+        errorResponseBuilder_.setMessage(value);
+      }
+      messageResponseCase_ = 7;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used when an error occurs.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+     */
+    public Builder setErrorResponse(
+        io.grpc.reflection.v1alpha.ErrorResponse.Builder builderForValue) {
+      if (errorResponseBuilder_ == null) {
+        messageResponse_ = builderForValue.build();
+        onChanged();
+      } else {
+        errorResponseBuilder_.setMessage(builderForValue.build());
+      }
+      messageResponseCase_ = 7;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used when an error occurs.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+     */
+    public Builder mergeErrorResponse(io.grpc.reflection.v1alpha.ErrorResponse value) {
+      if (errorResponseBuilder_ == null) {
+        if (messageResponseCase_ == 7 &&
+            messageResponse_ != io.grpc.reflection.v1alpha.ErrorResponse.getDefaultInstance()) {
+          messageResponse_ = io.grpc.reflection.v1alpha.ErrorResponse.newBuilder((io.grpc.reflection.v1alpha.ErrorResponse) messageResponse_)
+              .mergeFrom(value).buildPartial();
+        } else {
+          messageResponse_ = value;
+        }
+        onChanged();
+      } else {
+        if (messageResponseCase_ == 7) {
+          errorResponseBuilder_.mergeFrom(value);
+        }
+        errorResponseBuilder_.setMessage(value);
+      }
+      messageResponseCase_ = 7;
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used when an error occurs.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+     */
+    public Builder clearErrorResponse() {
+      if (errorResponseBuilder_ == null) {
+        if (messageResponseCase_ == 7) {
+          messageResponseCase_ = 0;
+          messageResponse_ = null;
+          onChanged();
+        }
+      } else {
+        if (messageResponseCase_ == 7) {
+          messageResponseCase_ = 0;
+          messageResponse_ = null;
+        }
+        errorResponseBuilder_.clear();
+      }
+      return this;
+    }
+    /**
+     * <pre>
+     * This message is used when an error occurs.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+     */
+    public io.grpc.reflection.v1alpha.ErrorResponse.Builder getErrorResponseBuilder() {
+      return getErrorResponseFieldBuilder().getBuilder();
+    }
+    /**
+     * <pre>
+     * This message is used when an error occurs.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+     */
+    public io.grpc.reflection.v1alpha.ErrorResponseOrBuilder getErrorResponseOrBuilder() {
+      if ((messageResponseCase_ == 7) && (errorResponseBuilder_ != null)) {
+        return errorResponseBuilder_.getMessageOrBuilder();
+      } else {
+        if (messageResponseCase_ == 7) {
+          return (io.grpc.reflection.v1alpha.ErrorResponse) messageResponse_;
+        }
+        return io.grpc.reflection.v1alpha.ErrorResponse.getDefaultInstance();
+      }
+    }
+    /**
+     * <pre>
+     * This message is used when an error occurs.
+     * </pre>
+     *
+     * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        io.grpc.reflection.v1alpha.ErrorResponse, io.grpc.reflection.v1alpha.ErrorResponse.Builder, io.grpc.reflection.v1alpha.ErrorResponseOrBuilder> 
+        getErrorResponseFieldBuilder() {
+      if (errorResponseBuilder_ == null) {
+        if (!(messageResponseCase_ == 7)) {
+          messageResponse_ = io.grpc.reflection.v1alpha.ErrorResponse.getDefaultInstance();
+        }
+        errorResponseBuilder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            io.grpc.reflection.v1alpha.ErrorResponse, io.grpc.reflection.v1alpha.ErrorResponse.Builder, io.grpc.reflection.v1alpha.ErrorResponseOrBuilder>(
+                (io.grpc.reflection.v1alpha.ErrorResponse) messageResponse_,
+                getParentForChildren(),
+                isClean());
+        messageResponse_ = null;
+      }
+      messageResponseCase_ = 7;
+      onChanged();;
+      return errorResponseBuilder_;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.v1alpha.ServerReflectionResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.v1alpha.ServerReflectionResponse)
+  private static final io.grpc.reflection.v1alpha.ServerReflectionResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.v1alpha.ServerReflectionResponse();
+  }
+
+  public static io.grpc.reflection.v1alpha.ServerReflectionResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ServerReflectionResponse>
+      PARSER = new com.google.protobuf.AbstractParser<ServerReflectionResponse>() {
+    public ServerReflectionResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ServerReflectionResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ServerReflectionResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ServerReflectionResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.v1alpha.ServerReflectionResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionResponseOrBuilder.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionResponseOrBuilder.java
new file mode 100644
index 0000000..0a24d91
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServerReflectionResponseOrBuilder.java
@@ -0,0 +1,149 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+public interface ServerReflectionResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.v1alpha.ServerReflectionResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>string valid_host = 1;</code>
+   */
+  java.lang.String getValidHost();
+  /**
+   * <code>string valid_host = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getValidHostBytes();
+
+  /**
+   * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+   */
+  boolean hasOriginalRequest();
+  /**
+   * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+   */
+  io.grpc.reflection.v1alpha.ServerReflectionRequest getOriginalRequest();
+  /**
+   * <code>.grpc.reflection.v1alpha.ServerReflectionRequest original_request = 2;</code>
+   */
+  io.grpc.reflection.v1alpha.ServerReflectionRequestOrBuilder getOriginalRequestOrBuilder();
+
+  /**
+   * <pre>
+   * This message is used to answer file_by_filename, file_containing_symbol,
+   * file_containing_extension requests with transitive dependencies. As
+   * the repeated label is not allowed in oneof fields, we use a
+   * FileDescriptorResponse message to encapsulate the repeated fields.
+   * The reflection service is allowed to avoid sending FileDescriptorProtos
+   * that were previously sent in response to earlier requests in the stream.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+   */
+  boolean hasFileDescriptorResponse();
+  /**
+   * <pre>
+   * This message is used to answer file_by_filename, file_containing_symbol,
+   * file_containing_extension requests with transitive dependencies. As
+   * the repeated label is not allowed in oneof fields, we use a
+   * FileDescriptorResponse message to encapsulate the repeated fields.
+   * The reflection service is allowed to avoid sending FileDescriptorProtos
+   * that were previously sent in response to earlier requests in the stream.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+   */
+  io.grpc.reflection.v1alpha.FileDescriptorResponse getFileDescriptorResponse();
+  /**
+   * <pre>
+   * This message is used to answer file_by_filename, file_containing_symbol,
+   * file_containing_extension requests with transitive dependencies. As
+   * the repeated label is not allowed in oneof fields, we use a
+   * FileDescriptorResponse message to encapsulate the repeated fields.
+   * The reflection service is allowed to avoid sending FileDescriptorProtos
+   * that were previously sent in response to earlier requests in the stream.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.FileDescriptorResponse file_descriptor_response = 4;</code>
+   */
+  io.grpc.reflection.v1alpha.FileDescriptorResponseOrBuilder getFileDescriptorResponseOrBuilder();
+
+  /**
+   * <pre>
+   * This message is used to answer all_extension_numbers_of_type requst.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+   */
+  boolean hasAllExtensionNumbersResponse();
+  /**
+   * <pre>
+   * This message is used to answer all_extension_numbers_of_type requst.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+   */
+  io.grpc.reflection.v1alpha.ExtensionNumberResponse getAllExtensionNumbersResponse();
+  /**
+   * <pre>
+   * This message is used to answer all_extension_numbers_of_type requst.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ExtensionNumberResponse all_extension_numbers_response = 5;</code>
+   */
+  io.grpc.reflection.v1alpha.ExtensionNumberResponseOrBuilder getAllExtensionNumbersResponseOrBuilder();
+
+  /**
+   * <pre>
+   * This message is used to answer list_services request.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+   */
+  boolean hasListServicesResponse();
+  /**
+   * <pre>
+   * This message is used to answer list_services request.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+   */
+  io.grpc.reflection.v1alpha.ListServiceResponse getListServicesResponse();
+  /**
+   * <pre>
+   * This message is used to answer list_services request.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ListServiceResponse list_services_response = 6;</code>
+   */
+  io.grpc.reflection.v1alpha.ListServiceResponseOrBuilder getListServicesResponseOrBuilder();
+
+  /**
+   * <pre>
+   * This message is used when an error occurs.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+   */
+  boolean hasErrorResponse();
+  /**
+   * <pre>
+   * This message is used when an error occurs.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+   */
+  io.grpc.reflection.v1alpha.ErrorResponse getErrorResponse();
+  /**
+   * <pre>
+   * This message is used when an error occurs.
+   * </pre>
+   *
+   * <code>.grpc.reflection.v1alpha.ErrorResponse error_response = 7;</code>
+   */
+  io.grpc.reflection.v1alpha.ErrorResponseOrBuilder getErrorResponseOrBuilder();
+
+  public io.grpc.reflection.v1alpha.ServerReflectionResponse.MessageResponseCase getMessageResponseCase();
+}
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServiceResponse.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServiceResponse.java
new file mode 100644
index 0000000..031decc
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServiceResponse.java
@@ -0,0 +1,561 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+/**
+ * <pre>
+ * The information of a single service used by ListServiceResponse to answer
+ * list_services request.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.reflection.v1alpha.ServiceResponse}
+ */
+public  final class ServiceResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.v1alpha.ServiceResponse)
+    ServiceResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ServiceResponse.newBuilder() to construct.
+  private ServiceResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private ServiceResponse() {
+    name_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ServiceResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            name_ = s;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServiceResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServiceResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.v1alpha.ServiceResponse.class, io.grpc.reflection.v1alpha.ServiceResponse.Builder.class);
+  }
+
+  public static final int NAME_FIELD_NUMBER = 1;
+  private volatile java.lang.Object name_;
+  /**
+   * <pre>
+   * Full name of a registered service, including its package name. The format
+   * is &lt;package&gt;.&lt;service&gt;
+   * </pre>
+   *
+   * <code>string name = 1;</code>
+   */
+  public java.lang.String getName() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      name_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * Full name of a registered service, including its package name. The format
+   * is &lt;package&gt;.&lt;service&gt;
+   * </pre>
+   *
+   * <code>string name = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getNameBytes() {
+    java.lang.Object ref = name_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      name_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getNameBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, name_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getNameBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, name_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.v1alpha.ServiceResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.v1alpha.ServiceResponse other = (io.grpc.reflection.v1alpha.ServiceResponse) obj;
+
+    boolean result = true;
+    result = result && getName()
+        .equals(other.getName());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + NAME_FIELD_NUMBER;
+    hash = (53 * hash) + getName().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.v1alpha.ServiceResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ServiceResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServiceResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ServiceResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServiceResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.v1alpha.ServiceResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServiceResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ServiceResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServiceResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ServiceResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.v1alpha.ServiceResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.v1alpha.ServiceResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.v1alpha.ServiceResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * The information of a single service used by ListServiceResponse to answer
+   * list_services request.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.reflection.v1alpha.ServiceResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.v1alpha.ServiceResponse)
+      io.grpc.reflection.v1alpha.ServiceResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServiceResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServiceResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.v1alpha.ServiceResponse.class, io.grpc.reflection.v1alpha.ServiceResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.v1alpha.ServiceResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      name_ = "";
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.v1alpha.ServerReflectionProto.internal_static_grpc_reflection_v1alpha_ServiceResponse_descriptor;
+    }
+
+    public io.grpc.reflection.v1alpha.ServiceResponse getDefaultInstanceForType() {
+      return io.grpc.reflection.v1alpha.ServiceResponse.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.v1alpha.ServiceResponse build() {
+      io.grpc.reflection.v1alpha.ServiceResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.v1alpha.ServiceResponse buildPartial() {
+      io.grpc.reflection.v1alpha.ServiceResponse result = new io.grpc.reflection.v1alpha.ServiceResponse(this);
+      result.name_ = name_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.v1alpha.ServiceResponse) {
+        return mergeFrom((io.grpc.reflection.v1alpha.ServiceResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.v1alpha.ServiceResponse other) {
+      if (other == io.grpc.reflection.v1alpha.ServiceResponse.getDefaultInstance()) return this;
+      if (!other.getName().isEmpty()) {
+        name_ = other.name_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.v1alpha.ServiceResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.v1alpha.ServiceResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private java.lang.Object name_ = "";
+    /**
+     * <pre>
+     * Full name of a registered service, including its package name. The format
+     * is &lt;package&gt;.&lt;service&gt;
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public java.lang.String getName() {
+      java.lang.Object ref = name_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        name_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Full name of a registered service, including its package name. The format
+     * is &lt;package&gt;.&lt;service&gt;
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getNameBytes() {
+      java.lang.Object ref = name_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        name_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * Full name of a registered service, including its package name. The format
+     * is &lt;package&gt;.&lt;service&gt;
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public Builder setName(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      name_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Full name of a registered service, including its package name. The format
+     * is &lt;package&gt;.&lt;service&gt;
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public Builder clearName() {
+      
+      name_ = getDefaultInstance().getName();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * Full name of a registered service, including its package name. The format
+     * is &lt;package&gt;.&lt;service&gt;
+     * </pre>
+     *
+     * <code>string name = 1;</code>
+     */
+    public Builder setNameBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      name_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.v1alpha.ServiceResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.v1alpha.ServiceResponse)
+  private static final io.grpc.reflection.v1alpha.ServiceResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.v1alpha.ServiceResponse();
+  }
+
+  public static io.grpc.reflection.v1alpha.ServiceResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<ServiceResponse>
+      PARSER = new com.google.protobuf.AbstractParser<ServiceResponse>() {
+    public ServiceResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ServiceResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ServiceResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ServiceResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.v1alpha.ServiceResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServiceResponseOrBuilder.java b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServiceResponseOrBuilder.java
new file mode 100644
index 0000000..ed234c7
--- /dev/null
+++ b/services/src/generated/main/java/io/grpc/reflection/v1alpha/ServiceResponseOrBuilder.java
@@ -0,0 +1,29 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/v1alpha/reflection.proto
+
+package io.grpc.reflection.v1alpha;
+
+public interface ServiceResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.v1alpha.ServiceResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * Full name of a registered service, including its package name. The format
+   * is &lt;package&gt;.&lt;service&gt;
+   * </pre>
+   *
+   * <code>string name = 1;</code>
+   */
+  java.lang.String getName();
+  /**
+   * <pre>
+   * Full name of a registered service, including its package name. The format
+   * is &lt;package&gt;.&lt;service&gt;
+   * </pre>
+   *
+   * <code>string name = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getNameBytes();
+}
diff --git a/services/src/generated/test/grpc/io/grpc/reflection/testing/AnotherDynamicServiceGrpc.java b/services/src/generated/test/grpc/io/grpc/reflection/testing/AnotherDynamicServiceGrpc.java
new file mode 100644
index 0000000..f50f2b1
--- /dev/null
+++ b/services/src/generated/test/grpc/io/grpc/reflection/testing/AnotherDynamicServiceGrpc.java
@@ -0,0 +1,307 @@
+package io.grpc.reflection.testing;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ * <pre>
+ * AnotherDynamicService
+ * </pre>
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: io/grpc/reflection/testing/dynamic_reflection_test.proto")
+public final class AnotherDynamicServiceGrpc {
+
+  private AnotherDynamicServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.reflection.testing.AnotherDynamicService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.reflection.testing.DynamicRequest,
+      io.grpc.reflection.testing.DynamicReply> getMethodMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "Method",
+      requestType = io.grpc.reflection.testing.DynamicRequest.class,
+      responseType = io.grpc.reflection.testing.DynamicReply.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.reflection.testing.DynamicRequest,
+      io.grpc.reflection.testing.DynamicReply> getMethodMethod() {
+    io.grpc.MethodDescriptor<io.grpc.reflection.testing.DynamicRequest, io.grpc.reflection.testing.DynamicReply> getMethodMethod;
+    if ((getMethodMethod = AnotherDynamicServiceGrpc.getMethodMethod) == null) {
+      synchronized (AnotherDynamicServiceGrpc.class) {
+        if ((getMethodMethod = AnotherDynamicServiceGrpc.getMethodMethod) == null) {
+          AnotherDynamicServiceGrpc.getMethodMethod = getMethodMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.reflection.testing.DynamicRequest, io.grpc.reflection.testing.DynamicReply>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.reflection.testing.AnotherDynamicService", "Method"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.reflection.testing.DynamicRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.reflection.testing.DynamicReply.getDefaultInstance()))
+                  .setSchemaDescriptor(new AnotherDynamicServiceMethodDescriptorSupplier("Method"))
+                  .build();
+          }
+        }
+     }
+     return getMethodMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static AnotherDynamicServiceStub newStub(io.grpc.Channel channel) {
+    return new AnotherDynamicServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static AnotherDynamicServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new AnotherDynamicServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static AnotherDynamicServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new AnotherDynamicServiceFutureStub(channel);
+  }
+
+  /**
+   * <pre>
+   * AnotherDynamicService
+   * </pre>
+   */
+  public static abstract class AnotherDynamicServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * A method
+     * </pre>
+     */
+    public void method(io.grpc.reflection.testing.DynamicRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.reflection.testing.DynamicReply> responseObserver) {
+      asyncUnimplementedUnaryCall(getMethodMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getMethodMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.reflection.testing.DynamicRequest,
+                io.grpc.reflection.testing.DynamicReply>(
+                  this, METHODID_METHOD)))
+          .build();
+    }
+  }
+
+  /**
+   * <pre>
+   * AnotherDynamicService
+   * </pre>
+   */
+  public static final class AnotherDynamicServiceStub extends io.grpc.stub.AbstractStub<AnotherDynamicServiceStub> {
+    private AnotherDynamicServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private AnotherDynamicServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected AnotherDynamicServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new AnotherDynamicServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * A method
+     * </pre>
+     */
+    public void method(io.grpc.reflection.testing.DynamicRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.reflection.testing.DynamicReply> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getMethodMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   * <pre>
+   * AnotherDynamicService
+   * </pre>
+   */
+  public static final class AnotherDynamicServiceBlockingStub extends io.grpc.stub.AbstractStub<AnotherDynamicServiceBlockingStub> {
+    private AnotherDynamicServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private AnotherDynamicServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected AnotherDynamicServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new AnotherDynamicServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * A method
+     * </pre>
+     */
+    public io.grpc.reflection.testing.DynamicReply method(io.grpc.reflection.testing.DynamicRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getMethodMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   * <pre>
+   * AnotherDynamicService
+   * </pre>
+   */
+  public static final class AnotherDynamicServiceFutureStub extends io.grpc.stub.AbstractStub<AnotherDynamicServiceFutureStub> {
+    private AnotherDynamicServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private AnotherDynamicServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected AnotherDynamicServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new AnotherDynamicServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * A method
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.reflection.testing.DynamicReply> method(
+        io.grpc.reflection.testing.DynamicRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getMethodMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_METHOD = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final AnotherDynamicServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(AnotherDynamicServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_METHOD:
+          serviceImpl.method((io.grpc.reflection.testing.DynamicRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.reflection.testing.DynamicReply>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class AnotherDynamicServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    AnotherDynamicServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.reflection.testing.DynamicReflectionTestProto.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("AnotherDynamicService");
+    }
+  }
+
+  private static final class AnotherDynamicServiceFileDescriptorSupplier
+      extends AnotherDynamicServiceBaseDescriptorSupplier {
+    AnotherDynamicServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class AnotherDynamicServiceMethodDescriptorSupplier
+      extends AnotherDynamicServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    AnotherDynamicServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (AnotherDynamicServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new AnotherDynamicServiceFileDescriptorSupplier())
+              .addMethod(getMethodMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/services/src/generated/test/grpc/io/grpc/reflection/testing/DynamicServiceGrpc.java b/services/src/generated/test/grpc/io/grpc/reflection/testing/DynamicServiceGrpc.java
new file mode 100644
index 0000000..80d28b1
--- /dev/null
+++ b/services/src/generated/test/grpc/io/grpc/reflection/testing/DynamicServiceGrpc.java
@@ -0,0 +1,307 @@
+package io.grpc.reflection.testing;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ * <pre>
+ * A DynamicService
+ * </pre>
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: io/grpc/reflection/testing/dynamic_reflection_test.proto")
+public final class DynamicServiceGrpc {
+
+  private DynamicServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.reflection.testing.DynamicService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.reflection.testing.DynamicRequest,
+      io.grpc.reflection.testing.DynamicReply> getMethodMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "Method",
+      requestType = io.grpc.reflection.testing.DynamicRequest.class,
+      responseType = io.grpc.reflection.testing.DynamicReply.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.reflection.testing.DynamicRequest,
+      io.grpc.reflection.testing.DynamicReply> getMethodMethod() {
+    io.grpc.MethodDescriptor<io.grpc.reflection.testing.DynamicRequest, io.grpc.reflection.testing.DynamicReply> getMethodMethod;
+    if ((getMethodMethod = DynamicServiceGrpc.getMethodMethod) == null) {
+      synchronized (DynamicServiceGrpc.class) {
+        if ((getMethodMethod = DynamicServiceGrpc.getMethodMethod) == null) {
+          DynamicServiceGrpc.getMethodMethod = getMethodMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.reflection.testing.DynamicRequest, io.grpc.reflection.testing.DynamicReply>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.reflection.testing.DynamicService", "Method"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.reflection.testing.DynamicRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.reflection.testing.DynamicReply.getDefaultInstance()))
+                  .setSchemaDescriptor(new DynamicServiceMethodDescriptorSupplier("Method"))
+                  .build();
+          }
+        }
+     }
+     return getMethodMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static DynamicServiceStub newStub(io.grpc.Channel channel) {
+    return new DynamicServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static DynamicServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new DynamicServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static DynamicServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new DynamicServiceFutureStub(channel);
+  }
+
+  /**
+   * <pre>
+   * A DynamicService
+   * </pre>
+   */
+  public static abstract class DynamicServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * A method
+     * </pre>
+     */
+    public void method(io.grpc.reflection.testing.DynamicRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.reflection.testing.DynamicReply> responseObserver) {
+      asyncUnimplementedUnaryCall(getMethodMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getMethodMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.reflection.testing.DynamicRequest,
+                io.grpc.reflection.testing.DynamicReply>(
+                  this, METHODID_METHOD)))
+          .build();
+    }
+  }
+
+  /**
+   * <pre>
+   * A DynamicService
+   * </pre>
+   */
+  public static final class DynamicServiceStub extends io.grpc.stub.AbstractStub<DynamicServiceStub> {
+    private DynamicServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private DynamicServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected DynamicServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new DynamicServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * A method
+     * </pre>
+     */
+    public void method(io.grpc.reflection.testing.DynamicRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.reflection.testing.DynamicReply> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getMethodMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   * <pre>
+   * A DynamicService
+   * </pre>
+   */
+  public static final class DynamicServiceBlockingStub extends io.grpc.stub.AbstractStub<DynamicServiceBlockingStub> {
+    private DynamicServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private DynamicServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected DynamicServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new DynamicServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * A method
+     * </pre>
+     */
+    public io.grpc.reflection.testing.DynamicReply method(io.grpc.reflection.testing.DynamicRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getMethodMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   * <pre>
+   * A DynamicService
+   * </pre>
+   */
+  public static final class DynamicServiceFutureStub extends io.grpc.stub.AbstractStub<DynamicServiceFutureStub> {
+    private DynamicServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private DynamicServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected DynamicServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new DynamicServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * A method
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.reflection.testing.DynamicReply> method(
+        io.grpc.reflection.testing.DynamicRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getMethodMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_METHOD = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final DynamicServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(DynamicServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_METHOD:
+          serviceImpl.method((io.grpc.reflection.testing.DynamicRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.reflection.testing.DynamicReply>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class DynamicServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    DynamicServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.reflection.testing.DynamicReflectionTestProto.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("DynamicService");
+    }
+  }
+
+  private static final class DynamicServiceFileDescriptorSupplier
+      extends DynamicServiceBaseDescriptorSupplier {
+    DynamicServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class DynamicServiceMethodDescriptorSupplier
+      extends DynamicServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    DynamicServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (DynamicServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new DynamicServiceFileDescriptorSupplier())
+              .addMethod(getMethodMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/services/src/generated/test/grpc/io/grpc/reflection/testing/ReflectableServiceGrpc.java b/services/src/generated/test/grpc/io/grpc/reflection/testing/ReflectableServiceGrpc.java
new file mode 100644
index 0000000..239f944
--- /dev/null
+++ b/services/src/generated/test/grpc/io/grpc/reflection/testing/ReflectableServiceGrpc.java
@@ -0,0 +1,280 @@
+package io.grpc.reflection.testing;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: io/grpc/reflection/testing/reflection_test.proto")
+public final class ReflectableServiceGrpc {
+
+  private ReflectableServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.reflection.testing.ReflectableService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.reflection.testing.Request,
+      io.grpc.reflection.testing.Reply> getMethodMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "Method",
+      requestType = io.grpc.reflection.testing.Request.class,
+      responseType = io.grpc.reflection.testing.Reply.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.reflection.testing.Request,
+      io.grpc.reflection.testing.Reply> getMethodMethod() {
+    io.grpc.MethodDescriptor<io.grpc.reflection.testing.Request, io.grpc.reflection.testing.Reply> getMethodMethod;
+    if ((getMethodMethod = ReflectableServiceGrpc.getMethodMethod) == null) {
+      synchronized (ReflectableServiceGrpc.class) {
+        if ((getMethodMethod = ReflectableServiceGrpc.getMethodMethod) == null) {
+          ReflectableServiceGrpc.getMethodMethod = getMethodMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.reflection.testing.Request, io.grpc.reflection.testing.Reply>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.reflection.testing.ReflectableService", "Method"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.reflection.testing.Request.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.reflection.testing.Reply.getDefaultInstance()))
+                  .setSchemaDescriptor(new ReflectableServiceMethodDescriptorSupplier("Method"))
+                  .build();
+          }
+        }
+     }
+     return getMethodMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static ReflectableServiceStub newStub(io.grpc.Channel channel) {
+    return new ReflectableServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static ReflectableServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new ReflectableServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static ReflectableServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new ReflectableServiceFutureStub(channel);
+  }
+
+  /**
+   */
+  public static abstract class ReflectableServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     */
+    public void method(io.grpc.reflection.testing.Request request,
+        io.grpc.stub.StreamObserver<io.grpc.reflection.testing.Reply> responseObserver) {
+      asyncUnimplementedUnaryCall(getMethodMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getMethodMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.reflection.testing.Request,
+                io.grpc.reflection.testing.Reply>(
+                  this, METHODID_METHOD)))
+          .build();
+    }
+  }
+
+  /**
+   */
+  public static final class ReflectableServiceStub extends io.grpc.stub.AbstractStub<ReflectableServiceStub> {
+    private ReflectableServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ReflectableServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ReflectableServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ReflectableServiceStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public void method(io.grpc.reflection.testing.Request request,
+        io.grpc.stub.StreamObserver<io.grpc.reflection.testing.Reply> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getMethodMethod(), getCallOptions()), request, responseObserver);
+    }
+  }
+
+  /**
+   */
+  public static final class ReflectableServiceBlockingStub extends io.grpc.stub.AbstractStub<ReflectableServiceBlockingStub> {
+    private ReflectableServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ReflectableServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ReflectableServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ReflectableServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public io.grpc.reflection.testing.Reply method(io.grpc.reflection.testing.Request request) {
+      return blockingUnaryCall(
+          getChannel(), getMethodMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   */
+  public static final class ReflectableServiceFutureStub extends io.grpc.stub.AbstractStub<ReflectableServiceFutureStub> {
+    private ReflectableServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private ReflectableServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected ReflectableServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new ReflectableServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.reflection.testing.Reply> method(
+        io.grpc.reflection.testing.Request request) {
+      return futureUnaryCall(
+          getChannel().newCall(getMethodMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_METHOD = 0;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final ReflectableServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(ReflectableServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_METHOD:
+          serviceImpl.method((io.grpc.reflection.testing.Request) request,
+              (io.grpc.stub.StreamObserver<io.grpc.reflection.testing.Reply>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class ReflectableServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    ReflectableServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.reflection.testing.ReflectionTestProto.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("ReflectableService");
+    }
+  }
+
+  private static final class ReflectableServiceFileDescriptorSupplier
+      extends ReflectableServiceBaseDescriptorSupplier {
+    ReflectableServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class ReflectableServiceMethodDescriptorSupplier
+      extends ReflectableServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    ReflectableServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (ReflectableServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new ReflectableServiceFileDescriptorSupplier())
+              .addMethod(getMethodMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/DynamicReflectionTestDepthTwoProto.java b/services/src/generated/test/java/io/grpc/reflection/testing/DynamicReflectionTestDepthTwoProto.java
new file mode 100644
index 0000000..f9bc021
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/DynamicReflectionTestDepthTwoProto.java
@@ -0,0 +1,97 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/dynamic_reflection_test_depth_two.proto
+
+package io.grpc.reflection.testing;
+
+public final class DynamicReflectionTestDepthTwoProto {
+  private DynamicReflectionTestDepthTwoProto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+    registry.add(io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.extension);
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  public static final int EXTENSION_FIELD_NUMBER = 200;
+  /**
+   * <code>extend .grpc.reflection.testing.TypeWithExtensions { ... }</code>
+   */
+  public static final
+    com.google.protobuf.GeneratedMessage.GeneratedExtension<
+      io.grpc.reflection.testing.TypeWithExtensions,
+      java.lang.Integer> extension = com.google.protobuf.GeneratedMessage
+          .newFileScopedGeneratedExtension(
+        java.lang.Integer.class,
+        null);
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_testing_DynamicRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_testing_DynamicRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_testing_DynamicReply_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_testing_DynamicReply_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_testing_TypeWithExtensions_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_testing_TypeWithExtensions_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\nBio/grpc/reflection/testing/dynamic_ref" +
+      "lection_test_depth_two.proto\022\027grpc.refle" +
+      "ction.testing\"!\n\016DynamicRequest\022\017\n\007messa" +
+      "ge\030\001 \001(\t\"\037\n\014DynamicReply\022\017\n\007message\030\001 \001(" +
+      "\t\"-\n\022TypeWithExtensions\022\017\n\007message\030\001 \001(\t" +
+      "*\006\010\310\001\020\254\002:?\n\textension\022+.grpc.reflection." +
+      "testing.TypeWithExtensions\030\310\001 \001(\005BB\n\032io." +
+      "grpc.reflection.testingB\"DynamicReflecti" +
+      "onTestDepthTwoProtoP\001"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        }, assigner);
+    internal_static_grpc_reflection_testing_DynamicRequest_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_reflection_testing_DynamicRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_testing_DynamicRequest_descriptor,
+        new java.lang.String[] { "Message", });
+    internal_static_grpc_reflection_testing_DynamicReply_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_reflection_testing_DynamicReply_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_testing_DynamicReply_descriptor,
+        new java.lang.String[] { "Message", });
+    internal_static_grpc_reflection_testing_TypeWithExtensions_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_reflection_testing_TypeWithExtensions_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_testing_TypeWithExtensions_descriptor,
+        new java.lang.String[] { "Message", });
+    extension.internalInit(descriptor.getExtensions().get(0));
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/DynamicReflectionTestProto.java b/services/src/generated/test/java/io/grpc/reflection/testing/DynamicReflectionTestProto.java
new file mode 100644
index 0000000..7142bde
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/DynamicReflectionTestProto.java
@@ -0,0 +1,55 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/dynamic_reflection_test.proto
+
+package io.grpc.reflection.testing;
+
+public final class DynamicReflectionTestProto {
+  private DynamicReflectionTestProto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n8io/grpc/reflection/testing/dynamic_ref" +
+      "lection_test.proto\022\027grpc.reflection.test" +
+      "ing\032Bio/grpc/reflection/testing/dynamic_" +
+      "reflection_test_depth_two.proto2l\n\016Dynam" +
+      "icService\022Z\n\006Method\022\'.grpc.reflection.te" +
+      "sting.DynamicRequest\032%.grpc.reflection.t" +
+      "esting.DynamicReply\"\0002s\n\025AnotherDynamicS" +
+      "ervice\022Z\n\006Method\022\'.grpc.reflection.testi" +
+      "ng.DynamicRequest\032%.grpc.reflection.test" +
+      "ing.DynamicReply\"\000B:\n\032io.grpc.reflection" +
+      ".testingB\032DynamicReflectionTestProtoP\001"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.getDescriptor(),
+        }, assigner);
+    io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/DynamicReply.java b/services/src/generated/test/java/io/grpc/reflection/testing/DynamicReply.java
new file mode 100644
index 0000000..d934af3
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/DynamicReply.java
@@ -0,0 +1,545 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/dynamic_reflection_test_depth_two.proto
+
+package io.grpc.reflection.testing;
+
+/**
+ * Protobuf type {@code grpc.reflection.testing.DynamicReply}
+ */
+public  final class DynamicReply extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.testing.DynamicReply)
+    DynamicReplyOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use DynamicReply.newBuilder() to construct.
+  private DynamicReply(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private DynamicReply() {
+    message_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private DynamicReply(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            com.google.protobuf.ByteString bs = input.readBytes();
+            bitField0_ |= 0x00000001;
+            message_ = bs;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_DynamicReply_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_DynamicReply_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.testing.DynamicReply.class, io.grpc.reflection.testing.DynamicReply.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int MESSAGE_FIELD_NUMBER = 1;
+  private volatile java.lang.Object message_;
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public boolean hasMessage() {
+    return ((bitField0_ & 0x00000001) == 0x00000001);
+  }
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public java.lang.String getMessage() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      if (bs.isValidUtf8()) {
+        message_ = s;
+      }
+      return s;
+    }
+  }
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getMessageBytes() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      message_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (((bitField0_ & 0x00000001) == 0x00000001)) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, message_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (((bitField0_ & 0x00000001) == 0x00000001)) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, message_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.testing.DynamicReply)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.testing.DynamicReply other = (io.grpc.reflection.testing.DynamicReply) obj;
+
+    boolean result = true;
+    result = result && (hasMessage() == other.hasMessage());
+    if (hasMessage()) {
+      result = result && getMessage()
+          .equals(other.getMessage());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasMessage()) {
+      hash = (37 * hash) + MESSAGE_FIELD_NUMBER;
+      hash = (53 * hash) + getMessage().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.testing.DynamicReply parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.DynamicReply parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.DynamicReply parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.DynamicReply parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.DynamicReply parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.DynamicReply parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.DynamicReply parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.DynamicReply parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.DynamicReply parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.DynamicReply parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.DynamicReply parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.DynamicReply parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.testing.DynamicReply prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.reflection.testing.DynamicReply}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.testing.DynamicReply)
+      io.grpc.reflection.testing.DynamicReplyOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_DynamicReply_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_DynamicReply_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.testing.DynamicReply.class, io.grpc.reflection.testing.DynamicReply.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.testing.DynamicReply.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      message_ = "";
+      bitField0_ = (bitField0_ & ~0x00000001);
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_DynamicReply_descriptor;
+    }
+
+    public io.grpc.reflection.testing.DynamicReply getDefaultInstanceForType() {
+      return io.grpc.reflection.testing.DynamicReply.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.testing.DynamicReply build() {
+      io.grpc.reflection.testing.DynamicReply result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.testing.DynamicReply buildPartial() {
+      io.grpc.reflection.testing.DynamicReply result = new io.grpc.reflection.testing.DynamicReply(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+        to_bitField0_ |= 0x00000001;
+      }
+      result.message_ = message_;
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.testing.DynamicReply) {
+        return mergeFrom((io.grpc.reflection.testing.DynamicReply)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.testing.DynamicReply other) {
+      if (other == io.grpc.reflection.testing.DynamicReply.getDefaultInstance()) return this;
+      if (other.hasMessage()) {
+        bitField0_ |= 0x00000001;
+        message_ = other.message_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.testing.DynamicReply parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.testing.DynamicReply) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.lang.Object message_ = "";
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public boolean hasMessage() {
+      return ((bitField0_ & 0x00000001) == 0x00000001);
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public java.lang.String getMessage() {
+      java.lang.Object ref = message_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (bs.isValidUtf8()) {
+          message_ = s;
+        }
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getMessageBytes() {
+      java.lang.Object ref = message_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        message_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder setMessage(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder clearMessage() {
+      bitField0_ = (bitField0_ & ~0x00000001);
+      message_ = getDefaultInstance().getMessage();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder setMessageBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.testing.DynamicReply)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.testing.DynamicReply)
+  private static final io.grpc.reflection.testing.DynamicReply DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.testing.DynamicReply();
+  }
+
+  public static io.grpc.reflection.testing.DynamicReply getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  @java.lang.Deprecated public static final com.google.protobuf.Parser<DynamicReply>
+      PARSER = new com.google.protobuf.AbstractParser<DynamicReply>() {
+    public DynamicReply parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new DynamicReply(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<DynamicReply> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<DynamicReply> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.testing.DynamicReply getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/DynamicReplyOrBuilder.java b/services/src/generated/test/java/io/grpc/reflection/testing/DynamicReplyOrBuilder.java
new file mode 100644
index 0000000..2bf559b
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/DynamicReplyOrBuilder.java
@@ -0,0 +1,23 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/dynamic_reflection_test_depth_two.proto
+
+package io.grpc.reflection.testing;
+
+public interface DynamicReplyOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.testing.DynamicReply)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  boolean hasMessage();
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  java.lang.String getMessage();
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getMessageBytes();
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/DynamicRequest.java b/services/src/generated/test/java/io/grpc/reflection/testing/DynamicRequest.java
new file mode 100644
index 0000000..2ea9a14
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/DynamicRequest.java
@@ -0,0 +1,545 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/dynamic_reflection_test_depth_two.proto
+
+package io.grpc.reflection.testing;
+
+/**
+ * Protobuf type {@code grpc.reflection.testing.DynamicRequest}
+ */
+public  final class DynamicRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.testing.DynamicRequest)
+    DynamicRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use DynamicRequest.newBuilder() to construct.
+  private DynamicRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private DynamicRequest() {
+    message_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private DynamicRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            com.google.protobuf.ByteString bs = input.readBytes();
+            bitField0_ |= 0x00000001;
+            message_ = bs;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_DynamicRequest_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_DynamicRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.testing.DynamicRequest.class, io.grpc.reflection.testing.DynamicRequest.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int MESSAGE_FIELD_NUMBER = 1;
+  private volatile java.lang.Object message_;
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public boolean hasMessage() {
+    return ((bitField0_ & 0x00000001) == 0x00000001);
+  }
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public java.lang.String getMessage() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      if (bs.isValidUtf8()) {
+        message_ = s;
+      }
+      return s;
+    }
+  }
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getMessageBytes() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      message_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (((bitField0_ & 0x00000001) == 0x00000001)) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, message_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (((bitField0_ & 0x00000001) == 0x00000001)) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, message_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.testing.DynamicRequest)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.testing.DynamicRequest other = (io.grpc.reflection.testing.DynamicRequest) obj;
+
+    boolean result = true;
+    result = result && (hasMessage() == other.hasMessage());
+    if (hasMessage()) {
+      result = result && getMessage()
+          .equals(other.getMessage());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasMessage()) {
+      hash = (37 * hash) + MESSAGE_FIELD_NUMBER;
+      hash = (53 * hash) + getMessage().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.testing.DynamicRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.DynamicRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.DynamicRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.DynamicRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.DynamicRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.DynamicRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.DynamicRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.DynamicRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.DynamicRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.DynamicRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.DynamicRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.DynamicRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.testing.DynamicRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.reflection.testing.DynamicRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.testing.DynamicRequest)
+      io.grpc.reflection.testing.DynamicRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_DynamicRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_DynamicRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.testing.DynamicRequest.class, io.grpc.reflection.testing.DynamicRequest.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.testing.DynamicRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      message_ = "";
+      bitField0_ = (bitField0_ & ~0x00000001);
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_DynamicRequest_descriptor;
+    }
+
+    public io.grpc.reflection.testing.DynamicRequest getDefaultInstanceForType() {
+      return io.grpc.reflection.testing.DynamicRequest.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.testing.DynamicRequest build() {
+      io.grpc.reflection.testing.DynamicRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.testing.DynamicRequest buildPartial() {
+      io.grpc.reflection.testing.DynamicRequest result = new io.grpc.reflection.testing.DynamicRequest(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+        to_bitField0_ |= 0x00000001;
+      }
+      result.message_ = message_;
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.testing.DynamicRequest) {
+        return mergeFrom((io.grpc.reflection.testing.DynamicRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.testing.DynamicRequest other) {
+      if (other == io.grpc.reflection.testing.DynamicRequest.getDefaultInstance()) return this;
+      if (other.hasMessage()) {
+        bitField0_ |= 0x00000001;
+        message_ = other.message_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.testing.DynamicRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.testing.DynamicRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.lang.Object message_ = "";
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public boolean hasMessage() {
+      return ((bitField0_ & 0x00000001) == 0x00000001);
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public java.lang.String getMessage() {
+      java.lang.Object ref = message_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (bs.isValidUtf8()) {
+          message_ = s;
+        }
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getMessageBytes() {
+      java.lang.Object ref = message_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        message_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder setMessage(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder clearMessage() {
+      bitField0_ = (bitField0_ & ~0x00000001);
+      message_ = getDefaultInstance().getMessage();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder setMessageBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.testing.DynamicRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.testing.DynamicRequest)
+  private static final io.grpc.reflection.testing.DynamicRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.testing.DynamicRequest();
+  }
+
+  public static io.grpc.reflection.testing.DynamicRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  @java.lang.Deprecated public static final com.google.protobuf.Parser<DynamicRequest>
+      PARSER = new com.google.protobuf.AbstractParser<DynamicRequest>() {
+    public DynamicRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new DynamicRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<DynamicRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<DynamicRequest> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.testing.DynamicRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/DynamicRequestOrBuilder.java b/services/src/generated/test/java/io/grpc/reflection/testing/DynamicRequestOrBuilder.java
new file mode 100644
index 0000000..8ad284a
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/DynamicRequestOrBuilder.java
@@ -0,0 +1,23 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/dynamic_reflection_test_depth_two.proto
+
+package io.grpc.reflection.testing;
+
+public interface DynamicRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.testing.DynamicRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  boolean hasMessage();
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  java.lang.String getMessage();
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getMessageBytes();
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/EmptyMessage.java b/services/src/generated/test/java/io/grpc/reflection/testing/EmptyMessage.java
new file mode 100644
index 0000000..b7481c1
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/EmptyMessage.java
@@ -0,0 +1,388 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test_depth_three.proto
+
+package io.grpc.reflection.testing;
+
+/**
+ * Protobuf type {@code grpc.reflection.testing.EmptyMessage}
+ */
+public  final class EmptyMessage extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.testing.EmptyMessage)
+    EmptyMessageOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use EmptyMessage.newBuilder() to construct.
+  private EmptyMessage(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private EmptyMessage() {
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private EmptyMessage(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_EmptyMessage_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_EmptyMessage_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.testing.EmptyMessage.class, io.grpc.reflection.testing.EmptyMessage.Builder.class);
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.testing.EmptyMessage)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.testing.EmptyMessage other = (io.grpc.reflection.testing.EmptyMessage) obj;
+
+    boolean result = true;
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.testing.EmptyMessage parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.EmptyMessage parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.EmptyMessage parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.EmptyMessage parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.EmptyMessage parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.EmptyMessage parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.EmptyMessage parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.EmptyMessage parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.EmptyMessage parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.EmptyMessage parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.EmptyMessage parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.EmptyMessage parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.testing.EmptyMessage prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.reflection.testing.EmptyMessage}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.testing.EmptyMessage)
+      io.grpc.reflection.testing.EmptyMessageOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_EmptyMessage_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_EmptyMessage_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.testing.EmptyMessage.class, io.grpc.reflection.testing.EmptyMessage.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.testing.EmptyMessage.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_EmptyMessage_descriptor;
+    }
+
+    public io.grpc.reflection.testing.EmptyMessage getDefaultInstanceForType() {
+      return io.grpc.reflection.testing.EmptyMessage.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.testing.EmptyMessage build() {
+      io.grpc.reflection.testing.EmptyMessage result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.testing.EmptyMessage buildPartial() {
+      io.grpc.reflection.testing.EmptyMessage result = new io.grpc.reflection.testing.EmptyMessage(this);
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.testing.EmptyMessage) {
+        return mergeFrom((io.grpc.reflection.testing.EmptyMessage)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.testing.EmptyMessage other) {
+      if (other == io.grpc.reflection.testing.EmptyMessage.getDefaultInstance()) return this;
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.testing.EmptyMessage parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.testing.EmptyMessage) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.testing.EmptyMessage)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.testing.EmptyMessage)
+  private static final io.grpc.reflection.testing.EmptyMessage DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.testing.EmptyMessage();
+  }
+
+  public static io.grpc.reflection.testing.EmptyMessage getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  @java.lang.Deprecated public static final com.google.protobuf.Parser<EmptyMessage>
+      PARSER = new com.google.protobuf.AbstractParser<EmptyMessage>() {
+    public EmptyMessage parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new EmptyMessage(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<EmptyMessage> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<EmptyMessage> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.testing.EmptyMessage getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/EmptyMessageOrBuilder.java b/services/src/generated/test/java/io/grpc/reflection/testing/EmptyMessageOrBuilder.java
new file mode 100644
index 0000000..5f02779
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/EmptyMessageOrBuilder.java
@@ -0,0 +1,9 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test_depth_three.proto
+
+package io.grpc.reflection.testing;
+
+public interface EmptyMessageOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.testing.EmptyMessage)
+    com.google.protobuf.MessageOrBuilder {
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/NestedTypeOuter.java b/services/src/generated/test/java/io/grpc/reflection/testing/NestedTypeOuter.java
new file mode 100644
index 0000000..0c733a5
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/NestedTypeOuter.java
@@ -0,0 +1,1255 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test_depth_three.proto
+
+package io.grpc.reflection.testing;
+
+/**
+ * Protobuf type {@code grpc.reflection.testing.NestedTypeOuter}
+ */
+public  final class NestedTypeOuter extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.testing.NestedTypeOuter)
+    NestedTypeOuterOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use NestedTypeOuter.newBuilder() to construct.
+  private NestedTypeOuter(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private NestedTypeOuter() {
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private NestedTypeOuter(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.testing.NestedTypeOuter.class, io.grpc.reflection.testing.NestedTypeOuter.Builder.class);
+  }
+
+  public interface MiddleOrBuilder extends
+      // @@protoc_insertion_point(interface_extends:grpc.reflection.testing.NestedTypeOuter.Middle)
+      com.google.protobuf.MessageOrBuilder {
+  }
+  /**
+   * Protobuf type {@code grpc.reflection.testing.NestedTypeOuter.Middle}
+   */
+  public  static final class Middle extends
+      com.google.protobuf.GeneratedMessageV3 implements
+      // @@protoc_insertion_point(message_implements:grpc.reflection.testing.NestedTypeOuter.Middle)
+      MiddleOrBuilder {
+  private static final long serialVersionUID = 0L;
+    // Use Middle.newBuilder() to construct.
+    private Middle(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+      super(builder);
+    }
+    private Middle() {
+    }
+
+    @java.lang.Override
+    public final com.google.protobuf.UnknownFieldSet
+    getUnknownFields() {
+      return this.unknownFields;
+    }
+    private Middle(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      this();
+      if (extensionRegistry == null) {
+        throw new java.lang.NullPointerException();
+      }
+      com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder();
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            default: {
+              if (!parseUnknownField(
+                  input, unknownFields, extensionRegistry, tag)) {
+                done = true;
+              }
+              break;
+            }
+          }
+        }
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(this);
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(
+            e).setUnfinishedMessage(this);
+      } finally {
+        this.unknownFields = unknownFields.build();
+        makeExtensionsImmutable();
+      }
+    }
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.testing.NestedTypeOuter.Middle.class, io.grpc.reflection.testing.NestedTypeOuter.Middle.Builder.class);
+    }
+
+    public interface InnerOrBuilder extends
+        // @@protoc_insertion_point(interface_extends:grpc.reflection.testing.NestedTypeOuter.Middle.Inner)
+        com.google.protobuf.MessageOrBuilder {
+
+      /**
+       * <code>optional int32 ival = 1;</code>
+       */
+      boolean hasIval();
+      /**
+       * <code>optional int32 ival = 1;</code>
+       */
+      int getIval();
+    }
+    /**
+     * Protobuf type {@code grpc.reflection.testing.NestedTypeOuter.Middle.Inner}
+     */
+    public  static final class Inner extends
+        com.google.protobuf.GeneratedMessageV3 implements
+        // @@protoc_insertion_point(message_implements:grpc.reflection.testing.NestedTypeOuter.Middle.Inner)
+        InnerOrBuilder {
+    private static final long serialVersionUID = 0L;
+      // Use Inner.newBuilder() to construct.
+      private Inner(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+        super(builder);
+      }
+      private Inner() {
+        ival_ = 0;
+      }
+
+      @java.lang.Override
+      public final com.google.protobuf.UnknownFieldSet
+      getUnknownFields() {
+        return this.unknownFields;
+      }
+      private Inner(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        this();
+        if (extensionRegistry == null) {
+          throw new java.lang.NullPointerException();
+        }
+        int mutable_bitField0_ = 0;
+        com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+            com.google.protobuf.UnknownFieldSet.newBuilder();
+        try {
+          boolean done = false;
+          while (!done) {
+            int tag = input.readTag();
+            switch (tag) {
+              case 0:
+                done = true;
+                break;
+              default: {
+                if (!parseUnknownField(
+                    input, unknownFields, extensionRegistry, tag)) {
+                  done = true;
+                }
+                break;
+              }
+              case 8: {
+                bitField0_ |= 0x00000001;
+                ival_ = input.readInt32();
+                break;
+              }
+            }
+          }
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          throw e.setUnfinishedMessage(this);
+        } catch (java.io.IOException e) {
+          throw new com.google.protobuf.InvalidProtocolBufferException(
+              e).setUnfinishedMessage(this);
+        } finally {
+          this.unknownFields = unknownFields.build();
+          makeExtensionsImmutable();
+        }
+      }
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_Inner_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_Inner_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner.class, io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner.Builder.class);
+      }
+
+      private int bitField0_;
+      public static final int IVAL_FIELD_NUMBER = 1;
+      private int ival_;
+      /**
+       * <code>optional int32 ival = 1;</code>
+       */
+      public boolean hasIval() {
+        return ((bitField0_ & 0x00000001) == 0x00000001);
+      }
+      /**
+       * <code>optional int32 ival = 1;</code>
+       */
+      public int getIval() {
+        return ival_;
+      }
+
+      private byte memoizedIsInitialized = -1;
+      public final boolean isInitialized() {
+        byte isInitialized = memoizedIsInitialized;
+        if (isInitialized == 1) return true;
+        if (isInitialized == 0) return false;
+
+        memoizedIsInitialized = 1;
+        return true;
+      }
+
+      public void writeTo(com.google.protobuf.CodedOutputStream output)
+                          throws java.io.IOException {
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          output.writeInt32(1, ival_);
+        }
+        unknownFields.writeTo(output);
+      }
+
+      public int getSerializedSize() {
+        int size = memoizedSize;
+        if (size != -1) return size;
+
+        size = 0;
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          size += com.google.protobuf.CodedOutputStream
+            .computeInt32Size(1, ival_);
+        }
+        size += unknownFields.getSerializedSize();
+        memoizedSize = size;
+        return size;
+      }
+
+      @java.lang.Override
+      public boolean equals(final java.lang.Object obj) {
+        if (obj == this) {
+         return true;
+        }
+        if (!(obj instanceof io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner)) {
+          return super.equals(obj);
+        }
+        io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner other = (io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner) obj;
+
+        boolean result = true;
+        result = result && (hasIval() == other.hasIval());
+        if (hasIval()) {
+          result = result && (getIval()
+              == other.getIval());
+        }
+        result = result && unknownFields.equals(other.unknownFields);
+        return result;
+      }
+
+      @java.lang.Override
+      public int hashCode() {
+        if (memoizedHashCode != 0) {
+          return memoizedHashCode;
+        }
+        int hash = 41;
+        hash = (19 * hash) + getDescriptor().hashCode();
+        if (hasIval()) {
+          hash = (37 * hash) + IVAL_FIELD_NUMBER;
+          hash = (53 * hash) + getIval();
+        }
+        hash = (29 * hash) + unknownFields.hashCode();
+        memoizedHashCode = hash;
+        return hash;
+      }
+
+      public static io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner parseFrom(
+          java.nio.ByteBuffer data)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data);
+      }
+      public static io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner parseFrom(
+          java.nio.ByteBuffer data,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data, extensionRegistry);
+      }
+      public static io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner parseFrom(
+          com.google.protobuf.ByteString data)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data);
+      }
+      public static io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner parseFrom(
+          com.google.protobuf.ByteString data,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data, extensionRegistry);
+      }
+      public static io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner parseFrom(byte[] data)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data);
+      }
+      public static io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner parseFrom(
+          byte[] data,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return PARSER.parseFrom(data, extensionRegistry);
+      }
+      public static io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner parseFrom(java.io.InputStream input)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseWithIOException(PARSER, input);
+      }
+      public static io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner parseFrom(
+          java.io.InputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseWithIOException(PARSER, input, extensionRegistry);
+      }
+      public static io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner parseDelimitedFrom(java.io.InputStream input)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseDelimitedWithIOException(PARSER, input);
+      }
+      public static io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner parseDelimitedFrom(
+          java.io.InputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+      }
+      public static io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner parseFrom(
+          com.google.protobuf.CodedInputStream input)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseWithIOException(PARSER, input);
+      }
+      public static io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner parseFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return com.google.protobuf.GeneratedMessageV3
+            .parseWithIOException(PARSER, input, extensionRegistry);
+      }
+
+      public Builder newBuilderForType() { return newBuilder(); }
+      public static Builder newBuilder() {
+        return DEFAULT_INSTANCE.toBuilder();
+      }
+      public static Builder newBuilder(io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner prototype) {
+        return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+      }
+      public Builder toBuilder() {
+        return this == DEFAULT_INSTANCE
+            ? new Builder() : new Builder().mergeFrom(this);
+      }
+
+      @java.lang.Override
+      protected Builder newBuilderForType(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        Builder builder = new Builder(parent);
+        return builder;
+      }
+      /**
+       * Protobuf type {@code grpc.reflection.testing.NestedTypeOuter.Middle.Inner}
+       */
+      public static final class Builder extends
+          com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+          // @@protoc_insertion_point(builder_implements:grpc.reflection.testing.NestedTypeOuter.Middle.Inner)
+          io.grpc.reflection.testing.NestedTypeOuter.Middle.InnerOrBuilder {
+        public static final com.google.protobuf.Descriptors.Descriptor
+            getDescriptor() {
+          return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_Inner_descriptor;
+        }
+
+        protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+            internalGetFieldAccessorTable() {
+          return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_Inner_fieldAccessorTable
+              .ensureFieldAccessorsInitialized(
+                  io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner.class, io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner.Builder.class);
+        }
+
+        // Construct using io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner.newBuilder()
+        private Builder() {
+          maybeForceBuilderInitialization();
+        }
+
+        private Builder(
+            com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+          super(parent);
+          maybeForceBuilderInitialization();
+        }
+        private void maybeForceBuilderInitialization() {
+          if (com.google.protobuf.GeneratedMessageV3
+                  .alwaysUseFieldBuilders) {
+          }
+        }
+        public Builder clear() {
+          super.clear();
+          ival_ = 0;
+          bitField0_ = (bitField0_ & ~0x00000001);
+          return this;
+        }
+
+        public com.google.protobuf.Descriptors.Descriptor
+            getDescriptorForType() {
+          return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_Inner_descriptor;
+        }
+
+        public io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner getDefaultInstanceForType() {
+          return io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner.getDefaultInstance();
+        }
+
+        public io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner build() {
+          io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner result = buildPartial();
+          if (!result.isInitialized()) {
+            throw newUninitializedMessageException(result);
+          }
+          return result;
+        }
+
+        public io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner buildPartial() {
+          io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner result = new io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner(this);
+          int from_bitField0_ = bitField0_;
+          int to_bitField0_ = 0;
+          if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+            to_bitField0_ |= 0x00000001;
+          }
+          result.ival_ = ival_;
+          result.bitField0_ = to_bitField0_;
+          onBuilt();
+          return result;
+        }
+
+        public Builder clone() {
+          return (Builder) super.clone();
+        }
+        public Builder setField(
+            com.google.protobuf.Descriptors.FieldDescriptor field,
+            java.lang.Object value) {
+          return (Builder) super.setField(field, value);
+        }
+        public Builder clearField(
+            com.google.protobuf.Descriptors.FieldDescriptor field) {
+          return (Builder) super.clearField(field);
+        }
+        public Builder clearOneof(
+            com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+          return (Builder) super.clearOneof(oneof);
+        }
+        public Builder setRepeatedField(
+            com.google.protobuf.Descriptors.FieldDescriptor field,
+            int index, java.lang.Object value) {
+          return (Builder) super.setRepeatedField(field, index, value);
+        }
+        public Builder addRepeatedField(
+            com.google.protobuf.Descriptors.FieldDescriptor field,
+            java.lang.Object value) {
+          return (Builder) super.addRepeatedField(field, value);
+        }
+        public Builder mergeFrom(com.google.protobuf.Message other) {
+          if (other instanceof io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner) {
+            return mergeFrom((io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner)other);
+          } else {
+            super.mergeFrom(other);
+            return this;
+          }
+        }
+
+        public Builder mergeFrom(io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner other) {
+          if (other == io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner.getDefaultInstance()) return this;
+          if (other.hasIval()) {
+            setIval(other.getIval());
+          }
+          this.mergeUnknownFields(other.unknownFields);
+          onChanged();
+          return this;
+        }
+
+        public final boolean isInitialized() {
+          return true;
+        }
+
+        public Builder mergeFrom(
+            com.google.protobuf.CodedInputStream input,
+            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+            throws java.io.IOException {
+          io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner parsedMessage = null;
+          try {
+            parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+          } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+            parsedMessage = (io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner) e.getUnfinishedMessage();
+            throw e.unwrapIOException();
+          } finally {
+            if (parsedMessage != null) {
+              mergeFrom(parsedMessage);
+            }
+          }
+          return this;
+        }
+        private int bitField0_;
+
+        private int ival_ ;
+        /**
+         * <code>optional int32 ival = 1;</code>
+         */
+        public boolean hasIval() {
+          return ((bitField0_ & 0x00000001) == 0x00000001);
+        }
+        /**
+         * <code>optional int32 ival = 1;</code>
+         */
+        public int getIval() {
+          return ival_;
+        }
+        /**
+         * <code>optional int32 ival = 1;</code>
+         */
+        public Builder setIval(int value) {
+          bitField0_ |= 0x00000001;
+          ival_ = value;
+          onChanged();
+          return this;
+        }
+        /**
+         * <code>optional int32 ival = 1;</code>
+         */
+        public Builder clearIval() {
+          bitField0_ = (bitField0_ & ~0x00000001);
+          ival_ = 0;
+          onChanged();
+          return this;
+        }
+        public final Builder setUnknownFields(
+            final com.google.protobuf.UnknownFieldSet unknownFields) {
+          return super.setUnknownFields(unknownFields);
+        }
+
+        public final Builder mergeUnknownFields(
+            final com.google.protobuf.UnknownFieldSet unknownFields) {
+          return super.mergeUnknownFields(unknownFields);
+        }
+
+
+        // @@protoc_insertion_point(builder_scope:grpc.reflection.testing.NestedTypeOuter.Middle.Inner)
+      }
+
+      // @@protoc_insertion_point(class_scope:grpc.reflection.testing.NestedTypeOuter.Middle.Inner)
+      private static final io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner DEFAULT_INSTANCE;
+      static {
+        DEFAULT_INSTANCE = new io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner();
+      }
+
+      public static io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner getDefaultInstance() {
+        return DEFAULT_INSTANCE;
+      }
+
+      @java.lang.Deprecated public static final com.google.protobuf.Parser<Inner>
+          PARSER = new com.google.protobuf.AbstractParser<Inner>() {
+        public Inner parsePartialFrom(
+            com.google.protobuf.CodedInputStream input,
+            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+            throws com.google.protobuf.InvalidProtocolBufferException {
+          return new Inner(input, extensionRegistry);
+        }
+      };
+
+      public static com.google.protobuf.Parser<Inner> parser() {
+        return PARSER;
+      }
+
+      @java.lang.Override
+      public com.google.protobuf.Parser<Inner> getParserForType() {
+        return PARSER;
+      }
+
+      public io.grpc.reflection.testing.NestedTypeOuter.Middle.Inner getDefaultInstanceForType() {
+        return DEFAULT_INSTANCE;
+      }
+
+    }
+
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized == 1) return true;
+      if (isInitialized == 0) return false;
+
+      memoizedIsInitialized = 1;
+      return true;
+    }
+
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      unknownFields.writeTo(output);
+    }
+
+    public int getSerializedSize() {
+      int size = memoizedSize;
+      if (size != -1) return size;
+
+      size = 0;
+      size += unknownFields.getSerializedSize();
+      memoizedSize = size;
+      return size;
+    }
+
+    @java.lang.Override
+    public boolean equals(final java.lang.Object obj) {
+      if (obj == this) {
+       return true;
+      }
+      if (!(obj instanceof io.grpc.reflection.testing.NestedTypeOuter.Middle)) {
+        return super.equals(obj);
+      }
+      io.grpc.reflection.testing.NestedTypeOuter.Middle other = (io.grpc.reflection.testing.NestedTypeOuter.Middle) obj;
+
+      boolean result = true;
+      result = result && unknownFields.equals(other.unknownFields);
+      return result;
+    }
+
+    @java.lang.Override
+    public int hashCode() {
+      if (memoizedHashCode != 0) {
+        return memoizedHashCode;
+      }
+      int hash = 41;
+      hash = (19 * hash) + getDescriptor().hashCode();
+      hash = (29 * hash) + unknownFields.hashCode();
+      memoizedHashCode = hash;
+      return hash;
+    }
+
+    public static io.grpc.reflection.testing.NestedTypeOuter.Middle parseFrom(
+        java.nio.ByteBuffer data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.reflection.testing.NestedTypeOuter.Middle parseFrom(
+        java.nio.ByteBuffer data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.reflection.testing.NestedTypeOuter.Middle parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.reflection.testing.NestedTypeOuter.Middle parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.reflection.testing.NestedTypeOuter.Middle parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data);
+    }
+    public static io.grpc.reflection.testing.NestedTypeOuter.Middle parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return PARSER.parseFrom(data, extensionRegistry);
+    }
+    public static io.grpc.reflection.testing.NestedTypeOuter.Middle parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.reflection.testing.NestedTypeOuter.Middle parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.reflection.testing.NestedTypeOuter.Middle parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input);
+    }
+    public static io.grpc.reflection.testing.NestedTypeOuter.Middle parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+    }
+    public static io.grpc.reflection.testing.NestedTypeOuter.Middle parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input);
+    }
+    public static io.grpc.reflection.testing.NestedTypeOuter.Middle parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return com.google.protobuf.GeneratedMessageV3
+          .parseWithIOException(PARSER, input, extensionRegistry);
+    }
+
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder() {
+      return DEFAULT_INSTANCE.toBuilder();
+    }
+    public static Builder newBuilder(io.grpc.reflection.testing.NestedTypeOuter.Middle prototype) {
+      return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() {
+      return this == DEFAULT_INSTANCE
+          ? new Builder() : new Builder().mergeFrom(this);
+    }
+
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    /**
+     * Protobuf type {@code grpc.reflection.testing.NestedTypeOuter.Middle}
+     */
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+        // @@protoc_insertion_point(builder_implements:grpc.reflection.testing.NestedTypeOuter.Middle)
+        io.grpc.reflection.testing.NestedTypeOuter.MiddleOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_descriptor;
+      }
+
+      protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_fieldAccessorTable
+            .ensureFieldAccessorsInitialized(
+                io.grpc.reflection.testing.NestedTypeOuter.Middle.class, io.grpc.reflection.testing.NestedTypeOuter.Middle.Builder.class);
+      }
+
+      // Construct using io.grpc.reflection.testing.NestedTypeOuter.Middle.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+
+      private Builder(
+          com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessageV3
+                .alwaysUseFieldBuilders) {
+        }
+      }
+      public Builder clear() {
+        super.clear();
+        return this;
+      }
+
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_descriptor;
+      }
+
+      public io.grpc.reflection.testing.NestedTypeOuter.Middle getDefaultInstanceForType() {
+        return io.grpc.reflection.testing.NestedTypeOuter.Middle.getDefaultInstance();
+      }
+
+      public io.grpc.reflection.testing.NestedTypeOuter.Middle build() {
+        io.grpc.reflection.testing.NestedTypeOuter.Middle result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+
+      public io.grpc.reflection.testing.NestedTypeOuter.Middle buildPartial() {
+        io.grpc.reflection.testing.NestedTypeOuter.Middle result = new io.grpc.reflection.testing.NestedTypeOuter.Middle(this);
+        onBuilt();
+        return result;
+      }
+
+      public Builder clone() {
+        return (Builder) super.clone();
+      }
+      public Builder setField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.setField(field, value);
+      }
+      public Builder clearField(
+          com.google.protobuf.Descriptors.FieldDescriptor field) {
+        return (Builder) super.clearField(field);
+      }
+      public Builder clearOneof(
+          com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+        return (Builder) super.clearOneof(oneof);
+      }
+      public Builder setRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          int index, java.lang.Object value) {
+        return (Builder) super.setRepeatedField(field, index, value);
+      }
+      public Builder addRepeatedField(
+          com.google.protobuf.Descriptors.FieldDescriptor field,
+          java.lang.Object value) {
+        return (Builder) super.addRepeatedField(field, value);
+      }
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof io.grpc.reflection.testing.NestedTypeOuter.Middle) {
+          return mergeFrom((io.grpc.reflection.testing.NestedTypeOuter.Middle)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+
+      public Builder mergeFrom(io.grpc.reflection.testing.NestedTypeOuter.Middle other) {
+        if (other == io.grpc.reflection.testing.NestedTypeOuter.Middle.getDefaultInstance()) return this;
+        this.mergeUnknownFields(other.unknownFields);
+        onChanged();
+        return this;
+      }
+
+      public final boolean isInitialized() {
+        return true;
+      }
+
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        io.grpc.reflection.testing.NestedTypeOuter.Middle parsedMessage = null;
+        try {
+          parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+        } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+          parsedMessage = (io.grpc.reflection.testing.NestedTypeOuter.Middle) e.getUnfinishedMessage();
+          throw e.unwrapIOException();
+        } finally {
+          if (parsedMessage != null) {
+            mergeFrom(parsedMessage);
+          }
+        }
+        return this;
+      }
+      public final Builder setUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.setUnknownFields(unknownFields);
+      }
+
+      public final Builder mergeUnknownFields(
+          final com.google.protobuf.UnknownFieldSet unknownFields) {
+        return super.mergeUnknownFields(unknownFields);
+      }
+
+
+      // @@protoc_insertion_point(builder_scope:grpc.reflection.testing.NestedTypeOuter.Middle)
+    }
+
+    // @@protoc_insertion_point(class_scope:grpc.reflection.testing.NestedTypeOuter.Middle)
+    private static final io.grpc.reflection.testing.NestedTypeOuter.Middle DEFAULT_INSTANCE;
+    static {
+      DEFAULT_INSTANCE = new io.grpc.reflection.testing.NestedTypeOuter.Middle();
+    }
+
+    public static io.grpc.reflection.testing.NestedTypeOuter.Middle getDefaultInstance() {
+      return DEFAULT_INSTANCE;
+    }
+
+    @java.lang.Deprecated public static final com.google.protobuf.Parser<Middle>
+        PARSER = new com.google.protobuf.AbstractParser<Middle>() {
+      public Middle parsePartialFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return new Middle(input, extensionRegistry);
+      }
+    };
+
+    public static com.google.protobuf.Parser<Middle> parser() {
+      return PARSER;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Parser<Middle> getParserForType() {
+      return PARSER;
+    }
+
+    public io.grpc.reflection.testing.NestedTypeOuter.Middle getDefaultInstanceForType() {
+      return DEFAULT_INSTANCE;
+    }
+
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.testing.NestedTypeOuter)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.testing.NestedTypeOuter other = (io.grpc.reflection.testing.NestedTypeOuter) obj;
+
+    boolean result = true;
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.testing.NestedTypeOuter parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.NestedTypeOuter parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.NestedTypeOuter parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.NestedTypeOuter parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.NestedTypeOuter parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.NestedTypeOuter parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.NestedTypeOuter parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.NestedTypeOuter parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.NestedTypeOuter parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.NestedTypeOuter parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.NestedTypeOuter parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.NestedTypeOuter parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.testing.NestedTypeOuter prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.reflection.testing.NestedTypeOuter}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.testing.NestedTypeOuter)
+      io.grpc.reflection.testing.NestedTypeOuterOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.testing.NestedTypeOuter.class, io.grpc.reflection.testing.NestedTypeOuter.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.testing.NestedTypeOuter.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_NestedTypeOuter_descriptor;
+    }
+
+    public io.grpc.reflection.testing.NestedTypeOuter getDefaultInstanceForType() {
+      return io.grpc.reflection.testing.NestedTypeOuter.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.testing.NestedTypeOuter build() {
+      io.grpc.reflection.testing.NestedTypeOuter result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.testing.NestedTypeOuter buildPartial() {
+      io.grpc.reflection.testing.NestedTypeOuter result = new io.grpc.reflection.testing.NestedTypeOuter(this);
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.testing.NestedTypeOuter) {
+        return mergeFrom((io.grpc.reflection.testing.NestedTypeOuter)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.testing.NestedTypeOuter other) {
+      if (other == io.grpc.reflection.testing.NestedTypeOuter.getDefaultInstance()) return this;
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.testing.NestedTypeOuter parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.testing.NestedTypeOuter) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.testing.NestedTypeOuter)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.testing.NestedTypeOuter)
+  private static final io.grpc.reflection.testing.NestedTypeOuter DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.testing.NestedTypeOuter();
+  }
+
+  public static io.grpc.reflection.testing.NestedTypeOuter getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  @java.lang.Deprecated public static final com.google.protobuf.Parser<NestedTypeOuter>
+      PARSER = new com.google.protobuf.AbstractParser<NestedTypeOuter>() {
+    public NestedTypeOuter parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new NestedTypeOuter(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<NestedTypeOuter> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<NestedTypeOuter> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.testing.NestedTypeOuter getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/NestedTypeOuterOrBuilder.java b/services/src/generated/test/java/io/grpc/reflection/testing/NestedTypeOuterOrBuilder.java
new file mode 100644
index 0000000..96599fa
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/NestedTypeOuterOrBuilder.java
@@ -0,0 +1,9 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test_depth_three.proto
+
+package io.grpc.reflection.testing;
+
+public interface NestedTypeOuterOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.testing.NestedTypeOuter)
+    com.google.protobuf.MessageOrBuilder {
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/ReflectionTestDepthThreeProto.java b/services/src/generated/test/java/io/grpc/reflection/testing/ReflectionTestDepthThreeProto.java
new file mode 100644
index 0000000..e3ea7e4
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/ReflectionTestDepthThreeProto.java
@@ -0,0 +1,104 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test_depth_three.proto
+
+package io.grpc.reflection.testing;
+
+public final class ReflectionTestDepthThreeProto {
+  private ReflectionTestDepthThreeProto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_testing_EmptyMessage_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_testing_EmptyMessage_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_testing_ThirdLevelType_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_testing_ThirdLevelType_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_testing_NestedTypeOuter_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_testing_NestedTypeOuter_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_Inner_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_Inner_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n<io/grpc/reflection/testing/reflection_" +
+      "test_depth_three.proto\022\027grpc.reflection." +
+      "testing\"\016\n\014EmptyMessage\"(\n\016ThirdLevelTyp" +
+      "e\022\017\n\007message\030\001 \001(\t*\005\010d\020\310\001\"2\n\017NestedTypeO" +
+      "uter\032\037\n\006Middle\032\025\n\005Inner\022\014\n\004ival\030\001 \001(\005B=\n" +
+      "\032io.grpc.reflection.testingB\035ReflectionT" +
+      "estDepthThreeProtoP\001"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        }, assigner);
+    internal_static_grpc_reflection_testing_EmptyMessage_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_reflection_testing_EmptyMessage_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_testing_EmptyMessage_descriptor,
+        new java.lang.String[] { });
+    internal_static_grpc_reflection_testing_ThirdLevelType_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_reflection_testing_ThirdLevelType_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_testing_ThirdLevelType_descriptor,
+        new java.lang.String[] { "Message", });
+    internal_static_grpc_reflection_testing_NestedTypeOuter_descriptor =
+      getDescriptor().getMessageTypes().get(2);
+    internal_static_grpc_reflection_testing_NestedTypeOuter_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_testing_NestedTypeOuter_descriptor,
+        new java.lang.String[] { });
+    internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_descriptor =
+      internal_static_grpc_reflection_testing_NestedTypeOuter_descriptor.getNestedTypes().get(0);
+    internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_descriptor,
+        new java.lang.String[] { });
+    internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_Inner_descriptor =
+      internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_descriptor.getNestedTypes().get(0);
+    internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_Inner_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_testing_NestedTypeOuter_Middle_Inner_descriptor,
+        new java.lang.String[] { "Ival", });
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/ReflectionTestDepthTwoAlternateProto.java b/services/src/generated/test/java/io/grpc/reflection/testing/ReflectionTestDepthTwoAlternateProto.java
new file mode 100644
index 0000000..12ac5a9
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/ReflectionTestDepthTwoAlternateProto.java
@@ -0,0 +1,50 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test_depth_two_alternate.proto
+
+package io.grpc.reflection.testing;
+
+public final class ReflectionTestDepthTwoAlternateProto {
+  private ReflectionTestDepthTwoAlternateProto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\nDio/grpc/reflection/testing/reflection_" +
+      "test_depth_two_alternate.proto\032<io/grpc/" +
+      "reflection/testing/reflection_test_depth" +
+      "_three.protoBD\n\032io.grpc.reflection.testi" +
+      "ngB$ReflectionTestDepthTwoAlternateProto" +
+      "P\001P\000"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          io.grpc.reflection.testing.ReflectionTestDepthThreeProto.getDescriptor(),
+        }, assigner);
+    io.grpc.reflection.testing.ReflectionTestDepthThreeProto.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/ReflectionTestDepthTwoProto.java b/services/src/generated/test/java/io/grpc/reflection/testing/ReflectionTestDepthTwoProto.java
new file mode 100644
index 0000000..59902aa
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/ReflectionTestDepthTwoProto.java
@@ -0,0 +1,89 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test_depth_two.proto
+
+package io.grpc.reflection.testing;
+
+public final class ReflectionTestDepthTwoProto {
+  private ReflectionTestDepthTwoProto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+    registry.add(io.grpc.reflection.testing.ReflectionTestDepthTwoProto.nestedExtension);
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  public static final int NESTED_EXTENSION_FIELD_NUMBER = 101;
+  /**
+   * <code>extend .grpc.reflection.testing.ThirdLevelType { ... }</code>
+   */
+  public static final
+    com.google.protobuf.GeneratedMessage.GeneratedExtension<
+      io.grpc.reflection.testing.ThirdLevelType,
+      io.grpc.reflection.testing.EmptyMessage> nestedExtension = com.google.protobuf.GeneratedMessage
+          .newFileScopedGeneratedExtension(
+        io.grpc.reflection.testing.EmptyMessage.class,
+        io.grpc.reflection.testing.EmptyMessage.getDefaultInstance());
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_testing_Request_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_testing_Request_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_reflection_testing_Reply_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_reflection_testing_Reply_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n:io/grpc/reflection/testing/reflection_" +
+      "test_depth_two.proto\022\027grpc.reflection.te" +
+      "sting\032<io/grpc/reflection/testing/reflec" +
+      "tion_test_depth_three.proto\"\032\n\007Request\022\017" +
+      "\n\007message\030\001 \001(\t\"\030\n\005Reply\022\017\n\007message\030\001 \001(" +
+      "\t:h\n\020nested_extension\022\'.grpc.reflection." +
+      "testing.ThirdLevelType\030e \001(\0132%.grpc.refl" +
+      "ection.testing.EmptyMessageB;\n\032io.grpc.r" +
+      "eflection.testingB\033ReflectionTestDepthTw" +
+      "oProtoP\001P\000"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          io.grpc.reflection.testing.ReflectionTestDepthThreeProto.getDescriptor(),
+        }, assigner);
+    internal_static_grpc_reflection_testing_Request_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_reflection_testing_Request_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_testing_Request_descriptor,
+        new java.lang.String[] { "Message", });
+    internal_static_grpc_reflection_testing_Reply_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_reflection_testing_Reply_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_reflection_testing_Reply_descriptor,
+        new java.lang.String[] { "Message", });
+    nestedExtension.internalInit(descriptor.getExtensions().get(0));
+    io.grpc.reflection.testing.ReflectionTestDepthThreeProto.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/ReflectionTestProto.java b/services/src/generated/test/java/io/grpc/reflection/testing/ReflectionTestProto.java
new file mode 100644
index 0000000..e8e93f3
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/ReflectionTestProto.java
@@ -0,0 +1,70 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test.proto
+
+package io.grpc.reflection.testing;
+
+public final class ReflectionTestProto {
+  private ReflectionTestProto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+    registry.add(io.grpc.reflection.testing.ReflectionTestProto.bar);
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  public static final int BAR_FIELD_NUMBER = 100;
+  /**
+   * <code>extend .grpc.reflection.testing.ThirdLevelType { ... }</code>
+   */
+  public static final
+    com.google.protobuf.GeneratedMessage.GeneratedExtension<
+      io.grpc.reflection.testing.ThirdLevelType,
+      java.lang.Integer> bar = com.google.protobuf.GeneratedMessage
+          .newFileScopedGeneratedExtension(
+        java.lang.Integer.class,
+        null);
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n0io/grpc/reflection/testing/reflection_" +
+      "test.proto\022\027grpc.reflection.testing\032:io/" +
+      "grpc/reflection/testing/reflection_test_" +
+      "depth_two.proto\032Dio/grpc/reflection/test" +
+      "ing/reflection_test_depth_two_alternate." +
+      "proto2b\n\022ReflectableService\022L\n\006Method\022 ." +
+      "grpc.reflection.testing.Request\032\036.grpc.r" +
+      "eflection.testing.Reply\"\000:4\n\003bar\022\'.grpc." +
+      "reflection.testing.ThirdLevelType\030d \001(\005B" +
+      "3\n\032io.grpc.reflection.testingB\023Reflectio" +
+      "nTestProtoP\001"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          io.grpc.reflection.testing.ReflectionTestDepthTwoProto.getDescriptor(),
+          io.grpc.reflection.testing.ReflectionTestDepthTwoAlternateProto.getDescriptor(),
+        }, assigner);
+    bar.internalInit(descriptor.getExtensions().get(0));
+    io.grpc.reflection.testing.ReflectionTestDepthTwoProto.getDescriptor();
+    io.grpc.reflection.testing.ReflectionTestDepthTwoAlternateProto.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/Reply.java b/services/src/generated/test/java/io/grpc/reflection/testing/Reply.java
new file mode 100644
index 0000000..508e940
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/Reply.java
@@ -0,0 +1,545 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test_depth_two.proto
+
+package io.grpc.reflection.testing;
+
+/**
+ * Protobuf type {@code grpc.reflection.testing.Reply}
+ */
+public  final class Reply extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.testing.Reply)
+    ReplyOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Reply.newBuilder() to construct.
+  private Reply(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Reply() {
+    message_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Reply(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            com.google.protobuf.ByteString bs = input.readBytes();
+            bitField0_ |= 0x00000001;
+            message_ = bs;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.testing.ReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_Reply_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.testing.ReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_Reply_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.testing.Reply.class, io.grpc.reflection.testing.Reply.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int MESSAGE_FIELD_NUMBER = 1;
+  private volatile java.lang.Object message_;
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public boolean hasMessage() {
+    return ((bitField0_ & 0x00000001) == 0x00000001);
+  }
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public java.lang.String getMessage() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      if (bs.isValidUtf8()) {
+        message_ = s;
+      }
+      return s;
+    }
+  }
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getMessageBytes() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      message_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (((bitField0_ & 0x00000001) == 0x00000001)) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, message_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (((bitField0_ & 0x00000001) == 0x00000001)) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, message_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.testing.Reply)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.testing.Reply other = (io.grpc.reflection.testing.Reply) obj;
+
+    boolean result = true;
+    result = result && (hasMessage() == other.hasMessage());
+    if (hasMessage()) {
+      result = result && getMessage()
+          .equals(other.getMessage());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasMessage()) {
+      hash = (37 * hash) + MESSAGE_FIELD_NUMBER;
+      hash = (53 * hash) + getMessage().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.testing.Reply parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.Reply parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.Reply parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.Reply parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.Reply parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.Reply parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.Reply parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.Reply parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.Reply parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.Reply parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.Reply parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.Reply parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.testing.Reply prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.reflection.testing.Reply}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.testing.Reply)
+      io.grpc.reflection.testing.ReplyOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.testing.ReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_Reply_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.testing.ReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_Reply_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.testing.Reply.class, io.grpc.reflection.testing.Reply.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.testing.Reply.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      message_ = "";
+      bitField0_ = (bitField0_ & ~0x00000001);
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.testing.ReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_Reply_descriptor;
+    }
+
+    public io.grpc.reflection.testing.Reply getDefaultInstanceForType() {
+      return io.grpc.reflection.testing.Reply.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.testing.Reply build() {
+      io.grpc.reflection.testing.Reply result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.testing.Reply buildPartial() {
+      io.grpc.reflection.testing.Reply result = new io.grpc.reflection.testing.Reply(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+        to_bitField0_ |= 0x00000001;
+      }
+      result.message_ = message_;
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.testing.Reply) {
+        return mergeFrom((io.grpc.reflection.testing.Reply)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.testing.Reply other) {
+      if (other == io.grpc.reflection.testing.Reply.getDefaultInstance()) return this;
+      if (other.hasMessage()) {
+        bitField0_ |= 0x00000001;
+        message_ = other.message_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.testing.Reply parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.testing.Reply) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.lang.Object message_ = "";
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public boolean hasMessage() {
+      return ((bitField0_ & 0x00000001) == 0x00000001);
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public java.lang.String getMessage() {
+      java.lang.Object ref = message_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (bs.isValidUtf8()) {
+          message_ = s;
+        }
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getMessageBytes() {
+      java.lang.Object ref = message_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        message_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder setMessage(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder clearMessage() {
+      bitField0_ = (bitField0_ & ~0x00000001);
+      message_ = getDefaultInstance().getMessage();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder setMessageBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.testing.Reply)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.testing.Reply)
+  private static final io.grpc.reflection.testing.Reply DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.testing.Reply();
+  }
+
+  public static io.grpc.reflection.testing.Reply getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  @java.lang.Deprecated public static final com.google.protobuf.Parser<Reply>
+      PARSER = new com.google.protobuf.AbstractParser<Reply>() {
+    public Reply parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Reply(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Reply> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Reply> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.testing.Reply getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/ReplyOrBuilder.java b/services/src/generated/test/java/io/grpc/reflection/testing/ReplyOrBuilder.java
new file mode 100644
index 0000000..4f5a6ca
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/ReplyOrBuilder.java
@@ -0,0 +1,23 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test_depth_two.proto
+
+package io.grpc.reflection.testing;
+
+public interface ReplyOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.testing.Reply)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  boolean hasMessage();
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  java.lang.String getMessage();
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getMessageBytes();
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/Request.java b/services/src/generated/test/java/io/grpc/reflection/testing/Request.java
new file mode 100644
index 0000000..9d657af
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/Request.java
@@ -0,0 +1,545 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test_depth_two.proto
+
+package io.grpc.reflection.testing;
+
+/**
+ * Protobuf type {@code grpc.reflection.testing.Request}
+ */
+public  final class Request extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.testing.Request)
+    RequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use Request.newBuilder() to construct.
+  private Request(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private Request() {
+    message_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private Request(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            com.google.protobuf.ByteString bs = input.readBytes();
+            bitField0_ |= 0x00000001;
+            message_ = bs;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.testing.ReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_Request_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.testing.ReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_Request_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.testing.Request.class, io.grpc.reflection.testing.Request.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int MESSAGE_FIELD_NUMBER = 1;
+  private volatile java.lang.Object message_;
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public boolean hasMessage() {
+    return ((bitField0_ & 0x00000001) == 0x00000001);
+  }
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public java.lang.String getMessage() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      if (bs.isValidUtf8()) {
+        message_ = s;
+      }
+      return s;
+    }
+  }
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getMessageBytes() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      message_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (((bitField0_ & 0x00000001) == 0x00000001)) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, message_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (((bitField0_ & 0x00000001) == 0x00000001)) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, message_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.testing.Request)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.testing.Request other = (io.grpc.reflection.testing.Request) obj;
+
+    boolean result = true;
+    result = result && (hasMessage() == other.hasMessage());
+    if (hasMessage()) {
+      result = result && getMessage()
+          .equals(other.getMessage());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasMessage()) {
+      hash = (37 * hash) + MESSAGE_FIELD_NUMBER;
+      hash = (53 * hash) + getMessage().hashCode();
+    }
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.testing.Request parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.Request parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.Request parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.Request parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.Request parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.Request parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.Request parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.Request parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.Request parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.Request parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.Request parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.Request parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.testing.Request prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.reflection.testing.Request}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.testing.Request)
+      io.grpc.reflection.testing.RequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.testing.ReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_Request_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.testing.ReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_Request_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.testing.Request.class, io.grpc.reflection.testing.Request.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.testing.Request.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      message_ = "";
+      bitField0_ = (bitField0_ & ~0x00000001);
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.testing.ReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_Request_descriptor;
+    }
+
+    public io.grpc.reflection.testing.Request getDefaultInstanceForType() {
+      return io.grpc.reflection.testing.Request.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.testing.Request build() {
+      io.grpc.reflection.testing.Request result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.testing.Request buildPartial() {
+      io.grpc.reflection.testing.Request result = new io.grpc.reflection.testing.Request(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+        to_bitField0_ |= 0x00000001;
+      }
+      result.message_ = message_;
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.testing.Request) {
+        return mergeFrom((io.grpc.reflection.testing.Request)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.testing.Request other) {
+      if (other == io.grpc.reflection.testing.Request.getDefaultInstance()) return this;
+      if (other.hasMessage()) {
+        bitField0_ |= 0x00000001;
+        message_ = other.message_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.testing.Request parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.testing.Request) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.lang.Object message_ = "";
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public boolean hasMessage() {
+      return ((bitField0_ & 0x00000001) == 0x00000001);
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public java.lang.String getMessage() {
+      java.lang.Object ref = message_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (bs.isValidUtf8()) {
+          message_ = s;
+        }
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getMessageBytes() {
+      java.lang.Object ref = message_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        message_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder setMessage(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder clearMessage() {
+      bitField0_ = (bitField0_ & ~0x00000001);
+      message_ = getDefaultInstance().getMessage();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder setMessageBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.testing.Request)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.testing.Request)
+  private static final io.grpc.reflection.testing.Request DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.testing.Request();
+  }
+
+  public static io.grpc.reflection.testing.Request getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  @java.lang.Deprecated public static final com.google.protobuf.Parser<Request>
+      PARSER = new com.google.protobuf.AbstractParser<Request>() {
+    public Request parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new Request(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<Request> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<Request> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.testing.Request getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/RequestOrBuilder.java b/services/src/generated/test/java/io/grpc/reflection/testing/RequestOrBuilder.java
new file mode 100644
index 0000000..333e280
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/RequestOrBuilder.java
@@ -0,0 +1,23 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test_depth_two.proto
+
+package io.grpc.reflection.testing;
+
+public interface RequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.testing.Request)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  boolean hasMessage();
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  java.lang.String getMessage();
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getMessageBytes();
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/ThirdLevelType.java b/services/src/generated/test/java/io/grpc/reflection/testing/ThirdLevelType.java
new file mode 100644
index 0000000..7b82b1a
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/ThirdLevelType.java
@@ -0,0 +1,586 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test_depth_three.proto
+
+package io.grpc.reflection.testing;
+
+/**
+ * Protobuf type {@code grpc.reflection.testing.ThirdLevelType}
+ */
+public  final class ThirdLevelType extends
+    com.google.protobuf.GeneratedMessageV3.ExtendableMessage<
+      ThirdLevelType> implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.testing.ThirdLevelType)
+    ThirdLevelTypeOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use ThirdLevelType.newBuilder() to construct.
+  private ThirdLevelType(com.google.protobuf.GeneratedMessageV3.ExtendableBuilder<io.grpc.reflection.testing.ThirdLevelType, ?> builder) {
+    super(builder);
+  }
+  private ThirdLevelType() {
+    message_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private ThirdLevelType(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            com.google.protobuf.ByteString bs = input.readBytes();
+            bitField0_ |= 0x00000001;
+            message_ = bs;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_ThirdLevelType_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_ThirdLevelType_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.testing.ThirdLevelType.class, io.grpc.reflection.testing.ThirdLevelType.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int MESSAGE_FIELD_NUMBER = 1;
+  private volatile java.lang.Object message_;
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public boolean hasMessage() {
+    return ((bitField0_ & 0x00000001) == 0x00000001);
+  }
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public java.lang.String getMessage() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      if (bs.isValidUtf8()) {
+        message_ = s;
+      }
+      return s;
+    }
+  }
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getMessageBytes() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      message_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    if (!extensionsAreInitialized()) {
+      memoizedIsInitialized = 0;
+      return false;
+    }
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    com.google.protobuf.GeneratedMessageV3
+      .ExtendableMessage<io.grpc.reflection.testing.ThirdLevelType>.ExtensionWriter
+        extensionWriter = newExtensionWriter();
+    if (((bitField0_ & 0x00000001) == 0x00000001)) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, message_);
+    }
+    extensionWriter.writeUntil(200, output);
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (((bitField0_ & 0x00000001) == 0x00000001)) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, message_);
+    }
+    size += extensionsSerializedSize();
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.testing.ThirdLevelType)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.testing.ThirdLevelType other = (io.grpc.reflection.testing.ThirdLevelType) obj;
+
+    boolean result = true;
+    result = result && (hasMessage() == other.hasMessage());
+    if (hasMessage()) {
+      result = result && getMessage()
+          .equals(other.getMessage());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    result = result &&
+        getExtensionFields().equals(other.getExtensionFields());
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasMessage()) {
+      hash = (37 * hash) + MESSAGE_FIELD_NUMBER;
+      hash = (53 * hash) + getMessage().hashCode();
+    }
+    hash = hashFields(hash, getExtensionFields());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.testing.ThirdLevelType parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.ThirdLevelType parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.ThirdLevelType parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.ThirdLevelType parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.ThirdLevelType parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.ThirdLevelType parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.ThirdLevelType parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.ThirdLevelType parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.ThirdLevelType parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.ThirdLevelType parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.ThirdLevelType parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.ThirdLevelType parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.testing.ThirdLevelType prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.reflection.testing.ThirdLevelType}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.ExtendableBuilder<
+        io.grpc.reflection.testing.ThirdLevelType, Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.testing.ThirdLevelType)
+      io.grpc.reflection.testing.ThirdLevelTypeOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_ThirdLevelType_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_ThirdLevelType_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.testing.ThirdLevelType.class, io.grpc.reflection.testing.ThirdLevelType.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.testing.ThirdLevelType.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      message_ = "";
+      bitField0_ = (bitField0_ & ~0x00000001);
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.testing.ReflectionTestDepthThreeProto.internal_static_grpc_reflection_testing_ThirdLevelType_descriptor;
+    }
+
+    public io.grpc.reflection.testing.ThirdLevelType getDefaultInstanceForType() {
+      return io.grpc.reflection.testing.ThirdLevelType.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.testing.ThirdLevelType build() {
+      io.grpc.reflection.testing.ThirdLevelType result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.testing.ThirdLevelType buildPartial() {
+      io.grpc.reflection.testing.ThirdLevelType result = new io.grpc.reflection.testing.ThirdLevelType(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+        to_bitField0_ |= 0x00000001;
+      }
+      result.message_ = message_;
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public <Type> Builder setExtension(
+        com.google.protobuf.GeneratedMessage.GeneratedExtension<
+            io.grpc.reflection.testing.ThirdLevelType, Type> extension,
+        Type value) {
+      return (Builder) super.setExtension(extension, value);
+    }
+    public <Type> Builder setExtension(
+        com.google.protobuf.GeneratedMessage.GeneratedExtension<
+            io.grpc.reflection.testing.ThirdLevelType, java.util.List<Type>> extension,
+        int index, Type value) {
+      return (Builder) super.setExtension(extension, index, value);
+    }
+    public <Type> Builder addExtension(
+        com.google.protobuf.GeneratedMessage.GeneratedExtension<
+            io.grpc.reflection.testing.ThirdLevelType, java.util.List<Type>> extension,
+        Type value) {
+      return (Builder) super.addExtension(extension, value);
+    }
+    public <Type> Builder clearExtension(
+        com.google.protobuf.GeneratedMessage.GeneratedExtension<
+            io.grpc.reflection.testing.ThirdLevelType, ?> extension) {
+      return (Builder) super.clearExtension(extension);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.testing.ThirdLevelType) {
+        return mergeFrom((io.grpc.reflection.testing.ThirdLevelType)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.testing.ThirdLevelType other) {
+      if (other == io.grpc.reflection.testing.ThirdLevelType.getDefaultInstance()) return this;
+      if (other.hasMessage()) {
+        bitField0_ |= 0x00000001;
+        message_ = other.message_;
+        onChanged();
+      }
+      this.mergeExtensionFields(other);
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      if (!extensionsAreInitialized()) {
+        return false;
+      }
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.testing.ThirdLevelType parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.testing.ThirdLevelType) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.lang.Object message_ = "";
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public boolean hasMessage() {
+      return ((bitField0_ & 0x00000001) == 0x00000001);
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public java.lang.String getMessage() {
+      java.lang.Object ref = message_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (bs.isValidUtf8()) {
+          message_ = s;
+        }
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getMessageBytes() {
+      java.lang.Object ref = message_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        message_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder setMessage(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder clearMessage() {
+      bitField0_ = (bitField0_ & ~0x00000001);
+      message_ = getDefaultInstance().getMessage();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder setMessageBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.testing.ThirdLevelType)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.testing.ThirdLevelType)
+  private static final io.grpc.reflection.testing.ThirdLevelType DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.testing.ThirdLevelType();
+  }
+
+  public static io.grpc.reflection.testing.ThirdLevelType getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  @java.lang.Deprecated public static final com.google.protobuf.Parser<ThirdLevelType>
+      PARSER = new com.google.protobuf.AbstractParser<ThirdLevelType>() {
+    public ThirdLevelType parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new ThirdLevelType(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<ThirdLevelType> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<ThirdLevelType> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.testing.ThirdLevelType getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/ThirdLevelTypeOrBuilder.java b/services/src/generated/test/java/io/grpc/reflection/testing/ThirdLevelTypeOrBuilder.java
new file mode 100644
index 0000000..ca3ff1a
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/ThirdLevelTypeOrBuilder.java
@@ -0,0 +1,24 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/reflection_test_depth_three.proto
+
+package io.grpc.reflection.testing;
+
+public interface ThirdLevelTypeOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.testing.ThirdLevelType)
+    com.google.protobuf.GeneratedMessageV3.
+        ExtendableMessageOrBuilder<ThirdLevelType> {
+
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  boolean hasMessage();
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  java.lang.String getMessage();
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getMessageBytes();
+}
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/TypeWithExtensions.java b/services/src/generated/test/java/io/grpc/reflection/testing/TypeWithExtensions.java
new file mode 100644
index 0000000..3f430ff
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/TypeWithExtensions.java
@@ -0,0 +1,586 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/dynamic_reflection_test_depth_two.proto
+
+package io.grpc.reflection.testing;
+
+/**
+ * Protobuf type {@code grpc.reflection.testing.TypeWithExtensions}
+ */
+public  final class TypeWithExtensions extends
+    com.google.protobuf.GeneratedMessageV3.ExtendableMessage<
+      TypeWithExtensions> implements
+    // @@protoc_insertion_point(message_implements:grpc.reflection.testing.TypeWithExtensions)
+    TypeWithExtensionsOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use TypeWithExtensions.newBuilder() to construct.
+  private TypeWithExtensions(com.google.protobuf.GeneratedMessageV3.ExtendableBuilder<io.grpc.reflection.testing.TypeWithExtensions, ?> builder) {
+    super(builder);
+  }
+  private TypeWithExtensions() {
+    message_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private TypeWithExtensions(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownField(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            com.google.protobuf.ByteString bs = input.readBytes();
+            bitField0_ |= 0x00000001;
+            message_ = bs;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_TypeWithExtensions_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_TypeWithExtensions_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.reflection.testing.TypeWithExtensions.class, io.grpc.reflection.testing.TypeWithExtensions.Builder.class);
+  }
+
+  private int bitField0_;
+  public static final int MESSAGE_FIELD_NUMBER = 1;
+  private volatile java.lang.Object message_;
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public boolean hasMessage() {
+    return ((bitField0_ & 0x00000001) == 0x00000001);
+  }
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public java.lang.String getMessage() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      if (bs.isValidUtf8()) {
+        message_ = s;
+      }
+      return s;
+    }
+  }
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getMessageBytes() {
+    java.lang.Object ref = message_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      message_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    if (!extensionsAreInitialized()) {
+      memoizedIsInitialized = 0;
+      return false;
+    }
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    com.google.protobuf.GeneratedMessageV3
+      .ExtendableMessage<io.grpc.reflection.testing.TypeWithExtensions>.ExtensionWriter
+        extensionWriter = newExtensionWriter();
+    if (((bitField0_ & 0x00000001) == 0x00000001)) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, message_);
+    }
+    extensionWriter.writeUntil(300, output);
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (((bitField0_ & 0x00000001) == 0x00000001)) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, message_);
+    }
+    size += extensionsSerializedSize();
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.reflection.testing.TypeWithExtensions)) {
+      return super.equals(obj);
+    }
+    io.grpc.reflection.testing.TypeWithExtensions other = (io.grpc.reflection.testing.TypeWithExtensions) obj;
+
+    boolean result = true;
+    result = result && (hasMessage() == other.hasMessage());
+    if (hasMessage()) {
+      result = result && getMessage()
+          .equals(other.getMessage());
+    }
+    result = result && unknownFields.equals(other.unknownFields);
+    result = result &&
+        getExtensionFields().equals(other.getExtensionFields());
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasMessage()) {
+      hash = (37 * hash) + MESSAGE_FIELD_NUMBER;
+      hash = (53 * hash) + getMessage().hashCode();
+    }
+    hash = hashFields(hash, getExtensionFields());
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.reflection.testing.TypeWithExtensions parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.TypeWithExtensions parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.TypeWithExtensions parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.TypeWithExtensions parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.TypeWithExtensions parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.reflection.testing.TypeWithExtensions parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.TypeWithExtensions parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.TypeWithExtensions parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.TypeWithExtensions parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.TypeWithExtensions parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.reflection.testing.TypeWithExtensions parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.reflection.testing.TypeWithExtensions parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.reflection.testing.TypeWithExtensions prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code grpc.reflection.testing.TypeWithExtensions}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.ExtendableBuilder<
+        io.grpc.reflection.testing.TypeWithExtensions, Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.reflection.testing.TypeWithExtensions)
+      io.grpc.reflection.testing.TypeWithExtensionsOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_TypeWithExtensions_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_TypeWithExtensions_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.reflection.testing.TypeWithExtensions.class, io.grpc.reflection.testing.TypeWithExtensions.Builder.class);
+    }
+
+    // Construct using io.grpc.reflection.testing.TypeWithExtensions.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      message_ = "";
+      bitField0_ = (bitField0_ & ~0x00000001);
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto.internal_static_grpc_reflection_testing_TypeWithExtensions_descriptor;
+    }
+
+    public io.grpc.reflection.testing.TypeWithExtensions getDefaultInstanceForType() {
+      return io.grpc.reflection.testing.TypeWithExtensions.getDefaultInstance();
+    }
+
+    public io.grpc.reflection.testing.TypeWithExtensions build() {
+      io.grpc.reflection.testing.TypeWithExtensions result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.reflection.testing.TypeWithExtensions buildPartial() {
+      io.grpc.reflection.testing.TypeWithExtensions result = new io.grpc.reflection.testing.TypeWithExtensions(this);
+      int from_bitField0_ = bitField0_;
+      int to_bitField0_ = 0;
+      if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+        to_bitField0_ |= 0x00000001;
+      }
+      result.message_ = message_;
+      result.bitField0_ = to_bitField0_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public <Type> Builder setExtension(
+        com.google.protobuf.GeneratedMessage.GeneratedExtension<
+            io.grpc.reflection.testing.TypeWithExtensions, Type> extension,
+        Type value) {
+      return (Builder) super.setExtension(extension, value);
+    }
+    public <Type> Builder setExtension(
+        com.google.protobuf.GeneratedMessage.GeneratedExtension<
+            io.grpc.reflection.testing.TypeWithExtensions, java.util.List<Type>> extension,
+        int index, Type value) {
+      return (Builder) super.setExtension(extension, index, value);
+    }
+    public <Type> Builder addExtension(
+        com.google.protobuf.GeneratedMessage.GeneratedExtension<
+            io.grpc.reflection.testing.TypeWithExtensions, java.util.List<Type>> extension,
+        Type value) {
+      return (Builder) super.addExtension(extension, value);
+    }
+    public <Type> Builder clearExtension(
+        com.google.protobuf.GeneratedMessage.GeneratedExtension<
+            io.grpc.reflection.testing.TypeWithExtensions, ?> extension) {
+      return (Builder) super.clearExtension(extension);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.reflection.testing.TypeWithExtensions) {
+        return mergeFrom((io.grpc.reflection.testing.TypeWithExtensions)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.reflection.testing.TypeWithExtensions other) {
+      if (other == io.grpc.reflection.testing.TypeWithExtensions.getDefaultInstance()) return this;
+      if (other.hasMessage()) {
+        bitField0_ |= 0x00000001;
+        message_ = other.message_;
+        onChanged();
+      }
+      this.mergeExtensionFields(other);
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      if (!extensionsAreInitialized()) {
+        return false;
+      }
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.reflection.testing.TypeWithExtensions parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.reflection.testing.TypeWithExtensions) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+    private int bitField0_;
+
+    private java.lang.Object message_ = "";
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public boolean hasMessage() {
+      return ((bitField0_ & 0x00000001) == 0x00000001);
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public java.lang.String getMessage() {
+      java.lang.Object ref = message_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        if (bs.isValidUtf8()) {
+          message_ = s;
+        }
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getMessageBytes() {
+      java.lang.Object ref = message_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        message_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder setMessage(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder clearMessage() {
+      bitField0_ = (bitField0_ & ~0x00000001);
+      message_ = getDefaultInstance().getMessage();
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>optional string message = 1;</code>
+     */
+    public Builder setMessageBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+      message_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.reflection.testing.TypeWithExtensions)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.reflection.testing.TypeWithExtensions)
+  private static final io.grpc.reflection.testing.TypeWithExtensions DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.reflection.testing.TypeWithExtensions();
+  }
+
+  public static io.grpc.reflection.testing.TypeWithExtensions getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  @java.lang.Deprecated public static final com.google.protobuf.Parser<TypeWithExtensions>
+      PARSER = new com.google.protobuf.AbstractParser<TypeWithExtensions>() {
+    public TypeWithExtensions parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new TypeWithExtensions(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<TypeWithExtensions> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<TypeWithExtensions> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.reflection.testing.TypeWithExtensions getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/services/src/generated/test/java/io/grpc/reflection/testing/TypeWithExtensionsOrBuilder.java b/services/src/generated/test/java/io/grpc/reflection/testing/TypeWithExtensionsOrBuilder.java
new file mode 100644
index 0000000..b5cebab
--- /dev/null
+++ b/services/src/generated/test/java/io/grpc/reflection/testing/TypeWithExtensionsOrBuilder.java
@@ -0,0 +1,24 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/reflection/testing/dynamic_reflection_test_depth_two.proto
+
+package io.grpc.reflection.testing;
+
+public interface TypeWithExtensionsOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.reflection.testing.TypeWithExtensions)
+    com.google.protobuf.GeneratedMessageV3.
+        ExtendableMessageOrBuilder<TypeWithExtensions> {
+
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  boolean hasMessage();
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  java.lang.String getMessage();
+  /**
+   * <code>optional string message = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getMessageBytes();
+}
diff --git a/services/src/main/java/io/grpc/protobuf/services/ProtoReflectionService.java b/services/src/main/java/io/grpc/protobuf/services/ProtoReflectionService.java
new file mode 100644
index 0000000..53edf79
--- /dev/null
+++ b/services/src/main/java/io/grpc/protobuf/services/ProtoReflectionService.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf.services;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.protobuf.Descriptors.Descriptor;
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.Descriptors.FileDescriptor;
+import com.google.protobuf.Descriptors.MethodDescriptor;
+import com.google.protobuf.Descriptors.ServiceDescriptor;
+import io.grpc.BindableService;
+import io.grpc.ExperimentalApi;
+import io.grpc.InternalNotifyOnServerBuild;
+import io.grpc.Server;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.Status;
+import io.grpc.protobuf.ProtoFileDescriptorSupplier;
+import io.grpc.reflection.v1alpha.ErrorResponse;
+import io.grpc.reflection.v1alpha.ExtensionNumberResponse;
+import io.grpc.reflection.v1alpha.ExtensionRequest;
+import io.grpc.reflection.v1alpha.FileDescriptorResponse;
+import io.grpc.reflection.v1alpha.ListServiceResponse;
+import io.grpc.reflection.v1alpha.ServerReflectionGrpc;
+import io.grpc.reflection.v1alpha.ServerReflectionRequest;
+import io.grpc.reflection.v1alpha.ServerReflectionResponse;
+import io.grpc.reflection.v1alpha.ServiceResponse;
+import io.grpc.stub.ServerCallStreamObserver;
+import io.grpc.stub.StreamObserver;
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Provides a reflection service for Protobuf services (including the reflection service itself).
+ *
+ * <p>Separately tracks mutable and immutable services. Throws an exception if either group of
+ * services contains multiple Protobuf files with declarations of the same service, method, type, or
+ * extension.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2222")
+public final class ProtoReflectionService extends ServerReflectionGrpc.ServerReflectionImplBase
+    implements InternalNotifyOnServerBuild {
+
+  private final Object lock = new Object();
+
+  @GuardedBy("lock")
+  private ServerReflectionIndex serverReflectionIndex;
+
+  private Server server;
+
+  private ProtoReflectionService() {}
+
+  public static BindableService newInstance() {
+    return new ProtoReflectionService();
+  }
+
+  /** Receives a reference to the server at build time. */
+  @Override
+  public void notifyOnBuild(Server server) {
+    this.server = checkNotNull(server);
+  }
+
+  /**
+   * Checks for updates to the server's mutable services and updates the index if any changes are
+   * detected. A change is any addition or removal in the set of file descriptors attached to the
+   * mutable services or a change in the service names.
+   *
+   * @return The (potentially updated) index.
+   */
+  private ServerReflectionIndex updateIndexIfNecessary() {
+    synchronized (lock) {
+      if (serverReflectionIndex == null) {
+        serverReflectionIndex =
+            new ServerReflectionIndex(server.getImmutableServices(), server.getMutableServices());
+        return serverReflectionIndex;
+      }
+
+      Set<FileDescriptor> serverFileDescriptors = new HashSet<FileDescriptor>();
+      Set<String> serverServiceNames = new HashSet<String>();
+      List<ServerServiceDefinition> serverMutableServices = server.getMutableServices();
+      for (ServerServiceDefinition mutableService : serverMutableServices) {
+        io.grpc.ServiceDescriptor serviceDescriptor = mutableService.getServiceDescriptor();
+        if (serviceDescriptor.getSchemaDescriptor() instanceof ProtoFileDescriptorSupplier) {
+          String serviceName = serviceDescriptor.getName();
+          FileDescriptor fileDescriptor =
+              ((ProtoFileDescriptorSupplier) serviceDescriptor.getSchemaDescriptor())
+                  .getFileDescriptor();
+          serverFileDescriptors.add(fileDescriptor);
+          serverServiceNames.add(serviceName);
+        }
+      }
+
+      // Replace the index if the underlying mutable services have changed. Check both the file
+      // descriptors and the service names, because one file descriptor can define multiple
+      // services.
+      FileDescriptorIndex mutableServicesIndex = serverReflectionIndex.getMutableServicesIndex();
+      if (!mutableServicesIndex.getServiceFileDescriptors().equals(serverFileDescriptors)
+          || !mutableServicesIndex.getServiceNames().equals(serverServiceNames)) {
+        serverReflectionIndex =
+            new ServerReflectionIndex(server.getImmutableServices(), serverMutableServices);
+      }
+
+      return serverReflectionIndex;
+    }
+  }
+
+  @Override
+  public StreamObserver<ServerReflectionRequest> serverReflectionInfo(
+      final StreamObserver<ServerReflectionResponse> responseObserver) {
+    final ServerCallStreamObserver<ServerReflectionResponse> serverCallStreamObserver =
+        (ServerCallStreamObserver<ServerReflectionResponse>) responseObserver;
+    ProtoReflectionStreamObserver requestObserver =
+        new ProtoReflectionStreamObserver(updateIndexIfNecessary(), serverCallStreamObserver);
+    serverCallStreamObserver.setOnReadyHandler(requestObserver);
+    serverCallStreamObserver.disableAutoInboundFlowControl();
+    serverCallStreamObserver.request(1);
+    return requestObserver;
+  }
+
+  private static class ProtoReflectionStreamObserver
+      implements Runnable, StreamObserver<ServerReflectionRequest> {
+    private final ServerReflectionIndex serverReflectionIndex;
+    private final ServerCallStreamObserver<ServerReflectionResponse> serverCallStreamObserver;
+
+    private boolean closeAfterSend = false;
+    private ServerReflectionRequest request;
+
+    ProtoReflectionStreamObserver(
+        ServerReflectionIndex serverReflectionIndex,
+        ServerCallStreamObserver<ServerReflectionResponse> serverCallStreamObserver) {
+      this.serverReflectionIndex = serverReflectionIndex;
+      this.serverCallStreamObserver = checkNotNull(serverCallStreamObserver, "observer");
+    }
+
+    @Override
+    public void run() {
+      if (request != null) {
+        handleReflectionRequest();
+      }
+    }
+
+    @Override
+    public void onNext(ServerReflectionRequest request) {
+      checkState(this.request == null);
+      this.request = checkNotNull(request);
+      handleReflectionRequest();
+    }
+
+    private void handleReflectionRequest() {
+      if (serverCallStreamObserver.isReady()) {
+        switch (request.getMessageRequestCase()) {
+          case FILE_BY_FILENAME:
+            getFileByName(request);
+            break;
+          case FILE_CONTAINING_SYMBOL:
+            getFileContainingSymbol(request);
+            break;
+          case FILE_CONTAINING_EXTENSION:
+            getFileByExtension(request);
+            break;
+          case ALL_EXTENSION_NUMBERS_OF_TYPE:
+            getAllExtensions(request);
+            break;
+          case LIST_SERVICES:
+            listServices(request);
+            break;
+          default:
+            sendErrorResponse(
+                request,
+                Status.Code.UNIMPLEMENTED,
+                "not implemented " + request.getMessageRequestCase());
+        }
+        request = null;
+        if (closeAfterSend) {
+          serverCallStreamObserver.onCompleted();
+        } else {
+          serverCallStreamObserver.request(1);
+        }
+      }
+    }
+
+    @Override
+    public void onCompleted() {
+      if (request != null) {
+        closeAfterSend = true;
+      } else {
+        serverCallStreamObserver.onCompleted();
+      }
+    }
+
+    @Override
+    public void onError(Throwable cause) {
+      serverCallStreamObserver.onError(cause);
+    }
+
+    private void getFileByName(ServerReflectionRequest request) {
+      String name = request.getFileByFilename();
+      FileDescriptor fd = serverReflectionIndex.getFileDescriptorByName(name);
+      if (fd != null) {
+        serverCallStreamObserver.onNext(createServerReflectionResponse(request, fd));
+      } else {
+        sendErrorResponse(request, Status.Code.NOT_FOUND, "File not found.");
+      }
+    }
+
+    private void getFileContainingSymbol(ServerReflectionRequest request) {
+      String symbol = request.getFileContainingSymbol();
+      FileDescriptor fd = serverReflectionIndex.getFileDescriptorBySymbol(symbol);
+      if (fd != null) {
+        serverCallStreamObserver.onNext(createServerReflectionResponse(request, fd));
+      } else {
+        sendErrorResponse(request, Status.Code.NOT_FOUND, "Symbol not found.");
+      }
+    }
+
+    private void getFileByExtension(ServerReflectionRequest request) {
+      ExtensionRequest extensionRequest = request.getFileContainingExtension();
+      String type = extensionRequest.getContainingType();
+      int extension = extensionRequest.getExtensionNumber();
+      FileDescriptor fd =
+          serverReflectionIndex.getFileDescriptorByExtensionAndNumber(type, extension);
+      if (fd != null) {
+        serverCallStreamObserver.onNext(createServerReflectionResponse(request, fd));
+      } else {
+        sendErrorResponse(request, Status.Code.NOT_FOUND, "Extension not found.");
+      }
+    }
+
+    private void getAllExtensions(ServerReflectionRequest request) {
+      String type = request.getAllExtensionNumbersOfType();
+      Set<Integer> extensions = serverReflectionIndex.getExtensionNumbersOfType(type);
+      if (extensions != null) {
+        ExtensionNumberResponse.Builder builder =
+            ExtensionNumberResponse.newBuilder()
+                .setBaseTypeName(type)
+                .addAllExtensionNumber(extensions);
+        serverCallStreamObserver.onNext(
+            ServerReflectionResponse.newBuilder()
+                .setValidHost(request.getHost())
+                .setOriginalRequest(request)
+                .setAllExtensionNumbersResponse(builder)
+                .build());
+      } else {
+        sendErrorResponse(request, Status.Code.NOT_FOUND, "Type not found.");
+      }
+    }
+
+    private void listServices(ServerReflectionRequest request) {
+      ListServiceResponse.Builder builder = ListServiceResponse.newBuilder();
+      for (String serviceName : serverReflectionIndex.getServiceNames()) {
+        builder.addService(ServiceResponse.newBuilder().setName(serviceName));
+      }
+      serverCallStreamObserver.onNext(
+          ServerReflectionResponse.newBuilder()
+              .setValidHost(request.getHost())
+              .setOriginalRequest(request)
+              .setListServicesResponse(builder)
+              .build());
+    }
+
+    private void sendErrorResponse(
+        ServerReflectionRequest request, Status.Code code, String message) {
+      ServerReflectionResponse response =
+          ServerReflectionResponse.newBuilder()
+              .setValidHost(request.getHost())
+              .setOriginalRequest(request)
+              .setErrorResponse(
+                  ErrorResponse.newBuilder()
+                      .setErrorCode(code.value())
+                      .setErrorMessage(message))
+              .build();
+      serverCallStreamObserver.onNext(response);
+    }
+
+    private ServerReflectionResponse createServerReflectionResponse(
+        ServerReflectionRequest request, FileDescriptor fd) {
+      FileDescriptorResponse.Builder fdRBuilder = FileDescriptorResponse.newBuilder();
+
+      Set<String> seenFiles = new HashSet<String>();
+      Queue<FileDescriptor> frontier = new ArrayDeque<FileDescriptor>();
+      seenFiles.add(fd.getName());
+      frontier.add(fd);
+      while (!frontier.isEmpty()) {
+        FileDescriptor nextFd = frontier.remove();
+        fdRBuilder.addFileDescriptorProto(nextFd.toProto().toByteString());
+        for (FileDescriptor dependencyFd : nextFd.getDependencies()) {
+          if (!seenFiles.contains(dependencyFd.getName())) {
+            seenFiles.add(dependencyFd.getName());
+            frontier.add(dependencyFd);
+          }
+        }
+      }
+      return ServerReflectionResponse.newBuilder()
+          .setValidHost(request.getHost())
+          .setOriginalRequest(request)
+          .setFileDescriptorResponse(fdRBuilder)
+          .build();
+    }
+  }
+
+  /**
+   * Indexes the server's services and allows lookups of file descriptors by filename, symbol, type,
+   * and extension number.
+   *
+   * <p>Internally, this stores separate indices for the immutable and mutable services. When
+   * queried, the immutable service index is checked for a matching value. Only if there is no match
+   * in the immutable service index are the mutable services checked.
+   */
+  private static final class ServerReflectionIndex {
+    private final FileDescriptorIndex immutableServicesIndex;
+    private final FileDescriptorIndex mutableServicesIndex;
+
+    public ServerReflectionIndex(
+        List<ServerServiceDefinition> immutableServices,
+        List<ServerServiceDefinition> mutableServices) {
+      immutableServicesIndex = new FileDescriptorIndex(immutableServices);
+      mutableServicesIndex = new FileDescriptorIndex(mutableServices);
+    }
+
+    private FileDescriptorIndex getMutableServicesIndex() {
+      return mutableServicesIndex;
+    }
+
+    private Set<String> getServiceNames() {
+      Set<String> immutableServiceNames = immutableServicesIndex.getServiceNames();
+      Set<String> mutableServiceNames = mutableServicesIndex.getServiceNames();
+      Set<String> serviceNames =
+          new HashSet<String>(immutableServiceNames.size() + mutableServiceNames.size());
+      serviceNames.addAll(immutableServiceNames);
+      serviceNames.addAll(mutableServiceNames);
+      return serviceNames;
+    }
+
+    @Nullable
+    private FileDescriptor getFileDescriptorByName(String name) {
+      FileDescriptor fd = immutableServicesIndex.getFileDescriptorByName(name);
+      if (fd == null) {
+        fd = mutableServicesIndex.getFileDescriptorByName(name);
+      }
+      return fd;
+    }
+
+    @Nullable
+    private FileDescriptor getFileDescriptorBySymbol(String symbol) {
+      FileDescriptor fd = immutableServicesIndex.getFileDescriptorBySymbol(symbol);
+      if (fd == null) {
+        fd = mutableServicesIndex.getFileDescriptorBySymbol(symbol);
+      }
+      return fd;
+    }
+
+    @Nullable
+    private FileDescriptor getFileDescriptorByExtensionAndNumber(String type, int extension) {
+      FileDescriptor fd =
+          immutableServicesIndex.getFileDescriptorByExtensionAndNumber(type, extension);
+      if (fd == null) {
+        fd = mutableServicesIndex.getFileDescriptorByExtensionAndNumber(type, extension);
+      }
+      return fd;
+    }
+
+    @Nullable
+    private Set<Integer> getExtensionNumbersOfType(String type) {
+      Set<Integer> extensionNumbers = immutableServicesIndex.getExtensionNumbersOfType(type);
+      if (extensionNumbers == null) {
+        extensionNumbers = mutableServicesIndex.getExtensionNumbersOfType(type);
+      }
+      return extensionNumbers;
+    }
+  }
+
+  /**
+   * Provides a set of methods for answering reflection queries for the file descriptors underlying
+   * a set of services. Used by {@link ServerReflectionIndex} to separately index immutable and
+   * mutable services.
+   */
+  private static final class FileDescriptorIndex {
+    private final Set<String> serviceNames = new HashSet<String>();
+    private final Set<FileDescriptor> serviceFileDescriptors = new HashSet<FileDescriptor>();
+    private final Map<String, FileDescriptor> fileDescriptorsByName =
+        new HashMap<String, FileDescriptor>();
+    private final Map<String, FileDescriptor> fileDescriptorsBySymbol =
+        new HashMap<String, FileDescriptor>();
+    private final Map<String, Map<Integer, FileDescriptor>> fileDescriptorsByExtensionAndNumber =
+        new HashMap<String, Map<Integer, FileDescriptor>>();
+
+    FileDescriptorIndex(List<ServerServiceDefinition> services) {
+      Queue<FileDescriptor> fileDescriptorsToProcess = new ArrayDeque<FileDescriptor>();
+      Set<String> seenFiles = new HashSet<String>();
+      for (ServerServiceDefinition service : services) {
+        io.grpc.ServiceDescriptor serviceDescriptor = service.getServiceDescriptor();
+        if (serviceDescriptor.getSchemaDescriptor() instanceof ProtoFileDescriptorSupplier) {
+          FileDescriptor fileDescriptor =
+              ((ProtoFileDescriptorSupplier) serviceDescriptor.getSchemaDescriptor())
+                  .getFileDescriptor();
+          String serviceName = serviceDescriptor.getName();
+          checkState(
+              !serviceNames.contains(serviceName), "Service already defined: %s", serviceName);
+          serviceFileDescriptors.add(fileDescriptor);
+          serviceNames.add(serviceName);
+          if (!seenFiles.contains(fileDescriptor.getName())) {
+            seenFiles.add(fileDescriptor.getName());
+            fileDescriptorsToProcess.add(fileDescriptor);
+          }
+        }
+      }
+
+      while (!fileDescriptorsToProcess.isEmpty()) {
+        FileDescriptor currentFd = fileDescriptorsToProcess.remove();
+        processFileDescriptor(currentFd);
+        for (FileDescriptor dependencyFd : currentFd.getDependencies()) {
+          if (!seenFiles.contains(dependencyFd.getName())) {
+            seenFiles.add(dependencyFd.getName());
+            fileDescriptorsToProcess.add(dependencyFd);
+          }
+        }
+      }
+    }
+
+    /**
+     * Returns the file descriptors for the indexed services, but not their dependencies. This is
+     * used to check if the server's mutable services have changed.
+     */
+    private Set<FileDescriptor> getServiceFileDescriptors() {
+      return Collections.unmodifiableSet(serviceFileDescriptors);
+    }
+
+    private Set<String> getServiceNames() {
+      return Collections.unmodifiableSet(serviceNames);
+    }
+
+    @Nullable
+    private FileDescriptor getFileDescriptorByName(String name) {
+      return fileDescriptorsByName.get(name);
+    }
+
+    @Nullable
+    private FileDescriptor getFileDescriptorBySymbol(String symbol) {
+      return fileDescriptorsBySymbol.get(symbol);
+    }
+
+    @Nullable
+    private FileDescriptor getFileDescriptorByExtensionAndNumber(String type, int number) {
+      if (fileDescriptorsByExtensionAndNumber.containsKey(type)) {
+        return fileDescriptorsByExtensionAndNumber.get(type).get(number);
+      }
+      return null;
+    }
+
+    @Nullable
+    private Set<Integer> getExtensionNumbersOfType(String type) {
+      if (fileDescriptorsByExtensionAndNumber.containsKey(type)) {
+        return Collections.unmodifiableSet(fileDescriptorsByExtensionAndNumber.get(type).keySet());
+      }
+      return null;
+    }
+
+    private void processFileDescriptor(FileDescriptor fd) {
+      String fdName = fd.getName();
+      checkState(!fileDescriptorsByName.containsKey(fdName), "File name already used: %s", fdName);
+      fileDescriptorsByName.put(fdName, fd);
+      for (ServiceDescriptor service : fd.getServices()) {
+        processService(service, fd);
+      }
+      for (Descriptor type : fd.getMessageTypes()) {
+        processType(type, fd);
+      }
+      for (FieldDescriptor extension : fd.getExtensions()) {
+        processExtension(extension, fd);
+      }
+    }
+
+    private void processService(ServiceDescriptor service, FileDescriptor fd) {
+      String serviceName = service.getFullName();
+      checkState(
+          !fileDescriptorsBySymbol.containsKey(serviceName),
+          "Service already defined: %s",
+          serviceName);
+      fileDescriptorsBySymbol.put(serviceName, fd);
+      for (MethodDescriptor method : service.getMethods()) {
+        String methodName = method.getFullName();
+        checkState(
+            !fileDescriptorsBySymbol.containsKey(methodName),
+            "Method already defined: %s",
+            methodName);
+        fileDescriptorsBySymbol.put(methodName, fd);
+      }
+    }
+
+    private void processType(Descriptor type, FileDescriptor fd) {
+      String typeName = type.getFullName();
+      checkState(
+          !fileDescriptorsBySymbol.containsKey(typeName), "Type already defined: %s", typeName);
+      fileDescriptorsBySymbol.put(typeName, fd);
+      for (FieldDescriptor extension : type.getExtensions()) {
+        processExtension(extension, fd);
+      }
+      for (Descriptor nestedType : type.getNestedTypes()) {
+        processType(nestedType, fd);
+      }
+    }
+
+    private void processExtension(FieldDescriptor extension, FileDescriptor fd) {
+      String extensionName = extension.getContainingType().getFullName();
+      int extensionNumber = extension.getNumber();
+      if (!fileDescriptorsByExtensionAndNumber.containsKey(extensionName)) {
+        fileDescriptorsByExtensionAndNumber.put(
+            extensionName, new HashMap<Integer, FileDescriptor>());
+      }
+      checkState(
+          !fileDescriptorsByExtensionAndNumber.get(extensionName).containsKey(extensionNumber),
+          "Extension name and number already defined: %s, %s",
+          extensionName,
+          extensionNumber);
+      fileDescriptorsByExtensionAndNumber.get(extensionName).put(extensionNumber, fd);
+    }
+  }
+}
diff --git a/services/src/main/java/io/grpc/services/BinaryLogProvider.java b/services/src/main/java/io/grpc/services/BinaryLogProvider.java
new file mode 100644
index 0000000..3ca5de6
--- /dev/null
+++ b/services/src/main/java/io/grpc/services/BinaryLogProvider.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import io.grpc.BinaryLog;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ClientInterceptors;
+import io.grpc.Internal;
+import io.grpc.InternalClientInterceptors;
+import io.grpc.InternalServerInterceptors;
+import io.grpc.ManagedChannel;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.ServerMethodDefinition;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import javax.annotation.Nullable;
+
+// TODO(zpencer): rename class to AbstractBinaryLog
+@Internal
+public abstract class BinaryLogProvider extends BinaryLog {
+  @VisibleForTesting
+  public static final Marshaller<byte[]> BYTEARRAY_MARSHALLER = new ByteArrayMarshaller();
+
+  private final ClientInterceptor binaryLogShim = new BinaryLogShim();
+
+  /**
+   * Wraps a channel to provide binary logging on {@link ClientCall}s as needed.
+   */
+  @Override
+  public final Channel wrapChannel(Channel channel) {
+    return ClientInterceptors.intercept(channel, binaryLogShim);
+  }
+
+  private static MethodDescriptor<byte[], byte[]> toByteBufferMethod(
+      MethodDescriptor<?, ?> method) {
+    return method.toBuilder(BYTEARRAY_MARSHALLER, BYTEARRAY_MARSHALLER).build();
+  }
+
+  /**
+   * Wraps a {@link ServerMethodDefinition} such that it performs binary logging if needed.
+   */
+  @Override
+  public final <ReqT, RespT> ServerMethodDefinition<?, ?> wrapMethodDefinition(
+      ServerMethodDefinition<ReqT, RespT> oMethodDef) {
+    ServerInterceptor binlogInterceptor =
+        getServerInterceptor(oMethodDef.getMethodDescriptor().getFullMethodName());
+    if (binlogInterceptor == null) {
+      return oMethodDef;
+    }
+    MethodDescriptor<byte[], byte[]> binMethod =
+        BinaryLogProvider.toByteBufferMethod(oMethodDef.getMethodDescriptor());
+    ServerMethodDefinition<byte[], byte[]> binDef =
+        InternalServerInterceptors.wrapMethod(oMethodDef, binMethod);
+    ServerCallHandler<byte[], byte[]> binlogHandler =
+        InternalServerInterceptors.interceptCallHandlerCreate(
+            binlogInterceptor, binDef.getServerCallHandler());
+    return ServerMethodDefinition.create(binMethod, binlogHandler);
+  }
+
+  /**
+   * Returns a {@link ServerInterceptor} for binary logging. gRPC is free to cache the interceptor,
+   * so the interceptor must be reusable across calls. At runtime, the request and response
+   * marshallers are always {@code Marshaller<InputStream>}.
+   * Returns {@code null} if this method is not binary logged.
+   */
+  // TODO(zpencer): ensure the interceptor properly handles retries and hedging
+  @Nullable
+  protected abstract ServerInterceptor getServerInterceptor(String fullMethodName);
+
+  /**
+   * Returns a {@link ClientInterceptor} for binary logging. gRPC is free to cache the interceptor,
+   * so the interceptor must be reusable across calls. At runtime, the request and response
+   * marshallers are always {@code Marshaller<InputStream>}.
+   * Returns {@code null} if this method is not binary logged.
+   */
+  // TODO(zpencer): ensure the interceptor properly handles retries and hedging
+  @Nullable
+  protected abstract ClientInterceptor getClientInterceptor(
+      String fullMethodName, CallOptions callOptions);
+
+  @Override
+  public void close() throws IOException {
+    // default impl: noop
+    // TODO(zpencer): make BinaryLogProvider provide a BinaryLog, and this method belongs there
+  }
+
+  // Creating a named class makes debugging easier
+  private static final class ByteArrayMarshaller implements Marshaller<byte[]> {
+    @Override
+    public InputStream stream(byte[] value) {
+      return new ByteArrayInputStream(value);
+    }
+
+    @Override
+    public byte[] parse(InputStream stream) {
+      try {
+        return parseHelper(stream);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    private byte[] parseHelper(InputStream stream) throws IOException {
+      try {
+        return IoUtils.toByteArray(stream);
+      } finally {
+        stream.close();
+      }
+    }
+  }
+
+  /**
+   * The pipeline of interceptors is hard coded when the {@link ManagedChannel} is created.
+   * This shim interceptor should always be installed as a placeholder. When a call starts,
+   * this interceptor checks with the {@link BinaryLogProvider} to see if logging should happen
+   * for this particular {@link ClientCall}'s method.
+   */
+  private final class BinaryLogShim implements ClientInterceptor {
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+        MethodDescriptor<ReqT, RespT> method,
+        CallOptions callOptions,
+        Channel next) {
+      ClientInterceptor binlogInterceptor = getClientInterceptor(
+          method.getFullMethodName(), callOptions);
+      if (binlogInterceptor == null) {
+        return next.newCall(method, callOptions);
+      } else {
+        return InternalClientInterceptors
+            .wrapClientInterceptor(
+                binlogInterceptor,
+                BYTEARRAY_MARSHALLER,
+                BYTEARRAY_MARSHALLER)
+            .interceptCall(method, callOptions, next);
+      }
+    }
+  }
+
+  // Copied from internal
+  private static final class IoUtils {
+    /** maximum buffer to be read is 16 KB. */
+    private static final int MAX_BUFFER_LENGTH = 16384;
+
+    /** Returns the byte array. */
+    public static byte[] toByteArray(InputStream in) throws IOException {
+      ByteArrayOutputStream out = new ByteArrayOutputStream();
+      copy(in, out);
+      return out.toByteArray();
+    }
+
+    /** Copies the data from input stream to output stream. */
+    public static long copy(InputStream from, OutputStream to) throws IOException {
+      // Copied from guava com.google.common.io.ByteStreams because its API is unstable (beta)
+      Preconditions.checkNotNull(from);
+      Preconditions.checkNotNull(to);
+      byte[] buf = new byte[MAX_BUFFER_LENGTH];
+      long total = 0;
+      while (true) {
+        int r = from.read(buf);
+        if (r == -1) {
+          break;
+        }
+        to.write(buf, 0, r);
+        total += r;
+      }
+      return total;
+    }
+  }
+}
diff --git a/services/src/main/java/io/grpc/services/BinaryLogProviderImpl.java b/services/src/main/java/io/grpc/services/BinaryLogProviderImpl.java
new file mode 100644
index 0000000..4202813
--- /dev/null
+++ b/services/src/main/java/io/grpc/services/BinaryLogProviderImpl.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import com.google.common.base.Preconditions;
+import io.grpc.CallOptions;
+import io.grpc.ClientInterceptor;
+import io.grpc.ServerInterceptor;
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.annotation.Nullable;
+
+/**
+ * The default implementation of a {@link BinaryLogProvider}.
+ */
+class BinaryLogProviderImpl extends BinaryLogProvider {
+  private final BinlogHelper.Factory factory;
+  private final BinaryLogSink sink;
+  private final AtomicLong counter = new AtomicLong();
+
+  public BinaryLogProviderImpl() throws IOException {
+    this(new TempFileSink(), System.getenv("GRPC_BINARY_LOG_CONFIG"));
+  }
+
+  public BinaryLogProviderImpl(BinaryLogSink sink) throws IOException {
+    this(sink, System.getenv("GRPC_BINARY_LOG_CONFIG"));
+  }
+
+  /**
+   * Creates an instance.
+   * @param sink ownership is transferred to this class.
+   * @param configStr config string to parse to determine logged methods and msg size limits.
+   * @throws IOException if initialization failed.
+   */
+  BinaryLogProviderImpl(BinaryLogSink sink, String configStr) throws IOException {
+    this.sink = Preconditions.checkNotNull(sink);
+    try {
+      factory = new BinlogHelper.FactoryImpl(sink, configStr);
+    } catch (RuntimeException e) {
+      sink.close();
+      // parsing the conf string may throw if it is blank or contains errors
+      throw new IOException(
+          "Can not initialize. The env variable GRPC_BINARY_LOG_CONFIG must be valid.", e);
+    }
+  }
+
+  @Nullable
+  @Override
+  public ServerInterceptor getServerInterceptor(String fullMethodName) {
+    BinlogHelper helperForMethod = factory.getLog(fullMethodName);
+    if (helperForMethod == null) {
+      return null;
+    }
+    return helperForMethod.getServerInterceptor(getServerCallId());
+  }
+
+  @Nullable
+  @Override
+  public ClientInterceptor getClientInterceptor(
+      String fullMethodName, CallOptions callOptions) {
+    BinlogHelper helperForMethod = factory.getLog(fullMethodName);
+    if (helperForMethod == null) {
+      return null;
+    }
+    return helperForMethod.getClientInterceptor(getClientCallId(callOptions));
+  }
+
+  @Override
+  public void close() throws IOException {
+    sink.close();
+  }
+
+  protected CallId getServerCallId() {
+    return new CallId(0, counter.getAndIncrement());
+  }
+
+  protected CallId getClientCallId(CallOptions options) {
+    return new CallId(0, counter.getAndIncrement());
+  }
+}
diff --git a/services/src/main/java/io/grpc/services/BinaryLogSink.java b/services/src/main/java/io/grpc/services/BinaryLogSink.java
new file mode 100644
index 0000000..1c1c193
--- /dev/null
+++ b/services/src/main/java/io/grpc/services/BinaryLogSink.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import com.google.protobuf.MessageLite;
+import io.grpc.ExperimentalApi;
+import java.io.Closeable;
+
+/**
+ * A class that accepts binary log messages.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4017")
+public interface BinaryLogSink extends Closeable {
+  /**
+   * Writes the {@code message} to the destination.
+   */
+  void write(MessageLite message);
+}
diff --git a/services/src/main/java/io/grpc/services/BinaryLogs.java b/services/src/main/java/io/grpc/services/BinaryLogs.java
new file mode 100644
index 0000000..de7f791
--- /dev/null
+++ b/services/src/main/java/io/grpc/services/BinaryLogs.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018, gRPC Authors All rights reserved.
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import io.grpc.BinaryLog;
+import io.grpc.ExperimentalApi;
+import java.io.IOException;
+
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4017")
+public final class BinaryLogs {
+  /**
+   * Creates a binary log that writes to a temp file. <b>Warning:</b> this implementation is
+   * not performance optimized, and RPCs will experience back pressure if disk IO does not keep
+   * up.
+   */
+  public static BinaryLog createBinaryLog() throws IOException {
+    return new BinaryLogProviderImpl();
+  }
+
+  /**
+   * Creates a binary log with a custom {@link BinaryLogSink} for receiving the logged data.
+   */
+  public static BinaryLog createBinaryLog(BinaryLogSink sink) throws IOException {
+    return new BinaryLogProviderImpl(sink);
+  }
+
+  private BinaryLogs() {}
+}
diff --git a/services/src/main/java/io/grpc/services/BinlogHelper.java b/services/src/main/java/io/grpc/services/BinlogHelper.java
new file mode 100644
index 0000000..2600167
--- /dev/null
+++ b/services/src/main/java/io/grpc/services/BinlogHelper.java
@@ -0,0 +1,737 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static io.grpc.services.BinaryLogProvider.BYTEARRAY_MARSHALLER;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.Duration;
+import com.google.protobuf.util.Durations;
+import com.google.re2j.Matcher;
+import com.google.re2j.Pattern;
+import io.grpc.Attributes;
+import io.grpc.BinaryLog.CallId;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.Context;
+import io.grpc.Deadline;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
+import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
+import io.grpc.ForwardingServerCallListener.SimpleForwardingServerCallListener;
+import io.grpc.Grpc;
+import io.grpc.InternalMetadata;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.ServerCall;
+import io.grpc.ServerCall.Listener;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.Status;
+import io.grpc.binarylog.v1alpha.GrpcLogEntry;
+import io.grpc.binarylog.v1alpha.GrpcLogEntry.Type;
+import io.grpc.binarylog.v1alpha.Message;
+import io.grpc.binarylog.v1alpha.Metadata.Builder;
+import io.grpc.binarylog.v1alpha.Peer;
+import io.grpc.binarylog.v1alpha.Peer.PeerType;
+import io.grpc.binarylog.v1alpha.Uint128;
+import io.grpc.internal.GrpcUtil;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * A binary log class that is configured for a specific {@link MethodDescriptor}.
+ */
+@ThreadSafe
+final class BinlogHelper {
+  private static final Logger logger = Logger.getLogger(BinlogHelper.class.getName());
+  private static final boolean SERVER = true;
+  private static final boolean CLIENT = false;
+  // Normally 'grpc-' metadata keys are set from within gRPC, and applications are not allowed
+  // to set them. This key is a special well known key that set from the application layer, but
+  // represents a com.google.rpc.Status and is given special first class treatment.
+  // See StatusProto.java
+  static final Metadata.Key<byte[]> STATUS_DETAILS_KEY =
+      Metadata.Key.of(
+          "grpc-status-details-bin",
+          Metadata.BINARY_BYTE_MARSHALLER);
+
+  @VisibleForTesting
+  static final SocketAddress DUMMY_SOCKET = new DummySocketAddress();
+  @VisibleForTesting
+  static final boolean DUMMY_IS_COMPRESSED = false;
+
+  @VisibleForTesting
+  final SinkWriter writer;
+
+  @VisibleForTesting
+  BinlogHelper(SinkWriter writer) {
+    this.writer = writer;
+  }
+
+  // TODO(zpencer): move proto related static helpers into this class
+  static final class SinkWriterImpl extends SinkWriter {
+    private final BinaryLogSink sink;
+    private final int maxHeaderBytes;
+    private final int maxMessageBytes;
+
+    SinkWriterImpl(BinaryLogSink sink, int maxHeaderBytes, int maxMessageBytes) {
+      this.sink = sink;
+      this.maxHeaderBytes = maxHeaderBytes;
+      this.maxMessageBytes = maxMessageBytes;
+    }
+
+    @Override
+    void logSendInitialMetadata(
+        int seq,
+        @Nullable String methodName, // null on server
+        @Nullable Duration timeout, // null on server
+        Metadata metadata,
+        boolean isServer,
+        CallId callId) {
+      Preconditions.checkArgument(methodName == null || !isServer);
+      Preconditions.checkArgument(timeout == null || !isServer);
+      // Java does not include the leading '/'. To be consistent with the rest of gRPC we must
+      // include the '/' in the fully qualified name for binlogs.
+      Preconditions.checkArgument(methodName == null || !methodName.startsWith("/"));
+      GrpcLogEntry.Builder entryBuilder = GrpcLogEntry.newBuilder()
+          .setSequenceIdWithinCall(seq)
+          .setType(Type.SEND_INITIAL_METADATA)
+          .setLogger(isServer ? GrpcLogEntry.Logger.SERVER : GrpcLogEntry.Logger.CLIENT)
+          .setCallId(callIdToProto(callId));
+      addMetadataToProto(entryBuilder, metadata, maxHeaderBytes);
+      if (methodName != null) {
+        entryBuilder.setMethodName("/" + methodName);
+      }
+      if (timeout != null) {
+        entryBuilder.setTimeout(timeout);
+      }
+      sink.write(entryBuilder.build());
+    }
+
+    @Override
+    void logRecvInitialMetadata(
+        int seq,
+        @Nullable String methodName, // null on client
+        @Nullable Duration timeout,  // null on client
+        Metadata metadata,
+        boolean isServer,
+        CallId callId,
+        SocketAddress peerSocket) {
+      Preconditions.checkArgument(methodName == null || isServer);
+      Preconditions.checkArgument(timeout == null || isServer);
+      // Java does not include the leading '/'. To be consistent with the rest of gRPC we must
+      // include the '/' in the fully qualified name for binlogs.
+      Preconditions.checkArgument(methodName == null || !methodName.startsWith("/"));
+      GrpcLogEntry.Builder entryBuilder = GrpcLogEntry.newBuilder()
+          .setSequenceIdWithinCall(seq)
+          .setType(Type.RECV_INITIAL_METADATA)
+          .setLogger(isServer ? GrpcLogEntry.Logger.SERVER : GrpcLogEntry.Logger.CLIENT)
+          .setCallId(callIdToProto(callId))
+          .setPeer(socketToProto(peerSocket));
+      addMetadataToProto(entryBuilder, metadata, maxHeaderBytes);
+      if (methodName != null) {
+        entryBuilder.setMethodName("/" + methodName);
+      }
+      if (timeout != null) {
+        entryBuilder.setTimeout(timeout);
+      }
+      sink.write(entryBuilder.build());
+    }
+
+    @Override
+    void logTrailingMetadata(
+        int seq, Status status, Metadata metadata, boolean isServer, CallId callId) {
+      GrpcLogEntry.Builder entryBuilder = GrpcLogEntry.newBuilder()
+          .setSequenceIdWithinCall(seq)
+          .setType(isServer ? Type.SEND_TRAILING_METADATA : Type.RECV_TRAILING_METADATA)
+          .setLogger(isServer ? GrpcLogEntry.Logger.SERVER : GrpcLogEntry.Logger.CLIENT)
+          .setCallId(callIdToProto(callId))
+          .setStatusCode(status.getCode().value());
+      String statusDescription = status.getDescription();
+      if (statusDescription != null) {
+        entryBuilder.setStatusMessage(statusDescription);
+      }
+      byte[] statusDetailBytes = metadata.get(STATUS_DETAILS_KEY);
+      if (statusDetailBytes != null) {
+        entryBuilder.setStatusDetails(ByteString.copyFrom(statusDetailBytes));
+      }
+
+      addMetadataToProto(entryBuilder, metadata, maxHeaderBytes);
+      sink.write(entryBuilder.build());
+    }
+
+    @Override
+    <T> void logOutboundMessage(
+        int seq,
+        Marshaller<T> marshaller,
+        T message,
+        boolean compressed,
+        boolean isServer,
+        CallId callId) {
+      if (marshaller != BYTEARRAY_MARSHALLER) {
+        throw new IllegalStateException("Expected the BinaryLog's ByteArrayMarshaller");
+      }
+      GrpcLogEntry.Builder entryBuilder = GrpcLogEntry.newBuilder()
+          .setSequenceIdWithinCall(seq)
+          .setType(Type.SEND_MESSAGE)
+          .setLogger(isServer ? GrpcLogEntry.Logger.SERVER : GrpcLogEntry.Logger.CLIENT)
+          .setCallId(callIdToProto(callId));
+      messageToProto(entryBuilder, (byte[]) message, compressed, maxMessageBytes);
+      sink.write(entryBuilder.build());
+    }
+
+    @Override
+    <T> void logInboundMessage(
+        int seq,
+        Marshaller<T> marshaller,
+        T message,
+        boolean compressed,
+        boolean isServer,
+        CallId callId) {
+      if (marshaller != BYTEARRAY_MARSHALLER) {
+        throw new IllegalStateException("Expected the BinaryLog's ByteArrayMarshaller");
+      }
+      GrpcLogEntry.Builder entryBuilder = GrpcLogEntry.newBuilder()
+          .setSequenceIdWithinCall(seq)
+          .setType(Type.RECV_MESSAGE)
+          .setLogger(isServer ? GrpcLogEntry.Logger.SERVER : GrpcLogEntry.Logger.CLIENT)
+          .setCallId(callIdToProto(callId));
+
+      messageToProto(entryBuilder, (byte[]) message, compressed, maxMessageBytes);
+      sink.write(entryBuilder.build());
+    }
+
+    @Override
+    int getMaxHeaderBytes() {
+      return maxHeaderBytes;
+    }
+
+    @Override
+    int getMaxMessageBytes() {
+      return maxMessageBytes;
+    }
+  }
+
+  abstract static class SinkWriter {
+    /**
+     * Logs the sending of initial metadata. This method logs the appropriate number of bytes
+     * as determined by the binary logging configuration.
+     */
+    abstract void logSendInitialMetadata(
+        int seq,
+        String methodName,
+        Duration timeout,
+        Metadata metadata,
+        boolean isServer,
+        CallId callId);
+
+    /**
+     * Logs the receiving of initial metadata. This method logs the appropriate number of bytes
+     * as determined by the binary logging configuration.
+     */
+    abstract void logRecvInitialMetadata(
+        int seq,
+        String methodName,
+        Duration timeout,
+        Metadata metadata,
+        boolean isServer,
+        CallId callId,
+        SocketAddress peerSocket);
+
+    /**
+     * Logs the trailing metadata. This method logs the appropriate number of bytes
+     * as determined by the binary logging configuration.
+     */
+    abstract void logTrailingMetadata(
+        int seq, Status status, Metadata metadata, boolean isServer, CallId callId);
+
+    /**
+     * Logs the outbound message. This method logs the appropriate number of bytes from
+     * {@code message}, and returns a duplicate of the message.
+     * The number of bytes logged is determined by the binary logging configuration.
+     * This method takes ownership of {@code message}.
+     */
+    abstract <T> void logOutboundMessage(
+        int seq, Marshaller<T> marshaller, T message, boolean compressed, boolean isServer,
+        CallId callId);
+
+    /**
+     * Logs the inbound message. This method logs the appropriate number of bytes from
+     * {@code message}, and returns a duplicate of the message.
+     * The number of bytes logged is determined by the binary logging configuration.
+     * This method takes ownership of {@code message}.
+     */
+    abstract <T> void logInboundMessage(
+        int seq, Marshaller<T> marshaller, T message, boolean compressed, boolean isServer,
+        CallId callId);
+
+    /**
+     * Returns the number bytes of the header this writer will log, according to configuration.
+     */
+    abstract int getMaxHeaderBytes();
+
+    /**
+     * Returns the number bytes of the message this writer will log, according to configuration.
+     */
+    abstract int getMaxMessageBytes();
+  }
+
+  static SocketAddress getPeerSocket(Attributes streamAttributes) {
+    SocketAddress peer = streamAttributes.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
+    if (peer == null) {
+      return DUMMY_SOCKET;
+    }
+    return peer;
+  }
+
+  private static Deadline min(@Nullable Deadline deadline0, @Nullable Deadline deadline1) {
+    if (deadline0 == null) {
+      return deadline1;
+    }
+    if (deadline1 == null) {
+      return deadline0;
+    }
+    return deadline0.minimum(deadline1);
+  }
+
+  public ClientInterceptor getClientInterceptor(final CallId callId) {
+    return new ClientInterceptor() {
+      @Override
+      public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+          final MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+        final AtomicInteger seq = new AtomicInteger(1);
+        final String methodName = method.getFullMethodName();
+        // The timeout should reflect the time remaining when the call is started, so do not
+        // compute remaining time here.
+        final Deadline deadline = min(callOptions.getDeadline(), Context.current().getDeadline());
+
+        return new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
+          @Override
+          public void start(Listener<RespT> responseListener, Metadata headers) {
+            final Duration timeout = deadline == null ? null
+                : Durations.fromNanos(deadline.timeRemaining(TimeUnit.NANOSECONDS));
+            writer.logSendInitialMetadata(
+                seq.getAndIncrement(), methodName, timeout, headers, CLIENT, callId);
+            ClientCall.Listener<RespT> wListener =
+                new SimpleForwardingClientCallListener<RespT>(responseListener) {
+                  @Override
+                  public void onMessage(RespT message) {
+                    writer.logInboundMessage(
+                        seq.getAndIncrement(),
+                        method.getResponseMarshaller(),
+                        message,
+                        DUMMY_IS_COMPRESSED,
+                        CLIENT,
+                        callId);
+                    super.onMessage(message);
+                  }
+
+                  @Override
+                  public void onHeaders(Metadata headers) {
+                    SocketAddress peer = getPeerSocket(getAttributes());
+                    writer.logRecvInitialMetadata(
+                        seq.getAndIncrement(),
+                        /*methodName=*/ null,
+                        /*timeout=*/ null,
+                        headers,
+                        CLIENT,
+                        callId,
+                        peer);
+                    super.onHeaders(headers);
+                  }
+
+                  @Override
+                  public void onClose(Status status, Metadata trailers) {
+                    writer.logTrailingMetadata(
+                        seq.getAndIncrement(), status, trailers, CLIENT, callId);
+                    super.onClose(status, trailers);
+                  }
+                };
+            super.start(wListener, headers);
+          }
+
+          @Override
+          public void sendMessage(ReqT message) {
+            writer.logOutboundMessage(
+                seq.getAndIncrement(),
+                method.getRequestMarshaller(),
+                message,
+                DUMMY_IS_COMPRESSED,
+                CLIENT,
+                callId);
+            super.sendMessage(message);
+          }
+        };
+      }
+    };
+  }
+
+  public ServerInterceptor getServerInterceptor(final CallId callId) {
+    return new ServerInterceptor() {
+      @Override
+      public <ReqT, RespT> Listener<ReqT> interceptCall(
+          final ServerCall<ReqT, RespT> call,
+          Metadata headers,
+          ServerCallHandler<ReqT, RespT> next) {
+        final AtomicInteger seq = new AtomicInteger(1);
+        SocketAddress peer = getPeerSocket(call.getAttributes());
+        String methodName = call.getMethodDescriptor().getFullMethodName();
+        Long timeoutNanos = headers.get(GrpcUtil.TIMEOUT_KEY);
+        final Duration timeout =
+            timeoutNanos == null ? null : Durations.fromNanos(timeoutNanos);
+
+        writer.logRecvInitialMetadata(
+            seq.getAndIncrement(), methodName, timeout, headers, SERVER, callId, peer);
+        ServerCall<ReqT, RespT> wCall = new SimpleForwardingServerCall<ReqT, RespT>(call) {
+          @Override
+          public void sendMessage(RespT message) {
+            writer.logOutboundMessage(
+                seq.getAndIncrement(),
+                call.getMethodDescriptor().getResponseMarshaller(),
+                message,
+                DUMMY_IS_COMPRESSED,
+                SERVER,
+                callId);
+            super.sendMessage(message);
+          }
+
+          @Override
+          public void sendHeaders(Metadata headers) {
+            writer.logSendInitialMetadata(
+                seq.getAndIncrement(),
+                /*methodName=*/ null,
+                /*timeout=*/ null,
+                headers,
+                SERVER,
+                callId);
+            super.sendHeaders(headers);
+          }
+
+          @Override
+          public void close(Status status, Metadata trailers) {
+            writer.logTrailingMetadata(seq.getAndIncrement(), status, trailers, SERVER, callId);
+            super.close(status, trailers);
+          }
+        };
+
+        return new SimpleForwardingServerCallListener<ReqT>(next.startCall(wCall, headers)) {
+          @Override
+          public void onMessage(ReqT message) {
+            writer.logInboundMessage(
+                seq.getAndIncrement(),
+                call.getMethodDescriptor().getRequestMarshaller(),
+                message,
+                DUMMY_IS_COMPRESSED,
+                SERVER,
+                callId);
+            super.onMessage(message);
+          }
+        };
+      }
+    };
+  }
+
+  interface Factory {
+    @Nullable
+    BinlogHelper getLog(String fullMethodName);
+  }
+
+  static final class FactoryImpl implements Factory {
+    // '*' for global, 'service/*' for service glob, or 'service/method' for fully qualified.
+    private static final Pattern logPatternRe = Pattern.compile("[^{]+");
+    // A curly brace wrapped expression. Will be further matched with the more specified REs below.
+    private static final Pattern logOptionsRe = Pattern.compile("\\{[^}]+}");
+    private static final Pattern configRe = Pattern.compile(
+        String.format("^(%s)(%s)?$", logPatternRe.pattern(), logOptionsRe.pattern()));
+    // Regexes to extract per-binlog options
+    // The form: {m:256}
+    private static final Pattern msgRe = Pattern.compile("\\{m(?::(\\d+))?}");
+    // The form: {h:256}
+    private static final Pattern headerRe = Pattern.compile("\\{h(?::(\\d+))?}");
+    // The form: {h:256,m:256}
+    private static final Pattern bothRe = Pattern.compile("\\{h(?::(\\d+))?;m(?::(\\d+))?}");
+
+    private final BinlogHelper globalLog;
+    private final Map<String, BinlogHelper> perServiceLogs;
+    private final Map<String, BinlogHelper> perMethodLogs;
+    private final Set<String> blacklistedMethods;
+
+    /**
+     * Accepts a string in the format specified by the binary log spec.
+     */
+    @VisibleForTesting
+    FactoryImpl(BinaryLogSink sink, String configurationString) {
+      checkNotNull(sink, "sink");
+      BinlogHelper globalLog = null;
+      Map<String, BinlogHelper> perServiceLogs = new HashMap<String, BinlogHelper>();
+      Map<String, BinlogHelper> perMethodLogs = new HashMap<String, BinlogHelper>();
+      Set<String> blacklistedMethods = new HashSet<String>();
+      if (configurationString != null && configurationString.length() > 0) {
+        for (String configuration : Splitter.on(',').split(configurationString)) {
+          Matcher configMatcher = configRe.matcher(configuration);
+          if (!configMatcher.matches()) {
+            throw new IllegalArgumentException("Bad input: " + configuration);
+          }
+          String methodOrSvc = configMatcher.group(1);
+          String binlogOptionStr = configMatcher.group(2);
+          BinlogHelper binLog = createBinaryLog(sink, binlogOptionStr);
+          if (binLog == null) {
+            continue;
+          }
+          if (methodOrSvc.equals("*")) {
+            if (globalLog != null) {
+              logger.log(Level.SEVERE, "Ignoring duplicate entry: {0}", configuration);
+              continue;
+            }
+            globalLog = binLog;
+            logger.log(Level.INFO, "Global binlog: {0}", binlogOptionStr);
+          } else if (isServiceGlob(methodOrSvc)) {
+            String service = MethodDescriptor.extractFullServiceName(methodOrSvc);
+            if (perServiceLogs.containsKey(service)) {
+              logger.log(Level.SEVERE, "Ignoring duplicate entry: {0}", configuration);
+              continue;
+            }
+            perServiceLogs.put(service, binLog);
+            logger.log(
+                Level.INFO,
+                "Service binlog: service={0} config={1}",
+                new Object[] {service, binlogOptionStr});
+          } else if (methodOrSvc.startsWith("-")) {
+            String blacklistedMethod = methodOrSvc.substring(1);
+            if (blacklistedMethod.length() == 0) {
+              continue;
+            }
+            if (!blacklistedMethods.add(blacklistedMethod)) {
+              logger.log(Level.SEVERE, "Ignoring duplicate entry: {0}", configuration);
+            }
+          } else {
+            // assume fully qualified method name
+            if (perMethodLogs.containsKey(methodOrSvc)) {
+              logger.log(Level.SEVERE, "Ignoring duplicate entry: {0}", configuration);
+              continue;
+            }
+            perMethodLogs.put(methodOrSvc, binLog);
+            logger.log(
+                Level.INFO,
+                "Method binlog: method={0} config={1}",
+                new Object[] {methodOrSvc, binlogOptionStr});
+          }
+        }
+      }
+      this.globalLog = globalLog;
+      this.perServiceLogs = Collections.unmodifiableMap(perServiceLogs);
+      this.perMethodLogs = Collections.unmodifiableMap(perMethodLogs);
+      this.blacklistedMethods = Collections.unmodifiableSet(blacklistedMethods);
+    }
+
+    /**
+     * Accepts a full method name and returns the log that should be used.
+     */
+    @Override
+    public BinlogHelper getLog(String fullMethodName) {
+      if (blacklistedMethods.contains(fullMethodName)) {
+        return null;
+      }
+      BinlogHelper methodLog = perMethodLogs.get(fullMethodName);
+      if (methodLog != null) {
+        return methodLog;
+      }
+      BinlogHelper serviceLog = perServiceLogs.get(
+          MethodDescriptor.extractFullServiceName(fullMethodName));
+      if (serviceLog != null) {
+        return serviceLog;
+      }
+      return globalLog;
+    }
+
+    /**
+     * Returns a binlog with the correct header and message limits or {@code null} if the input
+     * is malformed. The input should be a string that is in one of these forms:
+     *
+     * <p>{@code {h(:\d+)?}, {m(:\d+)?}, {h(:\d+)?,m(:\d+)?}}
+     *
+     * <p>If the {@code logConfig} is null, the returned binlog will have a limit of
+     * Integer.MAX_VALUE.
+     */
+    @VisibleForTesting
+    @Nullable
+    static BinlogHelper createBinaryLog(BinaryLogSink sink, @Nullable String logConfig) {
+      if (logConfig == null) {
+        return new BinlogHelper(
+            new SinkWriterImpl(sink, Integer.MAX_VALUE, Integer.MAX_VALUE));
+      }
+      try {
+        Matcher headerMatcher;
+        Matcher msgMatcher;
+        Matcher bothMatcher;
+        final int maxHeaderBytes;
+        final int maxMsgBytes;
+        if ((headerMatcher = headerRe.matcher(logConfig)).matches()) {
+          String maxHeaderStr = headerMatcher.group(1);
+          maxHeaderBytes =
+              maxHeaderStr != null ? Integer.parseInt(maxHeaderStr) : Integer.MAX_VALUE;
+          maxMsgBytes = 0;
+        } else if ((msgMatcher = msgRe.matcher(logConfig)).matches()) {
+          maxHeaderBytes = 0;
+          String maxMsgStr = msgMatcher.group(1);
+          maxMsgBytes = maxMsgStr != null ? Integer.parseInt(maxMsgStr) : Integer.MAX_VALUE;
+        } else if ((bothMatcher = bothRe.matcher(logConfig)).matches()) {
+          String maxHeaderStr = bothMatcher.group(1);
+          String maxMsgStr = bothMatcher.group(2);
+          maxHeaderBytes =
+              maxHeaderStr != null ? Integer.parseInt(maxHeaderStr) : Integer.MAX_VALUE;
+          maxMsgBytes = maxMsgStr != null ? Integer.parseInt(maxMsgStr) : Integer.MAX_VALUE;
+        } else {
+          logger.log(Level.SEVERE, "Illegal log config pattern: " + logConfig);
+          return null;
+        }
+        return new BinlogHelper(new SinkWriterImpl(sink, maxHeaderBytes, maxMsgBytes));
+      } catch (NumberFormatException e) {
+        logger.log(Level.SEVERE, "Illegal log config pattern: " + logConfig);
+        return null;
+      }
+    }
+
+    /**
+     * Returns true if the input string is a glob of the form: {@code <package-service>/*}.
+     */
+    static boolean isServiceGlob(String input) {
+      return input.endsWith("/*");
+    }
+  }
+
+  /**
+   * Returns a {@link Uint128} from a CallId.
+   */
+  static Uint128 callIdToProto(CallId callId) {
+    checkNotNull(callId, "callId");
+    return Uint128
+        .newBuilder()
+        .setHigh(callId.hi)
+        .setLow(callId.lo)
+        .build();
+  }
+
+  @VisibleForTesting
+  static Peer socketToProto(SocketAddress address) {
+    checkNotNull(address, "address");
+
+    Peer.Builder builder = Peer.newBuilder();
+    if (address instanceof InetSocketAddress) {
+      InetAddress inetAddress = ((InetSocketAddress) address).getAddress();
+      if (inetAddress instanceof Inet4Address) {
+        builder.setPeerType(PeerType.PEER_IPV4)
+            .setAddress(InetAddressUtil.toAddrString(inetAddress));
+      } else if (inetAddress instanceof Inet6Address) {
+        builder.setPeerType(PeerType.PEER_IPV6)
+            .setAddress(InetAddressUtil.toAddrString(inetAddress));
+      } else {
+        logger.log(Level.SEVERE, "unknown type of InetSocketAddress: {}", address);
+        builder.setAddress(address.toString());
+      }
+      builder.setIpPort(((InetSocketAddress) address).getPort());
+    } else if (address.getClass().getName().equals("io.netty.channel.unix.DomainSocketAddress")) {
+      // To avoid a compile time dependency on grpc-netty, we check against the runtime class name.
+      builder.setPeerType(PeerType.PEER_UNIX)
+          .setAddress(address.toString());
+    } else {
+      builder.setPeerType(PeerType.UNKNOWN_PEERTYPE).setAddress(address.toString());
+    }
+    return builder.build();
+  }
+
+  @VisibleForTesting
+  static void addMetadataToProto(
+      GrpcLogEntry.Builder entryBuilder, Metadata metadata, int maxHeaderBytes) {
+    checkNotNull(entryBuilder, "entryBuilder");
+    checkNotNull(metadata, "metadata");
+    checkArgument(maxHeaderBytes >= 0, "maxHeaderBytes must be non negative");
+    Builder metaBuilder = io.grpc.binarylog.v1alpha.Metadata.newBuilder();
+    // This code is tightly coupled with Metadata's implementation
+    byte[][] serialized = null;
+    if (maxHeaderBytes > 0 && (serialized = InternalMetadata.serialize(metadata)) != null) {
+      int written = 0;
+      for (int i = 0; i < serialized.length && written < maxHeaderBytes; i += 2) {
+        byte[] key = serialized[i];
+        byte[] value = serialized[i + 1];
+        if (written + key.length + value.length <= maxHeaderBytes) {
+          metaBuilder.addEntryBuilder()
+                  .setKey(ByteString.copyFrom(key))
+                  .setValue(ByteString.copyFrom(value));
+          written += key.length;
+          written += value.length;
+        }
+      }
+    }
+    // This check must be updated when we add filtering
+    entryBuilder.setTruncated(maxHeaderBytes == 0
+        || (serialized != null && metaBuilder.getEntryCount() < (serialized.length / 2)));
+    entryBuilder.setMetadata(metaBuilder);
+  }
+
+  @VisibleForTesting
+  static void messageToProto(
+      GrpcLogEntry.Builder entryBuilder, byte[] message, boolean compressed, int maxMessageBytes) {
+    checkNotNull(message, "message");
+    checkArgument(maxMessageBytes >= 0, "maxMessageBytes must be non negative");
+    Message.Builder msgBuilder = Message
+        .newBuilder()
+        .setFlags(flagsForMessage(compressed))
+        .setLength(message.length);
+    if (maxMessageBytes > 0) {
+      int desiredBytes = Math.min(maxMessageBytes, message.length);
+      msgBuilder.setData(ByteString.copyFrom(message, 0, desiredBytes));
+    }
+    entryBuilder.setMessage(msgBuilder);
+    entryBuilder.setTruncated(maxMessageBytes < message.length);
+  }
+
+  /**
+   * Returns a flag based on the arguments.
+   */
+  @VisibleForTesting
+  static int flagsForMessage(boolean compressed) {
+    return compressed ? 1 : 0;
+  }
+
+  private static class DummySocketAddress extends SocketAddress {
+    private static final long serialVersionUID = 0;
+  }
+}
diff --git a/services/src/main/java/io/grpc/services/ChannelzProtoUtil.java b/services/src/main/java/io/grpc/services/ChannelzProtoUtil.java
new file mode 100644
index 0000000..a1ada8f
--- /dev/null
+++ b/services/src/main/java/io/grpc/services/ChannelzProtoUtil.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.protobuf.Any;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.Int64Value;
+import com.google.protobuf.util.Durations;
+import com.google.protobuf.util.Timestamps;
+import io.grpc.ConnectivityState;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.ChannelStats;
+import io.grpc.InternalChannelz.ChannelTrace.Event;
+import io.grpc.InternalChannelz.RootChannelList;
+import io.grpc.InternalChannelz.ServerList;
+import io.grpc.InternalChannelz.ServerSocketsList;
+import io.grpc.InternalChannelz.ServerStats;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalChannelz.TransportStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.InternalWithLogId;
+import io.grpc.Status;
+import io.grpc.channelz.v1.Address;
+import io.grpc.channelz.v1.Address.OtherAddress;
+import io.grpc.channelz.v1.Address.TcpIpAddress;
+import io.grpc.channelz.v1.Address.UdsAddress;
+import io.grpc.channelz.v1.Channel;
+import io.grpc.channelz.v1.ChannelConnectivityState;
+import io.grpc.channelz.v1.ChannelConnectivityState.State;
+import io.grpc.channelz.v1.ChannelData;
+import io.grpc.channelz.v1.ChannelRef;
+import io.grpc.channelz.v1.ChannelTrace;
+import io.grpc.channelz.v1.ChannelTraceEvent;
+import io.grpc.channelz.v1.ChannelTraceEvent.Severity;
+import io.grpc.channelz.v1.GetServerSocketsResponse;
+import io.grpc.channelz.v1.GetServersResponse;
+import io.grpc.channelz.v1.GetTopChannelsResponse;
+import io.grpc.channelz.v1.Security;
+import io.grpc.channelz.v1.Security.OtherSecurity;
+import io.grpc.channelz.v1.Security.Tls;
+import io.grpc.channelz.v1.Server;
+import io.grpc.channelz.v1.ServerData;
+import io.grpc.channelz.v1.ServerRef;
+import io.grpc.channelz.v1.Socket;
+import io.grpc.channelz.v1.Socket.Builder;
+import io.grpc.channelz.v1.SocketData;
+import io.grpc.channelz.v1.SocketOption;
+import io.grpc.channelz.v1.SocketOptionLinger;
+import io.grpc.channelz.v1.SocketOptionTcpInfo;
+import io.grpc.channelz.v1.SocketOptionTimeout;
+import io.grpc.channelz.v1.SocketRef;
+import io.grpc.channelz.v1.Subchannel;
+import io.grpc.channelz.v1.SubchannelRef;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.security.cert.CertificateEncodingException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A static utility class for turning internal data structures into protos.
+ */
+final class ChannelzProtoUtil {
+  private static final Logger logger = Logger.getLogger(ChannelzProtoUtil.class.getName());
+
+  private ChannelzProtoUtil() {
+    // do not instantiate.
+  }
+
+  static ChannelRef toChannelRef(InternalWithLogId obj) {
+    return ChannelRef
+        .newBuilder()
+        .setChannelId(obj.getLogId().getId())
+        .setName(obj.toString())
+        .build();
+  }
+
+  static SubchannelRef toSubchannelRef(InternalWithLogId obj) {
+    return SubchannelRef
+        .newBuilder()
+        .setSubchannelId(obj.getLogId().getId())
+        .setName(obj.toString())
+        .build();
+  }
+
+  static ServerRef toServerRef(InternalWithLogId obj) {
+    return ServerRef
+        .newBuilder()
+        .setServerId(obj.getLogId().getId())
+        .setName(obj.toString())
+        .build();
+  }
+
+  static SocketRef toSocketRef(InternalWithLogId obj) {
+    return SocketRef
+        .newBuilder()
+        .setSocketId(obj.getLogId().getId())
+        .setName(obj.toString())
+        .build();
+  }
+
+  static Server toServer(InternalInstrumented<ServerStats> obj) {
+    ServerStats stats = getFuture(obj.getStats());
+    Server.Builder builder = Server
+        .newBuilder()
+        .setRef(toServerRef(obj))
+        .setData(toServerData(stats));
+    for (InternalInstrumented<SocketStats> listenSocket : stats.listenSockets) {
+      builder.addListenSocket(toSocketRef(listenSocket));
+    }
+    return builder.build();
+  }
+
+  static ServerData toServerData(ServerStats stats) {
+    return ServerData
+        .newBuilder()
+        .setCallsStarted(stats.callsStarted)
+        .setCallsSucceeded(stats.callsSucceeded)
+        .setCallsFailed(stats.callsFailed)
+        .setLastCallStartedTimestamp(Timestamps.fromNanos(stats.lastCallStartedNanos))
+        .build();
+  }
+
+  static Security toSecurity(InternalChannelz.Security security) {
+    Preconditions.checkNotNull(security);
+    Preconditions.checkState(
+        security.tls != null ^ security.other != null,
+        "one of tls or othersecurity must be non null");
+    if (security.tls != null) {
+      Tls.Builder tlsBuilder
+          = Tls.newBuilder().setStandardName(security.tls.cipherSuiteStandardName);
+      try {
+        if (security.tls.localCert != null) {
+          tlsBuilder.setLocalCertificate(ByteString.copyFrom(
+              security.tls.localCert.getEncoded()));
+        }
+        if (security.tls.remoteCert != null) {
+          tlsBuilder.setRemoteCertificate(ByteString.copyFrom(
+              security.tls.remoteCert.getEncoded()));
+        }
+      } catch (CertificateEncodingException e) {
+        logger.log(Level.FINE, "Caught exception", e);
+      }
+      return Security.newBuilder().setTls(tlsBuilder).build();
+    } else {
+      OtherSecurity.Builder builder = OtherSecurity.newBuilder().setName(security.other.name);
+      if (security.other.any != null) {
+        builder.setValue((Any) security.other.any);
+      }
+      return Security.newBuilder().setOther(builder).build();
+    }
+  }
+
+  static Socket toSocket(InternalInstrumented<SocketStats> obj) {
+    SocketStats socketStats = getFuture(obj.getStats());
+    Builder builder = Socket.newBuilder()
+        .setRef(toSocketRef(obj))
+        .setLocal(toAddress(socketStats.local));
+    if (socketStats.security != null) {
+      builder.setSecurity(toSecurity(socketStats.security));
+    }
+    // listen sockets do not have remote nor data
+    if (socketStats.remote != null) {
+      builder.setRemote(toAddress(socketStats.remote));
+    }
+    builder.setData(extractSocketData(socketStats));
+    return builder.build();
+  }
+
+  static Address toAddress(SocketAddress address) {
+    Preconditions.checkNotNull(address);
+    Address.Builder builder = Address.newBuilder();
+    if (address instanceof InetSocketAddress) {
+      InetSocketAddress inetAddress = (InetSocketAddress) address;
+      builder.setTcpipAddress(
+          TcpIpAddress
+              .newBuilder()
+              .setIpAddress(
+                  ByteString.copyFrom(inetAddress.getAddress().getAddress()))
+              .setPort(inetAddress.getPort())
+              .build());
+    } else if (address.getClass().getName().endsWith("io.netty.channel.unix.DomainSocketAddress")) {
+      builder.setUdsAddress(
+          UdsAddress
+              .newBuilder()
+              .setFilename(address.toString()) // DomainSocketAddress.toString returns filename
+              .build());
+    } else {
+      builder.setOtherAddress(OtherAddress.newBuilder().setName(address.toString()).build());
+    }
+    return builder.build();
+  }
+
+  static SocketData extractSocketData(SocketStats socketStats) {
+    SocketData.Builder builder = SocketData.newBuilder();
+    if (socketStats.data != null) {
+      TransportStats s = socketStats.data;
+      builder
+          .setStreamsStarted(s.streamsStarted)
+          .setStreamsSucceeded(s.streamsSucceeded)
+          .setStreamsFailed(s.streamsFailed)
+          .setMessagesSent(s.messagesSent)
+          .setMessagesReceived(s.messagesReceived)
+          .setKeepAlivesSent(s.keepAlivesSent)
+          .setLastLocalStreamCreatedTimestamp(
+              Timestamps.fromNanos(s.lastLocalStreamCreatedTimeNanos))
+          .setLastRemoteStreamCreatedTimestamp(
+              Timestamps.fromNanos(s.lastRemoteStreamCreatedTimeNanos))
+          .setLastMessageSentTimestamp(
+              Timestamps.fromNanos(s.lastMessageSentTimeNanos))
+          .setLastMessageReceivedTimestamp(
+              Timestamps.fromNanos(s.lastMessageReceivedTimeNanos))
+          .setLocalFlowControlWindow(
+              Int64Value.of(s.localFlowControlWindow))
+          .setRemoteFlowControlWindow(
+              Int64Value.of(s.remoteFlowControlWindow));
+    }
+    builder.addAllOption(toSocketOptionsList(socketStats.socketOptions));
+    return builder.build();
+  }
+
+  public static final String SO_LINGER = "SO_LINGER";
+  public static final String SO_TIMEOUT = "SO_TIMEOUT";
+  public static final String TCP_INFO = "TCP_INFO";
+
+  static SocketOption toSocketOptionLinger(int lingerSeconds) {
+    final SocketOptionLinger lingerOpt;
+    if (lingerSeconds >= 0) {
+      lingerOpt = SocketOptionLinger
+          .newBuilder()
+          .setActive(true)
+          .setDuration(Durations.fromSeconds(lingerSeconds))
+          .build();
+    } else {
+      lingerOpt = SocketOptionLinger.getDefaultInstance();
+    }
+    return SocketOption
+        .newBuilder()
+        .setName(SO_LINGER)
+        .setAdditional(Any.pack(lingerOpt))
+        .build();
+  }
+
+  static SocketOption toSocketOptionTimeout(String name, int timeoutMillis) {
+    Preconditions.checkNotNull(name);
+    return SocketOption
+        .newBuilder()
+        .setName(name)
+        .setAdditional(
+            Any.pack(
+                SocketOptionTimeout
+                    .newBuilder()
+                    .setDuration(Durations.fromMillis(timeoutMillis))
+                    .build()))
+        .build();
+  }
+
+  static SocketOption toSocketOptionTcpInfo(InternalChannelz.TcpInfo i) {
+    SocketOptionTcpInfo tcpInfo = SocketOptionTcpInfo.newBuilder()
+        .setTcpiState(i.state)
+        .setTcpiCaState(i.caState)
+        .setTcpiRetransmits(i.retransmits)
+        .setTcpiProbes(i.probes)
+        .setTcpiBackoff(i.backoff)
+        .setTcpiOptions(i.options)
+        .setTcpiSndWscale(i.sndWscale)
+        .setTcpiRcvWscale(i.rcvWscale)
+        .setTcpiRto(i.rto)
+        .setTcpiAto(i.ato)
+        .setTcpiSndMss(i.sndMss)
+        .setTcpiRcvMss(i.rcvMss)
+        .setTcpiUnacked(i.unacked)
+        .setTcpiSacked(i.sacked)
+        .setTcpiLost(i.lost)
+        .setTcpiRetrans(i.retrans)
+        .setTcpiFackets(i.fackets)
+        .setTcpiLastDataSent(i.lastDataSent)
+        .setTcpiLastAckSent(i.lastAckSent)
+        .setTcpiLastDataRecv(i.lastDataRecv)
+        .setTcpiLastAckRecv(i.lastAckRecv)
+        .setTcpiPmtu(i.pmtu)
+        .setTcpiRcvSsthresh(i.rcvSsthresh)
+        .setTcpiRtt(i.rtt)
+        .setTcpiRttvar(i.rttvar)
+        .setTcpiSndSsthresh(i.sndSsthresh)
+        .setTcpiSndCwnd(i.sndCwnd)
+        .setTcpiAdvmss(i.advmss)
+        .setTcpiReordering(i.reordering)
+        .build();
+    return SocketOption
+        .newBuilder()
+        .setName(TCP_INFO)
+        .setAdditional(Any.pack(tcpInfo))
+        .build();
+  }
+
+  static SocketOption toSocketOptionAdditional(String name, String value) {
+    Preconditions.checkNotNull(name);
+    Preconditions.checkNotNull(value);
+    return SocketOption.newBuilder().setName(name).setValue(value).build();
+  }
+
+  static List<SocketOption> toSocketOptionsList(InternalChannelz.SocketOptions options) {
+    Preconditions.checkNotNull(options);
+    List<SocketOption> ret = new ArrayList<>();
+    if (options.lingerSeconds != null) {
+      ret.add(toSocketOptionLinger(options.lingerSeconds));
+    }
+    if (options.soTimeoutMillis != null) {
+      ret.add(toSocketOptionTimeout(SO_TIMEOUT, options.soTimeoutMillis));
+    }
+    if (options.tcpInfo != null) {
+      ret.add(toSocketOptionTcpInfo(options.tcpInfo));
+    }
+    for (Entry<String, String> entry : options.others.entrySet()) {
+      ret.add(toSocketOptionAdditional(entry.getKey(), entry.getValue()));
+    }
+    return ret;
+  }
+
+  static Channel toChannel(InternalInstrumented<ChannelStats> channel) {
+    ChannelStats stats = getFuture(channel.getStats());
+    Channel.Builder channelBuilder = Channel
+        .newBuilder()
+        .setRef(toChannelRef(channel))
+        .setData(extractChannelData(stats));
+    for (InternalWithLogId subchannel : stats.subchannels) {
+      channelBuilder.addSubchannelRef(toSubchannelRef(subchannel));
+    }
+
+    return channelBuilder.build();
+  }
+
+  static ChannelData extractChannelData(InternalChannelz.ChannelStats stats) {
+    ChannelData.Builder builder = ChannelData.newBuilder();
+    builder.setTarget(stats.target)
+        .setState(toChannelConnectivityState(stats.state))
+        .setCallsStarted(stats.callsStarted)
+        .setCallsSucceeded(stats.callsSucceeded)
+        .setCallsFailed(stats.callsFailed)
+        .setLastCallStartedTimestamp(Timestamps.fromNanos(stats.lastCallStartedNanos));
+    if (stats.channelTrace != null) {
+      builder.setTrace(toChannelTrace(stats.channelTrace));
+    }
+    return builder.build();
+  }
+
+  static ChannelConnectivityState toChannelConnectivityState(ConnectivityState s) {
+    return ChannelConnectivityState.newBuilder().setState(toState(s)).build();
+  }
+
+  private static ChannelTrace toChannelTrace(InternalChannelz.ChannelTrace channelTrace) {
+    return ChannelTrace.newBuilder()
+        .setNumEventsLogged(channelTrace.numEventsLogged)
+        .setCreationTimestamp(Timestamps.fromNanos(channelTrace.creationTimeNanos))
+        .addAllEvents(toChannelTraceEvents(channelTrace.events))
+        .build();
+  }
+
+  private static List<ChannelTraceEvent> toChannelTraceEvents(List<Event> events) {
+    List<ChannelTraceEvent> channelTraceEvents = new ArrayList<>();
+    for (Event event : events) {
+      ChannelTraceEvent.Builder builder = ChannelTraceEvent.newBuilder()
+          .setDescription(event.description)
+          .setSeverity(Severity.valueOf(event.severity.name()))
+          .setTimestamp(Timestamps.fromNanos(event.timestampNanos));
+      if (event.channelRef != null) {
+        builder.setChannelRef(toChannelRef(event.channelRef));
+      }
+      if (event.subchannelRef != null) {
+        builder.setSubchannelRef(toSubchannelRef(event.subchannelRef));
+      }
+      channelTraceEvents.add(builder.build());
+    }
+    return Collections.unmodifiableList(channelTraceEvents);
+  }
+
+  static State toState(ConnectivityState state) {
+    if (state == null) {
+      return State.UNKNOWN;
+    }
+    try {
+      return Enum.valueOf(State.class, state.name());
+    } catch (IllegalArgumentException e) {
+      return State.UNKNOWN;
+    }
+  }
+
+  static Subchannel toSubchannel(InternalInstrumented<ChannelStats> subchannel) {
+    ChannelStats stats = getFuture(subchannel.getStats());
+    Subchannel.Builder subchannelBuilder = Subchannel
+        .newBuilder()
+        .setRef(toSubchannelRef(subchannel))
+        .setData(extractChannelData(stats));
+    Preconditions.checkState(stats.sockets.isEmpty() || stats.subchannels.isEmpty());
+    for (InternalWithLogId childSocket : stats.sockets) {
+      subchannelBuilder.addSocketRef(toSocketRef(childSocket));
+    }
+    for (InternalWithLogId childSubchannel : stats.subchannels) {
+      subchannelBuilder.addSubchannelRef(toSubchannelRef(childSubchannel));
+    }
+    return subchannelBuilder.build();
+  }
+
+  static GetTopChannelsResponse toGetTopChannelResponse(RootChannelList rootChannels) {
+    GetTopChannelsResponse.Builder responseBuilder = GetTopChannelsResponse
+        .newBuilder()
+        .setEnd(rootChannels.end);
+    for (InternalInstrumented<ChannelStats> c : rootChannels.channels) {
+      responseBuilder.addChannel(ChannelzProtoUtil.toChannel(c));
+    }
+    return responseBuilder.build();
+  }
+
+  static GetServersResponse toGetServersResponse(ServerList servers) {
+    GetServersResponse.Builder responseBuilder = GetServersResponse
+        .newBuilder()
+        .setEnd(servers.end);
+    for (InternalInstrumented<ServerStats> s : servers.servers) {
+      responseBuilder.addServer(ChannelzProtoUtil.toServer(s));
+    }
+    return responseBuilder.build();
+  }
+
+  static GetServerSocketsResponse toGetServerSocketsResponse(ServerSocketsList serverSockets) {
+    GetServerSocketsResponse.Builder responseBuilder = GetServerSocketsResponse
+        .newBuilder()
+        .setEnd(serverSockets.end);
+    for (InternalWithLogId s : serverSockets.sockets) {
+      responseBuilder.addSocketRef(ChannelzProtoUtil.toSocketRef(s));
+    }
+    return responseBuilder.build();
+  }
+
+  private static <T> T getFuture(ListenableFuture<T> future) {
+    try {
+      T ret = future.get();
+      if (ret == null) {
+        throw Status.UNIMPLEMENTED
+            .withDescription("The entity's stats can not be retrieved. "
+                + "If this is an InProcessTransport this is expected.")
+            .asRuntimeException();
+      }
+      return ret;
+    } catch (InterruptedException e) {
+      throw Status.INTERNAL.withCause(e).asRuntimeException();
+    } catch (ExecutionException e) {
+      throw Status.INTERNAL.withCause(e).asRuntimeException();
+    }
+  }
+}
diff --git a/services/src/main/java/io/grpc/services/ChannelzService.java b/services/src/main/java/io/grpc/services/ChannelzService.java
new file mode 100644
index 0000000..b8b3f1f
--- /dev/null
+++ b/services/src/main/java/io/grpc/services/ChannelzService.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import com.google.common.annotations.VisibleForTesting;
+import io.grpc.ExperimentalApi;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.ChannelStats;
+import io.grpc.InternalChannelz.ServerList;
+import io.grpc.InternalChannelz.ServerSocketsList;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.Status;
+import io.grpc.channelz.v1.ChannelzGrpc;
+import io.grpc.channelz.v1.GetChannelRequest;
+import io.grpc.channelz.v1.GetChannelResponse;
+import io.grpc.channelz.v1.GetServerSocketsRequest;
+import io.grpc.channelz.v1.GetServerSocketsResponse;
+import io.grpc.channelz.v1.GetServersRequest;
+import io.grpc.channelz.v1.GetServersResponse;
+import io.grpc.channelz.v1.GetSocketRequest;
+import io.grpc.channelz.v1.GetSocketResponse;
+import io.grpc.channelz.v1.GetSubchannelRequest;
+import io.grpc.channelz.v1.GetSubchannelResponse;
+import io.grpc.channelz.v1.GetTopChannelsRequest;
+import io.grpc.channelz.v1.GetTopChannelsResponse;
+import io.grpc.stub.StreamObserver;
+
+/**
+ * The channelz service provides stats about a running gRPC process.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4206")
+public final class ChannelzService extends ChannelzGrpc.ChannelzImplBase {
+  private final InternalChannelz channelz;
+  private final int maxPageSize;
+
+  /**
+   * Creates an instance.
+   */
+  public static ChannelzService newInstance(int maxPageSize) {
+    return new ChannelzService(InternalChannelz.instance(), maxPageSize);
+  }
+
+  @VisibleForTesting
+  ChannelzService(InternalChannelz channelz, int maxPageSize) {
+    this.channelz = channelz;
+    this.maxPageSize = maxPageSize;
+  }
+
+  /** Returns top level channel aka {@link io.grpc.internal.ManagedChannelImpl}. */
+  @Override
+  public void getTopChannels(
+      GetTopChannelsRequest request, StreamObserver<GetTopChannelsResponse> responseObserver) {
+    InternalChannelz.RootChannelList rootChannels
+        = channelz.getRootChannels(request.getStartChannelId(), maxPageSize);
+
+    responseObserver.onNext(ChannelzProtoUtil.toGetTopChannelResponse(rootChannels));
+    responseObserver.onCompleted();
+  }
+
+  /** Returns a top level channel aka {@link io.grpc.internal.ManagedChannelImpl}. */
+  @Override
+  public void getChannel(
+      GetChannelRequest request, StreamObserver<GetChannelResponse> responseObserver) {
+    InternalInstrumented<ChannelStats> s = channelz.getRootChannel(request.getChannelId());
+    if (s == null) {
+      responseObserver.onError(Status.NOT_FOUND.asRuntimeException());
+      return;
+    }
+
+    responseObserver.onNext(
+        GetChannelResponse
+            .newBuilder()
+            .setChannel(ChannelzProtoUtil.toChannel(s))
+            .build());
+    responseObserver.onCompleted();
+  }
+
+  /** Returns servers. */
+  @Override
+  public void getServers(
+      GetServersRequest request, StreamObserver<GetServersResponse> responseObserver) {
+    ServerList servers = channelz.getServers(request.getStartServerId(), maxPageSize);
+
+    responseObserver.onNext(ChannelzProtoUtil.toGetServersResponse(servers));
+    responseObserver.onCompleted();
+  }
+
+  /** Returns a subchannel. */
+  @Override
+  public void getSubchannel(
+      GetSubchannelRequest request, StreamObserver<GetSubchannelResponse> responseObserver) {
+    InternalInstrumented<ChannelStats> s = channelz.getSubchannel(request.getSubchannelId());
+    if (s == null) {
+      responseObserver.onError(Status.NOT_FOUND.asRuntimeException());
+      return;
+    }
+
+    responseObserver.onNext(
+        GetSubchannelResponse
+            .newBuilder()
+            .setSubchannel(ChannelzProtoUtil.toSubchannel(s))
+            .build());
+    responseObserver.onCompleted();
+  }
+
+  /** Returns a socket. */
+  @Override
+  public void getSocket(
+      GetSocketRequest request, StreamObserver<GetSocketResponse> responseObserver) {
+    InternalInstrumented<SocketStats> s = channelz.getSocket(request.getSocketId());
+    if (s == null) {
+      responseObserver.onError(Status.NOT_FOUND.asRuntimeException());
+      return;
+    }
+
+    responseObserver.onNext(
+          GetSocketResponse
+              .newBuilder()
+              .setSocket(ChannelzProtoUtil.toSocket(s))
+              .build());
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void getServerSockets(
+      GetServerSocketsRequest request, StreamObserver<GetServerSocketsResponse> responseObserver) {
+    ServerSocketsList serverSockets
+        = channelz.getServerSockets(request.getServerId(), request.getStartSocketId(), maxPageSize);
+    if (serverSockets == null) {
+      responseObserver.onError(Status.NOT_FOUND.asRuntimeException());
+      return;
+    }
+
+    responseObserver.onNext(ChannelzProtoUtil.toGetServerSocketsResponse(serverSockets));
+    responseObserver.onCompleted();
+  }
+}
diff --git a/services/src/main/java/io/grpc/services/HealthServiceImpl.java b/services/src/main/java/io/grpc/services/HealthServiceImpl.java
new file mode 100644
index 0000000..d27299b
--- /dev/null
+++ b/services/src/main/java/io/grpc/services/HealthServiceImpl.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import io.grpc.Status;
+import io.grpc.StatusException;
+import io.grpc.health.v1.HealthCheckRequest;
+import io.grpc.health.v1.HealthCheckResponse;
+import io.grpc.health.v1.HealthCheckResponse.ServingStatus;
+import io.grpc.health.v1.HealthGrpc;
+import io.grpc.stub.StreamObserver;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.annotation.Nullable;
+
+
+final class HealthServiceImpl extends HealthGrpc.HealthImplBase {
+
+  /* Due to the latency of rpc calls, synchronization of the map does not help with consistency.
+   * However, need use ConcurrentHashMap to prevent the possible race condition of concurrently
+   * putting two keys with a colliding hashCode into the map.*/
+  private final Map<String, ServingStatus> statusMap
+      = new ConcurrentHashMap<String, ServingStatus>();
+
+  @Override
+  public void check(HealthCheckRequest request,
+      StreamObserver<HealthCheckResponse> responseObserver) {
+    ServingStatus status = getStatus(request.getService());
+    if (status == null) {
+      responseObserver.onError(new StatusException(
+          Status.NOT_FOUND.withDescription("unknown service " + request.getService())));
+    } else {
+      HealthCheckResponse response = HealthCheckResponse.newBuilder().setStatus(status).build();
+      responseObserver.onNext(response);
+      responseObserver.onCompleted();
+    }
+  }
+
+  void setStatus(String service, ServingStatus status) {
+    statusMap.put(service, status);
+  }
+
+  @Nullable
+  ServingStatus getStatus(String service) {
+    return statusMap.get(service);
+  }
+
+  void clearStatus(String service) {
+    statusMap.remove(service);
+  }
+}
diff --git a/services/src/main/java/io/grpc/services/HealthStatusManager.java b/services/src/main/java/io/grpc/services/HealthStatusManager.java
new file mode 100644
index 0000000..090ac0c
--- /dev/null
+++ b/services/src/main/java/io/grpc/services/HealthStatusManager.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.grpc.BindableService;
+import io.grpc.health.v1.HealthCheckResponse.ServingStatus;
+
+/**
+ * A {@code HealthStatusManager} object manages a health check service. A health check service is
+ * created in the constructor of {@code HealthStatusManager}, and it can be retrieved by the
+ * {@link #getHealthService()} method.
+ * The health status manager can update the health statuses of the server.
+ */
+@io.grpc.ExperimentalApi("https://github.com/grpc/grpc-java/issues/4696")
+public final class HealthStatusManager {
+
+  private final HealthServiceImpl healthService;
+
+  /**
+   * Creates a new health service instance.
+   */
+  public HealthStatusManager() {
+    healthService = new HealthServiceImpl();
+  }
+
+  /**
+   * Gets the health check service created in the constructor.
+   */
+  public BindableService getHealthService() {
+    return healthService;
+  }
+
+  /**
+   * Updates the status of the server.
+   * @param service the name of some aspect of the server that is associated with a health status.
+   *     This name can have no relation with the gRPC services that the server is running with.
+   *     It can also be an empty String {@code ""} per the gRPC specification.
+   * @param status is one of the values {@link ServingStatus#SERVING},
+   *     {@link ServingStatus#NOT_SERVING} and {@link ServingStatus#UNKNOWN}.
+   */
+  public void setStatus(String service, ServingStatus status) {
+    checkNotNull(status, "status");
+    healthService.setStatus(service, status);
+  }
+
+  /**
+   * Clears the health status record of a service. The health service will respond with NOT_FOUND
+   * error on checking the status of a cleared service.
+   * @param service the name of some aspect of the server that is associated with a health status.
+   *     This name can have no relation with the gRPC services that the server is running with.
+   *     It can also be an empty String {@code ""} per the gRPC specification.
+   */
+  public void clearStatus(String service) {
+    healthService.clearStatus(service);
+  }
+}
diff --git a/services/src/main/java/io/grpc/services/InetAddressUtil.java b/services/src/main/java/io/grpc/services/InetAddressUtil.java
new file mode 100644
index 0000000..057a8cc
--- /dev/null
+++ b/services/src/main/java/io/grpc/services/InetAddressUtil.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.primitives.Ints;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.util.Arrays;
+
+// This is copied from guava 20.0 because it is a @Beta api
+final class InetAddressUtil {
+  private static final int IPV6_PART_COUNT = 8;
+
+  public static String toAddrString(InetAddress ip) {
+    checkNotNull(ip);
+    if (ip instanceof Inet4Address) {
+      // For IPv4, Java's formatting is good enough.
+      return ip.getHostAddress();
+    }
+    checkArgument(ip instanceof Inet6Address);
+    byte[] bytes = ip.getAddress();
+    int[] hextets = new int[IPV6_PART_COUNT];
+    for (int i = 0; i < hextets.length; i++) {
+      hextets[i] = Ints.fromBytes((byte) 0, (byte) 0, bytes[2 * i], bytes[2 * i + 1]);
+    }
+    compressLongestRunOfZeroes(hextets);
+    return hextetsToIPv6String(hextets);
+  }
+
+  private static void compressLongestRunOfZeroes(int[] hextets) {
+    int bestRunStart = -1;
+    int bestRunLength = -1;
+    int runStart = -1;
+    for (int i = 0; i < hextets.length + 1; i++) {
+      if (i < hextets.length && hextets[i] == 0) {
+        if (runStart < 0) {
+          runStart = i;
+        }
+      } else if (runStart >= 0) {
+        int runLength = i - runStart;
+        if (runLength > bestRunLength) {
+          bestRunStart = runStart;
+          bestRunLength = runLength;
+        }
+        runStart = -1;
+      }
+    }
+    if (bestRunLength >= 2) {
+      Arrays.fill(hextets, bestRunStart, bestRunStart + bestRunLength, -1);
+    }
+  }
+
+  private static String hextetsToIPv6String(int[] hextets) {
+    // While scanning the array, handle these state transitions:
+    //   start->num => "num"     start->gap => "::"
+    //   num->num   => ":num"    num->gap   => "::"
+    //   gap->num   => "num"     gap->gap   => ""
+    StringBuilder buf = new StringBuilder(39);
+    boolean lastWasNumber = false;
+    for (int i = 0; i < hextets.length; i++) {
+      boolean thisIsNumber = hextets[i] >= 0;
+      if (thisIsNumber) {
+        if (lastWasNumber) {
+          buf.append(':');
+        }
+        buf.append(Integer.toHexString(hextets[i]));
+      } else {
+        if (i == 0 || lastWasNumber) {
+          buf.append("::");
+        }
+      }
+      lastWasNumber = thisIsNumber;
+    }
+    return buf.toString();
+  }
+}
\ No newline at end of file
diff --git a/services/src/main/java/io/grpc/services/TempFileSink.java b/services/src/main/java/io/grpc/services/TempFileSink.java
new file mode 100644
index 0000000..c28339d
--- /dev/null
+++ b/services/src/main/java/io/grpc/services/TempFileSink.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2018, gRPC Authors All rights reserved.
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import com.google.protobuf.MessageLite;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * The output file goes to the JVM's temp dir with a prefix of BINARY_INFO. The proto messages
+ * are written serially using {@link MessageLite#writeDelimitedTo(OutputStream)}.
+ */
+class TempFileSink implements BinaryLogSink {
+  private static final Logger logger = Logger.getLogger(TempFileSink.class.getName());
+
+  private final String outPath;
+  private final OutputStream out;
+  private boolean closed;
+
+  TempFileSink() throws IOException {
+    File outFile = File.createTempFile("BINARY_INFO.", "");
+    outPath = outFile.getPath();
+    logger.log(Level.INFO, "Writing binary logs to to {0}", outFile.getAbsolutePath());
+    out = new BufferedOutputStream(new FileOutputStream(outFile));
+  }
+
+  String getPath() {
+    return this.outPath;
+  }
+
+  @Override
+  public synchronized void write(MessageLite message) {
+    if (closed) {
+      logger.log(Level.FINEST, "Attempt to write after TempFileSink is closed.");
+      return;
+    }
+    try {
+      message.writeDelimitedTo(out);
+    } catch (IOException e) {
+      logger.log(Level.SEVERE, "Caught exception while writing", e);
+      closeQuietly();
+    }
+  }
+
+  @Override
+  public synchronized void close() throws IOException {
+    if (closed) {
+      return;
+    }
+    closed = true;
+    try {
+      out.flush();
+    } finally {
+      out.close();
+    }
+  }
+
+  private synchronized void closeQuietly() {
+    try {
+      close();
+    } catch (IOException e) {
+      logger.log(Level.SEVERE, "Caught exception while closing", e);
+    }
+  }
+}
diff --git a/services/src/main/java/io/grpc/services/package-info.java b/services/src/main/java/io/grpc/services/package-info.java
new file mode 100644
index 0000000..3b5b639
--- /dev/null
+++ b/services/src/main/java/io/grpc/services/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/**
+ * Service definitions and utilities for the pre-defined gRPC services.
+ */
+package io.grpc.services;
diff --git a/services/src/main/proto/grpc/binlog/v1alpha/binarylog.proto b/services/src/main/proto/grpc/binlog/v1alpha/binarylog.proto
new file mode 100644
index 0000000..73df5db
--- /dev/null
+++ b/services/src/main/proto/grpc/binlog/v1alpha/binarylog.proto
@@ -0,0 +1,160 @@
+// Copyright 2018 The gRPC Authors
+// All rights reserved.
+//
+// Licensed 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.
+// Service exported by server reflection
+
+syntax = "proto3";
+
+package grpc.binarylog.v1alpha;
+
+import "google/protobuf/duration.proto";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.binarylog.v1alpha";
+option java_outer_classname = "BinaryLogProto";
+
+// Log entry we store in binary logs
+message GrpcLogEntry {
+  // Enumerates the type of logs
+  enum Type {
+    UNKNOWN_TYPE = 0;
+    SEND_INITIAL_METADATA = 1;
+    SEND_TRAILING_METADATA = 2;
+    SEND_MESSAGE = 3;
+    RECV_INITIAL_METADATA = 4;
+    RECV_TRAILING_METADATA = 5;
+    RECV_MESSAGE = 6;
+  };
+
+  // Enumerates the entity that generates the log entry
+  enum Logger {
+    UNKNOWN_LOGGER = 0;
+    CLIENT = 1;
+    SERVER = 2;
+  }
+
+  Type type = 1;      // One of the above Type enum
+  Logger logger = 2;  // One of the above Logger enum
+
+  // Uniquely identifies a call. Each call may have several log entries, they
+  // will share the same call_id. 128 bits split into 2 64-bit parts.
+  Uint128 call_id = 3;
+
+  // The logger uses one of the following fields to record the payload,
+  // according to the type of the log entry.
+  oneof payload {
+    // Used by {SEND,RECV}_INITIAL_METADATA and
+    // {SEND,RECV}_TRAILING_METADATA. This contains only the metadata
+    // from the application.
+    Metadata metadata = 4;
+    // Used by {SEND,RECV}_MESSAGE
+    Message message = 5;
+  }
+
+  // Peer address information, will only be recorded in SEND_INITIAL_METADATA
+  // and RECV_INITIAL_METADATA entries.
+  Peer peer = 6;
+
+  // true if payload does not represent the full message or metadata.
+  bool truncated = 7;
+
+  // The method name. Logged for the first entry:
+  // RECV_INITIAL_METADATA for server side or
+  // SEND_INITIAL_METADATA for client side.
+  string method_name = 8;
+
+  // status_code and status_message:
+  // Only present for SEND_TRAILING_METADATA on server side or
+  // RECV_TRAILING_METADATA on client side.
+  uint32 status_code = 9;
+
+  // An original status message before any transport specific
+  // encoding.
+  string status_message = 10;
+
+  // The value of the 'grpc-status-details-bin' metadata key. If
+  // present, this is always an encoded 'google.rpc.Status' message.
+  bytes status_details = 11;
+
+  // the RPC timeout
+  google.protobuf.Duration timeout = 12;
+
+  // The entry sequence id for this call. The first GrpcLogEntry has a
+  // value of 1, to disambiguate from an unset value. The purpose of
+  // this field is to detect missing entries in environments where
+  // durability or ordering is not guaranteed.
+  uint32 sequence_id_within_call = 13;
+};
+
+// Message payload, used by REQUEST and RESPONSE
+message Message {
+  // This flag is currently used to indicate whether the payload is compressed,
+  // it may contain other semantics in the future. Value of 1 indicates that the
+  // binary octet sequence of Message is compressed using the mechanism declared
+  // by the Message-Encoding header. A value of 0 indicates that no encoding of
+  // Message bytes has occurred.
+  uint32 flags = 1; // TODO(zpencer): this is changed because there is no uint8
+  // Length of the message. It may not be the same as the length of the
+  // data field, as the logging payload can be truncated or omitted.
+  uint32 length = 2;
+  // May be truncated or omitted.
+  bytes data = 3;
+}
+
+// A list of metadata pairs, used in the payload of CLIENT_INIT_METADATA,
+// SERVER_INIT_METADATA and TRAILING_METADATA
+// Implementations may omit some entries to honor the header limits
+// of GRPC_BINARY_LOG_CONFIG.
+// 
+// Implementations will not log the following entries, and this is
+// not to be treated as a truncation:
+// - entries handled by grpc that are not user visible, such as those
+//   that begin with 'grpc-' or keys like 'lb-token'
+// - transport specific entries, including but not limited to:
+//   ':path', ':authority', 'content-encoding', 'user-agent', 'te', etc
+// - entries added for call credentials
+message Metadata {
+  repeated MetadataEntry entry = 1;
+}
+
+// A metadata key value pair
+message MetadataEntry {
+  bytes key = 1;
+  bytes value = 2;
+}
+
+// Peer information
+message Peer {
+  enum PeerType {
+    UNKNOWN_PEERTYPE = 0;
+    // address is the address in 1.2.3.4 form
+    PEER_IPV4 = 1;
+    // address the address in canonical form (RFC5952 section 4)
+    // The scope is NOT included in the peer string.
+    PEER_IPV6 = 2;
+    // address is UDS string
+    PEER_UNIX = 3;
+  };
+  PeerType peer_type = 1;
+  bytes peer = 2; // will be removed: do not use
+  string address = 3;
+  // only for PEER_IPV4 and PEER_IPV6
+  uint32 ip_port = 4;
+}
+
+// Used to record call_id.
+message Uint128 {
+  fixed64 high = 1;
+  fixed64 low = 2;
+};
diff --git a/services/src/main/proto/grpc/channelz/v1/channelz.proto b/services/src/main/proto/grpc/channelz/v1/channelz.proto
new file mode 100644
index 0000000..f061165
--- /dev/null
+++ b/services/src/main/proto/grpc/channelz/v1/channelz.proto
@@ -0,0 +1,517 @@
+// Copyright 2018 The gRPC Authors
+//
+// Licensed 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.
+
+// This file defines an interface for exporting monitoring information
+// out of gRPC servers.  See the full design at
+// https://github.com/grpc/proposal/blob/master/A14-channelz.md
+//
+// The canonical version of this proto can be found at
+// https://github.com/grpc/grpc-proto/blob/master/grpc/channelz/v1/channelz.proto
+
+syntax = "proto3";
+
+package grpc.channelz.v1;
+
+import "google/protobuf/any.proto";
+import "google/protobuf/duration.proto";
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/wrappers.proto";
+
+option go_package = "google.golang.org/grpc/channelz/grpc_channelz_v1";
+option java_multiple_files = true;
+option java_package = "io.grpc.channelz.v1";
+option java_outer_classname = "ChannelzProto";
+
+// Channel is a logical grouping of channels, subchannels, and sockets.
+message Channel {
+  // The identifier for this channel. This should bet set.
+  ChannelRef ref = 1;
+  // Data specific to this channel.
+  ChannelData data = 2;
+  // At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+
+  // There are no ordering guarantees on the order of channel refs.
+  // There may not be cycles in the ref graph.
+  // A channel ref may be present in more than one channel or subchannel.
+  repeated ChannelRef channel_ref = 3;
+
+  // At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+  // There are no ordering guarantees on the order of subchannel refs.
+  // There may not be cycles in the ref graph.
+  // A sub channel ref may be present in more than one channel or subchannel.
+  repeated SubchannelRef subchannel_ref = 4;
+
+  // There are no ordering guarantees on the order of sockets.
+  repeated SocketRef socket_ref = 5;
+}
+
+// Subchannel is a logical grouping of channels, subchannels, and sockets.
+// A subchannel is load balanced over by it's ancestor
+message Subchannel {
+  // The identifier for this channel.
+  SubchannelRef ref = 1;
+  // Data specific to this channel.
+  ChannelData data = 2;
+  // At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+
+  // There are no ordering guarantees on the order of channel refs.
+  // There may not be cycles in the ref graph.
+  // A channel ref may be present in more than one channel or subchannel.
+  repeated ChannelRef channel_ref = 3;
+
+  // At most one of 'channel_ref+subchannel_ref' and 'socket' is set.
+  // There are no ordering guarantees on the order of subchannel refs.
+  // There may not be cycles in the ref graph.
+  // A sub channel ref may be present in more than one channel or subchannel.
+  repeated SubchannelRef subchannel_ref = 4;
+
+  // There are no ordering guarantees on the order of sockets.
+  repeated SocketRef socket_ref = 5;
+}
+
+// These come from the specified states in this document:
+// https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md
+message ChannelConnectivityState {
+  enum State {
+    UNKNOWN = 0;
+    IDLE = 1;
+    CONNECTING = 2;
+    READY = 3;
+    TRANSIENT_FAILURE = 4;
+    SHUTDOWN = 5;
+  }
+  State state = 1;
+}
+
+// Channel data is data related to a specific Channel or Subchannel.
+message ChannelData {
+  // The connectivity state of the channel or subchannel.  Implementations
+  // should always set this.
+  ChannelConnectivityState state = 1;
+
+  // The target this channel originally tried to connect to.  May be absent
+  string target = 2;
+
+  // A trace of recent events on the channel.  May be absent.
+  ChannelTrace trace = 3;
+
+  // The number of calls started on the channel
+  int64 calls_started = 4;
+  // The number of calls that have completed with an OK status
+  int64 calls_succeeded = 5;
+  // The number of calls that have completed with a non-OK status
+  int64 calls_failed = 6;
+
+  // The last time a call was started on the channel.
+  google.protobuf.Timestamp last_call_started_timestamp = 7;
+}
+
+// A trace event is an interesting thing that happened to a channel or
+// subchannel, such as creation, address resolution, subchannel creation, etc.
+message ChannelTraceEvent {
+  // High level description of the event.
+  string description = 1;
+  // The supported severity levels of trace events.
+  enum Severity {
+    CT_UNKNOWN = 0;
+    CT_INFO = 1;
+    CT_WARNING = 2;
+    CT_ERROR = 3;
+  }
+  // the severity of the trace event
+  Severity severity = 2;
+  // When this event occurred.
+  google.protobuf.Timestamp timestamp = 3;
+  // ref of referenced channel or subchannel.
+  // Optional, only present if this event refers to a child object. For example,
+  // this field would be filled if this trace event was for a subchannel being
+  // created.
+  oneof child_ref {
+    ChannelRef channel_ref = 4;
+    SubchannelRef subchannel_ref = 5;
+  }
+}
+
+// ChannelTrace represents the recent events that have occurred on the channel.
+message ChannelTrace {
+  // Number of events ever logged in this tracing object. This can differ from
+  // events.size() because events can be overwritten or garbage collected by
+  // implementations.
+  int64 num_events_logged = 1;
+  // Time that this channel was created.
+  google.protobuf.Timestamp creation_timestamp = 2;
+  // List of events that have occurred on this channel.
+  repeated ChannelTraceEvent events = 3;
+}
+
+// ChannelRef is a reference to a Channel.
+message ChannelRef {
+  // The globally unique id for this channel.  Must be a positive number.
+  int64 channel_id = 1;
+  // An optional name associated with the channel.
+  string name = 2;
+  // Intentionally don't use field numbers from other refs.
+  reserved 3, 4, 5, 6, 7, 8;
+}
+
+// ChannelRef is a reference to a Subchannel.
+message SubchannelRef {
+  // The globally unique id for this subchannel.  Must be a positive number.
+  int64 subchannel_id = 7;
+  // An optional name associated with the subchannel.
+  string name = 8;
+  // Intentionally don't use field numbers from other refs.
+  reserved 1, 2, 3, 4, 5, 6;
+}
+
+// SocketRef is a reference to a Socket.
+message SocketRef {
+  int64 socket_id = 3;
+  // An optional name associated with the socket.
+  string name = 4;
+  // Intentionally don't use field numbers from other refs.
+  reserved 1, 2, 5, 6, 7, 8;
+}
+
+// ServerRef is a reference to a Server.
+message ServerRef {
+  // A globally unique identifier for this server.  Must be a positive number.
+  int64 server_id = 5;
+  // An optional name associated with the server.
+  string name = 6;
+  // Intentionally don't use field numbers from other refs.
+  reserved 1, 2, 3, 4, 7, 8;
+}
+
+// Server represents a single server.  There may be multiple servers in a single
+// program.
+message Server {
+  // The identifier for a Server.  This should be set.
+  ServerRef ref = 1;
+  // The associated data of the Server.
+  ServerData data = 2;
+
+  // The sockets that the server is listening on.  There are no ordering
+  // guarantees.  This may be absent.
+  repeated SocketRef listen_socket = 3;
+}
+
+// ServerData is data for a specific Server.
+message ServerData {
+  // A trace of recent events on the server.  May be absent.
+  ChannelTrace trace = 1;
+
+  // The number of incoming calls started on the server
+  int64 calls_started = 2;
+  // The number of incoming calls that have completed with an OK status
+  int64 calls_succeeded = 3;
+  // The number of incoming calls that have a completed with a non-OK status
+  int64 calls_failed = 4;
+
+  // The last time a call was started on the server.
+  google.protobuf.Timestamp last_call_started_timestamp = 5;
+}
+
+// Information about an actual connection.  Pronounced "sock-ay".
+message Socket {
+  // The identifier for the Socket.
+  SocketRef ref = 1;
+
+  // Data specific to this Socket.
+  SocketData data = 2;
+  // The locally bound address.
+  Address local = 3;
+  // The remote bound address.  May be absent.
+  Address remote = 4;
+  // Security details for this socket.  May be absent if not available, or
+  // there is no security on the socket.
+  Security security = 5;
+
+  // Optional, represents the name of the remote endpoint, if different than
+  // the original target name.
+  string remote_name = 6;
+}
+
+// SocketData is data associated for a specific Socket.  The fields present
+// are specific to the implementation, so there may be minor differences in
+// the semantics.  (e.g. flow control windows)
+message SocketData {
+  // The number of streams that have been started.
+  int64 streams_started = 1;
+  // The number of streams that have ended successfully:
+  // On client side, received frame with eos bit set;
+  // On server side, sent frame with eos bit set.
+  int64 streams_succeeded = 2;
+  // The number of streams that have ended unsuccessfully:
+  // On client side, ended without receiving frame with eos bit set;
+  // On server side, ended without sending frame with eos bit set.
+  int64 streams_failed = 3;
+  // The number of grpc messages successfully sent on this socket.
+  int64 messages_sent = 4;
+  // The number of grpc messages received on this socket.
+  int64 messages_received = 5;
+
+  // The number of keep alives sent.  This is typically implemented with HTTP/2
+  // ping messages.
+  int64 keep_alives_sent = 6;
+
+  // The last time a stream was created by this endpoint.  Usually unset for
+  // servers.
+  google.protobuf.Timestamp last_local_stream_created_timestamp = 7;
+  // The last time a stream was created by the remote endpoint.  Usually unset
+  // for clients.
+  google.protobuf.Timestamp last_remote_stream_created_timestamp = 8;
+
+  // The last time a message was sent by this endpoint.
+  google.protobuf.Timestamp last_message_sent_timestamp = 9;
+  // The last time a message was received by this endpoint.
+  google.protobuf.Timestamp last_message_received_timestamp = 10;
+
+  // The amount of window, granted to the local endpoint by the remote endpoint.
+  // This may be slightly out of date due to network latency.  This does NOT
+  // include stream level or TCP level flow control info.
+  google.protobuf.Int64Value local_flow_control_window = 11;
+
+  // The amount of window, granted to the remote endpoint by the local endpoint.
+  // This may be slightly out of date due to network latency.  This does NOT
+  // include stream level or TCP level flow control info.
+  google.protobuf.Int64Value  remote_flow_control_window = 12;
+
+  // Socket options set on this socket.  May be absent.
+  repeated SocketOption option = 13;
+}
+
+// Address represents the address used to create the socket.
+message Address {
+  message TcpIpAddress {
+    // Either the IPv4 or IPv6 address in bytes.  Will be either 4 bytes or 16
+    // bytes in length.
+    bytes ip_address = 1;
+    // 0-64k, or -1 if not appropriate.
+    int32 port = 2;
+  }
+  // A Unix Domain Socket address.
+  message UdsAddress {
+    string filename = 1;
+  }
+  // An address type not included above.
+  message OtherAddress {
+    // The human readable version of the value.  This value should be set.
+    string name = 1;
+    // The actual address message.
+    google.protobuf.Any value = 2;
+  }
+
+  oneof address {
+    TcpIpAddress tcpip_address = 1;
+    UdsAddress uds_address = 2;
+    OtherAddress other_address = 3;
+  }
+}
+
+// Security represents details about how secure the socket is.
+message Security {
+  message Tls {
+    oneof cipher_suite {
+      // The cipher suite name in the RFC 4346 format:
+      // https://tools.ietf.org/html/rfc4346#appendix-C
+      string standard_name = 1;
+      // Some other way to describe the cipher suite if
+      // the RFC 4346 name is not available.
+      string other_name = 2;
+    }
+    // the certificate used by this endpoint.
+    bytes local_certificate = 3;
+    // the certificate used by the remote endpoint.
+    bytes remote_certificate = 4;
+  }
+  message OtherSecurity {
+    // The human readable version of the value.
+    string name = 1;
+    // The actual security details message.
+    google.protobuf.Any value = 2;
+  }
+  oneof model {
+    Tls tls = 1;
+    OtherSecurity other = 2;
+  }
+}
+
+// SocketOption represents socket options for a socket.  Specifically, these
+// are the options returned by getsockopt().
+message SocketOption {
+  // The full name of the socket option.  Typically this will be the upper case
+  // name, such as "SO_REUSEPORT".
+  string name = 1;
+  // The human readable value of this socket option.  At least one of value or
+  // additional will be set.
+  string value = 2;
+  // Additional data associated with the socket option.  At least one of value
+  // or additional will be set.
+  google.protobuf.Any additional = 3;
+}
+
+// For use with SocketOption's additional field.  This is primarily used for
+// SO_RCVTIMEO and SO_SNDTIMEO
+message SocketOptionTimeout {
+  google.protobuf.Duration duration = 1;
+}
+
+// For use with SocketOption's additional field.  This is primarily used for
+// SO_LINGER.
+message SocketOptionLinger {
+  // active maps to `struct linger.l_onoff`
+  bool active = 1;
+  // duration maps to `struct linger.l_linger`
+  google.protobuf.Duration duration = 2;
+}
+
+// For use with SocketOption's additional field.  Tcp info for
+// SOL_TCP and TCP_INFO.
+message SocketOptionTcpInfo {
+  uint32 tcpi_state = 1;
+
+  uint32 tcpi_ca_state = 2;
+  uint32 tcpi_retransmits = 3;
+  uint32 tcpi_probes = 4;
+  uint32 tcpi_backoff = 5;
+  uint32 tcpi_options = 6;
+  uint32 tcpi_snd_wscale = 7;
+  uint32 tcpi_rcv_wscale = 8;
+
+  uint32 tcpi_rto = 9;
+  uint32 tcpi_ato = 10;
+  uint32 tcpi_snd_mss = 11;
+  uint32 tcpi_rcv_mss = 12;
+
+  uint32 tcpi_unacked = 13;
+  uint32 tcpi_sacked = 14;
+  uint32 tcpi_lost = 15;
+  uint32 tcpi_retrans = 16;
+  uint32 tcpi_fackets = 17;
+
+  uint32 tcpi_last_data_sent = 18;
+  uint32 tcpi_last_ack_sent = 19;
+  uint32 tcpi_last_data_recv = 20;
+  uint32 tcpi_last_ack_recv = 21;
+
+  uint32 tcpi_pmtu = 22;
+  uint32 tcpi_rcv_ssthresh = 23;
+  uint32 tcpi_rtt = 24;
+  uint32 tcpi_rttvar = 25;
+  uint32 tcpi_snd_ssthresh = 26;
+  uint32 tcpi_snd_cwnd = 27;
+  uint32 tcpi_advmss = 28;
+  uint32 tcpi_reordering = 29;
+}
+
+// Channelz is a service exposed by gRPC servers that provides detailed debug
+// information.
+service Channelz {
+  // Gets all root channels (i.e. channels the application has directly
+  // created). This does not include subchannels nor non-top level channels.
+  rpc GetTopChannels(GetTopChannelsRequest) returns (GetTopChannelsResponse);
+  // Gets all servers that exist in the process.
+  rpc GetServers(GetServersRequest) returns (GetServersResponse);
+  // Gets all server sockets that exist in the process.
+  rpc GetServerSockets(GetServerSocketsRequest) returns (GetServerSocketsResponse);
+  // Returns a single Channel, or else a NOT_FOUND code.
+  rpc GetChannel(GetChannelRequest) returns (GetChannelResponse);
+  // Returns a single Subchannel, or else a NOT_FOUND code.
+  rpc GetSubchannel(GetSubchannelRequest) returns (GetSubchannelResponse);
+  // Returns a single Socket or else a NOT_FOUND code.
+  rpc GetSocket(GetSocketRequest) returns (GetSocketResponse);
+}
+
+message GetTopChannelsRequest {
+  // start_channel_id indicates that only channels at or above this id should be
+  // included in the results.
+  int64 start_channel_id = 1;
+}
+
+message GetTopChannelsResponse {
+  // list of channels that the connection detail service knows about.  Sorted in
+  // ascending channel_id order.
+  repeated Channel channel = 1;
+  // If set, indicates that the list of channels is the final list.  Requesting
+  // more channels can only return more if they are created after this RPC
+  // completes.
+  bool end = 2;
+}
+
+message GetServersRequest {
+  // start_server_id indicates that only servers at or above this id should be
+  // included in the results.
+  int64 start_server_id = 1;
+}
+
+message GetServersResponse {
+  // list of servers that the connection detail service knows about.  Sorted in
+  // ascending server_id order.
+  repeated Server server = 1;
+  // If set, indicates that the list of servers is the final list.  Requesting
+  // more servers will only return more if they are created after this RPC
+  // completes.
+  bool end = 2;
+}
+
+message GetServerSocketsRequest {
+  int64 server_id = 1;
+  // start_socket_id indicates that only sockets at or above this id should be
+  // included in the results.
+  int64 start_socket_id = 2;
+}
+
+message GetServerSocketsResponse {
+  // list of socket refs that the connection detail service knows about.  Sorted in
+  // ascending socket_id order.
+  repeated SocketRef socket_ref = 1;
+  // If set, indicates that the list of sockets is the final list.  Requesting
+  // more sockets will only return more if they are created after this RPC
+  // completes.
+  bool end = 2;
+}
+
+message GetChannelRequest {
+  // channel_id is the the identifier of the specific channel to get.
+  int64 channel_id = 1;
+}
+
+message GetChannelResponse {
+  // The Channel that corresponds to the requested channel_id.  This field
+  // should be set.
+  Channel channel = 1;
+}
+
+message GetSubchannelRequest {
+  // subchannel_id is the the identifier of the specific subchannel to get.
+  int64 subchannel_id = 1;
+}
+
+message GetSubchannelResponse {
+  // The Subchannel that corresponds to the requested subchannel_id.  This
+  // field should be set.
+  Subchannel subchannel = 1;
+}
+
+message GetSocketRequest {
+  // socket_id is the the identifier of the specific socket to get.
+  int64 socket_id = 1;
+}
+
+message GetSocketResponse {
+  // The Socket that corresponds to the requested socket_id.  This field
+  // should be set.
+  Socket socket = 1;
+}
diff --git a/services/src/main/proto/grpc/health/v1/health.proto b/services/src/main/proto/grpc/health/v1/health.proto
new file mode 100644
index 0000000..44c4432
--- /dev/null
+++ b/services/src/main/proto/grpc/health/v1/health.proto
@@ -0,0 +1,39 @@
+// Copyright 2015 The gRPC Authors
+//
+// Licensed 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.
+
+syntax = "proto3";
+
+package grpc.health.v1;
+
+option csharp_namespace = "Grpc.Health.V1";
+option java_multiple_files = true;
+option java_outer_classname = "HealthProto";
+option java_package = "io.grpc.health.v1";
+
+message HealthCheckRequest {
+  string service = 1;
+}
+
+message HealthCheckResponse {
+  enum ServingStatus {
+    UNKNOWN = 0;
+    SERVING = 1;
+    NOT_SERVING = 2;
+  }
+  ServingStatus status = 1;
+}
+
+service Health {
+  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
+}
diff --git a/services/src/main/proto/io/grpc/reflection/v1alpha/reflection.proto b/services/src/main/proto/io/grpc/reflection/v1alpha/reflection.proto
new file mode 100644
index 0000000..55d5f65
--- /dev/null
+++ b/services/src/main/proto/io/grpc/reflection/v1alpha/reflection.proto
@@ -0,0 +1,139 @@
+// Copyright 2016 The gRPC Authors
+//
+// Licensed 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.
+// Service exported by server reflection
+
+syntax = "proto3";
+
+package grpc.reflection.v1alpha;
+
+option java_multiple_files = true;
+option java_package = "io.grpc.reflection.v1alpha";
+option java_outer_classname = "ServerReflectionProto";
+
+service ServerReflection {
+  // The reflection service is structured as a bidirectional stream, ensuring
+  // all related requests go to a single server.
+  rpc ServerReflectionInfo(stream ServerReflectionRequest)
+      returns (stream ServerReflectionResponse);
+}
+
+// The message sent by the client when calling ServerReflectionInfo method.
+message ServerReflectionRequest {
+  string host = 1;
+  // To use reflection service, the client should set one of the following
+  // fields in message_request. The server distinguishes requests by their
+  // defined field and then handles them using corresponding methods.
+  oneof message_request {
+    // Find a proto file by the file name.
+    string file_by_filename = 3;
+
+    // Find the proto file that declares the given fully-qualified symbol name.
+    // This field should be a fully-qualified symbol name
+    // (e.g. <package>.<service>[.<method>] or <package>.<type>).
+    string file_containing_symbol = 4;
+
+    // Find the proto file which defines an extension extending the given
+    // message type with the given field number.
+    ExtensionRequest file_containing_extension = 5;
+
+    // Finds the tag numbers used by all known extensions of extendee_type, and
+    // appends them to ExtensionNumberResponse in an undefined order.
+    // Its corresponding method is best-effort: it's not guaranteed that the
+    // reflection service will implement this method, and it's not guaranteed
+    // that this method will provide all extensions. Returns
+    // StatusCode::UNIMPLEMENTED if it's not implemented.
+    // This field should be a fully-qualified type name. The format is
+    // <package>.<type>
+    string all_extension_numbers_of_type = 6;
+
+    // List the full names of registered services. The content will not be
+    // checked.
+    string list_services = 7;
+  }
+}
+
+// The type name and extension number sent by the client when requesting
+// file_containing_extension.
+message ExtensionRequest {
+  // Fully-qualified type name. The format should be <package>.<type>
+  string containing_type = 1;
+  int32 extension_number = 2;
+}
+
+// The message sent by the server to answer ServerReflectionInfo method.
+message ServerReflectionResponse {
+  string valid_host = 1;
+  ServerReflectionRequest original_request = 2;
+  // The server set one of the following fields accroding to the message_request
+  // in the request.
+  oneof message_response {
+    // This message is used to answer file_by_filename, file_containing_symbol,
+    // file_containing_extension requests with transitive dependencies. As
+    // the repeated label is not allowed in oneof fields, we use a
+    // FileDescriptorResponse message to encapsulate the repeated fields.
+    // The reflection service is allowed to avoid sending FileDescriptorProtos
+    // that were previously sent in response to earlier requests in the stream.
+    FileDescriptorResponse file_descriptor_response = 4;
+
+    // This message is used to answer all_extension_numbers_of_type requst.
+    ExtensionNumberResponse all_extension_numbers_response = 5;
+
+    // This message is used to answer list_services request.
+    ListServiceResponse list_services_response = 6;
+
+    // This message is used when an error occurs.
+    ErrorResponse error_response = 7;
+  }
+}
+
+// Serialized FileDescriptorProto messages sent by the server answering
+// a file_by_filename, file_containing_symbol, or file_containing_extension
+// request.
+message FileDescriptorResponse {
+  // Serialized FileDescriptorProto messages. We avoid taking a dependency on
+  // descriptor.proto, which uses proto2 only features, by making them opaque
+  // bytes instead.
+  repeated bytes file_descriptor_proto = 1;
+}
+
+// A list of extension numbers sent by the server answering
+// all_extension_numbers_of_type request.
+message ExtensionNumberResponse {
+  // Full name of the base type, including the package name. The format
+  // is <package>.<type>
+  string base_type_name = 1;
+  repeated int32 extension_number = 2;
+}
+
+// A list of ServiceResponse sent by the server answering list_services request.
+message ListServiceResponse {
+  // The information of each service may be expanded in the future, so we use
+  // ServiceResponse message to encapsulate it.
+  repeated ServiceResponse service = 1;
+}
+
+// The information of a single service used by ListServiceResponse to answer
+// list_services request.
+message ServiceResponse {
+  // Full name of a registered service, including its package name. The format
+  // is <package>.<service>
+  string name = 1;
+}
+
+// The error code and error message sent by the server when an error occurs.
+message ErrorResponse {
+  // This field uses the error codes defined in grpc::StatusCode.
+  int32 error_code = 1;
+  string error_message = 2;
+}
diff --git a/services/src/test/java/io/grpc/protobuf/services/ProtoReflectionServiceTest.java b/services/src/test/java/io/grpc/protobuf/services/ProtoReflectionServiceTest.java
new file mode 100644
index 0000000..5e60802
--- /dev/null
+++ b/services/src/test/java/io/grpc/protobuf/services/ProtoReflectionServiceTest.java
@@ -0,0 +1,637 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.protobuf.services;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.protobuf.ByteString;
+import io.grpc.BindableService;
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.internal.testing.StreamRecorder;
+import io.grpc.reflection.testing.AnotherDynamicServiceGrpc;
+import io.grpc.reflection.testing.DynamicReflectionTestDepthTwoProto;
+import io.grpc.reflection.testing.DynamicServiceGrpc;
+import io.grpc.reflection.testing.ReflectableServiceGrpc;
+import io.grpc.reflection.testing.ReflectionTestDepthThreeProto;
+import io.grpc.reflection.testing.ReflectionTestDepthTwoAlternateProto;
+import io.grpc.reflection.testing.ReflectionTestDepthTwoProto;
+import io.grpc.reflection.testing.ReflectionTestProto;
+import io.grpc.reflection.v1alpha.ExtensionNumberResponse;
+import io.grpc.reflection.v1alpha.ExtensionRequest;
+import io.grpc.reflection.v1alpha.FileDescriptorResponse;
+import io.grpc.reflection.v1alpha.ServerReflectionGrpc;
+import io.grpc.reflection.v1alpha.ServerReflectionRequest;
+import io.grpc.reflection.v1alpha.ServerReflectionResponse;
+import io.grpc.reflection.v1alpha.ServiceResponse;
+import io.grpc.stub.ClientCallStreamObserver;
+import io.grpc.stub.ClientResponseObserver;
+import io.grpc.stub.StreamObserver;
+import io.grpc.util.MutableHandlerRegistry;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link ProtoReflectionService}. */
+@RunWith(JUnit4.class)
+public class ProtoReflectionServiceTest {
+  private static final String TEST_HOST = "localhost";
+  private MutableHandlerRegistry handlerRegistry = new MutableHandlerRegistry();
+  private BindableService reflectionService;
+  private ServerServiceDefinition dynamicService =
+      (new DynamicServiceGrpc.DynamicServiceImplBase() {}).bindService();
+  private ServerServiceDefinition anotherDynamicService =
+      (new AnotherDynamicServiceGrpc.AnotherDynamicServiceImplBase() {}).bindService();
+  private Server server;
+  private ManagedChannel channel;
+  private ServerReflectionGrpc.ServerReflectionStub stub;
+
+  @Before
+  public void setUp() throws Exception {
+    reflectionService = ProtoReflectionService.newInstance();
+    server =
+        InProcessServerBuilder.forName("proto-reflection-test")
+            .directExecutor()
+            .addService(reflectionService)
+            .addService(new ReflectableServiceGrpc.ReflectableServiceImplBase() {})
+            .fallbackHandlerRegistry(handlerRegistry)
+            .build()
+            .start();
+    channel = InProcessChannelBuilder.forName("proto-reflection-test").directExecutor().build();
+    stub = ServerReflectionGrpc.newStub(channel);
+  }
+
+  @After
+  public void tearDown() {
+    if (server != null) {
+      server.shutdownNow();
+    }
+    if (channel != null) {
+      channel.shutdownNow();
+    }
+  }
+
+  @Test
+  public void listServices() throws Exception {
+    Set<ServiceResponse> originalServices =
+        new HashSet<ServiceResponse>(
+            Arrays.asList(
+                ServiceResponse.newBuilder()
+                    .setName("grpc.reflection.v1alpha.ServerReflection")
+                    .build(),
+                ServiceResponse.newBuilder()
+                    .setName("grpc.reflection.testing.ReflectableService")
+                    .build()));
+    assertServiceResponseEquals(originalServices);
+
+    handlerRegistry.addService(dynamicService);
+    assertServiceResponseEquals(
+        new HashSet<ServiceResponse>(
+            Arrays.asList(
+                ServiceResponse.newBuilder()
+                    .setName("grpc.reflection.v1alpha.ServerReflection")
+                    .build(),
+                ServiceResponse.newBuilder()
+                    .setName("grpc.reflection.testing.ReflectableService")
+                    .build(),
+                ServiceResponse.newBuilder()
+                    .setName("grpc.reflection.testing.DynamicService")
+                    .build())));
+
+    handlerRegistry.addService(anotherDynamicService);
+    assertServiceResponseEquals(
+        new HashSet<ServiceResponse>(
+            Arrays.asList(
+                ServiceResponse.newBuilder()
+                    .setName("grpc.reflection.v1alpha.ServerReflection")
+                    .build(),
+                ServiceResponse.newBuilder()
+                    .setName("grpc.reflection.testing.ReflectableService")
+                    .build(),
+                ServiceResponse.newBuilder()
+                    .setName("grpc.reflection.testing.DynamicService")
+                    .build(),
+                ServiceResponse.newBuilder()
+                    .setName("grpc.reflection.testing.AnotherDynamicService")
+                    .build())));
+
+    handlerRegistry.removeService(dynamicService);
+    assertServiceResponseEquals(
+        new HashSet<ServiceResponse>(
+            Arrays.asList(
+                ServiceResponse.newBuilder()
+                    .setName("grpc.reflection.v1alpha.ServerReflection")
+                    .build(),
+                ServiceResponse.newBuilder()
+                    .setName("grpc.reflection.testing.ReflectableService")
+                    .build(),
+                ServiceResponse.newBuilder()
+                    .setName("grpc.reflection.testing.AnotherDynamicService")
+                    .build())));
+
+    handlerRegistry.removeService(anotherDynamicService);
+    assertServiceResponseEquals(originalServices);
+  }
+
+  @Test
+  public void fileByFilename() throws Exception {
+    ServerReflectionRequest request =
+        ServerReflectionRequest.newBuilder()
+            .setHost(TEST_HOST)
+            .setFileByFilename("io/grpc/reflection/testing/reflection_test_depth_three.proto")
+            .build();
+
+    ServerReflectionResponse goldenResponse =
+        ServerReflectionResponse.newBuilder()
+            .setValidHost(TEST_HOST)
+            .setOriginalRequest(request)
+            .setFileDescriptorResponse(
+                FileDescriptorResponse.newBuilder()
+                    .addFileDescriptorProto(
+                        ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString())
+                    .build())
+            .build();
+
+    StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver =
+        stub.serverReflectionInfo(responseObserver);
+    requestObserver.onNext(request);
+    requestObserver.onCompleted();
+
+    assertEquals(goldenResponse, responseObserver.firstValue().get());
+  }
+
+  @Test
+  public void fileByFilenameConsistentForMutableServices() throws Exception {
+    ServerReflectionRequest request =
+        ServerReflectionRequest.newBuilder()
+            .setHost(TEST_HOST)
+            .setFileByFilename("io/grpc/reflection/testing/dynamic_reflection_test_depth_two.proto")
+            .build();
+    ServerReflectionResponse goldenResponse =
+        ServerReflectionResponse.newBuilder()
+            .setValidHost(TEST_HOST)
+            .setOriginalRequest(request)
+            .setFileDescriptorResponse(
+                FileDescriptorResponse.newBuilder()
+                    .addFileDescriptorProto(
+                        DynamicReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString())
+                    .build())
+            .build();
+
+    StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver =
+        stub.serverReflectionInfo(responseObserver);
+    handlerRegistry.addService(dynamicService);
+    requestObserver.onNext(request);
+    requestObserver.onCompleted();
+    StreamRecorder<ServerReflectionResponse> responseObserver2 = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver2 =
+        stub.serverReflectionInfo(responseObserver2);
+    handlerRegistry.removeService(dynamicService);
+    requestObserver2.onNext(request);
+    requestObserver2.onCompleted();
+    StreamRecorder<ServerReflectionResponse> responseObserver3 = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver3 =
+        stub.serverReflectionInfo(responseObserver3);
+    requestObserver3.onNext(request);
+    requestObserver3.onCompleted();
+
+    assertEquals(
+        ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE,
+        responseObserver.firstValue().get().getMessageResponseCase());
+    assertEquals(goldenResponse, responseObserver2.firstValue().get());
+    assertEquals(
+        ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE,
+        responseObserver3.firstValue().get().getMessageResponseCase());
+  }
+
+  @Test
+  public void fileContainingSymbol() throws Exception {
+    ServerReflectionRequest request =
+        ServerReflectionRequest.newBuilder()
+            .setHost(TEST_HOST)
+            .setFileContainingSymbol("grpc.reflection.testing.ReflectableService.Method")
+            .build();
+
+    List<ByteString> goldenResponse =
+        Arrays.asList(
+            ReflectionTestProto.getDescriptor().toProto().toByteString(),
+            ReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString(),
+            ReflectionTestDepthTwoAlternateProto.getDescriptor().toProto().toByteString(),
+            ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString());
+
+    StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver =
+        stub.serverReflectionInfo(responseObserver);
+    requestObserver.onNext(request);
+    requestObserver.onCompleted();
+
+    List<ByteString> response =
+        responseObserver
+            .firstValue()
+            .get()
+            .getFileDescriptorResponse()
+            .getFileDescriptorProtoList();
+    assertEquals(goldenResponse.size(), response.size());
+    assertEquals(new HashSet<ByteString>(goldenResponse), new HashSet<ByteString>(response));
+  }
+
+  @Test
+  public void fileContainingNestedSymbol() throws Exception {
+    ServerReflectionRequest request =
+        ServerReflectionRequest.newBuilder()
+            .setHost(TEST_HOST)
+            .setFileContainingSymbol("grpc.reflection.testing.NestedTypeOuter.Middle.Inner")
+            .build();
+
+    ServerReflectionResponse goldenResponse =
+        ServerReflectionResponse.newBuilder()
+            .setValidHost(TEST_HOST)
+            .setOriginalRequest(request)
+            .setFileDescriptorResponse(
+                FileDescriptorResponse.newBuilder()
+                    .addFileDescriptorProto(
+                        ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString())
+                    .build())
+            .build();
+
+    StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver =
+        stub.serverReflectionInfo(responseObserver);
+    requestObserver.onNext(request);
+    requestObserver.onCompleted();
+    assertEquals(goldenResponse, responseObserver.firstValue().get());
+  }
+
+  @Test
+  public void fileContainingSymbolForMutableServices() throws Exception {
+    ServerReflectionRequest request =
+        ServerReflectionRequest.newBuilder()
+            .setHost(TEST_HOST)
+            .setFileContainingSymbol("grpc.reflection.testing.DynamicRequest")
+            .build();
+    ServerReflectionResponse goldenResponse =
+        ServerReflectionResponse.newBuilder()
+            .setValidHost(TEST_HOST)
+            .setOriginalRequest(request)
+            .setFileDescriptorResponse(
+                FileDescriptorResponse.newBuilder()
+                    .addFileDescriptorProto(
+                        DynamicReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString())
+                    .build())
+            .build();
+
+    StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver =
+        stub.serverReflectionInfo(responseObserver);
+    handlerRegistry.addService(dynamicService);
+    requestObserver.onNext(request);
+    requestObserver.onCompleted();
+    StreamRecorder<ServerReflectionResponse> responseObserver2 = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver2 =
+        stub.serverReflectionInfo(responseObserver2);
+    handlerRegistry.removeService(dynamicService);
+    requestObserver2.onNext(request);
+    requestObserver2.onCompleted();
+    StreamRecorder<ServerReflectionResponse> responseObserver3 = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver3 =
+        stub.serverReflectionInfo(responseObserver3);
+    requestObserver3.onNext(request);
+    requestObserver3.onCompleted();
+
+    assertEquals(
+        ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE,
+        responseObserver.firstValue().get().getMessageResponseCase());
+    assertEquals(goldenResponse, responseObserver2.firstValue().get());
+    assertEquals(
+        ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE,
+        responseObserver3.firstValue().get().getMessageResponseCase());
+  }
+
+  @Test
+  public void fileContainingExtension() throws Exception {
+    ServerReflectionRequest request =
+        ServerReflectionRequest.newBuilder()
+            .setHost(TEST_HOST)
+            .setFileContainingExtension(
+                ExtensionRequest.newBuilder()
+                    .setContainingType("grpc.reflection.testing.ThirdLevelType")
+                    .setExtensionNumber(100)
+                    .build())
+            .build();
+
+    List<ByteString> goldenResponse =
+        Arrays.asList(
+            ReflectionTestProto.getDescriptor().toProto().toByteString(),
+            ReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString(),
+            ReflectionTestDepthTwoAlternateProto.getDescriptor().toProto().toByteString(),
+            ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString());
+
+    StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver =
+        stub.serverReflectionInfo(responseObserver);
+    requestObserver.onNext(request);
+    requestObserver.onCompleted();
+
+    List<ByteString> response =
+        responseObserver
+            .firstValue()
+            .get()
+            .getFileDescriptorResponse()
+            .getFileDescriptorProtoList();
+    assertEquals(goldenResponse.size(), response.size());
+    assertEquals(new HashSet<ByteString>(goldenResponse), new HashSet<ByteString>(response));
+  }
+
+  @Test
+  public void fileContainingNestedExtension() throws Exception {
+    ServerReflectionRequest request =
+        ServerReflectionRequest.newBuilder()
+            .setHost(TEST_HOST)
+            .setFileContainingExtension(
+                ExtensionRequest.newBuilder()
+                    .setContainingType("grpc.reflection.testing.ThirdLevelType")
+                    .setExtensionNumber(101)
+                    .build())
+            .build();
+
+    ServerReflectionResponse goldenResponse =
+        ServerReflectionResponse.newBuilder()
+            .setValidHost(TEST_HOST)
+            .setOriginalRequest(request)
+            .setFileDescriptorResponse(
+                FileDescriptorResponse.newBuilder()
+                    .addFileDescriptorProto(
+                        ReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString())
+                    .addFileDescriptorProto(
+                        ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString())
+                    .build())
+            .build();
+
+    StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver =
+        stub.serverReflectionInfo(responseObserver);
+    requestObserver.onNext(request);
+    requestObserver.onCompleted();
+    assertEquals(goldenResponse, responseObserver.firstValue().get());
+  }
+
+  @Test
+  public void fileContainingExtensionForMutableServices() throws Exception {
+    ServerReflectionRequest request =
+        ServerReflectionRequest.newBuilder()
+            .setHost(TEST_HOST)
+            .setFileContainingExtension(
+                ExtensionRequest.newBuilder()
+                    .setContainingType("grpc.reflection.testing.TypeWithExtensions")
+                    .setExtensionNumber(200)
+                    .build())
+            .build();
+    ServerReflectionResponse goldenResponse =
+        ServerReflectionResponse.newBuilder()
+            .setValidHost(TEST_HOST)
+            .setOriginalRequest(request)
+            .setFileDescriptorResponse(
+                FileDescriptorResponse.newBuilder()
+                    .addFileDescriptorProto(
+                        DynamicReflectionTestDepthTwoProto.getDescriptor().toProto().toByteString())
+                    .build())
+            .build();
+
+    StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver =
+        stub.serverReflectionInfo(responseObserver);
+    handlerRegistry.addService(dynamicService);
+    requestObserver.onNext(request);
+    requestObserver.onCompleted();
+    StreamRecorder<ServerReflectionResponse> responseObserver2 = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver2 =
+        stub.serverReflectionInfo(responseObserver2);
+    handlerRegistry.removeService(dynamicService);
+    requestObserver2.onNext(request);
+    requestObserver2.onCompleted();
+    StreamRecorder<ServerReflectionResponse> responseObserver3 = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver3 =
+        stub.serverReflectionInfo(responseObserver3);
+    requestObserver3.onNext(request);
+    requestObserver3.onCompleted();
+
+    assertEquals(
+        ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE,
+        responseObserver.firstValue().get().getMessageResponseCase());
+    assertEquals(goldenResponse, responseObserver2.firstValue().get());
+    assertEquals(
+        ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE,
+        responseObserver3.firstValue().get().getMessageResponseCase());
+  }
+
+  @Test
+  public void allExtensionNumbersOfType() throws Exception {
+    ServerReflectionRequest request =
+        ServerReflectionRequest.newBuilder()
+            .setHost(TEST_HOST)
+            .setAllExtensionNumbersOfType("grpc.reflection.testing.ThirdLevelType")
+            .build();
+
+    Set<Integer> goldenResponse = new HashSet<Integer>(Arrays.asList(100, 101));
+
+    StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver =
+        stub.serverReflectionInfo(responseObserver);
+    requestObserver.onNext(request);
+    requestObserver.onCompleted();
+    Set<Integer> extensionNumberResponseSet =
+        new HashSet<Integer>(
+            responseObserver
+                .firstValue()
+                .get()
+                .getAllExtensionNumbersResponse()
+                .getExtensionNumberList());
+    assertEquals(goldenResponse, extensionNumberResponseSet);
+  }
+
+  @Test
+  public void allExtensionNumbersOfTypeForMutableServices() throws Exception {
+    String type = "grpc.reflection.testing.TypeWithExtensions";
+    ServerReflectionRequest request =
+        ServerReflectionRequest.newBuilder()
+            .setHost(TEST_HOST)
+            .setAllExtensionNumbersOfType(type)
+            .build();
+    ServerReflectionResponse goldenResponse =
+        ServerReflectionResponse.newBuilder()
+            .setValidHost(TEST_HOST)
+            .setOriginalRequest(request)
+            .setAllExtensionNumbersResponse(
+                ExtensionNumberResponse.newBuilder()
+                    .setBaseTypeName(type)
+                    .addExtensionNumber(200)
+                    .build())
+            .build();
+
+    StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver =
+        stub.serverReflectionInfo(responseObserver);
+    handlerRegistry.addService(dynamicService);
+    requestObserver.onNext(request);
+    requestObserver.onCompleted();
+    StreamRecorder<ServerReflectionResponse> responseObserver2 = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver2 =
+        stub.serverReflectionInfo(responseObserver2);
+    handlerRegistry.removeService(dynamicService);
+    requestObserver2.onNext(request);
+    requestObserver2.onCompleted();
+    StreamRecorder<ServerReflectionResponse> responseObserver3 = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver3 =
+        stub.serverReflectionInfo(responseObserver3);
+    requestObserver3.onNext(request);
+    requestObserver3.onCompleted();
+
+    assertEquals(
+        ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE,
+        responseObserver.firstValue().get().getMessageResponseCase());
+    assertEquals(goldenResponse, responseObserver2.firstValue().get());
+    assertEquals(
+        ServerReflectionResponse.MessageResponseCase.ERROR_RESPONSE,
+        responseObserver3.firstValue().get().getMessageResponseCase());
+  }
+
+  @Test
+  public void flowControl() throws Exception {
+    FlowControlClientResponseObserver clientResponseObserver =
+        new FlowControlClientResponseObserver();
+    ClientCallStreamObserver<ServerReflectionRequest> requestObserver =
+        (ClientCallStreamObserver<ServerReflectionRequest>)
+            stub.serverReflectionInfo(clientResponseObserver);
+
+    // ClientCalls.startCall() calls request(1) initially, so we should get an immediate response.
+    requestObserver.onNext(flowControlRequest);
+    assertEquals(1, clientResponseObserver.getResponses().size());
+    assertEquals(flowControlGoldenResponse, clientResponseObserver.getResponses().get(0));
+
+    // Verify we don't receive an additional response until we request it.
+    requestObserver.onNext(flowControlRequest);
+    assertEquals(1, clientResponseObserver.getResponses().size());
+
+    requestObserver.request(1);
+    assertEquals(2, clientResponseObserver.getResponses().size());
+    assertEquals(flowControlGoldenResponse, clientResponseObserver.getResponses().get(1));
+
+    requestObserver.onCompleted();
+    assertTrue(clientResponseObserver.onCompleteCalled());
+  }
+
+  @Test
+  public void flowControlOnCompleteWithPendingRequest() throws Exception {
+    FlowControlClientResponseObserver clientResponseObserver =
+        new FlowControlClientResponseObserver();
+    ClientCallStreamObserver<ServerReflectionRequest> requestObserver =
+        (ClientCallStreamObserver<ServerReflectionRequest>)
+            stub.serverReflectionInfo(clientResponseObserver);
+
+    // ClientCalls.startCall() calls request(1) initially, so make additional request.
+    requestObserver.onNext(flowControlRequest);
+    requestObserver.onNext(flowControlRequest);
+    requestObserver.onCompleted();
+    assertEquals(1, clientResponseObserver.getResponses().size());
+    assertFalse(clientResponseObserver.onCompleteCalled());
+
+    requestObserver.request(1);
+    assertTrue(clientResponseObserver.onCompleteCalled());
+    assertEquals(2, clientResponseObserver.getResponses().size());
+    assertEquals(flowControlGoldenResponse, clientResponseObserver.getResponses().get(1));
+  }
+
+  private final ServerReflectionRequest flowControlRequest =
+      ServerReflectionRequest.newBuilder()
+          .setHost(TEST_HOST)
+          .setFileByFilename("io/grpc/reflection/testing/reflection_test_depth_three.proto")
+          .build();
+  private final ServerReflectionResponse flowControlGoldenResponse =
+      ServerReflectionResponse.newBuilder()
+          .setValidHost(TEST_HOST)
+          .setOriginalRequest(flowControlRequest)
+          .setFileDescriptorResponse(
+              FileDescriptorResponse.newBuilder()
+                  .addFileDescriptorProto(
+                      ReflectionTestDepthThreeProto.getDescriptor().toProto().toByteString())
+                  .build())
+          .build();
+
+  private static class FlowControlClientResponseObserver
+      implements ClientResponseObserver<ServerReflectionRequest, ServerReflectionResponse> {
+    private final List<ServerReflectionResponse> responses =
+        new ArrayList<>();
+    private boolean onCompleteCalled = false;
+
+    @Override
+    public void beforeStart(final ClientCallStreamObserver<ServerReflectionRequest> requestStream) {
+      requestStream.disableAutoInboundFlowControl();
+    }
+
+    @Override
+    public void onNext(ServerReflectionResponse value) {
+      responses.add(value);
+    }
+
+    @Override
+    public void onError(Throwable t) {
+      fail("onError called");
+    }
+
+    @Override
+    public void onCompleted() {
+      onCompleteCalled = true;
+    }
+
+    public List<ServerReflectionResponse> getResponses() {
+      return responses;
+    }
+
+    public boolean onCompleteCalled() {
+      return onCompleteCalled;
+    }
+  }
+
+  private void assertServiceResponseEquals(Set<ServiceResponse> goldenResponse) throws Exception {
+    ServerReflectionRequest request =
+        ServerReflectionRequest.newBuilder().setHost(TEST_HOST).setListServices("services").build();
+    StreamRecorder<ServerReflectionResponse> responseObserver = StreamRecorder.create();
+    StreamObserver<ServerReflectionRequest> requestObserver =
+        stub.serverReflectionInfo(responseObserver);
+    requestObserver.onNext(request);
+    requestObserver.onCompleted();
+    List<ServiceResponse> response =
+        responseObserver.firstValue().get().getListServicesResponse().getServiceList();
+    assertEquals(goldenResponse.size(), response.size());
+    assertEquals(goldenResponse, new HashSet<ServiceResponse>(response));
+  }
+}
diff --git a/services/src/test/java/io/grpc/services/BinaryLogProviderImplTest.java b/services/src/test/java/io/grpc/services/BinaryLogProviderImplTest.java
new file mode 100644
index 0000000..238f5fb
--- /dev/null
+++ b/services/src/test/java/io/grpc/services/BinaryLogProviderImplTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.CallOptions;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link BinaryLogProviderImpl}. */
+@RunWith(JUnit4.class)
+public class BinaryLogProviderImplTest {
+  @Test
+  public void configStrNullTest() throws Exception {
+    BinaryLogSink sink = mock(BinaryLogSink.class);
+    BinaryLogProviderImpl binlog = new BinaryLogProviderImpl(sink, /*configStr=*/ null);
+    assertNull(binlog.getServerInterceptor("package.service/method"));
+    assertNull(binlog.getClientInterceptor("package.service/method", CallOptions.DEFAULT));
+  }
+
+  @Test
+  public void configStrEmptyTest() throws Exception {
+    BinaryLogSink sink = mock(BinaryLogSink.class);
+    BinaryLogProviderImpl binlog = new BinaryLogProviderImpl(sink, "");
+    assertNull(binlog.getServerInterceptor("package.service/method"));
+    assertNull(binlog.getClientInterceptor("package.service/method", CallOptions.DEFAULT));
+  }
+
+  @Test
+  public void closeTest() throws Exception {
+    BinaryLogSink sink = mock(BinaryLogSink.class);
+    BinaryLogProviderImpl log = new BinaryLogProviderImpl(sink, "*");
+    verify(sink, never()).close();
+    log.close();
+    verify(sink).close();
+  }
+}
diff --git a/services/src/test/java/io/grpc/services/BinaryLogProviderTest.java b/services/src/test/java/io/grpc/services/BinaryLogProviderTest.java
new file mode 100644
index 0000000..8e87030
--- /dev/null
+++ b/services/src/test/java/io/grpc/services/BinaryLogProviderTest.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.io.ByteStreams;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
+import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
+import io.grpc.ForwardingServerCallListener.SimpleForwardingServerCallListener;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.Marshaller;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.ServerCall;
+import io.grpc.ServerCall.Listener;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.ServerMethodDefinition;
+import io.grpc.internal.IoUtils;
+import io.grpc.internal.NoopClientCall;
+import io.grpc.internal.NoopServerCall;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link BinaryLogProvider}. */
+@RunWith(JUnit4.class)
+public class BinaryLogProviderTest {
+  private final InvocationCountMarshaller<String> reqMarshaller =
+      new InvocationCountMarshaller<String>() {
+        @Override
+        Marshaller<String> delegate() {
+          return StringMarshaller.INSTANCE;
+        }
+      };
+  private final InvocationCountMarshaller<Integer> respMarshaller =
+      new InvocationCountMarshaller<Integer>() {
+        @Override
+        Marshaller<Integer> delegate() {
+          return IntegerMarshaller.INSTANCE;
+        }
+      };
+  private final MethodDescriptor<String, Integer> method =
+      MethodDescriptor
+          .newBuilder(reqMarshaller, respMarshaller)
+          .setFullMethodName("myservice/mymethod")
+          .setType(MethodType.UNARY)
+          .setSchemaDescriptor(new Object())
+          .setIdempotent(true)
+          .setSafe(true)
+          .setSampledToLocalTracing(true)
+          .build();
+  private final List<byte[]> binlogReq = new ArrayList<byte[]>();
+  private final List<byte[]> binlogResp = new ArrayList<byte[]>();
+  private final BinaryLogProvider binlogProvider = new BinaryLogProvider() {
+    @Override
+    public ServerInterceptor getServerInterceptor(String fullMethodName) {
+      return new TestBinaryLogServerInterceptor();
+    }
+
+    @Override
+    public ClientInterceptor getClientInterceptor(
+        String fullMethodName, CallOptions callOptions) {
+      return new TestBinaryLogClientInterceptor();
+    }
+  };
+
+  @Test
+  public void wrapChannel_methodDescriptor() throws Exception {
+    final AtomicReference<MethodDescriptor<?, ?>> methodRef =
+        new AtomicReference<MethodDescriptor<?, ?>>();
+    Channel channel = new Channel() {
+      @Override
+      public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
+          MethodDescriptor<RequestT, ResponseT> method, CallOptions callOptions) {
+        methodRef.set(method);
+        return new NoopClientCall<RequestT, ResponseT>();
+      }
+
+      @Override
+      public String authority() {
+        throw new UnsupportedOperationException();
+      }
+    };
+    Channel wChannel = binlogProvider.wrapChannel(channel);
+    ClientCall<String, Integer> unusedClientCall = wChannel.newCall(method, CallOptions.DEFAULT);
+    validateWrappedMethod(methodRef.get());
+  }
+
+  @Test
+  public void wrapChannel_handler() throws Exception {
+    final List<byte[]> serializedReq = new ArrayList<byte[]>();
+    final AtomicReference<ClientCall.Listener<?>> listener =
+        new AtomicReference<ClientCall.Listener<?>>();
+    Channel channel = new Channel() {
+      @Override
+      public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
+          MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) {
+        return new NoopClientCall<RequestT, ResponseT>() {
+          @Override
+          public void start(Listener<ResponseT> responseListener, Metadata headers) {
+            listener.set(responseListener);
+          }
+
+          @Override
+          public void sendMessage(RequestT message) {
+            serializedReq.add((byte[]) message);
+          }
+        };
+      }
+
+      @Override
+      public String authority() {
+        throw new UnsupportedOperationException();
+      }
+    };
+    Channel wChannel = binlogProvider.wrapChannel(channel);
+    ClientCall<String, Integer> clientCall = wChannel.newCall(method, CallOptions.DEFAULT);
+    final List<Integer> observedResponse = new ArrayList<>();
+    clientCall.start(
+        new NoopClientCall.NoopClientCallListener<Integer>() {
+          @Override
+          public void onMessage(Integer message) {
+            observedResponse.add(message);
+          }
+        },
+        new Metadata());
+
+    String expectedRequest = "hello world";
+    assertThat(binlogReq).isEmpty();
+    assertThat(serializedReq).isEmpty();
+    assertEquals(0, reqMarshaller.streamInvocations);
+    clientCall.sendMessage(expectedRequest);
+    // it is unacceptably expensive for the binlog to double parse every logged message
+    assertEquals(1, reqMarshaller.streamInvocations);
+    assertEquals(0, reqMarshaller.parseInvocations);
+    assertThat(binlogReq).hasSize(1);
+    assertThat(serializedReq).hasSize(1);
+    assertEquals(
+        expectedRequest,
+        StringMarshaller.INSTANCE.parse(new ByteArrayInputStream(binlogReq.get(0))));
+    assertEquals(
+        expectedRequest,
+        StringMarshaller.INSTANCE.parse(new ByteArrayInputStream(serializedReq.get(0))));
+
+    int expectedResponse = 12345;
+    assertThat(binlogResp).isEmpty();
+    assertThat(observedResponse).isEmpty();
+    assertEquals(0, respMarshaller.parseInvocations);
+    onClientMessageHelper(listener.get(), IntegerMarshaller.INSTANCE.stream(expectedResponse));
+    // it is unacceptably expensive for the binlog to double parse every logged message
+    assertEquals(1, respMarshaller.parseInvocations);
+    assertEquals(0, respMarshaller.streamInvocations);
+    assertThat(binlogResp).hasSize(1);
+    assertThat(observedResponse).hasSize(1);
+    assertEquals(
+        expectedResponse,
+        (int) IntegerMarshaller.INSTANCE.parse(new ByteArrayInputStream(binlogResp.get(0))));
+    assertEquals(expectedResponse, (int) observedResponse.get(0));
+  }
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  private static void onClientMessageHelper(ClientCall.Listener listener, Object request) {
+    listener.onMessage(request);
+  }
+
+  private void validateWrappedMethod(MethodDescriptor<?, ?> wMethod) {
+    assertSame(BinaryLogProvider.BYTEARRAY_MARSHALLER, wMethod.getRequestMarshaller());
+    assertSame(BinaryLogProvider.BYTEARRAY_MARSHALLER, wMethod.getResponseMarshaller());
+    assertEquals(method.getType(), wMethod.getType());
+    assertEquals(method.getFullMethodName(), wMethod.getFullMethodName());
+    assertEquals(method.getSchemaDescriptor(), wMethod.getSchemaDescriptor());
+    assertEquals(method.isIdempotent(), wMethod.isIdempotent());
+    assertEquals(method.isSafe(), wMethod.isSafe());
+    assertEquals(method.isSampledToLocalTracing(), wMethod.isSampledToLocalTracing());
+  }
+
+  @Test
+  public void wrapMethodDefinition_methodDescriptor() throws Exception {
+    ServerMethodDefinition<String, Integer> methodDef =
+        ServerMethodDefinition.create(
+            method,
+            new ServerCallHandler<String, Integer>() {
+              @Override
+              public Listener<String> startCall(
+                  ServerCall<String, Integer> call, Metadata headers) {
+                throw new UnsupportedOperationException();
+              }
+            });
+    ServerMethodDefinition<?, ?> wMethodDef = binlogProvider.wrapMethodDefinition(methodDef);
+    validateWrappedMethod(wMethodDef.getMethodDescriptor());
+  }
+
+  @Test
+  public void wrapMethodDefinition_handler() throws Exception {
+    // The request as seen by the user supplied server code
+    final List<String> observedRequest = new ArrayList<>();
+    final AtomicReference<ServerCall<String, Integer>> serverCall =
+        new AtomicReference<ServerCall<String, Integer>>();
+    ServerMethodDefinition<String, Integer> methodDef =
+        ServerMethodDefinition.create(
+            method,
+            new ServerCallHandler<String, Integer>() {
+              @Override
+              public ServerCall.Listener<String> startCall(
+                  ServerCall<String, Integer> call, Metadata headers) {
+                serverCall.set(call);
+                return new ServerCall.Listener<String>() {
+                  @Override
+                  public void onMessage(String message) {
+                    observedRequest.add(message);
+                  }
+                };
+              }
+            });
+    ServerMethodDefinition<?, ?> wDef = binlogProvider.wrapMethodDefinition(methodDef);
+    List<Object> serializedResp = new ArrayList<>();
+    ServerCall.Listener<?> wListener = startServerCallHelper(wDef, serializedResp);
+
+    String expectedRequest = "hello world";
+    assertThat(binlogReq).isEmpty();
+    assertThat(observedRequest).isEmpty();
+    assertEquals(0, reqMarshaller.parseInvocations);
+    onServerMessageHelper(wListener, StringMarshaller.INSTANCE.stream(expectedRequest));
+    // it is unacceptably expensive for the binlog to double parse every logged message
+    assertEquals(1, reqMarshaller.parseInvocations);
+    assertEquals(0, reqMarshaller.streamInvocations);
+    assertThat(binlogReq).hasSize(1);
+    assertThat(observedRequest).hasSize(1);
+    assertEquals(
+        expectedRequest,
+        StringMarshaller.INSTANCE.parse(new ByteArrayInputStream(binlogReq.get(0))));
+    assertEquals(expectedRequest, observedRequest.get(0));
+
+    int expectedResponse = 12345;
+    assertThat(binlogResp).isEmpty();
+    assertThat(serializedResp).isEmpty();
+    assertEquals(0, respMarshaller.streamInvocations);
+    serverCall.get().sendMessage(expectedResponse);
+    // it is unacceptably expensive for the binlog to double parse every logged message
+    assertEquals(0, respMarshaller.parseInvocations);
+    assertEquals(1, respMarshaller.streamInvocations);
+    assertThat(binlogResp).hasSize(1);
+    assertThat(serializedResp).hasSize(1);
+    assertEquals(
+        expectedResponse,
+        (int) IntegerMarshaller.INSTANCE.parse(new ByteArrayInputStream(binlogResp.get(0))));
+    assertEquals(expectedResponse,
+        (int) method.parseResponse(new ByteArrayInputStream((byte[]) serializedResp.get(0))));
+  }
+
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  private static void onServerMessageHelper(ServerCall.Listener listener, Object request) {
+    listener.onMessage(request);
+  }
+
+  private static <ReqT, RespT> ServerCall.Listener<ReqT> startServerCallHelper(
+      final ServerMethodDefinition<ReqT, RespT> methodDef,
+      final List<Object> serializedResp) {
+    ServerCall<ReqT, RespT> serverCall = new NoopServerCall<ReqT, RespT>() {
+      @Override
+      public void sendMessage(RespT message) {
+        serializedResp.add(message);
+      }
+
+      @Override
+      public MethodDescriptor<ReqT, RespT> getMethodDescriptor() {
+        return methodDef.getMethodDescriptor();
+      }
+    };
+    return methodDef.getServerCallHandler().startCall(serverCall, new Metadata());
+  }
+
+  private final class TestBinaryLogClientInterceptor implements ClientInterceptor {
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+        final MethodDescriptor<ReqT, RespT> method,
+        CallOptions callOptions,
+        Channel next) {
+      assertSame(BinaryLogProvider.BYTEARRAY_MARSHALLER, method.getRequestMarshaller());
+      assertSame(BinaryLogProvider.BYTEARRAY_MARSHALLER, method.getResponseMarshaller());
+      return new SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
+        @Override
+        public void start(Listener<RespT> responseListener, Metadata headers) {
+          super.start(
+              new SimpleForwardingClientCallListener<RespT>(responseListener) {
+                @Override
+                public void onMessage(RespT message) {
+                  assertTrue(message instanceof InputStream);
+                  try {
+                    byte[] bytes = IoUtils.toByteArray((InputStream) message);
+                    binlogResp.add(bytes);
+                    ByteArrayInputStream input = new ByteArrayInputStream(bytes);
+                    RespT dup = method.parseResponse(input);
+                    super.onMessage(dup);
+                  } catch (IOException e) {
+                    throw new RuntimeException(e);
+                  }
+                }
+              },
+              headers);
+        }
+
+        @Override
+        public void sendMessage(ReqT message) {
+            byte[] bytes = (byte[]) message;
+            binlogReq.add(bytes);
+            ByteArrayInputStream input = new ByteArrayInputStream(bytes);
+            ReqT dup = method.parseRequest(input);
+            super.sendMessage(dup);
+        }
+      };
+    }
+  }
+
+  private final class TestBinaryLogServerInterceptor implements ServerInterceptor {
+    @Override
+    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+        final ServerCall<ReqT, RespT> call,
+        Metadata headers,
+        ServerCallHandler<ReqT, RespT> next) {
+      assertSame(
+          BinaryLogProvider.BYTEARRAY_MARSHALLER,
+          call.getMethodDescriptor().getRequestMarshaller());
+      assertSame(
+          BinaryLogProvider.BYTEARRAY_MARSHALLER,
+          call.getMethodDescriptor().getResponseMarshaller());
+      ServerCall<ReqT, RespT> wCall = new SimpleForwardingServerCall<ReqT, RespT>(call) {
+        @Override
+        public void sendMessage(RespT message) {
+            byte[] bytes = (byte[]) message;
+            binlogResp.add(bytes);
+            ByteArrayInputStream input = new ByteArrayInputStream(bytes);
+            RespT dup = call.getMethodDescriptor().parseResponse(input);
+            super.sendMessage(dup);
+        }
+      };
+      final ServerCall.Listener<ReqT> oListener = next.startCall(wCall, headers);
+      return new SimpleForwardingServerCallListener<ReqT>(oListener) {
+        @Override
+        public void onMessage(ReqT message) {
+          assertTrue(message instanceof InputStream);
+          try {
+            byte[] bytes = IoUtils.toByteArray((InputStream) message);
+            binlogReq.add(bytes);
+            ByteArrayInputStream input = new ByteArrayInputStream(bytes);
+            ReqT dup = call.getMethodDescriptor().parseRequest(input);
+            super.onMessage(dup);
+          } catch (IOException e) {
+            throw new RuntimeException(e);
+          }
+        }
+      };
+    }
+  }
+
+  private abstract static class InvocationCountMarshaller<T>
+      implements MethodDescriptor.Marshaller<T> {
+    private int streamInvocations = 0;
+    private int parseInvocations = 0;
+
+    abstract MethodDescriptor.Marshaller<T> delegate();
+
+    @Override
+    public InputStream stream(T value) {
+      streamInvocations++;
+      return delegate().stream(value);
+    }
+
+    @Override
+    public T parse(InputStream stream) {
+      parseInvocations++;
+      return delegate().parse(stream);
+    }
+  }
+
+
+  private static class StringMarshaller implements MethodDescriptor.Marshaller<String> {
+    public static final StringMarshaller INSTANCE = new StringMarshaller();
+
+    @Override
+    public InputStream stream(String value) {
+      return new ByteArrayInputStream(value.getBytes(UTF_8));
+    }
+
+    @Override
+    public String parse(InputStream stream) {
+      try {
+        return new String(ByteStreams.toByteArray(stream), UTF_8);
+      } catch (IOException ex) {
+        throw new RuntimeException(ex);
+      }
+    }
+  }
+
+  private static class IntegerMarshaller implements MethodDescriptor.Marshaller<Integer> {
+    public static final IntegerMarshaller INSTANCE = new IntegerMarshaller();
+
+    @Override
+    public InputStream stream(Integer value) {
+      return StringMarshaller.INSTANCE.stream(value.toString());
+    }
+
+    @Override
+    public Integer parse(InputStream stream) {
+      return Integer.valueOf(StringMarshaller.INSTANCE.parse(stream));
+    }
+  }
+}
diff --git a/services/src/test/java/io/grpc/services/BinlogHelperTest.java b/services/src/test/java/io/grpc/services/BinlogHelperTest.java
new file mode 100644
index 0000000..cdaa836
--- /dev/null
+++ b/services/src/test/java/io/grpc/services/BinlogHelperTest.java
@@ -0,0 +1,1264 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.services.BinaryLogProvider.BYTEARRAY_MARSHALLER;
+import static io.grpc.services.BinlogHelper.DUMMY_SOCKET;
+import static io.grpc.services.BinlogHelper.STATUS_DETAILS_KEY;
+import static io.grpc.services.BinlogHelper.getPeerSocket;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import com.google.common.util.concurrent.SettableFuture;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.Duration;
+import com.google.protobuf.util.Durations;
+import io.grpc.Attributes;
+import io.grpc.BinaryLog.CallId;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.Context;
+import io.grpc.Deadline;
+import io.grpc.Grpc;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.MethodType;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.Status;
+import io.grpc.binarylog.v1alpha.GrpcLogEntry;
+import io.grpc.binarylog.v1alpha.Message;
+import io.grpc.binarylog.v1alpha.MetadataEntry;
+import io.grpc.binarylog.v1alpha.Peer;
+import io.grpc.binarylog.v1alpha.Peer.PeerType;
+import io.grpc.binarylog.v1alpha.Uint128;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.NoopClientCall;
+import io.grpc.internal.NoopServerCall;
+import io.grpc.services.BinlogHelper.FactoryImpl;
+import io.grpc.services.BinlogHelper.SinkWriter;
+import io.grpc.services.BinlogHelper.SinkWriterImpl;
+import io.netty.channel.unix.DomainSocketAddress;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.charset.Charset;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+/** Tests for {@link BinlogHelper}. */
+@RunWith(JUnit4.class)
+public final class BinlogHelperTest {
+  private static final Charset US_ASCII = Charset.forName("US-ASCII");
+  private static final BinlogHelper HEADER_FULL = new Builder().header(Integer.MAX_VALUE).build();
+  private static final BinlogHelper HEADER_256 = new Builder().header(256).build();
+  private static final BinlogHelper MSG_FULL = new Builder().msg(Integer.MAX_VALUE).build();
+  private static final BinlogHelper MSG_256 = new Builder().msg(256).build();
+  private static final BinlogHelper BOTH_256 = new Builder().header(256).msg(256).build();
+  private static final BinlogHelper BOTH_FULL =
+      new Builder().header(Integer.MAX_VALUE).msg(Integer.MAX_VALUE).build();
+
+  private static final String DATA_A = "aaaaaaaaa";
+  private static final String DATA_B = "bbbbbbbbb";
+  private static final String DATA_C = "ccccccccc";
+  private static final Metadata.Key<String> KEY_A =
+      Metadata.Key.of("a", Metadata.ASCII_STRING_MARSHALLER);
+  private static final Metadata.Key<String> KEY_B =
+      Metadata.Key.of("b", Metadata.ASCII_STRING_MARSHALLER);
+  private static final Metadata.Key<String> KEY_C =
+      Metadata.Key.of("c", Metadata.ASCII_STRING_MARSHALLER);
+  private static final MetadataEntry ENTRY_A =
+      MetadataEntry
+            .newBuilder()
+            .setKey(ByteString.copyFrom(KEY_A.name(), US_ASCII))
+            .setValue(ByteString.copyFrom(DATA_A.getBytes(US_ASCII)))
+            .build();
+  private static final MetadataEntry ENTRY_B =
+        MetadataEntry
+            .newBuilder()
+            .setKey(ByteString.copyFrom(KEY_B.name(), US_ASCII))
+            .setValue(ByteString.copyFrom(DATA_B.getBytes(US_ASCII)))
+            .build();
+  private static final MetadataEntry ENTRY_C =
+        MetadataEntry
+            .newBuilder()
+            .setKey(ByteString.copyFrom(KEY_C.name(), US_ASCII))
+            .setValue(ByteString.copyFrom(DATA_C.getBytes(US_ASCII)))
+            .build();
+  private static final boolean IS_SERVER = true;
+  private static final boolean IS_CLIENT = false;
+  private static final boolean IS_COMPRESSED = true;
+  private static final boolean IS_UNCOMPRESSED = false;
+  // TODO(zpencer): rename this to callId, since byte[] is mutable
+  private static final CallId CALL_ID =
+      new CallId(0x1112131415161718L, 0x19101a1b1c1d1e1fL);
+  private static final int HEADER_LIMIT = 10;
+  private static final int MESSAGE_LIMIT = Integer.MAX_VALUE;
+
+  private final Metadata nonEmptyMetadata = new Metadata();
+  private final BinaryLogSink sink = mock(BinaryLogSink.class);
+  private final SinkWriter sinkWriterImpl =
+      new SinkWriterImpl(sink, HEADER_LIMIT, MESSAGE_LIMIT);
+  private final SinkWriter mockSinkWriter = mock(SinkWriter.class);
+  private final byte[] message = new byte[100];
+  private SocketAddress peer;
+
+  @Before
+  public void setUp() throws Exception {
+    nonEmptyMetadata.put(KEY_A, DATA_A);
+    nonEmptyMetadata.put(KEY_B, DATA_B);
+    nonEmptyMetadata.put(KEY_C, DATA_C);
+    peer = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 1234);
+  }
+
+  @Test
+  public void configBinLog_global() throws Exception {
+    assertSameLimits(BOTH_FULL, makeLog("*", "p.s/m"));
+    assertSameLimits(BOTH_FULL, makeLog("*{h;m}", "p.s/m"));
+    assertSameLimits(HEADER_FULL, makeLog("*{h}", "p.s/m"));
+    assertSameLimits(MSG_FULL, makeLog("*{m}", "p.s/m"));
+    assertSameLimits(HEADER_256, makeLog("*{h:256}", "p.s/m"));
+    assertSameLimits(MSG_256, makeLog("*{m:256}", "p.s/m"));
+    assertSameLimits(BOTH_256, makeLog("*{h:256;m:256}", "p.s/m"));
+    assertSameLimits(
+        new Builder().header(Integer.MAX_VALUE).msg(256).build(),
+        makeLog("*{h;m:256}", "p.s/m"));
+    assertSameLimits(
+        new Builder().header(256).msg(Integer.MAX_VALUE).build(),
+        makeLog("*{h:256;m}", "p.s/m"));
+  }
+
+  @Test
+  public void configBinLog_method() throws Exception {
+    assertSameLimits(BOTH_FULL, makeLog("p.s/m", "p.s/m"));
+    assertSameLimits(BOTH_FULL, makeLog("p.s/m{h;m}", "p.s/m"));
+    assertSameLimits(HEADER_FULL, makeLog("p.s/m{h}", "p.s/m"));
+    assertSameLimits(MSG_FULL, makeLog("p.s/m{m}", "p.s/m"));
+    assertSameLimits(HEADER_256, makeLog("p.s/m{h:256}", "p.s/m"));
+    assertSameLimits(MSG_256, makeLog("p.s/m{m:256}", "p.s/m"));
+    assertSameLimits(BOTH_256, makeLog("p.s/m{h:256;m:256}", "p.s/m"));
+    assertSameLimits(
+        new Builder().header(Integer.MAX_VALUE).msg(256).build(),
+        makeLog("p.s/m{h;m:256}", "p.s/m"));
+    assertSameLimits(
+        new Builder().header(256).msg(Integer.MAX_VALUE).build(),
+        makeLog("p.s/m{h:256;m}", "p.s/m"));
+  }
+
+  @Test
+  public void configBinLog_method_absent() throws Exception {
+    assertNull(makeLog("p.s/m", "p.s/absent"));
+  }
+
+  @Test
+  public void configBinLog_service() throws Exception {
+    assertSameLimits(BOTH_FULL, makeLog("p.s/*", "p.s/m"));
+    assertSameLimits(BOTH_FULL, makeLog("p.s/*{h;m}", "p.s/m"));
+    assertSameLimits(HEADER_FULL, makeLog("p.s/*{h}", "p.s/m"));
+    assertSameLimits(MSG_FULL, makeLog("p.s/*{m}", "p.s/m"));
+    assertSameLimits(HEADER_256, makeLog("p.s/*{h:256}", "p.s/m"));
+    assertSameLimits(MSG_256, makeLog("p.s/*{m:256}", "p.s/m"));
+    assertSameLimits(BOTH_256, makeLog("p.s/*{h:256;m:256}", "p.s/m"));
+    assertSameLimits(
+        new Builder().header(Integer.MAX_VALUE).msg(256).build(),
+        makeLog("p.s/*{h;m:256}", "p.s/m"));
+    assertSameLimits(
+        new Builder().header(256).msg(Integer.MAX_VALUE).build(),
+        makeLog("p.s/*{h:256;m}", "p.s/m"));
+  }
+
+  @Test
+  public void configBinLog_service_absent() throws Exception {
+    assertNull(makeLog("p.s/*", "p.other/m"));
+  }
+
+  @Test
+  public void createLogFromOptionString() throws Exception {
+    assertSameLimits(BOTH_FULL, makeLog(null));
+    assertSameLimits(HEADER_FULL, makeLog("{h}"));
+    assertSameLimits(MSG_FULL, makeLog("{m}"));
+    assertSameLimits(HEADER_256, makeLog("{h:256}"));
+    assertSameLimits(MSG_256, makeLog("{m:256}"));
+    assertSameLimits(BOTH_256, makeLog("{h:256;m:256}"));
+    assertSameLimits(
+        new Builder().header(Integer.MAX_VALUE).msg(256).build(),
+        makeLog("{h;m:256}"));
+    assertSameLimits(
+        new Builder().header(256).msg(Integer.MAX_VALUE).build(),
+        makeLog("{h:256;m}"));
+  }
+
+  @Test
+  public void createLogFromOptionString_malformed() throws Exception {
+    assertNull(makeLog("bad"));
+    assertNull(makeLog("{bad}"));
+    assertNull(makeLog("{x;y}"));
+    assertNull(makeLog("{h:abc}"));
+    assertNull(makeLog("{2}"));
+    assertNull(makeLog("{2;2}"));
+    // The grammar specifies that if both h and m are present, h comes before m
+    assertNull(makeLog("{m:123;h:123}"));
+    // NumberFormatException
+    assertNull(makeLog("{h:99999999999999}"));
+  }
+
+  @Test
+  public void configBinLog_multiConfig_withGlobal() throws Exception {
+    String configStr =
+        "*{h},"
+        + "package.both256/*{h:256;m:256},"
+        + "package.service1/both128{h:128;m:128},"
+        + "package.service2/method_messageOnly{m}";
+    assertSameLimits(HEADER_FULL, makeLog(configStr, "otherpackage.service/method"));
+
+    assertSameLimits(BOTH_256, makeLog(configStr, "package.both256/method1"));
+    assertSameLimits(BOTH_256, makeLog(configStr, "package.both256/method2"));
+    assertSameLimits(BOTH_256, makeLog(configStr, "package.both256/method3"));
+
+    assertSameLimits(
+        new Builder().header(128).msg(128).build(), makeLog(configStr, "package.service1/both128"));
+    // the global config is in effect
+    assertSameLimits(HEADER_FULL, makeLog(configStr, "package.service1/absent"));
+
+    assertSameLimits(MSG_FULL, makeLog(configStr, "package.service2/method_messageOnly"));
+    // the global config is in effect
+    assertSameLimits(HEADER_FULL, makeLog(configStr, "package.service2/absent"));
+  }
+
+  @Test
+  public void configBinLog_multiConfig_noGlobal() throws Exception {
+    String configStr =
+        "package.both256/*{h:256;m:256},"
+        + "package.service1/both128{h:128;m:128},"
+        + "package.service2/method_messageOnly{m}";
+    assertNull(makeLog(configStr, "otherpackage.service/method"));
+
+    assertSameLimits(BOTH_256, makeLog(configStr, "package.both256/method1"));
+    assertSameLimits(BOTH_256, makeLog(configStr, "package.both256/method2"));
+    assertSameLimits(BOTH_256, makeLog(configStr, "package.both256/method3"));
+
+    assertSameLimits(
+        new Builder().header(128).msg(128).build(), makeLog(configStr, "package.service1/both128"));
+    // no global config in effect
+    assertNull(makeLog(configStr, "package.service1/absent"));
+
+    assertSameLimits(MSG_FULL, makeLog(configStr, "package.service2/method_messageOnly"));
+    // no global config in effect
+    assertNull(makeLog(configStr, "package.service2/absent"));
+  }
+
+  @Test
+  public void configBinLog_blacklist() {
+    assertNull(makeLog("*,-p.s/blacklisted", "p.s/blacklisted"));
+    assertNull(makeLog("-p.s/blacklisted,*", "p.s/blacklisted"));
+    assertNotNull(makeLog("-p.s/method,*", "p.s/allowed"));
+
+    assertNull(makeLog("p.s/*,-p.s/blacklisted", "p.s/blacklisted"));
+    assertNull(makeLog("-p.s/blacklisted,p.s/*", "p.s/blacklisted"));
+    assertNotNull(makeLog("-p.s/blacklisted,p.s/*", "p.s/allowed"));
+  }
+
+  @Test
+  public void configBinLog_ignoreDuplicates_global() throws Exception {
+    String configStr = "*{h},p.s/m,*{h:256}";
+    // The duplicate
+    assertSameLimits(HEADER_FULL, makeLog(configStr, "p.other1/m"));
+    assertSameLimits(HEADER_FULL, makeLog(configStr, "p.other2/m"));
+    // Other
+    assertSameLimits(BOTH_FULL, makeLog(configStr, "p.s/m"));
+  }
+
+  @Test
+  public void configBinLog_ignoreDuplicates_service() throws Exception {
+    String configStr = "p.s/*,*{h:256},p.s/*{h}";
+    // The duplicate
+    assertSameLimits(BOTH_FULL, makeLog(configStr, "p.s/m1"));
+    assertSameLimits(BOTH_FULL, makeLog(configStr, "p.s/m2"));
+    // Other
+    assertSameLimits(HEADER_256, makeLog(configStr, "p.other1/m"));
+    assertSameLimits(HEADER_256, makeLog(configStr, "p.other2/m"));
+  }
+
+  @Test
+  public void configBinLog_ignoreDuplicates_method() throws Exception {
+    String configStr = "p.s/m,*{h:256},p.s/m{h}";
+    // The duplicate
+    assertSameLimits(BOTH_FULL, makeLog(configStr, "p.s/m"));
+    // Other
+    assertSameLimits(HEADER_256, makeLog(configStr, "p.other1/m"));
+    assertSameLimits(HEADER_256, makeLog(configStr, "p.other2/m"));
+  }
+
+  @Test
+  public void callIdToProto() {
+    CallId callId = new CallId(0x1112131415161718L, 0x19101a1b1c1d1e1fL);
+    assertEquals(
+        Uint128
+            .newBuilder()
+            .setHigh(0x1112131415161718L)
+            .setLow(0x19101a1b1c1d1e1fL)
+            .build(),
+        BinlogHelper.callIdToProto(callId));
+  }
+
+  @Test
+  public void socketToProto_ipv4() throws Exception {
+    InetAddress address = InetAddress.getByName("127.0.0.1");
+    int port = 12345;
+    InetSocketAddress socketAddress = new InetSocketAddress(address, port);
+    assertEquals(
+        Peer
+            .newBuilder()
+            .setPeerType(Peer.PeerType.PEER_IPV4)
+            .setAddress("127.0.0.1")
+            .setIpPort(12345)
+            .build(),
+        BinlogHelper.socketToProto(socketAddress));
+  }
+
+  @Test
+  public void socketToProto_ipv6() throws Exception {
+    // this is a ipv6 link local address
+    InetAddress address = InetAddress.getByName("2001:db8:0:0:0:0:2:1");
+    int port = 12345;
+    InetSocketAddress socketAddress = new InetSocketAddress(address, port);
+    assertEquals(
+        Peer
+            .newBuilder()
+            .setPeerType(Peer.PeerType.PEER_IPV6)
+            .setAddress("2001:db8::2:1") // RFC 5952 section 4: ipv6 canonical form required
+            .setIpPort(12345)
+            .build(),
+        BinlogHelper.socketToProto(socketAddress));
+  }
+
+  @Test
+  public void socketToProto_unix() throws Exception {
+    String path = "/some/path";
+    DomainSocketAddress socketAddress = new DomainSocketAddress(path);
+    assertEquals(
+        Peer
+            .newBuilder()
+            .setPeerType(Peer.PeerType.PEER_UNIX)
+            .setAddress("/some/path")
+            .build(),
+        BinlogHelper.socketToProto(socketAddress)
+    );
+  }
+
+  @Test
+  public void socketToProto_unknown() throws Exception {
+    SocketAddress unknownSocket = new SocketAddress() {
+      @Override
+      public String toString() {
+        return "some-socket-address";
+      }
+    };
+    assertEquals(
+        Peer.newBuilder()
+            .setPeerType(PeerType.UNKNOWN_PEERTYPE)
+            .setAddress("some-socket-address")
+            .build(),
+        BinlogHelper.socketToProto(unknownSocket));
+  }
+
+  @Test
+  public void metadataToProto_empty() throws Exception {
+    assertEquals(
+        GrpcLogEntry.newBuilder()
+            .setMetadata(io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance())
+            .build(),
+        metadataToProtoTestHelper(new Metadata(), Integer.MAX_VALUE));
+  }
+
+  @Test
+  public void metadataToProto() throws Exception {
+    assertEquals(
+        GrpcLogEntry.newBuilder()
+            .setMetadata(
+                io.grpc.binarylog.v1alpha.Metadata
+                    .newBuilder()
+                    .addEntry(ENTRY_A)
+                    .addEntry(ENTRY_B)
+                    .addEntry(ENTRY_C)
+                    .build())
+            .build(),
+        metadataToProtoTestHelper(nonEmptyMetadata, Integer.MAX_VALUE));
+  }
+
+  @Test
+  public void metadataToProto_truncated() throws Exception {
+    // 0 byte limit not enough for any metadata
+    assertEquals(
+        GrpcLogEntry.newBuilder()
+            .setMetadata(io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance())
+            .setTruncated(true)
+            .build(),
+        metadataToProtoTestHelper(nonEmptyMetadata, 0));
+    // not enough bytes for first key value
+    assertEquals(
+        GrpcLogEntry.newBuilder()
+            .setMetadata(io.grpc.binarylog.v1alpha.Metadata.getDefaultInstance())
+            .setTruncated(true)
+            .build(),
+        metadataToProtoTestHelper(nonEmptyMetadata, 9));
+    // enough for first key value
+    assertEquals(
+        GrpcLogEntry.newBuilder()
+            .setMetadata(
+                io.grpc.binarylog.v1alpha.Metadata
+                    .newBuilder()
+                    .addEntry(ENTRY_A)
+                    .build())
+            .setTruncated(true)
+            .build(),
+        metadataToProtoTestHelper(nonEmptyMetadata, 10));
+    // Test edge cases for >= 2 key values
+    assertEquals(
+        GrpcLogEntry.newBuilder()
+            .setMetadata(
+                io.grpc.binarylog.v1alpha.Metadata
+                    .newBuilder()
+                    .addEntry(ENTRY_A)
+                    .build())
+            .setTruncated(true)
+            .build(),
+        metadataToProtoTestHelper(nonEmptyMetadata, 19));
+    assertEquals(
+        GrpcLogEntry.newBuilder()
+            .setMetadata(
+                io.grpc.binarylog.v1alpha.Metadata
+                    .newBuilder()
+                    .addEntry(ENTRY_A)
+                    .addEntry(ENTRY_B)
+                    .build())
+            .setTruncated(true)
+            .build(),
+        metadataToProtoTestHelper(nonEmptyMetadata, 20));
+    assertEquals(
+        GrpcLogEntry.newBuilder()
+            .setMetadata(
+                io.grpc.binarylog.v1alpha.Metadata
+                    .newBuilder()
+                    .addEntry(ENTRY_A)
+                    .addEntry(ENTRY_B)
+                    .build())
+            .setTruncated(true)
+            .build(),
+        metadataToProtoTestHelper(nonEmptyMetadata, 29));
+    // not truncated: enough for all keys
+    assertEquals(
+        GrpcLogEntry.newBuilder()
+            .setMetadata(
+                io.grpc.binarylog.v1alpha.Metadata
+                    .newBuilder()
+                    .addEntry(ENTRY_A)
+                    .addEntry(ENTRY_B)
+                    .addEntry(ENTRY_C)
+                    .build())
+            .build(),
+        metadataToProtoTestHelper(nonEmptyMetadata, 30));
+  }
+
+  @Test
+  public void messageToProto() throws Exception {
+    byte[] bytes
+        = "this is a long message: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".getBytes(US_ASCII);
+    assertEquals(
+        GrpcLogEntry.newBuilder()
+            .setMessage(
+                Message
+                    .newBuilder()
+                    .setData(ByteString.copyFrom(bytes))
+                    .setFlags(0)
+                    .setLength(bytes.length)
+                    .build())
+            .build(),
+        messageToProtoTestHelper(bytes, false, Integer.MAX_VALUE));
+  }
+
+  @Test
+  public void messageToProto_truncated() throws Exception {
+    byte[] bytes
+        = "this is a long message: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".getBytes(US_ASCII);
+    assertEquals(
+        GrpcLogEntry.newBuilder()
+            .setMessage(
+                Message
+                    .newBuilder()
+                    .setFlags(0)
+                    .setLength(bytes.length)
+                    .build())
+            .setTruncated(true)
+            .build(),
+        messageToProtoTestHelper(bytes, false, 0));
+
+    int limit = 10;
+    String truncatedMessage = "this is a ";
+    assertEquals(
+        GrpcLogEntry.newBuilder()
+            .setMessage(
+                Message
+                    .newBuilder()
+                    .setData(ByteString.copyFrom(truncatedMessage.getBytes(US_ASCII)))
+                    .setFlags(0)
+                    .setLength(bytes.length)
+                    .build())
+            .setTruncated(true)
+            .build(),
+        messageToProtoTestHelper(bytes, false, limit));
+  }
+
+  @Test
+  public void toFlag() throws Exception {
+    assertEquals(0, BinlogHelper.flagsForMessage(IS_UNCOMPRESSED));
+    assertEquals(1, BinlogHelper.flagsForMessage(IS_COMPRESSED));
+  }
+
+  @Test
+  public void logSendInitialMetadata_server() throws Exception {
+    sinkWriterImpl.logSendInitialMetadata(
+        /*seq=*/ 1,
+        /*methodName=*/ null,
+        /*timeout=*/ null,
+        nonEmptyMetadata,
+        IS_SERVER,
+        CALL_ID);
+    verify(sink).write(
+        metadataToProtoTestHelper(nonEmptyMetadata, 10).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.SEND_INITIAL_METADATA)
+            .setLogger(GrpcLogEntry.Logger.SERVER)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .build());
+  }
+
+  @Test
+  public void logSendInitialMetadata_client() throws Exception {
+    sinkWriterImpl.logSendInitialMetadata(
+        /*seq=*/ 1,
+        "service/method",
+        Durations.fromMillis(1234),
+        nonEmptyMetadata,
+        IS_CLIENT,
+        CALL_ID);
+    verify(sink).write(
+        metadataToProtoTestHelper(nonEmptyMetadata, 10).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setMethodName("/service/method")
+            .setTimeout(Durations.fromMillis(1234))
+            .setType(GrpcLogEntry.Type.SEND_INITIAL_METADATA)
+            .setLogger(GrpcLogEntry.Logger.CLIENT)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .build());
+
+    sinkWriterImpl.logSendInitialMetadata(
+        /*seq=*/ 1,
+        "service/method",
+        /*timeout=*/ null,
+        nonEmptyMetadata,
+        IS_CLIENT,
+        CALL_ID);
+    verify(sink).write(
+        metadataToProtoTestHelper(nonEmptyMetadata, 10).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setMethodName("/service/method")
+            .setType(GrpcLogEntry.Type.SEND_INITIAL_METADATA)
+            .setLogger(GrpcLogEntry.Logger.CLIENT)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .build());
+  }
+
+  @Test
+  public void logRecvInitialMetadata_server() throws Exception {
+    InetAddress address = InetAddress.getByName("127.0.0.1");
+    int port = 12345;
+    InetSocketAddress socketAddress = new InetSocketAddress(address, port);
+    sinkWriterImpl.logRecvInitialMetadata(
+        /*seq=*/ 1,
+        "service/method",
+        Durations.fromMillis(1234),
+        nonEmptyMetadata,
+        IS_SERVER,
+        CALL_ID,
+        socketAddress);
+    verify(sink).write(
+        metadataToProtoTestHelper(nonEmptyMetadata, 10).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setMethodName("/service/method")
+            .setTimeout(Durations.fromMillis(1234))
+            .setType(GrpcLogEntry.Type.RECV_INITIAL_METADATA)
+            .setLogger(GrpcLogEntry.Logger.SERVER)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .setPeer(BinlogHelper.socketToProto(socketAddress))
+            .build());
+
+    sinkWriterImpl.logRecvInitialMetadata(
+        /*seq=*/ 1,
+        "service/method",
+        /*timeout=*/ null,
+        nonEmptyMetadata,
+        IS_SERVER,
+        CALL_ID,
+        socketAddress);
+    verify(sink).write(
+        metadataToProtoTestHelper(nonEmptyMetadata, 10).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setMethodName("/service/method")
+            .setType(GrpcLogEntry.Type.RECV_INITIAL_METADATA)
+            .setLogger(GrpcLogEntry.Logger.SERVER)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .setPeer(BinlogHelper.socketToProto(socketAddress))
+            .build());
+  }
+
+  @Test
+  public void logRecvInitialMetadata_client() throws Exception {
+    InetAddress address = InetAddress.getByName("127.0.0.1");
+    int port = 12345;
+    InetSocketAddress socketAddress = new InetSocketAddress(address, port);
+    sinkWriterImpl.logRecvInitialMetadata(
+        /*seq=*/ 1,
+        /*methodName=*/ null,
+        /*timeout=*/ null,
+        nonEmptyMetadata,
+        IS_CLIENT,
+        CALL_ID,
+        socketAddress);
+    verify(sink).write(
+        metadataToProtoTestHelper(nonEmptyMetadata, 10).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.RECV_INITIAL_METADATA)
+            .setLogger(GrpcLogEntry.Logger.CLIENT)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .setPeer(BinlogHelper.socketToProto(socketAddress))
+            .build());
+  }
+
+  @Test
+  public void logTrailingMetadata_server() throws Exception {
+    sinkWriterImpl.logTrailingMetadata(/*seq=*/ 1, Status.OK, nonEmptyMetadata, IS_SERVER, CALL_ID);
+    verify(sink).write(
+        metadataToProtoTestHelper(nonEmptyMetadata, 10).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.SEND_TRAILING_METADATA)
+            .setLogger(GrpcLogEntry.Logger.SERVER)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .build());
+
+    Metadata detailedStatus = new Metadata();
+    byte[] statusBytes = new byte[] {1, 2, 3, 4};
+    detailedStatus.merge(nonEmptyMetadata);
+    detailedStatus.put(STATUS_DETAILS_KEY, statusBytes);
+    sinkWriterImpl.logTrailingMetadata(
+        /*seq=*/ 1,
+        Status.INTERNAL.withDescription("description"),
+        detailedStatus,
+        IS_SERVER,
+        CALL_ID);
+    verify(sink).write(
+        metadataToProtoTestHelper(detailedStatus, 10).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.SEND_TRAILING_METADATA)
+            .setLogger(GrpcLogEntry.Logger.SERVER)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .setStatusCode(13)
+            .setStatusMessage("description")
+            .setStatusDetails(ByteString.copyFrom(statusBytes))
+            .build());
+  }
+
+  @Test
+  public void logTrailingMetadata_client() throws Exception {
+    sinkWriterImpl.logTrailingMetadata(/*seq=*/ 1, Status.OK, nonEmptyMetadata, IS_CLIENT, CALL_ID);
+    verify(sink).write(
+        metadataToProtoTestHelper(nonEmptyMetadata, 10).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.RECV_TRAILING_METADATA)
+            .setLogger(GrpcLogEntry.Logger.CLIENT)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .build());
+
+    Metadata detailedStatus = new Metadata();
+    byte[] statusBytes = new byte[] {1, 2, 3, 4};
+    detailedStatus.merge(nonEmptyMetadata);
+    detailedStatus.put(STATUS_DETAILS_KEY, statusBytes);
+    sinkWriterImpl.logTrailingMetadata(
+        /*seq=*/ 1,
+        Status.INTERNAL.withDescription("description"),
+        detailedStatus,
+        IS_CLIENT,
+        CALL_ID);
+    verify(sink).write(
+        metadataToProtoTestHelper(detailedStatus, 10).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.RECV_TRAILING_METADATA)
+            .setLogger(GrpcLogEntry.Logger.CLIENT)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .setStatusCode(13)
+            .setStatusMessage("description")
+            .setStatusDetails(ByteString.copyFrom(statusBytes))
+            .build());
+
+  }
+
+  @Test
+  public void logOutboundMessage_server() throws Exception {
+    sinkWriterImpl.logOutboundMessage(
+        /*seq=*/ 1, BYTEARRAY_MARSHALLER, message, IS_COMPRESSED, IS_SERVER, CALL_ID);
+    verify(sink).write(
+        messageToProtoTestHelper(message, IS_COMPRESSED, MESSAGE_LIMIT).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.SEND_MESSAGE)
+            .setLogger(GrpcLogEntry.Logger.SERVER)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .build());
+
+    sinkWriterImpl.logOutboundMessage(
+        /*seq=*/ 1, BYTEARRAY_MARSHALLER, message, IS_UNCOMPRESSED, IS_SERVER, CALL_ID);
+    verify(sink).write(
+        messageToProtoTestHelper(message, IS_UNCOMPRESSED, MESSAGE_LIMIT).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.SEND_MESSAGE)
+            .setLogger(GrpcLogEntry.Logger.SERVER)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .build());
+    verifyNoMoreInteractions(sink);
+  }
+
+  @Test
+  public void logOutboundMessage_client() throws Exception {
+    sinkWriterImpl.logOutboundMessage(
+        /*seq=*/ 1, BYTEARRAY_MARSHALLER, message, IS_COMPRESSED, IS_CLIENT, CALL_ID);
+    verify(sink).write(
+        messageToProtoTestHelper(message, IS_COMPRESSED, MESSAGE_LIMIT).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.SEND_MESSAGE)
+            .setLogger(GrpcLogEntry.Logger.CLIENT)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .build());
+
+    sinkWriterImpl.logOutboundMessage(
+        /*seq=*/ 1, BYTEARRAY_MARSHALLER, message, IS_UNCOMPRESSED, IS_CLIENT, CALL_ID);
+    verify(sink).write(
+        messageToProtoTestHelper(message, IS_UNCOMPRESSED, MESSAGE_LIMIT).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.SEND_MESSAGE)
+            .setLogger(GrpcLogEntry.Logger.CLIENT)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .build());
+    verifyNoMoreInteractions(sink);
+  }
+
+  @Test
+  public void logInboundMessage_server() throws Exception {
+    sinkWriterImpl.logInboundMessage(
+        /*seq=*/ 1, BYTEARRAY_MARSHALLER, message, IS_COMPRESSED, IS_SERVER, CALL_ID);
+    verify(sink).write(
+        messageToProtoTestHelper(message, IS_COMPRESSED, MESSAGE_LIMIT).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.RECV_MESSAGE)
+            .setLogger(GrpcLogEntry.Logger.SERVER)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .build());
+
+    sinkWriterImpl.logInboundMessage(
+        /*seq=*/ 1, BYTEARRAY_MARSHALLER, message, IS_UNCOMPRESSED, IS_SERVER, CALL_ID);
+    verify(sink).write(
+        messageToProtoTestHelper(message, IS_UNCOMPRESSED, MESSAGE_LIMIT).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.RECV_MESSAGE)
+            .setLogger(GrpcLogEntry.Logger.SERVER)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .build());
+    verifyNoMoreInteractions(sink);
+  }
+
+  @Test
+  public void logInboundMessage_client() throws Exception {
+    sinkWriterImpl.logInboundMessage(
+        /*seq=*/ 1, BYTEARRAY_MARSHALLER, message, IS_COMPRESSED, IS_CLIENT, CALL_ID);
+    verify(sink).write(
+        messageToProtoTestHelper(message, IS_COMPRESSED, MESSAGE_LIMIT).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.RECV_MESSAGE)
+            .setLogger(GrpcLogEntry.Logger.CLIENT)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .build());
+
+    sinkWriterImpl.logInboundMessage(
+        /*seq=*/ 1, BYTEARRAY_MARSHALLER, message, IS_UNCOMPRESSED, IS_CLIENT, CALL_ID);
+    verify(sink).write(
+        messageToProtoTestHelper(message, IS_UNCOMPRESSED, MESSAGE_LIMIT).toBuilder()
+            .setSequenceIdWithinCall(1)
+            .setType(GrpcLogEntry.Type.RECV_MESSAGE)
+            .setLogger(GrpcLogEntry.Logger.CLIENT)
+            .setCallId(BinlogHelper.callIdToProto(CALL_ID))
+            .build());
+    verifyNoMoreInteractions(sink);
+  }
+
+  @Test
+  public void getPeerSocketTest() {
+    assertSame(DUMMY_SOCKET, getPeerSocket(Attributes.EMPTY));
+    assertSame(
+        peer,
+        getPeerSocket(Attributes.newBuilder().set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, peer).build()));
+  }
+
+  @Test
+  @SuppressWarnings({"unchecked"})
+  public void clientDeadlineLogged_deadlineSetViaCallOption() {
+    MethodDescriptor<byte[], byte[]> method =
+        MethodDescriptor.<byte[], byte[]>newBuilder()
+            .setType(MethodType.UNKNOWN)
+            .setFullMethodName("service/method")
+            .setRequestMarshaller(BYTEARRAY_MARSHALLER)
+            .setResponseMarshaller(BYTEARRAY_MARSHALLER)
+            .build();
+    ClientCall.Listener<byte[]> mockListener = mock(ClientCall.Listener.class);
+
+    ClientCall<byte[], byte[]> call =
+        new BinlogHelper(mockSinkWriter)
+            .getClientInterceptor(CALL_ID)
+            .interceptCall(
+                method,
+                CallOptions.DEFAULT.withDeadlineAfter(1, TimeUnit.SECONDS),
+                new Channel() {
+                  @Override
+                  public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
+                      MethodDescriptor<RequestT, ResponseT> methodDescriptor,
+                      CallOptions callOptions) {
+                    return new NoopClientCall<RequestT, ResponseT>();
+                  }
+
+                  @Override
+                  public String authority() {
+                    return null;
+                  }
+                });
+    call.start(mockListener, new Metadata());
+    ArgumentCaptor<Duration> callOptTimeoutCaptor = ArgumentCaptor.forClass(Duration.class);
+    verify(mockSinkWriter).logSendInitialMetadata(
+        any(Integer.class),
+        any(String.class),
+        callOptTimeoutCaptor.capture(),
+        any(Metadata.class),
+        any(Boolean.class),
+        any(CallId.class));
+    Duration timeout = callOptTimeoutCaptor.getValue();
+    assertThat(TimeUnit.SECONDS.toNanos(1) - Durations.toNanos(timeout))
+        .isAtMost(TimeUnit.MILLISECONDS.toNanos(250));
+  }
+
+  @Test
+  @SuppressWarnings({"unchecked"})
+  public void clientDeadlineLogged_deadlineSetViaContext() throws Exception {
+    // important: deadline is read from the ctx where call was created
+    final SettableFuture<ClientCall<byte[], byte[]>> callFuture = SettableFuture.create();
+    Context.current()
+        .withDeadline(
+            Deadline.after(1, TimeUnit.SECONDS), Executors.newSingleThreadScheduledExecutor())
+        .run(new Runnable() {
+          @Override
+          public void run() {
+            MethodDescriptor<byte[], byte[]> method =
+                MethodDescriptor.<byte[], byte[]>newBuilder()
+                    .setType(MethodType.UNKNOWN)
+                    .setFullMethodName("service/method")
+                    .setRequestMarshaller(BYTEARRAY_MARSHALLER)
+                    .setResponseMarshaller(BYTEARRAY_MARSHALLER)
+                    .build();
+
+            callFuture.set(new BinlogHelper(mockSinkWriter)
+                .getClientInterceptor(CALL_ID)
+                .interceptCall(
+                    method,
+                    CallOptions.DEFAULT.withDeadlineAfter(1, TimeUnit.SECONDS),
+                    new Channel() {
+                      @Override
+                      public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
+                          MethodDescriptor<RequestT, ResponseT> methodDescriptor,
+                          CallOptions callOptions) {
+                        return new NoopClientCall<RequestT, ResponseT>();
+                      }
+
+                      @Override
+                      public String authority() {
+                        return null;
+                      }
+                    }));
+          }
+        });
+    ClientCall.Listener<byte[]> mockListener = mock(ClientCall.Listener.class);
+    callFuture.get().start(mockListener, new Metadata());
+    ArgumentCaptor<Duration> callOptTimeoutCaptor = ArgumentCaptor.forClass(Duration.class);
+    verify(mockSinkWriter).logSendInitialMetadata(
+        any(Integer.class),
+        any(String.class),
+        callOptTimeoutCaptor.capture(),
+        any(Metadata.class),
+        any(Boolean.class),
+        any(CallId.class));
+    Duration timeout = callOptTimeoutCaptor.getValue();
+    assertThat(TimeUnit.SECONDS.toNanos(1) - Durations.toNanos(timeout))
+        .isAtMost(TimeUnit.MILLISECONDS.toNanos(250));
+  }
+
+  @Test
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  public void clientInterceptor() throws Exception {
+    final AtomicReference<ClientCall.Listener> interceptedListener =
+        new AtomicReference<ClientCall.Listener>();
+    // capture these manually because ClientCall can not be mocked
+    final AtomicReference<Metadata> actualClientInitial = new AtomicReference<Metadata>();
+    final AtomicReference<Object> actualRequest = new AtomicReference<Object>();
+
+    Channel channel = new Channel() {
+      @Override
+      public <RequestT, ResponseT> ClientCall<RequestT, ResponseT> newCall(
+          MethodDescriptor<RequestT, ResponseT> methodDescriptor, CallOptions callOptions) {
+        return new NoopClientCall<RequestT, ResponseT>() {
+          @Override
+          public void start(Listener<ResponseT> responseListener, Metadata headers) {
+            interceptedListener.set(responseListener);
+            actualClientInitial.set(headers);
+          }
+
+          @Override
+          public void sendMessage(RequestT message) {
+            actualRequest.set(message);
+          }
+
+          @Override
+          public Attributes getAttributes() {
+            return Attributes.newBuilder().set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, peer).build();
+          }
+        };
+      }
+
+      @Override
+      public String authority() {
+        throw new UnsupportedOperationException();
+      }
+    };
+
+    ClientCall.Listener<byte[]> mockListener = mock(ClientCall.Listener.class);
+
+    MethodDescriptor<byte[], byte[]> method =
+        MethodDescriptor.<byte[], byte[]>newBuilder()
+            .setType(MethodType.UNKNOWN)
+            .setFullMethodName("service/method")
+            .setRequestMarshaller(BYTEARRAY_MARSHALLER)
+            .setResponseMarshaller(BYTEARRAY_MARSHALLER)
+            .build();
+    ClientCall<byte[], byte[]> interceptedCall =
+        new BinlogHelper(mockSinkWriter)
+            .getClientInterceptor(CALL_ID)
+            .interceptCall(
+                method,
+                CallOptions.DEFAULT.withDeadlineAfter(1, TimeUnit.SECONDS),
+                channel);
+
+    // send initial metadata
+    {
+      Metadata clientInitial = new Metadata();
+      interceptedCall.start(mockListener, clientInitial);
+      ArgumentCaptor<Duration> timeoutCaptor = ArgumentCaptor.forClass(Duration.class);
+      verify(mockSinkWriter).logSendInitialMetadata(
+          /*seq=*/ eq(1),
+          eq("service/method"),
+          timeoutCaptor.capture(),
+          same(clientInitial),
+          eq(IS_CLIENT),
+          same(CALL_ID));
+      verifyNoMoreInteractions(mockSinkWriter);
+      Duration timeout = timeoutCaptor.getValue();
+      assertThat(TimeUnit.SECONDS.toNanos(1) - Durations.toNanos(timeout))
+          .isAtMost(TimeUnit.MILLISECONDS.toNanos(250));
+      assertSame(clientInitial, actualClientInitial.get());
+    }
+
+    // receive initial metadata
+    {
+      Metadata serverInitial = new Metadata();
+      interceptedListener.get().onHeaders(serverInitial);
+      verify(mockSinkWriter).logRecvInitialMetadata(
+          /*seq=*/ eq(2),
+          isNull(String.class),
+          isNull(Duration.class),
+          same(serverInitial),
+          eq(IS_CLIENT),
+          same(CALL_ID),
+          same(peer));
+      verifyNoMoreInteractions(mockSinkWriter);
+      verify(mockListener).onHeaders(same(serverInitial));
+    }
+
+    // send request
+    {
+      byte[] request = "this is a request".getBytes(US_ASCII);
+      interceptedCall.sendMessage(request);
+      verify(mockSinkWriter).logOutboundMessage(
+          /*seq=*/ eq(3),
+          same(BYTEARRAY_MARSHALLER),
+          same(request),
+          eq(BinlogHelper.DUMMY_IS_COMPRESSED),
+          eq(IS_CLIENT),
+          same(CALL_ID));
+      verifyNoMoreInteractions(mockSinkWriter);
+      assertSame(request, actualRequest.get());
+    }
+
+    // receive response
+    {
+      byte[] response = "this is a response".getBytes(US_ASCII);
+      interceptedListener.get().onMessage(response);
+      verify(mockSinkWriter).logInboundMessage(
+          /*seq=*/ eq(4),
+          same(BYTEARRAY_MARSHALLER),
+          eq(response),
+          eq(BinlogHelper.DUMMY_IS_COMPRESSED),
+          eq(IS_CLIENT),
+          same(CALL_ID));
+      verifyNoMoreInteractions(mockSinkWriter);
+      verify(mockListener).onMessage(same(response));
+    }
+
+    // receive trailers
+    {
+      Status status = Status.INTERNAL.withDescription("some description");
+      Metadata trailers = new Metadata();
+
+      interceptedListener.get().onClose(status, trailers);
+      verify(mockSinkWriter).logTrailingMetadata(
+          /*seq=*/ eq(5),
+          same(status),
+          same(trailers),
+          eq(IS_CLIENT),
+          same(CALL_ID));
+      verifyNoMoreInteractions(mockSinkWriter);
+      verify(mockListener).onClose(same(status), same(trailers));
+    }
+  }
+
+  @Test
+  @SuppressWarnings({"rawtypes", "unchecked"})
+  public void serverInterceptor() throws Exception {
+    final AtomicReference<ServerCall> interceptedCall =
+        new AtomicReference<ServerCall>();
+    ServerCall.Listener<byte[]> capturedListener;
+    final ServerCall.Listener mockListener = mock(ServerCall.Listener.class);
+    // capture these manually because ServerCall can not be mocked
+    final AtomicReference<Metadata> actualServerInitial = new AtomicReference<Metadata>();
+    final AtomicReference<byte[]> actualResponse = new AtomicReference<byte[]>();
+    final AtomicReference<Status> actualStatus = new AtomicReference<Status>();
+    final AtomicReference<Metadata> actualTrailers = new AtomicReference<Metadata>();
+
+    // begin call and receive initial metadata
+    {
+      Metadata clientInitial = new Metadata();
+      clientInitial.put(GrpcUtil.TIMEOUT_KEY, TimeUnit.MILLISECONDS.toNanos(1234));
+      final MethodDescriptor<byte[], byte[]> method =
+          MethodDescriptor.<byte[], byte[]>newBuilder()
+              .setType(MethodType.UNKNOWN)
+              .setFullMethodName("service/method")
+              .setRequestMarshaller(BYTEARRAY_MARSHALLER)
+              .setResponseMarshaller(BYTEARRAY_MARSHALLER)
+              .build();
+      capturedListener =
+          new BinlogHelper(mockSinkWriter)
+              .getServerInterceptor(CALL_ID)
+              .interceptCall(
+                  new NoopServerCall<byte[], byte[]>() {
+                    @Override
+                    public void sendHeaders(Metadata headers) {
+                      actualServerInitial.set(headers);
+                    }
+
+                    @Override
+                    public void sendMessage(byte[] message) {
+                      actualResponse.set(message);
+                    }
+
+                    @Override
+                    public void close(Status status, Metadata trailers) {
+                      actualStatus.set(status);
+                      actualTrailers.set(trailers);
+                    }
+
+                    @Override
+                    public MethodDescriptor<byte[], byte[]> getMethodDescriptor() {
+                      return method;
+                    }
+
+                    @Override
+                    public Attributes getAttributes() {
+                      return Attributes
+                          .newBuilder()
+                          .set(Grpc.TRANSPORT_ATTR_REMOTE_ADDR, peer)
+                          .build();
+                    }
+                  },
+                  clientInitial,
+                  new ServerCallHandler<byte[], byte[]>() {
+                    @Override
+                    public ServerCall.Listener<byte[]> startCall(
+                        ServerCall<byte[], byte[]> call,
+                        Metadata headers) {
+                      interceptedCall.set(call);
+                      return mockListener;
+                    }
+                  });
+      verify(mockSinkWriter).logRecvInitialMetadata(
+          /*seq=*/ eq(1),
+          eq("service/method"),
+          eq(Durations.fromMillis(1234)),
+          same(clientInitial),
+          eq(IS_SERVER),
+          same(CALL_ID),
+          same(peer));
+      verifyNoMoreInteractions(mockSinkWriter);
+    }
+
+    // send initial metadata
+    {
+      Metadata serverInital = new Metadata();
+      interceptedCall.get().sendHeaders(serverInital);
+      verify(mockSinkWriter).logSendInitialMetadata(
+          /*seq=*/ eq(2),
+          isNull(String.class),
+          isNull(Duration.class),
+          same(serverInital),
+          eq(IS_SERVER),
+          same(CALL_ID));
+      verifyNoMoreInteractions(mockSinkWriter);
+      assertSame(serverInital, actualServerInitial.get());
+    }
+
+    // receive request
+    {
+      byte[] request = "this is a request".getBytes(US_ASCII);
+      capturedListener.onMessage(request);
+      verify(mockSinkWriter).logInboundMessage(
+          /*seq=*/ eq(3),
+          same(BYTEARRAY_MARSHALLER),
+          same(request),
+          eq(BinlogHelper.DUMMY_IS_COMPRESSED),
+          eq(IS_SERVER),
+          same(CALL_ID));
+      verifyNoMoreInteractions(mockSinkWriter);
+      verify(mockListener).onMessage(same(request));
+    }
+
+    // send response
+    {
+      byte[] response = "this is a response".getBytes(US_ASCII);
+      interceptedCall.get().sendMessage(response);
+      verify(mockSinkWriter).logOutboundMessage(
+          /*seq=*/ eq(4),
+          same(BYTEARRAY_MARSHALLER),
+          same(response),
+          eq(BinlogHelper.DUMMY_IS_COMPRESSED),
+          eq(IS_SERVER),
+          same(CALL_ID));
+      verifyNoMoreInteractions(mockSinkWriter);
+      assertSame(response, actualResponse.get());
+    }
+
+    // send trailers
+    {
+      Status status = Status.INTERNAL.withDescription("some description");
+      Metadata trailers = new Metadata();
+      interceptedCall.get().close(status, trailers);
+      verify(mockSinkWriter).logTrailingMetadata(
+          /*seq=*/ eq(5),
+          same(status),
+          same(trailers),
+          eq(IS_SERVER),
+          same(CALL_ID));
+      verifyNoMoreInteractions(mockSinkWriter);
+      assertSame(status, actualStatus.get());
+      assertSame(trailers, actualTrailers.get());
+    }
+  }
+
+  /** A builder class to make unit test code more readable. */
+  private static final class Builder {
+    int maxHeaderBytes = 0;
+    int maxMessageBytes = 0;
+
+    Builder header(int bytes) {
+      maxHeaderBytes = bytes;
+      return this;
+    }
+
+    Builder msg(int bytes) {
+      maxMessageBytes = bytes;
+      return this;
+    }
+
+    BinlogHelper build() {
+      return new BinlogHelper(
+          new SinkWriterImpl(mock(BinaryLogSink.class), maxHeaderBytes, maxMessageBytes));
+    }
+  }
+
+  private static void assertSameLimits(BinlogHelper a, BinlogHelper b) {
+    assertEquals(a.writer.getMaxMessageBytes(), b.writer.getMaxMessageBytes());
+    assertEquals(a.writer.getMaxHeaderBytes(), b.writer.getMaxHeaderBytes());
+  }
+
+  private BinlogHelper makeLog(String factoryConfigStr, String lookup) {
+    return new BinlogHelper.FactoryImpl(sink, factoryConfigStr).getLog(lookup);
+  }
+
+  private BinlogHelper makeLog(String logConfigStr) {
+    return FactoryImpl.createBinaryLog(sink, logConfigStr);
+  }
+
+  private static GrpcLogEntry metadataToProtoTestHelper(
+      Metadata metadata, int maxHeaderBytes) {
+    GrpcLogEntry.Builder builder = GrpcLogEntry.newBuilder();
+    BinlogHelper.addMetadataToProto(builder, metadata, maxHeaderBytes);
+    return builder.build();
+  }
+
+  private static GrpcLogEntry messageToProtoTestHelper(
+      byte[] message, boolean compressed, int maxMessageBytes) {
+    GrpcLogEntry.Builder builder = GrpcLogEntry.newBuilder();
+    BinlogHelper.messageToProto(builder, message, compressed, maxMessageBytes);
+    return builder.build();
+  }
+}
diff --git a/services/src/test/java/io/grpc/services/ChannelzProtoUtilTest.java b/services/src/test/java/io/grpc/services/ChannelzProtoUtilTest.java
new file mode 100644
index 0000000..03890fb
--- /dev/null
+++ b/services/src/test/java/io/grpc/services/ChannelzProtoUtilTest.java
@@ -0,0 +1,949 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.grpc.InternalChannelz.id;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.protobuf.Any;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.Int64Value;
+import com.google.protobuf.Message;
+import com.google.protobuf.util.Durations;
+import com.google.protobuf.util.Timestamps;
+import io.grpc.ConnectivityState;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.ChannelStats;
+import io.grpc.InternalChannelz.ChannelTrace.Event;
+import io.grpc.InternalChannelz.ChannelTrace.Event.Severity;
+import io.grpc.InternalChannelz.RootChannelList;
+import io.grpc.InternalChannelz.ServerList;
+import io.grpc.InternalChannelz.ServerSocketsList;
+import io.grpc.InternalChannelz.ServerStats;
+import io.grpc.InternalChannelz.SocketOptions;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.InternalWithLogId;
+import io.grpc.channelz.v1.Address;
+import io.grpc.channelz.v1.Address.OtherAddress;
+import io.grpc.channelz.v1.Address.TcpIpAddress;
+import io.grpc.channelz.v1.Address.UdsAddress;
+import io.grpc.channelz.v1.Channel;
+import io.grpc.channelz.v1.ChannelConnectivityState;
+import io.grpc.channelz.v1.ChannelConnectivityState.State;
+import io.grpc.channelz.v1.ChannelData;
+import io.grpc.channelz.v1.ChannelRef;
+import io.grpc.channelz.v1.ChannelTrace;
+import io.grpc.channelz.v1.ChannelTraceEvent;
+import io.grpc.channelz.v1.GetChannelRequest;
+import io.grpc.channelz.v1.GetServerSocketsResponse;
+import io.grpc.channelz.v1.GetServersResponse;
+import io.grpc.channelz.v1.GetTopChannelsResponse;
+import io.grpc.channelz.v1.Security;
+import io.grpc.channelz.v1.Security.OtherSecurity;
+import io.grpc.channelz.v1.Security.Tls;
+import io.grpc.channelz.v1.Server;
+import io.grpc.channelz.v1.ServerData;
+import io.grpc.channelz.v1.ServerRef;
+import io.grpc.channelz.v1.Socket;
+import io.grpc.channelz.v1.SocketData;
+import io.grpc.channelz.v1.SocketOption;
+import io.grpc.channelz.v1.SocketOptionLinger;
+import io.grpc.channelz.v1.SocketOptionTcpInfo;
+import io.grpc.channelz.v1.SocketOptionTimeout;
+import io.grpc.channelz.v1.SocketRef;
+import io.grpc.channelz.v1.Subchannel;
+import io.grpc.channelz.v1.SubchannelRef;
+import io.grpc.services.ChannelzTestHelper.TestChannel;
+import io.grpc.services.ChannelzTestHelper.TestListenSocket;
+import io.grpc.services.ChannelzTestHelper.TestServer;
+import io.grpc.services.ChannelzTestHelper.TestSocket;
+import io.netty.channel.unix.DomainSocketAddress;
+import java.net.Inet4Address;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.security.cert.Certificate;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map.Entry;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ChannelzProtoUtilTest {
+
+  private final TestChannel channel = new TestChannel();
+  private final ChannelRef channelRef = ChannelRef
+      .newBuilder()
+      .setName(channel.toString())
+      .setChannelId(channel.getLogId().getId())
+      .build();
+  private final ChannelData channelData = ChannelData
+      .newBuilder()
+      .setTarget("sometarget")
+      .setState(ChannelConnectivityState.newBuilder().setState(State.READY))
+      .setCallsStarted(1)
+      .setCallsSucceeded(2)
+      .setCallsFailed(3)
+      .setLastCallStartedTimestamp(Timestamps.fromNanos(4))
+      .build();
+  private final Channel channelProto = Channel
+      .newBuilder()
+      .setRef(channelRef)
+      .setData(channelData)
+      .build();
+
+  private final TestChannel subchannel = new TestChannel();
+  private final SubchannelRef subchannelRef = SubchannelRef
+      .newBuilder()
+      .setName(subchannel.toString())
+      .setSubchannelId(subchannel.getLogId().getId())
+      .build();
+  private final ChannelData subchannelData = ChannelData
+      .newBuilder()
+      .setTarget("sometarget")
+      .setState(ChannelConnectivityState.newBuilder().setState(State.READY))
+      .setCallsStarted(1)
+      .setCallsSucceeded(2)
+      .setCallsFailed(3)
+      .setLastCallStartedTimestamp(Timestamps.fromNanos(4))
+      .build();
+  private final Subchannel subchannelProto = Subchannel
+        .newBuilder()
+        .setRef(subchannelRef)
+        .setData(subchannelData)
+        .build();
+
+  private final TestServer server = new TestServer();
+  private final ServerRef serverRef = ServerRef
+      .newBuilder()
+      .setName(server.toString())
+      .setServerId(server.getLogId().getId())
+      .build();
+  private final ServerData serverData = ServerData
+      .newBuilder()
+      .setCallsStarted(1)
+      .setCallsSucceeded(2)
+      .setCallsFailed(3)
+      .setLastCallStartedTimestamp(Timestamps.fromNanos(4))
+      .build();
+  private final Server serverProto = Server
+      .newBuilder()
+      .setRef(serverRef)
+      .setData(serverData)
+      .build();
+
+  private final SocketOption sockOptLingerDisabled = SocketOption
+      .newBuilder()
+      .setName("SO_LINGER")
+      .setAdditional(
+          Any.pack(SocketOptionLinger.getDefaultInstance()))
+      .build();
+
+  private final SocketOption sockOptlinger10s = SocketOption
+      .newBuilder()
+      .setName("SO_LINGER")
+      .setAdditional(
+          Any.pack(SocketOptionLinger
+              .newBuilder()
+              .setActive(true)
+              .setDuration(Durations.fromSeconds(10))
+              .build()))
+      .build();
+
+  private final SocketOption sockOptTimeout200ms = SocketOption
+      .newBuilder()
+      .setName("SO_TIMEOUT")
+      .setAdditional(
+          Any.pack(SocketOptionTimeout
+          .newBuilder()
+          .setDuration(Durations.fromMillis(200))
+          .build())
+      ).build();
+
+  private final SocketOption sockOptAdditional = SocketOption
+      .newBuilder()
+      .setName("SO_MADE_UP_OPTION")
+      .setValue("some-made-up-value")
+      .build();
+
+  private final InternalChannelz.TcpInfo channelzTcpInfo
+      = new InternalChannelz.TcpInfo.Builder()
+      .setState(70)
+      .setCaState(71)
+      .setRetransmits(72)
+      .setProbes(73)
+      .setBackoff(74)
+      .setOptions(75)
+      .setSndWscale(76)
+      .setRcvWscale(77)
+      .setRto(78)
+      .setAto(79)
+      .setSndMss(710)
+      .setRcvMss(711)
+      .setUnacked(712)
+      .setSacked(713)
+      .setLost(714)
+      .setRetrans(715)
+      .setFackets(716)
+      .setLastDataSent(717)
+      .setLastAckSent(718)
+      .setLastDataRecv(719)
+      .setLastAckRecv(720)
+      .setPmtu(721)
+      .setRcvSsthresh(722)
+      .setRtt(723)
+      .setRttvar(724)
+      .setSndSsthresh(725)
+      .setSndCwnd(726)
+      .setAdvmss(727)
+      .setReordering(728)
+      .build();
+
+  private final SocketOption socketOptionTcpInfo = SocketOption
+      .newBuilder()
+      .setName("TCP_INFO")
+      .setAdditional(
+          Any.pack(
+              SocketOptionTcpInfo.newBuilder()
+                  .setTcpiState(70)
+                  .setTcpiCaState(71)
+                  .setTcpiRetransmits(72)
+                  .setTcpiProbes(73)
+                  .setTcpiBackoff(74)
+                  .setTcpiOptions(75)
+                  .setTcpiSndWscale(76)
+                  .setTcpiRcvWscale(77)
+                  .setTcpiRto(78)
+                  .setTcpiAto(79)
+                  .setTcpiSndMss(710)
+                  .setTcpiRcvMss(711)
+                  .setTcpiUnacked(712)
+                  .setTcpiSacked(713)
+                  .setTcpiLost(714)
+                  .setTcpiRetrans(715)
+                  .setTcpiFackets(716)
+                  .setTcpiLastDataSent(717)
+                  .setTcpiLastAckSent(718)
+                  .setTcpiLastDataRecv(719)
+                  .setTcpiLastAckRecv(720)
+                  .setTcpiPmtu(721)
+                  .setTcpiRcvSsthresh(722)
+                  .setTcpiRtt(723)
+                  .setTcpiRttvar(724)
+                  .setTcpiSndSsthresh(725)
+                  .setTcpiSndCwnd(726)
+                  .setTcpiAdvmss(727)
+                  .setTcpiReordering(728)
+                  .build()))
+      .build();
+
+  private final TestListenSocket listenSocket = new TestListenSocket();
+  private final SocketRef listenSocketRef = SocketRef
+      .newBuilder()
+      .setName(listenSocket.toString())
+      .setSocketId(id(listenSocket))
+      .build();
+  private final Address listenAddress = Address
+      .newBuilder()
+      .setTcpipAddress(
+          TcpIpAddress
+              .newBuilder()
+              .setIpAddress(ByteString.copyFrom(
+                  ((InetSocketAddress) listenSocket.listenAddress).getAddress().getAddress()))
+              .setPort(1234))
+      .build();
+
+  private final TestSocket socket = new TestSocket();
+  private final SocketRef socketRef = SocketRef
+      .newBuilder()
+      .setName(socket.toString())
+      .setSocketId(socket.getLogId().getId())
+      .build();
+  private final SocketData socketDataWithDataNoSockOpts = SocketData
+      .newBuilder()
+      .setStreamsStarted(1)
+      .setLastLocalStreamCreatedTimestamp(Timestamps.fromNanos(2))
+      .setLastRemoteStreamCreatedTimestamp(Timestamps.fromNanos(3))
+      .setStreamsSucceeded(4)
+      .setStreamsFailed(5)
+      .setMessagesSent(6)
+      .setMessagesReceived(7)
+      .setKeepAlivesSent(8)
+      .setLastMessageSentTimestamp(Timestamps.fromNanos(9))
+      .setLastMessageReceivedTimestamp(Timestamps.fromNanos(10))
+      .setLocalFlowControlWindow(Int64Value.newBuilder().setValue(11))
+      .setRemoteFlowControlWindow(Int64Value.newBuilder().setValue(12))
+      .build();
+  private final Address localAddress = Address
+      .newBuilder()
+      .setTcpipAddress(
+          TcpIpAddress
+              .newBuilder()
+              .setIpAddress(ByteString.copyFrom(
+                  ((InetSocketAddress) socket.local).getAddress().getAddress()))
+              .setPort(1000))
+      .build();
+  private final Address remoteAddress = Address
+      .newBuilder()
+      .setTcpipAddress(
+          TcpIpAddress
+              .newBuilder()
+              .setIpAddress(ByteString.copyFrom(
+                  ((InetSocketAddress) socket.remote).getAddress().getAddress()))
+              .setPort(1000))
+      .build();
+
+  private final ChannelTrace channelTrace = ChannelTrace
+      .newBuilder()
+      .setNumEventsLogged(1234)
+      .setCreationTimestamp(Timestamps.fromNanos(1000))
+      .build();
+
+  @Test
+  public void toChannelRef() {
+    assertEquals(channelRef, ChannelzProtoUtil.toChannelRef(channel));
+  }
+
+  @Test
+  public void toSubchannelRef() {
+    assertEquals(subchannelRef, ChannelzProtoUtil.toSubchannelRef(subchannel));
+  }
+
+  @Test
+  public void toServerRef() {
+    assertEquals(serverRef, ChannelzProtoUtil.toServerRef(server));
+  }
+
+  @Test
+  public void toSocketRef() {
+    assertEquals(socketRef, ChannelzProtoUtil.toSocketRef(socket));
+  }
+
+  @Test
+  public void toState() {
+    for (ConnectivityState connectivityState : ConnectivityState.values()) {
+      assertEquals(
+          connectivityState.name(),
+          ChannelzProtoUtil.toState(connectivityState).getValueDescriptor().getName());
+    }
+    assertEquals(State.UNKNOWN, ChannelzProtoUtil.toState(null));
+  }
+
+  @Test
+  public void toSocket_withDataNoOptions() throws Exception {
+    assertEquals(
+        Socket
+            .newBuilder()
+            .setRef(socketRef)
+            .setLocal(localAddress)
+            .setRemote(remoteAddress)
+            .setData(socketDataWithDataNoSockOpts)
+            .build(),
+        ChannelzProtoUtil.toSocket(socket));
+  }
+
+  @Test
+  public void toSocket_noDataWithOptions() throws Exception {
+    assertEquals(
+        Socket
+            .newBuilder()
+            .setRef(listenSocketRef)
+            .setLocal(listenAddress)
+            .setData(
+                SocketData
+                    .newBuilder()
+                    .addOption(
+                        SocketOption
+                            .newBuilder()
+                            .setName("listen_option")
+                            .setValue("listen_option_value")))
+            .build(),
+        ChannelzProtoUtil.toSocket(listenSocket));
+  }
+
+  @Test
+  public void toSocket_withDataWithOptions() throws Exception {
+    socket.socketOptions
+        = new SocketOptions(null, null, null, ImmutableMap.of("test_name", "test_value"));
+    assertEquals(
+        Socket
+            .newBuilder()
+            .setRef(socketRef)
+            .setLocal(localAddress)
+            .setRemote(remoteAddress)
+            .setData(
+                SocketData
+                    .newBuilder(socketDataWithDataNoSockOpts)
+                    .addOption(
+                        SocketOption.newBuilder()
+                            .setName("test_name").setValue("test_value")))
+            .build(),
+        ChannelzProtoUtil.toSocket(socket));
+  }
+
+  @Test
+  public void extractSocketData() throws Exception {
+    // no options
+    assertEquals(
+        socketDataWithDataNoSockOpts,
+        ChannelzProtoUtil.extractSocketData(socket.getStats().get()));
+
+    // with options
+    socket.socketOptions = toBuilder(socket.socketOptions)
+        .setSocketOptionLingerSeconds(10)
+        .setTcpInfo(channelzTcpInfo)
+        .build();
+    assertEquals(
+        socketDataWithDataNoSockOpts
+            .toBuilder()
+            .addOption(sockOptlinger10s)
+            .addOption(socketOptionTcpInfo)
+            .build(),
+        ChannelzProtoUtil.extractSocketData(socket.getStats().get()));
+  }
+
+  @Test
+  public void toSocketData() throws Exception {
+    assertEquals(
+        socketDataWithDataNoSockOpts
+            .toBuilder()
+            .build(),
+        ChannelzProtoUtil.extractSocketData(socket.getStats().get()));
+  }
+
+  @Test
+  public void socketSecurityTls() throws Exception {
+    Certificate local = mock(Certificate.class);
+    Certificate remote = mock(Certificate.class);
+    when(local.getEncoded()).thenReturn("localcert".getBytes(Charsets.UTF_8));
+    when(remote.getEncoded()).thenReturn("remotecert".getBytes(Charsets.UTF_8));
+
+    socket.security = new InternalChannelz.Security(
+        new InternalChannelz.Tls("TLS_NULL_WITH_NULL_NULL", local, remote));
+    assertEquals(
+        Security.newBuilder().setTls(
+            Tls.newBuilder()
+            .setStandardName("TLS_NULL_WITH_NULL_NULL")
+            .setLocalCertificate(ByteString.copyFrom("localcert", Charsets.UTF_8))
+            .setRemoteCertificate(ByteString.copyFrom("remotecert", Charsets.UTF_8)))
+        .build(),
+        ChannelzProtoUtil.toSocket(socket).getSecurity());
+
+    socket.security = new InternalChannelz.Security(
+        new InternalChannelz.Tls("TLS_NULL_WITH_NULL_NULL", /*localCert=*/ null, remote));
+    assertEquals(
+        Security.newBuilder().setTls(
+            Tls.newBuilder()
+            .setStandardName("TLS_NULL_WITH_NULL_NULL")
+            .setRemoteCertificate(ByteString.copyFrom("remotecert", Charsets.UTF_8)))
+        .build(),
+        ChannelzProtoUtil.toSocket(socket).getSecurity());
+
+    socket.security = new InternalChannelz.Security(
+        new InternalChannelz.Tls("TLS_NULL_WITH_NULL_NULL", local, /*remoteCert=*/ null));
+    assertEquals(
+        Security.newBuilder().setTls(
+            Tls.newBuilder()
+                .setStandardName("TLS_NULL_WITH_NULL_NULL")
+                .setLocalCertificate(ByteString.copyFrom("localcert", Charsets.UTF_8)))
+            .build(),
+            ChannelzProtoUtil.toSocket(socket).getSecurity());
+  }
+
+  @Test
+  public void socketSecurityOther() throws Exception {
+    // what is packed here is not important, just pick some proto message
+    Message contents = GetChannelRequest.newBuilder().setChannelId(1).build();
+    Any packed = Any.pack(contents);
+    socket.security
+        = new InternalChannelz.Security(
+            new InternalChannelz.OtherSecurity("other_security", packed));
+    assertEquals(
+        Security.newBuilder().setOther(
+            OtherSecurity.newBuilder().setName("other_security").setValue(packed))
+        .build(),
+        ChannelzProtoUtil.toSocket(socket).getSecurity());
+  }
+
+  @Test
+  public void toAddress_inet() throws Exception {
+    InetSocketAddress inet4 = new InetSocketAddress(Inet4Address.getByName("10.0.0.1"), 1000);
+    assertEquals(
+        Address.newBuilder().setTcpipAddress(
+            TcpIpAddress
+                .newBuilder()
+                .setIpAddress(ByteString.copyFrom(inet4.getAddress().getAddress()))
+                .setPort(1000))
+            .build(),
+        ChannelzProtoUtil.toAddress(inet4));
+  }
+
+  @Test
+  public void toAddress_uds() throws Exception {
+    String path = "/tmp/foo";
+    DomainSocketAddress uds = new DomainSocketAddress(path);
+    assertEquals(
+        Address.newBuilder().setUdsAddress(
+            UdsAddress
+                .newBuilder()
+                .setFilename(path))
+            .build(),
+        ChannelzProtoUtil.toAddress(uds));
+  }
+
+  @Test
+  public void toAddress_other() throws Exception {
+    final String name = "my name";
+    SocketAddress other = new SocketAddress() {
+      @Override
+      public String toString() {
+        return name;
+      }
+    };
+    assertEquals(
+        Address.newBuilder().setOtherAddress(
+            OtherAddress
+                .newBuilder()
+                .setName(name))
+            .build(),
+        ChannelzProtoUtil.toAddress(other));
+  }
+
+  @Test
+  public void toServer() throws Exception {
+    // no listen sockets
+    assertEquals(serverProto, ChannelzProtoUtil.toServer(server));
+
+    // 1 listen socket
+    server.serverStats = toBuilder(server.serverStats)
+        .setListenSockets(ImmutableList.<InternalInstrumented<SocketStats>>of(listenSocket))
+        .build();
+    assertEquals(
+        serverProto
+            .toBuilder()
+            .addListenSocket(listenSocketRef)
+            .build(),
+        ChannelzProtoUtil.toServer(server));
+
+    // multiple listen sockets
+    TestListenSocket otherListenSocket = new TestListenSocket();
+    SocketRef otherListenSocketRef = ChannelzProtoUtil.toSocketRef(otherListenSocket);
+    server.serverStats = toBuilder(server.serverStats)
+        .setListenSockets(
+            ImmutableList.<InternalInstrumented<SocketStats>>of(listenSocket, otherListenSocket))
+        .build();
+    assertEquals(
+        serverProto
+            .toBuilder()
+            .addListenSocket(listenSocketRef)
+            .addListenSocket(otherListenSocketRef)
+            .build(),
+        ChannelzProtoUtil.toServer(server));
+  }
+
+  @Test
+  public void toServerData() throws Exception {
+    assertEquals(serverData, ChannelzProtoUtil.toServerData(server.serverStats));
+  }
+
+  @Test
+  public void toChannel() throws Exception {
+    assertEquals(channelProto, ChannelzProtoUtil.toChannel(channel));
+
+    channel.stats = toBuilder(channel.stats)
+        .setSubchannels(ImmutableList.<InternalWithLogId>of(subchannel))
+        .build();
+
+    assertEquals(
+        channelProto
+            .toBuilder()
+            .addSubchannelRef(subchannelRef)
+            .build(),
+        ChannelzProtoUtil.toChannel(channel));
+
+    TestChannel otherSubchannel = new TestChannel();
+    channel.stats = toBuilder(channel.stats)
+        .setSubchannels(ImmutableList.<InternalWithLogId>of(subchannel, otherSubchannel))
+        .build();
+    assertEquals(
+        channelProto
+            .toBuilder()
+            .addSubchannelRef(subchannelRef)
+            .addSubchannelRef(ChannelzProtoUtil.toSubchannelRef(otherSubchannel))
+            .build(),
+        ChannelzProtoUtil.toChannel(channel));
+  }
+
+  @Test
+  public void extractChannelData() {
+    assertEquals(channelData, ChannelzProtoUtil.extractChannelData(channel.stats));
+  }
+
+  @Test
+  public void toSubchannel_noChildren() throws Exception {
+    assertEquals(
+        subchannelProto,
+        ChannelzProtoUtil.toSubchannel(subchannel));
+  }
+
+  @Test
+  public void toSubchannel_socketChildren() throws Exception {
+    subchannel.stats = toBuilder(subchannel.stats)
+        .setSockets(ImmutableList.<InternalWithLogId>of(socket))
+        .build();
+
+    assertEquals(
+        subchannelProto.toBuilder()
+            .addSocketRef(socketRef)
+            .build(),
+        ChannelzProtoUtil.toSubchannel(subchannel));
+
+    TestSocket otherSocket = new TestSocket();
+    subchannel.stats = toBuilder(subchannel.stats)
+        .setSockets(ImmutableList.<InternalWithLogId>of(socket, otherSocket))
+        .build();
+    assertEquals(
+        subchannelProto
+            .toBuilder()
+            .addSocketRef(socketRef)
+            .addSocketRef(ChannelzProtoUtil.toSocketRef(otherSocket))
+            .build(),
+        ChannelzProtoUtil.toSubchannel(subchannel));
+  }
+
+  @Test
+  public void toSubchannel_subchannelChildren() throws Exception {
+    TestChannel subchannel1 = new TestChannel();
+    subchannel.stats = toBuilder(subchannel.stats)
+        .setSubchannels(ImmutableList.<InternalWithLogId>of(subchannel1))
+        .build();
+    assertEquals(
+        subchannelProto.toBuilder()
+            .addSubchannelRef(ChannelzProtoUtil.toSubchannelRef(subchannel1))
+            .build(),
+        ChannelzProtoUtil.toSubchannel(subchannel));
+
+    TestChannel subchannel2 = new TestChannel();
+    subchannel.stats = toBuilder(subchannel.stats)
+        .setSubchannels(ImmutableList.<InternalWithLogId>of(subchannel1, subchannel2))
+        .build();
+    assertEquals(
+        subchannelProto
+            .toBuilder()
+            .addSubchannelRef(ChannelzProtoUtil.toSubchannelRef(subchannel1))
+            .addSubchannelRef(ChannelzProtoUtil.toSubchannelRef(subchannel2))
+            .build(),
+        ChannelzProtoUtil.toSubchannel(subchannel));
+  }
+
+  @Test
+  public void toGetTopChannelsResponse() {
+    // empty results
+    assertEquals(
+        GetTopChannelsResponse.newBuilder().setEnd(true).build(),
+        ChannelzProtoUtil.toGetTopChannelResponse(
+            new RootChannelList(
+                Collections.<InternalInstrumented<ChannelStats>>emptyList(), true)));
+
+    // 1 result, paginated
+    assertEquals(
+        GetTopChannelsResponse
+            .newBuilder()
+            .addChannel(channelProto)
+            .build(),
+        ChannelzProtoUtil.toGetTopChannelResponse(
+            new RootChannelList(
+                ImmutableList.<InternalInstrumented<ChannelStats>>of(channel), false)));
+
+    // 1 result, end
+    assertEquals(
+        GetTopChannelsResponse
+            .newBuilder()
+            .addChannel(channelProto)
+            .setEnd(true)
+            .build(),
+        ChannelzProtoUtil.toGetTopChannelResponse(
+            new RootChannelList(
+                ImmutableList.<InternalInstrumented<ChannelStats>>of(channel), true)));
+
+    // 2 results, end
+    TestChannel channel2 = new TestChannel();
+    assertEquals(
+        GetTopChannelsResponse
+            .newBuilder()
+            .addChannel(channelProto)
+            .addChannel(ChannelzProtoUtil.toChannel(channel2))
+            .setEnd(true)
+            .build(),
+        ChannelzProtoUtil.toGetTopChannelResponse(
+            new RootChannelList(
+                ImmutableList.<InternalInstrumented<ChannelStats>>of(channel, channel2), true)));
+  }
+
+  @Test
+  public void toGetServersResponse() {
+    // empty results
+    assertEquals(
+        GetServersResponse.getDefaultInstance(),
+        ChannelzProtoUtil.toGetServersResponse(
+            new ServerList(Collections.<InternalInstrumented<ServerStats>>emptyList(), false)));
+
+    // 1 result, paginated
+    assertEquals(
+        GetServersResponse
+            .newBuilder()
+            .addServer(serverProto)
+            .build(),
+        ChannelzProtoUtil.toGetServersResponse(
+            new ServerList(ImmutableList.<InternalInstrumented<ServerStats>>of(server), false)));
+
+    // 1 result, end
+    assertEquals(
+        GetServersResponse
+            .newBuilder()
+            .addServer(serverProto)
+            .setEnd(true)
+            .build(),
+        ChannelzProtoUtil.toGetServersResponse(
+            new ServerList(ImmutableList.<InternalInstrumented<ServerStats>>of(server), true)));
+
+    TestServer server2 = new TestServer();
+    // 2 results, end
+    assertEquals(
+        GetServersResponse
+            .newBuilder()
+            .addServer(serverProto)
+            .addServer(ChannelzProtoUtil.toServer(server2))
+            .setEnd(true)
+            .build(),
+        ChannelzProtoUtil.toGetServersResponse(
+            new ServerList(
+                ImmutableList.<InternalInstrumented<ServerStats>>of(server, server2), true)));
+  }
+
+  @Test
+  public void toGetServerSocketsResponse() {
+    // empty results
+    assertEquals(
+        GetServerSocketsResponse.getDefaultInstance(),
+        ChannelzProtoUtil.toGetServerSocketsResponse(
+            new ServerSocketsList(Collections.<InternalWithLogId>emptyList(), false)));
+
+    // 1 result, paginated
+    assertEquals(
+        GetServerSocketsResponse
+            .newBuilder()
+            .addSocketRef(socketRef)
+            .build(),
+        ChannelzProtoUtil.toGetServerSocketsResponse(
+            new ServerSocketsList(ImmutableList.<InternalWithLogId>of(socket), false)));
+
+    // 1 result, end
+    assertEquals(
+        GetServerSocketsResponse
+            .newBuilder()
+            .addSocketRef(socketRef)
+            .setEnd(true)
+            .build(),
+        ChannelzProtoUtil.toGetServerSocketsResponse(
+            new ServerSocketsList(ImmutableList.<InternalWithLogId>of(socket), true)));
+
+    TestSocket socket2 = new TestSocket();
+    // 2 results, end
+    assertEquals(
+        GetServerSocketsResponse
+            .newBuilder()
+            .addSocketRef(socketRef)
+            .addSocketRef(ChannelzProtoUtil.toSocketRef(socket2))
+            .setEnd(true)
+            .build(),
+        ChannelzProtoUtil.toGetServerSocketsResponse(
+            new ServerSocketsList(ImmutableList.<InternalWithLogId>of(socket, socket2), true)));
+  }
+
+  @Test
+  public void toSocketOptionLinger() {
+    assertEquals(sockOptLingerDisabled, ChannelzProtoUtil.toSocketOptionLinger(-1));
+    assertEquals(sockOptlinger10s, ChannelzProtoUtil.toSocketOptionLinger(10));
+  }
+
+  @Test
+  public void toSocketOptionTimeout() {
+    assertEquals(
+        sockOptTimeout200ms, ChannelzProtoUtil.toSocketOptionTimeout("SO_TIMEOUT", 200));
+  }
+
+  @Test
+  public void toSocketOptionAdditional() {
+    assertEquals(
+        sockOptAdditional,
+        ChannelzProtoUtil.toSocketOptionAdditional("SO_MADE_UP_OPTION", "some-made-up-value"));
+  }
+
+  @Test
+  public void toSocketOptionTcpInfo() {
+    assertEquals(
+        socketOptionTcpInfo,
+        ChannelzProtoUtil.toSocketOptionTcpInfo(channelzTcpInfo));
+  }
+
+  @Test
+  public void toSocketOptionsList() {
+    assertThat(
+        ChannelzProtoUtil.toSocketOptionsList(
+            new InternalChannelz.SocketOptions.Builder().build()))
+        .isEmpty();
+
+    assertThat(
+        ChannelzProtoUtil.toSocketOptionsList(
+            new InternalChannelz.SocketOptions.Builder().setSocketOptionLingerSeconds(10).build()))
+        .containsExactly(sockOptlinger10s);
+
+    assertThat(
+        ChannelzProtoUtil.toSocketOptionsList(
+            new InternalChannelz.SocketOptions.Builder().setSocketOptionTimeoutMillis(200).build()))
+        .containsExactly(sockOptTimeout200ms);
+
+    assertThat(
+        ChannelzProtoUtil.toSocketOptionsList(
+            new InternalChannelz.SocketOptions
+                .Builder()
+                .addOption("SO_MADE_UP_OPTION", "some-made-up-value")
+                .build()))
+        .containsExactly(sockOptAdditional);
+
+    SocketOption otherOption = SocketOption
+        .newBuilder()
+        .setName("SO_MADE_UP_OPTION2")
+        .setValue("some-made-up-value2")
+        .build();
+    assertThat(
+        ChannelzProtoUtil.toSocketOptionsList(
+            new InternalChannelz.SocketOptions.Builder()
+                .addOption("SO_MADE_UP_OPTION", "some-made-up-value")
+                .addOption("SO_MADE_UP_OPTION2", "some-made-up-value2")
+                .build()))
+        .containsExactly(sockOptAdditional, otherOption);
+  }
+
+  @Test
+  public void channelTrace_withoutEvents() {
+    ChannelStats stats = toBuilder(channel.stats)
+        .setChannelTrace(new InternalChannelz.ChannelTrace.Builder()
+            .setNumEventsLogged(1234)
+            .setCreationTimeNanos(1000)
+            .build())
+        .build();
+
+    ChannelData protoStats = channelData.toBuilder().setTrace(channelTrace).build();
+    assertEquals(ChannelzProtoUtil.extractChannelData(stats), protoStats);
+  }
+
+  @Test
+  public void channelTrace_withEvents() {
+    Event event1 = new Event.Builder()
+        .setDescription("event1")
+        .setSeverity(Severity.CT_ERROR)
+        .setTimestampNanos(12)
+        .setSubchannelRef(subchannel)
+        .build();
+    Event event2 = new Event.Builder()
+        .setDescription("event2")
+        .setTimestampNanos(34)
+        .setSeverity(Severity.CT_INFO)
+        .setChannelRef(channel)
+        .build();
+
+    ChannelStats stats =
+        toBuilder(channel.stats)
+            .setChannelTrace(
+                new InternalChannelz.ChannelTrace.Builder()
+                    .setNumEventsLogged(1234)
+                    .setCreationTimeNanos(1000)
+                    .setEvents(Arrays.asList(event1, event2))
+                    .build())
+            .build();
+
+    ChannelTraceEvent protoEvent1 = ChannelTraceEvent
+        .newBuilder()
+        .setDescription("event1")
+        .setTimestamp(Timestamps.fromNanos(12))
+        .setSeverity(ChannelTraceEvent.Severity.CT_ERROR)
+        .setSubchannelRef(subchannelRef)
+        .build();
+    ChannelTraceEvent protoEvent2 = ChannelTraceEvent
+        .newBuilder()
+        .setDescription("event2")
+        .setTimestamp(Timestamps.fromNanos(34))
+        .setSeverity(ChannelTraceEvent.Severity.CT_INFO)
+        .setChannelRef(channelRef)
+        .build();
+    ChannelData protoStats = channelData
+        .toBuilder()
+        .setTrace(channelTrace
+            .toBuilder()
+            .addAllEvents(Arrays.asList(protoEvent1, protoEvent2))
+            .build())
+        .build();
+    assertEquals(ChannelzProtoUtil.extractChannelData(stats), protoStats);
+  }
+
+  private static ChannelStats.Builder toBuilder(ChannelStats stats) {
+    ChannelStats.Builder builder = new ChannelStats.Builder()
+        .setTarget(stats.target)
+        .setState(stats.state)
+        .setCallsStarted(stats.callsStarted)
+        .setCallsSucceeded(stats.callsSucceeded)
+        .setCallsFailed(stats.callsFailed)
+        .setLastCallStartedNanos(stats.lastCallStartedNanos);
+    if (!stats.subchannels.isEmpty()) {
+      builder.setSubchannels(stats.subchannels);
+    }
+    if (!stats.sockets.isEmpty()) {
+      builder.setSockets(stats.sockets);
+    }
+    return builder;
+  }
+
+
+  private static SocketOptions.Builder toBuilder(SocketOptions options) {
+    SocketOptions.Builder builder = new SocketOptions.Builder()
+        .setSocketOptionTimeoutMillis(options.soTimeoutMillis)
+        .setSocketOptionLingerSeconds(options.lingerSeconds);
+    for (Entry<String, String> entry : options.others.entrySet()) {
+      builder.addOption(entry.getKey(), entry.getValue());
+    }
+    return builder;
+  }
+
+  private static ServerStats.Builder toBuilder(ServerStats stats) {
+    return new ServerStats.Builder()
+        .setCallsStarted(stats.callsStarted)
+        .setCallsSucceeded(stats.callsSucceeded)
+        .setCallsFailed(stats.callsFailed)
+        .setLastCallStartedNanos(stats.lastCallStartedNanos)
+        .setListenSockets(stats.listenSockets);
+  }
+}
diff --git a/services/src/test/java/io/grpc/services/ChannelzServiceTest.java b/services/src/test/java/io/grpc/services/ChannelzServiceTest.java
new file mode 100644
index 0000000..a8cafde
--- /dev/null
+++ b/services/src/test/java/io/grpc/services/ChannelzServiceTest.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.InternalChannelz;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.channelz.v1.GetChannelRequest;
+import io.grpc.channelz.v1.GetChannelResponse;
+import io.grpc.channelz.v1.GetServersRequest;
+import io.grpc.channelz.v1.GetServersResponse;
+import io.grpc.channelz.v1.GetSocketRequest;
+import io.grpc.channelz.v1.GetSocketResponse;
+import io.grpc.channelz.v1.GetSubchannelRequest;
+import io.grpc.channelz.v1.GetSubchannelResponse;
+import io.grpc.channelz.v1.GetTopChannelsRequest;
+import io.grpc.channelz.v1.GetTopChannelsResponse;
+import io.grpc.services.ChannelzTestHelper.TestChannel;
+import io.grpc.services.ChannelzTestHelper.TestServer;
+import io.grpc.services.ChannelzTestHelper.TestSocket;
+import io.grpc.stub.StreamObserver;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+
+@RunWith(JUnit4.class)
+public class ChannelzServiceTest {
+  // small value to force pagination
+  private static final int MAX_PAGE_SIZE = 1;
+
+  private final InternalChannelz channelz = new InternalChannelz();
+  private ChannelzService service = new ChannelzService(channelz, MAX_PAGE_SIZE);
+
+  @Test
+  public void getTopChannels_empty() {
+    assertEquals(
+        GetTopChannelsResponse.newBuilder().setEnd(true).build(),
+        getTopChannelHelper(0));
+  }
+
+  @Test
+  public void getTopChannels_onePage() throws Exception {
+    TestChannel root = new TestChannel();
+    channelz.addRootChannel(root);
+
+    assertEquals(
+        GetTopChannelsResponse
+            .newBuilder()
+            .addChannel(ChannelzProtoUtil.toChannel(root))
+            .setEnd(true)
+            .build(),
+        getTopChannelHelper(0));
+  }
+
+  @Test
+  public void getChannel() throws ExecutionException, InterruptedException {
+    TestChannel root = new TestChannel();
+    assertChannelNotFound(root.getLogId().getId());
+
+    channelz.addRootChannel(root);
+    assertEquals(
+        GetChannelResponse
+            .newBuilder()
+            .setChannel(ChannelzProtoUtil.toChannel(root))
+            .build(),
+        getChannelHelper(root.getLogId().getId()));
+
+    channelz.removeRootChannel(root);
+    assertChannelNotFound(root.getLogId().getId());
+  }
+
+  @Test
+  public void getSubchannel() throws Exception {
+    TestChannel subchannel = new TestChannel();
+    assertSubchannelNotFound(subchannel.getLogId().getId());
+
+    channelz.addSubchannel(subchannel);
+    assertEquals(
+        GetSubchannelResponse
+            .newBuilder()
+            .setSubchannel(ChannelzProtoUtil.toSubchannel(subchannel))
+            .build(),
+        getSubchannelHelper(subchannel.getLogId().getId()));
+
+    channelz.removeSubchannel(subchannel);
+    assertSubchannelNotFound(subchannel.getLogId().getId());
+  }
+
+  @Test
+  public void getServers_empty() {
+    assertEquals(
+        GetServersResponse.newBuilder().setEnd(true).build(),
+        getServersHelper(0));
+  }
+
+  @Test
+  public void getServers_onePage() throws Exception {
+    TestServer server = new TestServer();
+    channelz.addServer(server);
+
+    assertEquals(
+        GetServersResponse
+            .newBuilder()
+            .addServer(ChannelzProtoUtil.toServer(server))
+            .setEnd(true)
+            .build(),
+        getServersHelper(0));
+  }
+
+  @Test
+  public void getSocket() throws Exception {
+    TestSocket socket = new TestSocket();
+    assertSocketNotFound(socket.getLogId().getId());
+
+    channelz.addClientSocket(socket);
+    assertEquals(
+        GetSocketResponse
+            .newBuilder()
+            .setSocket(ChannelzProtoUtil.toSocket(socket))
+            .build(),
+        getSocketHelper(socket.getLogId().getId()));
+
+    channelz.removeClientSocket(socket);
+    assertSocketNotFound(socket.getLogId().getId());
+  }
+
+  private GetTopChannelsResponse getTopChannelHelper(long startId) {
+    @SuppressWarnings("unchecked")
+    StreamObserver<GetTopChannelsResponse> observer = mock(StreamObserver.class);
+    ArgumentCaptor<GetTopChannelsResponse> responseCaptor
+        = ArgumentCaptor.forClass(GetTopChannelsResponse.class);
+    service.getTopChannels(
+        GetTopChannelsRequest.newBuilder().setStartChannelId(startId).build(),
+        observer);
+    verify(observer).onNext(responseCaptor.capture());
+    verify(observer).onCompleted();
+    return responseCaptor.getValue();
+  }
+
+  private GetChannelResponse getChannelHelper(long id) {
+    @SuppressWarnings("unchecked")
+    StreamObserver<GetChannelResponse> observer = mock(StreamObserver.class);
+    ArgumentCaptor<GetChannelResponse> response
+        = ArgumentCaptor.forClass(GetChannelResponse.class);
+    service.getChannel(GetChannelRequest.newBuilder().setChannelId(id).build(), observer);
+    verify(observer).onNext(response.capture());
+    verify(observer).onCompleted();
+    return response.getValue();
+  }
+
+  private void assertChannelNotFound(long id) {
+    @SuppressWarnings("unchecked")
+    StreamObserver<GetChannelResponse> observer = mock(StreamObserver.class);
+    ArgumentCaptor<Exception> exceptionCaptor = ArgumentCaptor.forClass(Exception.class);
+    service.getChannel(GetChannelRequest.newBuilder().setChannelId(id).build(), observer);
+    verify(observer).onError(exceptionCaptor.capture());
+    Exception exception = exceptionCaptor.getValue();
+    assertEquals(Status.NOT_FOUND, ((StatusRuntimeException) exception).getStatus());
+  }
+
+  private GetSubchannelResponse getSubchannelHelper(long id) {
+    @SuppressWarnings("unchecked")
+    StreamObserver<GetSubchannelResponse> observer = mock(StreamObserver.class);
+    ArgumentCaptor<GetSubchannelResponse> response
+        = ArgumentCaptor.forClass(GetSubchannelResponse.class);
+    service.getSubchannel(GetSubchannelRequest.newBuilder().setSubchannelId(id).build(), observer);
+    verify(observer).onNext(response.capture());
+    verify(observer).onCompleted();
+    return response.getValue();
+  }
+
+  private void assertSubchannelNotFound(long id) {
+    @SuppressWarnings("unchecked")
+    StreamObserver<GetSubchannelResponse> observer = mock(StreamObserver.class);
+    ArgumentCaptor<Exception> exceptionCaptor = ArgumentCaptor.forClass(Exception.class);
+    service.getSubchannel(GetSubchannelRequest.newBuilder().setSubchannelId(id).build(), observer);
+    verify(observer).onError(exceptionCaptor.capture());
+    Exception exception = exceptionCaptor.getValue();
+    assertEquals(Status.NOT_FOUND, ((StatusRuntimeException) exception).getStatus());
+  }
+
+  private GetServersResponse getServersHelper(long startId) {
+    @SuppressWarnings("unchecked")
+    StreamObserver<GetServersResponse> observer = mock(StreamObserver.class);
+    ArgumentCaptor<GetServersResponse> responseCaptor
+        = ArgumentCaptor.forClass(GetServersResponse.class);
+    service.getServers(
+        GetServersRequest.newBuilder().setStartServerId(startId).build(),
+        observer);
+    verify(observer).onNext(responseCaptor.capture());
+    verify(observer).onCompleted();
+    return responseCaptor.getValue();
+  }
+
+  private void assertSocketNotFound(long id) {
+    @SuppressWarnings("unchecked")
+    StreamObserver<GetSocketResponse> observer = mock(StreamObserver.class);
+    ArgumentCaptor<Exception> exceptionCaptor = ArgumentCaptor.forClass(Exception.class);
+    service.getSocket(GetSocketRequest.newBuilder().setSocketId(id).build(), observer);
+    verify(observer).onError(exceptionCaptor.capture());
+    Exception exception = exceptionCaptor.getValue();
+    assertEquals(Status.NOT_FOUND, ((StatusRuntimeException) exception).getStatus());
+  }
+
+  private GetSocketResponse getSocketHelper(long id) {
+    @SuppressWarnings("unchecked")
+    StreamObserver<GetSocketResponse> observer = mock(StreamObserver.class);
+    ArgumentCaptor<GetSocketResponse> response
+        = ArgumentCaptor.forClass(GetSocketResponse.class);
+    service.getSocket(GetSocketRequest.newBuilder().setSocketId(id).build(), observer);
+    verify(observer).onNext(response.capture());
+    verify(observer).onCompleted();
+    return response.getValue();
+  }
+}
diff --git a/services/src/test/java/io/grpc/services/ChannelzTestHelper.java b/services/src/test/java/io/grpc/services/ChannelzTestHelper.java
new file mode 100644
index 0000000..cd8b676
--- /dev/null
+++ b/services/src/test/java/io/grpc/services/ChannelzTestHelper.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.ConnectivityState;
+import io.grpc.InternalChannelz;
+import io.grpc.InternalChannelz.ChannelStats;
+import io.grpc.InternalChannelz.Security;
+import io.grpc.InternalChannelz.ServerStats;
+import io.grpc.InternalChannelz.SocketOptions;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalChannelz.TransportStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.InternalLogId;
+import io.grpc.InternalWithLogId;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.Collections;
+
+/**
+ * Test class definitions that will be used in the proto utils test as well as
+ * channelz service test.
+ */
+final class ChannelzTestHelper {
+
+  static final class TestSocket implements InternalInstrumented<SocketStats> {
+    private final InternalLogId id = InternalLogId.allocate("socket");
+    TransportStats transportStats = new TransportStats(
+        /*streamsStarted=*/ 1,
+        /*lastLocalStreamCreatedTimeNanos=*/ 2,
+        /*lastRemoteStreamCreatedTimeNanos=*/ 3,
+        /*streamsSucceeded=*/ 4,
+        /*streamsFailed=*/ 5,
+        /*messagesSent=*/ 6,
+        /*messagesReceived=*/ 7,
+        /*keepAlivesSent=*/ 8,
+        /*lastMessageSentTimeNanos=*/ 9,
+        /*lastMessageReceivedTimeNanos=*/ 10,
+        /*localFlowControlWindow=*/ 11,
+        /*remoteFlowControlWindow=*/ 12);
+    SocketAddress local = new InetSocketAddress("10.0.0.1", 1000);
+    SocketAddress remote = new InetSocketAddress("10.0.0.2", 1000);
+    InternalChannelz.SocketOptions socketOptions
+        = new InternalChannelz.SocketOptions.Builder().build();
+    Security security = null;
+
+    @Override
+    public ListenableFuture<SocketStats> getStats() {
+      SettableFuture<SocketStats> ret = SettableFuture.create();
+      ret.set(
+          new SocketStats(
+              transportStats,
+              local,
+              remote,
+              socketOptions,
+              security));
+      return ret;
+    }
+
+    @Override
+    public InternalLogId getLogId() {
+      return id;
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("logId", getLogId())
+          .toString();
+    }
+  }
+
+  static final class TestListenSocket implements InternalInstrumented<SocketStats> {
+    private final InternalLogId id = InternalLogId.allocate("listensocket");
+    SocketAddress listenAddress = new InetSocketAddress("10.0.0.1", 1234);
+
+    @Override
+    public ListenableFuture<SocketStats> getStats() {
+      SettableFuture<SocketStats> ret = SettableFuture.create();
+      ret.set(
+          new SocketStats(
+              /*data=*/ null,
+              listenAddress,
+              /*remote=*/ null,
+              new SocketOptions.Builder().addOption("listen_option", "listen_option_value").build(),
+              /*security=*/ null));
+      return ret;
+    }
+
+    @Override
+    public InternalLogId getLogId() {
+      return id;
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("logId", getLogId())
+          .toString();
+    }
+  }
+
+  static final class TestServer implements InternalInstrumented<ServerStats> {
+    private final InternalLogId id = InternalLogId.allocate("server");
+    ServerStats serverStats = new ServerStats(
+        /*callsStarted=*/ 1,
+        /*callsSucceeded=*/ 2,
+        /*callsFailed=*/ 3,
+        /*lastCallStartedNanos=*/ 4,
+        Collections.<InternalInstrumented<SocketStats>>emptyList());
+
+    @Override
+    public ListenableFuture<ServerStats> getStats() {
+      SettableFuture<ServerStats> ret = SettableFuture.create();
+      ret.set(serverStats);
+      return ret;
+    }
+
+    @Override
+    public InternalLogId getLogId() {
+      return id;
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("logId", getLogId())
+          .toString();
+    }
+  }
+
+  static final class TestChannel implements InternalInstrumented<ChannelStats> {
+    private final InternalLogId id = InternalLogId.allocate("channel-or-subchannel");
+
+    ChannelStats stats = new ChannelStats.Builder()
+        .setTarget("sometarget")
+        .setState(ConnectivityState.READY)
+        .setCallsStarted(1)
+        .setCallsSucceeded(2)
+        .setCallsFailed(3)
+        .setLastCallStartedNanos(4)
+        .setSubchannels(Collections.<InternalWithLogId>emptyList())
+        .setSockets(Collections.<InternalWithLogId>emptyList())
+        .build();
+
+    @Override
+    public ListenableFuture<ChannelStats> getStats() {
+      SettableFuture<ChannelStats> ret = SettableFuture.create();
+      ret.set(stats);
+      return ret;
+    }
+
+    @Override
+    public InternalLogId getLogId() {
+      return id;
+    }
+
+    @Override
+    public String toString() {
+      return MoreObjects.toStringHelper(this)
+          .add("logId", getLogId())
+          .toString();
+    }
+  }
+}
diff --git a/services/src/test/java/io/grpc/services/HealthStatusManagerTest.java b/services/src/test/java/io/grpc/services/HealthStatusManagerTest.java
new file mode 100644
index 0000000..ef71ff8
--- /dev/null
+++ b/services/src/test/java/io/grpc/services/HealthStatusManagerTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import io.grpc.Status;
+import io.grpc.StatusException;
+import io.grpc.health.v1.HealthCheckRequest;
+import io.grpc.health.v1.HealthCheckResponse;
+import io.grpc.health.v1.HealthGrpc;
+import io.grpc.stub.StreamObserver;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+
+/** Unit tests for {@link HealthStatusManager}. */
+@RunWith(JUnit4.class)
+public class HealthStatusManagerTest {
+
+  private final HealthStatusManager manager = new HealthStatusManager();
+  private final HealthGrpc.HealthImplBase health =
+      (HealthGrpc.HealthImplBase) manager.getHealthService();
+  private final HealthCheckResponse.ServingStatus status
+      = HealthCheckResponse.ServingStatus.UNKNOWN;
+
+  @Test
+  public void getHealthService_getterReturnsTheSameHealthRefAfterUpdate() throws Exception {
+    manager.setStatus("", status);
+    assertEquals(health, manager.getHealthService());
+  }
+
+
+  @Test
+  public void checkValidStatus() throws Exception {
+    //setup
+    manager.setStatus("", status);
+    HealthCheckRequest request = HealthCheckRequest.newBuilder().setService("").build();
+    @SuppressWarnings("unchecked")
+    StreamObserver<HealthCheckResponse> observer = mock(StreamObserver.class);
+
+    //test
+    health.check(request, observer);
+
+    //verify
+    InOrder inOrder = inOrder(observer);
+    inOrder.verify(observer, times(1)).onNext(any(HealthCheckResponse.class));
+    inOrder.verify(observer, times(1)).onCompleted();
+    verify(observer, never()).onError(any(Throwable.class));
+  }
+
+  @Test
+  public void checkStatusNotFound() throws Exception {
+    //setup
+    manager.setStatus("", status);
+    HealthCheckRequest request
+        = HealthCheckRequest.newBuilder().setService("invalid").build();
+    @SuppressWarnings("unchecked")
+    StreamObserver<HealthCheckResponse> observer = mock(StreamObserver.class);
+
+    //test
+    health.check(request, observer);
+
+    //verify
+    ArgumentCaptor<StatusException> exception = ArgumentCaptor.forClass(StatusException.class);
+    verify(observer, times(1)).onError(exception.capture());
+    assertEquals(Status.Code.NOT_FOUND, exception.getValue().getStatus().getCode());
+
+    verify(observer, never()).onCompleted();
+  }
+
+  @Test
+  public void notFoundForClearedStatus() throws Exception {
+    //setup
+    manager.setStatus("", status);
+    manager.clearStatus("");
+    HealthCheckRequest request
+        = HealthCheckRequest.newBuilder().setService("").build();
+    @SuppressWarnings("unchecked")
+    StreamObserver<HealthCheckResponse> observer = mock(StreamObserver.class);
+
+    //test
+    health.check(request, observer);
+
+    //verify
+    ArgumentCaptor<StatusException> exception = ArgumentCaptor.forClass(StatusException.class);
+    verify(observer, times(1)).onError(exception.capture());
+    assertEquals(Status.Code.NOT_FOUND, exception.getValue().getStatus().getCode());
+
+    verify(observer, never()).onCompleted();
+  }
+}
diff --git a/services/src/test/java/io/grpc/services/TempFileSinkTest.java b/services/src/test/java/io/grpc/services/TempFileSinkTest.java
new file mode 100644
index 0000000..69ca39f
--- /dev/null
+++ b/services/src/test/java/io/grpc/services/TempFileSinkTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2018, gRPC Authors All rights reserved.
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.services;
+
+import static org.junit.Assert.assertEquals;
+
+import io.grpc.binarylog.v1alpha.GrpcLogEntry;
+import io.grpc.binarylog.v1alpha.Uint128;
+import java.io.DataInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link io.grpc.services.TempFileSink}.
+ */
+@RunWith(JUnit4.class)
+public class TempFileSinkTest {
+  @Test
+  public void readMyWrite() throws Exception {
+    TempFileSink sink = new TempFileSink();
+    GrpcLogEntry e1 = GrpcLogEntry.newBuilder()
+        .setCallId(Uint128.newBuilder().setLow(1234))
+        .build();
+    GrpcLogEntry e2 = GrpcLogEntry.newBuilder()
+        .setCallId(Uint128.newBuilder().setLow(5678))
+        .build();
+    sink.write(e1);
+    sink.write(e2);
+    sink.close();
+
+    DataInputStream input = new DataInputStream(new FileInputStream(sink.getPath()));
+    try {
+      GrpcLogEntry read1 = GrpcLogEntry.parseDelimitedFrom(input);
+      GrpcLogEntry read2 = GrpcLogEntry.parseDelimitedFrom(input);
+
+      assertEquals(e1, read1);
+      assertEquals(e2, read2);
+      assertEquals(-1, input.read());
+    } finally {
+      input.close();
+    }
+  }
+
+  @Test
+  public void writeAfterCloseIsSilent() throws IOException {
+    TempFileSink sink = new TempFileSink();
+    sink.close();
+    sink.write(GrpcLogEntry.newBuilder()
+        .setCallId(Uint128.newBuilder().setLow(1234))
+        .build());
+  }
+}
diff --git a/services/src/test/proto/io/grpc/reflection/testing/dynamic_reflection_test.proto b/services/src/test/proto/io/grpc/reflection/testing/dynamic_reflection_test.proto
new file mode 100644
index 0000000..4964404
--- /dev/null
+++ b/services/src/test/proto/io/grpc/reflection/testing/dynamic_reflection_test.proto
@@ -0,0 +1,37 @@
+// Copyright 2016 The gRPC Authors
+//
+// Licensed 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.
+// Service definition designed to test the ProtoReflectionService when a service
+// is dynamically added and removed.
+
+syntax = "proto2";
+
+import "io/grpc/reflection/testing/dynamic_reflection_test_depth_two.proto";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.reflection.testing";
+option java_outer_classname = "DynamicReflectionTestProto";
+
+package grpc.reflection.testing;
+
+// A DynamicService
+service DynamicService {
+  // A method
+  rpc Method (DynamicRequest) returns (DynamicReply) {}
+}
+
+// AnotherDynamicService
+service AnotherDynamicService {
+  // A method
+  rpc Method (DynamicRequest) returns (DynamicReply) {}
+}
diff --git a/services/src/test/proto/io/grpc/reflection/testing/dynamic_reflection_test_depth_two.proto b/services/src/test/proto/io/grpc/reflection/testing/dynamic_reflection_test_depth_two.proto
new file mode 100644
index 0000000..87d4b26
--- /dev/null
+++ b/services/src/test/proto/io/grpc/reflection/testing/dynamic_reflection_test_depth_two.proto
@@ -0,0 +1,37 @@
+// Copyright 2016 The gRPC Authors
+//
+// Licensed 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.
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.reflection.testing";
+option java_outer_classname = "DynamicReflectionTestDepthTwoProto";
+
+package grpc.reflection.testing;
+
+message DynamicRequest {
+  optional string message = 1;
+}
+
+message DynamicReply {
+  optional string message = 1;
+}
+
+message TypeWithExtensions {
+  optional string message = 1;
+  extensions 200 to 299;
+}
+
+extend TypeWithExtensions {
+  optional int32 extension = 200;
+}
diff --git a/services/src/test/proto/io/grpc/reflection/testing/reflection_test.proto b/services/src/test/proto/io/grpc/reflection/testing/reflection_test.proto
new file mode 100644
index 0000000..12f3969
--- /dev/null
+++ b/services/src/test/proto/io/grpc/reflection/testing/reflection_test.proto
@@ -0,0 +1,34 @@
+// Copyright 2016 The gRPC Authors
+//
+// Licensed 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.
+// Service definition designed to test the ProtoReflectionService. It contains
+// nested types, extensions, and multiple levels of imports.
+
+syntax = "proto2";
+
+import "io/grpc/reflection/testing/reflection_test_depth_two.proto";
+import "io/grpc/reflection/testing/reflection_test_depth_two_alternate.proto";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.reflection.testing";
+option java_outer_classname = "ReflectionTestProto";
+
+package grpc.reflection.testing;
+
+extend ThirdLevelType {
+  optional int32 bar = 100;
+}
+
+service ReflectableService {
+  rpc Method (Request) returns (Reply) {}
+}
diff --git a/services/src/test/proto/io/grpc/reflection/testing/reflection_test_depth_three.proto b/services/src/test/proto/io/grpc/reflection/testing/reflection_test_depth_three.proto
new file mode 100644
index 0000000..c6370a5
--- /dev/null
+++ b/services/src/test/proto/io/grpc/reflection/testing/reflection_test_depth_three.proto
@@ -0,0 +1,37 @@
+// Copyright 2016 The gRPC Authors
+//
+// Licensed 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 the service definition designed to test the ProtoReflectionService.
+
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.reflection.testing";
+option java_outer_classname = "ReflectionTestDepthThreeProto";
+
+package grpc.reflection.testing;
+
+message EmptyMessage {}
+
+message ThirdLevelType {
+  optional string message = 1;
+  extensions 100 to 199;
+}
+
+message NestedTypeOuter {
+  message Middle {
+    message Inner {
+      optional int32 ival = 1;
+    }
+  }
+}
\ No newline at end of file
diff --git a/services/src/test/proto/io/grpc/reflection/testing/reflection_test_depth_two.proto b/services/src/test/proto/io/grpc/reflection/testing/reflection_test_depth_two.proto
new file mode 100644
index 0000000..19d6a69
--- /dev/null
+++ b/services/src/test/proto/io/grpc/reflection/testing/reflection_test_depth_two.proto
@@ -0,0 +1,36 @@
+// Copyright 2016 The gRPC Authors
+//
+// Licensed 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 the service definition designed to test the ProtoReflectionService.
+
+syntax = "proto2";
+
+import public "io/grpc/reflection/testing/reflection_test_depth_three.proto";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.reflection.testing";
+option java_outer_classname = "ReflectionTestDepthTwoProto";
+
+package grpc.reflection.testing;
+
+message Request {
+  optional string message = 1;
+}
+
+message Reply {
+  optional string message = 1;
+}
+
+extend ThirdLevelType {
+  optional EmptyMessage nested_extension = 101;
+}
\ No newline at end of file
diff --git a/services/src/test/proto/io/grpc/reflection/testing/reflection_test_depth_two_alternate.proto b/services/src/test/proto/io/grpc/reflection/testing/reflection_test_depth_two_alternate.proto
new file mode 100644
index 0000000..c5b0c41
--- /dev/null
+++ b/services/src/test/proto/io/grpc/reflection/testing/reflection_test_depth_two_alternate.proto
@@ -0,0 +1,23 @@
+// Copyright 2016 The gRPC Authors
+//
+// Licensed 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.
+// Provides an alternate dependency path to reflection_test_depth_three.proto,
+// to test that the reflection service only returns dependencies once.
+
+syntax = "proto2";
+
+import public "io/grpc/reflection/testing/reflection_test_depth_three.proto";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.reflection.testing";
+option java_outer_classname = "ReflectionTestDepthTwoAlternateProto";
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..65dd763
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,49 @@
+rootProject.name = "grpc"
+include ":grpc-core"
+include ":grpc-context"
+include ":grpc-stub"
+include ":grpc-auth"
+include ":grpc-okhttp"
+include ":grpc-protobuf"
+include ":grpc-protobuf-lite"
+include ":grpc-protobuf-nano"
+include ":grpc-netty"
+include ":grpc-netty-shaded"
+include ":grpc-grpclb"
+include ":grpc-testing"
+include ":grpc-testing-proto"
+include ":grpc-interop-testing"
+include ":grpc-gae-interop-testing-jdk7"
+include ":grpc-gae-interop-testing-jdk8"
+include ":grpc-all"
+include ":grpc-alts"
+include ":grpc-benchmarks"
+include ":grpc-services"
+
+project(':grpc-core').projectDir = "$rootDir/core" as File
+project(':grpc-context').projectDir = "$rootDir/context" as File
+project(':grpc-stub').projectDir = "$rootDir/stub" as File
+project(':grpc-auth').projectDir = "$rootDir/auth" as File
+project(':grpc-okhttp').projectDir = "$rootDir/okhttp" as File
+project(':grpc-protobuf').projectDir = "$rootDir/protobuf" as File
+project(':grpc-protobuf-lite').projectDir = "$rootDir/protobuf-lite" as File
+project(':grpc-protobuf-nano').projectDir = "$rootDir/protobuf-nano" as File
+project(':grpc-netty').projectDir = "$rootDir/netty" as File
+project(':grpc-netty-shaded').projectDir = "$rootDir/netty/shaded" as File
+project(':grpc-grpclb').projectDir = "$rootDir/grpclb" as File
+project(':grpc-testing').projectDir = "$rootDir/testing" as File
+project(':grpc-testing-proto').projectDir = "$rootDir/testing-proto" as File
+project(':grpc-interop-testing').projectDir = "$rootDir/interop-testing" as File
+project(':grpc-gae-interop-testing-jdk7').projectDir = "$rootDir/gae-interop-testing/gae-jdk7" as File
+project(':grpc-gae-interop-testing-jdk8').projectDir = "$rootDir/gae-interop-testing/gae-jdk8" as File
+project(':grpc-all').projectDir = "$rootDir/all" as File
+project(':grpc-alts').projectDir = "$rootDir/alts" as File
+project(':grpc-benchmarks').projectDir = "$rootDir/benchmarks" as File
+project(':grpc-services').projectDir = "$rootDir/services" as File
+
+if (settings.hasProperty('skipCodegen') && skipCodegen.toBoolean()) {
+    println '*** Skipping the build of codegen and compilation of proto files because skipCodegen=true'
+} else {
+    include ":grpc-compiler"
+    project(':grpc-compiler').projectDir = "$rootDir/compiler" as File
+}
diff --git a/stub/BUILD.bazel b/stub/BUILD.bazel
new file mode 100644
index 0000000..b0304cf
--- /dev/null
+++ b/stub/BUILD.bazel
@@ -0,0 +1,22 @@
+java_library(
+    name = "stub",
+    srcs = glob([
+        "src/main/java/**/*.java",
+    ]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//context",
+        "//core",
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_guava_guava//jar",
+    ],
+)
+
+# javax.annotation.Generated is not included in the default root modules in 9,
+# see: http://openjdk.java.net/jeps/320.
+java_library(
+    name = "javax_annotation",
+    neverlink = 1,  # @Generated is source-retention
+    visibility = ["//visibility:public"],
+    exports = ["@javax_annotation_javax_annotation_api//jar"],
+)
diff --git a/stub/build.gradle b/stub/build.gradle
new file mode 100644
index 0000000..651173f
--- /dev/null
+++ b/stub/build.gradle
@@ -0,0 +1,10 @@
+description = "gRPC: Stub"
+dependencies {
+    compile project(':grpc-core')
+    testCompile libraries.truth,
+            project(':grpc-testing')
+    signature "org.codehaus.mojo.signature:java17:1.0@signature"
+    signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
+}
+
+javadoc.options.links "https://google.github.io/guava/releases/${guavaVersion}/api/docs/"
diff --git a/stub/src/main/java/io/grpc/stub/AbstractStub.java b/stub/src/main/java/io/grpc/stub/AbstractStub.java
new file mode 100644
index 0000000..346512a
--- /dev/null
+++ b/stub/src/main/java/io/grpc/stub/AbstractStub.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.grpc.CallCredentials;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientInterceptor;
+import io.grpc.ClientInterceptors;
+import io.grpc.Deadline;
+import io.grpc.ExperimentalApi;
+import io.grpc.ManagedChannelBuilder;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Common base type for stub implementations. Stub configuration is immutable; changing the
+ * configuration returns a new stub with updated configuration. Changing the configuration is cheap
+ * and may be done before every RPC, such as would be common when using {@link #withDeadlineAfter}.
+ *
+ * <p>Configuration is stored in {@link CallOptions} and is passed to the {@link Channel} when
+ * performing an RPC.
+ *
+ * <p>DO NOT MOCK: Customizing options doesn't work properly in mocks. Use InProcessChannelBuilder
+ * to create a real channel suitable for testing. It is also possible to mock Channel instead.
+ *
+ * @since 1.0.0
+ * @param <S> the concrete type of this stub.
+ */
+@ThreadSafe
+@CheckReturnValue
+public abstract class AbstractStub<S extends AbstractStub<S>> {
+  private final Channel channel;
+  private final CallOptions callOptions;
+
+  /**
+   * Constructor for use by subclasses, with the default {@code CallOptions}.
+   *
+   * @since 1.0.0
+   * @param channel the channel that this stub will use to do communications
+   */
+  protected AbstractStub(Channel channel) {
+    this(channel, CallOptions.DEFAULT);
+  }
+
+  /**
+   * Constructor for use by subclasses, with the default {@code CallOptions}.
+   *
+   * @since 1.0.0
+   * @param channel the channel that this stub will use to do communications
+   * @param callOptions the runtime call options to be applied to every call on this stub
+   */
+  protected AbstractStub(Channel channel, CallOptions callOptions) {
+    this.channel = checkNotNull(channel, "channel");
+    this.callOptions = checkNotNull(callOptions, "callOptions");
+  }
+
+  /**
+   * The underlying channel of the stub.
+   *
+   * @since 1.0.0
+   */
+  public final Channel getChannel() {
+    return channel;
+  }
+
+  /**
+   * The {@code CallOptions} of the stub.
+   *
+   * @since 1.0.0
+   */
+  public final CallOptions getCallOptions() {
+    return callOptions;
+  }
+
+  /**
+   * Returns a new stub with the given channel for the provided method configurations.
+   *
+   * @since 1.0.0
+   * @param channel the channel that this stub will use to do communications
+   * @param callOptions the runtime call options to be applied to every call on this stub
+   */
+  protected abstract S build(Channel channel, CallOptions callOptions);
+
+  /**
+   * Returns a new stub with an absolute deadline.
+   *
+   * <p>This is mostly used for propagating an existing deadline. {@link #withDeadlineAfter} is the
+   * recommended way of setting a new deadline,
+   *
+   * @since 1.0.0
+   * @param deadline the deadline or {@code null} for unsetting the deadline.
+   */
+  public final S withDeadline(@Nullable Deadline deadline) {
+    return build(channel, callOptions.withDeadline(deadline));
+  }
+
+  /**
+   * Returns a new stub with a deadline that is after the given {@code duration} from now.
+   *
+   * @since 1.0.0
+   * @see CallOptions#withDeadlineAfter
+   */
+  public final S withDeadlineAfter(long duration, TimeUnit unit) {
+    return build(channel, callOptions.withDeadlineAfter(duration, unit));
+  }
+
+  /**
+   * Returns a new stub with the given executor that is to be used instead of the default one
+   * specified with {@link ManagedChannelBuilder#executor}. Note that setting this option may not
+   * take effect for blocking calls.
+   *
+   * @since 1.8.0
+   */
+  public final S withExecutor(Executor executor) {
+    return build(channel, callOptions.withExecutor(executor));
+  }
+
+  /**
+   *  Set's the compressor name to use for the call.  It is the responsibility of the application
+   *  to make sure the server supports decoding the compressor picked by the client.  To be clear,
+   *  this is the compressor used by the stub to compress messages to the server.  To get
+   *  compressed responses from the server, set the appropriate {@link io.grpc.DecompressorRegistry}
+   *  on the {@link io.grpc.ManagedChannelBuilder}.
+   *
+   * @since 1.0.0
+   * @param compressorName the name (e.g. "gzip") of the compressor to use.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
+  public final S withCompression(String compressorName) {
+    return build(channel, callOptions.withCompression(compressorName));
+  }
+
+  /**
+   * Returns a new stub that uses the given channel.
+   *
+   * <p>This method is vestigial and is unlikely to be useful.  Instead, users should prefer to
+   * use {@link #withInterceptors}.
+   *
+   * @since 1.0.0
+   */
+  @Deprecated // use withInterceptors() instead
+  public final S withChannel(Channel newChannel) {
+    return build(newChannel, callOptions);
+  }
+
+  /**
+   * Sets a custom option to be passed to client interceptors on the channel
+   * {@link io.grpc.ClientInterceptor} via the CallOptions parameter.
+   *
+   * @since 1.0.0
+   * @param key the option being set
+   * @param value the value for the key
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1869")
+  public final <T> S withOption(CallOptions.Key<T> key, T value) {
+    return build(channel, callOptions.withOption(key, value));
+  }
+
+  /**
+   * Returns a new stub that has the given interceptors attached to the underlying channel.
+   *
+   * @since 1.0.0
+   */
+  public final S withInterceptors(ClientInterceptor... interceptors) {
+    return build(ClientInterceptors.intercept(channel, interceptors), callOptions);
+  }
+
+  /**
+   * Returns a new stub that uses the given call credentials.
+   *
+   * @since 1.0.0
+   */
+  public final S withCallCredentials(CallCredentials credentials) {
+    return build(channel, callOptions.withCallCredentials(credentials));
+  }
+
+  /**
+   * Returns a new stub that uses the 'wait for ready' call option.
+   *
+   * @since 1.1.0
+   */
+  public final S withWaitForReady() {
+    return build(channel, callOptions.withWaitForReady());
+  }
+
+  /**
+   * Returns a new stub that limits the maximum acceptable message size from a remote peer.
+   *
+   * <p>If unset, the {@link ManagedChannelBuilder#maxInboundMessageSize(int)} limit is used.
+   *
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2563")
+  public final S withMaxInboundMessageSize(int maxSize) {
+    return build(channel, callOptions.withMaxInboundMessageSize(maxSize));
+  }
+
+  /**
+   * Returns a new stub that limits the maximum acceptable message size to send a remote peer.
+   *
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2563")
+  public final S withMaxOutboundMessageSize(int maxSize) {
+    return build(channel, callOptions.withMaxOutboundMessageSize(maxSize));
+  }
+}
diff --git a/stub/src/main/java/io/grpc/stub/CallStreamObserver.java b/stub/src/main/java/io/grpc/stub/CallStreamObserver.java
new file mode 100644
index 0000000..8bcfb21
--- /dev/null
+++ b/stub/src/main/java/io/grpc/stub/CallStreamObserver.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+import io.grpc.ExperimentalApi;
+
+/**
+ * A refinement of StreamObserver provided by the GRPC runtime to the application that allows for
+ * more complex interactions with call behavior.
+ *
+ * <p>In any call there are logically two {@link StreamObserver} implementations:
+ * <ul>
+ *   <li>'inbound' - which the GRPC runtime calls when it receives messages from the
+ *   remote peer. This is implemented by the application.
+ *   </li>
+ *   <li>'outbound' - which the GRPC runtime provides to the application which it uses to
+ *   send messages to the remote peer.
+ *   </li>
+ * </ul>
+ *
+ * <p>Implementations of this class represent the 'outbound' message stream.
+ *
+ * <p>Like {@code StreamObserver}, implementations are not required to be thread-safe; if multiple
+ * threads will be writing to an instance concurrently, the application must synchronize its calls.
+ *
+ * <p>DO NOT MOCK: The API is too complex to reliably mock. Use InProcessChannelBuilder to create
+ * "real" RPCs suitable for testing.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1788")
+public abstract class CallStreamObserver<V> implements StreamObserver<V> {
+
+  /**
+   * If {@code true}, indicates that the observer is capable of sending additional messages
+   * without requiring excessive buffering internally. This value is just a suggestion and the
+   * application is free to ignore it, however doing so may result in excessive buffering within the
+   * observer.
+   */
+  public abstract boolean isReady();
+
+  /**
+   * Set a {@link Runnable} that will be executed every time the stream {@link #isReady()} state
+   * changes from {@code false} to {@code true}.  While it is not guaranteed that the same
+   * thread will always be used to execute the {@link Runnable}, it is guaranteed that executions
+   * are serialized with calls to the 'inbound' {@link StreamObserver}.
+   *
+   * <p>On client-side this method may only be called during {@link
+   * ClientResponseObserver#beforeStart}. On server-side it may only be called during the initial
+   * call to the application, before the service returns its {@code StreamObserver}.
+   *
+   * <p>Note that the handler may be called some time after {@link #isReady} has transitioned to
+   * true as other callbacks may still be executing in the 'inbound' observer.
+   *
+   * @param onReadyHandler to call when peer is ready to receive more messages.
+   */
+  public abstract void setOnReadyHandler(Runnable onReadyHandler);
+
+  /**
+   * Disables automatic flow control where a token is returned to the peer after a call
+   * to the 'inbound' {@link io.grpc.stub.StreamObserver#onNext(Object)} has completed. If disabled
+   * an application must make explicit calls to {@link #request} to receive messages.
+   *
+   * <p>On client-side this method may only be called during {@link
+   * ClientResponseObserver#beforeStart}. On server-side it may only be called during the initial
+   * call to the application, before the service returns its {@code StreamObserver}.
+   *
+   * <p>Note that for cases where the runtime knows that only one inbound message is allowed
+   * calling this method will have no effect and the runtime will always permit one and only
+   * one message. This is true for:
+   * <ul>
+   *   <li>{@link io.grpc.MethodDescriptor.MethodType#UNARY} operations on both the
+   *   client and server.
+   *   </li>
+   *   <li>{@link io.grpc.MethodDescriptor.MethodType#CLIENT_STREAMING} operations on the client.
+   *   </li>
+   *   <li>{@link io.grpc.MethodDescriptor.MethodType#SERVER_STREAMING} operations on the server.
+   *   </li>
+   * </ul>
+   * </p>
+   */
+  public abstract void disableAutoInboundFlowControl();
+
+  /**
+   * Requests the peer to produce {@code count} more messages to be delivered to the 'inbound'
+   * {@link StreamObserver}.
+   *
+   * <p>This method is safe to call from multiple threads without external synchronization.
+   *
+   * @param count more messages
+   */
+  public abstract void request(int count);
+
+  /**
+   * Sets message compression for subsequent calls to {@link #onNext}.
+   *
+   * @param enable whether to enable compression.
+   */
+  public abstract void setMessageCompression(boolean enable);
+}
diff --git a/stub/src/main/java/io/grpc/stub/ClientCallStreamObserver.java b/stub/src/main/java/io/grpc/stub/ClientCallStreamObserver.java
new file mode 100644
index 0000000..36fe05b
--- /dev/null
+++ b/stub/src/main/java/io/grpc/stub/ClientCallStreamObserver.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+import io.grpc.ExperimentalApi;
+
+import javax.annotation.Nullable;
+
+/**
+ * A refinement of {@link CallStreamObserver} that allows for lower-level interaction with
+ * client calls.
+ *
+ * <p>Like {@code StreamObserver}, implementations are not required to be thread-safe; if multiple
+ * threads will be writing to an instance concurrently, the application must synchronize its calls.
+ *
+ * <p>DO NOT MOCK: The API is too complex to reliably mock. Use InProcessChannelBuilder to create
+ * "real" RPCs suitable for testing and make a fake for the server-side.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1788")
+public abstract class ClientCallStreamObserver<V> extends CallStreamObserver<V> {
+  /**
+   * Prevent any further processing for this {@code ClientCallStreamObserver}. No further messages
+   * will be received. The server is informed of cancellations, but may not stop processing the
+   * call. Cancelling an already
+   * {@code cancel()}ed {@code ClientCallStreamObserver} has no effect.
+   *
+   * <p>No other methods on this class can be called after this method has been called.
+   *
+   * <p>It is recommended that at least one of the arguments to be non-{@code null}, to provide
+   * useful debug information. Both argument being null may log warnings and result in suboptimal
+   * performance. Also note that the provided information will not be sent to the server.
+   *
+   * @param message if not {@code null}, will appear as the description of the CANCELLED status
+   * @param cause if not {@code null}, will appear as the cause of the CANCELLED status
+   */
+  public abstract void cancel(@Nullable String message, @Nullable Throwable cause);
+}
diff --git a/stub/src/main/java/io/grpc/stub/ClientCalls.java b/stub/src/main/java/io/grpc/stub/ClientCalls.java
new file mode 100644
index 0000000..db24403
--- /dev/null
+++ b/stub/src/main/java/io/grpc/stub/ClientCalls.java
@@ -0,0 +1,658 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.AbstractFuture;
+import com.google.common.util.concurrent.ListenableFuture;
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.grpc.StatusException;
+import io.grpc.StatusRuntimeException;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.Nullable;
+
+/**
+ * Utility functions for processing different call idioms. We have one-to-one correspondence
+ * between utilities in this class and the potential signatures in a generated stub class so
+ * that the runtime can vary behavior without requiring regeneration of the stub.
+ */
+public final class ClientCalls {
+
+  private static final Logger logger = Logger.getLogger(ClientCalls.class.getName());
+
+  // Prevent instantiation
+  private ClientCalls() {}
+
+  /**
+   * Executes a unary call with a response {@link StreamObserver}.  The {@code call} should not be
+   * already started.  After calling this method, {@code call} should no longer be used.
+   */
+  public static <ReqT, RespT> void asyncUnaryCall(
+      ClientCall<ReqT, RespT> call, ReqT req, StreamObserver<RespT> responseObserver) {
+    asyncUnaryRequestCall(call, req, responseObserver, false);
+  }
+
+  /**
+   * Executes a server-streaming call with a response {@link StreamObserver}.  The {@code call}
+   * should not be already started.  After calling this method, {@code call} should no longer be
+   * used.
+   */
+  public static <ReqT, RespT> void asyncServerStreamingCall(
+      ClientCall<ReqT, RespT> call, ReqT req, StreamObserver<RespT> responseObserver) {
+    asyncUnaryRequestCall(call, req, responseObserver, true);
+  }
+
+  /**
+   * Executes a client-streaming call returning a {@link StreamObserver} for the request messages.
+   * The {@code call} should not be already started.  After calling this method, {@code call}
+   * should no longer be used.
+   *
+   * @return request stream observer.
+   */
+  public static <ReqT, RespT> StreamObserver<ReqT> asyncClientStreamingCall(
+      ClientCall<ReqT, RespT> call,
+      StreamObserver<RespT> responseObserver) {
+    return asyncStreamingRequestCall(call, responseObserver, false);
+  }
+
+  /**
+   * Executes a bidirectional-streaming call.  The {@code call} should not be already started.
+   * After calling this method, {@code call} should no longer be used.
+   *
+   * @return request stream observer.
+   */
+  public static <ReqT, RespT> StreamObserver<ReqT> asyncBidiStreamingCall(
+      ClientCall<ReqT, RespT> call, StreamObserver<RespT> responseObserver) {
+    return asyncStreamingRequestCall(call, responseObserver, true);
+  }
+
+  /**
+   * Executes a unary call and blocks on the response.  The {@code call} should not be already
+   * started.  After calling this method, {@code call} should no longer be used.
+   *
+   * @return the single response message.
+   */
+  public static <ReqT, RespT> RespT blockingUnaryCall(ClientCall<ReqT, RespT> call, ReqT req) {
+    try {
+      return getUnchecked(futureUnaryCall(call, req));
+    } catch (RuntimeException e) {
+      throw cancelThrow(call, e);
+    } catch (Error e) {
+      throw cancelThrow(call, e);
+    }
+  }
+
+  /**
+   * Executes a unary call and blocks on the response.  The {@code call} should not be already
+   * started.  After calling this method, {@code call} should no longer be used.
+   *
+   * @return the single response message.
+   */
+  public static <ReqT, RespT> RespT blockingUnaryCall(
+      Channel channel, MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, ReqT req) {
+    ThreadlessExecutor executor = new ThreadlessExecutor();
+    ClientCall<ReqT, RespT> call = channel.newCall(method, callOptions.withExecutor(executor));
+    try {
+      ListenableFuture<RespT> responseFuture = futureUnaryCall(call, req);
+      while (!responseFuture.isDone()) {
+        try {
+          executor.waitAndDrain();
+        } catch (InterruptedException e) {
+          Thread.currentThread().interrupt();
+          throw Status.CANCELLED
+              .withDescription("Call was interrupted")
+              .withCause(e)
+              .asRuntimeException();
+        }
+      }
+      return getUnchecked(responseFuture);
+    } catch (RuntimeException e) {
+      throw cancelThrow(call, e);
+    } catch (Error e) {
+      throw cancelThrow(call, e);
+    }
+  }
+
+  /**
+   * Executes a server-streaming call returning a blocking {@link Iterator} over the
+   * response stream.  The {@code call} should not be already started.  After calling this method,
+   * {@code call} should no longer be used.
+   *
+   * @return an iterator over the response stream.
+   */
+  // TODO(louiscryan): Not clear if we want to use this idiom for 'simple' stubs.
+  public static <ReqT, RespT> Iterator<RespT> blockingServerStreamingCall(
+      ClientCall<ReqT, RespT> call, ReqT req) {
+    BlockingResponseStream<RespT> result = new BlockingResponseStream<RespT>(call);
+    asyncUnaryRequestCall(call, req, result.listener(), true);
+    return result;
+  }
+
+  /**
+   * Executes a server-streaming call returning a blocking {@link Iterator} over the
+   * response stream.  The {@code call} should not be already started.  After calling this method,
+   * {@code call} should no longer be used.
+   *
+   * @return an iterator over the response stream.
+   */
+  // TODO(louiscryan): Not clear if we want to use this idiom for 'simple' stubs.
+  public static <ReqT, RespT> Iterator<RespT> blockingServerStreamingCall(
+      Channel channel, MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, ReqT req) {
+    ThreadlessExecutor executor = new ThreadlessExecutor();
+    ClientCall<ReqT, RespT> call = channel.newCall(method, callOptions.withExecutor(executor));
+    BlockingResponseStream<RespT> result = new BlockingResponseStream<RespT>(call, executor);
+    asyncUnaryRequestCall(call, req, result.listener(), true);
+    return result;
+  }
+
+  /**
+   * Executes a unary call and returns a {@link ListenableFuture} to the response.  The
+   * {@code call} should not be already started.  After calling this method, {@code call} should no
+   * longer be used.
+   *
+   * @return a future for the single response message.
+   */
+  public static <ReqT, RespT> ListenableFuture<RespT> futureUnaryCall(
+      ClientCall<ReqT, RespT> call, ReqT req) {
+    GrpcFuture<RespT> responseFuture = new GrpcFuture<RespT>(call);
+    asyncUnaryRequestCall(call, req, new UnaryStreamToFuture<RespT>(responseFuture), false);
+    return responseFuture;
+  }
+
+  /**
+   * Returns the result of calling {@link Future#get()} interruptibly on a task known not to throw a
+   * checked exception.
+   *
+   * <p>If interrupted, the interrupt is restored before throwing an exception..
+   *
+   * @throws java.util.concurrent.CancellationException
+   *     if {@code get} throws a {@code CancellationException}.
+   * @throws io.grpc.StatusRuntimeException if {@code get} throws an {@link ExecutionException}
+   *     or an {@link InterruptedException}.
+   */
+  private static <V> V getUnchecked(Future<V> future) {
+    try {
+      return future.get();
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      throw Status.CANCELLED
+          .withDescription("Call was interrupted")
+          .withCause(e)
+          .asRuntimeException();
+    } catch (ExecutionException e) {
+      throw toStatusRuntimeException(e.getCause());
+    }
+  }
+
+  /**
+   * Wraps the given {@link Throwable} in a {@link StatusRuntimeException}. If it contains an
+   * embedded {@link StatusException} or {@link StatusRuntimeException}, the returned exception will
+   * contain the embedded trailers and status, with the given exception as the cause. Otherwise, an
+   * exception will be generated from an {@link Status#UNKNOWN} status.
+   */
+  private static StatusRuntimeException toStatusRuntimeException(Throwable t) {
+    Throwable cause = checkNotNull(t, "t");
+    while (cause != null) {
+      // If we have an embedded status, use it and replace the cause
+      if (cause instanceof StatusException) {
+        StatusException se = (StatusException) cause;
+        return new StatusRuntimeException(se.getStatus(), se.getTrailers());
+      } else if (cause instanceof StatusRuntimeException) {
+        StatusRuntimeException se = (StatusRuntimeException) cause;
+        return new StatusRuntimeException(se.getStatus(), se.getTrailers());
+      }
+      cause = cause.getCause();
+    }
+    return Status.UNKNOWN.withDescription("unexpected exception").withCause(t)
+        .asRuntimeException();
+  }
+
+  /**
+   * Cancels a call, and throws the exception.
+   *
+   * @param t must be a RuntimeException or Error
+   */
+  private static RuntimeException cancelThrow(ClientCall<?, ?> call, Throwable t) {
+    try {
+      call.cancel(null, t);
+    } catch (Throwable e) {
+      assert e instanceof RuntimeException || e instanceof Error;
+      logger.log(Level.SEVERE, "RuntimeException encountered while closing call", e);
+    }
+    if (t instanceof RuntimeException) {
+      throw (RuntimeException) t;
+    } else if (t instanceof Error) {
+      throw (Error) t;
+    }
+    // should be impossible
+    throw new AssertionError(t);
+  }
+
+  private static <ReqT, RespT> void asyncUnaryRequestCall(
+      ClientCall<ReqT, RespT> call, ReqT req, StreamObserver<RespT> responseObserver,
+      boolean streamingResponse) {
+    asyncUnaryRequestCall(
+        call,
+        req,
+        new StreamObserverToCallListenerAdapter<ReqT, RespT>(
+            responseObserver,
+            new CallToStreamObserverAdapter<ReqT>(call),
+            streamingResponse),
+        streamingResponse);
+  }
+
+  private static <ReqT, RespT> void asyncUnaryRequestCall(
+      ClientCall<ReqT, RespT> call,
+      ReqT req,
+      ClientCall.Listener<RespT> responseListener,
+      boolean streamingResponse) {
+    startCall(call, responseListener, streamingResponse);
+    try {
+      call.sendMessage(req);
+      call.halfClose();
+    } catch (RuntimeException e) {
+      throw cancelThrow(call, e);
+    } catch (Error e) {
+      throw cancelThrow(call, e);
+    }
+  }
+
+  private static <ReqT, RespT> StreamObserver<ReqT> asyncStreamingRequestCall(
+      ClientCall<ReqT, RespT> call,
+      StreamObserver<RespT> responseObserver,
+      boolean streamingResponse) {
+    CallToStreamObserverAdapter<ReqT> adapter = new CallToStreamObserverAdapter<ReqT>(call);
+    startCall(
+        call,
+        new StreamObserverToCallListenerAdapter<ReqT, RespT>(
+            responseObserver, adapter, streamingResponse),
+        streamingResponse);
+    return adapter;
+  }
+
+  private static <ReqT, RespT> void startCall(
+      ClientCall<ReqT, RespT> call,
+      ClientCall.Listener<RespT> responseListener,
+      boolean streamingResponse) {
+    call.start(responseListener, new Metadata());
+    if (streamingResponse) {
+      call.request(1);
+    } else {
+      // Initially ask for two responses from flow-control so that if a misbehaving server sends
+      // more than one responses, we can catch it and fail it in the listener.
+      call.request(2);
+    }
+  }
+
+  private static final class CallToStreamObserverAdapter<T> extends ClientCallStreamObserver<T> {
+    private boolean frozen;
+    private final ClientCall<T, ?> call;
+    private Runnable onReadyHandler;
+    private boolean autoFlowControlEnabled = true;
+
+    // Non private to avoid synthetic class
+    CallToStreamObserverAdapter(ClientCall<T, ?> call) {
+      this.call = call;
+    }
+
+    private void freeze() {
+      this.frozen = true;
+    }
+
+    @Override
+    public void onNext(T value) {
+      call.sendMessage(value);
+    }
+
+    @Override
+    public void onError(Throwable t) {
+      call.cancel("Cancelled by client with StreamObserver.onError()", t);
+    }
+
+    @Override
+    public void onCompleted() {
+      call.halfClose();
+    }
+
+    @Override
+    public boolean isReady() {
+      return call.isReady();
+    }
+
+    @Override
+    public void setOnReadyHandler(Runnable onReadyHandler) {
+      if (frozen) {
+        throw new IllegalStateException("Cannot alter onReadyHandler after call started");
+      }
+      this.onReadyHandler = onReadyHandler;
+    }
+
+    @Override
+    public void disableAutoInboundFlowControl() {
+      if (frozen) {
+        throw new IllegalStateException("Cannot disable auto flow control call started");
+      }
+      autoFlowControlEnabled = false;
+    }
+
+    @Override
+    public void request(int count) {
+      call.request(count);
+    }
+
+    @Override
+    public void setMessageCompression(boolean enable) {
+      call.setMessageCompression(enable);
+    }
+
+    @Override
+    public void cancel(@Nullable String message, @Nullable Throwable cause) {
+      call.cancel(message, cause);
+    }
+  }
+
+  private static final class StreamObserverToCallListenerAdapter<ReqT, RespT>
+      extends ClientCall.Listener<RespT> {
+    private final StreamObserver<RespT> observer;
+    private final CallToStreamObserverAdapter<ReqT> adapter;
+    private final boolean streamingResponse;
+    private boolean firstResponseReceived;
+
+    // Non private to avoid synthetic class
+    StreamObserverToCallListenerAdapter(
+        StreamObserver<RespT> observer,
+        CallToStreamObserverAdapter<ReqT> adapter,
+        boolean streamingResponse) {
+      this.observer = observer;
+      this.streamingResponse = streamingResponse;
+      this.adapter = adapter;
+      if (observer instanceof ClientResponseObserver) {
+        @SuppressWarnings("unchecked")
+        ClientResponseObserver<ReqT, RespT> clientResponseObserver =
+            (ClientResponseObserver<ReqT, RespT>) observer;
+        clientResponseObserver.beforeStart(adapter);
+      }
+      adapter.freeze();
+    }
+
+    @Override
+    public void onHeaders(Metadata headers) {
+    }
+
+    @Override
+    public void onMessage(RespT message) {
+      if (firstResponseReceived && !streamingResponse) {
+        throw Status.INTERNAL
+            .withDescription("More than one responses received for unary or client-streaming call")
+            .asRuntimeException();
+      }
+      firstResponseReceived = true;
+      observer.onNext(message);
+
+      if (streamingResponse && adapter.autoFlowControlEnabled) {
+        // Request delivery of the next inbound message.
+        adapter.request(1);
+      }
+    }
+
+    @Override
+    public void onClose(Status status, Metadata trailers) {
+      if (status.isOk()) {
+        observer.onCompleted();
+      } else {
+        observer.onError(status.asRuntimeException(trailers));
+      }
+    }
+
+    @Override
+    public void onReady() {
+      if (adapter.onReadyHandler != null) {
+        adapter.onReadyHandler.run();
+      }
+    }
+  }
+
+  /**
+   * Completes a {@link GrpcFuture} using {@link StreamObserver} events.
+   */
+  private static final class UnaryStreamToFuture<RespT> extends ClientCall.Listener<RespT> {
+    private final GrpcFuture<RespT> responseFuture;
+    private RespT value;
+
+    // Non private to avoid synthetic class
+    UnaryStreamToFuture(GrpcFuture<RespT> responseFuture) {
+      this.responseFuture = responseFuture;
+    }
+
+    @Override
+    public void onHeaders(Metadata headers) {
+    }
+
+    @Override
+    public void onMessage(RespT value) {
+      if (this.value != null) {
+        throw Status.INTERNAL.withDescription("More than one value received for unary call")
+            .asRuntimeException();
+      }
+      this.value = value;
+    }
+
+    @Override
+    public void onClose(Status status, Metadata trailers) {
+      if (status.isOk()) {
+        if (value == null) {
+          // No value received so mark the future as an error
+          responseFuture.setException(
+              Status.INTERNAL.withDescription("No value received for unary call")
+                  .asRuntimeException(trailers));
+        }
+        responseFuture.set(value);
+      } else {
+        responseFuture.setException(status.asRuntimeException(trailers));
+      }
+    }
+  }
+
+  private static final class GrpcFuture<RespT> extends AbstractFuture<RespT> {
+    private final ClientCall<?, RespT> call;
+
+    // Non private to avoid synthetic class
+    GrpcFuture(ClientCall<?, RespT> call) {
+      this.call = call;
+    }
+
+    @Override
+    protected void interruptTask() {
+      call.cancel("GrpcFuture was cancelled", null);
+    }
+
+    @Override
+    protected boolean set(@Nullable RespT resp) {
+      return super.set(resp);
+    }
+
+    @Override
+    protected boolean setException(Throwable throwable) {
+      return super.setException(throwable);
+    }
+
+    @SuppressWarnings("MissingOverride") // Add @Override once Java 6 support is dropped
+    protected String pendingToString() {
+      return MoreObjects.toStringHelper(this).add("clientCall", call).toString();
+    }
+  }
+
+  /**
+   * Convert events on a {@link io.grpc.ClientCall.Listener} into a blocking {@link Iterator}.
+   *
+   * <p>The class is not thread-safe, but it does permit {@link ClientCall.Listener} calls in a
+   * separate thread from {@link Iterator} calls.
+   */
+  // TODO(ejona86): determine how to allow ClientCall.cancel() in case of application error.
+  private static final class BlockingResponseStream<T> implements Iterator<T> {
+    // Due to flow control, only needs to hold up to 2 items: 1 for value, 1 for close.
+    private final BlockingQueue<Object> buffer = new ArrayBlockingQueue<Object>(2);
+    private final ClientCall.Listener<T> listener = new QueuingListener();
+    private final ClientCall<?, T> call;
+    /** May be null. */
+    private final ThreadlessExecutor threadless;
+    // Only accessed when iterating.
+    private Object last;
+
+    // Non private to avoid synthetic class
+    BlockingResponseStream(ClientCall<?, T> call) {
+      this(call, null);
+    }
+
+    // Non private to avoid synthetic class
+    BlockingResponseStream(ClientCall<?, T> call, ThreadlessExecutor threadless) {
+      this.call = call;
+      this.threadless = threadless;
+    }
+
+    ClientCall.Listener<T> listener() {
+      return listener;
+    }
+
+    private Object waitForNext() throws InterruptedException {
+      if (threadless == null) {
+        return buffer.take();
+      } else {
+        Object next = buffer.poll();
+        while (next == null) {
+          threadless.waitAndDrain();
+          next = buffer.poll();
+        }
+        return next;
+      }
+    }
+
+    @Override
+    public boolean hasNext() {
+      if (last == null) {
+        try {
+          // Will block here indefinitely waiting for content. RPC timeouts defend against permanent
+          // hangs here as the call will become closed.
+          last = waitForNext();
+        } catch (InterruptedException ie) {
+          Thread.currentThread().interrupt();
+          throw Status.CANCELLED.withDescription("interrupted").withCause(ie).asRuntimeException();
+        }
+      }
+      if (last instanceof StatusRuntimeException) {
+        // Rethrow the exception with a new stacktrace.
+        StatusRuntimeException e = (StatusRuntimeException) last;
+        throw e.getStatus().asRuntimeException(e.getTrailers());
+      }
+      return last != this;
+    }
+
+    @Override
+    public T next() {
+      if (!hasNext()) {
+        throw new NoSuchElementException();
+      }
+      try {
+        call.request(1);
+        @SuppressWarnings("unchecked")
+        T tmp = (T) last;
+        return tmp;
+      } finally {
+        last = null;
+      }
+    }
+
+    @Override
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+
+    private final class QueuingListener extends ClientCall.Listener<T> {
+      // Non private to avoid synthetic class
+      QueuingListener() {}
+
+      private boolean done = false;
+
+      @Override
+      public void onHeaders(Metadata headers) {
+      }
+
+      @Override
+      public void onMessage(T value) {
+        Preconditions.checkState(!done, "ClientCall already closed");
+        buffer.add(value);
+      }
+
+      @Override
+      public void onClose(Status status, Metadata trailers) {
+        Preconditions.checkState(!done, "ClientCall already closed");
+        if (status.isOk()) {
+          buffer.add(BlockingResponseStream.this);
+        } else {
+          buffer.add(status.asRuntimeException(trailers));
+        }
+        done = true;
+      }
+    }
+  }
+
+  private static final class ThreadlessExecutor implements Executor {
+    private static final Logger log = Logger.getLogger(ThreadlessExecutor.class.getName());
+
+    private final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
+
+    // Non private to avoid synthetic class
+    ThreadlessExecutor() {}
+
+    /**
+     * Waits until there is a Runnable, then executes it and all queued Runnables after it.
+     */
+    public void waitAndDrain() throws InterruptedException {
+      Runnable runnable = queue.take();
+      while (runnable != null) {
+        try {
+          runnable.run();
+        } catch (Throwable t) {
+          log.log(Level.WARNING, "Runnable threw exception", t);
+        }
+        runnable = queue.poll();
+      }
+    }
+
+    @Override
+    public void execute(Runnable runnable) {
+      queue.add(runnable);
+    }
+  }
+}
diff --git a/stub/src/main/java/io/grpc/stub/ClientResponseObserver.java b/stub/src/main/java/io/grpc/stub/ClientResponseObserver.java
new file mode 100644
index 0000000..2e7ea35
--- /dev/null
+++ b/stub/src/main/java/io/grpc/stub/ClientResponseObserver.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+import io.grpc.ExperimentalApi;
+
+/**
+ * Specialization of {@link StreamObserver} implemented by clients in order to interact with the
+ * advanced features of a call such as flow-control.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4693")
+public interface ClientResponseObserver<ReqT, RespT> extends StreamObserver<RespT> {
+  /**
+   * Called by the runtime priot to the start of a call to provide a reference to the
+   * {@link ClientCallStreamObserver} for the outbound stream. This can be used to listen to
+   * onReady events, disable auto inbound flow and perform other advanced functions.
+   *
+   * <p>Only the methods {@link ClientCallStreamObserver#setOnReadyHandler(Runnable)} and
+   * {@link ClientCallStreamObserver#disableAutoInboundFlowControl()} may be called within this
+   * callback
+   *
+   * <pre>
+   *   // Copy an iterator to the request stream under flow-control
+   *   someStub.fullDuplexCall(new ClientResponseObserver&lt;ReqT, RespT&gt;() {
+   *     public void beforeStart(final ClientCallStreamObserver&lt;Req&gt; requestStream) {
+   *       StreamObservers.copyWithFlowControl(someIterator, requestStream);
+   *   });
+   * </pre>
+   */
+  void beforeStart(final ClientCallStreamObserver<ReqT> requestStream);
+}
diff --git a/stub/src/main/java/io/grpc/stub/MetadataUtils.java b/stub/src/main/java/io/grpc/stub/MetadataUtils.java
new file mode 100644
index 0000000..ad23e4e
--- /dev/null
+++ b/stub/src/main/java/io/grpc/stub/MetadataUtils.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ExperimentalApi;
+import io.grpc.ForwardingClientCall.SimpleForwardingClientCall;
+import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Utility functions for binding and receiving headers.
+ */
+public final class MetadataUtils {
+  // Prevent instantiation
+  private MetadataUtils() {}
+
+  /**
+   * Attaches a set of request headers to a stub.
+   *
+   * @param stub to bind the headers to.
+   * @param extraHeaders the headers to be passed by each call on the returned stub.
+   * @return an implementation of the stub with {@code extraHeaders} bound to each call.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1789")
+  public static <T extends AbstractStub<T>> T attachHeaders(T stub, Metadata extraHeaders) {
+    return stub.withInterceptors(newAttachHeadersInterceptor(extraHeaders));
+  }
+
+  /**
+   * Returns a client interceptor that attaches a set of headers to requests.
+   *
+   * @param extraHeaders the headers to be passed by each call that is processed by the returned
+   *                     interceptor
+   */
+  public static ClientInterceptor newAttachHeadersInterceptor(Metadata extraHeaders) {
+    return new HeaderAttachingClientInterceptor(extraHeaders);
+  }
+
+  private static final class HeaderAttachingClientInterceptor implements ClientInterceptor {
+
+    private final Metadata extraHeaders;
+
+    // Non private to avoid synthetic class
+    HeaderAttachingClientInterceptor(Metadata extraHeaders) {
+      this.extraHeaders = checkNotNull(extraHeaders, extraHeaders);
+    }
+
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+        MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+      return new HeaderAttachingClientCall<ReqT, RespT>(next.newCall(method, callOptions));
+    }
+
+    private final class HeaderAttachingClientCall<ReqT, RespT>
+        extends SimpleForwardingClientCall<ReqT, RespT> {
+
+      // Non private to avoid synthetic class
+      HeaderAttachingClientCall(ClientCall<ReqT, RespT> call) {
+        super(call);
+      }
+
+      @Override
+      public void start(Listener<RespT> responseListener, Metadata headers) {
+        headers.merge(extraHeaders);
+        super.start(responseListener, headers);
+      }
+    }
+  }
+
+  /**
+   * Captures the last received metadata for a stub. Useful for testing
+   *
+   * @param stub to capture for
+   * @param headersCapture to record the last received headers
+   * @param trailersCapture to record the last received trailers
+   * @return an implementation of the stub that allows to access the last received call's
+   *         headers and trailers via {@code headersCapture} and {@code trailersCapture}.
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1789")
+  public static <T extends AbstractStub<T>> T captureMetadata(
+      T stub,
+      AtomicReference<Metadata> headersCapture,
+      AtomicReference<Metadata> trailersCapture) {
+    return stub.withInterceptors(
+        newCaptureMetadataInterceptor(headersCapture, trailersCapture));
+  }
+
+  /**
+   * Captures the last received metadata on a channel. Useful for testing.
+   *
+   * @param headersCapture to record the last received headers
+   * @param trailersCapture to record the last received trailers
+   * @return an implementation of the channel with captures installed.
+   */
+  public static ClientInterceptor newCaptureMetadataInterceptor(
+      AtomicReference<Metadata> headersCapture, AtomicReference<Metadata> trailersCapture) {
+    return new MetadataCapturingClientInterceptor(headersCapture, trailersCapture);
+  }
+
+  private static final class MetadataCapturingClientInterceptor implements ClientInterceptor {
+
+    final AtomicReference<Metadata> headersCapture;
+    final AtomicReference<Metadata> trailersCapture;
+
+    // Non private to avoid synthetic class
+    MetadataCapturingClientInterceptor(
+        AtomicReference<Metadata> headersCapture, AtomicReference<Metadata> trailersCapture) {
+      this.headersCapture = checkNotNull(headersCapture, "headersCapture");
+      this.trailersCapture = checkNotNull(trailersCapture, "trailersCapture");
+    }
+
+    @Override
+    public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
+        MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
+      return new MetadataCapturingClientCall<ReqT, RespT>(next.newCall(method, callOptions));
+    }
+
+    private final class MetadataCapturingClientCall<ReqT, RespT>
+        extends SimpleForwardingClientCall<ReqT, RespT> {
+
+      // Non private to avoid synthetic class
+      MetadataCapturingClientCall(ClientCall<ReqT, RespT> call) {
+        super(call);
+      }
+
+      @Override
+      public void start(ClientCall.Listener<RespT> responseListener, Metadata headers) {
+        headersCapture.set(null);
+        trailersCapture.set(null);
+        super.start(new MetadataCapturingClientCallListener(responseListener), headers);
+      }
+
+      private final class MetadataCapturingClientCallListener
+          extends SimpleForwardingClientCallListener<RespT> {
+
+        MetadataCapturingClientCallListener(ClientCall.Listener<RespT> responseListener) {
+          super(responseListener);
+        }
+
+        @Override
+        public void onHeaders(Metadata headers) {
+          headersCapture.set(headers);
+          super.onHeaders(headers);
+        }
+
+        @Override
+        public void onClose(Status status, Metadata trailers) {
+          trailersCapture.set(trailers);
+          super.onClose(status, trailers);
+        }
+      }
+    }
+  }
+}
diff --git a/stub/src/main/java/io/grpc/stub/ServerCallStreamObserver.java b/stub/src/main/java/io/grpc/stub/ServerCallStreamObserver.java
new file mode 100644
index 0000000..721fc8c
--- /dev/null
+++ b/stub/src/main/java/io/grpc/stub/ServerCallStreamObserver.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+import io.grpc.ExperimentalApi;
+
+/**
+ * A refinement of {@link CallStreamObserver} to allows for interaction with call
+ * cancellation events on the server side.
+ *
+ * <p>Like {@code StreamObserver}, implementations are not required to be thread-safe; if multiple
+ * threads will be writing to an instance concurrently, the application must synchronize its calls.
+ *
+ * <p>DO NOT MOCK: The API is too complex to reliably mock. Use InProcessChannelBuilder to create
+ * "real" RPCs suitable for testing and interact with the server using a normal client stub.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1788")
+public abstract class ServerCallStreamObserver<V> extends CallStreamObserver<V> {
+
+  /**
+   * If {@code true} indicates that the call has been cancelled by the remote peer.
+   *
+   * <p>This method may safely be called concurrently from multiple threads.
+   */
+  public abstract boolean isCancelled();
+
+  /**
+   * Set a {@link Runnable} that will be called if the calls  {@link #isCancelled()} state
+   * changes from {@code false} to {@code true}. It is guaranteed that execution of the
+   * {@link Runnable} are serialized with calls to the 'inbound' {@link StreamObserver}.
+   *
+   * <p>Note that the handler may be called some time after {@link #isCancelled} has transitioned to
+   * {@code true} as other callbacks may still be executing in the 'inbound' observer.
+   *
+   * @param onCancelHandler to call when client has cancelled the call.
+   */
+  public abstract void setOnCancelHandler(Runnable onCancelHandler);
+
+  /**
+   * Sets the compression algorithm to use for the call.  May only be called before sending any
+   * messages.
+   *
+   * @param compression the compression algorithm to use.
+   */
+  public abstract void setCompression(String compression);
+}
diff --git a/stub/src/main/java/io/grpc/stub/ServerCalls.java b/stub/src/main/java/io/grpc/stub/ServerCalls.java
new file mode 100644
index 0000000..aac13a5
--- /dev/null
+++ b/stub/src/main/java/io/grpc/stub/ServerCalls.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.Status;
+
+/**
+ * Utility functions for adapting {@link ServerCallHandler}s to application service implementation,
+ * meant to be used by the generated code.
+ */
+public final class ServerCalls {
+
+  @VisibleForTesting
+  static final String TOO_MANY_REQUESTS = "Too many requests";
+  @VisibleForTesting
+  static final String MISSING_REQUEST = "Half-closed without a request";
+
+  private ServerCalls() {
+  }
+
+  /**
+   * Creates a {@link ServerCallHandler} for a unary call method of the service.
+   *
+   * @param method an adaptor to the actual method on the service implementation.
+   */
+  public static <ReqT, RespT> ServerCallHandler<ReqT, RespT> asyncUnaryCall(
+      UnaryMethod<ReqT, RespT> method) {
+    return asyncUnaryRequestCall(method);
+  }
+
+  /**
+   * Creates a {@link ServerCallHandler} for a server streaming method of the service.
+   *
+   * @param method an adaptor to the actual method on the service implementation.
+   */
+  public static <ReqT, RespT> ServerCallHandler<ReqT, RespT> asyncServerStreamingCall(
+      ServerStreamingMethod<ReqT, RespT> method) {
+    return asyncUnaryRequestCall(method);
+  }
+
+  /**
+   * Creates a {@link ServerCallHandler} for a client streaming method of the service.
+   *
+   * @param method an adaptor to the actual method on the service implementation.
+   */
+  public static <ReqT, RespT> ServerCallHandler<ReqT, RespT> asyncClientStreamingCall(
+      ClientStreamingMethod<ReqT, RespT> method) {
+    return asyncStreamingRequestCall(method);
+  }
+
+  /**
+   * Creates a {@link ServerCallHandler} for a bidi streaming method of the service.
+   *
+   * @param method an adaptor to the actual method on the service implementation.
+   */
+  public static <ReqT, RespT> ServerCallHandler<ReqT, RespT> asyncBidiStreamingCall(
+      BidiStreamingMethod<ReqT, RespT> method) {
+    return asyncStreamingRequestCall(method);
+  }
+
+  /**
+   * Adaptor to a unary call method.
+   */
+  public interface UnaryMethod<ReqT, RespT> extends UnaryRequestMethod<ReqT, RespT> {}
+
+  /**
+   * Adaptor to a server streaming method.
+   */
+  public interface ServerStreamingMethod<ReqT, RespT> extends UnaryRequestMethod<ReqT, RespT> {}
+
+  /**
+   * Adaptor to a client streaming method.
+   */
+  public interface ClientStreamingMethod<ReqT, RespT> extends StreamingRequestMethod<ReqT, RespT> {}
+
+  /**
+   * Adaptor to a bidirectional streaming method.
+   */
+  public interface BidiStreamingMethod<ReqT, RespT> extends StreamingRequestMethod<ReqT, RespT> {}
+
+  private static final class UnaryServerCallHandler<ReqT, RespT>
+      implements ServerCallHandler<ReqT, RespT> {
+
+    private final UnaryRequestMethod<ReqT, RespT> method;
+
+    // Non private to avoid synthetic class
+    UnaryServerCallHandler(UnaryRequestMethod<ReqT, RespT> method) {
+      this.method = method;
+    }
+
+    @Override
+    public ServerCall.Listener<ReqT> startCall(ServerCall<ReqT, RespT> call, Metadata headers) {
+      Preconditions.checkArgument(
+          call.getMethodDescriptor().getType().clientSendsOneMessage(),
+          "asyncUnaryRequestCall is only for clientSendsOneMessage methods");
+      ServerCallStreamObserverImpl<ReqT, RespT> responseObserver =
+          new ServerCallStreamObserverImpl<ReqT, RespT>(call);
+      // We expect only 1 request, but we ask for 2 requests here so that if a misbehaving client
+      // sends more than 1 requests, ServerCall will catch it. Note that disabling auto
+      // inbound flow control has no effect on unary calls.
+      call.request(2);
+      return new UnaryServerCallListener(responseObserver, call);
+    }
+
+    private final class UnaryServerCallListener extends ServerCall.Listener<ReqT> {
+      private final ServerCall<ReqT, RespT> call;
+      private final ServerCallStreamObserverImpl<ReqT, RespT> responseObserver;
+      private boolean canInvoke = true;
+      private ReqT request;
+
+      // Non private to avoid synthetic class
+      UnaryServerCallListener(
+          ServerCallStreamObserverImpl<ReqT, RespT> responseObserver,
+          ServerCall<ReqT, RespT> call) {
+        this.call = call;
+        this.responseObserver = responseObserver;
+      }
+
+      @Override
+      public void onMessage(ReqT request) {
+        if (this.request != null) {
+          // Safe to close the call, because the application has not yet been invoked
+          call.close(
+              Status.INTERNAL.withDescription(TOO_MANY_REQUESTS),
+              new Metadata());
+          canInvoke = false;
+          return;
+        }
+
+        // We delay calling method.invoke() until onHalfClose() to make sure the client
+        // half-closes.
+        this.request = request;
+      }
+
+      @Override
+      public void onHalfClose() {
+        if (!canInvoke) {
+          return;
+        }
+        if (request == null) {
+          // Safe to close the call, because the application has not yet been invoked
+          call.close(
+              Status.INTERNAL.withDescription(MISSING_REQUEST),
+              new Metadata());
+          return;
+        }
+
+        method.invoke(request, responseObserver);
+        responseObserver.freeze();
+        if (call.isReady()) {
+          // Since we are calling invoke in halfClose we have missed the onReady
+          // event from the transport so recover it here.
+          onReady();
+        }
+      }
+
+      @Override
+      public void onCancel() {
+        responseObserver.cancelled = true;
+        if (responseObserver.onCancelHandler != null) {
+          responseObserver.onCancelHandler.run();
+        }
+      }
+
+      @Override
+      public void onReady() {
+        if (responseObserver.onReadyHandler != null) {
+          responseObserver.onReadyHandler.run();
+        }
+      }
+    }
+  }
+
+  /**
+   * Creates a {@link ServerCallHandler} for a unary request call method of the service.
+   *
+   * @param method an adaptor to the actual method on the service implementation.
+   */
+  private static <ReqT, RespT> ServerCallHandler<ReqT, RespT> asyncUnaryRequestCall(
+      UnaryRequestMethod<ReqT, RespT> method) {
+    return new UnaryServerCallHandler<ReqT, RespT>(method);
+  }
+
+  private static final class StreamingServerCallHandler<ReqT, RespT>
+      implements ServerCallHandler<ReqT, RespT> {
+
+    private final StreamingRequestMethod<ReqT, RespT> method;
+
+    // Non private to avoid synthetic class
+    StreamingServerCallHandler(StreamingRequestMethod<ReqT, RespT> method) {
+      this.method = method;
+    }
+
+    @Override
+    public ServerCall.Listener<ReqT> startCall(ServerCall<ReqT, RespT> call, Metadata headers) {
+      ServerCallStreamObserverImpl<ReqT, RespT> responseObserver =
+          new ServerCallStreamObserverImpl<ReqT, RespT>(call);
+      StreamObserver<ReqT> requestObserver = method.invoke(responseObserver);
+      responseObserver.freeze();
+      if (responseObserver.autoFlowControlEnabled) {
+        call.request(1);
+      }
+      return new StreamingServerCallListener(requestObserver, responseObserver, call);
+    }
+
+    private final class StreamingServerCallListener extends ServerCall.Listener<ReqT> {
+
+      private final StreamObserver<ReqT> requestObserver;
+      private final ServerCallStreamObserverImpl<ReqT, RespT> responseObserver;
+      private final ServerCall<ReqT, RespT> call;
+      private boolean halfClosed = false;
+
+      // Non private to avoid synthetic class
+      StreamingServerCallListener(
+          StreamObserver<ReqT> requestObserver,
+          ServerCallStreamObserverImpl<ReqT, RespT> responseObserver,
+          ServerCall<ReqT, RespT> call) {
+        this.requestObserver = requestObserver;
+        this.responseObserver = responseObserver;
+        this.call = call;
+      }
+
+      @Override
+      public void onMessage(ReqT request) {
+        requestObserver.onNext(request);
+
+        // Request delivery of the next inbound message.
+        if (responseObserver.autoFlowControlEnabled) {
+          call.request(1);
+        }
+      }
+
+      @Override
+      public void onHalfClose() {
+        halfClosed = true;
+        requestObserver.onCompleted();
+      }
+
+      @Override
+      public void onCancel() {
+        responseObserver.cancelled = true;
+        if (responseObserver.onCancelHandler != null) {
+          responseObserver.onCancelHandler.run();
+        }
+        if (!halfClosed) {
+          requestObserver.onError(
+              Status.CANCELLED
+                  .withDescription("cancelled before receiving half close")
+                  .asRuntimeException());
+        }
+      }
+
+      @Override
+      public void onReady() {
+        if (responseObserver.onReadyHandler != null) {
+          responseObserver.onReadyHandler.run();
+        }
+      }
+    }
+  }
+
+  /**
+   * Creates a {@link ServerCallHandler} for a streaming request call method of the service.
+   *
+   * @param method an adaptor to the actual method on the service implementation.
+   */
+  private static <ReqT, RespT> ServerCallHandler<ReqT, RespT> asyncStreamingRequestCall(
+      StreamingRequestMethod<ReqT, RespT> method) {
+    return new StreamingServerCallHandler<ReqT, RespT>(method);
+  }
+
+  private interface UnaryRequestMethod<ReqT, RespT> {
+    void invoke(ReqT request, StreamObserver<RespT> responseObserver);
+  }
+
+  private interface StreamingRequestMethod<ReqT, RespT> {
+    StreamObserver<ReqT> invoke(StreamObserver<RespT> responseObserver);
+  }
+
+  private static final class ServerCallStreamObserverImpl<ReqT, RespT>
+      extends ServerCallStreamObserver<RespT> {
+    final ServerCall<ReqT, RespT> call;
+    volatile boolean cancelled;
+    private boolean frozen;
+    private boolean autoFlowControlEnabled = true;
+    private boolean sentHeaders;
+    private Runnable onReadyHandler;
+    private Runnable onCancelHandler;
+
+    // Non private to avoid synthetic class
+    ServerCallStreamObserverImpl(ServerCall<ReqT, RespT> call) {
+      this.call = call;
+    }
+
+    private void freeze() {
+      this.frozen = true;
+    }
+
+    @Override
+    public void setMessageCompression(boolean enable) {
+      call.setMessageCompression(enable);
+    }
+
+    @Override
+    public void setCompression(String compression) {
+      call.setCompression(compression);
+    }
+
+    @Override
+    public void onNext(RespT response) {
+      if (cancelled) {
+        throw Status.CANCELLED.withDescription("call already cancelled").asRuntimeException();
+      }
+      if (!sentHeaders) {
+        call.sendHeaders(new Metadata());
+        sentHeaders = true;
+      }
+      call.sendMessage(response);
+    }
+
+    @Override
+    public void onError(Throwable t) {
+      Metadata metadata = Status.trailersFromThrowable(t);
+      if (metadata == null) {
+        metadata = new Metadata();
+      }
+      call.close(Status.fromThrowable(t), metadata);
+    }
+
+    @Override
+    public void onCompleted() {
+      if (cancelled) {
+        throw Status.CANCELLED.withDescription("call already cancelled").asRuntimeException();
+      } else {
+        call.close(Status.OK, new Metadata());
+      }
+    }
+
+    @Override
+    public boolean isReady() {
+      return call.isReady();
+    }
+
+    @Override
+    public void setOnReadyHandler(Runnable r) {
+      checkState(!frozen, "Cannot alter onReadyHandler after initialization");
+      this.onReadyHandler = r;
+    }
+
+    @Override
+    public boolean isCancelled() {
+      return call.isCancelled();
+    }
+
+    @Override
+    public void setOnCancelHandler(Runnable onCancelHandler) {
+      checkState(!frozen, "Cannot alter onCancelHandler after initialization");
+      this.onCancelHandler = onCancelHandler;
+    }
+
+    @Override
+    public void disableAutoInboundFlowControl() {
+      checkState(!frozen, "Cannot disable auto flow control after initialization");
+      autoFlowControlEnabled = false;
+    }
+
+    @Override
+    public void request(int count) {
+      call.request(count);
+    }
+  }
+
+  /**
+   * Sets unimplemented status for method on given response stream for unary call.
+   *
+   * @param methodDescriptor of method for which error will be thrown.
+   * @param responseObserver on which error will be set.
+   */
+  public static void asyncUnimplementedUnaryCall(
+      MethodDescriptor<?, ?> methodDescriptor, StreamObserver<?> responseObserver) {
+    checkNotNull(methodDescriptor, "methodDescriptor");
+    checkNotNull(responseObserver, "responseObserver");
+    responseObserver.onError(Status.UNIMPLEMENTED
+        .withDescription(String.format("Method %s is unimplemented",
+            methodDescriptor.getFullMethodName()))
+        .asRuntimeException());
+  }
+
+  /**
+   * Sets unimplemented status for streaming call.
+   *
+   * @param methodDescriptor of method for which error will be thrown.
+   * @param responseObserver on which error will be set.
+   */
+  public static <T> StreamObserver<T> asyncUnimplementedStreamingCall(
+      MethodDescriptor<?, ?> methodDescriptor, StreamObserver<?> responseObserver) {
+    // NB: For streaming call we want to do the same as for unary call. Fail-fast by setting error
+    // on responseObserver and then return no-op observer.
+    asyncUnimplementedUnaryCall(methodDescriptor, responseObserver);
+    return new NoopStreamObserver<T>();
+  }
+
+  /**
+   * No-op implementation of StreamObserver. Used in abstract stubs for default implementations of
+   * methods which throws UNIMPLEMENTED error and tests.
+   */
+  static class NoopStreamObserver<V> implements StreamObserver<V> {
+    @Override
+    public void onNext(V value) {
+    }
+
+    @Override
+    public void onError(Throwable t) {
+    }
+
+    @Override
+    public void onCompleted() {
+    }
+  }
+}
diff --git a/stub/src/main/java/io/grpc/stub/StreamObserver.java b/stub/src/main/java/io/grpc/stub/StreamObserver.java
new file mode 100644
index 0000000..92040d9
--- /dev/null
+++ b/stub/src/main/java/io/grpc/stub/StreamObserver.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+/**
+ * Receives notifications from an observable stream of messages.
+ *
+ * <p>It is used by both the client stubs and service implementations for sending or receiving
+ * stream messages. It is used for all {@link io.grpc.MethodDescriptor.MethodType}, including
+ * {@code UNARY} calls.  For outgoing messages, a {@code StreamObserver} is provided by the GRPC
+ * library to the application. For incoming messages, the application implements the
+ * {@code StreamObserver} and passes it to the GRPC library for receiving.
+ *
+ * <p>Implementations are not required to be thread-safe (but should be
+ * <a href="http://www.ibm.com/developerworks/library/j-jtp09263/">thread-compatible</a>).
+ * Separate {@code StreamObserver}s do
+ * not need to be synchronized together; incoming and outgoing directions are independent.
+ * Since individual {@code StreamObserver}s are not thread-safe, if multiple threads will be
+ * writing to a {@code StreamObserver} concurrently, the application must synchronize calls.
+ */
+public interface StreamObserver<V>  {
+  /**
+   * Receives a value from the stream.
+   *
+   * <p>Can be called many times but is never called after {@link #onError(Throwable)} or {@link
+   * #onCompleted()} are called.
+   *
+   * <p>Unary calls must invoke onNext at most once.  Clients may invoke onNext at most once for
+   * server streaming calls, but may receive many onNext callbacks.  Servers may invoke onNext at
+   * most once for client streaming calls, but may receive many onNext callbacks.
+   *
+   * <p>If an exception is thrown by an implementation the caller is expected to terminate the
+   * stream by calling {@link #onError(Throwable)} with the caught exception prior to
+   * propagating it.
+   *
+   * @param value the value passed to the stream
+   */
+  void onNext(V value);
+
+  /**
+   * Receives a terminating error from the stream.
+   *
+   * <p>May only be called once and if called it must be the last method called. In particular if an
+   * exception is thrown by an implementation of {@code onError} no further calls to any method are
+   * allowed.
+   *
+   * <p>{@code t} should be a {@link io.grpc.StatusException} or {@link
+   * io.grpc.StatusRuntimeException}, but other {@code Throwable} types are possible. Callers should
+   * generally convert from a {@link io.grpc.Status} via {@link io.grpc.Status#asException()} or
+   * {@link io.grpc.Status#asRuntimeException()}. Implementations should generally convert to a
+   * {@code Status} via {@link io.grpc.Status#fromThrowable(Throwable)}.
+   *
+   * @param t the error occurred on the stream
+   */
+  void onError(Throwable t);
+
+  /**
+   * Receives a notification of successful stream completion.
+   *
+   * <p>May only be called once and if called it must be the last method called. In particular if an
+   * exception is thrown by an implementation of {@code onCompleted} no further calls to any method
+   * are allowed.
+   */
+  void onCompleted();
+}
diff --git a/stub/src/main/java/io/grpc/stub/StreamObservers.java b/stub/src/main/java/io/grpc/stub/StreamObservers.java
new file mode 100644
index 0000000..f7ccc97
--- /dev/null
+++ b/stub/src/main/java/io/grpc/stub/StreamObservers.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+import com.google.common.base.Preconditions;
+import io.grpc.ExperimentalApi;
+import java.util.Iterator;
+
+/**
+ * Utility functions for working with {@link StreamObserver} and it's common subclasses like
+ * {@link CallStreamObserver}.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/4694")
+public final class StreamObservers {
+  /**
+   * Copy the values of an {@link Iterator} to the target {@link CallStreamObserver} while properly
+   * accounting for outbound flow-control.  After calling this method, {@code target} should no
+   * longer be used.
+   *
+   * <p>For clients this method is safe to call inside {@link ClientResponseObserver#beforeStart},
+   * on servers it is safe to call inside the service method implementation.
+   * </p>
+   *
+   * @param source of values expressed as an {@link Iterator}.
+   * @param target {@link CallStreamObserver} which accepts values from the source.
+   */
+  public static <V> void copyWithFlowControl(final Iterator<V> source,
+      final CallStreamObserver<V> target) {
+    Preconditions.checkNotNull(source, "source");
+    Preconditions.checkNotNull(target, "target");
+
+    final class FlowControllingOnReadyHandler implements Runnable {
+      @Override
+      public void run() {
+        while (target.isReady() && source.hasNext()) {
+          target.onNext(source.next());
+        }
+        if (!source.hasNext()) {
+          target.onCompleted();
+        }
+      }
+    }
+
+    target.setOnReadyHandler(new FlowControllingOnReadyHandler());
+  }
+
+  /**
+   * Copy the values of an {@link Iterable} to the target {@link CallStreamObserver} while properly
+   * accounting for outbound flow-control.  After calling this method, {@code target} should no
+   * longer be used.
+   *
+   * <p>For clients this method is safe to call inside {@link ClientResponseObserver#beforeStart},
+   * on servers it is safe to call inside the service method implementation.
+   * </p>
+   *
+   * @param source of values expressed as an {@link Iterable}.
+   * @param target {@link CallStreamObserver} which accepts values from the source.
+   */
+  public static <V> void copyWithFlowControl(final Iterable<V> source,
+      CallStreamObserver<V> target) {
+    Preconditions.checkNotNull(source, "source");
+    copyWithFlowControl(source.iterator(), target);
+  }
+}
diff --git a/stub/src/main/java/io/grpc/stub/annotations/RpcMethod.java b/stub/src/main/java/io/grpc/stub/annotations/RpcMethod.java
new file mode 100644
index 0000000..fbf46ba
--- /dev/null
+++ b/stub/src/main/java/io/grpc/stub/annotations/RpcMethod.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub.annotations;
+
+import io.grpc.MethodDescriptor;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * {@link RpcMethod} contains a limited subset of information about the RPC to assist
+ * <a href="https://docs.oracle.com/javase/6/docs/api/javax/annotation/processing/Processor.html">
+ * Java Annotation Processors.</a>
+ *
+ * <p>
+ *   This annotation is used by the gRPC stub compiler to annotate {@link MethodDescriptor}
+ *   getters.  Users should not annotate their own classes with this annotation.  Not all stubs may
+ *   have this annotation, so consumers should not assume that it is present.
+ * </p>
+ *
+ * @since 1.14.0
+ */
+@Retention(RetentionPolicy.CLASS)
+@Target(ElementType.METHOD)
+public @interface RpcMethod {
+
+  /**
+   * The fully qualified method name.  This should match the name as returned by
+   * {@link MethodDescriptor#generateFullMethodName(String, String)}.
+   */
+  String fullMethodName();
+
+  /**
+   * The request type of the method.  The request type class should be assignable from (i.e.
+   * {@link Class#isAssignableFrom(Class)} the request type {@code ReqT} of the
+   * {@link MethodDescriptor}.  Additionally, if the request {@code MethodDescriptor.Marshaller}
+   * is a {@code MethodDescriptor.ReflectableMarshaller}, the request type should be assignable
+   * from {@code MethodDescriptor.ReflectableMarshaller#getMessageClass()}.
+   */
+  Class<?> requestType();
+
+  /**
+   * The response type of the method.  The response type class should be assignable from (i.e.
+   * {@link Class#isAssignableFrom(Class)} the response type {@code RespT} of the
+   * {@link MethodDescriptor}.  Additionally, if the response {@code MethodDescriptor.Marshaller}
+   * is a {@code MethodDescriptor.ReflectableMarshaller}, the response type should be assignable
+   * from {@code MethodDescriptor.ReflectableMarshaller#getMessageClass()}.
+   */
+  Class<?> responseType();
+
+  /**
+   * The call type of the method.
+   */
+  MethodDescriptor.MethodType methodType();
+}
diff --git a/stub/src/main/java/io/grpc/stub/package-info.java b/stub/src/main/java/io/grpc/stub/package-info.java
new file mode 100644
index 0000000..fa10854
--- /dev/null
+++ b/stub/src/main/java/io/grpc/stub/package-info.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/**
+ * API for the Stub layer.
+ *
+ * <p>The gRPC Java API is split into two main parts: The stub layer and the call layer. The stub
+ * layer is a wrapper around the call layer.
+ *
+ * <p>In the most common case of gRPC over Protocol Buffers, stub classes are automatically
+ * generated from service definition .proto files by the Protobuf compiler. See <a
+ * href="https://grpc.io/docs/reference/java/generated-code.html">gRPC Java Generated Code Guide</a>
+ * .
+ *
+ * <p>The server side stub classes are abstract classes with RPC methods for the server application
+ * to implement/override. These classes internally use {@link io.grpc.stub.ServerCalls} to interact
+ * with the call layer. The RPC methods consume a {@link io.grpc.stub.StreamObserver} object {@code
+ * responseObserver} as one of its arguments. When users are implementing/override these
+ * methods in the server application, they may call the {@link io.grpc.stub.StreamObserver#onNext
+ * onNext()}, {@link io.grpc.stub.StreamObserver#onError onError()} and {@link
+ * io.grpc.stub.StreamObserver#onCompleted onCompleted()} methods on the {@code responseObserver}
+ * argument to send out a response message, error and completion notification respectively. If the
+ * RPC is client-streaming or bidirectional-streaming, the abstract RPC method should return a
+ * {@code requestObserver} which is also a {@link io.grpc.stub.StreamObserver} object. User should
+ * typically implement the {@link io.grpc.stub.StreamObserver#onNext onNext()}, {@link
+ * io.grpc.stub.StreamObserver#onError onError()} and {@link io.grpc.stub.StreamObserver#onCompleted
+ * onCompleted()} callbacks of {@code requestObserver} to define how the server application would
+ * react when receiving a message, error and completion notification respectively from the client
+ * side.
+ *
+ * <p>The client side stub classes are implementations of {@link io.grpc.stub.AbstractStub} that
+ * provide the RPC methods for the client application to call. The RPC methods in the client stubs
+ * internally use {@link io.grpc.stub.ClientCalls} to interact with the call layer. For asynchronous
+ * stubs, the RPC methods also consume a {@link io.grpc.stub.StreamObserver} object {@code
+ * responseObserver} as one of its arguments, and moreover for client-streaming or
+ * bidirectional-streaming, also return a {@code requestObserver} which is also a {@link
+ * io.grpc.stub.StreamObserver} object. In contrast to the server side, users should implement the
+ * {@link io.grpc.stub.StreamObserver#onNext onNext()}, {@link io.grpc.stub.StreamObserver#onError
+ * onError()} and {@link io.grpc.stub.StreamObserver#onCompleted onCompleted()} callbacks of {@code
+ * responseObserver} to define what the client application would do when receiving a response
+ * message, error and completion notification respectively from the server side, and then pass the
+ * {@code responseObserver} to the RPC method in the client stub. If the RPC method returns a {@code
+ * requestObserver}, users should call the {@link io.grpc.stub.StreamObserver#onNext onNext()},
+ * {@link io.grpc.stub.StreamObserver#onError onError()} and {@link
+ * io.grpc.stub.StreamObserver#onCompleted onCompleted()} methods on the {@code requestObserver} to
+ * send out a request message, error and completion notification respectively.
+ */
+package io.grpc.stub;
diff --git a/stub/src/test/java/io/grpc/stub/AbstractStubTest.java b/stub/src/test/java/io/grpc/stub/AbstractStubTest.java
new file mode 100644
index 0000000..7c73f69
--- /dev/null
+++ b/stub/src/test/java/io/grpc/stub/AbstractStubTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import java.util.concurrent.Executor;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class AbstractStubTest {
+
+  @Mock
+  Channel channel;
+
+  @Before
+  public void setup() {
+    MockitoAnnotations.initMocks(this);
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void channelMustNotBeNull() {
+    new NoopStub(null);
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void callOptionsMustNotBeNull() {
+    new NoopStub(channel, null);
+  }
+
+  @Test(expected = NullPointerException.class)
+  public void channelMustNotBeNull2() {
+    new NoopStub(null, CallOptions.DEFAULT);
+  }
+
+  @Test()
+  public void withWaitForReady() {
+    NoopStub stub = new NoopStub(channel);
+    CallOptions callOptions = stub.getCallOptions();
+    assertFalse(callOptions.isWaitForReady());
+
+    stub = stub.withWaitForReady();
+    callOptions = stub.getCallOptions();
+    assertTrue(callOptions.isWaitForReady());
+  }
+
+  class NoopStub extends AbstractStub<NoopStub> {
+
+    NoopStub(Channel channel) {
+      super(channel);
+    }
+
+    NoopStub(Channel channel, CallOptions options) {
+      super(channel, options);
+    }
+
+    @Override
+    protected NoopStub build(Channel channel, CallOptions callOptions) {
+      return new NoopStub(channel, callOptions);
+    }
+  }
+
+  @Test
+  public void withExecutor() {
+    NoopStub stub = new NoopStub(channel);
+    CallOptions callOptions = stub.getCallOptions();
+
+    assertNull(callOptions.getExecutor());
+
+    Executor executor = mock(Executor.class);
+    stub = stub.withExecutor(executor);
+    callOptions = stub.getCallOptions();
+
+    assertEquals(callOptions.getExecutor(), executor);
+  }
+}
diff --git a/stub/src/test/java/io/grpc/stub/ClientCallsTest.java b/stub/src/test/java/io/grpc/stub/ClientCallsTest.java
new file mode 100644
index 0000000..91ca89a
--- /dev/null
+++ b/stub/src/test/java/io/grpc/stub/ClientCallsTest.java
@@ -0,0 +1,567 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Server;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.ServiceDescriptor;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.internal.NoopClientCall;
+import io.grpc.stub.ServerCalls.NoopStreamObserver;
+import io.grpc.stub.ServerCallsTest.IntegerMarshaller;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for {@link ClientCalls}.
+ */
+@RunWith(JUnit4.class)
+public class ClientCallsTest {
+
+  private static final MethodDescriptor<Integer, Integer> STREAMING_METHOD =
+      MethodDescriptor.<Integer, Integer>newBuilder()
+          .setType(MethodDescriptor.MethodType.BIDI_STREAMING)
+          .setFullMethodName("some/method")
+          .setRequestMarshaller(new IntegerMarshaller())
+          .setResponseMarshaller(new IntegerMarshaller())
+          .build();
+
+  private Server server;
+  private ManagedChannel channel;
+
+  @Before
+  public void setUp() {
+    MockitoAnnotations.initMocks(this);
+  }
+
+  @After
+  public void tearDown() {
+    if (server != null) {
+      server.shutdownNow();
+    }
+    if (channel != null) {
+      channel.shutdownNow();
+    }
+  }
+
+  @Test
+  public void unaryBlockingCallSuccess() throws Exception {
+    Integer req = 2;
+    final String resp = "bar";
+    final Status status = Status.OK;
+    final Metadata trailers = new Metadata();
+
+    NoopClientCall<Integer, String> call = new NoopClientCall<Integer, String>() {
+      @Override
+      public void start(ClientCall.Listener<String> listener, Metadata headers) {
+        listener.onMessage(resp);
+        listener.onClose(status, trailers);
+      }
+    };
+
+    String actualResponse = ClientCalls.blockingUnaryCall(call, req);
+    assertEquals(resp, actualResponse);
+  }
+
+  @Test
+  public void unaryBlockingCallFailed() throws Exception {
+    Integer req = 2;
+    final Status status = Status.INTERNAL.withDescription("Unique status");
+    final Metadata trailers = new Metadata();
+
+    NoopClientCall<Integer, String> call = new NoopClientCall<Integer, String>() {
+      @Override
+      public void start(io.grpc.ClientCall.Listener<String> listener, Metadata headers) {
+        listener.onClose(status, trailers);
+      }
+    };
+
+    try {
+      ClientCalls.blockingUnaryCall(call, req);
+      fail("Should fail");
+    } catch (StatusRuntimeException e) {
+      assertSame(status, e.getStatus());
+      assertSame(trailers, e.getTrailers());
+    }
+  }
+
+  @Test
+  public void unaryFutureCallSuccess() throws Exception {
+    final AtomicReference<ClientCall.Listener<String>> listener =
+        new AtomicReference<ClientCall.Listener<String>>();
+    final AtomicReference<Integer> message = new AtomicReference<Integer>();
+    final AtomicReference<Boolean> halfClosed = new AtomicReference<Boolean>();
+    NoopClientCall<Integer, String> call = new NoopClientCall<Integer, String>() {
+      @Override
+      public void start(io.grpc.ClientCall.Listener<String> responseListener, Metadata headers) {
+        listener.set(responseListener);
+      }
+
+      @Override
+      public void sendMessage(Integer msg) {
+        message.set(msg);
+      }
+
+      @Override
+      public void halfClose() {
+        halfClosed.set(true);
+      }
+    };
+    Integer req = 2;
+    ListenableFuture<String> future = ClientCalls.futureUnaryCall(call, req);
+
+    assertEquals(req, message.get());
+    assertTrue(halfClosed.get());
+    listener.get().onMessage("bar");
+    listener.get().onClose(Status.OK, new Metadata());
+    assertEquals("bar", future.get());
+  }
+
+  @Test
+  public void unaryFutureCallFailed() throws Exception {
+    final AtomicReference<ClientCall.Listener<String>> listener =
+        new AtomicReference<ClientCall.Listener<String>>();
+    NoopClientCall<Integer, String> call = new NoopClientCall<Integer, String>() {
+      @Override
+      public void start(io.grpc.ClientCall.Listener<String> responseListener, Metadata headers) {
+        listener.set(responseListener);
+      }
+    };
+    Integer req = 2;
+    ListenableFuture<String> future = ClientCalls.futureUnaryCall(call, req);
+    Metadata trailers = new Metadata();
+    listener.get().onClose(Status.INTERNAL, trailers);
+    try {
+      future.get();
+      fail("Should fail");
+    } catch (ExecutionException e) {
+      Status status = Status.fromThrowable(e);
+      assertEquals(Status.INTERNAL, status);
+      Metadata metadata = Status.trailersFromThrowable(e);
+      assertSame(trailers, metadata);
+    }
+  }
+
+  @Test
+  public void unaryFutureCallCancelled() throws Exception {
+    final AtomicReference<ClientCall.Listener<String>> listener =
+        new AtomicReference<ClientCall.Listener<String>>();
+    final AtomicReference<String> cancelMessage = new AtomicReference<String>();
+    final AtomicReference<Throwable> cancelCause = new AtomicReference<Throwable>();
+    NoopClientCall<Integer, String> call = new NoopClientCall<Integer, String>() {
+      @Override
+      public void start(io.grpc.ClientCall.Listener<String> responseListener, Metadata headers) {
+        listener.set(responseListener);
+      }
+
+      @Override
+      public void cancel(String message, Throwable cause) {
+        cancelMessage.set(message);
+        cancelCause.set(cause);
+      }
+    };
+    Integer req = 2;
+    ListenableFuture<String> future = ClientCalls.futureUnaryCall(call, req);
+    future.cancel(true);
+    assertEquals("GrpcFuture was cancelled", cancelMessage.get());
+    assertNull(cancelCause.get());
+    listener.get().onMessage("bar");
+    listener.get().onClose(Status.OK, new Metadata());
+    try {
+      future.get();
+      fail("Should fail");
+    } catch (CancellationException e) {
+      // Exepcted
+    }
+  }
+
+  @Test
+  public void cannotSetOnReadyAfterCallStarted() throws Exception {
+    NoopClientCall<Integer, String> call = new NoopClientCall<Integer, String>();
+    CallStreamObserver<Integer> callStreamObserver =
+        (CallStreamObserver<Integer>) ClientCalls.asyncClientStreamingCall(call,
+            new NoopStreamObserver<String>());
+    Runnable noOpRunnable = new Runnable() {
+      @Override
+      public void run() {
+      }
+    };
+    try {
+      callStreamObserver.setOnReadyHandler(noOpRunnable);
+      fail("Should not be able to set handler after call started");
+    } catch (IllegalStateException ise) {
+      // expected
+    }
+  }
+
+  @Test
+  public void disablingInboundAutoFlowControlSuppressesRequestsForMoreMessages()
+      throws Exception {
+    final AtomicReference<ClientCall.Listener<String>> listener =
+        new AtomicReference<ClientCall.Listener<String>>();
+    final List<Integer> requests = new ArrayList<>();
+    NoopClientCall<Integer, String> call = new NoopClientCall<Integer, String>() {
+      @Override
+      public void start(io.grpc.ClientCall.Listener<String> responseListener, Metadata headers) {
+        listener.set(responseListener);
+      }
+
+      @Override
+      public void request(int numMessages) {
+        requests.add(numMessages);
+      }
+    };
+    ClientCalls.asyncBidiStreamingCall(call, new ClientResponseObserver<Integer, String>() {
+      @Override
+      public void beforeStart(ClientCallStreamObserver<Integer> requestStream) {
+        requestStream.disableAutoInboundFlowControl();
+      }
+
+      @Override
+      public void onNext(String value) {
+
+      }
+
+      @Override
+      public void onError(Throwable t) {
+
+      }
+
+      @Override
+      public void onCompleted() {
+
+      }
+    });
+    listener.get().onMessage("message");
+    assertThat(requests).containsExactly(1);
+  }
+
+  @Test
+  public void callStreamObserverPropagatesFlowControlRequestsToCall()
+      throws Exception {
+    ClientResponseObserver<Integer, String> responseObserver =
+        new ClientResponseObserver<Integer, String>() {
+          @Override
+          public void beforeStart(ClientCallStreamObserver<Integer> requestStream) {
+            requestStream.disableAutoInboundFlowControl();
+          }
+
+          @Override
+          public void onNext(String value) {
+          }
+
+          @Override
+          public void onError(Throwable t) {
+          }
+
+          @Override
+          public void onCompleted() {
+          }
+        };
+    final AtomicReference<ClientCall.Listener<String>> listener =
+        new AtomicReference<ClientCall.Listener<String>>();
+    final List<Integer> requests = new ArrayList<>();
+    NoopClientCall<Integer, String> call = new NoopClientCall<Integer, String>() {
+      @Override
+      public void start(io.grpc.ClientCall.Listener<String> responseListener, Metadata headers) {
+        listener.set(responseListener);
+      }
+
+      @Override
+      public void request(int numMessages) {
+        requests.add(numMessages);
+      }
+    };
+    CallStreamObserver<Integer> requestObserver =
+        (CallStreamObserver<Integer>)
+            ClientCalls.asyncBidiStreamingCall(call, responseObserver);
+    listener.get().onMessage("message");
+    requestObserver.request(5);
+    assertThat(requests).contains(5);
+  }
+
+  @Test
+  public void canCaptureInboundFlowControlForServerStreamingObserver()
+      throws Exception {
+
+    ClientResponseObserver<Integer, String> responseObserver =
+        new ClientResponseObserver<Integer, String>() {
+          @Override
+          public void beforeStart(ClientCallStreamObserver<Integer> requestStream) {
+            requestStream.disableAutoInboundFlowControl();
+            requestStream.request(5);
+          }
+
+          @Override
+          public void onNext(String value) {
+          }
+
+          @Override
+          public void onError(Throwable t) {
+          }
+
+          @Override
+          public void onCompleted() {
+          }
+        };
+    final AtomicReference<ClientCall.Listener<String>> listener =
+            new AtomicReference<ClientCall.Listener<String>>();
+    final List<Integer> requests = new ArrayList<>();
+    NoopClientCall<Integer, String> call = new NoopClientCall<Integer, String>() {
+      @Override
+      public void start(io.grpc.ClientCall.Listener<String> responseListener, Metadata headers) {
+        listener.set(responseListener);
+      }
+
+      @Override
+      public void request(int numMessages) {
+        requests.add(numMessages);
+      }
+    };
+    ClientCalls.asyncServerStreamingCall(call, 1, responseObserver);
+    listener.get().onMessage("message");
+    assertThat(requests).containsExactly(5, 1).inOrder();
+  }
+
+  @Test
+  public void inprocessTransportInboundFlowControl() throws Exception {
+    final Semaphore semaphore = new Semaphore(0);
+    ServerServiceDefinition service = ServerServiceDefinition.builder(
+        new ServiceDescriptor("some", STREAMING_METHOD))
+        .addMethod(STREAMING_METHOD, ServerCalls.asyncBidiStreamingCall(
+            new ServerCalls.BidiStreamingMethod<Integer, Integer>() {
+              int iteration;
+
+              @Override
+              public StreamObserver<Integer> invoke(StreamObserver<Integer> responseObserver) {
+                final ServerCallStreamObserver<Integer> serverCallObserver =
+                    (ServerCallStreamObserver<Integer>) responseObserver;
+                serverCallObserver.setOnReadyHandler(new Runnable() {
+                  @Override
+                  public void run() {
+                    while (serverCallObserver.isReady()) {
+                      serverCallObserver.onNext(iteration);
+                    }
+                    iteration++;
+                    semaphore.release();
+                  }
+                });
+                return new ServerCalls.NoopStreamObserver<Integer>() {
+                  @Override
+                  public void onCompleted() {
+                    serverCallObserver.onCompleted();
+                  }
+                };
+              }
+            }))
+        .build();
+    long tag = System.nanoTime();
+    server = InProcessServerBuilder.forName("go-with-the-flow" + tag).directExecutor()
+        .addService(service).build().start();
+    channel = InProcessChannelBuilder.forName("go-with-the-flow" + tag).directExecutor().build();
+    final ClientCall<Integer, Integer> clientCall = channel.newCall(STREAMING_METHOD,
+        CallOptions.DEFAULT);
+    final CountDownLatch latch = new CountDownLatch(1);
+    final List<Object> receivedMessages = new ArrayList<>(6);
+
+    ClientResponseObserver<Integer, Integer> responseObserver =
+        new ClientResponseObserver<Integer, Integer>() {
+          @Override
+          public void beforeStart(final ClientCallStreamObserver<Integer> requestStream) {
+            requestStream.disableAutoInboundFlowControl();
+          }
+
+          @Override
+          public void onNext(Integer value) {
+            receivedMessages.add(value);
+          }
+
+          @Override
+          public void onError(Throwable t) {
+            receivedMessages.add(t);
+            latch.countDown();
+          }
+
+          @Override
+          public void onCompleted() {
+            latch.countDown();
+          }
+        };
+
+    CallStreamObserver<Integer> integerStreamObserver = (CallStreamObserver<Integer>)
+        ClientCalls.asyncBidiStreamingCall(clientCall, responseObserver);
+    semaphore.acquire();
+    integerStreamObserver.request(2);
+    semaphore.acquire();
+    integerStreamObserver.request(3);
+    integerStreamObserver.onCompleted();
+    assertTrue(latch.await(5, TimeUnit.SECONDS));
+    // Verify that number of messages produced in each onReady handler call matches the number
+    // requested by the client. Note that ClientCalls.asyncBidiStreamingCall will request(1)
+    assertEquals(Arrays.asList(0, 1, 1, 2, 2, 2), receivedMessages);
+  }
+
+  @Test
+  public void inprocessTransportOutboundFlowControl() throws Exception {
+    final Semaphore semaphore = new Semaphore(0);
+    final List<Object> receivedMessages = new ArrayList<>(6);
+    final SettableFuture<ServerCallStreamObserver<Integer>> observerFuture
+        = SettableFuture.create();
+    ServerServiceDefinition service = ServerServiceDefinition.builder(
+        new ServiceDescriptor("some", STREAMING_METHOD))
+        .addMethod(STREAMING_METHOD, ServerCalls.asyncBidiStreamingCall(
+            new ServerCalls.BidiStreamingMethod<Integer, Integer>() {
+              @Override
+              public StreamObserver<Integer> invoke(StreamObserver<Integer> responseObserver) {
+                final ServerCallStreamObserver<Integer> serverCallObserver =
+                    (ServerCallStreamObserver<Integer>) responseObserver;
+                serverCallObserver.disableAutoInboundFlowControl();
+                observerFuture.set(serverCallObserver);
+                return new StreamObserver<Integer>() {
+                  @Override
+                  public void onNext(Integer value) {
+                    receivedMessages.add(value);
+                  }
+
+                  @Override
+                  public void onError(Throwable t) {
+                    receivedMessages.add(t);
+                  }
+
+                  @Override
+                  public void onCompleted() {
+                    serverCallObserver.onCompleted();
+                  }
+                };
+              }
+            }))
+        .build();
+    long tag = System.nanoTime();
+    server = InProcessServerBuilder.forName("go-with-the-flow" + tag).directExecutor()
+        .addService(service).build().start();
+    channel = InProcessChannelBuilder.forName("go-with-the-flow" + tag).directExecutor().build();
+    final ClientCall<Integer, Integer> clientCall = channel.newCall(STREAMING_METHOD,
+        CallOptions.DEFAULT);
+
+    final SettableFuture<Void> future = SettableFuture.create();
+    ClientResponseObserver<Integer, Integer> responseObserver =
+        new ClientResponseObserver<Integer, Integer>() {
+          @Override
+          public void beforeStart(final ClientCallStreamObserver<Integer> requestStream) {
+            requestStream.setOnReadyHandler(new Runnable() {
+              int iteration;
+
+              @Override
+              public void run() {
+                while (requestStream.isReady()) {
+                  requestStream.onNext(iteration);
+                }
+                iteration++;
+                if (iteration == 3) {
+                  requestStream.onCompleted();
+                }
+                semaphore.release();
+              }
+            });
+          }
+
+          @Override
+          public void onNext(Integer value) {
+          }
+
+          @Override
+          public void onError(Throwable t) {
+            future.setException(t);
+          }
+
+          @Override
+          public void onCompleted() {
+            future.set(null);
+          }
+        };
+
+    ClientCalls.asyncBidiStreamingCall(clientCall, responseObserver);
+    ServerCallStreamObserver<Integer> serverCallObserver = observerFuture.get(5, TimeUnit.SECONDS);
+    serverCallObserver.request(1);
+    assertTrue(semaphore.tryAcquire(5, TimeUnit.SECONDS));
+    serverCallObserver.request(2);
+    assertTrue(semaphore.tryAcquire(5, TimeUnit.SECONDS));
+    serverCallObserver.request(3);
+    future.get(5, TimeUnit.SECONDS);
+    // Verify that number of messages produced in each onReady handler call matches the number
+    // requested by the client.
+    assertEquals(Arrays.asList(0, 1, 1, 2, 2, 2), receivedMessages);
+  }
+
+  @Test
+  public void blockingResponseStreamFailed() throws Exception {
+    final AtomicReference<ClientCall.Listener<String>> listener =
+        new AtomicReference<ClientCall.Listener<String>>();
+    NoopClientCall<Integer, String> call = new NoopClientCall<Integer, String>() {
+      @Override
+      public void start(io.grpc.ClientCall.Listener<String> responseListener, Metadata headers) {
+        listener.set(responseListener);
+      }
+    };
+
+    Integer req = 2;
+    Iterator<String> iter = ClientCalls.blockingServerStreamingCall(call, req);
+
+    Metadata trailers = new Metadata();
+    listener.get().onClose(Status.INTERNAL, trailers);
+    try {
+      iter.next();
+      fail("Should fail");
+    } catch (Exception e) {
+      Status status = Status.fromThrowable(e);
+      assertEquals(Status.INTERNAL, status);
+      Metadata metadata = Status.trailersFromThrowable(e);
+      assertSame(trailers, metadata);
+    }
+  }
+}
diff --git a/stub/src/test/java/io/grpc/stub/ServerCallsTest.java b/stub/src/test/java/io/grpc/stub/ServerCallsTest.java
new file mode 100644
index 0000000..a3d12d3
--- /dev/null
+++ b/stub/src/test/java/io/grpc/stub/ServerCallsTest.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.stub;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import io.grpc.CallOptions;
+import io.grpc.ClientCall;
+import io.grpc.ManagedChannel;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.ServiceDescriptor;
+import io.grpc.Status;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+
+/**
+ * Tests for {@link ServerCalls}.
+ */
+@RunWith(JUnit4.class)
+public class ServerCallsTest {
+  static final MethodDescriptor<Integer, Integer> STREAMING_METHOD =
+      MethodDescriptor.<Integer, Integer>newBuilder()
+          .setType(MethodDescriptor.MethodType.BIDI_STREAMING)
+          .setFullMethodName("some/bidi_streaming")
+          .setRequestMarshaller(new IntegerMarshaller())
+          .setResponseMarshaller(new IntegerMarshaller())
+          .build();
+
+  static final MethodDescriptor<Integer, Integer> SERVER_STREAMING_METHOD =
+      STREAMING_METHOD.toBuilder()
+          .setType(MethodDescriptor.MethodType.SERVER_STREAMING)
+          .setFullMethodName("some/client_streaming")
+          .build();
+
+  static final MethodDescriptor<Integer, Integer> UNARY_METHOD =
+      STREAMING_METHOD.toBuilder()
+          .setType(MethodDescriptor.MethodType.UNARY)
+          .setFullMethodName("some/unary")
+          .build();
+
+  private final ServerCallRecorder serverCall = new ServerCallRecorder(UNARY_METHOD);
+
+  @Test
+  public void runtimeStreamObserverIsServerCallStreamObserver() throws Exception {
+    final AtomicBoolean invokeCalled = new AtomicBoolean();
+    final AtomicBoolean onCancelCalled = new AtomicBoolean();
+    final AtomicBoolean onReadyCalled = new AtomicBoolean();
+    final AtomicReference<ServerCallStreamObserver<Integer>> callObserver =
+        new AtomicReference<ServerCallStreamObserver<Integer>>();
+    ServerCallHandler<Integer, Integer> callHandler =
+        ServerCalls.asyncBidiStreamingCall(
+            new ServerCalls.BidiStreamingMethod<Integer, Integer>() {
+              @Override
+              public StreamObserver<Integer> invoke(StreamObserver<Integer> responseObserver) {
+                assertTrue(responseObserver instanceof ServerCallStreamObserver);
+                ServerCallStreamObserver<Integer> serverCallObserver =
+                    (ServerCallStreamObserver<Integer>) responseObserver;
+                callObserver.set(serverCallObserver);
+                serverCallObserver.setOnCancelHandler(new Runnable() {
+                  @Override
+                  public void run() {
+                    onCancelCalled.set(true);
+                  }
+                });
+                serverCallObserver.setOnReadyHandler(new Runnable() {
+                  @Override
+                  public void run() {
+                    onReadyCalled.set(true);
+                  }
+                });
+                invokeCalled.set(true);
+                return new ServerCalls.NoopStreamObserver<Integer>();
+              }
+            });
+    ServerCall.Listener<Integer> callListener =
+        callHandler.startCall(serverCall, new Metadata());
+    serverCall.isReady = true;
+    serverCall.isCancelled = false;
+    assertTrue(callObserver.get().isReady());
+    assertFalse(callObserver.get().isCancelled());
+    callListener.onReady();
+    callListener.onMessage(1);
+    callListener.onCancel();
+    assertTrue(invokeCalled.get());
+    assertTrue(onReadyCalled.get());
+    assertTrue(onCancelCalled.get());
+    serverCall.isReady = false;
+    serverCall.isCancelled = true;
+    assertFalse(callObserver.get().isReady());
+    assertTrue(callObserver.get().isCancelled());
+    // Is called twice, once to permit the first message and once again after the first message
+    // has been processed (auto flow control)
+    assertThat(serverCall.requestCalls).containsExactly(1, 1).inOrder();
+  }
+
+  @Test
+  public void cannotSetOnCancelHandlerAfterServiceInvocation() throws Exception {
+    final AtomicReference<ServerCallStreamObserver<Integer>> callObserver =
+        new AtomicReference<ServerCallStreamObserver<Integer>>();
+    ServerCallHandler<Integer, Integer> callHandler =
+        ServerCalls.asyncBidiStreamingCall(
+            new ServerCalls.BidiStreamingMethod<Integer, Integer>() {
+              @Override
+              public StreamObserver<Integer> invoke(StreamObserver<Integer> responseObserver) {
+                callObserver.set((ServerCallStreamObserver<Integer>) responseObserver);
+                return new ServerCalls.NoopStreamObserver<Integer>();
+              }
+            });
+    ServerCall.Listener<Integer> callListener =
+        callHandler.startCall(serverCall, new Metadata());
+    callListener.onMessage(1);
+    try {
+      callObserver.get().setOnCancelHandler(new Runnable() {
+        @Override
+        public void run() {
+        }
+      });
+      fail("Cannot set onCancel handler after service invocation");
+    } catch (IllegalStateException expected) {
+      // Expected
+    }
+  }
+
+  @Test
+  public void cannotSetOnReadyHandlerAfterServiceInvocation() throws Exception {
+    final AtomicReference<ServerCallStreamObserver<Integer>> callObserver =
+        new AtomicReference<ServerCallStreamObserver<Integer>>();
+    ServerCallHandler<Integer, Integer> callHandler =
+        ServerCalls.asyncBidiStreamingCall(
+            new ServerCalls.BidiStreamingMethod<Integer, Integer>() {
+              @Override
+              public StreamObserver<Integer> invoke(StreamObserver<Integer> responseObserver) {
+                callObserver.set((ServerCallStreamObserver<Integer>) responseObserver);
+                return new ServerCalls.NoopStreamObserver<Integer>();
+              }
+            });
+    ServerCall.Listener<Integer> callListener =
+        callHandler.startCall(serverCall, new Metadata());
+    callListener.onMessage(1);
+    try {
+      callObserver.get().setOnReadyHandler(new Runnable() {
+        @Override
+        public void run() {
+        }
+      });
+      fail("Cannot set onReady after service invocation");
+    } catch (IllegalStateException expected) {
+      // Expected
+    }
+  }
+
+  @Test
+  public void cannotDisableAutoFlowControlAfterServiceInvocation() throws Exception {
+    final AtomicReference<ServerCallStreamObserver<Integer>> callObserver =
+        new AtomicReference<ServerCallStreamObserver<Integer>>();
+    ServerCallHandler<Integer, Integer> callHandler =
+        ServerCalls.asyncBidiStreamingCall(
+            new ServerCalls.BidiStreamingMethod<Integer, Integer>() {
+              @Override
+              public StreamObserver<Integer> invoke(StreamObserver<Integer> responseObserver) {
+                callObserver.set((ServerCallStreamObserver<Integer>) responseObserver);
+                return new ServerCalls.NoopStreamObserver<Integer>();
+              }
+            });
+    ServerCall.Listener<Integer> callListener =
+        callHandler.startCall(serverCall, new Metadata());
+    callListener.onMessage(1);
+    try {
+      callObserver.get().disableAutoInboundFlowControl();
+      fail("Cannot set onCancel handler after service invocation");
+    } catch (IllegalStateException expected) {
+      // Expected
+    }
+  }
+
+  @Test
+  public void disablingInboundAutoFlowControlSuppressesRequestsForMoreMessages() throws Exception {
+    ServerCallHandler<Integer, Integer> callHandler =
+        ServerCalls.asyncBidiStreamingCall(
+            new ServerCalls.BidiStreamingMethod<Integer, Integer>() {
+              @Override
+              public StreamObserver<Integer> invoke(StreamObserver<Integer> responseObserver) {
+                ServerCallStreamObserver<Integer> serverCallObserver =
+                    (ServerCallStreamObserver<Integer>) responseObserver;
+                serverCallObserver.disableAutoInboundFlowControl();
+                return new ServerCalls.NoopStreamObserver<Integer>();
+              }
+            });
+    ServerCall.Listener<Integer> callListener =
+        callHandler.startCall(serverCall, new Metadata());
+    callListener.onReady();
+    // Transport should not call this if nothing has been requested but forcing it here
+    // to verify that message delivery does not trigger a call to request(1).
+    callListener.onMessage(1);
+    // Should never be called
+    assertThat(serverCall.requestCalls).isEmpty();
+  }
+
+  @Test
+  public void disablingInboundAutoFlowControlForUnaryHasNoEffect() throws Exception {
+    ServerCallHandler<Integer, Integer> callHandler =
+        ServerCalls.asyncUnaryCall(
+            new ServerCalls.UnaryMethod<Integer, Integer>() {
+              @Override
+              public void invoke(Integer req, StreamObserver<Integer> responseObserver) {
+                ServerCallStreamObserver<Integer> serverCallObserver =
+                    (ServerCallStreamObserver<Integer>) responseObserver;
+                serverCallObserver.disableAutoInboundFlowControl();
+              }
+            });
+    callHandler.startCall(serverCall, new Metadata());
+    // Auto inbound flow-control always requests 2 messages for unary to detect a violation
+    // of the unary semantic.
+    assertThat(serverCall.requestCalls).containsExactly(2);
+  }
+
+  @Test
+  public void onReadyHandlerCalledForUnaryRequest() throws Exception {
+    final AtomicInteger onReadyCalled = new AtomicInteger();
+    ServerCallHandler<Integer, Integer> callHandler =
+        ServerCalls.asyncServerStreamingCall(
+            new ServerCalls.ServerStreamingMethod<Integer, Integer>() {
+              @Override
+              public void invoke(Integer req, StreamObserver<Integer> responseObserver) {
+                ServerCallStreamObserver<Integer> serverCallObserver =
+                    (ServerCallStreamObserver<Integer>) responseObserver;
+                serverCallObserver.setOnReadyHandler(new Runnable() {
+                  @Override
+                  public void run() {
+                    onReadyCalled.incrementAndGet();
+                  }
+                });
+              }
+            });
+    ServerCall.Listener<Integer> callListener =
+        callHandler.startCall(serverCall, new Metadata());
+    serverCall.isReady = true;
+    serverCall.isCancelled = false;
+    callListener.onReady();
+    // On ready is not called until the unary request message is delivered
+    assertEquals(0, onReadyCalled.get());
+    // delivering the message doesn't trigger onReady listener either
+    callListener.onMessage(1);
+    assertEquals(0, onReadyCalled.get());
+    // half-closing triggers the unary request delivery and onReady
+    callListener.onHalfClose();
+    assertEquals(1, onReadyCalled.get());
+    // Next on ready event from the transport triggers listener
+    callListener.onReady();
+    assertEquals(2, onReadyCalled.get());
+  }
+
+  @Test
+  public void clientSendsOne_errorMissingRequest_unary() {
+    ServerCallRecorder serverCall = new ServerCallRecorder(UNARY_METHOD);
+    ServerCallHandler<Integer, Integer> callHandler =
+        ServerCalls.asyncUnaryCall(
+            new ServerCalls.UnaryMethod<Integer, Integer>() {
+              @Override
+              public void invoke(Integer req, StreamObserver<Integer> responseObserver) {
+                fail("should not be reached");
+              }
+            });
+    ServerCall.Listener<Integer> listener = callHandler.startCall(serverCall, new Metadata());
+    listener.onHalfClose();
+    assertThat(serverCall.responses).isEmpty();
+    assertEquals(Status.Code.INTERNAL, serverCall.status.getCode());
+    assertEquals(ServerCalls.MISSING_REQUEST, serverCall.status.getDescription());
+  }
+
+  @Test
+  public void clientSendsOne_errorMissingRequest_serverStreaming() {
+    ServerCallRecorder serverCall = new ServerCallRecorder(SERVER_STREAMING_METHOD);
+    ServerCallHandler<Integer, Integer> callHandler =
+        ServerCalls.asyncServerStreamingCall(
+            new ServerCalls.ServerStreamingMethod<Integer, Integer>() {
+              @Override
+              public void invoke(Integer req, StreamObserver<Integer> responseObserver) {
+                fail("should not be reached");
+              }
+            });
+    ServerCall.Listener<Integer> listener = callHandler.startCall(serverCall, new Metadata());
+    listener.onHalfClose();
+    assertThat(serverCall.responses).isEmpty();
+    assertEquals(Status.Code.INTERNAL, serverCall.status.getCode());
+    assertEquals(ServerCalls.MISSING_REQUEST, serverCall.status.getDescription());
+
+  }
+
+  @Test
+  public void clientSendsOne_errorTooManyRequests_unary() {
+    ServerCallRecorder serverCall = new ServerCallRecorder(UNARY_METHOD);
+    ServerCallHandler<Integer, Integer> callHandler =
+        ServerCalls.asyncUnaryCall(
+            new ServerCalls.UnaryMethod<Integer, Integer>() {
+              @Override
+              public void invoke(Integer req, StreamObserver<Integer> responseObserver) {
+                fail("should not be reached");
+              }
+            });
+    ServerCall.Listener<Integer> listener = callHandler.startCall(serverCall, new Metadata());
+    listener.onMessage(1);
+    listener.onMessage(1);
+    assertThat(serverCall.responses).isEmpty();
+    assertEquals(Status.Code.INTERNAL, serverCall.status.getCode());
+    assertEquals(ServerCalls.TOO_MANY_REQUESTS, serverCall.status.getDescription());
+    // ensure onHalfClose does not invoke
+    listener.onHalfClose();
+  }
+
+  @Test
+  public void clientSendsOne_errorTooManyRequests_serverStreaming() {
+    ServerCallRecorder serverCall = new ServerCallRecorder(SERVER_STREAMING_METHOD);
+    ServerCallHandler<Integer, Integer> callHandler =
+        ServerCalls.asyncServerStreamingCall(
+            new ServerCalls.ServerStreamingMethod<Integer, Integer>() {
+              @Override
+              public void invoke(Integer req, StreamObserver<Integer> responseObserver) {
+                fail("should not be reached");
+              }
+            });
+    ServerCall.Listener<Integer> listener = callHandler.startCall(serverCall, new Metadata());
+    listener.onMessage(1);
+    listener.onMessage(1);
+    assertThat(serverCall.responses).isEmpty();
+    assertEquals(Status.Code.INTERNAL, serverCall.status.getCode());
+    assertEquals(ServerCalls.TOO_MANY_REQUESTS, serverCall.status.getDescription());
+    // ensure onHalfClose does not invoke
+    listener.onHalfClose();
+  }
+
+  @Test
+  public void inprocessTransportManualFlow() throws Exception {
+    final Semaphore semaphore = new Semaphore(1);
+    ServerServiceDefinition service = ServerServiceDefinition.builder(
+        new ServiceDescriptor("some", STREAMING_METHOD))
+        .addMethod(STREAMING_METHOD, ServerCalls.asyncBidiStreamingCall(
+            new ServerCalls.BidiStreamingMethod<Integer, Integer>() {
+              int iteration;
+
+              @Override
+              public StreamObserver<Integer> invoke(StreamObserver<Integer> responseObserver) {
+                final ServerCallStreamObserver<Integer> serverCallObserver =
+                    (ServerCallStreamObserver<Integer>) responseObserver;
+                serverCallObserver.setOnReadyHandler(new Runnable() {
+                  @Override
+                  public void run() {
+                    while (serverCallObserver.isReady()) {
+                      serverCallObserver.onNext(iteration);
+                    }
+                    iteration++;
+                    semaphore.release();
+                  }
+                });
+                return new ServerCalls.NoopStreamObserver<Integer>() {
+                  @Override
+                  public void onCompleted() {
+                    serverCallObserver.onCompleted();
+                  }
+                };
+              }
+            }))
+        .build();
+    long tag = System.nanoTime();
+    InProcessServerBuilder.forName("go-with-the-flow" + tag).addService(service).build().start();
+    ManagedChannel channel = InProcessChannelBuilder.forName("go-with-the-flow" + tag).build();
+    final ClientCall<Integer, Integer> clientCall = channel.newCall(STREAMING_METHOD,
+        CallOptions.DEFAULT);
+    final CountDownLatch latch = new CountDownLatch(1);
+    final int[] receivedMessages = new int[6];
+    clientCall.start(new ClientCall.Listener<Integer>() {
+      int index;
+
+      @Override
+      public void onMessage(Integer message) {
+        receivedMessages[index++] = message;
+      }
+
+      @Override
+      public void onClose(Status status, Metadata trailers) {
+        latch.countDown();
+      }
+    }, new Metadata());
+    semaphore.acquire();
+    clientCall.request(1);
+    semaphore.acquire();
+    clientCall.request(2);
+    semaphore.acquire();
+    clientCall.request(3);
+    clientCall.halfClose();
+    latch.await(5, TimeUnit.SECONDS);
+    // Very that number of messages produced in each onReady handler call matches the number
+    // requested by the client.
+    assertArrayEquals(new int[]{0, 1, 1, 2, 2, 2}, receivedMessages);
+  }
+
+  public static class IntegerMarshaller implements MethodDescriptor.Marshaller<Integer> {
+    @Override
+    public InputStream stream(Integer value) {
+      try {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(4);
+        DataOutputStream dos = new DataOutputStream(baos);
+        dos.writeInt(value);
+        return new ByteArrayInputStream(baos.toByteArray());
+      } catch (IOException ioe) {
+        throw new RuntimeException(ioe);
+      }
+    }
+
+    @Override
+    public Integer parse(InputStream stream) {
+      try {
+        DataInputStream dis = new DataInputStream(stream);
+        return dis.readInt();
+      } catch (IOException ioe) {
+        throw new RuntimeException(ioe);
+      }
+    }
+  }
+
+  private static class ServerCallRecorder extends ServerCall<Integer, Integer> {
+    private final MethodDescriptor<Integer, Integer> methodDescriptor;
+    private final List<Integer> requestCalls = new ArrayList<>();
+    private final List<Integer> responses = new ArrayList<>();
+    private Metadata headers;
+    private Metadata trailers;
+    private Status status;
+    private boolean isCancelled;
+    private boolean isReady;
+
+    public ServerCallRecorder(MethodDescriptor<Integer, Integer> methodDescriptor) {
+      this.methodDescriptor = methodDescriptor;
+    }
+
+    @Override
+    public void request(int numMessages) {
+      requestCalls.add(numMessages);
+    }
+
+    @Override
+    public void sendHeaders(Metadata headers) {
+      this.headers = headers;
+    }
+
+    @Override
+    public void sendMessage(Integer message) {
+      this.responses.add(message);
+    }
+
+    @Override
+    public void close(Status status, Metadata trailers) {
+      this.status = status;
+      this.trailers = trailers;
+    }
+
+    @Override
+    public boolean isCancelled() {
+      return isCancelled;
+    }
+
+    @Override
+    public boolean isReady() {
+      return isReady;
+    }
+
+    @Override
+    public MethodDescriptor<Integer, Integer> getMethodDescriptor() {
+      return methodDescriptor;
+    }
+  }
+}
diff --git a/testing-proto/build.gradle b/testing-proto/build.gradle
new file mode 100644
index 0000000..144c06d
--- /dev/null
+++ b/testing-proto/build.gradle
@@ -0,0 +1,20 @@
+description = "gRPC: Testing Protos"
+
+// Add dependency on the protobuf plugin
+buildscript {
+    repositories {
+        maven { // The google mirror is less flaky than mavenCentral()
+            url "https://maven-central.storage-download.googleapis.com/repos/central/data/" }
+    }
+    dependencies { classpath libraries.protobuf_plugin }
+}
+
+dependencies {
+    compile project(':grpc-protobuf'),
+            project(':grpc-stub')
+    compileOnly libraries.javax_annotation
+    testCompile libraries.truth
+    testRuntime libraries.javax_annotation
+}
+
+configureProtoCompilation()
diff --git a/testing-proto/src/generated/main/grpc/io/grpc/testing/protobuf/SimpleServiceGrpc.java b/testing-proto/src/generated/main/grpc/io/grpc/testing/protobuf/SimpleServiceGrpc.java
new file mode 100644
index 0000000..9674531
--- /dev/null
+++ b/testing-proto/src/generated/main/grpc/io/grpc/testing/protobuf/SimpleServiceGrpc.java
@@ -0,0 +1,514 @@
+package io.grpc.testing.protobuf;
+
+import static io.grpc.MethodDescriptor.generateFullMethodName;
+import static io.grpc.stub.ClientCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ClientCalls.asyncUnaryCall;
+import static io.grpc.stub.ClientCalls.blockingServerStreamingCall;
+import static io.grpc.stub.ClientCalls.blockingUnaryCall;
+import static io.grpc.stub.ClientCalls.futureUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncBidiStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncClientStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncServerStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnaryCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedStreamingCall;
+import static io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall;
+
+/**
+ * <pre>
+ * A simple service for test.
+ * </pre>
+ */
+@javax.annotation.Generated(
+    value = "by gRPC proto compiler",
+    comments = "Source: io/grpc/testing/protobuf/simpleservice.proto")
+public final class SimpleServiceGrpc {
+
+  private SimpleServiceGrpc() {}
+
+  public static final String SERVICE_NAME = "grpc.testing.SimpleService";
+
+  // Static method descriptors that strictly reflect the proto.
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.protobuf.SimpleRequest,
+      io.grpc.testing.protobuf.SimpleResponse> getUnaryRpcMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "UnaryRpc",
+      requestType = io.grpc.testing.protobuf.SimpleRequest.class,
+      responseType = io.grpc.testing.protobuf.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.protobuf.SimpleRequest,
+      io.grpc.testing.protobuf.SimpleResponse> getUnaryRpcMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.protobuf.SimpleRequest, io.grpc.testing.protobuf.SimpleResponse> getUnaryRpcMethod;
+    if ((getUnaryRpcMethod = SimpleServiceGrpc.getUnaryRpcMethod) == null) {
+      synchronized (SimpleServiceGrpc.class) {
+        if ((getUnaryRpcMethod = SimpleServiceGrpc.getUnaryRpcMethod) == null) {
+          SimpleServiceGrpc.getUnaryRpcMethod = getUnaryRpcMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.protobuf.SimpleRequest, io.grpc.testing.protobuf.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.UNARY)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.SimpleService", "UnaryRpc"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.protobuf.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.protobuf.SimpleResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new SimpleServiceMethodDescriptorSupplier("UnaryRpc"))
+                  .build();
+          }
+        }
+     }
+     return getUnaryRpcMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.protobuf.SimpleRequest,
+      io.grpc.testing.protobuf.SimpleResponse> getClientStreamingRpcMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "ClientStreamingRpc",
+      requestType = io.grpc.testing.protobuf.SimpleRequest.class,
+      responseType = io.grpc.testing.protobuf.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.protobuf.SimpleRequest,
+      io.grpc.testing.protobuf.SimpleResponse> getClientStreamingRpcMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.protobuf.SimpleRequest, io.grpc.testing.protobuf.SimpleResponse> getClientStreamingRpcMethod;
+    if ((getClientStreamingRpcMethod = SimpleServiceGrpc.getClientStreamingRpcMethod) == null) {
+      synchronized (SimpleServiceGrpc.class) {
+        if ((getClientStreamingRpcMethod = SimpleServiceGrpc.getClientStreamingRpcMethod) == null) {
+          SimpleServiceGrpc.getClientStreamingRpcMethod = getClientStreamingRpcMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.protobuf.SimpleRequest, io.grpc.testing.protobuf.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.SimpleService", "ClientStreamingRpc"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.protobuf.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.protobuf.SimpleResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new SimpleServiceMethodDescriptorSupplier("ClientStreamingRpc"))
+                  .build();
+          }
+        }
+     }
+     return getClientStreamingRpcMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.protobuf.SimpleRequest,
+      io.grpc.testing.protobuf.SimpleResponse> getServerStreamingRpcMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "ServerStreamingRpc",
+      requestType = io.grpc.testing.protobuf.SimpleRequest.class,
+      responseType = io.grpc.testing.protobuf.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.protobuf.SimpleRequest,
+      io.grpc.testing.protobuf.SimpleResponse> getServerStreamingRpcMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.protobuf.SimpleRequest, io.grpc.testing.protobuf.SimpleResponse> getServerStreamingRpcMethod;
+    if ((getServerStreamingRpcMethod = SimpleServiceGrpc.getServerStreamingRpcMethod) == null) {
+      synchronized (SimpleServiceGrpc.class) {
+        if ((getServerStreamingRpcMethod = SimpleServiceGrpc.getServerStreamingRpcMethod) == null) {
+          SimpleServiceGrpc.getServerStreamingRpcMethod = getServerStreamingRpcMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.protobuf.SimpleRequest, io.grpc.testing.protobuf.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.SimpleService", "ServerStreamingRpc"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.protobuf.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.protobuf.SimpleResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new SimpleServiceMethodDescriptorSupplier("ServerStreamingRpc"))
+                  .build();
+          }
+        }
+     }
+     return getServerStreamingRpcMethod;
+  }
+
+  private static volatile io.grpc.MethodDescriptor<io.grpc.testing.protobuf.SimpleRequest,
+      io.grpc.testing.protobuf.SimpleResponse> getBidiStreamingRpcMethod;
+
+  @io.grpc.stub.annotations.RpcMethod(
+      fullMethodName = SERVICE_NAME + '/' + "BidiStreamingRpc",
+      requestType = io.grpc.testing.protobuf.SimpleRequest.class,
+      responseType = io.grpc.testing.protobuf.SimpleResponse.class,
+      methodType = io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+  public static io.grpc.MethodDescriptor<io.grpc.testing.protobuf.SimpleRequest,
+      io.grpc.testing.protobuf.SimpleResponse> getBidiStreamingRpcMethod() {
+    io.grpc.MethodDescriptor<io.grpc.testing.protobuf.SimpleRequest, io.grpc.testing.protobuf.SimpleResponse> getBidiStreamingRpcMethod;
+    if ((getBidiStreamingRpcMethod = SimpleServiceGrpc.getBidiStreamingRpcMethod) == null) {
+      synchronized (SimpleServiceGrpc.class) {
+        if ((getBidiStreamingRpcMethod = SimpleServiceGrpc.getBidiStreamingRpcMethod) == null) {
+          SimpleServiceGrpc.getBidiStreamingRpcMethod = getBidiStreamingRpcMethod = 
+              io.grpc.MethodDescriptor.<io.grpc.testing.protobuf.SimpleRequest, io.grpc.testing.protobuf.SimpleResponse>newBuilder()
+              .setType(io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING)
+              .setFullMethodName(generateFullMethodName(
+                  "grpc.testing.SimpleService", "BidiStreamingRpc"))
+              .setSampledToLocalTracing(true)
+              .setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.protobuf.SimpleRequest.getDefaultInstance()))
+              .setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
+                  io.grpc.testing.protobuf.SimpleResponse.getDefaultInstance()))
+                  .setSchemaDescriptor(new SimpleServiceMethodDescriptorSupplier("BidiStreamingRpc"))
+                  .build();
+          }
+        }
+     }
+     return getBidiStreamingRpcMethod;
+  }
+
+  /**
+   * Creates a new async stub that supports all call types for the service
+   */
+  public static SimpleServiceStub newStub(io.grpc.Channel channel) {
+    return new SimpleServiceStub(channel);
+  }
+
+  /**
+   * Creates a new blocking-style stub that supports unary and streaming output calls on the service
+   */
+  public static SimpleServiceBlockingStub newBlockingStub(
+      io.grpc.Channel channel) {
+    return new SimpleServiceBlockingStub(channel);
+  }
+
+  /**
+   * Creates a new ListenableFuture-style stub that supports unary calls on the service
+   */
+  public static SimpleServiceFutureStub newFutureStub(
+      io.grpc.Channel channel) {
+    return new SimpleServiceFutureStub(channel);
+  }
+
+  /**
+   * <pre>
+   * A simple service for test.
+   * </pre>
+   */
+  public static abstract class SimpleServiceImplBase implements io.grpc.BindableService {
+
+    /**
+     * <pre>
+     * Simple unary RPC.
+     * </pre>
+     */
+    public void unaryRpc(io.grpc.testing.protobuf.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getUnaryRpcMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Simple client-to-server streaming RPC.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleRequest> clientStreamingRpc(
+        io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getClientStreamingRpcMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Simple server-to-client streaming RPC.
+     * </pre>
+     */
+    public void serverStreamingRpc(io.grpc.testing.protobuf.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleResponse> responseObserver) {
+      asyncUnimplementedUnaryCall(getServerStreamingRpcMethod(), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Simple bidirectional streaming RPC.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleRequest> bidiStreamingRpc(
+        io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleResponse> responseObserver) {
+      return asyncUnimplementedStreamingCall(getBidiStreamingRpcMethod(), responseObserver);
+    }
+
+    @java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
+      return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
+          .addMethod(
+            getUnaryRpcMethod(),
+            asyncUnaryCall(
+              new MethodHandlers<
+                io.grpc.testing.protobuf.SimpleRequest,
+                io.grpc.testing.protobuf.SimpleResponse>(
+                  this, METHODID_UNARY_RPC)))
+          .addMethod(
+            getClientStreamingRpcMethod(),
+            asyncClientStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.protobuf.SimpleRequest,
+                io.grpc.testing.protobuf.SimpleResponse>(
+                  this, METHODID_CLIENT_STREAMING_RPC)))
+          .addMethod(
+            getServerStreamingRpcMethod(),
+            asyncServerStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.protobuf.SimpleRequest,
+                io.grpc.testing.protobuf.SimpleResponse>(
+                  this, METHODID_SERVER_STREAMING_RPC)))
+          .addMethod(
+            getBidiStreamingRpcMethod(),
+            asyncBidiStreamingCall(
+              new MethodHandlers<
+                io.grpc.testing.protobuf.SimpleRequest,
+                io.grpc.testing.protobuf.SimpleResponse>(
+                  this, METHODID_BIDI_STREAMING_RPC)))
+          .build();
+    }
+  }
+
+  /**
+   * <pre>
+   * A simple service for test.
+   * </pre>
+   */
+  public static final class SimpleServiceStub extends io.grpc.stub.AbstractStub<SimpleServiceStub> {
+    private SimpleServiceStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private SimpleServiceStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected SimpleServiceStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new SimpleServiceStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Simple unary RPC.
+     * </pre>
+     */
+    public void unaryRpc(io.grpc.testing.protobuf.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleResponse> responseObserver) {
+      asyncUnaryCall(
+          getChannel().newCall(getUnaryRpcMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Simple client-to-server streaming RPC.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleRequest> clientStreamingRpc(
+        io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleResponse> responseObserver) {
+      return asyncClientStreamingCall(
+          getChannel().newCall(getClientStreamingRpcMethod(), getCallOptions()), responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Simple server-to-client streaming RPC.
+     * </pre>
+     */
+    public void serverStreamingRpc(io.grpc.testing.protobuf.SimpleRequest request,
+        io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleResponse> responseObserver) {
+      asyncServerStreamingCall(
+          getChannel().newCall(getServerStreamingRpcMethod(), getCallOptions()), request, responseObserver);
+    }
+
+    /**
+     * <pre>
+     * Simple bidirectional streaming RPC.
+     * </pre>
+     */
+    public io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleRequest> bidiStreamingRpc(
+        io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleResponse> responseObserver) {
+      return asyncBidiStreamingCall(
+          getChannel().newCall(getBidiStreamingRpcMethod(), getCallOptions()), responseObserver);
+    }
+  }
+
+  /**
+   * <pre>
+   * A simple service for test.
+   * </pre>
+   */
+  public static final class SimpleServiceBlockingStub extends io.grpc.stub.AbstractStub<SimpleServiceBlockingStub> {
+    private SimpleServiceBlockingStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private SimpleServiceBlockingStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected SimpleServiceBlockingStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new SimpleServiceBlockingStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Simple unary RPC.
+     * </pre>
+     */
+    public io.grpc.testing.protobuf.SimpleResponse unaryRpc(io.grpc.testing.protobuf.SimpleRequest request) {
+      return blockingUnaryCall(
+          getChannel(), getUnaryRpcMethod(), getCallOptions(), request);
+    }
+
+    /**
+     * <pre>
+     * Simple server-to-client streaming RPC.
+     * </pre>
+     */
+    public java.util.Iterator<io.grpc.testing.protobuf.SimpleResponse> serverStreamingRpc(
+        io.grpc.testing.protobuf.SimpleRequest request) {
+      return blockingServerStreamingCall(
+          getChannel(), getServerStreamingRpcMethod(), getCallOptions(), request);
+    }
+  }
+
+  /**
+   * <pre>
+   * A simple service for test.
+   * </pre>
+   */
+  public static final class SimpleServiceFutureStub extends io.grpc.stub.AbstractStub<SimpleServiceFutureStub> {
+    private SimpleServiceFutureStub(io.grpc.Channel channel) {
+      super(channel);
+    }
+
+    private SimpleServiceFutureStub(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      super(channel, callOptions);
+    }
+
+    @java.lang.Override
+    protected SimpleServiceFutureStub build(io.grpc.Channel channel,
+        io.grpc.CallOptions callOptions) {
+      return new SimpleServiceFutureStub(channel, callOptions);
+    }
+
+    /**
+     * <pre>
+     * Simple unary RPC.
+     * </pre>
+     */
+    public com.google.common.util.concurrent.ListenableFuture<io.grpc.testing.protobuf.SimpleResponse> unaryRpc(
+        io.grpc.testing.protobuf.SimpleRequest request) {
+      return futureUnaryCall(
+          getChannel().newCall(getUnaryRpcMethod(), getCallOptions()), request);
+    }
+  }
+
+  private static final int METHODID_UNARY_RPC = 0;
+  private static final int METHODID_SERVER_STREAMING_RPC = 1;
+  private static final int METHODID_CLIENT_STREAMING_RPC = 2;
+  private static final int METHODID_BIDI_STREAMING_RPC = 3;
+
+  private static final class MethodHandlers<Req, Resp> implements
+      io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ServerStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.ClientStreamingMethod<Req, Resp>,
+      io.grpc.stub.ServerCalls.BidiStreamingMethod<Req, Resp> {
+    private final SimpleServiceImplBase serviceImpl;
+    private final int methodId;
+
+    MethodHandlers(SimpleServiceImplBase serviceImpl, int methodId) {
+      this.serviceImpl = serviceImpl;
+      this.methodId = methodId;
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_UNARY_RPC:
+          serviceImpl.unaryRpc((io.grpc.testing.protobuf.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleResponse>) responseObserver);
+          break;
+        case METHODID_SERVER_STREAMING_RPC:
+          serviceImpl.serverStreamingRpc((io.grpc.testing.protobuf.SimpleRequest) request,
+              (io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleResponse>) responseObserver);
+          break;
+        default:
+          throw new AssertionError();
+      }
+    }
+
+    @java.lang.Override
+    @java.lang.SuppressWarnings("unchecked")
+    public io.grpc.stub.StreamObserver<Req> invoke(
+        io.grpc.stub.StreamObserver<Resp> responseObserver) {
+      switch (methodId) {
+        case METHODID_CLIENT_STREAMING_RPC:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.clientStreamingRpc(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleResponse>) responseObserver);
+        case METHODID_BIDI_STREAMING_RPC:
+          return (io.grpc.stub.StreamObserver<Req>) serviceImpl.bidiStreamingRpc(
+              (io.grpc.stub.StreamObserver<io.grpc.testing.protobuf.SimpleResponse>) responseObserver);
+        default:
+          throw new AssertionError();
+      }
+    }
+  }
+
+  private static abstract class SimpleServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoFileDescriptorSupplier, io.grpc.protobuf.ProtoServiceDescriptorSupplier {
+    SimpleServiceBaseDescriptorSupplier() {}
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.FileDescriptor getFileDescriptor() {
+      return io.grpc.testing.protobuf.SimpleServiceProto.getDescriptor();
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.ServiceDescriptor getServiceDescriptor() {
+      return getFileDescriptor().findServiceByName("SimpleService");
+    }
+  }
+
+  private static final class SimpleServiceFileDescriptorSupplier
+      extends SimpleServiceBaseDescriptorSupplier {
+    SimpleServiceFileDescriptorSupplier() {}
+  }
+
+  private static final class SimpleServiceMethodDescriptorSupplier
+      extends SimpleServiceBaseDescriptorSupplier
+      implements io.grpc.protobuf.ProtoMethodDescriptorSupplier {
+    private final String methodName;
+
+    SimpleServiceMethodDescriptorSupplier(String methodName) {
+      this.methodName = methodName;
+    }
+
+    @java.lang.Override
+    public com.google.protobuf.Descriptors.MethodDescriptor getMethodDescriptor() {
+      return getServiceDescriptor().findMethodByName(methodName);
+    }
+  }
+
+  private static volatile io.grpc.ServiceDescriptor serviceDescriptor;
+
+  public static io.grpc.ServiceDescriptor getServiceDescriptor() {
+    io.grpc.ServiceDescriptor result = serviceDescriptor;
+    if (result == null) {
+      synchronized (SimpleServiceGrpc.class) {
+        result = serviceDescriptor;
+        if (result == null) {
+          serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
+              .setSchemaDescriptor(new SimpleServiceFileDescriptorSupplier())
+              .addMethod(getUnaryRpcMethod())
+              .addMethod(getClientStreamingRpcMethod())
+              .addMethod(getServerStreamingRpcMethod())
+              .addMethod(getBidiStreamingRpcMethod())
+              .build();
+        }
+      }
+    }
+    return result;
+  }
+}
diff --git a/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleRequest.java b/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleRequest.java
new file mode 100644
index 0000000..f39c33c
--- /dev/null
+++ b/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleRequest.java
@@ -0,0 +1,552 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/testing/protobuf/simpleservice.proto
+
+package io.grpc.testing.protobuf;
+
+/**
+ * <pre>
+ * A simple request message type for test.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.testing.SimpleRequest}
+ */
+public  final class SimpleRequest extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.testing.SimpleRequest)
+    SimpleRequestOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use SimpleRequest.newBuilder() to construct.
+  private SimpleRequest(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private SimpleRequest() {
+    requestMessage_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private SimpleRequest(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            requestMessage_ = s;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.testing.protobuf.SimpleServiceProto.internal_static_grpc_testing_SimpleRequest_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.testing.protobuf.SimpleServiceProto.internal_static_grpc_testing_SimpleRequest_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.testing.protobuf.SimpleRequest.class, io.grpc.testing.protobuf.SimpleRequest.Builder.class);
+  }
+
+  public static final int REQUESTMESSAGE_FIELD_NUMBER = 1;
+  private volatile java.lang.Object requestMessage_;
+  /**
+   * <pre>
+   * An optional string message for test.
+   * </pre>
+   *
+   * <code>string requestMessage = 1;</code>
+   */
+  public java.lang.String getRequestMessage() {
+    java.lang.Object ref = requestMessage_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      requestMessage_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * An optional string message for test.
+   * </pre>
+   *
+   * <code>string requestMessage = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getRequestMessageBytes() {
+    java.lang.Object ref = requestMessage_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      requestMessage_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getRequestMessageBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, requestMessage_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getRequestMessageBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, requestMessage_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.testing.protobuf.SimpleRequest)) {
+      return super.equals(obj);
+    }
+    io.grpc.testing.protobuf.SimpleRequest other = (io.grpc.testing.protobuf.SimpleRequest) obj;
+
+    boolean result = true;
+    result = result && getRequestMessage()
+        .equals(other.getRequestMessage());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + REQUESTMESSAGE_FIELD_NUMBER;
+    hash = (53 * hash) + getRequestMessage().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.testing.protobuf.SimpleRequest parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.testing.protobuf.SimpleRequest parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.testing.protobuf.SimpleRequest parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.testing.protobuf.SimpleRequest parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.testing.protobuf.SimpleRequest parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.testing.protobuf.SimpleRequest parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.testing.protobuf.SimpleRequest parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.testing.protobuf.SimpleRequest parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.testing.protobuf.SimpleRequest parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.testing.protobuf.SimpleRequest parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.testing.protobuf.SimpleRequest parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.testing.protobuf.SimpleRequest parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.testing.protobuf.SimpleRequest prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * A simple request message type for test.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.SimpleRequest}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.testing.SimpleRequest)
+      io.grpc.testing.protobuf.SimpleRequestOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.protobuf.SimpleServiceProto.internal_static_grpc_testing_SimpleRequest_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.protobuf.SimpleServiceProto.internal_static_grpc_testing_SimpleRequest_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.protobuf.SimpleRequest.class, io.grpc.testing.protobuf.SimpleRequest.Builder.class);
+    }
+
+    // Construct using io.grpc.testing.protobuf.SimpleRequest.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      requestMessage_ = "";
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.testing.protobuf.SimpleServiceProto.internal_static_grpc_testing_SimpleRequest_descriptor;
+    }
+
+    public io.grpc.testing.protobuf.SimpleRequest getDefaultInstanceForType() {
+      return io.grpc.testing.protobuf.SimpleRequest.getDefaultInstance();
+    }
+
+    public io.grpc.testing.protobuf.SimpleRequest build() {
+      io.grpc.testing.protobuf.SimpleRequest result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.testing.protobuf.SimpleRequest buildPartial() {
+      io.grpc.testing.protobuf.SimpleRequest result = new io.grpc.testing.protobuf.SimpleRequest(this);
+      result.requestMessage_ = requestMessage_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.testing.protobuf.SimpleRequest) {
+        return mergeFrom((io.grpc.testing.protobuf.SimpleRequest)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.testing.protobuf.SimpleRequest other) {
+      if (other == io.grpc.testing.protobuf.SimpleRequest.getDefaultInstance()) return this;
+      if (!other.getRequestMessage().isEmpty()) {
+        requestMessage_ = other.requestMessage_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.testing.protobuf.SimpleRequest parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.testing.protobuf.SimpleRequest) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private java.lang.Object requestMessage_ = "";
+    /**
+     * <pre>
+     * An optional string message for test.
+     * </pre>
+     *
+     * <code>string requestMessage = 1;</code>
+     */
+    public java.lang.String getRequestMessage() {
+      java.lang.Object ref = requestMessage_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        requestMessage_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An optional string message for test.
+     * </pre>
+     *
+     * <code>string requestMessage = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getRequestMessageBytes() {
+      java.lang.Object ref = requestMessage_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        requestMessage_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An optional string message for test.
+     * </pre>
+     *
+     * <code>string requestMessage = 1;</code>
+     */
+    public Builder setRequestMessage(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      requestMessage_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An optional string message for test.
+     * </pre>
+     *
+     * <code>string requestMessage = 1;</code>
+     */
+    public Builder clearRequestMessage() {
+      
+      requestMessage_ = getDefaultInstance().getRequestMessage();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An optional string message for test.
+     * </pre>
+     *
+     * <code>string requestMessage = 1;</code>
+     */
+    public Builder setRequestMessageBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      requestMessage_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.testing.SimpleRequest)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.testing.SimpleRequest)
+  private static final io.grpc.testing.protobuf.SimpleRequest DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.testing.protobuf.SimpleRequest();
+  }
+
+  public static io.grpc.testing.protobuf.SimpleRequest getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<SimpleRequest>
+      PARSER = new com.google.protobuf.AbstractParser<SimpleRequest>() {
+    public SimpleRequest parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new SimpleRequest(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<SimpleRequest> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<SimpleRequest> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.testing.protobuf.SimpleRequest getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleRequestOrBuilder.java b/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleRequestOrBuilder.java
new file mode 100644
index 0000000..a7e9afe
--- /dev/null
+++ b/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleRequestOrBuilder.java
@@ -0,0 +1,27 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/testing/protobuf/simpleservice.proto
+
+package io.grpc.testing.protobuf;
+
+public interface SimpleRequestOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.testing.SimpleRequest)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * An optional string message for test.
+   * </pre>
+   *
+   * <code>string requestMessage = 1;</code>
+   */
+  java.lang.String getRequestMessage();
+  /**
+   * <pre>
+   * An optional string message for test.
+   * </pre>
+   *
+   * <code>string requestMessage = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getRequestMessageBytes();
+}
diff --git a/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleResponse.java b/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleResponse.java
new file mode 100644
index 0000000..b93f6b7
--- /dev/null
+++ b/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleResponse.java
@@ -0,0 +1,552 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/testing/protobuf/simpleservice.proto
+
+package io.grpc.testing.protobuf;
+
+/**
+ * <pre>
+ * A simple response message type for test.
+ * </pre>
+ *
+ * Protobuf type {@code grpc.testing.SimpleResponse}
+ */
+public  final class SimpleResponse extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:grpc.testing.SimpleResponse)
+    SimpleResponseOrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use SimpleResponse.newBuilder() to construct.
+  private SimpleResponse(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private SimpleResponse() {
+    responseMessage_ = "";
+  }
+
+  @java.lang.Override
+  public final com.google.protobuf.UnknownFieldSet
+  getUnknownFields() {
+    return this.unknownFields;
+  }
+  private SimpleResponse(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    this();
+    if (extensionRegistry == null) {
+      throw new java.lang.NullPointerException();
+    }
+    int mutable_bitField0_ = 0;
+    com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+        com.google.protobuf.UnknownFieldSet.newBuilder();
+    try {
+      boolean done = false;
+      while (!done) {
+        int tag = input.readTag();
+        switch (tag) {
+          case 0:
+            done = true;
+            break;
+          default: {
+            if (!parseUnknownFieldProto3(
+                input, unknownFields, extensionRegistry, tag)) {
+              done = true;
+            }
+            break;
+          }
+          case 10: {
+            java.lang.String s = input.readStringRequireUtf8();
+
+            responseMessage_ = s;
+            break;
+          }
+        }
+      }
+    } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (java.io.IOException e) {
+      throw new com.google.protobuf.InvalidProtocolBufferException(
+          e).setUnfinishedMessage(this);
+    } finally {
+      this.unknownFields = unknownFields.build();
+      makeExtensionsImmutable();
+    }
+  }
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return io.grpc.testing.protobuf.SimpleServiceProto.internal_static_grpc_testing_SimpleResponse_descriptor;
+  }
+
+  protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return io.grpc.testing.protobuf.SimpleServiceProto.internal_static_grpc_testing_SimpleResponse_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            io.grpc.testing.protobuf.SimpleResponse.class, io.grpc.testing.protobuf.SimpleResponse.Builder.class);
+  }
+
+  public static final int RESPONSEMESSAGE_FIELD_NUMBER = 1;
+  private volatile java.lang.Object responseMessage_;
+  /**
+   * <pre>
+   * An optional string message for test.
+   * </pre>
+   *
+   * <code>string responseMessage = 1;</code>
+   */
+  public java.lang.String getResponseMessage() {
+    java.lang.Object ref = responseMessage_;
+    if (ref instanceof java.lang.String) {
+      return (java.lang.String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      java.lang.String s = bs.toStringUtf8();
+      responseMessage_ = s;
+      return s;
+    }
+  }
+  /**
+   * <pre>
+   * An optional string message for test.
+   * </pre>
+   *
+   * <code>string responseMessage = 1;</code>
+   */
+  public com.google.protobuf.ByteString
+      getResponseMessageBytes() {
+    java.lang.Object ref = responseMessage_;
+    if (ref instanceof java.lang.String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (java.lang.String) ref);
+      responseMessage_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!getResponseMessageBytes().isEmpty()) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 1, responseMessage_);
+    }
+    unknownFields.writeTo(output);
+  }
+
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!getResponseMessageBytes().isEmpty()) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, responseMessage_);
+    }
+    size += unknownFields.getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @java.lang.Override
+  public boolean equals(final java.lang.Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof io.grpc.testing.protobuf.SimpleResponse)) {
+      return super.equals(obj);
+    }
+    io.grpc.testing.protobuf.SimpleResponse other = (io.grpc.testing.protobuf.SimpleResponse) obj;
+
+    boolean result = true;
+    result = result && getResponseMessage()
+        .equals(other.getResponseMessage());
+    result = result && unknownFields.equals(other.unknownFields);
+    return result;
+  }
+
+  @java.lang.Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + RESPONSEMESSAGE_FIELD_NUMBER;
+    hash = (53 * hash) + getResponseMessage().hashCode();
+    hash = (29 * hash) + unknownFields.hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static io.grpc.testing.protobuf.SimpleResponse parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.testing.protobuf.SimpleResponse parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.testing.protobuf.SimpleResponse parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.testing.protobuf.SimpleResponse parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.testing.protobuf.SimpleResponse parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static io.grpc.testing.protobuf.SimpleResponse parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static io.grpc.testing.protobuf.SimpleResponse parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.testing.protobuf.SimpleResponse parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.testing.protobuf.SimpleResponse parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+  public static io.grpc.testing.protobuf.SimpleResponse parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static io.grpc.testing.protobuf.SimpleResponse parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static io.grpc.testing.protobuf.SimpleResponse parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(io.grpc.testing.protobuf.SimpleResponse prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @java.lang.Override
+  protected Builder newBuilderForType(
+      com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * <pre>
+   * A simple response message type for test.
+   * </pre>
+   *
+   * Protobuf type {@code grpc.testing.SimpleResponse}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:grpc.testing.SimpleResponse)
+      io.grpc.testing.protobuf.SimpleResponseOrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return io.grpc.testing.protobuf.SimpleServiceProto.internal_static_grpc_testing_SimpleResponse_descriptor;
+    }
+
+    protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return io.grpc.testing.protobuf.SimpleServiceProto.internal_static_grpc_testing_SimpleResponse_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              io.grpc.testing.protobuf.SimpleResponse.class, io.grpc.testing.protobuf.SimpleResponse.Builder.class);
+    }
+
+    // Construct using io.grpc.testing.protobuf.SimpleResponse.newBuilder()
+    private Builder() {
+      maybeForceBuilderInitialization();
+    }
+
+    private Builder(
+        com.google.protobuf.GeneratedMessageV3.BuilderParent parent) {
+      super(parent);
+      maybeForceBuilderInitialization();
+    }
+    private void maybeForceBuilderInitialization() {
+      if (com.google.protobuf.GeneratedMessageV3
+              .alwaysUseFieldBuilders) {
+      }
+    }
+    public Builder clear() {
+      super.clear();
+      responseMessage_ = "";
+
+      return this;
+    }
+
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return io.grpc.testing.protobuf.SimpleServiceProto.internal_static_grpc_testing_SimpleResponse_descriptor;
+    }
+
+    public io.grpc.testing.protobuf.SimpleResponse getDefaultInstanceForType() {
+      return io.grpc.testing.protobuf.SimpleResponse.getDefaultInstance();
+    }
+
+    public io.grpc.testing.protobuf.SimpleResponse build() {
+      io.grpc.testing.protobuf.SimpleResponse result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    public io.grpc.testing.protobuf.SimpleResponse buildPartial() {
+      io.grpc.testing.protobuf.SimpleResponse result = new io.grpc.testing.protobuf.SimpleResponse(this);
+      result.responseMessage_ = responseMessage_;
+      onBuilt();
+      return result;
+    }
+
+    public Builder clone() {
+      return (Builder) super.clone();
+    }
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.setField(field, value);
+    }
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return (Builder) super.clearField(field);
+    }
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return (Builder) super.clearOneof(oneof);
+    }
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, java.lang.Object value) {
+      return (Builder) super.setRepeatedField(field, index, value);
+    }
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        java.lang.Object value) {
+      return (Builder) super.addRepeatedField(field, value);
+    }
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof io.grpc.testing.protobuf.SimpleResponse) {
+        return mergeFrom((io.grpc.testing.protobuf.SimpleResponse)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(io.grpc.testing.protobuf.SimpleResponse other) {
+      if (other == io.grpc.testing.protobuf.SimpleResponse.getDefaultInstance()) return this;
+      if (!other.getResponseMessage().isEmpty()) {
+        responseMessage_ = other.responseMessage_;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.unknownFields);
+      onChanged();
+      return this;
+    }
+
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      io.grpc.testing.protobuf.SimpleResponse parsedMessage = null;
+      try {
+        parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        parsedMessage = (io.grpc.testing.protobuf.SimpleResponse) e.getUnfinishedMessage();
+        throw e.unwrapIOException();
+      } finally {
+        if (parsedMessage != null) {
+          mergeFrom(parsedMessage);
+        }
+      }
+      return this;
+    }
+
+    private java.lang.Object responseMessage_ = "";
+    /**
+     * <pre>
+     * An optional string message for test.
+     * </pre>
+     *
+     * <code>string responseMessage = 1;</code>
+     */
+    public java.lang.String getResponseMessage() {
+      java.lang.Object ref = responseMessage_;
+      if (!(ref instanceof java.lang.String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        java.lang.String s = bs.toStringUtf8();
+        responseMessage_ = s;
+        return s;
+      } else {
+        return (java.lang.String) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An optional string message for test.
+     * </pre>
+     *
+     * <code>string responseMessage = 1;</code>
+     */
+    public com.google.protobuf.ByteString
+        getResponseMessageBytes() {
+      java.lang.Object ref = responseMessage_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (java.lang.String) ref);
+        responseMessage_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <pre>
+     * An optional string message for test.
+     * </pre>
+     *
+     * <code>string responseMessage = 1;</code>
+     */
+    public Builder setResponseMessage(
+        java.lang.String value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  
+      responseMessage_ = value;
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An optional string message for test.
+     * </pre>
+     *
+     * <code>string responseMessage = 1;</code>
+     */
+    public Builder clearResponseMessage() {
+      
+      responseMessage_ = getDefaultInstance().getResponseMessage();
+      onChanged();
+      return this;
+    }
+    /**
+     * <pre>
+     * An optional string message for test.
+     * </pre>
+     *
+     * <code>string responseMessage = 1;</code>
+     */
+    public Builder setResponseMessageBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) {
+    throw new NullPointerException();
+  }
+  checkByteStringIsUtf8(value);
+      
+      responseMessage_ = value;
+      onChanged();
+      return this;
+    }
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFieldsProto3(unknownFields);
+    }
+
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:grpc.testing.SimpleResponse)
+  }
+
+  // @@protoc_insertion_point(class_scope:grpc.testing.SimpleResponse)
+  private static final io.grpc.testing.protobuf.SimpleResponse DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new io.grpc.testing.protobuf.SimpleResponse();
+  }
+
+  public static io.grpc.testing.protobuf.SimpleResponse getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<SimpleResponse>
+      PARSER = new com.google.protobuf.AbstractParser<SimpleResponse>() {
+    public SimpleResponse parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return new SimpleResponse(input, extensionRegistry);
+    }
+  };
+
+  public static com.google.protobuf.Parser<SimpleResponse> parser() {
+    return PARSER;
+  }
+
+  @java.lang.Override
+  public com.google.protobuf.Parser<SimpleResponse> getParserForType() {
+    return PARSER;
+  }
+
+  public io.grpc.testing.protobuf.SimpleResponse getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+
diff --git a/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleResponseOrBuilder.java b/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleResponseOrBuilder.java
new file mode 100644
index 0000000..1426a69
--- /dev/null
+++ b/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleResponseOrBuilder.java
@@ -0,0 +1,27 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/testing/protobuf/simpleservice.proto
+
+package io.grpc.testing.protobuf;
+
+public interface SimpleResponseOrBuilder extends
+    // @@protoc_insertion_point(interface_extends:grpc.testing.SimpleResponse)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <pre>
+   * An optional string message for test.
+   * </pre>
+   *
+   * <code>string responseMessage = 1;</code>
+   */
+  java.lang.String getResponseMessage();
+  /**
+   * <pre>
+   * An optional string message for test.
+   * </pre>
+   *
+   * <code>string responseMessage = 1;</code>
+   */
+  com.google.protobuf.ByteString
+      getResponseMessageBytes();
+}
diff --git a/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleServiceProto.java b/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleServiceProto.java
new file mode 100644
index 0000000..b042763
--- /dev/null
+++ b/testing-proto/src/generated/main/java/io/grpc/testing/protobuf/SimpleServiceProto.java
@@ -0,0 +1,78 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: io/grpc/testing/protobuf/simpleservice.proto
+
+package io.grpc.testing.protobuf;
+
+public final class SimpleServiceProto {
+  private SimpleServiceProto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_SimpleRequest_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_SimpleRequest_fieldAccessorTable;
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_grpc_testing_SimpleResponse_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_grpc_testing_SimpleResponse_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n,io/grpc/testing/protobuf/simpleservice" +
+      ".proto\022\014grpc.testing\"\'\n\rSimpleRequest\022\026\n" +
+      "\016requestMessage\030\001 \001(\t\")\n\016SimpleResponse\022" +
+      "\027\n\017responseMessage\030\001 \001(\t2\327\002\n\rSimpleServi" +
+      "ce\022G\n\010UnaryRpc\022\033.grpc.testing.SimpleRequ" +
+      "est\032\034.grpc.testing.SimpleResponse\"\000\022S\n\022C" +
+      "lientStreamingRpc\022\033.grpc.testing.SimpleR" +
+      "equest\032\034.grpc.testing.SimpleResponse\"\000(\001" +
+      "\022S\n\022ServerStreamingRpc\022\033.grpc.testing.Si" +
+      "mpleRequest\032\034.grpc.testing.SimpleRespons" +
+      "e\"\0000\001\022S\n\020BidiStreamingRpc\022\033.grpc.testing" +
+      ".SimpleRequest\032\034.grpc.testing.SimpleResp" +
+      "onse\"\000(\0010\001B0\n\030io.grpc.testing.protobufB\022" +
+      "SimpleServiceProtoP\001b\006proto3"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+        new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
+          public com.google.protobuf.ExtensionRegistry assignDescriptors(
+              com.google.protobuf.Descriptors.FileDescriptor root) {
+            descriptor = root;
+            return null;
+          }
+        };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        }, assigner);
+    internal_static_grpc_testing_SimpleRequest_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_grpc_testing_SimpleRequest_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_SimpleRequest_descriptor,
+        new java.lang.String[] { "RequestMessage", });
+    internal_static_grpc_testing_SimpleResponse_descriptor =
+      getDescriptor().getMessageTypes().get(1);
+    internal_static_grpc_testing_SimpleResponse_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_grpc_testing_SimpleResponse_descriptor,
+        new java.lang.String[] { "ResponseMessage", });
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/testing-proto/src/main/proto/io/grpc/testing/protobuf/simpleservice.proto b/testing-proto/src/main/proto/io/grpc/testing/protobuf/simpleservice.proto
new file mode 100644
index 0000000..154cc6a
--- /dev/null
+++ b/testing-proto/src/main/proto/io/grpc/testing/protobuf/simpleservice.proto
@@ -0,0 +1,51 @@
+// Copyright 2017 The gRPC Authors
+// All rights reserved.
+//
+// Licensed 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.
+// An integration test service that covers all the method signature permutations
+// of unary/streaming requests/responses.
+
+syntax = "proto3";
+
+package grpc.testing;
+
+option java_package = "io.grpc.testing.protobuf";
+option java_outer_classname = "SimpleServiceProto";
+option java_multiple_files = true;
+
+// A simple service for test.
+service SimpleService {
+  // Simple unary RPC.
+  rpc UnaryRpc (SimpleRequest) returns (SimpleResponse) {}
+
+  // Simple client-to-server streaming RPC.
+  rpc ClientStreamingRpc (stream SimpleRequest) returns (SimpleResponse) {}
+
+  // Simple server-to-client streaming RPC.
+  rpc ServerStreamingRpc (SimpleRequest) returns (stream SimpleResponse) {}
+
+  // Simple bidirectional streaming RPC.
+  rpc BidiStreamingRpc (stream SimpleRequest) returns (stream SimpleResponse) {}
+}
+
+// A simple request message type for test.
+message SimpleRequest {
+  // An optional string message for test.
+  string requestMessage = 1;
+}
+
+// A simple response message type for test.
+message SimpleResponse {
+  // An optional string message for test.
+  string responseMessage = 1;
+}
diff --git a/testing-proto/src/test/java/io/grpc/testing/protobuf/SimpleServiceTest.java b/testing-proto/src/test/java/io/grpc/testing/protobuf/SimpleServiceTest.java
new file mode 100644
index 0000000..85c39f3
--- /dev/null
+++ b/testing-proto/src/test/java/io/grpc/testing/protobuf/SimpleServiceTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing.protobuf;
+
+import static io.grpc.MethodDescriptor.MethodType.BIDI_STREAMING;
+import static io.grpc.MethodDescriptor.MethodType.CLIENT_STREAMING;
+import static io.grpc.MethodDescriptor.MethodType.SERVER_STREAMING;
+import static io.grpc.MethodDescriptor.MethodType.UNARY;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import io.grpc.MethodDescriptor;
+import io.grpc.stub.annotations.RpcMethod;
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.MirroredTypeException;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test to verify that the proto file simpleservice.proto generates the expected service. */
+@RunWith(JUnit4.class)
+public class SimpleServiceTest {
+  @Test
+  public void serviceDescriptor() {
+    assertEquals("grpc.testing.SimpleService", SimpleServiceGrpc.getServiceDescriptor().getName());
+  }
+
+  @Test
+  public void serviceMethodDescriotrs() {
+    MethodDescriptor<SimpleRequest, SimpleResponse> genericTypeShouldMatchWhenAssigned;
+
+    genericTypeShouldMatchWhenAssigned = SimpleServiceGrpc.getUnaryRpcMethod();
+    assertEquals(UNARY, genericTypeShouldMatchWhenAssigned.getType());
+
+    genericTypeShouldMatchWhenAssigned = SimpleServiceGrpc.getClientStreamingRpcMethod();
+    assertEquals(CLIENT_STREAMING, genericTypeShouldMatchWhenAssigned.getType());
+
+    genericTypeShouldMatchWhenAssigned = SimpleServiceGrpc.getServerStreamingRpcMethod();
+    assertEquals(SERVER_STREAMING, genericTypeShouldMatchWhenAssigned.getType());
+
+    genericTypeShouldMatchWhenAssigned = SimpleServiceGrpc.getBidiStreamingRpcMethod();
+    assertEquals(BIDI_STREAMING, genericTypeShouldMatchWhenAssigned.getType());
+  }
+
+  @Test
+  public void generatedMethodsAreSampledToLocalTracing() throws Exception {
+    assertTrue(SimpleServiceGrpc.getUnaryRpcMethod().isSampledToLocalTracing());
+  }
+
+  public static class AnnotationProcessor extends AbstractProcessor {
+
+    private boolean processedClass = false;
+
+    @Override
+    public Set<String> getSupportedAnnotationTypes() {
+      return Collections.singleton(RpcMethod.class.getCanonicalName());
+    }
+
+    private void verifyRpcMethodAnnotation(
+        MethodDescriptor<SimpleRequest, SimpleResponse> descriptor, RpcMethod annotation) {
+      assertEquals(descriptor.getFullMethodName(), annotation.fullMethodName());
+      assertEquals(descriptor.getType(), annotation.methodType());
+
+      // Class objects may not be available at runtime, handle MirroredTypeException if it occurs
+      try {
+        assertEquals(SimpleRequest.class, annotation.requestType());
+      } catch (MirroredTypeException e) {
+        assertEquals(SimpleRequest.class.getCanonicalName(), e.getTypeMirror().toString());
+      }
+
+      try {
+        assertEquals(SimpleResponse.class, annotation.responseType());
+      } catch (MirroredTypeException e) {
+        assertEquals(SimpleResponse.class.getCanonicalName(), e.getTypeMirror().toString());
+      }
+    }
+
+    @Override
+    public synchronized boolean process(
+        Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+      for (Element rootElement : roundEnv.getRootElements()) {
+        if (!rootElement.asType().toString().equals(SimpleServiceGrpc.class.getCanonicalName())) {
+          continue;
+        }
+
+        Map<String, RpcMethod> methodToAnnotation = new HashMap<String, RpcMethod>();
+        for (Element enclosedElement : rootElement.getEnclosedElements()) {
+          RpcMethod annotation = enclosedElement.getAnnotation(RpcMethod.class);
+          if (annotation != null) {
+            methodToAnnotation.put(enclosedElement.getSimpleName().toString(), annotation);
+          }
+        }
+
+        verifyRpcMethodAnnotation(
+            SimpleServiceGrpc.getUnaryRpcMethod(), methodToAnnotation.get("getUnaryRpcMethod"));
+        verifyRpcMethodAnnotation(
+            SimpleServiceGrpc.getServerStreamingRpcMethod(),
+            methodToAnnotation.get("getServerStreamingRpcMethod"));
+        verifyRpcMethodAnnotation(
+            SimpleServiceGrpc.getClientStreamingRpcMethod(),
+            methodToAnnotation.get("getClientStreamingRpcMethod"));
+        verifyRpcMethodAnnotation(
+            SimpleServiceGrpc.getBidiStreamingRpcMethod(),
+            methodToAnnotation.get("getBidiStreamingRpcMethod"));
+
+        processedClass = true;
+      }
+      return false;
+    }
+  }
+
+  @Test
+  public void testRpcMethodAnnotations() throws Exception {
+    File grpcJavaFile =
+        new File("./src/generated/main/grpc/io/grpc/testing/protobuf/SimpleServiceGrpc.java");
+    Assume.assumeTrue(grpcJavaFile.exists());
+
+    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+    StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
+    AnnotationProcessor processor = new AnnotationProcessor();
+    Iterable<? extends JavaFileObject> obs = fileManager.getJavaFileObjects(grpcJavaFile);
+
+    CompilationTask task =
+        compiler.getTask(
+            null,
+            fileManager,
+            null,
+            Collections.singleton("-proc:only"),
+            Collections.singleton(SimpleServiceGrpc.class.getCanonicalName()),
+            obs);
+    task.setProcessors(Collections.singleton(processor));
+    assertTrue(task.call());
+    assertTrue(processor.processedClass);
+    fileManager.close();
+  }
+}
diff --git a/testing/BUILD.bazel b/testing/BUILD.bazel
new file mode 100644
index 0000000..eebb37c
--- /dev/null
+++ b/testing/BUILD.bazel
@@ -0,0 +1,35 @@
+java_library(
+    name = "testing",
+    srcs = glob([
+        "src/main/java/io/grpc/testing/*.java",
+    ]),
+    resources = glob([
+        "src/main/resources/**",
+    ]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//context",
+        "//core",
+        "//core:inprocess",
+        "//core:util",
+        "//stub",
+
+        "@com_google_code_findbugs_jsr305//jar",
+        "@com_google_guava_guava//jar",
+        "@com_google_truth_truth//jar",
+        "@junit_junit//jar",
+    ],
+    testonly = 1,
+)
+
+java_library(
+    name = "internal",
+    srcs = glob([
+        "src/main/java/io/grpc/internal/*.java",
+    ]),
+    visibility = ["//:__subpackages__"],
+    deps = [
+        "//core",
+        "//core:internal",
+    ],
+)
diff --git a/testing/build.gradle b/testing/build.gradle
new file mode 100644
index 0000000..4a4805e
--- /dev/null
+++ b/testing/build.gradle
@@ -0,0 +1,23 @@
+description = "gRPC: Testing"
+
+dependencies {
+    compile project(':grpc-core'),
+            project(':grpc-stub'),
+            libraries.junit
+    compile (libraries.mockito) {
+        // prefer 1.3 from JUnit instead of 1.1
+        exclude group: 'org.hamcrest', module: 'hamcrest-core'
+    }
+    // Use compileOnly to avoid dependencyConvergence problem with the Guava
+    // pulled in via Truth, for users that don't use Truth. Truth requires a
+    // more up-to-date Guava than we support elsewhere, which would trigger
+    // convergence failures in tests that only our users could resolve. Using
+    // compileOnly means only users using Truth would have the problem and
+    // they'd have to resolve it like normal anyway.
+    compileOnly libraries.truth
+
+    testCompile project(':grpc-testing-proto'),
+            project(':grpc-core').sourceSets.test.output
+}
+
+javadoc { exclude 'io/grpc/internal/**' }
diff --git a/testing/src/main/java/io/grpc/internal/NoopClientCall.java b/testing/src/main/java/io/grpc/internal/NoopClientCall.java
new file mode 100644
index 0000000..a81fdae
--- /dev/null
+++ b/testing/src/main/java/io/grpc/internal/NoopClientCall.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.ClientCall;
+import io.grpc.Metadata;
+
+/**
+ * {@link NoopClientCall} is a class that is designed for use in tests.  It is designed to be used
+ * in places where a scriptable call is necessary.  By default, all methods are noops, and designed
+ * to be overridden.
+ */
+public class NoopClientCall<ReqT, RespT> extends ClientCall<ReqT, RespT> {
+
+  /**
+   * {@link NoopClientCall.NoopClientCallListener} is a class that is designed for use in tests.
+   * It is designed to be used in places where a scriptable call listener is necessary.  By
+   * default, all methods are noops, and designed to be overridden.
+   */
+  public static class NoopClientCallListener<T> extends ClientCall.Listener<T> {
+  }
+
+  @Override
+  public void start(ClientCall.Listener<RespT> listener, Metadata headers) {}
+
+  @Override
+  public void request(int numMessages) {}
+
+  @Override
+  public void cancel(String message, Throwable cause) {}
+
+  @Override
+  public void halfClose() {}
+
+  @Override
+  public void sendMessage(ReqT message) {}
+}
diff --git a/testing/src/main/java/io/grpc/internal/NoopServerCall.java b/testing/src/main/java/io/grpc/internal/NoopServerCall.java
new file mode 100644
index 0000000..4503819
--- /dev/null
+++ b/testing/src/main/java/io/grpc/internal/NoopServerCall.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerCall;
+import io.grpc.Status;
+
+/**
+ * {@link NoopServerCall} is a class that is designed for use in tests.  It is designed to be used
+ * in places where a scriptable call is necessary.  By default, all methods are noops, and designed
+ * to be overridden.
+ */
+public class NoopServerCall<ReqT, RespT> extends ServerCall<ReqT, RespT> {
+
+  /**
+   * {@link NoopServerCall.NoopServerCallListener} is a class that is designed for use in tests.
+   * It is designed to be used in places where a scriptable call listener is necessary.  By
+   * default, all methods are noops, and designed to be overridden.
+   */
+  public static class NoopServerCallListener<T> extends ServerCall.Listener<T> {
+  }
+
+  @Override
+  public void request(int numMessages) {}
+
+  @Override
+  public void sendHeaders(Metadata headers) {}
+
+  @Override
+  public void sendMessage(RespT message) {}
+
+  @Override
+  public void close(Status status, Metadata trailers) {}
+
+  @Override
+  public boolean isCancelled() {
+    return false;
+  }
+
+  @Override
+  public MethodDescriptor<ReqT, RespT> getMethodDescriptor() {
+    return null;
+  }
+}
diff --git a/testing/src/main/java/io/grpc/internal/TestingAccessor.java b/testing/src/main/java/io/grpc/internal/TestingAccessor.java
new file mode 100644
index 0000000..4c99429
--- /dev/null
+++ b/testing/src/main/java/io/grpc/internal/TestingAccessor.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal;
+
+/**
+ * Test helper that allows accessing package-private stuff.
+ */
+public final class TestingAccessor {
+  /**
+   * Sets a custom stats implementation for tests.
+   */
+  public static void setStatsImplementation(
+      AbstractManagedChannelImplBuilder<?> builder, CensusStatsModule censusStats) {
+    builder.overrideCensusStatsModule(censusStats);
+  }
+
+  /**
+   * Sets a custom stats implementation for tests.
+   */
+  public static void setStatsImplementation(
+      AbstractServerImplBuilder<?> builder, CensusStatsModule censusStats) {
+    builder.overrideCensusStatsModule(censusStats);
+  }
+
+  private TestingAccessor() {
+  }
+}
diff --git a/testing/src/main/java/io/grpc/internal/testing/AbstractClientTransportFactoryTest.java b/testing/src/main/java/io/grpc/internal/testing/AbstractClientTransportFactoryTest.java
new file mode 100644
index 0000000..b257824
--- /dev/null
+++ b/testing/src/main/java/io/grpc/internal/testing/AbstractClientTransportFactoryTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal.testing;
+
+import io.grpc.internal.ClientTransportFactory;
+import java.net.InetSocketAddress;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public abstract class AbstractClientTransportFactoryTest {
+
+  protected abstract ClientTransportFactory newClientTransportFactory();
+
+  @Test
+  public void multipleCallsToCloseShouldNotThrow() {
+    ClientTransportFactory transportFactory = newClientTransportFactory();
+    transportFactory.close();
+    transportFactory.close();
+    transportFactory.close();
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void newClientTransportAfterCloseShouldThrow() {
+    ClientTransportFactory transportFactory = newClientTransportFactory();
+    transportFactory.close();
+    transportFactory.newClientTransport(
+        new InetSocketAddress("localhost", 12345),
+        new ClientTransportFactory.ClientTransportOptions());
+  }
+}
diff --git a/testing/src/main/java/io/grpc/internal/testing/AbstractTransportTest.java b/testing/src/main/java/io/grpc/internal/testing/AbstractTransportTest.java
new file mode 100644
index 0000000..2acbba9
--- /dev/null
+++ b/testing/src/main/java/io/grpc/internal/testing/AbstractTransportTest.java
@@ -0,0 +1,2069 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal.testing;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.Attributes;
+import io.grpc.CallCredentials;
+import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
+import io.grpc.Grpc;
+import io.grpc.InternalChannelz.SocketStats;
+import io.grpc.InternalChannelz.TransportStats;
+import io.grpc.InternalInstrumented;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerStreamTracer;
+import io.grpc.Status;
+import io.grpc.internal.ClientStream;
+import io.grpc.internal.ClientStreamListener;
+import io.grpc.internal.ClientTransport;
+import io.grpc.internal.ConnectionClientTransport;
+import io.grpc.internal.InternalServer;
+import io.grpc.internal.IoUtils;
+import io.grpc.internal.ManagedClientTransport;
+import io.grpc.internal.ServerListener;
+import io.grpc.internal.ServerStream;
+import io.grpc.internal.ServerStreamListener;
+import io.grpc.internal.ServerTransport;
+import io.grpc.internal.ServerTransportListener;
+import io.grpc.internal.TimeProvider;
+import io.grpc.internal.TransportTracer;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.SocketAddress;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Matchers;
+
+/** Standard unit tests for {@link ClientTransport}s and {@link ServerTransport}s. */
+@RunWith(JUnit4.class)
+public abstract class AbstractTransportTest {
+  private static final int TIMEOUT_MS = 1000;
+
+  private static final Attributes.Key<String> ADDITIONAL_TRANSPORT_ATTR_KEY =
+      Attributes.Key.create("additional-attr");
+
+  protected final TransportTracer.Factory fakeClockTransportTracer = new TransportTracer.Factory(
+      new TimeProvider() {
+        @Override
+        public long currentTimeNanos() {
+          return fakeCurrentTimeNanos();
+        }
+      });
+
+  /**
+   * Returns a new server that when started will be able to be connected to from the client. Each
+   * returned instance should be new and yet be accessible by new client transports.
+   */
+  protected abstract InternalServer newServer(
+      List<ServerStreamTracer.Factory> streamTracerFactories);
+
+  /**
+   * Builds a new server that is listening on the same location as the given server instance does.
+   */
+  protected abstract InternalServer newServer(
+      InternalServer server, List<ServerStreamTracer.Factory> streamTracerFactories);
+
+  /**
+   * Returns a new transport that when started will be able to connect to {@code server}.
+   */
+  protected abstract ManagedClientTransport newClientTransport(InternalServer server);
+
+  /**
+   * Returns the authority string used by a client to connect to {@code server}.
+   */
+  protected abstract String testAuthority(InternalServer server);
+
+  /**
+   * Returns true (which is default) if the transport reports message sizes to StreamTracers.
+   */
+  protected boolean sizesReported() {
+    return true;
+  }
+
+  /**
+   * When non-null, will be shut down during tearDown(). However, it _must_ have been started with
+   * {@code serverListener}, otherwise tearDown() can't wait for shutdown which can put following
+   * tests in an indeterminate state.
+   */
+  private InternalServer server;
+  private ServerTransport serverTransport;
+  private ManagedClientTransport client;
+  private MethodDescriptor<String, String> methodDescriptor =
+      MethodDescriptor.<String, String>newBuilder()
+          .setType(MethodDescriptor.MethodType.UNKNOWN)
+          .setFullMethodName("service/method")
+          .setRequestMarshaller(StringMarshaller.INSTANCE)
+          .setResponseMarshaller(StringMarshaller.INSTANCE)
+          .build();
+  private CallOptions callOptions;
+
+  private Metadata.Key<String> asciiKey = Metadata.Key.of(
+      "ascii-key", Metadata.ASCII_STRING_MARSHALLER);
+  private Metadata.Key<String> binaryKey = Metadata.Key.of(
+      "key-bin", StringBinaryMarshaller.INSTANCE);
+
+  private ManagedClientTransport.Listener mockClientTransportListener
+      = mock(ManagedClientTransport.Listener.class);
+  private MockServerListener serverListener = new MockServerListener();
+  private ArgumentCaptor<Throwable> throwableCaptor = ArgumentCaptor.forClass(Throwable.class);
+  private final ClientStreamTracer.Factory clientStreamTracerFactory =
+      mock(ClientStreamTracer.Factory.class);
+
+  private final TestClientStreamTracer clientStreamTracer1 = new TestClientStreamTracer();
+  private final TestClientStreamTracer clientStreamTracer2 = new TestClientStreamTracer();
+  private final ServerStreamTracer.Factory serverStreamTracerFactory =
+      mock(ServerStreamTracer.Factory.class);
+  private final TestServerStreamTracer serverStreamTracer1 = new TestServerStreamTracer();
+  private final TestServerStreamTracer serverStreamTracer2 = new TestServerStreamTracer();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Before
+  public void setUp() {
+    server = newServer(Arrays.asList(serverStreamTracerFactory));
+    when(clientStreamTracerFactory
+        .newClientStreamTracer(any(CallOptions.class), any(Metadata.class)))
+        .thenReturn(clientStreamTracer1)
+        .thenReturn(clientStreamTracer2);
+    when(serverStreamTracerFactory.newServerStreamTracer(anyString(), any(Metadata.class)))
+        .thenReturn(serverStreamTracer1)
+        .thenReturn(serverStreamTracer2);
+    callOptions = CallOptions.DEFAULT.withStreamTracerFactory(clientStreamTracerFactory);
+  }
+
+  @After
+  public void tearDown() throws InterruptedException {
+    if (client != null) {
+      client.shutdownNow(Status.UNKNOWN.withDescription("teardown"));
+    }
+    if (serverTransport != null) {
+      serverTransport.shutdownNow(Status.UNKNOWN.withDescription("teardown"));
+    }
+    if (server != null) {
+      server.shutdown();
+      assertTrue(serverListener.waitForShutdown(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+  }
+
+  /**
+   * Moves the clock forward, for tests that require moving the clock forward. It is the transport
+   * subclass's responsibility to implement this method.
+   */
+  protected void advanceClock(long offset, TimeUnit unit) {
+    throw new UnsupportedOperationException();
+  }
+
+  /**
+   * Returns the current time, for tests that rely on the clock.
+   */
+  protected long fakeCurrentTimeNanos() {
+    throw new UnsupportedOperationException();
+  }
+
+  // TODO(ejona):
+  //   multiple streams on same transport
+  //   multiple client transports to same server
+  //   halfClose to trigger flush (client and server)
+  //   flow control pushes back (client and server)
+  //   flow control provides precisely number of messages requested (client and server)
+  //   onReady called when buffer drained (on server and client)
+  //   test no start reentrancy (esp. during failure) (transport and call)
+  //   multiple requests/responses (verifying contents received)
+  //   server transport shutdown triggers client shutdown (via GOAWAY)
+  //   queued message InputStreams are closed on stream cancel
+  //     (and maybe exceptions handled)
+
+  /**
+   * Test for issue https://github.com/grpc/grpc-java/issues/1682
+   */
+  @Test
+  public void frameAfterRstStreamShouldNotBreakClientChannel() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    // Try to create a sequence of frames so that the client receives a HEADERS or DATA frame
+    // after having sent a RST_STREAM to the server. Previously, this would have broken the
+    // Netty channel.
+
+    ClientStream stream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    stream.start(clientStreamListener);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    stream.flush();
+    stream.writeMessage(methodDescriptor.streamRequest("foo"));
+    stream.flush();
+    stream.cancel(Status.CANCELLED);
+    stream.flush();
+    serverStreamCreation.stream.writeHeaders(new Metadata());
+    serverStreamCreation.stream.flush();
+    serverStreamCreation.stream.writeMessage(methodDescriptor.streamResponse("bar"));
+    serverStreamCreation.stream.flush();
+
+    assertEquals(
+        Status.CANCELLED, clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+    ClientStreamListener mockClientStreamListener2 = mock(ClientStreamListener.class);
+
+    // Test that the channel is still usable i.e. we can receive headers from the server on a
+    // new stream.
+    stream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    stream.start(mockClientStreamListener2);
+    serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverStreamCreation.stream.writeHeaders(new Metadata());
+    serverStreamCreation.stream.flush();
+
+    verify(mockClientStreamListener2, timeout(250)).headersRead(any(Metadata.class));
+  }
+
+  @Test
+  public void serverNotListening() throws Exception {
+    // Start server to just acquire a port.
+    server.start(serverListener);
+    client = newClientTransport(server);
+    server.shutdown();
+    assertTrue(serverListener.waitForShutdown(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    server = null;
+
+    InOrder inOrder = inOrder(mockClientTransportListener);
+    runIfNotNull(client.start(mockClientTransportListener));
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportTerminated();
+    ArgumentCaptor<Status> statusCaptor = ArgumentCaptor.forClass(Status.class);
+    inOrder.verify(mockClientTransportListener).transportShutdown(statusCaptor.capture());
+    assertCodeEquals(Status.UNAVAILABLE, statusCaptor.getValue());
+    inOrder.verify(mockClientTransportListener).transportTerminated();
+    verify(mockClientTransportListener, never()).transportReady();
+    verify(mockClientTransportListener, never()).transportInUse(anyBoolean());
+  }
+
+  @Test
+  public void clientStartStop() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    InOrder inOrder = inOrder(mockClientTransportListener);
+    startTransport(client, mockClientTransportListener);
+    Status shutdownReason = Status.UNAVAILABLE.withDescription("shutdown called");
+    client.shutdown(shutdownReason);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportTerminated();
+    inOrder.verify(mockClientTransportListener).transportShutdown(same(shutdownReason));
+    inOrder.verify(mockClientTransportListener).transportTerminated();
+    verify(mockClientTransportListener, never()).transportInUse(anyBoolean());
+  }
+
+  @Test
+  public void clientStartAndStopOnceConnected() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    InOrder inOrder = inOrder(mockClientTransportListener);
+    startTransport(client, mockClientTransportListener);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportReady();
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    client.shutdown(Status.UNAVAILABLE);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportTerminated();
+    inOrder.verify(mockClientTransportListener).transportShutdown(any(Status.class));
+    inOrder.verify(mockClientTransportListener).transportTerminated();
+    assertTrue(serverTransportListener.waitForTermination(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    server.shutdown();
+    assertTrue(serverListener.waitForShutdown(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    server = null;
+    verify(mockClientTransportListener, never()).transportInUse(anyBoolean());
+  }
+
+  @Test
+  public void checkClientAttributes() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    assumeTrue(client instanceof ConnectionClientTransport);
+    ConnectionClientTransport connectionClient = (ConnectionClientTransport) client;
+    startTransport(connectionClient, mockClientTransportListener);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportReady();
+
+    assertNotNull("security level should be set in client attributes",
+        connectionClient.getAttributes().get(CallCredentials.ATTR_SECURITY_LEVEL));
+  }
+
+  @Test
+  public void serverAlreadyListening() throws Exception {
+    client = null;
+    server.start(serverListener);
+    InternalServer server2 = newServer(server, Arrays.asList(serverStreamTracerFactory));
+    thrown.expect(IOException.class);
+    server2.start(new MockServerListener());
+  }
+
+  @Test
+  public void openStreamPreventsTermination() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(true);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+
+    client.shutdown(Status.UNAVAILABLE);
+    client = null;
+    server.shutdown();
+    serverTransport.shutdown();
+    serverTransport = null;
+
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportShutdown(any(Status.class));
+    assertTrue(serverListener.waitForShutdown(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+    // A new server should be able to start listening, since the current server has given up
+    // resources. There may be cases this is impossible in the future, but for now it is a useful
+    // property.
+    serverListener = new MockServerListener();
+    server = newServer(server, Arrays.asList(serverStreamTracerFactory));
+    server.start(serverListener);
+
+    // Try to "flush" out any listener notifications on client and server. This also ensures that
+    // the stream still functions.
+    serverStream.writeHeaders(new Metadata());
+    clientStream.halfClose();
+    assertNotNull(clientStreamListener.headers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertTrue(serverStreamListener.awaitHalfClosed(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+    verify(mockClientTransportListener, never()).transportTerminated();
+    verify(mockClientTransportListener, never()).transportInUse(false);
+    assertFalse(serverTransportListener.isTerminated());
+
+    clientStream.cancel(Status.CANCELLED);
+
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportTerminated();
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(false);
+    assertTrue(serverTransportListener.waitForTermination(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+  }
+
+  @Test
+  public void shutdownNowKillsClientStream() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(true);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+
+    Status status = Status.UNKNOWN.withDescription("test shutdownNow");
+    client.shutdownNow(status);
+    client = null;
+
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportShutdown(any(Status.class));
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportTerminated();
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(false);
+    assertTrue(serverTransportListener.waitForTermination(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertTrue(serverTransportListener.isTerminated());
+
+    assertEquals(status, clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    Status serverStatus = serverStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertFalse(serverStatus.isOk());
+    assertTrue(clientStreamTracer1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertStatusEquals(status, clientStreamTracer1.getStatus());
+    assertTrue(serverStreamTracer1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertStatusEquals(serverStatus, serverStreamTracer1.getStatus());
+  }
+
+  @Test
+  public void shutdownNowKillsServerStream() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(true);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+
+    Status shutdownStatus = Status.UNKNOWN.withDescription("test shutdownNow");
+    serverTransport.shutdownNow(shutdownStatus);
+    serverTransport = null;
+
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportShutdown(any(Status.class));
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportTerminated();
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(false);
+    assertTrue(serverTransportListener.waitForTermination(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertTrue(serverTransportListener.isTerminated());
+
+    Status clientStreamStatus = clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertFalse(clientStreamStatus.isOk());
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertTrue(clientStreamTracer1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertStatusEquals(clientStreamStatus, clientStreamTracer1.getStatus());
+    assertTrue(serverStreamTracer1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertStatusEquals(shutdownStatus, serverStreamTracer1.getStatus());
+
+    // Generally will be same status provided to shutdownNow, but InProcessTransport can't
+    // differentiate between client and server shutdownNow. The status is not really used on
+    // server-side, so we don't care much.
+    assertNotNull(serverStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+  }
+
+  @Test
+  public void ping() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    ClientTransport.PingCallback mockPingCallback = mock(ClientTransport.PingCallback.class);
+    try {
+      client.ping(mockPingCallback, MoreExecutors.directExecutor());
+    } catch (UnsupportedOperationException ex) {
+      // Transport doesn't support ping, so this neither passes nor fails.
+      assumeTrue(false);
+    }
+    verify(mockPingCallback, timeout(TIMEOUT_MS)).onSuccess(Matchers.anyLong());
+    verify(mockClientTransportListener, never()).transportInUse(anyBoolean());
+  }
+
+  @Test
+  public void ping_duringShutdown() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    // Stream prevents termination
+    ClientStream stream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    stream.start(clientStreamListener);
+    client.shutdown(Status.UNAVAILABLE);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportShutdown(any(Status.class));
+    ClientTransport.PingCallback mockPingCallback = mock(ClientTransport.PingCallback.class);
+    try {
+      client.ping(mockPingCallback, MoreExecutors.directExecutor());
+    } catch (UnsupportedOperationException ex) {
+      // Transport doesn't support ping, so this neither passes nor fails.
+      assumeTrue(false);
+    }
+    verify(mockPingCallback, timeout(TIMEOUT_MS)).onSuccess(Matchers.anyLong());
+    stream.cancel(Status.CANCELLED);
+  }
+
+  @Test
+  public void ping_afterTermination() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportReady();
+    Status shutdownReason = Status.UNAVAILABLE.withDescription("shutdown called");
+    client.shutdown(shutdownReason);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportTerminated();
+    ClientTransport.PingCallback mockPingCallback = mock(ClientTransport.PingCallback.class);
+    try {
+      client.ping(mockPingCallback, MoreExecutors.directExecutor());
+    } catch (UnsupportedOperationException ex) {
+      // Transport doesn't support ping, so this neither passes nor fails.
+      assumeTrue(false);
+    }
+    verify(mockPingCallback, timeout(TIMEOUT_MS)).onFailure(throwableCaptor.capture());
+    Status status = Status.fromThrowable(throwableCaptor.getValue());
+    assertSame(shutdownReason, status);
+  }
+
+  @Test
+  public void newStream_duringShutdown() throws Exception {
+    InOrder inOrder = inOrder(clientStreamTracerFactory);
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    // Stream prevents termination
+    ClientStream stream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    inOrder.verify(clientStreamTracerFactory).newClientStreamTracer(
+        any(CallOptions.class), any(Metadata.class));
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    stream.start(clientStreamListener);
+    client.shutdown(Status.UNAVAILABLE);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportShutdown(any(Status.class));
+
+    ClientStream stream2 = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    inOrder.verify(clientStreamTracerFactory).newClientStreamTracer(
+        any(CallOptions.class), any(Metadata.class));
+    ClientStreamListenerBase clientStreamListener2 = new ClientStreamListenerBase();
+    stream2.start(clientStreamListener2);
+    Status clientStreamStatus2 =
+        clientStreamListener2.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertNotNull(clientStreamListener2.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertCodeEquals(Status.UNAVAILABLE, clientStreamStatus2);
+    assertSame(clientStreamStatus2, clientStreamTracer2.getStatus());
+
+    // Make sure earlier stream works.
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+    // TODO(zdapeng): Increased timeout to 20 seconds to see if flakiness of #2328 persists. Take
+    // further action after sufficient observation.
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(20 * TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverStreamCreation.stream.close(Status.OK, new Metadata());
+    assertCodeEquals(Status.OK, clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+  }
+
+  @Test
+  public void newStream_afterTermination() throws Exception {
+    // We expect the same general behavior as duringShutdown, but for some transports (e.g., Netty)
+    // dealing with afterTermination is harder than duringShutdown.
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportReady();
+    Status shutdownReason = Status.UNAVAILABLE.withDescription("shutdown called");
+    client.shutdown(shutdownReason);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportTerminated();
+    Thread.sleep(100);
+    ClientStream stream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    stream.start(clientStreamListener);
+    assertEquals(
+        shutdownReason, clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    verify(mockClientTransportListener, never()).transportInUse(anyBoolean());
+    verify(clientStreamTracerFactory).newClientStreamTracer(
+        any(CallOptions.class), any(Metadata.class));
+    assertSame(shutdownReason, clientStreamTracer1.getStatus());
+    // Assert no interactions
+    assertNull(serverStreamTracer1.getServerCallInfo());
+  }
+
+  @Test
+  public void transportInUse_normalClose() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    ClientStream stream1 = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener1 = new ClientStreamListenerBase();
+    stream1.start(clientStreamListener1);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(true);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    StreamCreation serverStreamCreation1
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ClientStream stream2 = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener2 = new ClientStreamListenerBase();
+    stream2.start(clientStreamListener2);
+    StreamCreation serverStreamCreation2
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+    stream1.halfClose();
+    serverStreamCreation1.stream.close(Status.OK, new Metadata());
+    stream2.halfClose();
+    verify(mockClientTransportListener, never()).transportInUse(false);
+    serverStreamCreation2.stream.close(Status.OK, new Metadata());
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(false);
+    // Verify that the callback has been called only once for true and false respectively
+    verify(mockClientTransportListener).transportInUse(true);
+    verify(mockClientTransportListener).transportInUse(false);
+  }
+
+  @Test
+  public void transportInUse_clientCancel() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    ClientStream stream1 = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener1 = new ClientStreamListenerBase();
+    stream1.start(clientStreamListener1);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(true);
+    ClientStream stream2 = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener2 = new ClientStreamListenerBase();
+    stream2.start(clientStreamListener2);
+
+    stream1.cancel(Status.CANCELLED);
+    verify(mockClientTransportListener, never()).transportInUse(false);
+    stream2.cancel(Status.CANCELLED);
+    verify(mockClientTransportListener, timeout(TIMEOUT_MS)).transportInUse(false);
+    // Verify that the callback has been called only once for true and false respectively
+    verify(mockClientTransportListener).transportInUse(true);
+    verify(mockClientTransportListener).transportInUse(false);
+  }
+
+  @Test
+  @SuppressWarnings("deprecation")
+  public void basicStream() throws Exception {
+    InOrder clientInOrder = inOrder(clientStreamTracerFactory);
+    InOrder serverInOrder = inOrder(serverStreamTracerFactory);
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    Metadata clientHeaders = new Metadata();
+    clientHeaders.put(asciiKey, "client");
+    clientHeaders.put(asciiKey, "dupvalue");
+    clientHeaders.put(asciiKey, "dupvalue");
+    clientHeaders.put(binaryKey, "äbinaryclient");
+    Metadata clientHeadersCopy = new Metadata();
+
+    clientHeadersCopy.merge(clientHeaders);
+    ClientStream clientStream = client.newStream(methodDescriptor, clientHeaders, callOptions);
+    clientInOrder.verify(clientStreamTracerFactory).newClientStreamTracer(
+        same(callOptions), same(clientHeaders));
+
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertTrue(clientStreamTracer1.awaitOutboundHeaders(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertEquals(methodDescriptor.getFullMethodName(), serverStreamCreation.method);
+    assertEquals(Lists.newArrayList(clientHeadersCopy.getAll(asciiKey)),
+        Lists.newArrayList(serverStreamCreation.headers.getAll(asciiKey)));
+    assertEquals(Lists.newArrayList(clientHeadersCopy.getAll(binaryKey)),
+        Lists.newArrayList(serverStreamCreation.headers.getAll(binaryKey)));
+    ServerStream serverStream = serverStreamCreation.stream;
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+
+    serverInOrder.verify(serverStreamTracerFactory).newServerStreamTracer(
+        eq(methodDescriptor.getFullMethodName()), any(Metadata.class));
+
+    assertEquals("additional attribute value",
+        serverStream.getAttributes().get(ADDITIONAL_TRANSPORT_ATTR_KEY));
+    assertNotNull(serverStream.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR));
+
+    serverStream.request(1);
+    assertTrue(clientStreamListener.awaitOnReadyAndDrain(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertTrue(clientStream.isReady());
+    clientStream.writeMessage(methodDescriptor.streamRequest("Hello!"));
+    assertThat(clientStreamTracer1.nextOutboundEvent()).isEqualTo("outboundMessage(0)");
+
+    clientStream.flush();
+    InputStream message = serverStreamListener.messageQueue.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertEquals("Hello!", methodDescriptor.parseRequest(message));
+    message.close();
+    assertThat(clientStreamTracer1.nextOutboundEvent())
+        .matches("outboundMessageSent\\(0, -?[0-9]+, -?[0-9]+\\)");
+    if (sizesReported()) {
+      assertThat(clientStreamTracer1.getOutboundWireSize()).isGreaterThan(0L);
+      assertThat(clientStreamTracer1.getOutboundUncompressedSize()).isGreaterThan(0L);
+    } else {
+      assertThat(clientStreamTracer1.getOutboundWireSize()).isEqualTo(0L);
+      assertThat(clientStreamTracer1.getOutboundUncompressedSize()).isEqualTo(0L);
+    }
+    assertThat(serverStreamTracer1.nextInboundEvent()).isEqualTo("inboundMessage(0)");
+    assertNull("no additional message expected", serverStreamListener.messageQueue.poll());
+
+    clientStream.halfClose();
+    assertTrue(serverStreamListener.awaitHalfClosed(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+    if (sizesReported()) {
+      assertThat(serverStreamTracer1.getInboundWireSize()).isGreaterThan(0L);
+      assertThat(serverStreamTracer1.getInboundUncompressedSize()).isGreaterThan(0L);
+    } else {
+      assertThat(serverStreamTracer1.getInboundWireSize()).isEqualTo(0L);
+      assertThat(serverStreamTracer1.getInboundUncompressedSize()).isEqualTo(0L);
+    }
+    assertThat(serverStreamTracer1.nextInboundEvent())
+        .matches("inboundMessageRead\\(0, -?[0-9]+, -?[0-9]+\\)");
+
+    Metadata serverHeaders = new Metadata();
+    serverHeaders.put(asciiKey, "server");
+    serverHeaders.put(asciiKey, "dupvalue");
+    serverHeaders.put(asciiKey, "dupvalue");
+    serverHeaders.put(binaryKey, "äbinaryserver");
+    Metadata serverHeadersCopy = new Metadata();
+    serverHeadersCopy.merge(serverHeaders);
+    serverStream.writeHeaders(serverHeaders);
+    Metadata headers = clientStreamListener.headers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertNotNull(headers);
+    assertEquals(
+        Lists.newArrayList(serverHeadersCopy.getAll(asciiKey)),
+        Lists.newArrayList(headers.getAll(asciiKey)));
+    assertEquals(
+        Lists.newArrayList(serverHeadersCopy.getAll(binaryKey)),
+        Lists.newArrayList(headers.getAll(binaryKey)));
+
+    clientStream.request(1);
+    assertTrue(serverStreamListener.awaitOnReadyAndDrain(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertTrue(serverStream.isReady());
+    serverStream.writeMessage(methodDescriptor.streamResponse("Hi. Who are you?"));
+    assertThat(serverStreamTracer1.nextOutboundEvent()).isEqualTo("outboundMessage(0)");
+
+    serverStream.flush();
+    message = clientStreamListener.messageQueue.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertNotNull("message expected", message);
+    assertThat(serverStreamTracer1.nextOutboundEvent())
+        .matches("outboundMessageSent\\(0, -?[0-9]+, -?[0-9]+\\)");
+    if (sizesReported()) {
+      assertThat(serverStreamTracer1.getOutboundWireSize()).isGreaterThan(0L);
+      assertThat(serverStreamTracer1.getOutboundUncompressedSize()).isGreaterThan(0L);
+    } else {
+      assertThat(serverStreamTracer1.getOutboundWireSize()).isEqualTo(0L);
+      assertThat(serverStreamTracer1.getOutboundUncompressedSize()).isEqualTo(0L);
+    }
+    assertTrue(clientStreamTracer1.getInboundHeaders());
+    assertThat(clientStreamTracer1.nextInboundEvent()).isEqualTo("inboundMessage(0)");
+    assertEquals("Hi. Who are you?", methodDescriptor.parseResponse(message));
+    assertThat(clientStreamTracer1.nextInboundEvent())
+        .matches("inboundMessageRead\\(0, -?[0-9]+, -?[0-9]+\\)");
+    if (sizesReported()) {
+      assertThat(clientStreamTracer1.getInboundWireSize()).isGreaterThan(0L);
+      assertThat(clientStreamTracer1.getInboundUncompressedSize()).isGreaterThan(0L);
+    } else {
+      assertThat(clientStreamTracer1.getInboundWireSize()).isEqualTo(0L);
+      assertThat(clientStreamTracer1.getInboundUncompressedSize()).isEqualTo(0L);
+    }
+
+    message.close();
+    assertNull("no additional message expected", clientStreamListener.messageQueue.poll());
+
+    Status status = Status.OK.withDescription("That was normal");
+    Metadata trailers = new Metadata();
+    trailers.put(asciiKey, "trailers");
+    trailers.put(asciiKey, "dupvalue");
+    trailers.put(asciiKey, "dupvalue");
+    trailers.put(binaryKey, "äbinarytrailers");
+    serverStream.close(status, trailers);
+    assertNull(serverStreamTracer1.nextInboundEvent());
+    assertNull(serverStreamTracer1.nextOutboundEvent());
+    assertCodeEquals(Status.OK, serverStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertSame(status, serverStreamTracer1.getStatus());
+    Status clientStreamStatus = clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    Metadata clientStreamTrailers =
+        clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertSame(clientStreamStatus, clientStreamTracer1.getStatus());
+    assertNull(clientStreamTracer1.nextInboundEvent());
+    assertNull(clientStreamTracer1.nextOutboundEvent());
+    assertEquals(status.getCode(), clientStreamStatus.getCode());
+    assertEquals(status.getDescription(), clientStreamStatus.getDescription());
+    assertEquals(
+        Lists.newArrayList(trailers.getAll(asciiKey)),
+        Lists.newArrayList(clientStreamTrailers.getAll(asciiKey)));
+    assertEquals(
+        Lists.newArrayList(trailers.getAll(binaryKey)),
+        Lists.newArrayList(clientStreamTrailers.getAll(binaryKey)));
+  }
+
+  @Test
+  @SuppressWarnings("deprecation")
+  public void authorityPropagation() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+            = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+    Metadata clientHeaders = new Metadata();
+    ClientStream clientStream = client.newStream(methodDescriptor, clientHeaders, callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    StreamCreation serverStreamCreation
+            = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+
+    assertEquals(testAuthority(server), serverStream.getAuthority());
+  }
+
+  @Test
+  public void zeroMessageStream() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+
+    clientStream.halfClose();
+    assertTrue(serverStreamListener.awaitHalfClosed(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+    serverStream.writeHeaders(new Metadata());
+    assertNotNull(clientStreamListener.headers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+    Status status = Status.OK.withDescription("Nice talking to you");
+    serverStream.close(status, new Metadata());
+    assertCodeEquals(Status.OK, serverStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    Status clientStreamStatus = clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertEquals(status.getCode(), clientStreamStatus.getCode());
+    assertEquals(status.getDescription(), clientStreamStatus.getDescription());
+    assertTrue(clientStreamTracer1.getOutboundHeaders());
+    assertTrue(clientStreamTracer1.getInboundHeaders());
+    assertSame(clientStreamStatus, clientStreamTracer1.getStatus());
+    assertSame(status, serverStreamTracer1.getStatus());
+  }
+
+  @Test
+  public void earlyServerClose_withServerHeaders() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+
+    serverStream.writeHeaders(new Metadata());
+    assertNotNull(clientStreamListener.headers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+    Status strippedStatus = Status.OK.withDescription("Hello. Goodbye.");
+    Status status = strippedStatus.withCause(new Exception());
+    serverStream.close(status, new Metadata());
+    assertCodeEquals(Status.OK, serverStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    Status clientStreamStatus = clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertEquals(status.getCode(), clientStreamStatus.getCode());
+    assertEquals("Hello. Goodbye.", clientStreamStatus.getDescription());
+    assertNull(clientStreamStatus.getCause());
+    assertTrue(clientStreamTracer1.getOutboundHeaders());
+    assertTrue(clientStreamTracer1.getInboundHeaders());
+    assertSame(clientStreamStatus, clientStreamTracer1.getStatus());
+    assertSame(status, serverStreamTracer1.getStatus());
+  }
+
+  @Test
+  public void earlyServerClose_noServerHeaders() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+
+    Status strippedStatus = Status.OK.withDescription("Hellogoodbye");
+    Status status = strippedStatus.withCause(new Exception());
+    Metadata trailers = new Metadata();
+    trailers.put(asciiKey, "trailers");
+    trailers.put(asciiKey, "dupvalue");
+    trailers.put(asciiKey, "dupvalue");
+    trailers.put(binaryKey, "äbinarytrailers");
+    serverStream.close(status, trailers);
+    assertCodeEquals(Status.OK, serverStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    Status clientStreamStatus = clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    Metadata clientStreamTrailers =
+        clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertEquals(status.getCode(), clientStreamStatus.getCode());
+    assertEquals("Hellogoodbye", clientStreamStatus.getDescription());
+    // Cause should not be transmitted to the client.
+    assertNull(clientStreamStatus.getCause());
+    assertEquals(
+        Lists.newArrayList(trailers.getAll(asciiKey)),
+        Lists.newArrayList(clientStreamTrailers.getAll(asciiKey)));
+    assertEquals(
+        Lists.newArrayList(trailers.getAll(binaryKey)),
+        Lists.newArrayList(clientStreamTrailers.getAll(binaryKey)));
+    assertTrue(clientStreamTracer1.getOutboundHeaders());
+    assertSame(clientStreamStatus, clientStreamTracer1.getStatus());
+    assertSame(status, serverStreamTracer1.getStatus());
+  }
+
+  @Test
+  public void earlyServerClose_serverFailure() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+
+    Status strippedStatus = Status.INTERNAL.withDescription("I'm not listening");
+    Status status = strippedStatus.withCause(new Exception());
+    serverStream.close(status, new Metadata());
+    assertCodeEquals(Status.OK, serverStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    Status clientStreamStatus = clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertEquals(status.getCode(), clientStreamStatus.getCode());
+    assertEquals(status.getDescription(), clientStreamStatus.getDescription());
+    assertNull(clientStreamStatus.getCause());
+    assertTrue(clientStreamTracer1.getOutboundHeaders());
+    assertSame(clientStreamStatus, clientStreamTracer1.getStatus());
+    assertSame(status, serverStreamTracer1.getStatus());
+  }
+
+  @Test
+  public void earlyServerClose_serverFailure_withClientCancelOnListenerClosed() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    runIfNotNull(client.start(mockClientTransportListener));
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    final ClientStream clientStream =
+        client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase() {
+      @Override
+      public void closed(Status status, Metadata trailers) {
+        super.closed(status, trailers);
+        // This simulates the blocking calls which can trigger clientStream.cancel().
+        clientStream.cancel(Status.CANCELLED.withCause(status.asRuntimeException()));
+      }
+
+      @Override
+      public void closed(
+          Status status, RpcProgress rpcProgress, Metadata trailers) {
+        super.closed(status, rpcProgress, trailers);
+        // This simulates the blocking calls which can trigger clientStream.cancel().
+        clientStream.cancel(Status.CANCELLED.withCause(status.asRuntimeException()));
+      }
+    };
+    clientStream.start(clientStreamListener);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+
+    Status strippedStatus = Status.INTERNAL.withDescription("I'm not listening");
+    Status status = strippedStatus.withCause(new Exception());
+    serverStream.close(status, new Metadata());
+    assertCodeEquals(Status.OK, serverStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    Status clientStreamStatus = clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertEquals(status.getCode(), clientStreamStatus.getCode());
+    assertEquals(status.getDescription(), clientStreamStatus.getDescription());
+    assertNull(clientStreamStatus.getCause());
+    assertTrue(clientStreamTracer1.getOutboundHeaders());
+    assertSame(clientStreamStatus, clientStreamTracer1.getStatus());
+    assertSame(status, serverStreamTracer1.getStatus());
+  }
+
+  @Test
+  public void clientCancel() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+
+    Status status = Status.CANCELLED.withDescription("Nevermind").withCause(new Exception());
+    clientStream.cancel(status);
+    assertEquals(status, clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    Status serverStatus = serverStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertNotEquals(Status.Code.OK, serverStatus.getCode());
+    // Cause should not be transmitted between client and server
+    assertNull(serverStatus.getCause());
+
+    clientStream.cancel(status);
+    assertTrue(clientStreamTracer1.getOutboundHeaders());
+    assertSame(status, clientStreamTracer1.getStatus());
+    assertSame(serverStatus, serverStreamTracer1.getStatus());
+  }
+
+  @Test
+  public void clientCancelFromWithinMessageRead() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    final SettableFuture<Boolean> closedCalled = SettableFuture.create();
+    final ClientStream clientStream =
+        client.newStream(methodDescriptor, new Metadata(), callOptions);
+    final Status status = Status.CANCELLED.withDescription("nevermind");
+    clientStream.start(new ClientStreamListener() {
+      private boolean messageReceived = false;
+
+      @Override
+      public void headersRead(Metadata headers) {
+      }
+
+      @Override
+      public void closed(Status status, Metadata trailers) {
+        closed(status, RpcProgress.PROCESSED, trailers);
+      }
+
+      @Override
+      public void closed(
+          Status status, RpcProgress rpcProgress, Metadata trailers) {
+        assertEquals(Status.CANCELLED.getCode(), status.getCode());
+        assertEquals("nevermind", status.getDescription());
+        closedCalled.set(true);
+      }
+
+      @Override
+      public void messagesAvailable(MessageProducer producer) {
+        InputStream message;
+        while ((message = producer.next()) != null) {
+          assertFalse("too many messages received", messageReceived);
+          messageReceived = true;
+          assertEquals("foo", methodDescriptor.parseResponse(message));
+          clientStream.cancel(status);
+        }
+      }
+
+      @Override
+      public void onReady() {
+      }
+    });
+    clientStream.halfClose();
+    clientStream.request(1);
+
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertEquals(methodDescriptor.getFullMethodName(), serverStreamCreation.method);
+    ServerStream serverStream = serverStreamCreation.stream;
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+    assertTrue(serverStreamListener.awaitOnReadyAndDrain(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+    assertTrue(serverStream.isReady());
+    serverStream.writeHeaders(new Metadata());
+    serverStream.writeMessage(methodDescriptor.streamRequest("foo"));
+    serverStream.flush();
+
+    // Block until closedCalled was set.
+    closedCalled.get(5, TimeUnit.SECONDS);
+
+    serverStream.close(Status.OK, new Metadata());
+    assertTrue(clientStreamTracer1.getOutboundHeaders());
+    assertTrue(clientStreamTracer1.getInboundHeaders());
+    if (sizesReported()) {
+      assertThat(clientStreamTracer1.getInboundWireSize()).isGreaterThan(0L);
+      assertThat(clientStreamTracer1.getInboundUncompressedSize()).isGreaterThan(0L);
+      assertThat(serverStreamTracer1.getOutboundWireSize()).isGreaterThan(0L);
+      assertThat(serverStreamTracer1.getOutboundUncompressedSize()).isGreaterThan(0L);
+    } else {
+      assertThat(clientStreamTracer1.getInboundWireSize()).isEqualTo(0L);
+      assertThat(clientStreamTracer1.getInboundUncompressedSize()).isEqualTo(0L);
+      assertThat(serverStreamTracer1.getOutboundWireSize()).isEqualTo(0L);
+      assertThat(serverStreamTracer1.getOutboundUncompressedSize()).isEqualTo(0L);
+    }
+    assertSame(status, clientStreamTracer1.getStatus());
+    // There is a race between client cancelling and server closing.  The final status seen by the
+    // server is non-deterministic.
+    assertTrue(serverStreamTracer1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertNotNull(serverStreamTracer1.getStatus());
+  }
+
+  @Test
+  public void serverCancel() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+
+    Status status = Status.DEADLINE_EXCEEDED.withDescription("It was bound to happen")
+        .withCause(new Exception());
+    serverStream.cancel(status);
+    assertEquals(status, serverStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    Status clientStreamStatus = clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    // Presently we can't sent much back to the client in this case. Verify that is the current
+    // behavior for consistency between transports.
+    assertCodeEquals(Status.CANCELLED, clientStreamStatus);
+    // Cause should not be transmitted between server and client
+    assertNull(clientStreamStatus.getCause());
+
+    verify(clientStreamTracerFactory).newClientStreamTracer(
+        any(CallOptions.class), any(Metadata.class));
+    assertTrue(clientStreamTracer1.getOutboundHeaders());
+    assertSame(clientStreamStatus, clientStreamTracer1.getStatus());
+    verify(serverStreamTracerFactory).newServerStreamTracer(anyString(), any(Metadata.class));
+    assertSame(status, serverStreamTracer1.getStatus());
+
+    // Second cancellation shouldn't trigger additional callbacks
+    serverStream.cancel(status);
+    doPingPong(serverListener);
+  }
+
+  @Test
+  public void flowControlPushBack() throws Exception {
+    // This test tries to create more streams than the number of distinctive stream tracers that the
+    // mock factory will return.  This causes the last stream tracer to be returned for more than
+    // one streams, resulting in duplicate callbacks.  Since we don't care the stream tracers in
+    // this test, we just disable the check.
+    clientStreamTracer2.setFailDuplicateCallbacks(false);
+    serverStreamTracer2.setFailDuplicateCallbacks(false);
+
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener =
+        serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    StreamCreation serverStreamCreation =
+        serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertEquals(methodDescriptor.getFullMethodName(), serverStreamCreation.method);
+    ServerStream serverStream = serverStreamCreation.stream;
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+
+    serverStream.writeHeaders(new Metadata());
+
+    String largeMessage;
+    {
+      int size = 1 * 1024;
+      StringBuilder sb = new StringBuilder(size);
+      for (int i = 0; i < size; i++) {
+        sb.append('a');
+      }
+      largeMessage = sb.toString();
+    }
+
+    serverStream.request(1);
+    assertTrue(clientStreamListener.awaitOnReadyAndDrain(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertTrue(clientStream.isReady());
+    final int maxToSend = 10 * 1024;
+    int clientSent;
+    // Verify that flow control will push back on client.
+    for (clientSent = 0; clientStream.isReady(); clientSent++) {
+      if (clientSent > maxToSend) {
+        // It seems like flow control isn't working. _Surely_ flow control would have pushed-back
+        // already. If this is normal, please configure the transport to buffer less.
+        fail("Too many messages sent before isReady() returned false");
+      }
+      clientStream.writeMessage(methodDescriptor.streamRequest(largeMessage));
+      clientStream.flush();
+    }
+    assertTrue(clientSent > 0);
+    // Make sure there are at least a few messages buffered.
+    for (; clientSent < 5; clientSent++) {
+      clientStream.writeMessage(methodDescriptor.streamResponse(largeMessage));
+      clientStream.flush();
+    }
+    doPingPong(serverListener);
+
+    int serverReceived = verifyMessageCountAndClose(serverStreamListener.messageQueue, 1);
+
+    clientStream.request(1);
+    assertTrue(serverStreamListener.awaitOnReadyAndDrain(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertTrue(serverStream.isReady());
+    int serverSent;
+    // Verify that flow control will push back on server.
+    for (serverSent = 0; serverStream.isReady(); serverSent++) {
+      if (serverSent > maxToSend) {
+        // It seems like flow control isn't working. _Surely_ flow control would have pushed-back
+        // already. If this is normal, please configure the transport to buffer less.
+        fail("Too many messages sent before isReady() returned false");
+      }
+      serverStream.writeMessage(methodDescriptor.streamResponse(largeMessage));
+      serverStream.flush();
+    }
+    assertTrue(serverSent > 0);
+    // Make sure there are at least a few messages buffered.
+    for (; serverSent < 5; serverSent++) {
+      serverStream.writeMessage(methodDescriptor.streamResponse(largeMessage));
+      serverStream.flush();
+    }
+    doPingPong(serverListener);
+
+    int clientReceived = verifyMessageCountAndClose(clientStreamListener.messageQueue, 1);
+
+    serverStream.request(3);
+    clientStream.request(3);
+    doPingPong(serverListener);
+    clientReceived += verifyMessageCountAndClose(clientStreamListener.messageQueue, 3);
+    serverReceived += verifyMessageCountAndClose(serverStreamListener.messageQueue, 3);
+
+    // Request the rest
+    serverStream.request(clientSent);
+    clientStream.request(serverSent);
+    clientReceived +=
+        verifyMessageCountAndClose(clientStreamListener.messageQueue, serverSent - clientReceived);
+    serverReceived +=
+        verifyMessageCountAndClose(serverStreamListener.messageQueue, clientSent - serverReceived);
+
+    assertTrue(clientStreamListener.awaitOnReadyAndDrain(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertTrue(clientStream.isReady());
+    assertTrue(serverStreamListener.awaitOnReadyAndDrain(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertTrue(serverStream.isReady());
+
+    // Request four more
+    for (int i = 0; i < 5; i++) {
+      clientStream.writeMessage(methodDescriptor.streamRequest(largeMessage));
+      clientStream.flush();
+      serverStream.writeMessage(methodDescriptor.streamResponse(largeMessage));
+      serverStream.flush();
+    }
+    doPingPong(serverListener);
+    clientReceived += verifyMessageCountAndClose(clientStreamListener.messageQueue, 4);
+    serverReceived += verifyMessageCountAndClose(serverStreamListener.messageQueue, 4);
+
+    // Drain exactly how many messages are left
+    serverStream.request(1);
+    clientStream.request(1);
+    clientReceived += verifyMessageCountAndClose(clientStreamListener.messageQueue, 1);
+    serverReceived += verifyMessageCountAndClose(serverStreamListener.messageQueue, 1);
+
+    // And now check that the streams can still complete gracefully
+    clientStream.writeMessage(methodDescriptor.streamRequest(largeMessage));
+    clientStream.flush();
+    clientStream.halfClose();
+    doPingPong(serverListener);
+    assertFalse(serverStreamListener.awaitHalfClosed(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+    serverStream.request(1);
+    serverReceived += verifyMessageCountAndClose(serverStreamListener.messageQueue, 1);
+    assertEquals(clientSent + 6, serverReceived);
+    assertTrue(serverStreamListener.awaitHalfClosed(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+    serverStream.writeMessage(methodDescriptor.streamResponse(largeMessage));
+    serverStream.flush();
+    Status status = Status.OK.withDescription("... quite a lengthy discussion");
+    serverStream.close(status, new Metadata());
+    doPingPong(serverListener);
+    try {
+      clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+      fail("Expected TimeoutException");
+    } catch (TimeoutException expectedException) {
+    }
+
+    clientStream.request(1);
+    clientReceived += verifyMessageCountAndClose(clientStreamListener.messageQueue, 1);
+    assertEquals(serverSent + 6, clientReceived);
+    assertCodeEquals(Status.OK, serverStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    Status clientStreamStatus = clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertEquals(status.getCode(), clientStreamStatus.getCode());
+    assertEquals(status.getDescription(), clientStreamStatus.getDescription());
+  }
+
+  private int verifyMessageCountAndClose(BlockingQueue<InputStream> messageQueue, int count)
+      throws Exception {
+    InputStream message;
+    for (int i = 0; i < count; i++) {
+      message = messageQueue.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+      assertNotNull(message);
+      message.close();
+    }
+    assertNull("no additional message expected", messageQueue.poll());
+    return count;
+  }
+
+  @Test
+  public void interactionsAfterServerStreamCloseAreNoops() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    // boilerplate
+    ClientStream clientStream =
+        client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    StreamCreation server
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+    // setup
+    clientStream.request(1);
+    server.stream.close(Status.INTERNAL, new Metadata());
+    assertNotNull(clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+    // Ensure that for a closed ServerStream, interactions are noops
+    server.stream.writeHeaders(new Metadata());
+    server.stream.writeMessage(methodDescriptor.streamResponse("response"));
+    server.stream.close(Status.INTERNAL, new Metadata());
+
+    // Make sure new streams still work properly
+    doPingPong(serverListener);
+  }
+
+  @Test
+  public void interactionsAfterClientStreamCancelAreNoops() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    serverTransport = serverTransportListener.transport;
+
+    // boilerplate
+    ClientStream clientStream =
+        client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListener clientListener = mock(ClientStreamListener.class);
+    clientStream.start(clientListener);
+    StreamCreation server
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+    // setup
+    server.stream.request(1);
+    clientStream.cancel(Status.UNKNOWN);
+    assertNotNull(server.listener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+    // Ensure that for a cancelled ClientStream, interactions are noops
+    clientStream.writeMessage(methodDescriptor.streamRequest("request"));
+    clientStream.halfClose();
+    clientStream.cancel(Status.UNKNOWN);
+
+    // Make sure new streams still work properly
+    doPingPong(serverListener);
+  }
+
+  // Not all transports support the tracer yet
+  protected boolean haveTransportTracer() {
+    return false;
+  }
+
+  @Test
+  public void transportTracer_streamStarted() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    if (!haveTransportTracer()) {
+      return;
+    }
+
+    // start first stream
+    long serverFirstTimestampNanos;
+    long clientFirstTimestampNanos;
+    {
+      TransportStats serverBefore = getTransportStats(serverTransportListener.transport);
+      assertEquals(0, serverBefore.streamsStarted);
+      assertEquals(0, serverBefore.lastRemoteStreamCreatedTimeNanos);
+      TransportStats clientBefore = getTransportStats(client);
+      assertEquals(0, clientBefore.streamsStarted);
+      assertEquals(0, clientBefore.lastRemoteStreamCreatedTimeNanos);
+
+      ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+      ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+      clientStream.start(clientStreamListener);
+      StreamCreation serverStreamCreation = serverTransportListener
+          .takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+      TransportStats serverAfter = getTransportStats(serverTransportListener.transport);
+      assertEquals(1, serverAfter.streamsStarted);
+      serverFirstTimestampNanos = serverAfter.lastRemoteStreamCreatedTimeNanos;
+      assertEquals(fakeCurrentTimeNanos(), serverAfter.lastRemoteStreamCreatedTimeNanos);
+
+      TransportStats clientAfter = getTransportStats(client);
+      assertEquals(1, clientAfter.streamsStarted);
+      clientFirstTimestampNanos = clientAfter.lastLocalStreamCreatedTimeNanos;
+      assertEquals(fakeCurrentTimeNanos(), clientFirstTimestampNanos);
+
+      ServerStream serverStream = serverStreamCreation.stream;
+      serverStream.close(Status.OK, new Metadata());
+    }
+
+    final long elapsedMillis = 100;
+    advanceClock(100, TimeUnit.MILLISECONDS);
+
+    // start second stream
+    {
+      TransportStats serverBefore = getTransportStats(serverTransportListener.transport);
+      assertEquals(1, serverBefore.streamsStarted);
+      TransportStats clientBefore = getTransportStats(client);
+      assertEquals(1, clientBefore.streamsStarted);
+
+      ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+      ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+      clientStream.start(clientStreamListener);
+      StreamCreation serverStreamCreation = serverTransportListener
+          .takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+      TransportStats serverAfter = getTransportStats(serverTransportListener.transport);
+      assertEquals(2, serverAfter.streamsStarted);
+      assertEquals(
+          TimeUnit.MILLISECONDS.toNanos(elapsedMillis),
+          serverAfter.lastRemoteStreamCreatedTimeNanos - serverFirstTimestampNanos);
+      assertEquals(fakeCurrentTimeNanos(), serverAfter.lastRemoteStreamCreatedTimeNanos);
+
+      TransportStats clientAfter = getTransportStats(client);
+      assertEquals(2, clientAfter.streamsStarted);
+      assertEquals(
+          TimeUnit.MILLISECONDS.toNanos(elapsedMillis),
+          clientAfter.lastLocalStreamCreatedTimeNanos - clientFirstTimestampNanos);
+      assertEquals(fakeCurrentTimeNanos(), clientAfter.lastLocalStreamCreatedTimeNanos);
+
+      ServerStream serverStream = serverStreamCreation.stream;
+      serverStream.close(Status.OK, new Metadata());
+    }
+  }
+
+  @Test
+  public void transportTracer_server_streamEnded_ok() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+    if (!haveTransportTracer()) {
+      return;
+    }
+
+    TransportStats serverBefore = getTransportStats(serverTransportListener.transport);
+    assertEquals(0, serverBefore.streamsSucceeded);
+    assertEquals(0, serverBefore.streamsFailed);
+    TransportStats clientBefore = getTransportStats(client);
+    assertEquals(0, clientBefore.streamsSucceeded);
+    assertEquals(0, clientBefore.streamsFailed);
+
+    clientStream.halfClose();
+    serverStream.close(Status.OK, new Metadata());
+    // do not validate stats until close() has been called on client
+    assertNotNull(clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+
+    TransportStats serverAfter = getTransportStats(serverTransportListener.transport);
+    assertEquals(1, serverAfter.streamsSucceeded);
+    assertEquals(0, serverAfter.streamsFailed);
+    TransportStats clientAfter = getTransportStats(client);
+    assertEquals(1, clientAfter.streamsSucceeded);
+    assertEquals(0, clientAfter.streamsFailed);
+  }
+
+  @Test
+  public void transportTracer_server_streamEnded_nonOk() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+    if (!haveTransportTracer()) {
+      return;
+    }
+
+    TransportStats serverBefore = getTransportStats(serverTransportListener.transport);
+    assertEquals(0, serverBefore.streamsFailed);
+    assertEquals(0, serverBefore.streamsSucceeded);
+    TransportStats clientBefore = getTransportStats(client);
+    assertEquals(0, clientBefore.streamsFailed);
+    assertEquals(0, clientBefore.streamsSucceeded);
+
+    serverStream.close(Status.UNKNOWN, new Metadata());
+    // do not validate stats until close() has been called on client
+    assertNotNull(clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+
+    TransportStats serverAfter = getTransportStats(serverTransportListener.transport);
+    assertEquals(1, serverAfter.streamsFailed);
+    assertEquals(0, serverAfter.streamsSucceeded);
+    TransportStats clientAfter = getTransportStats(client);
+    assertEquals(1, clientAfter.streamsFailed);
+    assertEquals(0, clientAfter.streamsSucceeded);
+
+    client.shutdown(Status.UNAVAILABLE);
+  }
+
+  @Test
+  public void transportTracer_client_streamEnded_nonOk() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    MockServerTransportListener serverTransportListener =
+        serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    StreamCreation serverStreamCreation =
+        serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    if (!haveTransportTracer()) {
+      return;
+    }
+
+    TransportStats serverBefore = getTransportStats(serverTransportListener.transport);
+    assertEquals(0, serverBefore.streamsFailed);
+    assertEquals(0, serverBefore.streamsSucceeded);
+    TransportStats clientBefore = getTransportStats(client);
+    assertEquals(0, clientBefore.streamsFailed);
+    assertEquals(0, clientBefore.streamsSucceeded);
+
+    clientStream.cancel(Status.UNKNOWN);
+    // do not validate stats until close() has been called on server
+    assertNotNull(serverStreamCreation.listener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+    TransportStats serverAfter = getTransportStats(serverTransportListener.transport);
+    assertEquals(1, serverAfter.streamsFailed);
+    assertEquals(0, serverAfter.streamsSucceeded);
+    TransportStats clientAfter = getTransportStats(client);
+    assertEquals(1, clientAfter.streamsFailed);
+    assertEquals(0, clientAfter.streamsSucceeded);
+  }
+
+  @Test
+  public void transportTracer_server_receive_msg() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+    if (!haveTransportTracer()) {
+      return;
+    }
+
+    TransportStats serverBefore = getTransportStats(serverTransportListener.transport);
+    assertEquals(0, serverBefore.messagesReceived);
+    assertEquals(0, serverBefore.lastMessageReceivedTimeNanos);
+    TransportStats clientBefore = getTransportStats(client);
+    assertEquals(0, clientBefore.messagesSent);
+    assertEquals(0, clientBefore.lastMessageSentTimeNanos);
+
+    serverStream.request(1);
+    clientStream.writeMessage(methodDescriptor.streamRequest("request"));
+    clientStream.flush();
+    clientStream.halfClose();
+    verifyMessageCountAndClose(serverStreamListener.messageQueue, 1);
+
+    TransportStats serverAfter = getTransportStats(serverTransportListener.transport);
+    assertEquals(1, serverAfter.messagesReceived);
+    assertEquals(fakeCurrentTimeNanos(), serverAfter.lastMessageReceivedTimeNanos);
+    TransportStats clientAfter = getTransportStats(client);
+    assertEquals(1, clientAfter.messagesSent);
+    assertEquals(fakeCurrentTimeNanos(), clientAfter.lastMessageSentTimeNanos);
+
+    serverStream.close(Status.OK, new Metadata());
+  }
+
+  @Test
+  public void transportTracer_server_send_msg() throws Exception {
+    server.start(serverListener);
+    client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+    if (!haveTransportTracer()) {
+      return;
+    }
+
+    TransportStats serverBefore = getTransportStats(serverTransportListener.transport);
+    assertEquals(0, serverBefore.messagesSent);
+    assertEquals(0, serverBefore.lastMessageSentTimeNanos);
+    TransportStats clientBefore = getTransportStats(client);
+    assertEquals(0, clientBefore.messagesReceived);
+    assertEquals(0, clientBefore.lastMessageReceivedTimeNanos);
+
+    clientStream.request(1);
+    serverStream.writeHeaders(new Metadata());
+    serverStream.writeMessage(methodDescriptor.streamResponse("response"));
+    serverStream.flush();
+    verifyMessageCountAndClose(clientStreamListener.messageQueue, 1);
+
+    TransportStats serverAfter = getTransportStats(serverTransportListener.transport);
+    assertEquals(1, serverAfter.messagesSent);
+    assertEquals(fakeCurrentTimeNanos(), serverAfter.lastMessageSentTimeNanos);
+    TransportStats clientAfter = getTransportStats(client);
+    assertEquals(1, clientAfter.messagesReceived);
+    assertEquals(fakeCurrentTimeNanos(), clientAfter.lastMessageReceivedTimeNanos);
+
+    serverStream.close(Status.OK, new Metadata());
+  }
+
+  @Test
+  public void socketStats() throws Exception {
+    server.start(serverListener);
+    ManagedClientTransport client = newClientTransport(server);
+    startTransport(client, mockClientTransportListener);
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+
+    SocketAddress serverAddress = clientStream.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
+    SocketAddress clientAddress = serverStream.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
+
+    SocketStats clientSocketStats = client.getStats().get();
+    assertEquals(clientAddress, clientSocketStats.local);
+    assertEquals(serverAddress, clientSocketStats.remote);
+    // very basic sanity check that socket options are populated
+    assertNotNull(clientSocketStats.socketOptions.lingerSeconds);
+    assertTrue(clientSocketStats.socketOptions.others.containsKey("SO_SNDBUF"));
+
+    SocketStats serverSocketStats = serverTransportListener.transport.getStats().get();
+    assertEquals(serverAddress, serverSocketStats.local);
+    assertEquals(clientAddress, serverSocketStats.remote);
+    // very basic sanity check that socket options are populated
+    assertNotNull(serverSocketStats.socketOptions.lingerSeconds);
+    assertTrue(serverSocketStats.socketOptions.others.containsKey("SO_SNDBUF"));
+  }
+
+  /**
+   * Helper that simply does an RPC. It can be used similar to a sleep for negative testing: to give
+   * time for actions _not_ to happen. Since it is based on doing an actual RPC with actual
+   * callbacks, it generally provides plenty of time for Runnables to execute. But it is also faster
+   * on faster machines and more reliable on slower machines.
+   */
+  private void doPingPong(MockServerListener serverListener) throws Exception {
+    ManagedClientTransport client = newClientTransport(server);
+    ManagedClientTransport.Listener listener = mock(ManagedClientTransport.Listener.class);
+    startTransport(client, listener);
+    ClientStream clientStream = client.newStream(methodDescriptor, new Metadata(), callOptions);
+    ClientStreamListenerBase clientStreamListener = new ClientStreamListenerBase();
+    clientStream.start(clientStreamListener);
+
+    MockServerTransportListener serverTransportListener
+        = serverListener.takeListenerOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    StreamCreation serverStreamCreation
+        = serverTransportListener.takeStreamOrFail(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+    ServerStream serverStream = serverStreamCreation.stream;
+    ServerStreamListenerBase serverStreamListener = serverStreamCreation.listener;
+
+    serverStream.close(Status.OK, new Metadata());
+    assertNotNull(clientStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertNotNull(clientStreamListener.trailers.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    assertNotNull(serverStreamListener.status.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    client.shutdown(Status.UNAVAILABLE);
+  }
+
+  /**
+   * Only assert that the Status.Code matches, but provide the entire actual result in case the
+   * assertion fails.
+   */
+  private static void assertCodeEquals(String message, Status expected, Status actual) {
+    if (expected == null) {
+      fail("expected should not be null");
+    }
+    if (actual == null || !expected.getCode().equals(actual.getCode())) {
+      assertEquals(message, expected, actual);
+    }
+  }
+
+  private static void assertCodeEquals(Status expected, Status actual) {
+    assertCodeEquals(null, expected, actual);
+  }
+
+  private static void assertStatusEquals(Status expected, Status actual) {
+    if (expected == null) {
+      fail("expected should not be null");
+    }
+    if (actual == null || !expected.getCode().equals(actual.getCode())
+        || !Objects.equal(expected.getDescription(), actual.getDescription())
+        || !Objects.equal(expected.getCause(), actual.getCause())) {
+      assertEquals(expected, actual);
+    }
+  }
+
+  private static boolean waitForFuture(Future<?> future, long timeout, TimeUnit unit)
+      throws InterruptedException {
+    try {
+      future.get(timeout, unit);
+    } catch (ExecutionException ex) {
+      throw new AssertionError(ex);
+    } catch (TimeoutException ex) {
+      return false;
+    }
+    return true;
+  }
+
+  private static void runIfNotNull(Runnable runnable) {
+    if (runnable != null) {
+      runnable.run();
+    }
+  }
+
+  private static void startTransport(
+      ManagedClientTransport clientTransport,
+      ManagedClientTransport.Listener listener) {
+    runIfNotNull(clientTransport.start(listener));
+    verify(listener, timeout(100)).transportReady();
+  }
+
+  private static class MockServerListener implements ServerListener {
+    public final BlockingQueue<MockServerTransportListener> listeners
+        = new LinkedBlockingQueue<MockServerTransportListener>();
+    private final SettableFuture<?> shutdown = SettableFuture.create();
+
+    @Override
+    public ServerTransportListener transportCreated(ServerTransport transport) {
+      MockServerTransportListener listener = new MockServerTransportListener(transport);
+      listeners.add(listener);
+      return listener;
+    }
+
+    @Override
+    public void serverShutdown() {
+      assertTrue(shutdown.set(null));
+    }
+
+    public boolean waitForShutdown(long timeout, TimeUnit unit) throws InterruptedException {
+      return waitForFuture(shutdown, timeout, unit);
+    }
+
+    public MockServerTransportListener takeListenerOrFail(long timeout, TimeUnit unit)
+        throws InterruptedException {
+      MockServerTransportListener listener = listeners.poll(timeout, unit);
+      if (listener == null) {
+        fail("Timed out waiting for server transport");
+      }
+      return listener;
+    }
+  }
+
+  private static class MockServerTransportListener implements ServerTransportListener {
+    public final ServerTransport transport;
+    public final BlockingQueue<StreamCreation> streams = new LinkedBlockingQueue<StreamCreation>();
+    private final SettableFuture<?> terminated = SettableFuture.create();
+
+    public MockServerTransportListener(ServerTransport transport) {
+      this.transport = transport;
+    }
+
+    @Override
+    public void streamCreated(ServerStream stream, String method, Metadata headers) {
+      ServerStreamListenerBase listener = new ServerStreamListenerBase();
+      streams.add(new StreamCreation(stream, method, headers, listener));
+      stream.setListener(listener);
+    }
+
+    @Override
+    public Attributes transportReady(Attributes attributes) {
+      return Attributes.newBuilder()
+          .setAll(attributes)
+          .set(ADDITIONAL_TRANSPORT_ATTR_KEY, "additional attribute value")
+          .build();
+    }
+
+    @Override
+    public void transportTerminated() {
+      assertTrue(terminated.set(null));
+    }
+
+    public boolean waitForTermination(long timeout, TimeUnit unit) throws InterruptedException {
+      return waitForFuture(terminated, timeout, unit);
+    }
+
+    public boolean isTerminated() {
+      return terminated.isDone();
+    }
+
+    public StreamCreation takeStreamOrFail(long timeout, TimeUnit unit)
+        throws InterruptedException {
+      StreamCreation stream = streams.poll(timeout, unit);
+      if (stream == null) {
+        fail("Timed out waiting for server stream");
+      }
+      return stream;
+    }
+  }
+
+  private static class ServerStreamListenerBase implements ServerStreamListener {
+    private final BlockingQueue<InputStream> messageQueue = new LinkedBlockingQueue<InputStream>();
+    // Would have used Void instead of Object, but null elements are not allowed
+    private final BlockingQueue<Object> readyQueue = new LinkedBlockingQueue<Object>();
+    private final CountDownLatch halfClosedLatch = new CountDownLatch(1);
+    private final SettableFuture<Status> status = SettableFuture.create();
+
+    private boolean awaitOnReady(int timeout, TimeUnit unit) throws Exception {
+      return readyQueue.poll(timeout, unit) != null;
+    }
+
+    private boolean awaitOnReadyAndDrain(int timeout, TimeUnit unit) throws Exception {
+      if (!awaitOnReady(timeout, unit)) {
+        return false;
+      }
+      // Throw the rest away
+      readyQueue.drainTo(Lists.newArrayList());
+      return true;
+    }
+
+    private boolean awaitHalfClosed(int timeout, TimeUnit unit) throws Exception {
+      return halfClosedLatch.await(timeout, unit);
+    }
+
+    @Override
+    public void messagesAvailable(MessageProducer producer) {
+      if (status.isDone()) {
+        fail("messagesAvailable invoked after closed");
+      }
+      InputStream message;
+      while ((message = producer.next()) != null) {
+        messageQueue.add(message);
+      }
+    }
+
+    @Override
+    public void onReady() {
+      if (status.isDone()) {
+        fail("onReady invoked after closed");
+      }
+      readyQueue.add(new Object());
+    }
+
+    @Override
+    public void halfClosed() {
+      if (status.isDone()) {
+        fail("halfClosed invoked after closed");
+      }
+      halfClosedLatch.countDown();
+    }
+
+    @Override
+    public void closed(Status status) {
+      if (this.status.isDone()) {
+        fail("closed invoked more than once");
+      }
+      this.status.set(status);
+    }
+  }
+
+  private static class ClientStreamListenerBase implements ClientStreamListener {
+    private final BlockingQueue<InputStream> messageQueue = new LinkedBlockingQueue<InputStream>();
+    // Would have used Void instead of Object, but null elements are not allowed
+    private final BlockingQueue<Object> readyQueue = new LinkedBlockingQueue<Object>();
+    private final SettableFuture<Metadata> headers = SettableFuture.create();
+    private final SettableFuture<Metadata> trailers = SettableFuture.create();
+    private final SettableFuture<Status> status = SettableFuture.create();
+
+    private boolean awaitOnReady(int timeout, TimeUnit unit) throws Exception {
+      return readyQueue.poll(timeout, unit) != null;
+    }
+
+    private boolean awaitOnReadyAndDrain(int timeout, TimeUnit unit) throws Exception {
+      if (!awaitOnReady(timeout, unit)) {
+        return false;
+      }
+      // Throw the rest away
+      readyQueue.drainTo(Lists.newArrayList());
+      return true;
+    }
+
+    @Override
+    public void messagesAvailable(MessageProducer producer) {
+      if (status.isDone()) {
+        fail("messagesAvailable invoked after closed");
+      }
+      InputStream message;
+      while ((message = producer.next()) != null) {
+        messageQueue.add(message);
+      }
+    }
+
+    @Override
+    public void onReady() {
+      if (status.isDone()) {
+        fail("onReady invoked after closed");
+      }
+      readyQueue.add(new Object());
+    }
+
+    @Override
+    public void headersRead(Metadata headers) {
+      if (status.isDone()) {
+        fail("headersRead invoked after closed");
+      }
+      this.headers.set(headers);
+    }
+
+    @Override
+    public void closed(Status status, Metadata trailers) {
+      closed(status, RpcProgress.PROCESSED, trailers);
+    }
+
+    @Override
+    public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) {
+      if (this.status.isDone()) {
+        fail("headersRead invoked after closed");
+      }
+      this.status.set(status);
+      this.trailers.set(trailers);
+    }
+  }
+
+  private static class StreamCreation {
+    public final ServerStream stream;
+    public final String method;
+    public final Metadata headers;
+    public final ServerStreamListenerBase listener;
+
+    public StreamCreation(
+        ServerStream stream, String method, Metadata headers, ServerStreamListenerBase listener) {
+      this.stream = stream;
+      this.method = method;
+      this.headers = headers;
+      this.listener = listener;
+    }
+  }
+
+  private static class StringMarshaller implements MethodDescriptor.Marshaller<String> {
+    public static final StringMarshaller INSTANCE = new StringMarshaller();
+
+    @Override
+    public InputStream stream(String value) {
+      return new ByteArrayInputStream(value.getBytes(UTF_8));
+    }
+
+    @Override
+    public String parse(InputStream stream) {
+      try {
+        return new String(IoUtils.toByteArray(stream), UTF_8);
+      } catch (IOException ex) {
+        throw new RuntimeException(ex);
+      }
+    }
+  }
+
+  private static class StringBinaryMarshaller implements Metadata.BinaryMarshaller<String> {
+    public static final StringBinaryMarshaller INSTANCE = new StringBinaryMarshaller();
+
+    @Override
+    public byte[] toBytes(String value) {
+      return value.getBytes(UTF_8);
+    }
+
+    @Override
+    public String parseBytes(byte[] serialized) {
+      return new String(serialized, UTF_8);
+    }
+  }
+
+  private static TransportStats getTransportStats(InternalInstrumented<SocketStats> socket)
+      throws ExecutionException, InterruptedException {
+    return socket.getStats().get().data;
+  }
+}
diff --git a/testing/src/main/java/io/grpc/internal/testing/SingleMessageProducer.java b/testing/src/main/java/io/grpc/internal/testing/SingleMessageProducer.java
new file mode 100644
index 0000000..c621e50
--- /dev/null
+++ b/testing/src/main/java/io/grpc/internal/testing/SingleMessageProducer.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal.testing;
+
+import io.grpc.internal.StreamListener;
+import java.io.InputStream;
+import javax.annotation.Nullable;
+
+public class SingleMessageProducer implements StreamListener.MessageProducer {
+  private InputStream message;
+
+  public SingleMessageProducer(InputStream message) {
+    this.message = message;
+  }
+
+  @Nullable
+  @Override
+  public InputStream next() {
+    InputStream messageToReturn = message;
+    message = null;
+    return messageToReturn;
+  }
+}
diff --git a/testing/src/main/java/io/grpc/internal/testing/StatsTestUtils.java b/testing/src/main/java/io/grpc/internal/testing/StatsTestUtils.java
new file mode 100644
index 0000000..16f6004
--- /dev/null
+++ b/testing/src/main/java/io/grpc/internal/testing/StatsTestUtils.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal.testing;
+
+import static com.google.common.base.Charsets.UTF_8;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Maps;
+import io.opencensus.common.Scope;
+import io.opencensus.stats.Measure;
+import io.opencensus.stats.MeasureMap;
+import io.opencensus.stats.StatsRecorder;
+import io.opencensus.tags.Tag;
+import io.opencensus.tags.TagContext;
+import io.opencensus.tags.TagContextBuilder;
+import io.opencensus.tags.TagKey;
+import io.opencensus.tags.TagValue;
+import io.opencensus.tags.Tagger;
+import io.opencensus.tags.propagation.TagContextBinarySerializer;
+import io.opencensus.tags.propagation.TagContextDeserializationException;
+import io.opencensus.tags.unsafe.ContextUtils;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.AttributeValue;
+import io.opencensus.trace.EndSpanOptions;
+import io.opencensus.trace.Link;
+import io.opencensus.trace.MessageEvent;
+import io.opencensus.trace.Sampler;
+import io.opencensus.trace.Span;
+import io.opencensus.trace.SpanBuilder;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.TraceOptions;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+public class StatsTestUtils {
+  private StatsTestUtils() {
+  }
+
+  public static class MetricsRecord {
+
+    public final ImmutableMap<TagKey, TagValue> tags;
+    public final ImmutableMap<Measure, Number> metrics;
+
+    private MetricsRecord(
+        ImmutableMap<TagKey, TagValue> tags, ImmutableMap<Measure, Number> metrics) {
+      this.tags = tags;
+      this.metrics = metrics;
+    }
+
+    /**
+     * Returns the value of a metric, or {@code null} if not found.
+     */
+    @Nullable
+    public Double getMetric(Measure measure) {
+      for (Map.Entry<Measure, Number> m : metrics.entrySet()) {
+        if (m.getKey().equals(measure)) {
+          Number value = m.getValue();
+          if (value instanceof Double) {
+            return (Double) value;
+          } else if (value instanceof Long) {
+            return (double) (Long) value;
+          }
+          throw new AssertionError("Unexpected measure value type: " + value.getClass().getName());
+        }
+      }
+      return null;
+    }
+
+    /**
+     * Returns the value of a metric converted to long, or throw if not found.
+     */
+    public long getMetricAsLongOrFail(Measure measure) {
+      Double doubleValue = getMetric(measure);
+      checkNotNull(doubleValue, "Measure not found: %s", measure.getName());
+      long longValue = (long) (Math.abs(doubleValue) + 0.0001);
+      if (doubleValue < 0) {
+        longValue = -longValue;
+      }
+      return longValue;
+    }
+
+    @Override
+    public String toString() {
+      return "[tags=" + tags + ", metrics=" + metrics + "]";
+    }
+  }
+
+  /**
+   * This tag will be propagated by {@link FakeTagger} on the wire.
+   */
+  public static final TagKey EXTRA_TAG = TagKey.create("/rpc/test/extratag");
+
+  private static final String EXTRA_TAG_HEADER_VALUE_PREFIX = "extratag:";
+
+  /**
+   * A {@link Tagger} implementation that saves metrics records to be accessible from {@link
+   * #pollRecord()} and {@link #pollRecord(long, TimeUnit)}, until {@link #rolloverRecords} is
+   * called.
+   */
+  public static final class FakeStatsRecorder extends StatsRecorder {
+
+    private BlockingQueue<MetricsRecord> records;
+
+    public FakeStatsRecorder() {
+      rolloverRecords();
+    }
+
+    @Override
+    public MeasureMap newMeasureMap() {
+      return new FakeStatsRecord(this);
+    }
+
+    public MetricsRecord pollRecord() {
+      return getCurrentRecordSink().poll();
+    }
+
+    public MetricsRecord pollRecord(long timeout, TimeUnit unit) throws InterruptedException {
+      return getCurrentRecordSink().poll(timeout, unit);
+    }
+
+    /**
+     * Disconnect this tagger with the contexts it has created so far.  The records from those
+     * contexts will not show up in {@link #pollRecord}.  Useful for isolating the records between
+     * test cases.
+     */
+    // This needs to be synchronized with getCurrentRecordSink() which may run concurrently.
+    public synchronized void rolloverRecords() {
+      records = new LinkedBlockingQueue<MetricsRecord>();
+    }
+
+    private synchronized BlockingQueue<MetricsRecord> getCurrentRecordSink() {
+      return records;
+    }
+  }
+
+  public static final class FakeTagger extends Tagger {
+
+    @Override
+    public FakeTagContext empty() {
+      return FakeTagContext.EMPTY;
+    }
+
+    @Override
+    public TagContext getCurrentTagContext() {
+      return ContextUtils.TAG_CONTEXT_KEY.get();
+    }
+
+    @Override
+    public TagContextBuilder emptyBuilder() {
+      return new FakeTagContextBuilder(ImmutableMap.<TagKey, TagValue>of());
+    }
+
+    @Override
+    public FakeTagContextBuilder toBuilder(TagContext tags) {
+      return new FakeTagContextBuilder(getTags(tags));
+    }
+
+    @Override
+    public TagContextBuilder currentBuilder() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Scope withTagContext(TagContext tags) {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  public static final class FakeTagContextBinarySerializer extends TagContextBinarySerializer {
+
+    private final FakeTagger tagger = new FakeTagger();
+
+    @Override
+    public TagContext fromByteArray(byte[] bytes) throws TagContextDeserializationException {
+      String serializedString = new String(bytes, UTF_8);
+      if (serializedString.startsWith(EXTRA_TAG_HEADER_VALUE_PREFIX)) {
+        return tagger.emptyBuilder()
+            .put(EXTRA_TAG,
+                TagValue.create(serializedString.substring(EXTRA_TAG_HEADER_VALUE_PREFIX.length())))
+            .build();
+      } else {
+        throw new TagContextDeserializationException("Malformed value");
+      }
+    }
+
+    @Override
+    public byte[] toByteArray(TagContext tags) {
+      TagValue extraTagValue = getTags(tags).get(EXTRA_TAG);
+      if (extraTagValue == null) {
+        throw new UnsupportedOperationException("TagContext must contain EXTRA_TAG");
+      }
+      return (EXTRA_TAG_HEADER_VALUE_PREFIX + extraTagValue.asString()).getBytes(UTF_8);
+    }
+  }
+
+  public static final class FakeStatsRecord extends MeasureMap {
+
+    private final BlockingQueue<MetricsRecord> recordSink;
+    public final Map<Measure, Number> metrics = Maps.newHashMap();
+
+    private FakeStatsRecord(FakeStatsRecorder statsRecorder) {
+      this.recordSink = statsRecorder.getCurrentRecordSink();
+    }
+
+    @Override
+    public MeasureMap put(Measure.MeasureDouble measure, double value) {
+      metrics.put(measure, value);
+      return this;
+    }
+
+    @Override
+    public MeasureMap put(Measure.MeasureLong measure, long value) {
+      metrics.put(measure, value);
+      return this;
+    }
+
+    @Override
+    public void record(TagContext tags) {
+      recordSink.add(new MetricsRecord(getTags(tags), ImmutableMap.copyOf(metrics)));
+    }
+
+    @Override
+    public void record() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  public static final class FakeTagContext extends TagContext {
+
+    private static final FakeTagContext EMPTY =
+        new FakeTagContext(ImmutableMap.<TagKey, TagValue>of());
+
+    private final ImmutableMap<TagKey, TagValue> tags;
+
+    private FakeTagContext(ImmutableMap<TagKey, TagValue> tags) {
+      this.tags = tags;
+    }
+
+    public ImmutableMap<TagKey, TagValue> getTags() {
+      return tags;
+    }
+
+    @Override
+    public String toString() {
+      return "[tags=" + tags + "]";
+    }
+
+    @Override
+    protected Iterator<Tag> getIterator() {
+      return Iterators.transform(
+          tags.entrySet().iterator(),
+          new Function<Map.Entry<TagKey, TagValue>, Tag>() {
+            @Override
+            public Tag apply(@Nullable Map.Entry<TagKey, TagValue> entry) {
+              return Tag.create(entry.getKey(), entry.getValue());
+            }
+          });
+    }
+  }
+
+  public static class FakeTagContextBuilder extends TagContextBuilder {
+
+    private final Map<TagKey, TagValue> tagsBuilder = Maps.newHashMap();
+
+    private FakeTagContextBuilder(Map<TagKey, TagValue> tags) {
+      tagsBuilder.putAll(tags);
+    }
+
+    @Override
+    public TagContextBuilder put(TagKey key, TagValue value) {
+      tagsBuilder.put(key, value);
+      return this;
+    }
+
+    @Override
+    public TagContextBuilder remove(TagKey key) {
+      tagsBuilder.remove(key);
+      return this;
+    }
+
+    @Override
+    public TagContext build() {
+      FakeTagContext context = new FakeTagContext(ImmutableMap.copyOf(tagsBuilder));
+      return context;
+    }
+
+    @Override
+    public Scope buildScoped() {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  // This method handles the default TagContext, which isn't an instance of FakeTagContext.
+  private static ImmutableMap<TagKey, TagValue> getTags(TagContext tags) {
+    return tags instanceof FakeTagContext
+        ? ((FakeTagContext) tags).getTags()
+        : ImmutableMap.<TagKey, TagValue>of();
+  }
+
+  // TODO(bdrutu): Remove this class after OpenCensus releases support for this class.
+  public static class MockableSpan extends Span {
+    /**
+     * Creates a MockableSpan with a random trace ID and span ID.
+     */
+    public static MockableSpan generateRandomSpan(Random random) {
+      return new MockableSpan(
+          SpanContext.create(
+              TraceId.generateRandomId(random),
+              SpanId.generateRandomId(random),
+              TraceOptions.DEFAULT),
+          null);
+    }
+
+    @Override
+    public void putAttributes(Map<String, AttributeValue> attributes) {}
+
+    @Override
+    public void addAnnotation(String description, Map<String, AttributeValue> attributes) {}
+
+    @Override
+    public void addAnnotation(Annotation annotation) {}
+
+    @Override
+    public void addMessageEvent(MessageEvent messageEvent) {}
+
+    @Override
+    public void addLink(Link link) {}
+
+    @Override
+    public void end(EndSpanOptions options) {}
+
+    private MockableSpan(SpanContext context, @Nullable EnumSet<Options> options) {
+      super(context, options);
+    }
+
+    /**
+     * Mockable implementation for the {@link SpanBuilder} class.
+     *
+     * <p>Not {@code final} to allow easy mocking.
+     *
+     */
+    public static class Builder extends SpanBuilder {
+
+      @Override
+      public SpanBuilder setSampler(Sampler sampler) {
+        return this;
+      }
+
+      @Override
+      public SpanBuilder setParentLinks(List<Span> parentLinks) {
+        return this;
+      }
+
+      @Override
+      public SpanBuilder setRecordEvents(boolean recordEvents) {
+        return this;
+      }
+
+      @Override
+      public Span startSpan() {
+        return null;
+      }
+    }
+  }
+}
diff --git a/testing/src/main/java/io/grpc/internal/testing/StreamRecorder.java b/testing/src/main/java/io/grpc/internal/testing/StreamRecorder.java
new file mode 100644
index 0000000..bd0ac9e
--- /dev/null
+++ b/testing/src/main/java/io/grpc/internal/testing/StreamRecorder.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal.testing;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.stub.StreamObserver;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+/**
+ * Utility implementation of {@link StreamObserver} used in testing. Records all the observed
+ * values produced by the stream as well as any errors.
+ */
+public class StreamRecorder<T> implements StreamObserver<T> {
+
+  /**
+   * Creates a new recorder.
+   */
+  public static <T> StreamRecorder<T> create() {
+    return new StreamRecorder<T>();
+  }
+
+  private final CountDownLatch latch;
+  private final List<T> results;
+  private Throwable error;
+  private final SettableFuture<T> firstValue;
+
+  private StreamRecorder() {
+    firstValue = SettableFuture.create();
+    latch = new CountDownLatch(1);
+    results = Collections.synchronizedList(new ArrayList<T>());
+  }
+
+  @Override
+  public void onNext(T value) {
+    if (!firstValue.isDone()) {
+      firstValue.set(value);
+    }
+    results.add(value);
+  }
+
+  @Override
+  public void onError(Throwable t) {
+    if (!firstValue.isDone()) {
+      firstValue.setException(t);
+    }
+    error = t;
+    latch.countDown();
+  }
+
+  @Override
+  public void onCompleted() {
+    if (!firstValue.isDone()) {
+      firstValue.setException(new IllegalStateException("No first value provided"));
+    }
+    latch.countDown();
+  }
+
+  /**
+   * Waits for the stream to terminate.
+   */
+  public void awaitCompletion() throws Exception {
+    latch.await();
+  }
+
+  /**
+   * Waits a fixed timeout for the stream to terminate.
+   */
+  public boolean awaitCompletion(int timeout, TimeUnit unit) throws Exception {
+    return latch.await(timeout, unit);
+  }
+
+  /**
+   * Returns the current set of received values.
+   */
+  public List<T> getValues() {
+    return Collections.unmodifiableList(results);
+  }
+
+  /**
+   * Returns the stream terminating error.
+   */
+  @Nullable public Throwable getError() {
+    return error;
+  }
+
+  /**
+   * Returns a {@link ListenableFuture} for the first value received from the stream. Useful
+   * for testing unary call patterns.
+   */
+  public ListenableFuture<T> firstValue() {
+    return firstValue;
+  }
+}
diff --git a/testing/src/main/java/io/grpc/internal/testing/TestClientStreamTracer.java b/testing/src/main/java/io/grpc/internal/testing/TestClientStreamTracer.java
new file mode 100644
index 0000000..7bf94bb
--- /dev/null
+++ b/testing/src/main/java/io/grpc/internal/testing/TestClientStreamTracer.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal.testing;
+
+import io.grpc.ClientStreamTracer;
+import io.grpc.Status;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A {@link ClientStreamTracer} suitable for testing.
+ */
+public class TestClientStreamTracer extends ClientStreamTracer implements TestStreamTracer {
+  private final TestBaseStreamTracer delegate = new TestBaseStreamTracer();
+  protected final CountDownLatch outboundHeadersLatch = new CountDownLatch(1);
+  protected final AtomicReference<Throwable> outboundHeadersCalled =
+      new AtomicReference<Throwable>();
+  protected final AtomicReference<Throwable> inboundHeadersCalled =
+      new AtomicReference<Throwable>();
+
+  @Override
+  public void await() throws InterruptedException {
+    delegate.await();
+  }
+
+  @Override
+  public boolean await(long timeout, TimeUnit timeUnit) throws InterruptedException {
+    return delegate.await(timeout, timeUnit);
+  }
+
+  /**
+   * Returns if {@link ClientStreamTracer#inboundHeaders} has been called.
+   */
+  public boolean getInboundHeaders() {
+    return inboundHeadersCalled.get() != null;
+  }
+
+  /**
+   * Returns if {@link ClientStreamTracer#outboundHeaders} has been called.
+   */
+  public boolean getOutboundHeaders() {
+    return outboundHeadersCalled.get() != null;
+  }
+
+  /**
+   * Allow tests to await the outbound header event, which depending on the test case may be
+   * necessary (e.g., if we test for a Netty client's outbound headers upon receiving the start of
+   * stream on the server side, the tracer won't know that headers were sent until a channel future
+   * executes).
+   */
+  public boolean awaitOutboundHeaders(int timeout, TimeUnit unit) throws Exception {
+    return outboundHeadersLatch.await(timeout, unit);
+  }
+
+  @Override
+  public Status getStatus() {
+    return delegate.getStatus();
+  }
+
+  @Override
+  public long getInboundWireSize() {
+    return delegate.getInboundWireSize();
+  }
+
+  @Override
+  public long getInboundUncompressedSize() {
+    return delegate.getInboundUncompressedSize();
+  }
+
+  @Override
+  public long getOutboundWireSize() {
+    return delegate.getOutboundWireSize();
+  }
+
+  @Override
+  public long getOutboundUncompressedSize() {
+    return delegate.getOutboundUncompressedSize();
+  }
+
+  @Override
+  public void setFailDuplicateCallbacks(boolean fail) {
+    delegate.setFailDuplicateCallbacks(fail);
+  }
+
+  @Override
+  public String nextOutboundEvent() {
+    return delegate.nextOutboundEvent();
+  }
+
+  @Override
+  public String nextInboundEvent() {
+    return delegate.nextInboundEvent();
+  }
+
+  @Override
+  public void outboundWireSize(long bytes) {
+    delegate.outboundWireSize(bytes);
+  }
+
+  @Override
+  public void inboundWireSize(long bytes) {
+    delegate.inboundWireSize(bytes);
+  }
+
+  @Override
+  public void outboundUncompressedSize(long bytes) {
+    delegate.outboundUncompressedSize(bytes);
+  }
+
+  @Override
+  public void inboundUncompressedSize(long bytes) {
+    delegate.inboundUncompressedSize(bytes);
+  }
+
+  @Override
+  public void streamClosed(Status status) {
+    delegate.streamClosed(status);
+  }
+
+  @Override
+  public void inboundMessage(int seqNo) {
+    delegate.inboundMessage(seqNo);
+  }
+
+  @Override
+  public void outboundMessage(int seqNo) {
+    delegate.outboundMessage(seqNo);
+  }
+
+  @Override
+  public void outboundMessageSent(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+    delegate.outboundMessageSent(seqNo, optionalWireSize, optionalUncompressedSize);
+  }
+
+  @Override
+  public void inboundMessageRead(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+    delegate.inboundMessageRead(seqNo, optionalWireSize, optionalUncompressedSize);
+  }
+
+  @Override
+  public void outboundHeaders() {
+    if (!outboundHeadersCalled.compareAndSet(null, new Exception("first stack"))
+        && delegate.failDuplicateCallbacks.get()) {
+      throw new AssertionError(
+          "outboundHeaders called more than once",
+          new Exception("second stack", outboundHeadersCalled.get()));
+    }
+    outboundHeadersLatch.countDown();
+  }
+
+  @Override
+  public void inboundHeaders() {
+    if (!inboundHeadersCalled.compareAndSet(null, new Exception("first stack"))
+        && delegate.failDuplicateCallbacks.get()) {
+      throw new AssertionError(
+          "inboundHeaders called more than once",
+          new Exception("second stack", inboundHeadersCalled.get()));
+    }
+  }
+}
diff --git a/testing/src/main/java/io/grpc/internal/testing/TestServerStreamTracer.java b/testing/src/main/java/io/grpc/internal/testing/TestServerStreamTracer.java
new file mode 100644
index 0000000..39665ca
--- /dev/null
+++ b/testing/src/main/java/io/grpc/internal/testing/TestServerStreamTracer.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal.testing;
+
+import io.grpc.ServerStreamTracer;
+import io.grpc.Status;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A {@link ServerStreamTracer} suitable for testing.
+ */
+public class TestServerStreamTracer extends ServerStreamTracer implements TestStreamTracer {
+  private final TestBaseStreamTracer delegate = new TestBaseStreamTracer();
+  protected final AtomicReference<ServerCallInfo<?, ?>> serverCallInfo =
+      new AtomicReference<ServerCallInfo<?, ?>>();
+
+  @Override
+  public void await() throws InterruptedException {
+    delegate.await();
+  }
+
+  @Override
+  public boolean await(long timeout, TimeUnit timeUnit) throws InterruptedException {
+    return delegate.await(timeout, timeUnit);
+  }
+
+  /**
+   * Returns the ServerCall passed to {@link ServerStreamTracer#serverCallStarted}.
+   */
+  public ServerCallInfo<?, ?> getServerCallInfo() {
+    return serverCallInfo.get();
+  }
+
+  @Override
+  public Status getStatus() {
+    return delegate.getStatus();
+  }
+
+  @Override
+  public long getInboundWireSize() {
+    return delegate.getInboundWireSize();
+  }
+
+  @Override
+  public long getInboundUncompressedSize() {
+    return delegate.getInboundUncompressedSize();
+  }
+
+  @Override
+  public long getOutboundWireSize() {
+    return delegate.getOutboundWireSize();
+  }
+
+  @Override
+  public long getOutboundUncompressedSize() {
+    return delegate.getOutboundUncompressedSize();
+  }
+
+  @Override
+  public void setFailDuplicateCallbacks(boolean fail) {
+    delegate.setFailDuplicateCallbacks(fail);
+  }
+
+  @Override
+  public String nextOutboundEvent() {
+    return delegate.nextOutboundEvent();
+  }
+
+  @Override
+  public String nextInboundEvent() {
+    return delegate.nextInboundEvent();
+  }
+
+  @Override
+  public void outboundWireSize(long bytes) {
+    delegate.outboundWireSize(bytes);
+  }
+
+  @Override
+  public void inboundWireSize(long bytes) {
+    delegate.inboundWireSize(bytes);
+  }
+
+  @Override
+  public void outboundUncompressedSize(long bytes) {
+    delegate.outboundUncompressedSize(bytes);
+  }
+
+  @Override
+  public void inboundUncompressedSize(long bytes) {
+    delegate.inboundUncompressedSize(bytes);
+  }
+
+  @Override
+  public void streamClosed(Status status) {
+    delegate.streamClosed(status);
+  }
+
+  @Override
+  public void inboundMessage(int seqNo) {
+    delegate.inboundMessage(seqNo);
+  }
+
+  @Override
+  public void outboundMessage(int seqNo) {
+    delegate.outboundMessage(seqNo);
+  }
+
+  @Override
+  public void outboundMessageSent(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+    delegate.outboundMessageSent(seqNo, optionalWireSize, optionalUncompressedSize);
+  }
+
+  @Override
+  public void inboundMessageRead(int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+    delegate.inboundMessageRead(seqNo, optionalWireSize, optionalUncompressedSize);
+  }
+
+  @Override
+  public void serverCallStarted(ServerCallInfo<?, ?> callInfo) {
+    if (!serverCallInfo.compareAndSet(null, callInfo) && delegate.failDuplicateCallbacks.get()) {
+      throw new AssertionError("serverCallStarted called more than once");
+    }
+  }
+}
diff --git a/testing/src/main/java/io/grpc/internal/testing/TestStreamTracer.java b/testing/src/main/java/io/grpc/internal/testing/TestStreamTracer.java
new file mode 100644
index 0000000..e2ce20c
--- /dev/null
+++ b/testing/src/main/java/io/grpc/internal/testing/TestStreamTracer.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal.testing;
+
+import io.grpc.Status;
+import io.grpc.StreamTracer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.Nullable;
+
+/**
+ * A {@link StreamTracer} suitable for testing.
+ */
+public interface TestStreamTracer {
+
+  /**
+   * Waits for the stream to be done.
+   */
+  void await() throws InterruptedException;
+
+  /**
+   * Waits for the stream to be done.
+   */
+  boolean await(long timeout, TimeUnit timeUnit) throws InterruptedException;
+
+  /**
+   * Returns the status passed to {@link StreamTracer#streamClosed}.
+   */
+  Status getStatus();
+
+  /**
+   * Returns to sum of all sizes passed to {@link StreamTracer#inboundWireSize}.
+   */
+  long getInboundWireSize();
+
+  /**
+   * Returns to sum of all sizes passed to {@link StreamTracer#inboundUncompressedSize}.
+   */
+  long getInboundUncompressedSize();
+
+  /**
+   * Returns to sum of all sizes passed to {@link StreamTracer#outboundWireSize}.
+   */
+  long getOutboundWireSize();
+
+  /**
+   * Returns to sum of al sizes passed to {@link StreamTracer#outboundUncompressedSize}.
+   */
+  long getOutboundUncompressedSize();
+
+  /**
+   * Sets whether to fail on unexpected duplicate calls to callback methods.
+   */
+  void setFailDuplicateCallbacks(boolean fail);
+
+  /**
+   * Returns the next captured outbound message event.
+   */
+  @Nullable
+  String nextOutboundEvent();
+
+  /**
+   * Returns the next captured outbound message event.
+   */
+  String nextInboundEvent();
+
+  /**
+   * A {@link StreamTracer} suitable for testing.
+   */
+  public static class TestBaseStreamTracer extends StreamTracer implements TestStreamTracer {
+
+    protected final AtomicLong outboundWireSize = new AtomicLong();
+    protected final AtomicLong inboundWireSize = new AtomicLong();
+    protected final AtomicLong outboundUncompressedSize = new AtomicLong();
+    protected final AtomicLong inboundUncompressedSize = new AtomicLong();
+    protected final LinkedBlockingQueue<String> outboundEvents = new LinkedBlockingQueue<String>();
+    protected final LinkedBlockingQueue<String> inboundEvents = new LinkedBlockingQueue<String>();
+    protected final AtomicReference<Status> streamClosedStatus = new AtomicReference<Status>();
+    protected final CountDownLatch streamClosed = new CountDownLatch(1);
+    protected final AtomicBoolean failDuplicateCallbacks = new AtomicBoolean(true);
+
+    @Override
+    public void await() throws InterruptedException {
+      streamClosed.await();
+    }
+
+    @Override
+    public boolean await(long timeout, TimeUnit timeUnit) throws InterruptedException {
+      return streamClosed.await(timeout, timeUnit);
+    }
+
+    @Override
+    public Status getStatus() {
+      return streamClosedStatus.get();
+    }
+
+    @Override
+    public long getInboundWireSize() {
+      return inboundWireSize.get();
+    }
+
+    @Override
+    public long getInboundUncompressedSize() {
+      return inboundUncompressedSize.get();
+    }
+
+    @Override
+    public long getOutboundWireSize() {
+      return outboundWireSize.get();
+    }
+
+    @Override
+    public long getOutboundUncompressedSize() {
+      return outboundUncompressedSize.get();
+    }
+
+    @Override
+    public void outboundWireSize(long bytes) {
+      outboundWireSize.addAndGet(bytes);
+    }
+
+    @Override
+    public void inboundWireSize(long bytes) {
+      inboundWireSize.addAndGet(bytes);
+    }
+
+    @Override
+    public void outboundUncompressedSize(long bytes) {
+      outboundUncompressedSize.addAndGet(bytes);
+    }
+
+    @Override
+    public void inboundUncompressedSize(long bytes) {
+      inboundUncompressedSize.addAndGet(bytes);
+    }
+
+    @Override
+    public void streamClosed(Status status) {
+      if (!streamClosedStatus.compareAndSet(null, status)) {
+        if (failDuplicateCallbacks.get()) {
+          throw new AssertionError("streamClosed called more than once");
+        }
+      } else {
+        streamClosed.countDown();
+      }
+    }
+
+    @Override
+    public void inboundMessage(int seqNo) {
+      inboundEvents.add("inboundMessage(" + seqNo + ")");
+    }
+
+    @Override
+    public void outboundMessage(int seqNo) {
+      outboundEvents.add("outboundMessage(" + seqNo + ")");
+    }
+
+    @Override
+    public void outboundMessageSent(
+        int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+      outboundEvents.add(
+          String.format(
+              "outboundMessageSent(%d, %d, %d)",
+              seqNo, optionalWireSize, optionalUncompressedSize));
+    }
+
+    @Override
+    public void inboundMessageRead(
+        int seqNo, long optionalWireSize, long optionalUncompressedSize) {
+      inboundEvents.add(
+          String.format(
+              "inboundMessageRead(%d, %d, %d)", seqNo, optionalWireSize, optionalUncompressedSize));
+    }
+
+    @Override
+    public void setFailDuplicateCallbacks(boolean fail) {
+      failDuplicateCallbacks.set(fail);
+    }
+
+    @Override
+    public String nextOutboundEvent() {
+      return outboundEvents.poll();
+    }
+
+    @Override
+    public String nextInboundEvent() {
+      return inboundEvents.poll();
+    }
+  }
+}
diff --git a/testing/src/main/java/io/grpc/internal/testing/TestUtils.java b/testing/src/main/java/io/grpc/internal/testing/TestUtils.java
new file mode 100644
index 0000000..9a01b6a
--- /dev/null
+++ b/testing/src/main/java/io/grpc/internal/testing/TestUtils.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal.testing;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Internal utility functions useful for writing tests.
+ */
+public class TestUtils {
+  public static final String TEST_SERVER_HOST = "foo.test.google.fr";
+
+  /**
+   * Creates a new {@link InetSocketAddress} that overrides the host with {@link #TEST_SERVER_HOST}.
+   */
+  public static InetSocketAddress testServerAddress(String host, int port) {
+    try {
+      InetAddress inetAddress = InetAddress.getByName(host);
+      inetAddress = InetAddress.getByAddress(TEST_SERVER_HOST, inetAddress.getAddress());
+      return new InetSocketAddress(inetAddress, port);
+    } catch (UnknownHostException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Creates a new {@link InetSocketAddress} on localhost that overrides the host with
+   * {@link #TEST_SERVER_HOST}.
+   */
+  public static InetSocketAddress testServerAddress(int port) {
+    try {
+      InetAddress inetAddress = InetAddress.getByName("localhost");
+      inetAddress = InetAddress.getByAddress(TEST_SERVER_HOST, inetAddress.getAddress());
+      return new InetSocketAddress(inetAddress, port);
+    } catch (UnknownHostException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Returns the ciphers preferred to use during tests. They may be chosen because they are widely
+   * available or because they are fast. There is no requirement that they provide confidentiality
+   * or integrity.
+   */
+  public static List<String> preferredTestCiphers() {
+    String[] ciphers;
+    try {
+      ciphers = SSLContext.getDefault().getDefaultSSLParameters().getCipherSuites();
+    } catch (NoSuchAlgorithmException ex) {
+      throw new RuntimeException(ex);
+    }
+    List<String> ciphersMinusGcm = new ArrayList<>();
+    for (String cipher : ciphers) {
+      // The GCM implementation in Java is _very_ slow (~1 MB/s)
+      if (cipher.contains("_GCM_")) {
+        continue;
+      }
+      ciphersMinusGcm.add(cipher);
+    }
+    return Collections.unmodifiableList(ciphersMinusGcm);
+  }
+
+  /**
+   * Saves a file from the classpath resources in src/main/resources/certs as a file on the
+   * filesystem.
+   *
+   * @param name  name of a file in src/main/resources/certs.
+   */
+  public static File loadCert(String name) throws IOException {
+    InputStream
+        in = new BufferedInputStream(TestUtils.class.getResourceAsStream("/certs/" + name));
+    File tmpFile = File.createTempFile(name, "");
+    tmpFile.deleteOnExit();
+
+    OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpFile));
+    try {
+      int b;
+      while ((b = in.read()) != -1) {
+        os.write(b);
+      }
+      os.flush();
+    } finally {
+      in.close();
+      os.close();
+    }
+
+    return tmpFile;
+  }
+
+  /**
+   * Loads an X.509 certificate from the classpath resources in src/main/resources/certs.
+   *
+   * @param fileName  name of a file in src/main/resources/certs.
+   */
+  public static X509Certificate loadX509Cert(String fileName)
+      throws CertificateException, IOException {
+    CertificateFactory cf = CertificateFactory.getInstance("X.509");
+
+    InputStream in = TestUtils.class.getResourceAsStream("/certs/" + fileName);
+    try {
+      return (X509Certificate) cf.generateCertificate(in);
+    } finally {
+      in.close();
+    }
+  }
+
+  private static boolean conscryptInstallAttempted;
+
+  /**
+   * Add Conscrypt to the list of security providers, if it is available. If it appears to be
+   * available but fails to load, this method will throw an exception. Since the list of security
+   * providers is static, this method does nothing if the provider is not available or succeeded
+   * previously.
+   */
+  public static void installConscryptIfAvailable() {
+    if (conscryptInstallAttempted) {
+      return;
+    }
+    Class<?> conscrypt;
+    try {
+      conscrypt = Class.forName("org.conscrypt.Conscrypt");
+    } catch (ClassNotFoundException ex) {
+      conscryptInstallAttempted = true;
+      return;
+    }
+    Method newProvider;
+    try {
+      newProvider = conscrypt.getMethod("newProvider");
+    } catch (NoSuchMethodException ex) {
+      throw new RuntimeException("Could not find newProvider method on Conscrypt", ex);
+    }
+    Provider provider;
+    try {
+      provider = (Provider) newProvider.invoke(null);
+    } catch (IllegalAccessException ex) {
+      throw new RuntimeException("Could not invoke Conscrypt.newProvider", ex);
+    } catch (InvocationTargetException ex) {
+      throw new RuntimeException("Could not invoke Conscrypt.newProvider", ex);
+    }
+    Security.addProvider(provider);
+    conscryptInstallAttempted = true;
+  }
+
+  /**
+   * Creates an SSLSocketFactory which contains {@code certChainFile} as its only root certificate.
+   */
+  public static SSLSocketFactory newSslSocketFactoryForCa(Provider provider,
+                                                          File certChainFile) throws Exception {
+    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+    ks.load(null, null);
+    CertificateFactory cf = CertificateFactory.getInstance("X.509");
+    X509Certificate cert = (X509Certificate) cf.generateCertificate(
+        new BufferedInputStream(new FileInputStream(certChainFile)));
+    X500Principal principal = cert.getSubjectX500Principal();
+    ks.setCertificateEntry(principal.getName("RFC2253"), cert);
+
+    // Set up trust manager factory to use our key store.
+    TrustManagerFactory trustManagerFactory =
+        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+    trustManagerFactory.init(ks);
+    SSLContext context = SSLContext.getInstance("TLS", provider);
+    context.init(null, trustManagerFactory.getTrustManagers(), null);
+    return context.getSocketFactory();
+  }
+
+  /**
+   * Sleeps for at least the specified time. When in need of a guaranteed sleep time, use this in
+   * preference to {@code Thread.sleep} which might not sleep for the required time.
+   */
+  public static void sleepAtLeast(long millis) throws InterruptedException {
+    long delay = TimeUnit.MILLISECONDS.toNanos(millis);
+    long end = System.nanoTime() + delay;
+    while (delay > 0) {
+      TimeUnit.NANOSECONDS.sleep(delay);
+      delay = end - System.nanoTime();
+    }
+  }
+
+  private TestUtils() {}
+}
diff --git a/testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java b/testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java
new file mode 100644
index 0000000..82a986f
--- /dev/null
+++ b/testing/src/main/java/io/grpc/testing/GrpcCleanupRule.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Ticker;
+import io.grpc.ExperimentalApi;
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.MultipleFailureException;
+import org.junit.runners.model.Statement;
+
+/**
+ * A JUnit {@link TestRule} that can register gRPC resources and manages its automatic release at
+ * the end of the test. If any of the resources registered to the rule can not be successfully
+ * released, the test will fail.
+ *
+ * @since 1.13.0
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2488")
+@NotThreadSafe
+public final class GrpcCleanupRule implements TestRule {
+
+  private final List<Resource> resources = new ArrayList<>();
+  private long timeoutNanos = TimeUnit.SECONDS.toNanos(10L);
+  private Stopwatch stopwatch = Stopwatch.createUnstarted();
+
+  private Throwable firstException;
+
+  /**
+   * Sets a positive total time limit for the automatic resource cleanup. If any of the resources
+   * registered to the rule fails to be released in time, the test will fail.
+   *
+   * <p>Note that the resource cleanup duration may or may not be counted as part of the JUnit
+   * {@link org.junit.rules.Timeout Timeout} rule's test duration, depending on which rule is
+   * applied first.
+   *
+   * @return this
+   */
+  public GrpcCleanupRule setTimeout(long timeout, TimeUnit timeUnit) {
+    checkArgument(timeout > 0, "timeout should be positive");
+    timeoutNanos = timeUnit.toNanos(timeout);
+    return this;
+  }
+
+  /**
+   * Sets a specified time source for monitoring cleanup timeout.
+   *
+   * @return this
+   */
+  @SuppressWarnings("BetaApi") // Stopwatch.createUnstarted(Ticker ticker) is not Beta. Test only.
+  @VisibleForTesting
+  GrpcCleanupRule setTicker(Ticker ticker) {
+    this.stopwatch = Stopwatch.createUnstarted(ticker);
+    return this;
+  }
+
+  /**
+   * Registers the given channel to the rule. Once registered, the channel will be automatically
+   * shutdown at the end of the test.
+   *
+   * <p>This method need be properly synchronized if used in multiple threads. This method must
+   * not be used during the test teardown.
+   *
+   * @return the input channel
+   */
+  public <T extends ManagedChannel> T register(@Nonnull T channel) {
+    checkNotNull(channel, "channel");
+    register(new ManagedChannelResource(channel));
+    return channel;
+  }
+
+  /**
+   * Registers the given server to the rule. Once registered, the server will be automatically
+   * shutdown at the end of the test.
+   *
+   * <p>This method need be properly synchronized if used in multiple threads. This method must
+   * not be used during the test teardown.
+   *
+   * @return the input server
+   */
+  public <T extends Server> T register(@Nonnull T server) {
+    checkNotNull(server, "server");
+    register(new ServerResource(server));
+    return server;
+  }
+
+  @VisibleForTesting
+  void register(Resource resource) {
+    resources.add(resource);
+  }
+
+  @Override
+  public Statement apply(final Statement base, Description description) {
+    return new Statement() {
+      @Override
+      public void evaluate() throws Throwable {
+        try {
+          base.evaluate();
+        } catch (Throwable t) {
+          firstException = t;
+
+          try {
+            teardown();
+          } catch (Throwable t2) {
+            throw new MultipleFailureException(Arrays.asList(t, t2));
+          }
+
+          throw t;
+        }
+
+        teardown();
+        if (firstException != null) {
+          throw firstException;
+        }
+      }
+    };
+  }
+
+  /**
+   * Releases all the registered resources.
+   */
+  private void teardown() {
+    stopwatch.start();
+
+    if (firstException == null) {
+      for (int i = resources.size() - 1; i >= 0; i--) {
+        resources.get(i).cleanUp();
+      }
+    }
+
+    for (int i = resources.size() - 1; i >= 0; i--) {
+      if (firstException != null) {
+        resources.get(i).forceCleanUp();
+        continue;
+      }
+
+      try {
+        boolean released = resources.get(i).awaitReleased(
+            timeoutNanos - stopwatch.elapsed(TimeUnit.NANOSECONDS), TimeUnit.NANOSECONDS);
+        if (!released) {
+          firstException = new AssertionError(
+              "Resource " + resources.get(i) + " can not be released in time at the end of test");
+        }
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        firstException = e;
+      }
+
+      if (firstException != null) {
+        resources.get(i).forceCleanUp();
+      }
+    }
+
+    resources.clear();
+  }
+
+  @VisibleForTesting
+  interface Resource {
+    void cleanUp();
+
+    /**
+     * Error already happened, try the best to clean up. Never throws.
+     */
+    void forceCleanUp();
+
+    /**
+     * Returns true if the resource is released in time.
+     */
+    boolean awaitReleased(long duration, TimeUnit timeUnit) throws InterruptedException;
+  }
+
+  private static final class ManagedChannelResource implements Resource {
+    final ManagedChannel channel;
+
+    ManagedChannelResource(ManagedChannel channel) {
+      this.channel = channel;
+    }
+
+    @Override
+    public void cleanUp() {
+      channel.shutdown();
+    }
+
+    @Override
+    public void forceCleanUp() {
+      channel.shutdownNow();
+    }
+
+    @Override
+    public boolean awaitReleased(long duration, TimeUnit timeUnit) throws InterruptedException {
+      return channel.awaitTermination(duration, timeUnit);
+    }
+
+    @Override
+    public String toString() {
+      return channel.toString();
+    }
+  }
+
+  private static final class ServerResource implements Resource {
+    final Server server;
+
+    ServerResource(Server server) {
+      this.server = server;
+    }
+
+    @Override
+    public void cleanUp() {
+      server.shutdown();
+    }
+
+    @Override
+    public void forceCleanUp() {
+      server.shutdownNow();
+    }
+
+    @Override
+    public boolean awaitReleased(long duration, TimeUnit timeUnit) throws InterruptedException {
+      return server.awaitTermination(duration, timeUnit);
+    }
+
+    @Override
+    public String toString() {
+      return server.toString();
+    }
+  }
+}
diff --git a/testing/src/main/java/io/grpc/testing/GrpcServerRule.java b/testing/src/main/java/io/grpc/testing/GrpcServerRule.java
new file mode 100644
index 0000000..cc99aca
--- /dev/null
+++ b/testing/src/main/java/io/grpc/testing/GrpcServerRule.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import io.grpc.BindableService;
+import io.grpc.ExperimentalApi;
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.ServerServiceDefinition;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.stub.AbstractStub;
+import io.grpc.util.MutableHandlerRegistry;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import org.junit.rules.ExternalResource;
+import org.junit.rules.TestRule;
+
+/**
+ * {@code GrpcServerRule} is a JUnit {@link TestRule} that starts an in-process gRPC service with
+ * a {@link MutableHandlerRegistry} for adding services. It is particularly useful for mocking out
+ * external gRPC-based services and asserting that the expected requests were made.
+ *
+ * <p>An {@link AbstractStub} can be created against this service by using the
+ * {@link ManagedChannel} provided by {@link GrpcServerRule#getChannel()}.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2488")
+public final class GrpcServerRule extends ExternalResource {
+
+  private ManagedChannel channel;
+  private Server server;
+  private String serverName;
+  private MutableHandlerRegistry serviceRegistry;
+  private boolean useDirectExecutor;
+
+  /**
+   * Returns {@code this} configured to use a direct executor for the {@link ManagedChannel} and
+   * {@link Server}. This can only be called at the rule instantiation.
+   */
+  public final GrpcServerRule directExecutor() {
+    checkState(serverName == null, "directExecutor() can only be called at the rule instantiation");
+    useDirectExecutor = true;
+    return this;
+  }
+
+  /**
+   * Returns a {@link ManagedChannel} connected to this service.
+   */
+  public final ManagedChannel getChannel() {
+    return channel;
+  }
+
+  /**
+   * Returns the underlying gRPC {@link Server} for this service.
+   */
+  public final Server getServer() {
+    return server;
+  }
+
+  /**
+   * Returns the randomly generated server name for this service.
+   */
+  public final String getServerName() {
+    return serverName;
+  }
+
+  /**
+   * Returns the service registry for this service. The registry is used to add service instances
+   * (e.g. {@link BindableService} or {@link ServerServiceDefinition} to the server.
+   */
+  public final MutableHandlerRegistry getServiceRegistry() {
+    return serviceRegistry;
+  }
+
+  /**
+   * After the test has completed, clean up the channel and server.
+   */
+  @Override
+  protected void after() {
+    serverName = null;
+    serviceRegistry = null;
+
+    channel.shutdown();
+    server.shutdown();
+
+    try {
+      channel.awaitTermination(1, TimeUnit.MINUTES);
+      server.awaitTermination(1, TimeUnit.MINUTES);
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      throw new RuntimeException(e);
+    } finally {
+      channel.shutdownNow();
+      channel = null;
+
+      server.shutdownNow();
+      server = null;
+    }
+  }
+
+  /**
+   * Before the test has started, create the server and channel.
+   */
+  @Override
+  protected void before() throws Throwable {
+    serverName = UUID.randomUUID().toString();
+
+    serviceRegistry = new MutableHandlerRegistry();
+
+    InProcessServerBuilder serverBuilder = InProcessServerBuilder.forName(serverName)
+        .fallbackHandlerRegistry(serviceRegistry);
+
+    if (useDirectExecutor) {
+      serverBuilder.directExecutor();
+    }
+
+    server = serverBuilder.build().start();
+
+    InProcessChannelBuilder channelBuilder = InProcessChannelBuilder.forName(serverName);
+
+    if (useDirectExecutor) {
+      channelBuilder.directExecutor();
+    }
+
+    channel = channelBuilder.build();
+  }
+}
diff --git a/testing/src/main/java/io/grpc/testing/StreamRecorder.java b/testing/src/main/java/io/grpc/testing/StreamRecorder.java
new file mode 100644
index 0000000..ae1d9d4
--- /dev/null
+++ b/testing/src/main/java/io/grpc/testing/StreamRecorder.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import io.grpc.ExperimentalApi;
+import io.grpc.stub.StreamObserver;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+/**
+ * Utility implementation of {@link StreamObserver} used in testing. Records all the observed
+ * values produced by the stream as well as any errors.
+ *
+ * @deprecated Not for public use
+ */
+@Deprecated
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1791")
+public class StreamRecorder<T> implements StreamObserver<T> {
+
+  /**
+   * Creates a new recorder.
+   */
+  public static <T> StreamRecorder<T> create() {
+    return new StreamRecorder<T>();
+  }
+
+  private final CountDownLatch latch;
+  private final List<T> results;
+  private Throwable error;
+  private final SettableFuture<T> firstValue;
+
+  private StreamRecorder() {
+    firstValue = SettableFuture.create();
+    latch = new CountDownLatch(1);
+    results = Collections.synchronizedList(new ArrayList<T>());
+  }
+
+  @Override
+  public void onNext(T value) {
+    if (!firstValue.isDone()) {
+      firstValue.set(value);
+    }
+    results.add(value);
+  }
+
+  @Override
+  public void onError(Throwable t) {
+    if (!firstValue.isDone()) {
+      firstValue.setException(t);
+    }
+    error = t;
+    latch.countDown();
+  }
+
+  @Override
+  public void onCompleted() {
+    if (!firstValue.isDone()) {
+      firstValue.setException(new IllegalStateException("No first value provided"));
+    }
+    latch.countDown();
+  }
+
+  /**
+   * Waits for the stream to terminate.
+   */
+  public void awaitCompletion() throws Exception {
+    latch.await();
+  }
+
+  /**
+   * Waits a fixed timeout for the stream to terminate.
+   */
+  public boolean awaitCompletion(int timeout, TimeUnit unit) throws Exception {
+    return latch.await(timeout, unit);
+  }
+
+  /**
+   * Returns the current set of received values.
+   */
+  public List<T> getValues() {
+    return Collections.unmodifiableList(results);
+  }
+
+  /**
+   * Returns the stream terminating error.
+   */
+  @Nullable public Throwable getError() {
+    return error;
+  }
+
+  /**
+   * Returns a {@link ListenableFuture} for the first value received from the stream. Useful
+   * for testing unary call patterns.
+   */
+  public ListenableFuture<T> firstValue() {
+    return firstValue;
+  }
+}
diff --git a/testing/src/main/java/io/grpc/testing/TestMethodDescriptors.java b/testing/src/main/java/io/grpc/testing/TestMethodDescriptors.java
new file mode 100644
index 0000000..b83c323
--- /dev/null
+++ b/testing/src/main/java/io/grpc/testing/TestMethodDescriptors.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing;
+
+import io.grpc.ExperimentalApi;
+import io.grpc.MethodDescriptor;
+import io.grpc.MethodDescriptor.MethodType;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+/**
+ * A collection of method descriptor constructors useful for tests.  These are useful if you need
+ * a descriptor, but don't really care how it works.
+ *
+ * @since 1.1.0
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/2600")
+public final class TestMethodDescriptors {
+  private TestMethodDescriptors() {}
+
+  /**
+   * Creates a new method descriptor that always creates zero length messages, and always parses to
+   * null objects.
+   *
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2600")
+  public static MethodDescriptor<Void, Void> voidMethod() {
+    return MethodDescriptor.<Void, Void>newBuilder()
+        .setType(MethodType.UNARY)
+        .setFullMethodName(MethodDescriptor.generateFullMethodName("service_foo", "method_bar"))
+        .setRequestMarshaller(TestMethodDescriptors.voidMarshaller())
+        .setResponseMarshaller(TestMethodDescriptors.voidMarshaller())
+        .build();
+  }
+
+  /**
+   * Creates a new marshaller that does nothing.
+   *
+   * @since 1.1.0
+   */
+  @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2600")
+  public static MethodDescriptor.Marshaller<Void> voidMarshaller() {
+    return new NoopMarshaller();
+  }
+
+  private static final class NoopMarshaller implements MethodDescriptor.Marshaller<Void> {
+    @Override
+    public InputStream stream(Void value) {
+      return new ByteArrayInputStream(new byte[]{});
+    }
+
+    @Override
+    public Void parse(InputStream stream) {
+      return null;
+    }
+  }
+}
diff --git a/testing/src/main/java/io/grpc/testing/TestUtils.java b/testing/src/main/java/io/grpc/testing/TestUtils.java
new file mode 100644
index 0000000..15de4cf
--- /dev/null
+++ b/testing/src/main/java/io/grpc/testing/TestUtils.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2014 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing;
+
+import io.grpc.ExperimentalApi;
+import io.grpc.Metadata;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+import javax.security.auth.x500.X500Principal;
+
+/**
+ * Common utility functions useful for writing tests.
+ */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1791")
+public class TestUtils {
+  /**
+   * Capture the request headers from a client. Useful for testing metadata propagation.
+   */
+  public static ServerInterceptor recordRequestHeadersInterceptor(
+      final AtomicReference<Metadata> headersCapture) {
+    return new ServerInterceptor() {
+      @Override
+      public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+          ServerCall<ReqT, RespT> call,
+          Metadata requestHeaders,
+          ServerCallHandler<ReqT, RespT> next) {
+        headersCapture.set(requestHeaders);
+        return next.startCall(call, requestHeaders);
+      }
+    };
+  }
+
+  /**
+   * Returns the ciphers preferred to use during tests. They may be chosen because they are widely
+   * available or because they are fast. There is no requirement that they provide confidentiality
+   * or integrity.
+   *
+   * @deprecated Not for public use
+   */
+  @Deprecated
+  public static List<String> preferredTestCiphers() {
+    String[] ciphers;
+    try {
+      ciphers = SSLContext.getDefault().getDefaultSSLParameters().getCipherSuites();
+    } catch (NoSuchAlgorithmException ex) {
+      throw new RuntimeException(ex);
+    }
+    List<String> ciphersMinusGcm = new ArrayList<>();
+    for (String cipher : ciphers) {
+      // The GCM implementation in Java is _very_ slow (~1 MB/s)
+      if (cipher.contains("_GCM_")) {
+        continue;
+      }
+      ciphersMinusGcm.add(cipher);
+    }
+    return Collections.unmodifiableList(ciphersMinusGcm);
+  }
+
+  /**
+   * Loads an X.509 certificate from the classpath resources in src/main/resources/certs.
+   *
+   * @param fileName  name of a file in src/main/resources/certs.
+   *
+   * @deprecated Not for public use
+   */
+  @Deprecated
+  public static X509Certificate loadX509Cert(String fileName)
+      throws CertificateException, IOException {
+    CertificateFactory cf = CertificateFactory.getInstance("X.509");
+
+    InputStream in = TestUtils.class.getResourceAsStream("/certs/" + fileName);
+    try {
+      return (X509Certificate) cf.generateCertificate(in);
+    } finally {
+      in.close();
+    }
+  }
+
+  /**
+   * Creates an SSLSocketFactory which contains {@code certChainFile} as its only root certificate.
+   *
+   * @deprecated Not for public use
+   */
+  @Deprecated
+  public static SSLSocketFactory newSslSocketFactoryForCa(Provider provider,
+      File certChainFile) throws Exception {
+    KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+    ks.load(null, null);
+    CertificateFactory cf = CertificateFactory.getInstance("X.509");
+    X509Certificate cert = (X509Certificate) cf.generateCertificate(
+        new BufferedInputStream(new FileInputStream(certChainFile)));
+    X500Principal principal = cert.getSubjectX500Principal();
+    ks.setCertificateEntry(principal.getName("RFC2253"), cert);
+
+    // Set up trust manager factory to use our key store.
+    TrustManagerFactory trustManagerFactory =
+        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+    trustManagerFactory.init(ks);
+    SSLContext context = SSLContext.getInstance("TLS", provider);
+    context.init(null, trustManagerFactory.getTrustManagers(), null);
+    return context.getSocketFactory();
+  }
+
+  private TestUtils() {}
+}
diff --git a/testing/src/main/java/io/grpc/testing/TlsTesting.java b/testing/src/main/java/io/grpc/testing/TlsTesting.java
new file mode 100644
index 0000000..3f88fd0
--- /dev/null
+++ b/testing/src/main/java/io/grpc/testing/TlsTesting.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing;
+
+import io.grpc.ExperimentalApi;
+import java.io.InputStream;
+
+/** Convenience utilities for using TLS in tests. */
+@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1791")
+public final class TlsTesting {
+  /**
+   * Retrieves the specified test certificate or key resource in src/main/resources/certs/ as an
+   * {@code InputStream}.
+   *
+   * @param name name of a file in src/main/resources/certs/, e.g., {@code "ca.key"}.
+   *
+   * @since 1.8.0
+   */
+  public static InputStream loadCert(String name) {
+    return TestUtils.class.getResourceAsStream("/certs/" + name);
+  }
+
+  private TlsTesting() {}
+}
diff --git a/testing/src/main/java/io/grpc/testing/package-info.java b/testing/src/main/java/io/grpc/testing/package-info.java
new file mode 100644
index 0000000..7de3e3b
--- /dev/null
+++ b/testing/src/main/java/io/grpc/testing/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2015 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+/**
+ * API that is useful for testing gRPC.
+ */
+package io.grpc.testing;
diff --git a/testing/src/main/resources/certs/README b/testing/src/main/resources/certs/README
new file mode 100644
index 0000000..e6d411a
--- /dev/null
+++ b/testing/src/main/resources/certs/README
@@ -0,0 +1,72 @@
+The test credentials (CONFIRMEDTESTKEY) have been generated with the following
+commands:
+
+Bad credentials (badclient.* / badserver.*):
+============================================
+
+These are self-signed certificates:
+
+$ openssl req -x509 -newkey rsa:1024 -keyout badserver.key -out badserver.pem \
+  -days 3650 -nodes
+
+When prompted for certificate information, everything is default except the
+common name which is set to badserver.test.google.com.
+
+
+Valid test credentials:
+=======================
+
+The ca is self-signed:
+----------------------
+
+$ openssl req -x509 -new -newkey rsa:1024 -nodes -out ca.pem -config ca-openssl.cnf -days 3650 -extensions v3_req
+When prompted for certificate information, everything is default.
+
+client is issued by CA:
+-----------------------
+
+$ openssl genrsa -out client.key.rsa 1024
+$ openssl pkcs8 -topk8 -in client.key.rsa -out client.key -nocrypt
+$ rm client.key.rsa
+$ openssl req -new -key client.key -out client.csr
+
+When prompted for certificate information, everything is default except the
+common name which is set to testclient.
+
+$ openssl ca -in client.csr -out client.pem -keyfile ca.key -cert ca.pem -verbose -config openssl.cnf -days 3650 -updatedb
+$ openssl x509 -in client.pem -out client.pem -outform PEM
+
+server0 is issued by CA:
+------------------------
+
+$ openssl genrsa -out server0.key.rsa 1024
+$ openssl pkcs8 -topk8 -in server0.key.rsa -out server0.key -nocrypt
+$ rm server0.key.rsa
+$ openssl req -new -key server0.key -out server0.csr
+
+When prompted for certificate information, everything is default except the
+common name which is set to *.test.google.com.au.
+
+$ openssl ca -in server0.csr -out server0.pem -keyfile ca.key -cert ca.pem -verbose -config openssl.cnf -days 3650 -updatedb
+$ openssl x509 -in server0.pem -out server0.pem -outform PEM
+
+server1 is issued by CA with a special config for subject alternative names:
+----------------------------------------------------------------------------
+
+$ openssl genrsa -out server1.key.rsa 1024
+$ openssl pkcs8 -topk8 -in server1.key.rsa -out server1.key -nocrypt
+$ rm server1.key.rsa
+$ openssl req -new -key server1.key -out server1.csr -config server1-openssl.cnf
+
+When prompted for certificate information, everything is default except the
+common name which is set to *.test.google.com.
+
+$ openssl ca -in server1.csr -out server1.pem -keyfile ca.key -cert ca.pem -verbose -config server1-openssl.cnf -days 3650 -extensions v3_req -updatedb
+$ openssl x509 -in server1.pem -out server1.pem -outform PEM
+
+Gotchas
+=======
+
+You may have to delete and recreate the index.txt file so that it is empty when
+running the `openssl ca` command.
+
diff --git a/testing/src/main/resources/certs/badclient.key b/testing/src/main/resources/certs/badclient.key
new file mode 100644
index 0000000..5832685
--- /dev/null
+++ b/testing/src/main/resources/certs/badclient.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALJfYnFn4nkj52WF
+E5W2qUxCfjsEFyuXYYKS/07UPWsv3gpZhtjXgdeGL+dpwEBC0IRDBfGnkMp6YY5S
+O7rnEz0X3r/fvgYy+dEl2jnaA6zgc7RzMGl9U11d56gP9FiDC2190mvP/hpq2xLZ
+CTbIximpmaoQyxuuH1bbYunesIG/AgMBAAECgYAdqJCEzMIyZE7oaW0tOpcB0BiP
+FYoIvH4BKRH8eHvR476mt+YdDhBP1scGUmYeCT4Ej+RgHv2LPTgVYwT9eciP2+E/
+CBCNRel0Sw9JepwW0r+jWJtDY1pp6YXAgNRGX2UflvUsT+o9lZvagf9moLTMyGvU
+uLFnsyfLim1B4vXvWQJBANouZllXGZoSrZLtR3VgV4tzRQvJxu84kLeIk64Ov47X
+pHVBMTRBfzPEhbBodjr1m5OLaVLqkFcXftzRCrbWoKsCQQDRSoLLXOiLrtJ3DLJC
+rX7Y8wrHZrqk5bMdZLGa/UX8RanhVw3+Xp+urd1711umeNJfzu/MCk4a1KkG/CU0
+rqs9AkA4cSx1DD1JSG+yxMNpsAS1xJomFIrsM9vsPt7FdndDwrF+y+CovhDkGYDk
+RAHh+svGfZg/pQK2JRPimAmHhzqFAkEAu6Ya70s2FUeB3Mu9aJs2CD6hg3dQEVkB
+53DI7TX48d9kGW58VX1xnqS02LyWqAPcW5qm1kLHFLdndaPNmBaj4QJBAJugl367
+9d9t/QLTSuULLaoYv2vJT3s1y9HN89EoaDDEkPVfQu6GVEXgIBtim1sI/VPSzI8H
+aXvaTUwblFWSM70=
+-----END PRIVATE KEY-----
diff --git a/testing/src/main/resources/certs/badclient.pem b/testing/src/main/resources/certs/badclient.pem
new file mode 100644
index 0000000..1785970
--- /dev/null
+++ b/testing/src/main/resources/certs/badclient.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICoDCCAgmgAwIBAgIJANIz2/zoRiapMA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxIjAgBgNVBAMMGWJhZGNsaWVudC50ZXN0Lmdvb2dsZS5j
+b20wHhcNMTQwNzI4MjAwODI1WhcNMjQwNzI1MjAwODI1WjBpMQswCQYDVQQGEwJB
+VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMSIwIAYDVQQDDBliYWRjbGllbnQudGVzdC5nb29nbGUuY29tMIGf
+MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyX2JxZ+J5I+dlhROVtqlMQn47BBcr
+l2GCkv9O1D1rL94KWYbY14HXhi/nacBAQtCEQwXxp5DKemGOUju65xM9F96/374G
+MvnRJdo52gOs4HO0czBpfVNdXeeoD/RYgwttfdJrz/4aatsS2Qk2yMYpqZmqEMsb
+rh9W22Lp3rCBvwIDAQABo1AwTjAdBgNVHQ4EFgQU523AJMR8Ds9V8fhf7gu1i0MM
+UqAwHwYDVR0jBBgwFoAU523AJMR8Ds9V8fhf7gu1i0MMUqAwDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQUFAAOBgQCI/tvSBYH1iyfLaCTBKwpdj36+MkR9EeJJmImx
+X+bjhKWXwsBX4PDMWvdusr++QGUYtyoya+hfYMXRhXua39mD54xgloQNuu9REDwX
+Ffto+aOw3BcYducz6ofxicFK/Y2VeXDurSMpRv5TfGf2Qr6eOOdaRhj6ed7BibHk
+X1VGZA==
+-----END CERTIFICATE-----
diff --git a/testing/src/main/resources/certs/badserver.key b/testing/src/main/resources/certs/badserver.key
new file mode 100644
index 0000000..abfbde1
--- /dev/null
+++ b/testing/src/main/resources/certs/badserver.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKeZ1e1y29cmBKaW
+oIUwJ5neOJUjx+eD/3nRPe+dvLXEd9+db0fG5RYRR0S3mF1Ywuj4PIxlTW2YprUS
+oGSw+tcqWNIzxv94HjwYFkkvER3AblXcDBh0P2zAkzg+nf9AcAsMh0QpDTyrXtMl
+gqryjq1/vkhFofKMMbY+aXJdG6OBAgMBAAECgYAAgaB51S0A22aMMkxN2rVj6530
+JWWHN4jgD1fGj41wZyWNkWYyq1Ep3ed/N6bIMWp1VbqpGe0/9YQba/D8HOTFHGRt
+72YXnP1e/ds8cxU4x4j1vvqSPtXpMmkiXfXijOvCl9mrMH2xjghFAt6/1Nb9xo1m
+VdcOB8OdSuOIw6CI+QJBAN5FZUbS+bRXDWII/FaAih1DBpwCxhYEN+TXPJBxSen6
+kOzGt5g+mB6YqRMZ/qshshwPq7bsgFGfJ2lIdS2t3GsCQQDBCKifV5AAkOdOUrkK
+HvoX3qnVmyIA8CyvWLcIWpfZ76QAYh0q0StedKdOMXaB1jTeSJ2KU1nlss7UD1Yw
+VbrDAkAwjMHpbW3jiVw//Kx5jIwehiRscWKpLnSzBJyTBFvbwsJjJai2lX2OuVO8
++2GYKb0Iyhd81j3VFkl6grwtpRtPAkB7+n+yt555fpfRKjhGU9b09cHGu7h/OcK5
+bBVCfE0DYHLI/DsXgPiF1g6Onh4rDdUu3xyv9xDKAqnscV099hHZAkEAvcFBfXZs
+tk18N+bUcvXTdZjzZbfLCHlJmwPIspZ8G/6Pn63deg4GVYoCvTwGruah+8y734Ph
+7PskfPgUQlB7Ag==
+-----END PRIVATE KEY-----
diff --git a/testing/src/main/resources/certs/badserver.pem b/testing/src/main/resources/certs/badserver.pem
new file mode 100644
index 0000000..983c979
--- /dev/null
+++ b/testing/src/main/resources/certs/badserver.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICoDCCAgmgAwIBAgIJAPdqwqsKNy81MA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxIjAgBgNVBAMMGWJhZHNlcnZlci50ZXN0Lmdvb2dsZS5j
+b20wHhcNMTQwNzI4MjAwODU0WhcNMjQwNzI1MjAwODU0WjBpMQswCQYDVQQGEwJB
+VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0
+cyBQdHkgTHRkMSIwIAYDVQQDDBliYWRzZXJ2ZXIudGVzdC5nb29nbGUuY29tMIGf
+MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnmdXtctvXJgSmlqCFMCeZ3jiVI8fn
+g/950T3vnby1xHffnW9HxuUWEUdEt5hdWMLo+DyMZU1tmKa1EqBksPrXKljSM8b/
+eB48GBZJLxEdwG5V3AwYdD9swJM4Pp3/QHALDIdEKQ08q17TJYKq8o6tf75IRaHy
+jDG2PmlyXRujgQIDAQABo1AwTjAdBgNVHQ4EFgQU3u/qvHr9knMBeZyAD7mAA/ec
+8cUwHwYDVR0jBBgwFoAU3u/qvHr9knMBeZyAD7mAA/ec8cUwDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQUFAAOBgQA/FmR1SGLguxCCfhp4CYCbrAePSyPWDi48gTwj
+vVZf/OMxdVu/H8sBYFf27BjbrEugAw16DElFtgTZ83pLb2BvkUgb6vBUK5sEkgmh
+z88zBsgDp8aCf4STDOLFZMBh/E9ZKkm1zogbEmlTjFp/ceSpa2gNv7OuN4WiorOh
+Wvw40g==
+-----END CERTIFICATE-----
diff --git a/testing/src/main/resources/certs/ca-openssl.cnf b/testing/src/main/resources/certs/ca-openssl.cnf
new file mode 100644
index 0000000..e15866b
--- /dev/null
+++ b/testing/src/main/resources/certs/ca-openssl.cnf
@@ -0,0 +1,18 @@
+[req]
+distinguished_name  = req_distinguished_name
+req_extensions = v3_req
+
+[req_distinguished_name]
+countryName           = Country Name (2 letter code)
+countryName_default = AU
+stateOrProvinceName   = State or Province Name (full name)
+stateOrProvinceName_default = Some-State
+organizationName          = Organization Name (eg, company)
+organizationName_default = Internet Widgits Pty Ltd
+commonName            = Common Name (eg, YOUR name)
+commonName_default = testca
+
+[v3_req]
+basicConstraints = CA:true
+keyUsage = critical, keyCertSign
+
diff --git a/testing/src/main/resources/certs/ca.key b/testing/src/main/resources/certs/ca.key
new file mode 100644
index 0000000..03c4f95
--- /dev/null
+++ b/testing/src/main/resources/certs/ca.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMBA3wVeTGHZR1Ry
+e/i+J8a2cu5gXwFV6TnObzGM7bLFCO5i9v4mLo4iFzPsHmWDUxKS3Y8iXbu0eYBl
+LoNY0lSvxDx33O+DuwMmVN+DzSD+Eod9zfvwOWHsazYCZT2PhNxnVWIuJXViY4JA
+HUGodjx+QAi6yCAurUZGvYXGgZSBAgMBAAECgYAxRi8i9BlFlufGSBVoGmydbJOm
+bwLKl9dP3o33ODSP9hok5y6A0w5plWk3AJSF1hPLleK9VcSKYGYnt0clmPVHF35g
+bx2rVK8dOT0mn7rz9Zr70jcSz1ETA2QonHZ+Y+niLmcic9At6hRtWiewblUmyFQm
+GwggIzi7LOyEUHrEcQJBAOXxyQvnLvtKzXiqcsW/K6rExqVJVk+KF0fzzVyMzTJx
+HRBxUVgvGdEJT7j+7P2kcTyafve0BBzDSPIaDyiJ+Y0CQQDWCb7jASFSbu5M3Zcd
+Gkr4ZKN1XO3VLQX10b22bQYdF45hrTN2tnzRvVUR4q86VVnXmiGiTqmLkXcA2WWf
+pHfFAkAhv9olUBo6MeF0i3frBEMRfm41hk0PwZHnMqZ6pgPcGnQMnMU2rzsXzkkQ
+OwJnvAIOxhJKovZTjmofdqmw5odlAkBYVUdRWjsNUTjJwj3GRf6gyq/nFMYWz3EB
+RWFdM1ttkDYzu45ctO2IhfHg4sPceDMO1s6AtKQmNI9/azkUjITdAkApNa9yFRzc
+TBaDNPd5KVd58LVIzoPQ6i7uMHteLXJUWqSroji6S3s4gKMFJ/dO+ZXIlgQgfJJJ
+ZDL4cdrdkeoM
+-----END PRIVATE KEY-----
diff --git a/testing/src/main/resources/certs/ca.pem b/testing/src/main/resources/certs/ca.pem
new file mode 100644
index 0000000..6c8511a
--- /dev/null
+++ b/testing/src/main/resources/certs/ca.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla
+Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0
+YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT
+BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7
++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu
+g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd
+Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau
+sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m
+oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG
+Dfcog5wrJytaQ6UA0wE=
+-----END CERTIFICATE-----
diff --git a/testing/src/main/resources/certs/client.key b/testing/src/main/resources/certs/client.key
new file mode 100644
index 0000000..f48d073
--- /dev/null
+++ b/testing/src/main/resources/certs/client.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICeQIBADANBgkqhkiG9w0BAQEFAASCAmMwggJfAgEAAoGBAOxUR9uhvhbeVUIM
+s5WbH0px0mehl2+6sZpNjzvE2KimZpHzMJHukVH0Ffkvhs0b8+S5Ut9VNUAqd3IM
+JCCAEGtRNoQhM1t9Yr2zAckSvbRacp+FL/Cj9eDmyo00KsVGaeefA4Dh4OW+ZhkT
+NKcldXqkSuj1sEf244JZYuqZp6/tAgMBAAECgYEAi2NSVqpZMafE5YYUTcMGe6QS
+k2jtpsqYgggI2RnLJ/2tNZwYI5pwP8QVSbnMaiF4gokD5hGdrNDfTnb2v+yIwYEH
+0w8+oG7Z81KodsiZSIDJfTGsAZhVNwOz9y0VD8BBZZ1/274Zh52AUKLjZS/ZwIbS
+W2ywya855dPnH/wj+0ECQQD9X8D920kByTNHhBG18biAEZ4pxs9f0OAG8333eVcI
+w2lJDLsYDZrCB2ocgA3lUdozlzPC7YDYw8reg0tkiRY5AkEA7sdNzOeQsQRn7++5
+0bP9DtT/iON1gbfxRzCfCfXdoOtfQWIzTePWtURt9X/5D9NofI0Rg5W2oGy/MLe5
+/sXHVQJBAIup5XrJDkQywNZyAUU2ecn2bCWBFjwtqd+LBmuMciI9fOKsZtEKZrz/
+U0lkeMRoSwvXE8wmGLjjrAbdfohrXFkCQQDZEx/LtIl6JINJQiswVe0tWr6k+ASP
+1WXoTm+HYpoF/XUvv9LccNF1IazFj34hwRQwhx7w/V52Ieb+p0jUMYGxAkEAjDhd
+9pBO1fKXWiXzi9ZKfoyTNcUq3eBSVKwPG2nItg5ycXengjT5sgcWDnciIzW7BIVI
+JiqOszq9GWESErAatg==
+-----END PRIVATE KEY-----
diff --git a/testing/src/main/resources/certs/client.pem b/testing/src/main/resources/certs/client.pem
new file mode 100644
index 0000000..913649b
--- /dev/null
+++ b/testing/src/main/resources/certs/client.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC6TCCAlKgAwIBAgIBCjANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET
+MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTEwMDEwOTU4WhcNMjUxMTA3
+MDEwOTU4WjBaMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8G
+A1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRMwEQYDVQQDDAp0ZXN0Y2xp
+ZW50MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDsVEfbob4W3lVCDLOVmx9K
+cdJnoZdvurGaTY87xNiopmaR8zCR7pFR9BX5L4bNG/PkuVLfVTVAKndyDCQggBBr
+UTaEITNbfWK9swHJEr20WnKfhS/wo/Xg5sqNNCrFRmnnnwOA4eDlvmYZEzSnJXV6
+pEro9bBH9uOCWWLqmaev7QIDAQABo4HCMIG/MAkGA1UdEwQCMAAwCwYDVR0PBAQD
+AgXgMB0GA1UdDgQWBBQAdbW5Vml/CnYwqdP3mOHDARU+8zBwBgNVHSMEaTBnoVqk
+WDBWMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMY
+SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2GCCQCRxhke
+HRoqBzAJBgNVHREEAjAAMAkGA1UdEgQCMAAwDQYJKoZIhvcNAQELBQADgYEAf4MM
+k+sdzd720DfrQ0PF2gDauR3M9uBubozDuMuF6ufAuQBJSKGQEGibXbUelrwHmnql
+UjTyfolVcxEBVaF4VFHmn7u6vP7S1NexIDdNUHcULqxIb7Tzl8JYq8OOHD2rQy4H
+s8BXaVIzw4YcaCGAMS0iDX052Sy7e2JhP8Noxvo=
+-----END CERTIFICATE-----
diff --git a/testing/src/main/resources/certs/index.txt b/testing/src/main/resources/certs/index.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/testing/src/main/resources/certs/index.txt
diff --git a/testing/src/main/resources/certs/openssl.cnf b/testing/src/main/resources/certs/openssl.cnf
new file mode 100644
index 0000000..6e2b54d
--- /dev/null
+++ b/testing/src/main/resources/certs/openssl.cnf
@@ -0,0 +1,359 @@
+#
+# OpenSSL example configuration file.
+# This is mostly being used for generation of certificate requests.
+#
+
+# This definition stops the following lines choking if HOME isn't
+# defined.
+HOME			= .
+RANDFILE		= $ENV::HOME/.rnd
+
+# Extra OBJECT IDENTIFIER info:
+#oid_file		= $ENV::HOME/.oid
+oid_section		= new_oids
+
+# To use this configuration file with the "-extfile" option of the
+# "openssl x509" utility, name here the section containing the
+# X.509v3 extensions to use:
+# extensions		=
+# (Alternatively, use a configuration file that has only
+# X.509v3 extensions in its main [= default] section.)
+
+[ new_oids ]
+
+# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
+# Add a simple OID like this:
+# testoid1=1.2.3.4
+# Or use config file substitution like this:
+# testoid2=${testoid1}.5.6
+
+# Policies used by the TSA examples.
+tsa_policy1 = 1.2.3.4.1
+tsa_policy2 = 1.2.3.4.5.6
+tsa_policy3 = 1.2.3.4.5.7
+
+####################################################################
+[ ca ]
+default_ca	= CA_default		# The default ca section
+
+####################################################################
+[ CA_default ]
+
+dir		= . # Where everything is kept
+certs		= $dir # Where the issued certs are kept
+crl_dir		= $dir		# Where the issued crl are kept
+database	= $dir/index.txt	# database index file.
+#unique_subject	= no			# Set to 'no' to allow creation of
+					# several ctificates with same subject.
+new_certs_dir	= $dir		# default place for new certs.
+
+certificate	= $dir/ca.pem 	# The CA certificate
+serial		= $dir/serial 		# The current serial number
+crlnumber	= $dir/crlnumber	# the current crl number
+					# must be commented out to leave a V1 CRL
+crl		= $dir/crl.pem 		# The current CRL
+private_key	= $dir/private/cakey.pem# The private key
+RANDFILE	= $dir/private/.rand	# private random number file
+
+x509_extensions	= usr_cert		# The extentions to add to the cert
+
+# Comment out the following two lines for the "traditional"
+# (and highly broken) format.
+name_opt 	= ca_default		# Subject Name options
+cert_opt 	= ca_default		# Certificate field options
+
+# Extension copying option: use with caution.
+# copy_extensions = copy
+
+# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
+# so this is commented out by default to leave a V1 CRL.
+# crlnumber must also be commented out to leave a V1 CRL.
+# crl_extensions	= crl_ext
+
+default_days	= 365			# how long to certify for
+default_crl_days= 30			# how long before next CRL
+default_md	= default		# use public key default MD
+preserve	= no			# keep passed DN ordering
+
+# A few difference way of specifying how similar the request should look
+# For type CA, the listed attributes must be the same, and the optional
+# and supplied fields are just that :-)
+policy		= policy_anything
+
+# For the CA policy
+[ policy_match ]
+countryName		= match
+stateOrProvinceName	= match
+organizationName	= match
+organizationalUnitName	= optional
+commonName		= supplied
+emailAddress		= optional
+
+# For the 'anything' policy
+# At this point in time, you must list all acceptable 'object'
+# types.
+[ policy_anything ]
+countryName		= optional
+stateOrProvinceName	= optional
+localityName		= optional
+organizationName	= optional
+organizationalUnitName	= optional
+commonName		= supplied
+emailAddress		= optional
+
+####################################################################
+[ req ]
+default_bits		= 2048
+default_keyfile 	= privkey.pem
+distinguished_name	= req_distinguished_name
+attributes		= req_attributes
+x509_extensions	= v3_ca	# The extentions to add to the self signed cert
+
+# Passwords for private keys if not present they will be prompted for
+# input_password = secret
+# output_password = secret
+
+# This sets a mask for permitted string types. There are several options.
+# default: PrintableString, T61String, BMPString.
+# pkix	 : PrintableString, BMPString (PKIX recommendation before 2004)
+# utf8only: only UTF8Strings (PKIX recommendation after 2004).
+# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
+# MASK:XXXX a literal mask value.
+# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
+string_mask = utf8only
+
+# req_extensions = v3_req # The extensions to add to a certificate request
+
+[ req_distinguished_name ]
+countryName			= Country Name (2 letter code)
+countryName_default		= AU
+countryName_min			= 2
+countryName_max			= 2
+
+stateOrProvinceName		= State or Province Name (full name)
+stateOrProvinceName_default	= Some-State
+
+localityName			= Locality Name (eg, city)
+
+0.organizationName		= Organization Name (eg, company)
+0.organizationName_default	= Internet Widgits Pty Ltd
+
+# we can do this but it is not needed normally :-)
+#1.organizationName		= Second Organization Name (eg, company)
+#1.organizationName_default	= World Wide Web Pty Ltd
+
+organizationalUnitName		= Organizational Unit Name (eg, section)
+#organizationalUnitName_default	=
+
+commonName			= Common Name (e.g. server FQDN or YOUR name)
+commonName_max			= 64
+
+emailAddress			= Email Address
+emailAddress_max		= 64
+
+# SET-ex3			= SET extension number 3
+
+[ req_attributes ]
+challengePassword		= A challenge password
+challengePassword_min		= 4
+challengePassword_max		= 20
+
+unstructuredName		= An optional company name
+
+[ usr_cert ]
+
+# These extensions are added when 'ca' signs a request.
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType			= server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+#nsComment			= "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+issuerAltName=issuer:copy
+
+#nsCaRevocationUrl		= http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This is required for TSA certificates.
+# extendedKeyUsage = critical,timeStamping
+
+[ v3_req ]
+
+# Extensions to add to a certificate request
+
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectAltName = @alt_names
+
+[ v3_ca ]
+
+
+# Extensions for a typical CA
+
+
+# PKIX recommendation.
+
+subjectKeyIdentifier=hash
+
+authorityKeyIdentifier=keyid:always,issuer
+
+# This is what PKIX recommends but some broken software chokes on critical
+# extensions.
+#basicConstraints = critical,CA:true
+# So we do this instead.
+basicConstraints = CA:true
+
+# Key usage: this is typical for a CA certificate. However since it will
+# prevent it being used as an test self-signed certificate it is best
+# left out by default.
+# keyUsage = cRLSign, keyCertSign
+
+# Some might want this also
+# nsCertType = sslCA, emailCA
+
+# Include email address in subject alt name: another PKIX recommendation
+# subjectAltName=email:copy
+# Copy issuer details
+# issuerAltName=issuer:copy
+subjectAltName = @alt_names
+
+# DER hex encoding of an extension: beware experts only!
+# obj=DER:02:03
+# Where 'obj' is a standard or added object
+# You can even override a supported extension:
+# basicConstraints= critical, DER:30:03:01:01:FF
+
+[ crl_ext ]
+
+# CRL extensions.
+# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
+
+# issuerAltName=issuer:copy
+authorityKeyIdentifier=keyid:always
+
+[ proxy_cert_ext ]
+# These extensions should be added when creating a proxy certificate
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# Here are some examples of the usage of nsCertType. If it is omitted
+# the certificate can be used for anything *except* object signing.
+
+# This is OK for an SSL server.
+# nsCertType			= server
+
+# For an object signing certificate this would be used.
+# nsCertType = objsign
+
+# For normal client use this is typical
+# nsCertType = client, email
+
+# and for everything including object signing:
+# nsCertType = client, email, objsign
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# This will be displayed in Netscape's comment listbox.
+nsComment			= "OpenSSL Generated Certificate"
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+#nsCaRevocationUrl		= http://www.domain.dom/ca-crl.pem
+#nsBaseUrl
+#nsRevocationUrl
+#nsRenewalUrl
+#nsCaPolicyUrl
+#nsSslServerName
+
+# This really needs to be in place for it to be a proxy certificate.
+proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
+
+####################################################################
+[ tsa ]
+
+default_tsa = tsa_config1	# the default TSA section
+
+[ tsa_config1 ]
+
+# These are used by the TSA reply generation only.
+dir		= ./demoCA		# TSA root directory
+serial		= $dir/tsaserial	# The current serial number (mandatory)
+crypto_device	= builtin		# OpenSSL engine to use for signing
+signer_cert	= $dir/tsacert.pem 	# The TSA signing certificate
+					# (optional)
+certs		= $dir/cacert.pem	# Certificate chain to include in reply
+					# (optional)
+signer_key	= $dir/private/tsakey.pem # The TSA private key (optional)
+
+default_policy	= tsa_policy1		# Policy if request did not specify it
+					# (optional)
+other_policies	= tsa_policy2, tsa_policy3	# acceptable policies (optional)
+digests		= md5, sha1		# Acceptable message digests (mandatory)
+accuracy	= secs:1, millisecs:500, microsecs:100	# (optional)
+clock_precision_digits  = 0	# number of digits after dot. (optional)
+ordering		= yes	# Is ordering defined for timestamps?
+				# (optional, default: no)
+tsa_name		= yes	# Must the TSA name be included in the reply?
+				# (optional, default: no)
+ess_cert_id_chain	= no	# Must the ESS cert id chain be included?
+				# (optional, default: no)
+
+[alt_names]
+DNS.1 = *.test.google.fr
+DNS.2 = waterzooi.test.google.be
+DNS.3 = *.test.youtube.com
+IP.1 = "192.168.1.3"
+
diff --git a/testing/src/main/resources/certs/server0.key b/testing/src/main/resources/certs/server0.key
new file mode 100644
index 0000000..add153c
--- /dev/null
+++ b/testing/src/main/resources/certs/server0.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANOmffupIGC8YDau
+rOF4eKnHwPszgpkkhWzKsVxhNDBxCVYx4TEjG0XWIO0iyRXupZbUC+7N/8HnEVNa
+8F1jYhng14Iiq99cNQbbnuHHhIztmpocrJTxmnhGzoAnRa1Tb+GnAuRoIHRA/V2c
+VUE9tbikQugFx/SPgXAw6tfWB+YvAgMBAAECgYEAoEq9qzUBgoHoVEGiSPiWWe8g
+5p6yUA1qx2QTQyWTAwT4z0DjjfVKmG99bFsl8+hTnJFnoCp/gnjflEOROwkjp5kG
+m0drqOPx1jeipJjpXYTBu49h+WpZ1PF+KhVtxsIm3OOCvh67iWaKyyOVb5Og8aiR
+jl6dn/TdG/dlGD8AfUECQQDuNMle6p0oU8amC6O9wIMBroxx2nFstzE6O35PLEzG
+/tj0kxxn9Jp2TS9mGaLCzSuXmpjlF4+NOWiBPkrLC2TfAkEA43Xg7uEUkaJAz2/W
+m1lIBTLt+4rIQY/2emh33bDcA+rv8rwwrMMIv17/xPx7bs49YqGG5xufD+Rwl6TL
+qFXYsQJAPrOwagax1aKvwJeBw3oAQhoTKAkLIEXcdGqipe6QSzVcIIz0xjxxyEAr
+AOIwoLxnBCISqwMXq2H4K0UdZPMb2wJAdhdYLY1L6YRMk6XjzImg25oidisKZweA
+FvMv8DgHMj2CUAqmVrt3SivfLH1M9C09L3zfFhOAFHcsgX58gav4MQJBANSBnrHj
+tIq4l8z79CPUIuu3QyeEh+XwY8s5qE5CNTck0U59lzp9NvENHbkx3KO896TTerko
++8bXHMLkJkHPXms=
+-----END PRIVATE KEY-----
diff --git a/testing/src/main/resources/certs/server0.pem b/testing/src/main/resources/certs/server0.pem
new file mode 100644
index 0000000..9458954
--- /dev/null
+++ b/testing/src/main/resources/certs/server0.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8zCCAlygAwIBAgIBCzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET
+MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTEwMDExNDU1WhcNMjUxMTA3
+MDExNDU1WjBkMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8G
+A1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMR0wGwYDVQQDDBQqLnRlc3Qu
+Z29vZ2xlLmNvbS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA06Z9+6kg
+YLxgNq6s4Xh4qcfA+zOCmSSFbMqxXGE0MHEJVjHhMSMbRdYg7SLJFe6lltQL7s3/
+wecRU1rwXWNiGeDXgiKr31w1Btue4ceEjO2amhyslPGaeEbOgCdFrVNv4acC5Ggg
+dED9XZxVQT21uKRC6AXH9I+BcDDq19YH5i8CAwEAAaOBwjCBvzAJBgNVHRMEAjAA
+MAsGA1UdDwQEAwIF4DAdBgNVHQ4EFgQUbyZIbUvqmePzv40xa0mMaDxLToYwcAYD
+VR0jBGkwZ6FapFgwVjELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
+ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEPMA0GA1UEAxMGdGVz
+dGNhggkAkcYZHh0aKgcwCQYDVR0RBAIwADAJBgNVHRIEAjAAMA0GCSqGSIb3DQEB
+CwUAA4GBAJ21MwMf4WwAjafPKn+8Ng7ordtdp6tlkjt+Xub4l4zMr6FCp6dc/Ceh
+6Hj43zYcKpAe5I6eaVcMc9qcYfUb9i4NVX82dMQpAwpNHgqTzqYt6GYEjF3YhKA7
+uOFdA0OvOFJa14SNdNRk9E1Cd/tElXnLnSE4DOguMNvXz8mRKfnD
+-----END CERTIFICATE-----
diff --git a/testing/src/main/resources/certs/server1-openssl.cnf b/testing/src/main/resources/certs/server1-openssl.cnf
new file mode 100644
index 0000000..7c6081a
--- /dev/null
+++ b/testing/src/main/resources/certs/server1-openssl.cnf
@@ -0,0 +1,82 @@
+[req]
+distinguished_name  = req_distinguished_name
+req_extensions     = v3_req
+
+[req_distinguished_name]
+countryName           = Country Name (2 letter code)
+countryName_default   = US
+stateOrProvinceName   = State or Province Name (full name)
+stateOrProvinceName_default = Illinois
+localityName          = Locality Name (eg, city)
+localityName_default  = Chicago
+organizationName          = Organization Name (eg, company)
+organizationName_default  = Example, Co.
+commonName            = Common Name (eg, YOUR name)
+commonName_max        = 64
+
+####################################################################
+[ ca ]
+default_ca	= CA_default		# The default ca section
+
+####################################################################
+[ CA_default ]
+
+dir		= . # Where everything is kept
+certs		= $dir # Where the issued certs are kept
+crl_dir		= $dir		# Where the issued crl are kept
+database	= $dir/index.txt	# database index file.
+#unique_subject	= no			# Set to 'no' to allow creation of
+					# several ctificates with same subject.
+new_certs_dir	= $dir		# default place for new certs.
+
+certificate	= $dir/ca.pem 	# The CA certificate
+serial		= $dir/serial 		# The current serial number
+crlnumber	= $dir/crlnumber	# the current crl number
+					# must be commented out to leave a V1 CRL
+crl		= $dir/crl.pem 		# The current CRL
+private_key	= $dir/private/cakey.pem# The private key
+RANDFILE	= $dir/private/.rand	# private random number file
+
+x509_extensions	= usr_cert		# The extentions to add to the cert
+
+# Comment out the following two lines for the "traditional"
+# (and highly broken) format.
+name_opt 	= ca_default		# Subject Name options
+cert_opt 	= ca_default		# Certificate field options
+
+# Extension copying option: use with caution.
+# copy_extensions = copy
+
+# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
+# so this is commented out by default to leave a V1 CRL.
+# crlnumber must also be commented out to leave a V1 CRL.
+# crl_extensions	= crl_ext
+
+default_days	= 365			# how long to certify for
+default_crl_days= 30			# how long before next CRL
+default_md	= default		# use public key default MD
+preserve	= no			# keep passed DN ordering
+
+# A few difference way of specifying how similar the request should look
+# For type CA, the listed attributes must be the same, and the optional
+# and supplied fields are just that :-)
+policy		= policy_anything
+[ policy_anything ]
+countryName		= optional
+stateOrProvinceName	= optional
+localityName		= optional
+organizationName	= optional
+organizationalUnitName	= optional
+commonName		= supplied
+emailAddress		= optional
+
+[v3_req]
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+subjectAltName = @alt_names
+
+[alt_names]
+DNS.1 = *.test.google.fr
+DNS.2 = waterzooi.test.google.be
+DNS.3 = *.test.youtube.com
+IP.1 = "192.168.1.3"
diff --git a/testing/src/main/resources/certs/server1.key b/testing/src/main/resources/certs/server1.key
new file mode 100644
index 0000000..143a5b8
--- /dev/null
+++ b/testing/src/main/resources/certs/server1.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD
+M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf
+3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY
+AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm
+V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY
+tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p
+dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q
+K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR
+81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff
+DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd
+aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2
+ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3
+XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe
+F98XJ7tIFfJq
+-----END PRIVATE KEY-----
diff --git a/testing/src/main/resources/certs/server1.pem b/testing/src/main/resources/certs/server1.pem
new file mode 100644
index 0000000..f3d43fc
--- /dev/null
+++ b/testing/src/main/resources/certs/server1.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET
+MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx
+MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
+BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50
+ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco
+LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg
+zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd
+9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw
+CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy
+em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G
+CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6
+hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh
+y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8
+-----END CERTIFICATE-----
diff --git a/testing/src/test/java/io/grpc/internal/testing/TestUtilsTest.java b/testing/src/test/java/io/grpc/internal/testing/TestUtilsTest.java
new file mode 100644
index 0000000..266cb7e
--- /dev/null
+++ b/testing/src/test/java/io/grpc/internal/testing/TestUtilsTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.internal.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@link io.grpc.internal.testing.TestUtils}.
+ */
+@RunWith(JUnit4.class)
+public class TestUtilsTest {
+  @Test
+  public void sleepAtLeast() throws Exception {
+    long sleepMilis = 10L;
+
+    long start = System.nanoTime();
+    TestUtils.sleepAtLeast(sleepMilis);
+    long end = System.nanoTime();
+
+    assertThat(end - start).isAtLeast(TimeUnit.MILLISECONDS.toNanos(sleepMilis));
+  }
+}
diff --git a/testing/src/test/java/io/grpc/testing/GrpcCleanupRuleTest.java b/testing/src/test/java/io/grpc/testing/GrpcCleanupRuleTest.java
new file mode 100644
index 0000000..06d41a2
--- /dev/null
+++ b/testing/src/test/java/io/grpc/testing/GrpcCleanupRuleTest.java
@@ -0,0 +1,447 @@
+/*
+ * Copyright 2018 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalAnswers.delegatesTo;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.internal.FakeClock;
+import io.grpc.testing.GrpcCleanupRule.Resource;
+import java.util.concurrent.TimeUnit;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.model.MultipleFailureException;
+import org.junit.runners.model.Statement;
+import org.mockito.InOrder;
+
+/**
+ * Unit tests for {@link GrpcCleanupRule}.
+ */
+@RunWith(JUnit4.class)
+public class GrpcCleanupRuleTest {
+  public static final FakeClock fakeClock = new FakeClock();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void registerChannelReturnSameChannel() {
+    ManagedChannel channel = mock(ManagedChannel.class);
+    assertSame(channel, new GrpcCleanupRule().register(channel));
+  }
+
+  @Test
+  public void registerServerReturnSameServer() {
+    Server server = mock(Server.class);
+    assertSame(server, new GrpcCleanupRule().register(server));
+  }
+
+  @Test
+  public void registerNullChannelThrowsNpe() {
+    ManagedChannel channel = null;
+    GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("channel");
+
+    grpcCleanup.register(channel);
+  }
+
+  @Test
+  public void registerNullServerThrowsNpe() {
+    Server server = null;
+    GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+    thrown.expect(NullPointerException.class);
+    thrown.expectMessage("server");
+
+    grpcCleanup.register(server);
+  }
+
+  @Test
+  public void singleChannelCleanup() throws Throwable {
+    // setup
+    ManagedChannel channel = mock(ManagedChannel.class);
+    Statement statement = mock(Statement.class);
+    InOrder inOrder = inOrder(statement, channel);
+    GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+    // run
+    grpcCleanup.register(channel);
+
+    boolean awaitTerminationFailed = false;
+    try {
+      // will throw because channel.awaitTermination(long, TimeUnit) will return false;
+      grpcCleanup.apply(statement, null /* description*/).evaluate();
+    } catch (AssertionError e) {
+      awaitTerminationFailed = true;
+    }
+
+    // verify
+    assertTrue(awaitTerminationFailed);
+    inOrder.verify(statement).evaluate();
+    inOrder.verify(channel).shutdown();
+    inOrder.verify(channel).awaitTermination(anyLong(), any(TimeUnit.class));
+    inOrder.verify(channel).shutdownNow();
+  }
+
+  @Test
+  public void singleServerCleanup() throws Throwable {
+    // setup
+    Server server = mock(Server.class);
+    Statement statement = mock(Statement.class);
+    InOrder inOrder = inOrder(statement, server);
+    GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+    // run
+    grpcCleanup.register(server);
+
+    boolean awaitTerminationFailed = false;
+    try {
+      // will throw because channel.awaitTermination(long, TimeUnit) will return false;
+      grpcCleanup.apply(statement, null /* description*/).evaluate();
+    } catch (AssertionError e) {
+      awaitTerminationFailed = true;
+    }
+
+    // verify
+    assertTrue(awaitTerminationFailed);
+    inOrder.verify(statement).evaluate();
+    inOrder.verify(server).shutdown();
+    inOrder.verify(server).awaitTermination(anyLong(), any(TimeUnit.class));
+    inOrder.verify(server).shutdownNow();
+  }
+
+  @Test
+  public void multiResource_cleanupGracefully() throws Throwable {
+    // setup
+    Resource resource1 = mock(Resource.class);
+    Resource resource2 = mock(Resource.class);
+    Resource resource3 = mock(Resource.class);
+    doReturn(true).when(resource1).awaitReleased(anyLong(), any(TimeUnit.class));
+    doReturn(true).when(resource2).awaitReleased(anyLong(), any(TimeUnit.class));
+    doReturn(true).when(resource3).awaitReleased(anyLong(), any(TimeUnit.class));
+
+    Statement statement = mock(Statement.class);
+    InOrder inOrder = inOrder(statement, resource1, resource2, resource3);
+    GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+    // run
+    grpcCleanup.register(resource1);
+    grpcCleanup.register(resource2);
+    grpcCleanup.register(resource3);
+    grpcCleanup.apply(statement, null /* description*/).evaluate();
+
+    // Verify.
+    inOrder.verify(statement).evaluate();
+
+    inOrder.verify(resource3).cleanUp();
+    inOrder.verify(resource2).cleanUp();
+    inOrder.verify(resource1).cleanUp();
+
+    inOrder.verify(resource3).awaitReleased(anyLong(), any(TimeUnit.class));
+    inOrder.verify(resource2).awaitReleased(anyLong(), any(TimeUnit.class));
+    inOrder.verify(resource1).awaitReleased(anyLong(), any(TimeUnit.class));
+
+    inOrder.verifyNoMoreInteractions();
+
+    verify(resource1, never()).forceCleanUp();
+    verify(resource2, never()).forceCleanUp();
+    verify(resource3, never()).forceCleanUp();
+  }
+
+  @Test
+  public void baseTestFails() throws Throwable {
+    // setup
+    Resource resource = mock(Resource.class);
+
+    Statement statement = mock(Statement.class);
+    doThrow(new Exception()).when(statement).evaluate();
+
+    GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+    // run
+    grpcCleanup.register(resource);
+
+    boolean baseTestFailed = false;
+    try {
+      grpcCleanup.apply(statement, null /* description*/).evaluate();
+    } catch (Exception e) {
+      baseTestFailed = true;
+    }
+
+    // verify
+    assertTrue(baseTestFailed);
+
+    verify(resource).forceCleanUp();
+    verifyNoMoreInteractions(resource);
+
+    verify(resource, never()).cleanUp();
+    verify(resource, never()).awaitReleased(anyLong(), any(TimeUnit.class));
+  }
+
+  @Test
+  public void multiResource_awaitReleasedFails() throws Throwable {
+    // setup
+    Resource resource1 = mock(Resource.class);
+    Resource resource2 = mock(Resource.class);
+    Resource resource3 = mock(Resource.class);
+    doReturn(true).when(resource1).awaitReleased(anyLong(), any(TimeUnit.class));
+    doReturn(false).when(resource2).awaitReleased(anyLong(), any(TimeUnit.class));
+    doReturn(true).when(resource3).awaitReleased(anyLong(), any(TimeUnit.class));
+
+    Statement statement = mock(Statement.class);
+    InOrder inOrder = inOrder(statement, resource1, resource2, resource3);
+    GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+    // run
+    grpcCleanup.register(resource1);
+    grpcCleanup.register(resource2);
+    grpcCleanup.register(resource3);
+
+    boolean cleanupFailed = false;
+    try {
+      grpcCleanup.apply(statement, null /* description*/).evaluate();
+    } catch (AssertionError e) {
+      cleanupFailed = true;
+    }
+
+    // verify
+    assertTrue(cleanupFailed);
+
+    inOrder.verify(statement).evaluate();
+
+    inOrder.verify(resource3).cleanUp();
+    inOrder.verify(resource2).cleanUp();
+    inOrder.verify(resource1).cleanUp();
+
+    inOrder.verify(resource3).awaitReleased(anyLong(), any(TimeUnit.class));
+    inOrder.verify(resource2).awaitReleased(anyLong(), any(TimeUnit.class));
+    inOrder.verify(resource2).forceCleanUp();
+    inOrder.verify(resource1).forceCleanUp();
+
+    inOrder.verifyNoMoreInteractions();
+
+    verify(resource3, never()).forceCleanUp();
+    verify(resource1, never()).awaitReleased(anyLong(), any(TimeUnit.class));
+  }
+
+  @Test
+  public void multiResource_awaitReleasedInterrupted() throws Throwable {
+    // setup
+    Resource resource1 = mock(Resource.class);
+    Resource resource2 = mock(Resource.class);
+    Resource resource3 = mock(Resource.class);
+    doReturn(true).when(resource1).awaitReleased(anyLong(), any(TimeUnit.class));
+    doThrow(new InterruptedException())
+        .when(resource2).awaitReleased(anyLong(), any(TimeUnit.class));
+    doReturn(true).when(resource3).awaitReleased(anyLong(), any(TimeUnit.class));
+
+    Statement statement = mock(Statement.class);
+    InOrder inOrder = inOrder(statement, resource1, resource2, resource3);
+    GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+    // run
+    grpcCleanup.register(resource1);
+    grpcCleanup.register(resource2);
+    grpcCleanup.register(resource3);
+
+    boolean cleanupFailed = false;
+    try {
+      grpcCleanup.apply(statement, null /* description*/).evaluate();
+    } catch (InterruptedException e) {
+      cleanupFailed = true;
+    }
+
+    // verify
+    assertTrue(cleanupFailed);
+    assertTrue(Thread.interrupted());
+
+    inOrder.verify(statement).evaluate();
+
+    inOrder.verify(resource3).cleanUp();
+    inOrder.verify(resource2).cleanUp();
+    inOrder.verify(resource1).cleanUp();
+
+    inOrder.verify(resource3).awaitReleased(anyLong(), any(TimeUnit.class));
+    inOrder.verify(resource2).awaitReleased(anyLong(), any(TimeUnit.class));
+    inOrder.verify(resource2).forceCleanUp();
+    inOrder.verify(resource1).forceCleanUp();
+
+    inOrder.verifyNoMoreInteractions();
+
+    verify(resource3, never()).forceCleanUp();
+    verify(resource1, never()).awaitReleased(anyLong(), any(TimeUnit.class));
+  }
+
+  @Test
+  public void multiResource_timeoutCalculation() throws Throwable {
+    // setup
+
+    Resource resource1 = mock(FakeResource.class,
+        delegatesTo(new FakeResource(1 /* cleanupNanos */, 10 /* awaitReleaseNanos */)));
+
+    Resource resource2 = mock(FakeResource.class,
+        delegatesTo(new FakeResource(100 /* cleanupNanos */, 1000 /* awaitReleaseNanos */)));
+
+
+    Statement statement = mock(Statement.class);
+    InOrder inOrder = inOrder(statement, resource1, resource2);
+    GrpcCleanupRule grpcCleanup = new GrpcCleanupRule().setTicker(fakeClock.getTicker());
+
+    // run
+    grpcCleanup.register(resource1);
+    grpcCleanup.register(resource2);
+    grpcCleanup.apply(statement, null /* description*/).evaluate();
+
+    // verify
+    inOrder.verify(statement).evaluate();
+
+    inOrder.verify(resource2).cleanUp();
+    inOrder.verify(resource1).cleanUp();
+
+    inOrder.verify(resource2).awaitReleased(
+        TimeUnit.SECONDS.toNanos(10) - 100 - 1, TimeUnit.NANOSECONDS);
+    inOrder.verify(resource1).awaitReleased(
+        TimeUnit.SECONDS.toNanos(10) - 100 - 1 - 1000, TimeUnit.NANOSECONDS);
+
+    inOrder.verifyNoMoreInteractions();
+
+    verify(resource2, never()).forceCleanUp();
+    verify(resource1, never()).forceCleanUp();
+  }
+
+  @Test
+  public void multiResource_timeoutCalculation_customTimeout() throws Throwable {
+    // setup
+
+    Resource resource1 = mock(FakeResource.class,
+        delegatesTo(new FakeResource(1 /* cleanupNanos */, 10 /* awaitReleaseNanos */)));
+
+    Resource resource2 = mock(FakeResource.class,
+        delegatesTo(new FakeResource(100 /* cleanupNanos */, 1000 /* awaitReleaseNanos */)));
+
+
+    Statement statement = mock(Statement.class);
+    InOrder inOrder = inOrder(statement, resource1, resource2);
+    GrpcCleanupRule grpcCleanup = new GrpcCleanupRule()
+        .setTicker(fakeClock.getTicker()).setTimeout(3000, TimeUnit.NANOSECONDS);
+
+    // run
+    grpcCleanup.register(resource1);
+    grpcCleanup.register(resource2);
+    grpcCleanup.apply(statement, null /* description*/).evaluate();
+
+    // verify
+    inOrder.verify(statement).evaluate();
+
+    inOrder.verify(resource2).cleanUp();
+    inOrder.verify(resource1).cleanUp();
+
+    inOrder.verify(resource2).awaitReleased(3000 - 100 - 1, TimeUnit.NANOSECONDS);
+    inOrder.verify(resource1).awaitReleased(3000 - 100 - 1 - 1000, TimeUnit.NANOSECONDS);
+
+    inOrder.verifyNoMoreInteractions();
+
+    verify(resource2, never()).forceCleanUp();
+    verify(resource1, never()).forceCleanUp();
+  }
+
+  @Test
+  public void baseTestFailsThenCleanupFails() throws Throwable {
+    // setup
+    Exception baseTestFailure = new Exception();
+
+    Statement statement = mock(Statement.class);
+    doThrow(baseTestFailure).when(statement).evaluate();
+
+    Resource resource1 = mock(Resource.class);
+    Resource resource2 = mock(Resource.class);
+    Resource resource3 = mock(Resource.class);
+    doThrow(new RuntimeException()).when(resource2).forceCleanUp();
+
+    InOrder inOrder = inOrder(statement, resource1, resource2, resource3);
+    GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+    // run
+    grpcCleanup.register(resource1);
+    grpcCleanup.register(resource2);
+    grpcCleanup.register(resource3);
+
+    Throwable failure = null;
+    try {
+      grpcCleanup.apply(statement, null /* description*/).evaluate();
+    } catch (Throwable e) {
+      failure = e;
+    }
+
+    // verify
+    assertThat(failure).isInstanceOf(MultipleFailureException.class);
+    assertSame(baseTestFailure, ((MultipleFailureException) failure).getFailures().get(0));
+
+    inOrder.verify(statement).evaluate();
+    inOrder.verify(resource3).forceCleanUp();
+    inOrder.verify(resource2).forceCleanUp();
+    inOrder.verifyNoMoreInteractions();
+
+    verify(resource1, never()).cleanUp();
+    verify(resource2, never()).cleanUp();
+    verify(resource3, never()).cleanUp();
+    verify(resource1, never()).forceCleanUp();
+  }
+
+  public static class FakeResource implements Resource {
+    private final long cleanupNanos;
+    private final long awaitReleaseNanos;
+
+    private FakeResource(long cleanupNanos, long awaitReleaseNanos) {
+      this.cleanupNanos = cleanupNanos;
+      this.awaitReleaseNanos = awaitReleaseNanos;
+    }
+
+    @Override
+    public void cleanUp() {
+      fakeClock.forwardTime(cleanupNanos, TimeUnit.NANOSECONDS);
+    }
+
+    @Override
+    public void forceCleanUp() {
+    }
+
+    @Override
+    public boolean awaitReleased(long duration, TimeUnit timeUnit) {
+      fakeClock.forwardTime(awaitReleaseNanos, TimeUnit.NANOSECONDS);
+      return true;
+    }
+  }
+}
diff --git a/testing/src/test/java/io/grpc/testing/GrpcServerRuleTest.java b/testing/src/test/java/io/grpc/testing/GrpcServerRuleTest.java
new file mode 100644
index 0000000..469aa26
--- /dev/null
+++ b/testing/src/test/java/io/grpc/testing/GrpcServerRuleTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2016 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.protobuf.SimpleRequest;
+import io.grpc.testing.protobuf.SimpleResponse;
+import io.grpc.testing.protobuf.SimpleServiceGrpc;
+import java.util.Collection;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.model.Statement;
+
+/** Unit tests for {@link GrpcServerRule}. */
+@RunWith(JUnit4.class)
+public class GrpcServerRuleTest {
+
+  @Rule public final GrpcServerRule grpcServerRule1 = new GrpcServerRule();
+  @Rule public final GrpcServerRule grpcServerRule2 = new GrpcServerRule().directExecutor();
+
+  @Test
+  public void serverAndChannelAreStarted_withoutDirectExecutor() {
+    assertThat(grpcServerRule1.getServer().isShutdown()).isFalse();
+    assertThat(grpcServerRule1.getServer().isTerminated()).isFalse();
+
+    assertThat(grpcServerRule1.getChannel().isShutdown()).isFalse();
+    assertThat(grpcServerRule1.getChannel().isTerminated()).isFalse();
+
+    assertThat(grpcServerRule1.getServerName()).isNotNull();
+    assertThat(grpcServerRule1.getServiceRegistry()).isNotNull();
+  }
+
+  @Test
+  public void serverAllowsServicesToBeAddedViaServiceRegistry_withoutDirectExecutor() {
+    TestServiceImpl testService = new TestServiceImpl();
+
+    grpcServerRule1.getServiceRegistry().addService(testService);
+
+    SimpleServiceGrpc.SimpleServiceBlockingStub stub =
+        SimpleServiceGrpc.newBlockingStub(grpcServerRule1.getChannel());
+
+    SimpleRequest request1 = SimpleRequest.getDefaultInstance();
+
+    SimpleRequest request2 = SimpleRequest.newBuilder().build();
+
+    stub.unaryRpc(request1);
+    stub.unaryRpc(request2);
+
+    assertThat(testService.unaryCallRequests).containsExactly(request1, request2);
+  }
+
+  @Test
+  public void serviceIsNotRunOnSameThreadAsTest_withoutDirectExecutor() {
+    TestServiceImpl testService = new TestServiceImpl();
+
+    grpcServerRule1.getServiceRegistry().addService(testService);
+
+    SimpleServiceGrpc.SimpleServiceBlockingStub stub =
+        SimpleServiceGrpc.newBlockingStub(grpcServerRule1.getChannel());
+
+    stub.serverStreamingRpc(SimpleRequest.getDefaultInstance());
+
+    assertThat(testService.lastServerStreamingRpcThread).isNotEqualTo(Thread.currentThread());
+  }
+
+  @Test(expected = IllegalStateException.class)
+  public void callDirectExecutorNotAtRuleInstantiation_withoutDirectExecutor() {
+    grpcServerRule1.directExecutor();
+  }
+
+  @Test
+  public void serverAndChannelAreStarted_withDirectExecutor() {
+    assertThat(grpcServerRule2.getServer().isShutdown()).isFalse();
+    assertThat(grpcServerRule2.getServer().isTerminated()).isFalse();
+
+    assertThat(grpcServerRule2.getChannel().isShutdown()).isFalse();
+    assertThat(grpcServerRule2.getChannel().isTerminated()).isFalse();
+
+    assertThat(grpcServerRule2.getServerName()).isNotNull();
+    assertThat(grpcServerRule2.getServiceRegistry()).isNotNull();
+  }
+
+  @Test
+  public void serverAllowsServicesToBeAddedViaServiceRegistry_withDirectExecutor() {
+    TestServiceImpl testService = new TestServiceImpl();
+
+    grpcServerRule2.getServiceRegistry().addService(testService);
+
+    SimpleServiceGrpc.SimpleServiceBlockingStub stub =
+        SimpleServiceGrpc.newBlockingStub(grpcServerRule2.getChannel());
+
+    SimpleRequest request1 = SimpleRequest.getDefaultInstance();
+
+    SimpleRequest request2 = SimpleRequest.newBuilder().build();
+
+    stub.unaryRpc(request1);
+    stub.unaryRpc(request2);
+
+    assertThat(testService.unaryCallRequests).containsExactly(request1, request2);
+  }
+
+  @Test
+  public void serviceIsRunOnSameThreadAsTest_withDirectExecutor() {
+    TestServiceImpl testService = new TestServiceImpl();
+
+    grpcServerRule2.getServiceRegistry().addService(testService);
+
+    SimpleServiceGrpc.SimpleServiceBlockingStub stub =
+        SimpleServiceGrpc.newBlockingStub(grpcServerRule2.getChannel());
+
+    stub.serverStreamingRpc(SimpleRequest.getDefaultInstance());
+
+    assertThat(testService.lastServerStreamingRpcThread).isEqualTo(Thread.currentThread());
+  }
+
+  @Test
+  public void serverAndChannelAreShutdownAfterRule() throws Throwable {
+    GrpcServerRule grpcServerRule = new GrpcServerRule();
+
+    // Before the rule has been executed, all of its resources should be null.
+    assertThat(grpcServerRule.getChannel()).isNull();
+    assertThat(grpcServerRule.getServer()).isNull();
+    assertThat(grpcServerRule.getServerName()).isNull();
+    assertThat(grpcServerRule.getServiceRegistry()).isNull();
+
+    // The TestStatement stores the channel and server instances so that we can inspect them after
+    // the rule cleans up.
+    TestStatement statement = new TestStatement(grpcServerRule);
+
+    grpcServerRule.apply(statement, null).evaluate();
+
+    // Ensure that the stored channel and server instances were shut down.
+    assertThat(statement.channel.isShutdown()).isTrue();
+    assertThat(statement.server.isShutdown()).isTrue();
+
+    // All references to the resources that we created should be set to null.
+    assertThat(grpcServerRule.getChannel()).isNull();
+    assertThat(grpcServerRule.getServer()).isNull();
+    assertThat(grpcServerRule.getServerName()).isNull();
+    assertThat(grpcServerRule.getServiceRegistry()).isNull();
+  }
+
+  private static class TestStatement extends Statement {
+
+    private final GrpcServerRule grpcServerRule;
+
+    private ManagedChannel channel;
+    private Server server;
+
+    private TestStatement(GrpcServerRule grpcServerRule) {
+      this.grpcServerRule = grpcServerRule;
+    }
+
+    @Override
+    public void evaluate() throws Throwable {
+      channel = grpcServerRule.getChannel();
+      server = grpcServerRule.getServer();
+    }
+  }
+
+  private static class TestServiceImpl extends SimpleServiceGrpc.SimpleServiceImplBase {
+
+    private final Collection<SimpleRequest> unaryCallRequests =
+        new ConcurrentLinkedQueue<SimpleRequest>();
+
+    private volatile Thread lastServerStreamingRpcThread;
+
+    @Override
+    public void serverStreamingRpc(
+        SimpleRequest request, StreamObserver<SimpleResponse> responseObserver) {
+
+      lastServerStreamingRpcThread = Thread.currentThread();
+
+      responseObserver.onNext(SimpleResponse.getDefaultInstance());
+
+      responseObserver.onCompleted();
+    }
+
+    @Override
+    public void unaryRpc(
+        SimpleRequest request, StreamObserver<SimpleResponse> responseObserver) {
+
+      unaryCallRequests.add(request);
+
+      responseObserver.onNext(SimpleResponse.getDefaultInstance());
+
+      responseObserver.onCompleted();
+    }
+  }
+}
diff --git a/testing/src/test/java/io/grpc/testing/TlsTestingTest.java b/testing/src/test/java/io/grpc/testing/TlsTestingTest.java
new file mode 100644
index 0000000..7ffc48f
--- /dev/null
+++ b/testing/src/test/java/io/grpc/testing/TlsTestingTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2017 The gRPC Authors
+ *
+ * Licensed 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.
+ */
+
+package io.grpc.testing;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.InputStream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link TlsTesting}. */
+@RunWith(JUnit4.class)
+public class TlsTestingTest {
+  @Test
+  public void loadCert() throws Exception {
+    InputStream is = null;
+    try {
+      is = TlsTesting.loadCert("ca.key");
+      assertNotNull(is);
+    } finally {
+      if (is != null) {
+        is.close();
+      }
+    }
+  }
+}